使用下面的函数查找元组中的第n个元素



"使用下面的函数和两个参数:

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

最新更新