Haskell 哈斯凯尔';t匹配类型“[Char]”和#x27;带有'Char';

Haskell 哈斯凯尔';t匹配类型“[Char]”和#x27;带有'Char';,haskell,Haskell,我目前在Haskell中有以下代码 splitStringOnDelimeter :: String -> Char -> [String] splitStringOnDelimeter "" delimeter = return [""] splitStringOnDelimeter string delimeter = do let split = splitStringOnDelimeter (tail string) delimeter if head s

我目前在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终端(即)中使用返回语句的值运行它,例如
([([(head“ZZZZ”)]++(head[“first”、“second”、“third”])]++(tail[“first”、“second”、“third”])
[+[“first”、“second”、“third”]
[“”]
然后我从终端接收正确的类型,这与我的本地堆栈编译器不同。此外,如果我还将最上面的return语句更改为
return”“
,那么它不会抱怨该语句,我很确定该语句是错误的


我的本地编译器与我的Haskell代码库的其余部分配合得很好,这就是为什么我认为我的代码可能有问题…

Monad类型类设计中不幸的一件事是,他们引入了一个名为
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))
通过使用模式匹配,我们可以确定
字符串
有一个头
h
和一个尾
t
,我们可以直接在表达式中使用它们。这使得表达式更短,也更具可读性。虽然
if
-
then
-
else
子句本身并不是反模式的,但我个人认为,从语法上讲,守卫更干净。因此,我们在这里使用了一个
where
子句,其中我们称之为
splitstringdeimeter t delimeter
,我们将其与
split
(以及
(sh:st)模式匹配
。我们知道这总是匹配的,因为基本情况和归纳情况总是产生一个至少包含一个元素的列表。这再次允许use编写一个简洁的表达式,我们可以直接使用
sh
st
,而不是调用
head
tail

如果我在本地测试此函数,我得到:

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

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

return
对于所有MA都有类型
。Monad m=>a
。 函数
splitStringOnDelimitor
的输出类型是
[String]
,因此如果您尝试使用
返回
写入一些输出值,编译器将推断您想要提供一些
ma
,从而将
m
实例化为
[]
(这确实是Monad类型类的一个实例),和
a
String
。因此,编译器现在希望将一些
String
用作
return
的参数。例如,
return([”“]++split)
中违反了此期望,因为这里
return
的参数,即
[”“]++split
具有类型
[String]
而不是
String

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

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

我还利用了输出列表总是非空的事实,所以我在类型中编写了它。通过更精确地说明我的意图,我现在知道递归调用的结果
split
,是一个
非空字符串
,因此我可以使用total函数
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
将Data.List.NonEmpty导入为NE(NonEmpty(..),(Char->NonEmpty字符串
SplitStringDelimeter字符串分隔符=foldr f(纯“”)字符串
其中f h split=如果h==分隔符

然后(““请注意,不要使用tryhaskell.org,它非常有限。可以在和其他地方获得更好的在线回复。类型错误隐藏了位置信息,以及更多有助于理解问题而无需复制的信息。与此同时,我已经开始推荐(对于拥有足够新版本的
ghc
的人来说)就是永远不要使用
return
,当你真的需要一个一元单位时,使用
pure
,它可以做同样的事情,而不会产生令人遗憾的命名影响。
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