簡體   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