创建安全版本的Haskell';初始';功能
我正在努力学习“真实世界的Haskell”,任务是制作创建安全版本的Haskell';初始';功能,haskell,Haskell,我正在努力学习“真实世界的Haskell”,任务是制作头、尾、尾、和init的安全版本。前三个版本我都成功了,但可能类型类在init上要了我的命 这是我的密码: -- safeInit safeInit :: [a] -> Maybe [a] safeInit [] = Nothing safeInit (x:xs) = if null xs then Just [x] else x
头、尾、尾、
和init的安全版本。
前三个版本我都成功了,但可能类型类在init上要了我的命
这是我的密码:
-- safeInit
safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit (x:xs) = if null xs
then Just [x]
else x : (safeInit xs)
以下是加载到GHCI时产生的错误(函数从原始文件的第23行开始:
[1 of 1] Compiling Main ( ch04.exercises.hs, interpreted )
> ch04.exercises.hs:27:26: error:
> • Couldn't match expected type ‘Maybe [a]’ with actual type ‘[a]’
> • In the expression: x : (safeInit xs)
> In the expression: if null xs then Just [x] else x : (safeInit xs)
> In an equation for ‘safeInit’:
> safeInit (x : xs) = if null xs then Just [x] else x : (safeInit xs)
> • Relevant bindings include
> xs :: [a] (bound at ch04.exercises.hs:25:13)
> x :: a (bound at ch04.exercises.hs:25:11)
> safeInit :: [a] -> Maybe [a] (bound at ch04.exercises.hs:24:1) | 27 | else x : (safeInit xs) |
> ^^^^^^^^^^^^^^^^^
>
> ch04.exercises.hs:27:31: error:
> • Couldn't match expected type ‘[a]’ with actual type ‘Maybe [a]’
> • In the second argument of ‘(:)’, namely ‘(safeInit xs)’
> In the expression: x : (safeInit xs)
> In the expression: if null xs then Just [x] else x : (safeInit xs)
> • Relevant bindings include
> xs :: [a] (bound at ch04.exercises.hs:25:13)
> x :: a (bound at ch04.exercises.hs:25:11)
> safeInit :: [a] -> Maybe [a] (bound at ch04.exercises.hs:24:1) | 27 | else x : (safeInit xs) |
> ^^^^^^^^^^^ Failed, no modules loaded.
无论我在最后两行中标记或不标记x
或xs
,都会出现不同但非常相关的键入错误。使用Maybe-type-with-list时,我缺少了什么微妙之处?这不起作用的主要原因是因为表达式x:safeInit-xs
将ot类型检查。实际上,safeInit xs
是一个可能[a]
,但是(:)
有类型(:)::a->[a]->[a]
,所以类型不匹配
还有一个语义错误。如果null xs
为True
,则应返回Just[]
,而不是Just[x]
,因为x
是列表中的最后一个元素
您可以使用fmap::Functor f=>(a->b)->fa->fb
(因此对于f~可能
,fmap
是fmap::(a->b)->可能a->可能b
),来更改包装在中的值,只需:
safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit [_] = Just []
safeInit (x:xs) = fmap (x:) (safeInit xs)
safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit (x:xs) = Just (go xs x)
where go [] _ = []
go (x2:xs) x = x : go xs x2
一个有趣的问题是如何根据foldr
编写safeInit
。除了难题的乐趣外,它还可以作为“好消费者”参与GHC中的列表融合优化,这在某些情况下可以提高性能。我们从Willem Van Onsem回答中的第一个(幼稚)版本开始:
safeInit0::[a]->可能[a]
safeInit0[]=无
safeInit0[]=仅[]
safeInit0(x:xs)=fmap(x:)(safeInit0-xs)
第一个问题是它的形状不太像一个折叠:它有两个不同的案例,分别是[p]
和p:q:rs
。修补这个问题的一个经典技巧是传递一个可能
,其中包含列表中的前一个值
safeInit1::[a]->可能[a]
safeInit1 xs0=无需执行xs0
哪里
--第一种情况只发生在
--整个列表是空的。
无事
go[](刚好x)=刚好[x]
go(x:xs)Nothing=go-xs(仅x)
go(x:xs)(正前方)=(正前方:)go-xs(正前方)
下一个问题是语义:它不适用于无限或部分定义的参数
safeInit [1..] = Just [1..]
但是在这种情况下,safeInit1
会产生分歧,因为fmap
在其参数中必然是严格的。但是我们可以使用一些信息:fmap
在这种情况下只会应用于值。练习:证明这一点
我们将利用这一点,用一种奇怪的方式将表示为(Bool,[a])
,其中没有任何表示为(False,[])
,而只是将xs
表示为(True,xs)
。现在我们可以更懒惰了:
safeInit2 :: [a] -> Maybe [a]
safeInit2 xs = case helper2 xs of
(False, _) -> Nothing
(True, xs) -> Just xs
helper2 :: [a] -> (Bool, [a])
helper2 xs0 = go xs0 Nothing
where
go [] Nothing = (False, [])
go [] _ = (True, [])
go (x:xs) mb = case mb of
Nothing -> (True, rest)
Just p -> (True, p:rest)
where
rest = snd (go xs (Just x))
现在这正是褶皱的形状:
safeInit3 :: [a] -> Maybe [a]
safeInit3 xs = case helper3 xs of
(False, _) -> Nothing
(True, xs) -> Just xs
helper3 :: [a] -> (Bool, [a])
helper3 xs0 = foldr go stop x0 Nothing
where
stop Nothing = (False, [])
stop _ = (True, [])
go x r mb = case mb of
Nothing -> (True, rest)
Just p -> (True, p:rest)
where
rest = snd (r (Just x))
您可能会担心所有这些中间可能会导致性能问题,但事实上GHC能够完全优化它们,产生与Willem Van Onsem的优化实现非常相似的东西。提示:否则x:(safeInit xs)
毫无意义,因为x
是一个a
,但是safeInit xs
是一个可能[a]
。另一个选项是safeInit[]=Nothing;safeInit xs=Just(init xs)
。即使它没有那么干净,也很短。非常感谢。我在发布之后看到了语义错误:-)我看到了使用嵌入式函数如何避免了我遇到的整个a,可能a,可能[a],[a]
问题。谢谢fmap
版本有一个更大的问题:它在返回任何内容之前遍历整个列表。如果列表是无限的,它将完全失败。如果列表很长,包装和展开的成本可能会比双重遍历的成本低。