[英]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
名称本身并不坏,但由于几乎所有命令式语言都附加了(几乎)等同的含义,因此大多数人认为它在函数式语言中的工作方式相同,但事实并非如此。
请注意,您使用head
, tail
等功能。这些通常被视为反模式:您可以使用模式匹配。 我们可以将其重写为:
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总是产生一个包含至少一个元素的列表。这又允许用来编写一个简洁的表达式,我们可以直接使用sh
和st
,而不是调用head
和tail
。
如果我在本地测试这个功能,我得到:
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
,所以我可以使用head
和tail
的总函数(来自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.