函数不仅具有类型:它们是类型。和种类。并排序。帮助把一个强大的思想重新组合在一起

时间:2012-01-31 04:11:57

标签: haskell types ghc type-systems higher-kinded-types

我正在做我平常的“在睡觉前阅读LYAH的一章”常规,感觉我的大脑随着每个代码样本而扩展。在这一点上,我确信我理解了Haskell的核心功能,现在只需要理解标准库和类型类,这样我就可以开始编写真正的软件了。

所以我正在阅读有关应用函子的章节,突然之间,本书声称函数不仅仅有类型,它们类型,并且可以这样对待(例如,通过使它们成为类型类的实例)。 ( - >)是一个类似于任何其他类型的构造函数。

我的思绪又被吹了,我立刻跳下床,启动电脑,去了GHCi并发现了以下内容:

Prelude> :k (->)
(->) :: ?? -> ? -> *
  • 这究竟是什么意思?
  • 如果( - >)是一个类型构造函数,那么值构造函数是什么?我可以猜测,但不知道如何用传统的data (->) ... = ... | ... | ...格式定义它。使用任何其他类型的构造函数执行此操作非常简单:data Either a b = Left a | Right b。我怀疑我无法以这种形式表达它与极端奇怪的类型签名有关。
  • 我偶然发现了什么?较高的kinded类型具有类似* -> * -> *的类型签名。想一想......( - >)也会出现在亲切的签名中!这是否意味着它不仅是一个类型构造函数,而且还是一种类型的构造函数?这是否与类型签名中的问号有关?

我已经阅读了某个地方(希望我能再次找到它,谷歌让我失望)关于能够通过从值,值类型,各种类型,各种类型,到其他方式任意扩展类型系统各种各样的东西,其他东西,等等,永远等等。这是否反映在( - >)的签名中?因为我也遇到了Lambda多维数据集的概念和构造的微积分而没有花时间去真正研究它们,如果我没记错,可以定义采用类型和返回类型的函数,取值和返回值,获取类型和返回值,并获取返回类型的值。

如果我不得不猜测一个带有值并返回类型的函数的类型签名,我可能会这样表达:

a -> ?

或可能

a -> *

虽然我没有看到为什么第二个例子不能轻易地被解释为从类型a的值到类型*的值的函数的基本不可变原因,其中*只是字符串或类型的类型同义词。

第一个例子更好地表达了一种函数,其类型超越了我心目中的类型签名:“一个函数,它接受一个类型为a的值并返回一些不能表示为类型的函数。”

2 个答案:

答案 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类型的第一个参数bSet。类型 变量在Haskell中被隐含地普遍量化,但是 在这里我们需要拼出它们,并指定它们的类型,即 Set。括号是为了使它们在隐形中不可见 定义,因为它们总是可以从其他参数中推断出来。

Set is abstract

你问(->)的构造函数是什么。有一点需要指出 是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中的数据类型。但是我们有Set1Set2Set3,依此类推。 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级代码中使用未装箱的类型,因此您可以想象->的类型只是* -> * -> *

由于???基本上只是*的更通用版本,因此它们与排序或类似内容无关。

但是,由于->只是一个类型构造函数,因此您实际上可以部分应用它;例如,(->) eFunctorMonad的实例。弄清楚如何编写这些实例是一个很好的思维伸展运动。

就值构造函数而言,它们必须只是lambdas(\ x ->)或函数声明。由于函数是语言的基础,因此它们有自己的语法。