繁体   English   中英

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

[英]Haskell - Couldn't match type `[Char]' with `Char'

我目前在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 )中运行它,其中包含return语句的值,如( [( [(head "ZZZZ")] ++ (head ["first", "second", "third"]) )] ++ (tail ["first", "second", "third"]))[""] ++ ["first", "second", "third"][""]然后我从终端接收到与本地堆栈编译器不同的正确类型。 此外,如果我也将top return语句更改为return ""那么它不会抱怨我非常肯定不正确的语句。

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

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

您可以通过删除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 -> ma用于包装monad中的值(类型a )。 由于这里有关于列表的签名提示,Haskell将假设您查找列表monad。 所以这意味着你return会将[""]包装到另一个列表中,所以你可以用return [""]隐式写入(在这种情况下), [[""]] ,这当然与[String] 匹配[String]

同样的do ,你再次做一个monadic函数,但在这里你的函数与monad没什么关系。

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

请注意,您使用headtail等功能。这些通常被视为反模式:您可以使用模式匹配。 我们可以将其重写为:

splitStringOnDelimeter :: 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这里使用where子句,我们调用splitStringOnDelimter t delimeter ,我们将它与split (以及with (sh:st)匹配。我们知道这将始终匹配,因为basecase和inductive case总是产生一个包含至少一个元素的列表。这又允许用来编写一个简洁的表达式,我们可以直接使用shst ,而不是调用headtail

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

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

作为外卖消息 ,我认为你最好避免使用return ,并且do ,除非你知道这个函数和关键字( do是关键字)的真正含义。 在函数式编程的上下文中,它们具有不同的含义。

return有类型forall m a. Monad m => a -> ma forall m a. Monad m => a -> ma 函数splitStringOnDelimiter的输出类型是[String] ,所以如果你尝试使用return写一些输出值,编译器会推断你想提供一些ma ,从而实例化m[] (这确实是一个实例) Monad类型类)和a String 因此,编译器现在希望将一些String用作return参数。 这种期望在例如return ([""] ++ split)被违反,因为这里return的参数,即[""] ++ split具有类型[String]而不是String

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

我将加上我的2美分并建议一个解决方案。 我使用了一个foldr ,这是一个递归方案的简单实例。 foldr这样的递归方案捕获常见的计算模式; 他们做递归定义清晰,易于推理和施工。

我还利用了输出列表总是非空的这一事实,所以我在类型中写了它。 通过更准确地了解我的意图,我现在知道, split ,递归调用的结果,是一个NonEmpty String ,所以我可以使用headtail函数(来自Data.List.NonEmpty ),因为非空列表总是有头和尾。

import Data.List.NonEmpty as NE (NonEmpty(..), (<|), head, tail)

splitStringOnDelimeter :: String -> Char -> NonEmpty String
splitStringOnDelimeter string delimiter = foldr f (pure "") string
  where f h split = if h == delimiter 
                      then ("" <| split) 
                      else (h : NE.head split) :| NE.tail split

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM