Haskell 构造函数作为函数的变量

问题描述

我有一个类型,有几个构造函数包装了另一种类型;让我们使用下面的例子(实际上我有很多构造函数):

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

问题

  1. 是否有一种更简短、更优雅的方式来定义 funcon 并合并大小写?
  2. 为什么 GHC 反对这些模式?
  3. 我是否想在这里做一些禁忌?

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

解决方法

严格来说,GHC 没有技术上的理由不允许这种事情,除非它只有在所有构造函数都具有相同类型的参数时才起作用——否则你不能对它们应用相同的函数。

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

这是歧视工会的预期含义。构造函数应该代表语义上不同种类的事物。

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


但是从您对所需用例的描述来看,您似乎在尝试表达完全不同的东西:aA a 中的 B 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 }