Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typed expression parser

Tags:

haskell

I'm trying to create a typed expression parser in Haskell, which works great so far, but I'm currently struggling to implement higher order functions. I've boiled the problem down to a simple example:

{-# LANGUAGE TypeFamilies,GADTs,FlexibleContexts,RankNTypes #-}

-- A function has an argument type and a result type
class Fun f where
  type FunArg f
  type FunRes f

-- Expressions are either constants of function applications
data Expr a where
  Const :: a -> Expr a
  App :: Fun f => f -> FunArg f -> Expr (FunRes f)

-- A very simple function
data Plus = Plus

-- Which takes two integer expressions and returns an integer expression
instance Fun Plus where
  type FunArg Plus = (Expr Int,Expr Int)
  type FunRes Plus = Int

-- A more complicated function which lifts a function to lists (like in haskell)
data Map f r = Map f

-- For this we need the concept of lifting function arguments:
class Liftable a where
  type LiftRes a

-- A singleton argument is lifted by changing the expression type from a to [a]
instance Liftable (Expr a) where
  type LiftRes (Expr a) = Expr [a]

-- Two function arguments are lifted by lifting each argument
instance (Liftable a,Liftable b) => Liftable (a,b)  where
  type LiftRes (a,b) = (LiftRes a,LiftRes b)

-- Now we can declare a function instance for Map
instance (Fun f,Liftable (FunArg f),r ~ LiftRes (FunArg f)) => Fun (Map f r) where
  type FunArg (Map f r) = r
  type FunRes (Map f r) = [FunRes f]

-- Now a parser for functions:
parseFun :: [String] -> (forall f. Fun f => f -> a) -> a
-- The parser for the plus function is easy:
parseFun ["plus"] f = f Plus
-- But the parser for map is not possible:
parseFun ("map":sym) f 
  = parseFun sym (\fun -> f (Map fun))

The problem seems to be that there is no way to convince the type checker that every LiftRes is itself Liftable, because recursive class declarations are forbidden.

My question is: How do I make this work? Are there other examples of typed expression parsers from which I could take hints?

EDIT: It seems that this discussion about type family constraints seems to be very related. However, I fail to make their solution work in my case, maybe someone can help with that?

like image 605
henning Avatar asked Feb 26 '13 09:02

henning


People also ask

What is the use of expressionparser lookup?

new ExpressionParser (lookup?: EvaluatorLookup) (xref:adaptive-expressions.EvaluatorLookup) for information from type string. The delegate to lookup function information from the type. Parse the input into an expression. Expression to parse. Expression tree.

How do you write a lexical parser?

The first stage to writing a text parser is to be able to identify what it is you are reading. There are many different methods for this, but the easiest is to use lex, a tool that converts input information into a series of tokens. What is lexical analysis?

How does a stack parser work in JavaScript?

When the parser identifies a number, you push it onto a stack, and when you see an operator, you pop the values off the stack and perform the calculation. It simplifies a lot of the processing and complex parsing that you had to go through above to perform normal expression parsing.

What happens if a parser method fails?

If one of the parser methods fails, it will signal failure by returning nullptr instead of a pointer to an expression tree. The parser class also has the ability to backtrack out of failed parses. Some grammar rules require us to find several structures in a sequence.


Video Answer


1 Answers

The easiest way to make your example work is to remove the Liftable (FunArg f) constraint from the instance declaration. But I think your example is just so condensed that it doesn't show why you actually need it.

So the next best thing is to add a Liftable (FunArg f) superclass constraint to the Fun class:

class Liftable (FunArg f) => Fun f where
  ...

If this is not feasible (i.e., if not all your functions have liftable argument types), then you cannot expect to write a parseFun of the given type.

A more general remark: I think what you're trying to do here is very strange, and perhaps too much at once. Parsing from unstructured strings into a context-free datatype is already difficult enough. Why not do that first, and write a separate function that transforms the "untyped", but structured representation of your language into a typed one.

EDIT (as a reaction to the comments, revised): As pointed out in the discussion on type family constraints that you also linked in your question, you can bypass the superclass cycle restriction by using ConstraintKinds. Here is a way to make your reduced example work. Perhaps this will scale to the full solution?

{-# LANGUAGE RankNTypes, ScopedTypeVariables, TypeFamilies, FlexibleContexts, GADTs #-}

import Data.Constraint  -- from the constraints package
import Data.Proxy       -- from the tagged package

-- A function has an argument type and a result type
class Liftable (FunArg f) => Fun f where
  type FunArg f
  type FunRes f

-- Expr, Plus, and instance Fun Plus as before

class Liftable a where
  type LiftRes a
  get :: p a -> Dict (Liftable (LiftRes a))
    -- acquire "superclass" dictionary by calling this method and
    -- then pattern matching on the result

instance Liftable (Expr a) where
  type LiftRes (Expr a) = Expr [a]
  get _ = Dict

instance (Liftable a, Liftable b) => Liftable (a, b) where
  type LiftRes (a, b) = (LiftRes a, LiftRes b)
  get (_ :: p (a, b)) =
    case get (Proxy :: Proxy a) of -- extra code required
      Dict -> case get (Proxy :: Proxy b) of -- extra code required
        Dict -> Dict

data Map f r = Map f

instance (Fun f, Liftable r, r ~ LiftRes (FunArg f)) => Fun (Map f r) where
  type FunArg (Map f r) = r
  type FunRes (Map f r) = [FunRes f]

parseFun :: forall a. [String] -> (forall f. Fun f => f -> a) -> a
parseFun ["plus"]      f = f Plus
parseFun ("map" : sym) f = parseFun sym
  (\ (fun :: g) -> case get (Proxy :: Proxy (FunArg g)) of -- extra code required
                     Dict -> f (Map fun))
like image 124
kosmikus Avatar answered Sep 29 '22 17:09

kosmikus