Haskell:解析错误,可能是缩进不正确或括号不匹配。

5
当我尝试使用下面的函数实现类似下面的函数时,编译器会返回“解析错误(可能是不正确的缩进或括号不匹配)”。
函数:
demo 8 [1,2,3] 应该返回 [1,2,3,1,2,3,1,2]
 demo :: Int -> [a] -> [a] 
    let n = 0 
    demo arg [] = [] 
    demo arg (x:xs) = 
         if arg <= length (x:xs) then
             take arg (x:xs) 
         else
             let (x:xs) = (x:xs) ++ (x:xs)!!n
             arg = arg - 1
             n = n + 1
             demo arg (x:xs)

我该如何纠正这个问题呢? 谢谢!


1
可能存在不正确的缩进。let..in块的缩进似乎不正确。尝试调整其缩进以使代码编译。 - Mark Seemann
1
let arg = arg - 1let n=n+1 是递归定义,将 argn 定义为无限循环计算。你不想要这样。你试图在 Haskell 中改变变量的值,但是它被设计成不可能做到这一点。这种方法对于函数式编程语言来说过于命令式而无法实现。大致上来说,“更改”变量的唯一方法是调用一个新值的函数。例如:f 0 = 0; f n = f (n-1) 定义了一个递归,它“减少” n 直到达到 0。 - chi
2个回答

7
您正在混淆命令式编程和函数式编程范式:let n = 0let (x:xs) = (x:xs) ++ (x:xs)!!narg = arg - 1n = n + 1 是(在您的代码中)命令式表达式。您期望 n(x:xs)arg 的值被修改,但函数式编程不是这样工作的。
您声明的函数应该是纯函数:这意味着您不能指望值被修改(我们称之为“副作用”)。您唯一能做的是使用从原始参数“即时”计算出来的新参数调用一个新函数(或相同函数)。
让我们尝试具体一些。
以下操作是无法实现的:
arg = arg - 1
demo arg (x:xs)

但您可以这样做:
demo (arg - 1) (x:xs)

后者是使用arg - 1作为参数调用demoarg的值从未被修改。
您代码中的主要问题在于变量n。它应该在第一次调用时为0,并且每当arg < length (x:xs)时都应增加以将(x:xs)的下一个元素添加到(x:xs)的末尾。因此,列表将以循环方式增长,直到我们可以取出所需数量的元素。
为了实现这个目标,您必须创建一个辅助函数,并将递归“移动”到该辅助函数中:
demo :: Int -> [a] -> [a]
demo arg [] = [] 
demo arg (x:xs) = demo' arg 0 (x:xs) -- n = 0
    where 
        demo' :: Int -> Int -> [a] -> [a]
        demo' arg n l = if arg <= length l 
                        then take arg l 
                        else 
                            -- call demo' with new parameters:
                            -- (x:xs) = (x:xs) ++ [(x:xs)!!n]
                            -- arg = arg - 1
                            -- n = n + 1 
                            demo' (arg-1) (n+1) ((x:xs) ++ [(x:xs)!!n]) ```

现在,从您给出的示例中,arg是您想创建的列表中元素数量的常数,因此不应减少。而且您不需要在 (x:xs) 中拆解该列表。最后,稍微清理一下,您就有了:

demo :: Int -> [a] -> [a]
demo arg [] = [] 
demo arg l = demo' arg 0 l
    where 
        demo' :: Int -> Int -> [a] -> [a]
        demo' arg n l = if arg <= length l 
                        then take arg l 
                        else demo' arg (n+1) (l ++ [l!!n])

有一种更好的方法可以实现这个功能(demo n=take n.cycle),但我试图保持与你原来的实现接近。


谢谢jferard!你的代码正是我想要的。我以为问题是由变量n引起的,但我不知道如何修复它,感谢你回答我的问题。 - Sean
@Sean 没问题。你应该接受我的答案或者 ephemient 的答案。 - jferard

3
你不能在顶层写let n = 0。一旦删除了它,你的代码中else let 部分的缩进也不正确,因为它的使用(在 do块之外)始终是let ... in ...,且let部分的内容必须等同缩进。即使格式化了,let 是递归的,所以 arg = arg - 1意味着一个比其自身小一的值,这是不正确的。

现在,这个函数实际上做了两件事情: 它遍历列表中的所有元素,并限制其长度为给定长度。这两个都已经可以在标准库中使用。

demo n xs = take n (cycle xs)

如果你想亲自编写,类似的分解也是合理的。
demo :: Int -> [a] -> [a]
-- Handle negative n, so we can assume it's positive below
demo n _ | n < 0 = []
-- Similarly, handle empty list, so we can assume it's non-empty below
demo _ [] = []
-- Given a positive n and non-empty list, start cycling list with limit n.
demo n list = demo' n list
  where
    -- Limit has been reached, stop.
    demo' 0 _ = []
    -- List is exhausted, cycle back to the start.
    demo' m [] = demo' m list
    -- Take the next element of the list and continue.
    demo' m (x:xs) = x : demo' (m-1) xs

请注意,没有必要使用length,这是一件好事!length在无限列表上会发散,而这种处理方式更加优雅。

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