"使用下面的函数和两个参数:
nth :: (a,a,a,a,a) -> Int -> a
,其中Int值应返回五元素元组的第Int值。"我试着:
nth (a,b,c,d,e) x = (a,b,c,d,e) !! x
但是GHC给了我一个错误信息:
file.hs:11:21: error:
* Couldn't match expected type `[a1]'
with actual type `(a, b, c, d, e)'
* In the first argument of `(!!)', namely `(a, b, c, d, e)'
In the expression: (a, b, c, d, e) !! x
In an equation for `nth':
nth (a, b, c, d, e) x = (a, b, c, d, e) !! x
* Relevant bindings include
e :: e (bound at file.hs:11:14)
d :: d (bound at file.hs:11:12)
c :: c (bound at file.hs:11:10)
b :: b (bound at file.hs:11:8)
a :: a (bound at file.hs:11:6)
nth :: (a, b, c, d, e) -> Int -> a1
(bound at file.hs:11:1)
我该怎么办?我该怎么写这个方程的元组部分呢?提前感谢您的回答!
不能使用(!!) :: [a] -> Int -> a
因为,正如签名所说,它适用于列表,而不是元组。您可以使用模式匹配并将其实现为:
nth :: (a, a, a, a, a) -> Int -> a
nth (a, _, _, _, _) 0 = a
nth (_, b, _, _, _) 1 = b
nth (_, _, c, _, _) 2 = c
nth (_, _, _, d, _) 3 = d
nth (_, _, _, _, e) 4 = e
有人建议将3元组定义为(a, (b, c))
,这样它就是一个递归结构,其中n-元组定义为2-元组,并将n-1-元组作为第二项。但目前情况并非如此。
通过将元组的模式匹配与n
上的匹配分开,可以避免现有模式匹配解决方案中的一些重复:
nth :: (a, a, a, a, a) -> Int -> a
nth (a, b, c, d, e) n = case n of
0 -> a
1 -> b
2 -> c
3 -> d
4 -> e
您还应该考虑当n不在[0, 4]
范围中时您希望发生什么。如前所述,您将在运行时得到一个非详尽的模式错误。更诚实的做法是返回Maybe a
或接受更约束的类型作为输入,如
data QuintupleIndex = Zero | One | Two | Three | Four
(改编自我自己的评论)
实际上有一个简单的修复方法,在其他答案中没有提到:你只需要改变两个符号:
nth :: (a, a, a, a, a) -> Int -> a
nth (a,b,c,d,e) x = [a,b,c,d,e] !! x
-- or alternatively
nth (a,b,c,d,e) = ([a,b,c,d,e] !!)
仍然需要进行模式匹配来查找第n个元素,但现在在!!
中隐式处理了。
之所以有效,是因为list是同构的,这意味着它的所有元素都是相同类型的。在nth
的左侧,有模式(a,b,c,d,e)
,因此类型推断给出了错误消息中指出的最一般的约束a :: a, b :: b, ...
。然而,一旦在右侧看到[a,b,c,d,e]
,它就给了类型推断一个线索,即所有a
,b
,c
,d
,e
都是同一类型!所以类型推断现在可以确定(a,b,c,d,e) :: (a,a,a,a,a)
类型检查
在Haskell中,元组不是像列表那样用Int
索引的容器类型(不像Python和其他语言中的元组),相反,它更像是一种结构,每个字段都有特定的含义(认为它是C中的struct
),如果你想要有效的索引,你需要字段选择器而不是自然数:
data Five a = Five { _1 :: a, _2 :: a, _3 :: a, _4 :: a, _5 :: a }
现在您可以使用_x
访问字段,但是nth
的类型将是:
nth :: Five a -> (Five a -> a) -> a
nth v f = f v
-- or pointfreely:
nth = flip id
(另一个好处是你有一个完整的函数,而不是一个可能失败的模式匹配和引发异常的函数)
请注意,字段选择器是函数而不是自然数——如果您正在处理列表索引,询问"下一个元素是什么"是有意义的,但当涉及到结构时就不那么重要了。
(!!)
函数只作用于列表。
对于元组,至少包含2个以上元素的元组,访问元素的唯一方法是直接模式匹配:
nth :: (a,a,a,a,a) -> Int -> a
nth (a, _, _, _, _) 0 = a
nth (_, a, _, _, _) 1 = a
nth (_, _, a, _, _) 2 = a
nth (_, _, _, a, _) 3 = a
nth (_, _, _, _, a) 4 = a
(注意_
s,意思是"我不在乎这个值是多少")是多少。您可以给它们起一个名字——例如每行匹配(a, b, c, d, e)
,第一行返回值为a
,第二行返回值为b
,以此类推。但它的视觉"噪音"更少。只命名你关心的值。)
还要注意,这个函数会因为"非穷举模式"而崩溃。如果整数参数是0-4以外的任何值,则会出错。但是在包含5个元素的列表中使用(!!)
也有类似的缺陷。您可以很容易地在末尾添加一个默认大小写,尽管在使用完全泛型类型a
时这并不容易,因为没有任何值可以作为调用函数的任何类型的返回值!
使用守卫的简单解决方案如下:
nth :: (a,a,a,a,a) -> Int -> a
nth (a, b, c, d, e) i
| i == 0 = a
| i == 1 = b
| i == 2 = c
| i == 3 = d
| otherwise = e