简体   繁体   English

与GADT进行模式匹配

[英]Pattern match with GADTs

I am implementing an Expression solver, but I am having some problems with pattern matching. 我正在实现一个表达式求解器,但是在模式匹配方面遇到了一些问题。 I have the following code 我有以下代码

data Expression a where
                Const   ∷  Int → Expression Int
                Add ∷  Expression Int → Expression Int → Expression Int
                Sub ∷  Expression Int → Expression Int → Expression Int


eval ∷  Expression a → a
eval (Const a) = a

eval (Add exp1 exp2) = (val1 + val2)
  where
    val1 = eval exp1
    val2 = eval exp2


eval (Sub exp1 exp2) = (val1 - val2)
  where
    val1 = eval exp1
    val2 = eval exp2

But since eval Add and eval Sub are very similar and I could want another operations I though of doing a more generic implementation, but I am having some problems. 但是由于eval Add和eval Sub非常相似,尽管我想进行更通用的实现,但我可能想要另一个操作,但是我遇到了一些问题。 I though of doing like 我虽然喜欢

data Op = Add | Sub

data Expression a where
                Const   ∷  Int → Expression Int
                Op ∷  Expression Int → Expression Int → Expression Int

eval (Op exp1 exp2) = case Op of
                           Add → (val1 + val2)
                           Sub → (val1 - val2)
                      where
                        val1 = eval exp1
                        val2 = eval exp2 

But it doesn't work. 但这是行不通的。 Is it possible to do something like this? 有可能做这样的事情吗? Thanks in advance 提前致谢

This does not work because you are defining Op as both a data constructor and a type. 这不起作用,因为您将Op定义为数据构造函数和类型。 The type Op has two constructors Add and Sub , but the Expression type has an Op constructor. Op类型具有两个构造函数AddSub ,而Expression类型具有Op构造函数。 This code is confusing the two. 此代码使两者混淆。

The case statement of your eval function attempts to match of the value Op , but Op is a constructor that takes two arguments in this context, so you can't pattern match on it. 您的eval函数的case语句尝试匹配值Op ,但是Op是在此上下文中带有两个参数的构造函数,因此您无法对其进行模式匹配。 I suspect you are going for something like this 我怀疑你要这样

data Op = Add | Sub

data Expression a where
                Const ::  Int -> Expression Int
                Op ::  Op -> Expression Int -> Expression Int -> Expression Int

eval (Const c)         = c
eval (Op op exp1 exp2) = case op of
                           Add -> (val1 + val2)
                           Sub -> (val1 - val2)
                      where
                        val1 = eval exp1
                        val2 = eval exp2

You will have to include a field in the Op constructor that denotes what operation is to be performed. 您将必须在Op构造函数中包含一个字段,该字段指示要执行的操作。 Since you have to match on the that operation anyway, it would probably be nicer to stick with the original definition of Expression . 由于无论如何您都必须在that操作上进行匹配,因此最好坚持使用Expression的原始定义。

Another possibility that is simpler and easier to extend might be something like the following 另一个更容易扩展的可能性可能是如下所示

data Expression a where
    Const ::  Int -> Expression Int
    Op    ::  (a -> b -> c) -> Expression a -> Expression b -> Expression c

eval :: Expression a -> a
eval (Const c)        = c
eval (Op f exp1 exp2) = f (eval exp1) (eval exp2)

where an Op wraps the actual function up with it. Op在其中包装实际功能。 You would not be able to do nice things like print out the expression and know what function it corresponds to though. 您将无法完成诸如打印出表达式并知道其对应的功能之类的出色工作。

Riffing on the comments: 对评论发表评论:

data Op a b c where
    Add :: Op Int Int Int
    Sub :: Op Int Int Int
    Less :: Op Int Int Bool

interpretOp :: Op a b c -> a -> b -> c
interpretOp Add = (+)
interpretOp Sub = (-)
interpretOp Less = (<)

data Expression a where
    Const :: Int -> Expression Int
    Op :: Op a b c -> Expression a -> Expression b -> Expression c

eval :: Expression a -> a
eval (Const x) = x
eval (Op op a b) = interpretOp op (eval a) (eval b)

Riffing on luqui's answer: 轻视卢基的答案:

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs #-}
class OpLike op a b where
    type Result op a b
    interpret :: op -> a -> b -> Result op a b

data Expression a where
    Const :: a -> Expression a
    Op :: OpLike op a b => op -> Expression a -> Expression b -> Expression (Result op a b)

eval :: Expression a -> a
eval (Const x) = x
eval (Op op a b) = interpret op (eval a) (eval b)

Now you can add operators at any point in your program, without having to alter something like luqui's Op datatype. 现在,您可以在程序的任何位置添加运算符,而无需更改诸如luqui的Op数据类型之类的内容。 Here's a very contrived example: 这是一个非常人为的示例:

data Add = Add
add x y = Op Add x y

instance OpLike Add Int Int where
    type Result Add Int Int = Int
    interpret Add x y = x + y

instance OpLike Add Int Bool where
    type Result Add Int Bool = String
    interpret Add x y = if y then reverse (show x) else show x    

example = (Const (3::Int) `add` Const (10::Int)) `add` (Const True)

example has type Expression String and eval uates to "31" :-) example类型为Expression Stringeval"31" :-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM