考虑以下ADT:
data Property f a = Property String (f a) | Zilch
deriving Show
什么是
f
?它是作用于a
的函数吗?它是“类型函数”吗?讲师说Haskell有图灵完整的类型语言...所以我认为在这种情况下类型也可以具有功能?*Main> var = Property "Colors" [1,2,3,4]
*Main> :t var
var :: Num a => Property [] a
此处的
f
行为如何?由于[]
是空列表的构造函数,因此[]
是否始终是f
类型的最外面的空构造函数,如以下示例所示?*Main> var = Property "Colors" [(1,"Red"),(2,"Blue")]
*Main> :t var
var :: Num t => Property [] (t, [Char])
*Main> var = Property "Colors" (1,"Red")
*Main> :t var
var :: Num t => Property ((,) t) [Char]
我不太了解后者,但是如果有人说Haskell推断出该元组的空构造函数,那么我可以购买。另一方面,
*Main> var = Property "Colors" 20
*Main> :t var
var :: Num (f a) => Property f a
这里的
a
是什么?由于f
不能成为身份,但我们需要id :: a -> a
。我设法通过以下方法使ADT成为函子:
instance Functor f => Functor (Property f) where
fmap fun (Property name a) = Property name (fmap fun a)
fmap g Zilch = Zilch
所以像下面的作品
*Main> var = Property "Colors" [1,2,3,4]
*Main> fmap (+1) var
Property "Colors" [2,3,4,5]
但是,如果我将其提供给先前的元组示例,该怎么办?
我真的在寻找解释性的答案(在夏季 class 中,我只见过Haskell不到两个月的时间),而不是引用诸如
(f a)
这样的东西来...说FlexibleContexts
可在任意fmap
上工作。
最佳答案
令人困惑的事实是,[]
在Haskell中的不同上下文中具有两种不同的含义,这使您感到困惑,这使您难以解释其余的实验。
在值级别[]
确实是列表的空构造函数。但是,当您询问Property "Colors" [1,2,3,4]
的类型并看到Property [] a
时,您正在查看的是类型表达式,而不是值表达式。在类型级别上没有空列表。1而是[]
是列表类型的类型构造函数。您可以使用[Int]
(整数列表的类型),[Bool]
(布尔列表的类型)或[a]
(a
列表的多态类型);在这些示例中,[]
是应用于Int
,Bool
和a
的东西。
尽管看起来很奇怪,但实际上您可以根据需要将[Int]
编写为[] Int
,因此,当您不使用它时,通常只在类型级别看到[]
。
让我们再次看一下您的数据声明:
data Property f a = Property String (f a) | Zilch
在左侧,您已经声明了的形状,类型为
Property
; Property f a
形成一个类型。在右侧,通过列出可能的值构造函数(Property
和Zilch
)以及这些构造函数中“槽”的类型(无Zilch
;一个槽的)来声明该类型的值的形状。类型String
和另一个类型f a
(对于Property
)。因此,从中我们可以看出,无论
f
和a
是什么,类型表达式f a
(应用于f
的a
)都必须形成具有值的类型。但是f
不一定非要(实际上也可以不是)正常的值类型! f
值构造函数中没有Property
类型的插槽。使用一个更清晰的示例是:
*Main> var = Property "Stuff" (Just True)
*Main> :t var
var :: Property Maybe Bool
如果您不知道,
Maybe
是一个内置类型,其声明如下所示:data Maybe a = Just a | Nothing
对于本示例而言,这是有益的,因为我们在类型级别和值级别上没有使用相同的名称,这可以避免在您尝试了解事物的工作方式时造成混淆。
Just True
是Maybe Bool
类型的值。在值级别,我们将Just
数据构造函数应用于值True
。在类型级别,我们将Maybe
类型构造函数应用于Bool
类型。 Maybe Bool
值放入f a
值构造函数的Property
插槽中,该插槽很简单:f
是Maybe
,a
是Bool
。所以回到您的原始示例:
*Main> var = Property "Colors" [1,2,3,4]
*Main> :t var
var :: Num a => Property [] a
您正在使用
f a
填充[1, 2, 3, 4]
插槽。那是某种数字的列表,所以它是Num t => [t]
。因此,a
中的f a
是t
(需要遵循Num
约束),而f
是列表类型构造函数 []
。此[]
类似于Maybe
,而不是Nothing
。*Main> var = Property "Colors" (1,"Red")
*Main> :t var
var :: Num t => Property ((,) t) [Char]
此处
f a
插槽中填充了(1, "Red")
,它是Num t => (t, [Char])
类型的(请记住String
只是编写[Char]
的另一种方式)。现在要了解这一点,我们必须有点挑剔。现在忘记约束,只关注(t, [Char])
。我们需要以某种方式将其解释为应用于其他对象,因此可以将其与f a
匹配。事实证明,尽管我们对元组类型有特殊的语法(例如(a, b)
),但它们实际上就像可以在不使用特殊语法的情况下声明的普通ADT一样。 2元组类型是一种类型构造函数,我们可以编写应用于其他两种类型的(,)
,在这种情况下为t
和[Char]
。并且我们可以使用部分应用的类型构造函数,因此我们可以将应用于(,)
的t
视为一个单元,并将该单元应用于[Char]
。我们可以将该解释编写为Haskell类型表达式((,) t) [Char]
,但是我不确定是否更清楚。但是归结为,我们可以通过将第一个“单元” f a
作为(,) t
和f
作为[Char]
将其与a
匹配。然后,这给了我们Property ((,) t) [Char]
(只有我们还必须放回我们之前忘记的Num t
约束)。最后是这个:
*Main> var = Property "Colors" 20
*Main> :t var
var :: Num (f a) => Property f a
在这里,我们用
f a
(某种数字)填充20
插槽。我们尚未确切指定数字的类型,因此Haskell愿意相信它可以是Num
类中的任何类型。但是我们仍然需要该类型具有可以与f a
匹配的“形状”:将某种类型构造函数应用于其他某种类型。整个类型表达式f a
需要与20
的类型匹配,因此这就是Num
约束的条件。但是关于f
或a
可能是什么,我们没有说什么,而且20
可以满足Num
约束的任何类型,因此它可以是我们想要的任何Num (f a) => f a
,因此,为什么var
的类型仍然是多态的f
和a
(仅具有添加的约束)。您可能只看到过
Integer
,Int
,Double
等数字类型,所以想知道怎么可能会有一个f a
就是一个数字。所有这些示例只是单个基本类型,而不是应用于某些对象的类型。但是您可以编写自己的Num
实例,因此Haskell永远不会假设给定类型(或类型的形状)不能为数字,它只会抱怨您是否实际尝试使用它而找不到Num
实例。因此,有时您会得到类似这样的东西,它们可能是错误的,但是Haskell暂时接受Num
类型的代码,这是您没有想到的奇怪的事情。实际上,内置库中已经有类型具有复合类型级别结构并具有
Num
实例。一个示例是Ratio
类型,用于将小数表示为两个整数的比率。您可以使用Ratio Int
或Ratio Integer
,例如:Main*> 4 :: Ratio Int
4 % 1
所以你可以说:
*Main> var = Property "Colors" (20 :: Ratio Integer)
*Main> :t var
var :: Property Ratio Integer
1实际上,启用了
DataKinds
扩展可以允许类型几乎反映任何值的结构,因此您可以拥有类型级别的列表。但这不是这里所发生的事情,它并不是真正可以使用的功能,除非您对香草Haskell中的类型和值的工作方式有了很好的了解,所以我建议您忽略此脚注并假装它不存在。
关于haskell - Haskell类型函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45202843/