在Haskell上进行练习,类型定义和守卫。

4

第一个问题:
定义一个函数,使用分隔符将列表中的所有列表连接在一起。
类型定义应该如下:

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 

但是,正如您所看到的,它没有类型定义。
如果我将类型定义放在函数上面,我会得到一个错误。 为什么?

第二个问题:
在解决方案之前,我想在保护程序列表中添加另一个保护程序。 这个保护程序应该在第一个保护程序之后。 我想检查列表变量是否只有一个列表,所以我只返回列表变量。 但我不能像那样设置保护程序(同样,会出现错误:-)):


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

and also this didn't work:


| lists == (_:[]) = lists
why why why ? :-).

之后我尝试创建其他的守卫:


| length lists == 1    = lists

但它引发了一个错误。

顺便说一句,我不需要那些保护,因为我发现在“where”关键字后的第一个模式恰好是我想要的。
这就是我所说的模式:
attach myChar (x:[]) = x

但是,我仍然想了解为什么我尝试的保护没有起作用。 而且,我是通过运气找到这个解决方案的,我不认为每次都能注意到这样的事情:-)

非常感谢 :-)。

p.s. 这个练习来自书籍《Real World Haskell》。


顺便提一下,这个可以用一个fold非常简单地写成:intersperse sep = foldr1 (\i a -> i ++ sep : a) - Chuck
4个回答

3
  1. "" is of type [Char], but your type signature says intersperse returns a [a], where a depends on the input type, so the types don't match.

  2. I don't think you can pattern match inside guards.

    For this guard

    | length lists == 1    = lists
    

    lists is of type [[a]], but you are supposed to be returning a [a]. That is, if lists is ["foo"], you want to return "foo". You do not want to return ["foo"].


再次伟大,我非常理解你 :-)。 非常感谢这个 :-)。 - Azriel
2
语言扩展 {-# LANGUAGE PatternGuards #-} 允许您在保护子句中进行模式匹配。 - ephemient

2
问题在于你的函数不够通用,它只能处理字符串(字符列表)类型。如果你把第二行改成下面这样:
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. 我执行了 lists == [] = [] 并加入了类型定义,但是它引发了一个错误。可能是因为等于号的代码(我现在不理解)。
  2. 我不明白你建议给我的修复和你代码的第一个例子之间有什么区别(那个也可以很好地使用类型定义)。你只是用了 case of,而我使用 guards,所以为什么我会出错,而你的例子却一切正常?
  3. 你说的习惯用语是什么意思?我对英语不是很流利,请解释一下 :-)。
- Azriel
请参见以下链接,其中包含有关Haskell类型定义和守卫的练习题:https://dev59.com/tEvSa4cB1Zd3GeqPdUw_#2057352 - Azriel
所谓习惯用法,是指更像 Haskell 风格的写法:Haskell 人写 Haskell 的方式。你知道吗?你可以用任何语言来编写 COBOL。但是当我学习一门新语言时,我喜欢学习更有经验的从业者(如果你愿意,可以称为母语使用者)如何使用它,而不仅仅是编译器能理解的内容。 - Thiago Arrais
修复和第一个示例之间的区别是,正如您正确注意到的那样,示例使用模式匹配(case of表达式),而您的原始代码使用了守卫。守卫表达式需要被评估为单个布尔值,这就是您通过使用等号运算符所实现的。它只是一个常规运算符,可以在函数体中的任何地方使用,您只是碰巧在守卫子句中使用它。因此,它需要两个完全定义的值才能工作,您无法在其中匹配模式。但是,在case表达式内部,您可以(并且应该)进行模式匹配。 - Thiago Arrais
顺便说一下,如果你在代码中将guard更改为length lists == 0,那么代码也将类型检查。在Haskell中,等号运算符是由Eq类型类提供的,只有它的类型可以进行比较。当你使用等号将输入与空列表进行比较时,你只是将你的代码绑定到了Eq类。通过使用长度,你要比较的是函数返回的整数和整数0,而不是列表本身。你也可以通过使用null来实现相同的效果(查看它的类型签名,你就会明白为什么)。 - Thiago Arrais
谢谢您的评论,您能看一下这个链接吗? https://dev59.com/tEvSa4cB1Zd3GeqPdUw_#2057352 - Azriel

1

这会引发一个错误。
为什么呢? 除了你用了 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

this don't raise an error(it's what you seggested):


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,并且未将结果为空的字符串更改为空的列表,则仍将出现类型错误。您的空列表情况将返回一个字符串,这将强制通用类型a更改为具体类型[Char](或String)。通过更改守卫条款,您只是摆脱了Eq类型类,但如果不更改返回值,则函数签名将需要更具体的类型[Char] - Thiago Arrais

1

可能错了,但似乎能运行

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接