Haskell 构造函数作为函数的变量
Haskell constructor as variables of functions
我有一个类型,其中有几个构造函数包装了另一种类型;让我们使用下面的示例(实际上我有很多构造函数):
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)
函数 returns 构造函数。同样,我可以进行模式匹配:
con (A _) = A
con (B _) = B
将这些统一起来的明显模式是 con (x _) = x
,但这会引发另一个 Parse error in pattern: x
。
问题:
- 是否有更短、更优雅的方式来通过合并案例来定义
fun
和 con
?
- 为什么 GHC 反对这些模式?
- 我是不是在做一些禁忌的事情?
注意:我受过正规的数学训练,但我是自学编程。我对 haskell
也有些陌生——抱歉,如果这个问题有一个明显的、琐碎的答案。我尝试了很多谷歌搜索,但没有成功。
严格来说,GHC 没有技术原因不允许这种事情,除了它只有在所有构造函数都具有相同类型的参数时才有效——否则你不能将相同的函数应用于他们。
而这给了我们一个启示:大多数时候,受歧视的联合构造函数具有不同类型的参数,例如data Thing = Text String | Number Int
,即使类型恰好相同,那通常也只是巧合,并且参数实际上具有不同的含义,例如 data Heisenberg = Velocity Vector2D | Position Vector2D
,因此即使在技术上可行,对它们应用相同的功能也没有意义。
这是受歧视工会的本意。构造函数应该表示语义上不同的事物。
这就是 GHC 不支持这种语法的原因,即使类型匹配也是如此:它超出了预期的用例,而且大多数时候它是无用的,即使它可能在某些非常有用的地方有用狭窄的区域。
但是从你对所需用例的描述来看,你似乎在试图表达一个完全不同的东西:它看起来像 A a
和 B a
中的 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 }
我有一个类型,其中有几个构造函数包装了另一种类型;让我们使用下面的示例(实际上我有很多构造函数):
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)
函数 returns 构造函数。同样,我可以进行模式匹配:
con (A _) = A
con (B _) = B
将这些统一起来的明显模式是 con (x _) = x
,但这会引发另一个 Parse error in pattern: x
。
问题:
- 是否有更短、更优雅的方式来通过合并案例来定义
fun
和con
? - 为什么 GHC 反对这些模式?
- 我是不是在做一些禁忌的事情?
注意:我受过正规的数学训练,但我是自学编程。我对 haskell
也有些陌生——抱歉,如果这个问题有一个明显的、琐碎的答案。我尝试了很多谷歌搜索,但没有成功。
严格来说,GHC 没有技术原因不允许这种事情,除了它只有在所有构造函数都具有相同类型的参数时才有效——否则你不能将相同的函数应用于他们。
而这给了我们一个启示:大多数时候,受歧视的联合构造函数具有不同类型的参数,例如data Thing = Text String | Number Int
,即使类型恰好相同,那通常也只是巧合,并且参数实际上具有不同的含义,例如 data Heisenberg = Velocity Vector2D | Position Vector2D
,因此即使在技术上可行,对它们应用相同的功能也没有意义。
这是受歧视工会的本意。构造函数应该表示语义上不同的事物。
这就是 GHC 不支持这种语法的原因,即使类型匹配也是如此:它超出了预期的用例,而且大多数时候它是无用的,即使它可能在某些非常有用的地方有用狭窄的区域。
但是从你对所需用例的描述来看,你似乎在试图表达一个完全不同的东西:它看起来像 A a
和 B a
中的 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 }