The Reasoned Schemer (2nd Edition) is a book by Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann that shows the beauty and elegance of relational programming. Relational programming, as presented in the book, captures the essence of logic programming.
This package shows how to implement and embed their relational programming language, miniKanren, using Elm.
William E. Byrd has decribed appendo as the "Hello, World!" of the relational programming. (video)
So, here it is:
import Logic exposing (..)
appendo : Value a -> Value a -> Value a -> Goal a
appendo l t out =
conde
[ [ nullo l, equals t out ]
, [ fresh3
(\a d res ->
conj
[ conso a d l
, conso a res out
, lazy (\_ -> appendo d t res)
]
)
]
]
nullo : Value a -> Goal a
nullo x =
equals null x
conso : Value a -> Value a -> Value a -> Goal a
conso a d p =
conj
[ caro p a
, cdro p d
]
caro : Value a -> Value a -> Goal a
caro p a =
fresh
(\d ->
equals (cons a d) p
)
cdro : Value a -> Value a -> Goal a
cdro p d =
fresh
(\a ->
equals (cons a d) p
)And, this is how it works:
let
actual =
toString <| run3AtMost 6 appendo
expected =
String.join " "
[ "((() _0 _0)"
, "((_0) _1 (_0 . _1))"
, "((_0 _1) _2 (_0 _1 . _2))"
, "((_0 _1 _2) _3 (_0 _1 _2 . _3))"
, "((_0 _1 _2 _3) _4 (_0 _1 _2 _3 . _4))"
, "((_0 _1 _2 _3 _4) _5 (_0 _1 _2 _3 _4 . _5)))"
]
in
actual == expected
-- True : BoolFor many more examples check out the source code for the Book.* modules as well as the extensive suite of tests.
The public API is exposed via the Logic module.
N.B. This section only presents a very high-level overview. Please see the documentation for more details.
The values (or terms) that can be associated with (logic) variables.
type Value aConstants:
int
float
bool
char
string
customPairs, lists, and dotted lists:
null -- '()
cons -- (cons 'a 'b)
list -- '(a b c)
dottedList -- '(a b . c)To deconstruct constants you can use:
toConstant : Value a -> Constant a
type Constant a
= Int Int
| Float Float
| Bool Bool
| Char Char
| String String
| Custom aTo deconstruct pairs you can use:
car
cdr
unconsGoals (or relations) are functions which more or less take a substitution and, if it returns, produces a stream of substitutions.
type Goal asucceed -- #s
fail -- #u
always
neverequals -- ≡
disj2
disj
conj2
conj
condelazyfresh
fresh2
fresh3
fresh4
fresh5
fresh6
fresh7
fresh8once
conda
condurun
run2
run3
run4
run5
run6
run7
run8
runAtMost
run2AtMost
run3AtMost
run4AtMost
run5AtMost
run6AtMost
run7AtMost
run8AtMosttoString : List (Value String) -> Stringtype Presenter a
= Default (a -> String)
| Alternative (Constant a -> String)
customToString : Presenter a -> List (Value a) -> StringThe Logic.* modules provide the low-level API upon which the Logic module is built. It is based on my understanding of "Chapter 10: Under the Hood" and "Connecting the Wires" from the book.
If you carefully read the source code of the modules from top to bottom in the following order:
Logic.VariableLogic.ValueLogic.StreamLogic.SubstitutionLogic.Goal
You should get a good sense of how it all works.
Enter the development environment using:
devbox shellAliases are provided for running common tasks:
f # Format Elm code
p # Preview docs
t # Run tests