我正在做我平常的“在睡觉前阅读LYAH的一章”常规,感觉我的大脑随着每个代码样本而扩展。在这一点上,我确信我理解了Haskell的核心功能,现在只需要理解标准库和类型类,这样我就可以开始编写真正的软件了。
所以我正在阅读有关应用函子的章节,突然之间,本书声称函数不仅仅有类型,它们是类型,并且可以这样对待(例如,通过使它们成为类型类的实例)。 ( - >)是一个类似于任何其他类型的构造函数。
我的思绪又被吹了,我立刻跳下床,启动电脑,去了GHCi并发现了以下内容:
Prelude> :k (->)
(->) :: ?? -> ? -> *
data (->) ... = ... | ... | ...
格式定义它。使用任何其他类型的构造函数执行此操作非常简单:data Either a b = Left a | Right b
。我怀疑我无法以这种形式表达它与极端奇怪的类型签名有关。* -> * -> *
的类型签名。想一想......( - >)也会出现在亲切的签名中!这是否意味着它不仅是一个类型构造函数,而且还是一种类型的构造函数?这是否与类型签名中的问号有关?我已经阅读了某个地方(希望我能再次找到它,谷歌让我失望)关于能够通过从值,值类型,各种类型,各种类型,到其他方式任意扩展类型系统各种各样的东西,其他东西,等等,永远等等。这是否反映在( - >)的签名中?因为我也遇到了Lambda多维数据集的概念和构造的微积分而没有花时间去真正研究它们,如果我没记错,可以定义采用类型和返回类型的函数,取值和返回值,获取类型和返回值,并获取返回类型的值。
如果我不得不猜测一个带有值并返回类型的函数的类型签名,我可能会这样表达:
a -> ?
或可能
a -> *
虽然我没有看到为什么第二个例子不能轻易地被解释为从类型a的值到类型*的值的函数的基本不可变原因,其中*只是字符串或类型的类型同义词。
第一个例子更好地表达了一种函数,其类型超越了我心目中的类型签名:“一个函数,它接受一个类型为a的值并返回一些不能表示为类型的函数。”
答案 0 :(得分:48)
你在问题中触及了很多有趣的点,所以我是 害怕这将是一个很长的答案:)
(->)
的类型是* -> * -> *
,如果我们忽略了框架GHC
插入。但是没有循环,->
在
(->)
种类是箭头,而不是功能箭头。的确,到了
区分它们类似的箭头可以写成(=>)
,然后
(->)
的种类是* => * => *
。
我们可以将(->)
视为类型构造函数,或者更确切地说是类型
运营商。同样,(=>)
可以被视为种类的运算符,并且
正如你在你的问题中所建议的那样,我们需要进入一个级别的'起来。我们
稍后将在 Beyond Kinds 部分中回到此处,但首先:
你问类型签名如何寻找一个带有a的函数 value并返回一个类型。在Haskell中这是不可能的: 功能无法返回类型!您可以使用模拟此行为 键入类和类型系列,但让我们进行插图更改 语言为依赖类型的语言 Agda。这是一个 与Haskell类似语法的语言,将类型拼凑在一起 价值观是第二天性。
要使用某些东西,我们定义一个自然的数据类型 数字,为了方便一元表示,如 Peano Arithmetic。 数据类型是写入的 GADT样式:
data Nat : Set where
Zero : Nat
Succ : Nat -> Nat
Set相当于Haskell中的*," type"所有(小)类型,
比如自然数。这告诉我们Nat
的类型是
Set
,而在Haskell中,Nat不会有类型,它会有
一种,即*
。在阿格达没有任何种类,但一切都有
一种类型。
我们现在可以编写一个获取值并返回类型的函数。
下面是一个采用自然数n
和类型的函数,
并使迭代List
构造函数n
应用于此
类型。 (在Agda中,[a]
通常是List a
)
listOfLists : Nat -> Set -> Set
listOfLists Zero a = a
listOfLists (Succ n) a = List (listOfLists n a)
一些例子:
listOfLists Zero Bool = Bool
listOfLists (Succ Zero) Bool = List Bool
listOfLists (Succ (Succ Zero)) Bool = List (List Bool)
我们现在可以创建一个map
函数,该函数在listsOfLists
上运行。
我们需要采用一个自然数,即迭代次数
列表构造函数。基本情况是数字的时间
Zero
,然后listOfList
只是身份,我们应用该功能。
另一个是空列表,返回空列表。
步骤案例涉及:我们将mapN
应用于头部
列表,但这有一层较少的嵌套,mapN
到列表的其余部分。
mapN : {a b : Set} -> (a -> b) -> (n : Nat) ->
listOfLists n a -> listOfLists n b
mapN f Zero x = f x
mapN f (Succ n) [] = []
mapN f (Succ n) (x :: xs) = mapN f n x :: mapN f (Succ n) xs
在mapN
的类型中,Nat
参数名为n
,因此剩下的
类型可以取决于它。所以这是一个类型的例子
取决于价值。
<子>
作为旁注,这里还有另外两个命名变量,
即a
类型的第一个参数b
和Set
。类型
变量在Haskell中被隐含地普遍量化,但是
在这里我们需要拼出它们,并指定它们的类型,即
Set
。括号是为了使它们在隐形中不可见
定义,因为它们总是可以从其他参数中推断出来。
子>
你问(->)
的构造函数是什么。有一点需要指出
是Set
(以及Haskell中的*
)是抽象的:你不能
模式匹配就可以了。所以这是非法的Agda:
cheating : Set -> Bool
cheating Nat = True
cheating _ = False
同样,您可以在类型构造函数中模拟模式匹配
Haskell使用类型族,给出了一个典型的例子
Brent Yorgey's blog。
我们可以在Agda中定义->
吗?因为我们可以从中返回类型
函数,我们可以定义自己的->
版本,如下所示:
_=>_ : Set -> Set -> Set
a => b = a -> b
(中缀运算符是_=>_
而不是(=>)
)
定义的内容很少,与做一个非常相似
在Haskell中输入同义词:
type Fun a b = a -> b
正如上面所承诺的,Agda中的所有内容都有一种类型,但随后
_=>_
的类型必须有类型!这触动了你的观点
关于排序,可以这么说,Set(种类)之上的一层。
在Agda中,这称为Set1
:
FunType : Set1
FunType = Set -> Set -> Set
事实上,他们有一个完整的层次结构! Set是类型
&#34;小&#34; types:haskell中的数据类型。但是我们有Set1
,
Set2
,Set3
,依此类推。 Set1
是提及的类型
Set
。这种层次结构是为了避免像Girard这样的不一致
悖论。
正如您在问题中所注意到的,->
用于表示类型和种类
Haskell和相同的表示法根本用于函数空间
阿格达的水平。这必须被视为内置式操作员,
构造函数是lambda抽象(或函数
定义)。此类型的层次结构类似于中的设置
System F omega等等
信息可以在后面的章节中找到
Pierce's Types and Programming Languages
在Agda中,类型可以依赖于值,函数可以返回类型, 如上所示,我们也有一个层次结构 类型。系统研究lambda的不同系统 在Pure Type Systems中更详细地研究了结石。一个好的 参考是 Barendregt Lambda Calculi with Types, 第96页介绍了PTS,第99页及以后介绍了很多例子。 您还可以在那里阅读有关lambda多维数据集的更多信息。
答案 1 :(得分:17)
首先,?? -> ? -> *
类型是GHC特定的扩展。 ?
和??
只是处理未装箱的类型,其行为与仅仅*
不同(据我所知,必须装箱)。因此??
可以是任何普通类型或未装箱类型(例如Int#
); ?
可以是其中之一,也可以是未装箱的元组。这里有更多信息:Haskell Weird Kinds: Kind of (->) is ?? -> ? -> *
我认为函数不能返回未装箱的类型,因为函数是惰性的。由于惰性值是值或thunk,因此必须将其装箱。盒装只是意味着它是一个指针,而不仅仅是一个值:它就像Java中的Integer()
vs int
。
由于您可能不会在LYAH级代码中使用未装箱的类型,因此您可以想象->
的类型只是* -> * -> *
。
由于?
和??
基本上只是*
的更通用版本,因此它们与排序或类似内容无关。
但是,由于->
只是一个类型构造函数,因此您实际上可以部分应用它;例如,(->) e
是Functor
和Monad
的实例。弄清楚如何编写这些实例是一个很好的思维伸展运动。
就值构造函数而言,它们必须只是lambdas(\ x ->
)或函数声明。由于函数是语言的基础,因此它们有自己的语法。