又一个 Haskell 教程/类型基础/解答
外观
Haskell | |
---|---|
又一个 Haskell 教程 | |
前言 | |
介绍 | |
入门 | |
语言基础 (解答) | |
类型基础 (解答) | |
IO (解答) | |
模块 (解答) | |
高级语言 (解答) | |
高级类型 (解答) | |
单子 (解答) | |
高级 IO | |
递归 | |
复杂度 | |
String
或[Char]
- 类型错误:列表是同质的
Num
{a}=> (a, Char)
Int
- 类型错误:无法添加不同类型的值
这些类型
(a,b) -> b
[a] -> a
[a] -> Bool
[a] -> a
[[a]] -> a
这些类型
a -> [a]
。此函数接收一个元素并返回只包含该元素的列表。a -> b -> b -> (a,[b])
。第二个和第三个参数必须是相同类型,因为它们进入同一个列表。第一个元素可以是任何类型。(Num a) => a -> a
。由于我们将(+)
应用于a
,它必须是Num
的实例。a -> String
或a -> [Char]
。这会忽略第一个参数,因此它可以是任何类型。(Char -> a) -> a
。在此表达式中,x
必须是一个函数,它接收Char
作为参数。然而,我们不知道它会产生什么,所以我们称之为a
。- 类型错误。在这里,我们假设
x
的类型是a
。但是x
应用于自身,所以它必须是b -> c
类型。但然后它必须是(b -> c) -> c
类型,然后它必须是((b -> c) -> c) -> c
类型,等等,导致无限类型。 (Num a) => a -> a
。同样,由于我们将(+)
应用于a
,它必须是Num
的实例。
定义类似于
data Triple a b c = Triple a b c tripleFst (Triple x y z) = x tripleSnd (Triple x y z) = y tripleThr (Triple x y z) = z
带有类型签名的代码为
data Quadruple a b = Quadruple a a b b firstTwo :: Quadruple a b -> [a] firstTwo (Quadruple x y z t) = [x,y] lastTwo :: Quadruple a b -> [b] lastTwo (Quadruple x y z t) = [z,t]
我们注意到这里只有两个类型变量,a
和 b
与 Quadruple
相关联。
代码
data Tuple a b c d = One a | Two a b | Three a b c | Four a b c d tuple1 (One a ) = Just a tuple1 (Two a b ) = Just a tuple1 (Three a b c ) = Just a tuple1 (Four a b c d) = Just a tuple2 (One a ) = Nothing tuple2 (Two a b ) = Just b tuple2 (Three a b c ) = Just b tuple2 (Four a b c d) = Just b tuple3 (One a ) = Nothing tuple3 (Two a b ) = Nothing tuple3 (Three a b c ) = Just c tuple3 (Four a b c d) = Just c tuple4 (One a ) = Nothing tuple4 (Two a b ) = Nothing tuple4 (Three a b c ) = Nothing tuple4 (Four a b c d) = Just d
代码
fromTuple :: Tuple a b c d -> Either (Either a (a,b)) (Either (a,b,c) (a,b,c,d)) fromTuple (One a ) = Left (Left a ) fromTuple (Two a b ) = Left (Right (a,b) ) fromTuple (Three a b c ) = Right (Left (a,b,c) ) fromTuple (Four a b c d) = Right (Right (a,b,c,d))
在这里,我们使用嵌套的 Either
来表示有四个(而不是两个)选项。
代码
listHead (Cons x xs) = x listTail (Cons x xs) = xs listFoldl f y Nil = y listFoldl f y (Cons x xs) = listFoldl f (f y x) xs listFoldr f y Nil = y listFoldr f y (Cons x xs) = f x (listFoldr f y xs)
代码
elements (Leaf x) = [x] elements (Branch lhs x rhs) = elements lhs ++ [x] ++ elements rhs
代码
treeFoldr :: (a -> b -> b) -> b -> BinaryTree a -> b treeFoldr f i (Leaf x) = f x i treeFoldr f i (Branch left x right) = treeFoldr f (f x (treeFoldr f i right)) left elements2 = treeFoldr (:) []
或
elements2 tree = treeFoldr (\a b -> a:b) [] tree
第一个 elements2
只是第二个的更紧凑版本。
代码
treeFoldl :: (a -> b -> a) -> a -> BinaryTree b -> a treeFoldl f i (Leaf x) = f i x treeFoldl f i (Branch left x right) = treeFoldl f (f (treeFoldl f i left) x) right elements3 t = treeFoldl (\i a -> i ++ [a]) [] t
它既不完全模仿。它的行为最接近 foldr
,但在初始值的处理上略有不同。我们可以在解释器中观察差异
示例
CPS> foldr (-) 0 [1,2,3] 2 CPS> foldl (-) 0 [1,2,3] -6 CPS> fold (-) 0 [1,2,3] -2
很明显,它的行为不同。通过写下 fold
和 foldr
的推导,我们可以确切地看到它们在何处不同
foldr (-) 0 [1,2,3] ==> 1 - foldr (-) 0 [2,3] ==> ... ==> 1 - (2 - (3 - foldr (-) 0 [])) ==> 1 - (2 - (3 - 0)) ==> 2 fold (-) 0 [1,2,3] ==> fold' (-) (\y -> 0 - y) [1,2,3] ==> 0 - fold' (-) (\y -> 1 - y) [2,3] ==> 0 - (1 - fold' (-) (\y -> 2 - y) [3]) ==> 0 - (1 - (2 - 3)) ==> -2
本质上,主要的区别在于,在 foldr
的情况下,"初始值"在最后使用(替换 []
),而在 CPS 的情况下,初始值在开头使用。
CPS 中的 map
函数
map' :: (a -> [b] -> [b]) -> [a] -> [b] map' f [] = [] map' f (x:xs) = f x (map' f xs) map2 :: (a -> b) -> [a] -> [b] map2 f l = map' (\x y -> f x : y) l
点消除
map2 f = map' (\x y -> (:) (f x) y) map2 f = map' (\x -> (:) (f x)) map2 f = map' (\x -> ((:) . f) x) map2 f = map' ((:) . f)
CPS 中的 filter
函数:(需要仔细检查)
filter' :: (a -> [b] -> [b]) -> [a] -> [b] filter' f [] = [] filter' f (x:xs) = f x (filter' f xs) filter2 :: (a -> Bool) -> [a] -> [a] filter2 f l = filter' (\x y -> if (f x) then x:y else y) l