问题描述
我有一个类型,有几个构造函数包装了另一种类型;让我们使用下面的例子(实际上我有很多构造函数):
data New a = A a | B a
现在我需要一个函数 fun :: (a -> b) -> New a -> b
,它将第一个参数 f :: a -> b
应用于 x :: New a
中包裹的值。我可以通过模式匹配来做到这一点:
fun f (A v) = f v
fun f (B v) = f v
这一点都不优雅!看起来我应该可以做类似 fun f (_ v) = f v
的事情,但 GHC 给了我 Parse error in pattern
。
同样,我想要一个返回构造函数的函数 con :: New a -> (a -> New a)
。同样,我可以模式匹配:
con (A _) = A
con (B _) = B
将这些联合起来的明显模式是 con (x _) = x
,但这会引发另一个 Parse error in pattern: x
。
问题:
注意:我接受过正规的数学培训,但我在编程方面是自学的。我对 haskell
也有些陌生——如果这个问题有一个明显的、微不足道的答案,我很抱歉。我尝试了很多谷歌搜索,但都没有成功。
解决方法
严格来说,GHC 没有技术上的理由不允许这种事情,除非它只有在所有构造函数都具有相同类型的参数时才起作用——否则你不能对它们应用相同的函数。
这给了我们一个启示:大多数情况下,可区分联合构造函数具有不同类型的参数,例如 data Thing = Text String | Number Int
,即使类型恰好相同,这通常也只是巧合,而参数实际上具有不同的含义,例如 data Heisenberg = Velocity Vector2D | Position Vector2D
,因此即使技术上可行,对它们应用相同的函数也是没有意义的。
这是歧视工会的预期含义。构造函数应该代表语义上不同种类的事物。
这就是 GHC 不支持这种语法的原因,即使类型匹配:它超出了预期的用例,而且大多数时候它是无用的,即使它可能在一些非常狭窄的领域有用.
但是从您对所需用例的描述来看,您似乎在尝试表达完全不同的东西:a
和 A a
中的 B a
看起来都是意在表示相同的事物,而 A
和 B
仅用作“标签”,以表达值的某些属性,该属性不是值本身固有的。例如,a
可以是气球的大小,而 A
和 B
可以代表气球可以具有的两种不同颜色。
如果这确实是您要表达的内容,那么更好的模型是对标签进行编码,而不是尝试硬塞 DU 构造函数来表示它们,然后将标签与记录中的值组合起来:
data NewTag = A | B
data New a = New { tag :: NewTag,value :: a }
有了这个定义,fun
和 con
都变得微不足道了:
fun :: (a -> b) -> New a -> b
fun f n = f $ value n
con :: NewTag -> a -> New a
con tag value = New { tag = tag,value = value }
或者如果你喜欢那种东西,那就不用点:
fun :: (a -> b) -> New a -> b
fun f = f . value
con :: NewTag -> a -> New a
con = New
,
解决您的第一个问题...
您可以尝试使用记录语法:
data New a = A { get :: a } | B { get :: a }
那么 fun
可以定义为
fun f x = f (get x)
如果您将 New
设为 Functor
...
instance Functor (New a)
where fmap f (A x) = A (f x)
fmap f (B x) = B (f x)
con
可以定义为
import Data.Functor (($>))
con x = (x $>)
如果您启用 DeriveFunctor Language pragma
{-# LANGUAGE DeriveFunctor #-}
在文件顶部,您可以避免写出 Functor
实例
data New a = A { get :: a } | B { get :: a } deriving (Functor)
编辑
糟糕...或者显而易见的方式(如 Fyodor 的例子)
con x y = x { get = y }