我承认这是我的功课。但是我努力了之后真的找不到好的解决方案。
可能有一些愚蠢的方法可以做到这一点,例如:
myHead (x:[]) = x
myHead (x:y:xs) = fst (x, y)
但我认为这不是老师想要的。
顺便说一句,不需要错误处理。
提前致谢!
我承认这是我的功课。但是我努力了之后真的找不到好的解决方案。
可能有一些愚蠢的方法可以做到这一点,例如:
myHead (x:[]) = x
myHead (x:y:xs) = fst (x, y)
但我认为这不是老师想要的。
顺便说一句,不需要错误处理。
提前致谢!
有一个非常自然的函数不在前奏中,称为“uncons”,它是 uncurried cons 的逆函数。
cons :: a -> [a] -> [a]
uncurry cons :: (a, [a]) -> [a]
uncons :: [a] -> (a, [a])
uncons (x:xs) = (x, xs)
您可以使用它来实现 head 作为
head = fst . uncons
为什么是uncons
自然的?
您可以将列表视为通过使用两个构造函数定义的数据类型
nil :: [a]
nil = []
cons :: (a, [a]) -> [a]
cons (a,as) = a:as
您也可以将其视为由函数解构的数据类型
destruct :: [a] -> Maybe (a, [a])
destruct [] = Nothing
destruct (a:as) = Just (a, as)
解释为什么这些与列表类型如此明确地联系在一起,远远超出了这个答案,但一种看待它的方法是尝试定义
nil :: f a
cons :: (a, f a) -> f a
或者
destruct :: f a -> Maybe (a, f a)
对于任何其他容器类型f
。你会发现它们都与列表有着非常密切的关系。
您几乎已经可以uncons
在 的定义的第二种情况下看到destruct
,但是有一个Just
障碍。这与未在空列表中定义uncons
的更好配对head
tail
head [] = error "Prelude.head"
所以我们可以调整之前的答案以适用于无限流。在这里,我们可以将无限流视为由一个函数构造
data Stream a = Next a (Stream a)
cons :: (a, Stream a) -> Stream a
cons (a, as) = Next a as
并被一个函数破坏
uncons :: Stream a -> (a, Stream a)
uncons (Next a as) = (a, as)
-- a. k. a.
uncons stream = (head stream, tail stream)
两者互为倒数。
现在我们可以通过获取返回元组的第一个元素来获取head
for sStream
uncons
head = fst . uncons
这就是 中的head
模型Prelude
,所以我们可以假装列表是无限流并以这种方式定义头部
uncons :: [a] -> (a, [a])
uncons (a:as) = (a, as)
-- a. k. a.
uncons list = (head list, tail list)
head = fst . uncons
也许您应该写入自己的 cons List 类型,那么它可能更有意义。虽然类型同义词不能递归,所以你最终使用非元组数据构造函数,使元组变得多余......它看起来像:
data List a = Nil | List (a, List a)
deriving( Show )
head :: List a -> a
head (List c) = fst c
就像评论中已经说过的那样,这只是一个愚蠢的任务,你不会得到你可以称之为良好实现的东西head
。
对于这些要求,您的解决方案很好 - 因为我将替换的唯一更改(x:y:xs)
因为(x:y:_)
根本xs
没有使用(这实际上会在某些设置中导致编译器警告)。事实上,你也可以这样做y
:
myHead (x:_:_) = fst (x, undefined)
会有一些看起来可能不是那么无用的替代方法,即fst
不要只是手动构建一个元组并立即再次解构它:
myHead' [x] = x
myHead' xs = myHead' . fst $ splitAt 1 xs
myHead'' = foldr1 $ curry fst
myHead''' = fromJust . find ((==0) . fst) . zip [0..]
但你可以正确地说这些都是荒谬的。