I have a question about recursive data structures in Haskell (language that I'm currently trying to learn).
I would like to encode in Haskell Prolog-like terms, but every solution I came up with has different drawbacks that I would really like to avoid. I would like to find a cheap and elegant way of encoding a BNF grammar in Haskell types, if you wish to see my problem from this perspective.
Just as a reminder, some prolog terms could be male
, sum(2, 3.1, 5.1)
, btree(btree(0, 1), Variable)
.
data Term = SConst String
| IConst Integer
| FConst Double
| Var String
| Predicate {predName :: String, predArgs :: [Term]}
With this solution I can have nested predicates (since predArgs
are Term
), but I can't distinguish predicates from other terms in type signatures.
data Term = SConst String
| IConst Integer
| FConst Double
| Var String
data Predicate = Predicate {predName :: String, predArgs ::[Either Term Predicate}
In this variant I can clearly distinguish predicates from basic terms, but the Either
type in the predArgs
list can be quite a nuisance to manage later in the code (I think... I'm new to Haskell).
data Term = SConst String
| IConst Integer
| FConst Double
| Var String
| Struct String [Term]
data Predicate = Predicate String [Term]
With this last solution, I split terms in two different types as before, but this time I avoid Either Term Predicate
adding a Struct
constructor in Term
with basically the same semantics as Predicate
.
It's just like solution 1 with two predicate constructors for terms. One is recursion-enabled, Struct
, and the other one, Predicate
is to be able to distinguish between predicates and regular terms.
The problem with this try is that Struct
and Predicate
are structurally equivalent and have almost the same meaning, but I will not be able to write functions that works - in example - both on (Predicate "p" [])
and (Struct "p" [])
.
So again my question is: please, is there a better way to encode my predicates and terms such that:
p(q(1), r(q(3), q(4)))
are supported;Please feel free to ask me for further clarifications should you need any.
Thank you very much.
You could add a term constructor to wrap a predicate. Here, I also factored all of the literals into their own data type:
data Term = TLit Literal
| TVar String
| TPred Predicate
data Literal = LitS String
| LitI Int
| LitF Double
data Predicate = Predicate String [Term]
Here's one way (that's probably not worth the trouble):
{-# LANGUAGE EmptyDataDecls #-}
-- 'T' and 'F' are short for 'True' and 'False'
data T = T
data F
-- 'p' is short for 'mayNotBeAPredicate'
data Term p
= SConst !p String
| IConst !p Integer
| FConst !p Double
| Var !p String
| Predicate {predName :: String, predArgs :: [Term T]}
sconst :: String -> Term T
iconst :: Integer -> Term T
fconst :: Double -> Term T
var :: String -> Term T
predicate :: String -> [Term T] -> Term p
sconst = SConst T
iconst = IConst T
fconst = FConst T
var = Var T
predicate = Predicate
checkPredicate :: Term p -> Maybe (Term F)
checkPredicate (Predicate name args) = Just (Predicate name args)
checkPredicate _ = Nothing
forgetPredicate :: Term p -> Term T
forgetPredicate (SConst _ s) = sconst s
forgetPredicate (IConst _ i) = iconst i
forgetPredicate (FConst _ f) = fconst f
forgetPredicate (Var _ s) = var s
forgetPredicate (Predicate name args) = predicate name args
You can now write functions which only accept predicates by giving them an input type of Term F
, and functions which accept any input type by giving them an input type of Term p
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With