jakubdaniel
jakubdaniel

Reputation: 2223

Parsing an Indexed Type

I just started exploring the possibilities of data types à la carte in combination with indexed types. My current experiment is a bit too large to include here, but can be found here. My example is mixing together an expression from different ingredients (arithmetic, functions, ...). The goal is to enforce only well-typed expressions. That is why an index is added to the expressions (the Sort type).

I can build expressions like:

-- define expressions over variables and arithmetic (+, *, numeric constants)
type Lia = IFix (VarF :+: ArithmeticF)

-- expression of integer type/sort
t :: Lia IntegralSort
t = var "c" .+. cnst 1

This is all good as long as I construct only fixed (static) expressions.

Is there a way to read an expression from string/other representation (that obviously has to encode the sort) and produce a dynamic value that gets represented by these functors?

For example, I would like to read ((c : Int) + (1 : Int)) and represent it somehow with VarF and ArithmeticF. Here I realize I cannot obtain a value of static type Lia IntegralSort. But suppose I have in addition:

data EqualityF a where
    Equals :: forall s. a s -> a s -> EqualityF a BoolSort

I could expect there being a function that can read String into Maybe (IFix (EqualityF :+: VarF :+: ...)). Such a function would attempt to build representations for the LHS and RHS and if the sorts matched it could produce a result of statically known type IFix (EqualityF :+: ...) BoolSort. The problem is that the representation of LHS (and RHS) has no fixed static sort. Is what I am trying to do impossible with this representation I chose?

Upvotes: 1

Views: 204

Answers (1)

Li-yao Xia
Li-yao Xia

Reputation: 33509

(.=.) :: EqualityF :<: f => IFix f s -> IFix f s -> IFix f BoolSort
(.=.) a b = inject (Equals a b)

You can use a GADT to hide the sort, allowing you to return values of sorts depending on the input. Pattern matching then allows you to recover the sort.

data Expr (f :: (Sort -> *) -> (Sort -> *)) where
  BoolExpr :: IFix f BoolSort -> Expr f
  IntExpr  :: IFix f IntegralSort -> Expr f

Here is a simplistic parser of postfix expressions involving + and =.

parse :: (EqualityF :<: f, ArithmeticF :<: f) => String -> [Expr f] -> Maybe (Expr f)

parse (c : s) stack | isDigit c =
  parse s (IntExpr (cnst (digitToInt c)) : stack)

parse ('+' : s) (IntExpr e1 : IntExpr e2 : stack) =
  parse s (IntExpr (e1 .+. e2) : stack)

parse ('=' : s) (IntExpr e1 : IntExpr e2 : stack) =
  parse s (BoolExpr (e1 .=. e2) : stack)

parse ('=' : s) (BoolExpr e1 : BoolExpr e2 : stack) =
  parse s (BoolExpr (e1 .=. e2) : stack)

parse [] [e] = Just e
parse _ _ = Nothing

You might not like the duplicate cases for =. A more general framework is Typeable, allowing you to just test for the type equalities you need.

data SomeExpr (f :: (Sort -> *) -> Sort -> *) where
  SomeExpr :: Typeable s => IFix f s -> SomeExpr f


parseSome :: forall f. (EqualityF :<: f, ArithmeticF :<: f) => String -> [SomeExpr f] -> Maybe (Expr f)

parseSome (c : s) stack | isDigit c =
  parseSome s (SomeExpr (cnst (digitToInt c)) : stack)

parseSome ('+' : s) (SomeExpr e1 : SomeExpr e2 : stack) = do
  e1 <- gcast e1
  e2 <- gcast e2
  parseSome s (SomeExpr (e1 .+. e2) : stack)

parseSome ('=' : s) (SomeExpr (e1 :: IFix f s1) : SomeExpr (e2 :: IFix f s2) : stack) = do
  Refl <- eqT :: Maybe (s1 :~: s2) 
  parseSome s (SomeExpr (e1 .=. e2) : stack)

parseSome [] [e] = Just e
parseSome _ _ = Nothing

Edit

To parse sorts, you want to track them at the type level. Again, use an existential type.

data SomeSort where
  SomeSort :: Typeable (s :: Sort) => proxy s -> SomeSort

You can construct the sort of arrays this way:

-- \i e -> array i e
arraySort :: SomeSort -> SomeSort -> SomeSort
arraySort (SomeSort (Proxy :: Proxy i)) (SomeSort (Proxy :: Proxy e)) =
  SomeSort (Proxy :: Proxy (ArraySort i e))

A potential problem with Typeable here is that it only allows you to test equality of types, when you may want only to check the head constructor: you can't ask "is this type an ArraySort?", but only "is this type equal to ArraySort IntSort BoolSort?" or some other full type. In that case you need a GADT that reflects the structure of a sort.

-- "Singleton type"
data SSort (s :: Sort) where
  SIntSort :: SSort IntSort
  SBoolSort :: SSort BoolSort
  SArraySort :: SSort i -> SSort e -> SSort (ArraySort i e)

data SomeSort where
  SomeSort :: SSort s -> SomeSort

array :: SomeSort -> SomeSort -> SomeSort
array (SomeSort i) (SomeSort e) = SomeSort (SArraySort i e)

The singleton package provides various facilities for defining and working with these singleton types, though it may be overkill for your use case.

Upvotes: 1

Related Questions