在Haskell中合并两个带有替代元素的列表

Merge two lists with alternative elements in Haskell

我是新的Haskell,并尝试练习自己的Haskell技能。

我想实现一个合并两个列表的功能,以便每个列表中的项都是可替换的。

1
2
["a","b","c"]
["1","2","3"]

合并后。

1
["a","1","b","2","c","3"]

这是我的Haskell代码

1
2
3
4
5
mergeList::[String]->[String]->[String]
mergeList [] [] = []
mergeList  _ [] = []
mergeList [] _  = []
mergeList (x:xs) (y:ys) = x:y:mergeList xs ys

只要两个列表的长度相同,代码就起作用。

但是我想对两个列表的长度进行一些错误检查。

如果两个列表的长度不同,那么我想报告一个错误。

1
2
3
mergeList::[String]->[String]->[String]
mergeList l r | length l /= length r = error"report an error"
              | otherwise =

如何完成其??他部分?


不建议使用error。在Haskell中,我们希望为所有可能的输入定义函数,以便类型系统可以指导我们编写正确的程序。如果您的函数对不同长度的列表无效,则一种方法是使用Maybe

1
2
3
4
5
6
7
mergeList :: [a] -> [a] -> Maybe [a]
mergeList [] [] = Just []
mergeList (x:xs) (y:ys) =
    case mergeList xs ys of
        Just merged -> Just (x:y:merged)
        Nothing     -> Nothing
mergeList _ _ = Nothing

有更多简洁的方法可以编写此代码,但是我待在底层。

现在,mergeList的用户需要定义传递两个不同长度的列表的情况下的操作。


如果列表中的一个完全为空,则在模式匹配中引发错误。
所以你会有

1
2
3
mergeList (x: xs) (y: ys) = x: y: mergeList xs ys
mergeList []      []      = []
mergeList _       _       = error"your error"  -- falling through to this pattern means that exactly one of the lists is empty

检查两个列表的长度是否相等有点多余,因为'length'具有O(n)复杂度,这意味着您将两次遍历列表(在相等长度的情况下)。

正如luqui正确指出的那样,存在更多使用Maybe类型解决问题的优雅方法。当您了解函子(也许是Functor类的实例)时,您可以这样写:

1
2
3
4
5
mergeList :: [a] -> [a] -> Maybe [a]
mergeList (x: xs) (y: ys) = fmap (\
est -> x: y: rest) $ mergeList xs ys
mergeList []      []      = Just []
mergeList _       _       = Nothing

最简单的方法可能只是预先进行检查,然后将主要工作路由到辅助功能:

1
2
3
4
5
6
7
8
9
mergeList::[String]->[String]->[String]
mergeList xs ys
    | length xs /= length ys = error"Lists must be the same length"
    | otherwise go xs ys
    where
        go [] [] = []
        go  _ [] = []
        go [] _  = []
        go (x:xs) (y:ys) = x:y:go xs ys

检查在主函数中完成,并且如果列表大小相同,则将它们交给执行主处理的内部函数。请注意go仅仅是您的原始功能;它只是在package函数中内部化了。您可以使用比go更具描述性的名称。我试图保持简短。

自从我写Haskell以来已经有一段时间了,因此对于任何语法问题,我深表歉意。

您还可以通过将签名更改为以下内容来概括该功能:

1
mergeList::[a]->[a]->[a]

由于您没有在函数中执行任何特定于Strings的操作。


您可以使用的其他功能可能是这样的:

1
otherwise = reverse $ foldl (\\a (x,y) -> x:y:a) [] (x `zip` y)

示例:

1
2
3
4
Prelude> let y = ["a","b","c"]
Prelude> let x = ["1","2","3"]
Prelude> reverse $ foldl (\\a (x,y) -> x:y:a) [] (x `zip` y)
["a","1","b","2","c","3"]

或带有foldr的elliptic00解决方案,避免使用reverse

1
 otherwise = foldr ((x, y) a -> x:y:a) [] (x `zip` y)


luqui指出我们希望在Haskell中保持安全,避免使用error。但是等到我们到达至少一个列表的末尾,然后再放弃任何东西时,这将使人们不再有任何懒惰的希望。一种选择是将错误处理扔给调用方。我们可以伪造这样的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Data.Bifunctor
import Data.Bifunctor.Fix
import Data.Bifunctor.Tannen

data ListF l a = NilF | ConsF a l

instance Bifunctor ListF where
  bimap _ _ NilF = NilF
  bimap f g (ConsF a l) = ConsF (f a) (g l)

{-instance Bifoldable ListF where ...

instance Bitraversable ListF where ... -}


type List = Fix ListF

但是我们可以添加错误处理:

1
type EList e = Fix (Tannen (Either e) ListF)

一个EList e ae类型的错误,一个NilF或一个ConsF包含一个a和另一个EList e a的错误。

但是,这种合成方法最终变得非常冗长,令人困惑并且效率低下。实际上,更好的方法是使用完全自定义的列表类型:

1
data EList' e a = Error e | Nil | Cons a (EList' e a)