initial
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist-newstyle/
|
63
README.md
Normal file
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# infer
|
||||
|
||||
is a simple inference engine. It’s inspired by [pgergis’ little
|
||||
library](https://github.com/pgergis/haskell-inferences). I built my version
|
||||
based on the initial description and realized I ended up with a super different
|
||||
internal solution and decided it was interesting enough to be kept around.
|
||||
|
||||
For a problem description read the README of pgergis’ repo.
|
||||
|
||||
## Example Usage
|
||||
|
||||
Using the human/mortal syllogism we end up with the following interaction:
|
||||
|
||||
```
|
||||
$ infer '[{"premises": ["mortal", "alive"], "conclusion": "will die"}, {"premises": ["human"], "conclusion": "mortal"}]' human alive
|
||||
Inferred the following statements: alive, human, mortal, will die
|
||||
```
|
||||
|
||||
That seems right!
|
||||
|
||||
As you can see, usage is a little different. Firstly, the premisses are not a
|
||||
list but instead use the rest of the arguments after the JSON-formatted rules
|
||||
list. Secondly we pretty-print the results a little.
|
||||
|
||||
## How does it work?
|
||||
|
||||
First we parse the JSON rules. Then we run `infer`, which will try and match
|
||||
the known premises against all the premises of the rules. If we find a match
|
||||
we insert it into the new known premises and discard the rule, since we know
|
||||
we don’t have to apply it anymore. We do this until trying to apply all the
|
||||
rules does not yield any new results.
|
||||
|
||||
The code I’ve written is fairly terse, so here’s an annotated version of
|
||||
`infer`:
|
||||
|
||||
```haskell
|
||||
-- pre are the known premises, env is the rule environment
|
||||
infer :: S.Set String -> [Rule] -> S.Set String
|
||||
infer pre env =
|
||||
-- find will get us a tuple of found premises and the new set of rules
|
||||
let (found, nenv) = find
|
||||
-- if the new premises are nothing new we’re done
|
||||
in if S.isSubsetOf found pre
|
||||
then pre
|
||||
-- otherwise we do it all over again with our new premises and env
|
||||
else infer (S.union found pre) nenv
|
||||
-- find goes over the environment, building a tuple of premises and
|
||||
-- still applicable rules
|
||||
where find = foldr mlook (S.empty, []) env
|
||||
-- mlook gets a rule, the new found premises, and the new env
|
||||
mlook r@Rule{premises, conclusion} (f, e) =
|
||||
if S.isSubsetOf premises pre
|
||||
-- if the premise matches, we add the conclusion and ignore the
|
||||
-- rule, since we know it’s been applied
|
||||
then (S.insert conclusion f, e)
|
||||
-- otherwise we ignore the conclusion and add the rule for later
|
||||
-- retesting
|
||||
else (f, r:e)
|
||||
```
|
||||
|
||||
<hr/>
|
||||
|
||||
Have fun!
|
27
infer.cabal
Normal file
27
infer.cabal
Normal file
@@ -0,0 +1,27 @@
|
||||
cabal-version: >=1.10
|
||||
|
||||
name: infer
|
||||
version: 0.1.0.0
|
||||
license-file: LICENSE
|
||||
author: hellerve
|
||||
maintainer: veit@veitheller.de
|
||||
build-type: Simple
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
executable infer
|
||||
main-is: App.hs
|
||||
build-depends: base >=4.12.0.0 && <4.14.0.0
|
||||
, infer-lib
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
||||
library infer-lib
|
||||
ghc-options: -Wall
|
||||
exposed-modules: Infer.Treat
|
||||
build-depends: base >=4.12.0.0 && <4.14.0.0
|
||||
, aeson
|
||||
, containers
|
||||
, utf8-string
|
||||
hs-source-dirs: lib
|
||||
default-language: Haskell2010
|
||||
|
33
lib/Infer/Treat.hs
Normal file
33
lib/Infer/Treat.hs
Normal file
@@ -0,0 +1,33 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
module Infer.Treat where
|
||||
|
||||
import Data.Aeson (ToJSON, FromJSON, eitherDecode)
|
||||
import Data.ByteString.Lazy.UTF8 (fromString)
|
||||
import GHC.Generics (Generic)
|
||||
import qualified Data.Set as S
|
||||
|
||||
data Rule = Rule {
|
||||
premises :: S.Set String,
|
||||
conclusion :: String
|
||||
}
|
||||
deriving (Eq, Ord, Show, Generic)
|
||||
|
||||
instance ToJSON Rule
|
||||
instance FromJSON Rule
|
||||
|
||||
run :: String -> [String] -> Either String [String]
|
||||
run input pre = fmap f (eitherDecode $ fromString input)
|
||||
where f = S.toList . infer (S.fromList pre)
|
||||
|
||||
infer :: S.Set String -> [Rule] -> S.Set String
|
||||
infer pre env =
|
||||
let (found, nenv) = find
|
||||
in if S.isSubsetOf found pre
|
||||
then pre
|
||||
else infer (S.union found pre) nenv
|
||||
where find = foldr mlook (S.empty, []) env
|
||||
mlook r@Rule{premises, conclusion} (f, e) =
|
||||
if S.isSubsetOf premises pre
|
||||
then (S.insert conclusion f, e)
|
||||
else (f, r:e)
|
19
src/App.hs
Normal file
19
src/App.hs
Normal file
@@ -0,0 +1,19 @@
|
||||
module Main where
|
||||
|
||||
import Data.List (intercalate)
|
||||
import System.Environment (getArgs)
|
||||
import System.IO (hPutStrLn, stderr)
|
||||
|
||||
import Infer.Treat (run)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
if length args < 2
|
||||
then errln "Expected at least two arguments."
|
||||
else
|
||||
case run (head args) (tail args) of
|
||||
Left e -> errln e
|
||||
Right v ->
|
||||
putStrLn $ "Inferred the following statements: " ++ (intercalate ", " v)
|
||||
where errln = hPutStrLn stderr
|
Reference in New Issue
Block a user