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

问题:

  1. 是否有更短、更优雅的方式来通过合并案例来定义 funcon
  2. 为什么 GHC 反对这些模式?
  3. 我是不是在做一些禁忌的事情?

注意:我受过正规的数学训练,但我是自学编程。我对 haskell 也有些陌生——抱歉,如果这个问题有一个明显的、琐碎的答案。我尝试了很多谷歌搜索,但没有成功。

严格来说,GHC 没有技术原因不允许这种事情,除了它只有在所有构造函数都具有相同类型的参数时才有效——否则你不能将相同的函数应用于他们。

而这给了我们一个启示:大多数时候,受歧视的联合构造函数具有不同类型的参数,例如data Thing = Text String | Number Int,即使类型恰好相同,那通常也只是巧合,并且参数实际上具有不同的含义,例如 data Heisenberg = Velocity Vector2D | Position Vector2D,因此即使在技术上可行,对它们应用相同的功能也没有意义。

这是受歧视工会的本意。构造函数应该表示语义上不同的事物。

这就是 GHC 不支持这种语法的原因,即使类型匹配也是如此:它超出了预期的用例,而且大多数时候它是无用的,即使它可能在某些非常有用的地方有用狭窄的区域。


但是从你对所需用例的描述来看,你似乎在试图表达一个完全不同的东西:它看起来像 A aB a 中的 a ] 表示同一事物,而 AB 仅用作“标签”,表示值的某些属性,这些属性不是值本身固有的。例如,a 可能是气球的大小,而 AB 可能代表气球可以具有的两种不同颜色。

如果这确实是您要表达的内容,那么更好的模型是对标签进行编码,而不是试图硬塞 DU 构造函数来表示它们,然后将标签与值组合在一起记录:

data NewTag = A | B
data New a = New { tag :: NewTag, value :: a }

有了这个定义,funcon 都变得微不足道了:

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 }