[英]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.