创建安全版本的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

我正在努力学习“真实世界的Haskell”,任务是制作
头、尾、尾、
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
版本有一个更大的问题:它在返回任何内容之前遍历整个列表。如果列表是无限的,它将完全失败。如果列表很长,包装和展开的成本可能会比双重遍历的成本低。