Haskell问题:限制数据类型使用show函数

3

代码:

data Exp a = Const a | Eq (Exp a) (Exp a)

我希望Const a包含一个show类型的值,这样我就可以稍后打印它。所以在C#中我会写:

class Const : Exp { IShow X; }
class Eq : Exp { Exp X, Y; }

我该如何在Haskell中实现这个功能?

你的Haskell版本的Exp函数需要一个类型参数,但是你的C#版本不需要(即使用泛型)。你能否修改你的问题,使它们在这方面匹配? - Martijn
我希望Haskell的版本与C#的版本相匹配。这基本上就是我寻求帮助的原因。 - usr
5个回答

6
{-# LANGUAGE GADTs #-}

data Exp a where
    Const :: Show a => a -> Exp a
    Eq :: Exp a -> Exp a -> Exp a

如果您希望在不同分支中允许不同的数据类型,那也可以。

data Exp where
    Const :: Show a => a -> Exp
    Eq :: Exp -> Exp -> Exp

2
在你的第一个例子中,为什么要对数据构造函数施加这种约束的目的是什么?任何需要在Exp上调用show的函数仍然必须在其签名中包含该约束。我能想到的唯一原因是提供有关“a”类型变量实际应该是什么的提示。我可能错过了其他好处吗? - Michael Steele
2
Eq (Const 0) (Const "") 是合法的。而且,不需要 Show 约束,因为类型系统已经保证了 Const 不会与任何缺少 show 的内容一起打包。请参见 http://www.haskell.org/haskellwiki/GADT。 - ephemient
1
在您的第二个示例中,我现在看到它必须是这样。由于Exp类型本身不是多态的,因此使用Exp参数的函数无法指定类约束。 - Michael Steele
我认为这是最好的答案,因为它基本上是我的C#示例的Haskell版本。我不知道不同的构造函数可以有不同的类型参数。 - usr

4
你可以通过说出以下内容来实现这一点:
data (Show a) => Exp a = Const a | Eq (Exp a) (Exp a)

但这几乎总是一个坏主意,因为它会强制每个使用Exp的函数都提到show约束条件,即使它从未使用Show方法。相反,只将展示约束条件放在相关的函数上。请参见Real World Haskell以了解说明。

1
如果我想要 Eq (Const 0) (Const "") 怎么办?这样做会导致同一表达式中出现不同类型的问题。如何解决这个问题? - usr
1
如果你正在编写一个与Haskell不同语义的解释器(其中0==""是类型错误),正确的做法是创建一个用于你的语言值的类型,而不是直接重用Haskell的:data IObj = IInt Int | IStr String |...。现在将Exp更改为data Exp = Const IObj | Eq IObj IObj。否则,你会发现很难编写一个可以检查两种不同类型的equals函数。 - Nathan Shively-Sanders
Nathan,我认为这是正确的方法。我想编写一个ORM,因此需要能够检查列与Int或String的相等性。 - usr

2
如果你只是想知道传递给Const的参数,为什么不直接在构造函数中存储生成的String值,这样你就可以show它了。例如:
data Exp = Const String | Eq Exp Expr

example = Eq (Const (show 0)) (Const (show ""))

这与您的C#版本非常相似。

并且为了额外加分,添加一个智能构造器: mkConst :: Show a => a -> Exp mkConst = Const . show 这样例子就变成了:example = Eq (mkConst 0) (mkConst "")通常我会使用小写的构造函数名称作为函数名,但那个已经被占用了... - yatima2975
这非常聪明。惰性求值来拯救。 - usr

1
回答评论中提出的第二个问题,Eq (Const 0) (Const "")在您拥有的数据类型中是无法实现的,因为Exp IntegerExp String不是相同的类型。一个选项是做类似于以下的操作:
data Exp = forall a . Show a => Const a | Eq Exp Exp

这是否对你有所帮助取决于你打算如何使用该类型。

编辑:如上所述,这需要启用语言扩展。


2
可能需要注意的是,这需要 {-# LANGUAGE ExistentialQuantification #-}-XExistentialQuantification 或类似的语法。个人而言,我更喜欢 GADT 风格,但它们是等效的。 - ephemient
非常正确,我忘记了。我想出于某种原因,我发现存在量化比GADT更容易理解,但仔细检查后发现它们在这种情况下实际上是等效的。 - Dan

1
我会将你的数据类型声明为 Show 类型类的一个实例:
data Exp a = Const a | Eq (Exp a) (Exp a)

instance (Show a) => Show (Exp a) where
    show (Const a) = show a
    show (Eq x y ) = "[ " ++ show x ++ " , " ++ show y ++ " ]"

看看当你在ghci中加载它并执行时会发生什么:

*Main> let x = Eq (Const 1) (Eq (Const 2) (Const 3))
*Main> x      
[1 , [2 , 3] ]

回复评论:

你可以轻松地处理不同类型。假设你想解析数学表达式。例如,你可以有以下结构:

data Expr  = Var String | Sum (Expr) (Expr) | Number Int | Prod (Expr) (Expr)

这已经足以表示由数字和已命名变量的乘积和求和组成的任何表达式。例如:

x = Sum (Var "x") (Prod (Number 5) (Var "y")) 

表示:x + 5y

为了漂亮地打印出来,我会这样做:

instance Show Expr where
    show (Var s) = show s
    show (Sum x y) = (show x) ++ " + " (show y)
    show (Prod x y) = (Show x) ++ (show y)
    show (Number x) = show x

这样就可以了。你也可以使用GADTs:

 data Expr where
      Var :: String -> Expr
      Sum :: Expr -> Expr -> Expr

等等……然后将其实例化为Show。


这不符合我的要求,因为我希望eq的分支能够包含不同类型。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接