Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
The very core of miniKanren. So core that it basically deals with
unification only. For writing relational programs, you will need Goal
as
well.
Synopsis
- class Logical a where
- data VarId a
- data Term a
- newtype Atomic a = Atomic a
- unify' :: Logical a => Term a -> Term a -> State -> Maybe State
- walk' :: Logical a => State -> Term a -> Term a
- occursCheck' :: Logical a => VarId b -> Term a -> State -> Bool
- inject' :: Logical a => a -> Term a
- extract' :: Logical a => Term a -> Maybe a
- class Logical a => Normalizable a where
- normalize' :: Normalizable a => (forall i. VarId i -> Normalizer (VarId i)) -> Term a -> Normalizer (Term a)
- runNormalize :: Normalizable a => Term a -> Term a
- disequality :: Logical a => Term a -> Term a -> State -> Maybe State
- data State
- empty :: State
- makeVariable :: State -> (State, Term a)
Values and terms
class Logical a where Source #
Types that can enter the relational world.
Simple types without fields, such as Bool
and Int
, can be used in
relational programs as is. Instances for such types are as simple as
data Ternary = True | False | Maybe deriving (Eq) instance Logical Ternary
>>>
run (\x -> x === Value True)
[True]
When a type contains other types, this becomes more tricky. Consider the following type:
data Point = Point { x :: Int, y :: Int }
In the relational world, values may be known only partially. For example, we
may find out that some equation is true only for a particular value of x
,
but once that holds, y
can be anything. The definition above cannot express
this, since Point
has to be instantiated with some particular pair of
Int
s.
To account for this, we'd like to modify the definition slightly, so that each field can possibly contain a variable:
data LogicPoint = LogicPoint { logicX :: Term Int, logicY :: Term Int }
here can either be a Term
aVar
or a Value
for type a
.
Now we can specify that a Point
becomes a LogicPoint
in the relational
world:
instance Logical Point where type Logic Point = LogicPoint
However, we are not finished here yet. When a type has a different
representation in the logical world, we need to show how we can go from
a Point
to a LogicPoint
with inject
and go back with extract
:
inject (Point x y) = LogicPoint (Value x) (Value y) extract (LogicPoint (Value x) (Value y)) = Just (Point x y) extract _ = Nothing
Note that while we can always transform a Point
to a LogicPoint
, going
back to a Point
can fail if any field contains a variable.
We also need to show how LogicPoint
s can be unified. For simple types,
unification of terms works in the following way. If both terms are already
known, we just check that they are equal. Otherwise, one of the terms is a
variable, and we record that it must be equal to the other term.
With complex types, a third case is possible: we can refine an existing value if one of its fields is a variable. We can achieve this by unifying each field separately.
unify (LogicPoint leftX leftY) (LogicPoint rightX rightY) state = unify' leftX rightX state >>= unify' leftY rightY
unify
takes two values being unified and the current state. If unification
succeeds, a new state with acquired knowledge is returned. if unification
leads to contradiction (the two values cannot be unified), unify
returns
Nothing
. You do not modify the state yourself — this is handled by
unify'
, a version of unify
which works on Term
s instead of logic
values.
When we find out that a variable must have a particular value, we need not
only to add a new entry in the state, but also update existing values which
might contain that variable. This is the job of walk
, which takes the value
being updated and the current state. Just like with unify
, the actual job
of replacing variables with values is done by walk'
, and you only need to
apply it to each field.
walk f (LogicPoint x y) = LogicPoint (walk' f x) (walk' f x)
You may notice that the logical representation of the type and the Logical
instance are suitable for automatic generation. Indeed, the
GenericLogical
module provides generic versions of Logical
's methods.
The TH
module goes further and provides makeLogic
to generate
logical representations for your types.
Although you'll see instances for base
types below, keep in mind that
they're only available from the LogicalBase
module.
Nothing
type Logic a = r | r -> a Source #
The logical representation of this type. This defaults to the type itself, but complex types will usually have a separate logic type.
Note that 'Logic a' is injective, so two different types cannot use the
same type as their logical representation. If you want to provide an
instance for newtype NT = NT T
, then the logical representation should be
a newtype as well: newtype LogicNT = LogicNT (Logic T)
.
type Logic a = a
unify :: Logic a -> Logic a -> State -> Maybe State Source #
Perform unification of two values. If unification succeeds, return the
possibly modified state. If unification leads to contradiction, return
Nothing
.
The default implementation checks for equality, which works well for simple
types. Complex types will provide their own implmentations which apply
unify'
to each field.
walk :: State -> Logic a -> Logic a Source #
Update the value with acquired knowledge. This method the current state to substitute variables with their obtained values.
The default implementation works for simple types and returns the value as
is (since there's nothing to substitute inside). Complex types will provide
their own implementations which apply walk'
to each field.
occursCheck :: VarId b -> Logic a -> State -> Bool Source #
inject :: a -> Logic a Source #
Transform a value to its logical representation.
The default implementation works for simple types and returns the value as
is. Complex types will provide their own implementations which apply
inject'
to each field. inject'
is also the function that you will use
in your relational programs.
extract :: Logic a -> Maybe a Source #
Transform a logical representation of a value back to its normal representation. Note that this transformation can fail in the general case, because variables cannot be transformed to values.
The default implementation works for simple types and returns the value as
is. Complex types will provide their own implementations which apply
extract'
to each field. extract'
is also the function that you will
use in your code.
Instances
A variable, which reserves a place for a logical value for type a
.
A logical value for type a
, or a variable.
Note that a
must be the “normal” type, not its logical representation,
since Term
stores
. For example, Logic
aTerm (Either String (Tree
Int))
will correctly use LogicList Char
and LogicTree Int
deep inside.
This way, you do not need to know what the logic representation for a type is
named, and deriving the logical representation for a type is trivial.
Instances
Generic (Term a) Source # | |
IsList (Logic a) => IsList (Term a) Source # | |
Num (Logic a) => Num (Term a) Source # | |
Show (Logic a) => Show (Term a) Source # | This instance doesn't print the |
NFData (Logic a) => NFData (Term a) Source # | |
Defined in Kanren.Core | |
Eq (Logic a) => Eq (Term a) Source # | |
Logical a => Fresh (Term a) Source # | |
Logical c => GLogical (K1 i c :: Type -> Type) (K1 i' (Term c) :: Type -> Type) Source # | |
Defined in Kanren.GenericLogical | |
type Rep (Term a) Source # | |
Defined in Kanren.Core type Rep (Term a) = D1 ('MetaData "Term" "Kanren.Core" "typedKanren-0.1.0.0-DCPxkt07imEEe1U3d5R120" 'False) (C1 ('MetaCons "Var" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (VarId a))) :+: C1 ('MetaCons "Value" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (Logic a)))) | |
type Item (Term a) Source # | |
Defined in Kanren.Core |
Treat a type as atomic, i.e. containing no variables inside. This requires
a
only to have an Eq
instance, thus ignoring its logical representation
if it exists. Useful when you really don't want to look inside something.
type Symbol = Atomic String
Atomic a |
Instances
Show a => Show (Atomic a) Source # | |
NFData a => NFData (Atomic a) Source # | |
Defined in Kanren.Core | |
Eq a => Eq (Atomic a) Source # | |
Eq a => Logical (Atomic a) Source # | |
Eq a => Normalizable (Atomic a) Source # | |
type Logic (Atomic a) Source # | |
Defined in Kanren.Core |
Operations on terms
extract' :: Logical a => Term a -> Maybe a Source #
extract
, but from a Term
instead of a Logic
value. Note that this
transformation can fail in the general case, because variables cannot be
transformed to values.
You will use this function to transform solutions of a program back to their normal representation.
>>>
extract' <$> run (\x -> x === inject' (Left 42 :: Either Int Bool))
[Just (Left 42)]
class Logical a => Normalizable a where Source #
Nothing
normalize :: (forall i. VarId i -> Normalizer (VarId i)) -> Logic a -> Normalizer (Logic a) Source #
Instances
Normalizable SExpr Source # | |
Eq a => Normalizable (Atomic a) Source # | |
normalize' :: Normalizable a => (forall i. VarId i -> Normalizer (VarId i)) -> Term a -> Normalizer (Term a) Source #
runNormalize :: Normalizable a => Term a -> Term a Source #
Constraints
disequality :: Logical a => Term a -> Term a -> State -> Maybe State Source #
Add a constraint that two terms must not be equal.
The search state
One branch in the search tree. Keeps track of known substitutions and variables.