Haskell中如何泛化GADT的Value类型,简化实例实现?
问题:Haskell中为带类型的Value GADT实现通用构造器与类型类实例
我正在实现一个函数式语言的解释器,需要做运行时类型检查,因为输入语言里不同表达式会返回不同类型的值。
最初定义的基础装箱数据类型如下:
-- 扩展值类型 data Value = StringValue T.Text | IntValue Integer | BoolValue Bool | Error EvalError deriving (Eq, Show)
这些值会在解析树表达式求值后返回,表达式的定义类似这样:
data Expression = Constant Value a | UnaryOp (Value a -> Value b) Expression | BinaryOp (Value a -> Value b -> Value c) Expression Expression
为定义操作,我写了一个辅助函数:
lift :: (Int -> Int -> Int) -> Value -> Value -> Value lift f (IntValue x) (IntValue y) = IntValue $ f x y lift _ _ _ = Error TypeError
这个函数可以用来构建运算符,示例如下:
case op of '+' -> (BinaryOp (lift (+)) arg1 arg2, rest2) -- 其他运算符类似
后续实现越来越繁琐,我觉得在Haskell已有对应类型类的情况下,自己写lift方法和相关运算符有点多余。我曾尝试把Value做成Functor和Applicative的实例,但原来的Value没有类型变量,没法实现。后来了解到GADT,就把Value类型改成了这样:
data Value a where StringValue :: T.Text -> Value T.Text IntValue :: Int -> Value Int BoolValue :: Bool -> Value Bool Error :: EvalError -> Value EvalError
现在我尝试为它实现Functor和Applicative实例。最初的Functor实例是这样的:
instance Functor Value where fmap f (StringValue t) = StringValue $ f t fmap f (IntValue x) = IntValue $ f x fmap f (BoolValue b) = BoolValue $ f b fmap _ _ = Error TypeError
我觉得这些重复的fmap分支太冗余了,要是以后要处理int转string这类类型转换,维护起来会特别麻烦。于是我试着用Applicative和辅助函数来规避,现在的实现变成了这样:
instance Functor Value where fmap f v = pure $ f (extractValue v) extractValue :: Value a -> a extractValue (StringValue t) = t extractValue (IntValue n) = n extractValue (BoolValue b) = b extractValue (Error e) = e
但这种重复的拆箱代码同样冗余,而且pure的实现也需要大量反向的重复代码。如果把Value改成通用构造器,之后又没法对值类型做模式匹配,会导致类似加法实现的问题——编译器没法确认值类型确实是int。
我希望能通过通用构造器来做模式匹配,比如像下面这样:
extractValue :: Value a -> a extractValue (T t) = t instance Applicative Value where pure x = T x -- 其他实例方法类似
想问下Haskell里有没有这样的实现方式?
内容的提问来源于stack exchange,提问作者Joel Croteau
相关产品推荐
相关产品推荐

