理论题
题干
traverse
是 Haskell
语言的一个重要函数。其类型为:
(a -> f b) -> t a -> f (t b)
示例:
traverse (\a -> if a > 0 then Just a else Nothing) [1, 2, 3, 4]
上例可以得到 Just [1, 2, 3, 4]
。
问题:
- 将上例中的
[1, 2, 3, 4]
变成[-1, 2, 3, 4]
会得到什么? - 根据类型信息,将函数应用到
a
后得到的应该是t (f b)
。请说明你的疑问,并给出猜测。 提示:traverse
要求在类型t
上定义“某个操作”。请说明该操作的作用,并推断当t = List
时其实现。用自然语言描述即可。 traverse (\i -> [0..i]) [0, 1, 2, 3]
会得到什么?- 本题预设
t
是一个满足某个约束的类型。请分析本题语境,猜测t
需要满足的约束。 提示:本题把将a -> f b
应用到t a
上看成应用到内部a
上。
材料和解释
a
b
f
t
都是类型变量(即不是某个特定的类型,而是一系列可行的类型)。其中,t
f
可以看成接收类型而给出类型的类型,可以视为某种“上下文”类型,即它为内部类型赋予了额外的信息。例如,[Int]
为整型添加了数组这一上下文,使得它可以表示多个整型,换句话说表示了“类型为整型的可能性”这样一个类型。- 从语义上讲,
traverse
作用在于,假如对于某一个被上下文t
包裹的a
进行操作导致产生了新的上下文f
,其可以将上下文f
提到外部,并保留内部t
的结构。 - 示例表达式该使得
traverse
的类型实例化(用具体类型取代)到了(Int -> Maybe Int) -> [Int] -> Maybe [Int]
。 type Maybe a = Just a | Nothing
。Maybe
表示“可能不存在”,List
(通常也表示为[]
)表示“多种可能”。- 在某种约束下,上下文类型
f
允许以下函数的存在:f (a -> b) -> f a -> f b
。- 显然,函数可以被推广到任意元数。
Maybe
和List
都是符合条件的f
。其中:- 在
Maybe
上的该函数的语义为:如果被包裹在上下文中的函数或值有任意一个不存在,则结果不存在。 List
上的该函数的语义为:被包裹在上下文中的函数(们)和值(们)进行组合并应用。
- 柯里化:任何函数都是一元函数。多元函数到等价一元函数的转换被称为“柯里化”。柯里化步骤如下:消费一个元,并给出一个消费剩下元而后给出结果的函数。
f(a,b) = a + b
柯里化后可以读成“给定某个a
,可以得到一个函数,这个函数是:给定一个b
,得到a + b
”。注意,第二个函数给出的结果中的a
是被第一个函数的参数“约束”了的。这种“越级现象”称为“函数闭包”。柯里化是基于函数闭包的。- 在
Haskell
中,a -> b -> c
就可以视为接收任意类型a
的变量,再接收任意类型b
的变量,最后给出任意类型c
的变量的“二元函数”。显然,->
符号是右结合的,也就是说,它等价于a -> (b -> c)
,而不是(a -> b) -> c
。
Haskell
中的 lambda 表达式\a -> c
表示接收a
作为参数,给出c
。