Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive data structures in haskell: prolog-like terms

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).

Solution 1

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.

Solution 2

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).

Solution 3

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:

  1. I'm able to distinguish between predicate and terms in type signatures;
  2. nested predicates like p(q(1), r(q(3), q(4))) are supported;
  3. I can write functions that will work uniformly on predicates, without any distinction like the one in solution #3?

Please feel free to ask me for further clarifications should you need any.

Thank you very much.

like image 744
Riccardo T. Avatar asked Jan 19 '23 15:01

Riccardo T.


2 Answers

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]
like image 196
pat Avatar answered Feb 01 '23 07:02

pat


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.

like image 31
Daniel Wagner Avatar answered Feb 01 '23 09:02

Daniel Wagner