Haskell - 无法将类型`[Char]'与`Char'匹配

时间:2018-04-02 12:48:23

标签: haskell

我目前在Haskell中有以下代码

splitStringOnDelimeter :: String -> Char -> [String]

splitStringOnDelimeter "" delimeter = return [""]

splitStringOnDelimeter string delimeter = do
    let split = splitStringOnDelimeter (tail string) delimeter
    if head string == delimeter
    then return ([""] ++ split)
    else return ( [( [(head string)] ++ (head split) )] ++ (tail split))

如果我在Haskell终端(即https://www.tryhaskell.org)中使用返回语句的值(例如( [( [(head "ZZZZ")] ++ (head ["first", "second", "third"]) )] ++ (tail ["first", "second", "third"]))[""] ++ ["first", "second", "third"][""]运行它,那么我会收到正确的类型从与本地堆栈编译器不同的终端。此外,如果我还将顶部return语句更改为return "",那么它不会抱怨我非常确定不正确的语句。

我的本​​地编译器可以正常使用我的Haskell代码库的其余部分,这就是为什么我认为我的代码可能有问题......

2 个答案:

答案 0 :(得分:12)

Monad类型类设计中不幸的一件事是,他们引入了名为return函数。但是在许多命令式编程语言return中,return是一个返回内容的关键字,但在Haskell return中有一个完全不同的含义,它真的会返回一些东西。

您可以通过删除splitStringOnDelimeter :: String -> Char -> [String] splitStringOnDelimeter "" delimeter = [""] splitStringOnDelimeter string delimeter = let split = splitStringOnDelimeter (tail string) delimeter in if head string == delimeter then ([""] ++ split) else ( [( [(head string)] ++ (head split) )] ++ (tail split))

来解决问题
return :: Monad m => a -> m a

a用于在monad中包装值(类型return)。由于这里有关于列表的签名提示,Haskell将假设您查找列表monad。这意味着你[""]return [""]包装到另一个列表中,因此隐含地使用[[""]]来编写(在此上下文中),[String],这当然是 do匹配。

同样适用于return,你再次制作一个monadic函数,但是这里你的函数与monad没什么关系。

请注意,名称head本身并不是 ,但由于几乎所有命令式语言都附加了(几乎)相同的含义,因此大多数人认为它的工作方式相同在函数式语言中,但它没有。

请注意您使用tailsplitStringOnDelimeter :: String -> Char -> [String] splitStringOnDelimeter "" delimeter = [""] splitStringOnDelimeter (h:t) delimeter | h == delimeter = "" : split | otherwise = (h : sh) : st where split@(sh:st) = splitStringOnDelimeter t delimeter等功能。这些通常被视为反模式:您可以使用模式匹配。我们可以将其重写为:

string

通过使用模式匹配,我们确信h有一个头t和一个尾if,我们可以直接将它们用于表达式。这使得表达式更短,更易读。虽然then - else - where条款本身并不反模式,但我个人认为警卫在语法上更加干净。因此,我们在此处使用splitStringOnDelimter t delimeter子句,我们称之为split,我们将其与(sh:st)(以及sh模式匹配。我们知道这将始终匹配,因为basecase和归纳案例总是产生一个至少包含一个元素的列表。这再次允许用来写一个简洁的表达式,我们可以直接使用sthead,而不是调用{{1 }和tail

如果我在本地测试这个功能,我得到了:

Prelude> splitStringOnDelimeter "foo!bar!!qux" '!'
["foo","bar","","qux"]

作为外卖消息,我认为您最好避免使用returndo,除非您知道此功能和关键字(do是什么一个关键字)真的意思。在函数式编程的上下文中,它们具有不同的含义。

答案 1 :(得分:0)

return的类型为forall m a. Monad m => a -> m a。 函数splitStringOnDelimiter的输出类型为[String],因此如果您尝试使用return编写一些输出值,编译器将推断您要提供一些m a,从而将m实例化为[](实际上是Monad类型类的实例),a实例化为String。因此,编译器现在希望将一些String用作return的参数。例如,return ([""] ++ split)会违反此期望,因为此处return的参数,即[""] ++ split的类型为[String],而不是String

do用作monadic代码的方便表示法,因此只有在您对使用输出类型的monadic操作感兴趣时才应依赖它。在这种情况下,您真的只想使用纯函数来操作列表。

我会加2美分并提出解决方案。我使用了foldr,这是recursion scheme的简单实例。像foldr这样的递归方案捕获了常见的计算模式;它们通过构造使递归定义清晰,易于推理和总计

我还利用了输出列表总是非空的事实,所以我在类型中写了它。通过更准确地说明我的意图,我现在知道split,递归调用的结果是NonEmpty String,所以我可以使用总计函数{{1 }和head(来自tail),因为非空列表始终是头部和尾部。

Data.List.NonEmpty