Extending a datatype in Haskell

后端 未结 4 1303
夕颜
夕颜 2020-12-31 08:33

Haskell newbie here.

I wrote an evaluator for a minimal assembly-like language.

Now, I want to extend that language to support some syntactic sugar which,

4条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-31 09:15

    You could do something a bit more OOP-like using existential types:

    -- We need to enable the ExistentialQuantification extension.
    {-# LANGUAGE ExistentialQuantification #-}
    
    -- I want to use const as a term in the language, so let's hide Prelude.const.
    import Prelude hiding (const)
    
    -- First we need a type class to represent an expression we can evaluate
    class Eval a where
      eval :: a -> Int
    
    -- Then we create an existential type that represents every member of Eval
    data Exp = forall t. Eval t => Exp t
    
    -- We want to be able to evaluate all expressions, so make Exp a member of Eval.
    -- Since the Exp type is just a wrapper around "any value that can be evaluated,"
    -- we simply unwrap that value and call eval on it.
    instance Eval Exp where
      eval (Exp e) = eval e
    
    -- Then we define our base language; constants, addition and multiplication.
    data BaseExp = Const Int | Add Exp Exp | Mul Exp Exp
    
    -- We make sure we can evaluate the language by making it a member of Eval.
    instance Eval BaseExp where
      eval (Const n) = n
      eval (Add a b) = eval a + eval b
      eval (Mul a b) = eval a * eval b
    
    -- In order to avoid having to clutter our expressions with Exp everywhere,
    -- let's define a few smart constructors.
    add x y = Exp $ Add x y
    mul x y = Exp $ Mul x y
    const   = Exp . Const
    
    -- However, now we want subtraction too, so we create another type for those
    -- expressions.
    data SubExp = Sub Exp Exp
    
    -- Then we make sure that we know how to evaluate subtraction.
    instance Eval SubExp where
      eval (Sub a b) = eval a - eval b
    
    -- Finally, create a smart constructor for sub too.
    sub x y = Exp $ Sub x y
    

    By doing this, we actually get a single extendable type so you could, for example, mix extended and base values in a list:

    > map eval [sub (const 10) (const 3), add (const 1) (const 1)]
    [7, 2]
    

    However, since the only thing we now can know about Exp values is that they are somehow members of Eval, we can't pattern match or do anything else that isn't specified in the type class. In OOP terms, think of Exp an exp value as an object that implements the Eval interface. If you have an object of type ISomethingThatCanBeEvaluated, obviously you can't safely cast it into something more specific; the same applies to Exp.

提交回复
热议问题