4

Say that I have got a GADT like this:

data Term a where
  Lit    :: a -> Term a
  Succ   :: Term Int -> Term Int
  IsZero :: Term Int -> Term Bool
  If     :: Term Bool -> Term a -> Term a -> Term a

Is it possible to store Succ (Lit 2) and IsZero (Succ (Lit 2)) inside the State monad transformer, as a value of the internal state?

The issue here being those two are of different types, and I don't know how the s of StateT s m a should be typed.

Edit: ATerm solved the initial question of how to store different GADT in the state, the issue now is since the type is lost it seemed impossible to compare the old and new state.

Edit: Final answer.

After going back and forth with @luqui, here's the full code snippet that answers this question.

Feel free to fork this repl and have a try.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}

import Data.Typeable

data Term a where
  Lit    :: a -> Term a
  Succ   :: Term Int -> Term Int
  IsZero :: Term Int -> Term Bool
  If     :: Term Bool -> Term a -> Term a -> Term a

deriving instance (Eq a) => Eq (Term a)

data ATerm where
  ATerm :: (Typeable a, Eq a) => Term a -> ATerm

instance Eq ATerm where
    ATerm t == ATerm u
        | Just t' <- cast t = t' == u
        | otherwise = False

main :: IO ()
main = return ()
Don Klein
  • 215
  • 2
  • 8
  • Possible duplicate of [Weaken GADTs type constraints to deal with unpredictable data](https://stackoverflow.com/questions/53904818/weaken-gadts-type-constraints-to-deal-with-unpredictable-data) – radrow Mar 15 '19 at 12:01
  • You shouldn't need the `Typeable a` constraint on the `Lit` constructor. – dfeuer Mar 21 '19 at 04:55
  • Alternatively, you can shove the `Eq` constraint in there too, and remove both constraints from the `ATerm` constructor. [Example](https://gist.github.com/treeowl/40ff82031132e642faaedac39fed6fb6) – dfeuer Mar 21 '19 at 05:53
  • Thanks @dfeuer for pointing out the extra constraint, code updated. – Don Klein Mar 21 '19 at 08:58

1 Answers1

6

Yes, you can use an existential

data ATerm where
   ATerm :: Term a -> ATerm

which is a monotype that stores "a Term of any type".

However you should be aware that you will lose the type information, which I have a hunch will not cause a problem in your case. If you do need to recover it, you will need to add some Typeable constraints or some other trick—hard to say without more context on what you are doing.

EDIT

To get the type information back, you will need to include it in ATerm

data ATerm where
    ATerm :: (Typeable a, Eq a) => Term a -> ATerm

Sadly, this change might cause the Typeable constraint to infect a fair amount of your code. That's just how it goes. We also include Eq a, since if we are comparing ATerms and do find that their types are the same, we will need to compare on that type.

Then to compare two ATerms, you first need to compare their types, then their values. This can be done with the Typeable library.

instance Eq ATerm where
    ATerm t == ATerm u
        | Just t' <- cast t = t' == u
        | otherwise = False

Luckily, your Term GADT doesn't hide any types. If you had a case like, for example

data Term a where
    ...
    Apply :: Func a b -> Term a -> Term b

you would need to add Typeable also to any hidden variables (variables that don't appear in the result type)

    Apply :: (Typeable a) => Func a b -> Term a -> Term b

Roughly, if you want to compare types, you need to have a Typeable constraint on them somewhere.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • Thanks, I revised it a bit and then it worked like a charm! – Don Klein Mar 15 '19 at 07:37
  • 2
    You may also like [`Some Term`](http://hackage.haskell.org/package/parameterized-utils-1.0.1/docs/Data-Parameterized-Some.html), not least for the collection of handy utilities you get for reuse. – Daniel Wagner Mar 15 '19 at 11:53
  • @luqui ended up you are correct in that the type will be lost once put inside ATerm, is there any way to retrieve it back since I need to compare the old state to the new state? – Don Klein Mar 15 '19 at 22:30
  • 1
    @DonKlein, I have expanded the answer – luqui Mar 16 '19 at 00:46
  • @luqui I tried your updated example but got some errors as shown [in this screencap](https://imgur.com/1wb7AbJ), mind having a look? – Don Klein Mar 19 '19 at 02:20
  • @DonKlein, made a tiny mistake. Should be `t' == u` instead of `t == u` – luqui Mar 19 '19 at 03:52
  • @luqui, actually that's the first thing I tried but no dice, you could try it here (press "Run" to see the error message on the right): https://repl.it/@coodoo1/OfficialDarkgreyProgramminglanguages – Don Klein Mar 19 '19 at 08:10
  • 1
    @DonKlein you forgot an `Eq` instance for your `Term`, and I forgot that we therefore need to close over an `Eq` dictionary in `ATerm` (assuming the former is compositional, which it almost surely will be). https://repl.it/@luqui/OfficialDarkgreyProgramminglanguages-3 – luqui Mar 19 '19 at 20:43
  • @luqui Thanks, it worked like a charm this time and I've updated the question with full code snippet – Don Klein Mar 20 '19 at 23:01