【问题标题】:exercise on haskell, type definition, and guards练习haskell、类型定义和守卫
【发布时间】:2026-01-24 14:00:01
【问题描述】:

第一个问题:
定义一个使用分隔符值将列表连接在一起的函数。
类型定义应该是这样的:

intersperse :: a -> [[a]] -> [a]

分隔符应出现在列表元素之间,但不应跟在最后一个元素之后。
您的函数应如下所示:


ghci> :load Intersperse
[1 of 1] Compiling Main             ( Intersperse.hs, interpreted )
Ok, modules loaded: Main.
ghci> intersperse ',' []
""
ghci> intersperse ',' ["foo"]
"foo"
ghci> intersperse ',' ["foo","bar","baz","quux"]
"foo,bar,baz,quux"

一段时间后我设法解决了它:


intersperse myChar lists
    | lists == []          = ""  
    | otherwise            = attach myChar lists
        where   attach myChar (x:[]) = x 
                attach myChar (x:xs) = x ++ (myChar : []) ++ attach myChar xs 

但是,如您所见,它没有类型定义。
如果我将类型定义放在函数上方,则会出现错误。 为什么?

第二个问题:
在我得到这个解决方案之前,我想在警卫列表中添加另一个警卫。 这个quard应该在第一个后卫之后。 我想检查列表变量中是否只有一个列表,所以我只返回列表变量。 但我不能做那样的守卫(再次,一个错误出现了:-)):


| lists == (x:[]) = lists

这也没用:


| lists == (_:[]) = lists
为什么为什么为什么? :-)。

在此之后,我试图让其他后卫:


| length lists == 1    = lists

但它也引发了错误。

(顺便说一句,我不需要那些守卫,因为我发现“where”关键字之后的第一个模式正是我想要的。
这就是我的意思:
附上 myChar (x:[]) = x

但是,我仍然想了解为什么我尝试的 quards 不起作用。 另外,我很幸运地找到了这个解决方案,而且我不认为每次我都会注意到这样的事情:-)

非常感谢:-)。

附言 这个练习来自本书real world haskell

【问题讨论】:

  • 顺便说一句,这可以很简单地写成intersperse sep = foldr1 (\i a -> i ++ sep : a)

标签: haskell


【解决方案1】:
  1. "" 的类型为 [Char],但您的类型签名说 intersperse 返回 [a],其中 a 取决于输入类型,因此类型不匹配。

  2. 我认为你不能在守卫内部进行模式匹配。

    为了这个守卫

    | length lists == 1    = lists
    

    lists 的类型为 [[a]],但您应该返回 [a]。也就是说,如果lists["foo"],你想返回"foo"。你不想返回["foo"]

【讨论】:

【解决方案2】:

问题是您的函数没有通用化,它仅适用于字符串(Char 列表)。如果您将第二行更改为

lists == []          = []

你会得到你想要的,尽管由于等于运算符而依赖于Eq 类型类。它甚至适用于字符串,因为所有字符串也是列表,但并非所有列表都是字符串。

顺便说一句,您可以通过使用模式匹配进一步概括您的功能:

intersperse myChar lists = case lists of
    []      -> []
    lists   -> attach myChar lists
    where   attach myChar (x:[]) = x 
            attach myChar (x:xs) = x ++ (myChar : []) ++ attach myChar xs 

或者,更惯用的说法:

intersperse _ [] = []
intersperse x xs = attach x xs
    where   attach myChar (x:[]) = x 
            attach myChar (x:xs) = x ++ (myChar : []) ++ attach myChar xs

并摆脱内部功能:

intersperse _ [] = []
intersperse _ (xs:[]) = xs
intersperse x (xs:xss) = (xs ++ x:intersperse x xss)

关于第二个问题,您在警卫中使用的等号运算符需要双方都有值。您无法与它进行模式匹配。也许您正在寻找的是类似于这里的第二个改进。

【讨论】:

  • 1.我做了 list == [] = [] 并输入了类型定义,但它引发了错误。也许是因为 eq 代码(我现在不明白)。 2. 我不明白您向我建议的修复与您的代码的第一个示例之间有什么区别(这也适用于类型定义)。您刚刚使用了案例并且我使用了警卫,那么为什么我会收到错误消息,并且在您的示例中一切都很好用? 3. 惯用语是什么意思?我的英语不是很流利,请解释一下:-)。
  • 按惯用语,我的意思是更具有 Haskell 风格:Haskell 人编写 Haskell 的方式。你可以用任何语言编写 COBOL,你知道吗?但是当我学习一门新语言时,我喜欢了解更有经验的从业者(如果你愿意,可以说母语)如何使用它,而不仅仅是编译器会理解的内容。
  • 修复和第一个示例之间的区别在于,正如您正确注意到的那样,该示例使用模式匹配(case of 表达式),而您的原始代码使用警卫。守卫表达式需要被评估为单个 Bool 值,这就是您使用 equals 运算符完成的工作。它只是一个普通的运算符,可以在函数体的任何地方使用,你只是碰巧在保护子句中使用了它。因此,它需要两个完全定义的值才能工作,您无法匹配其中的模式。但是,在 case 表达式中,您可以(并且应该)进行模式匹配。
  • 顺便说一句,如果你将代码中的守卫更改为length lists == 0,代码也会键入检查。 Haskell 中的 equals 运算符是由 Eq 类型类提供的,唯一可以比较相等的东西是它的类型。当您使用 equals 将条目与空列表进行比较时,您只需将代码绑定到 Eq 类。通过使用长度,您要比较的相等性是函数返回的整数与整数 0,而不是列表本身。你也可以通过使用null 来达到同样的效果(检查它的类型签名,你就会明白为什么)。
【解决方案3】:

这会引发错误。
为什么? 它和你做的一样,除了你用case of写它。


intersperse :: a -> [[a]] -> [a]
intersperse myChar lists
    | lists == []          = []  
    | otherwise            = attach myChar lists
        where   attach myChar (x:[]) = x 
                attach myChar (x:xs) = x ++ (myChar : []) ++ attach myChar xs

这不会引发错误(这就是你所分割的):


intersperse :: a -> [[a]] -> [a]
intersperse myChar lists = case lists of
    []      -> []
    lists   -> attach myChar lists
    where   attach myChar (x:[]) = x 
            attach myChar (x:xs) = x ++ (myChar : []) ++ attach myChar xs

但在我的示例中(此处的第一个函数),我将防护更改为您建议的内容,但仍然出现错误。

这是为什么呢?

非常感谢。

【讨论】:

  • 如果您将函数中的保护子句从问题更改为length lists == 0,并且没有将生成的空string更改为空list,你仍然会得到一个类型错误。您的空列表案例将返回一个字符串,这将强制将泛型类型 a 更改为具体类型 [Char](或 String)。通过更改保护子句,您只需摆脱 Eq 类型类,但如果您不更改返回值,函数签名将需要更具体的类型 [Char]
【解决方案4】:

它可能是错误的,但它似乎有效

intersperse :: a -> [[a]] -> [a]
intersperse _ []             = []
intersperse separator [xs]   = xs
intersperse separator (x:xs) = x ++ [separator] ++ (intersperse separator xs)

【讨论】: