替换列表中的元素

问题描述 投票:0回答:1

我需要实现一个替换列表中元素的函数 - 要替换​​的索引是元组中的fst,而元组中的snd是替换它的内容。我被要求使用foldrmap功能。

例如:

setElements   [(1, 'a'), (-4, 't'), (3, 'b')] "#####" = "#a#b#" 

setElements函数无法编译:

我的代码:

setElement :: Int -> a -> [a] -> [a]
setElement n x xs = if ((n < length xs) && n >= 0)
                    then (take n xs) ++ [x] ++ (drop (n + 1) xs)
                    else xs

setElements :: [(Int, a)] -> [a] -> [a]
setElements = foldr (\t l-> setElement (fst t) (snd t) l) [] 

我明白了:

• Couldn't match type ‘[a]’ with ‘[a] -> [a]’ 
  Expected type: [(Int, a)] -> [a] -> [a] 
  Actual type: [(Int, a)] -> [a] 
• Possible cause: ‘foldr’ is applied to too many arguments 
  In the expression: foldr (\ t l -> setElement (fst t) (snd t) l) [] 
  In an equation for ‘setElements’: 
      setElements = foldr (\ t l -> setElement (fst t) (snd t) l) [] 
• Relevant bindings include 
    setElements :: [(Int, a)] -> [a] -> [a] 
    (bound at hw3.hs:79:1) 
   | 
79 | setElements = foldr (\t l-> setElement (fst t) (snd t) l) [] 
   |

我该如何修复错误?

haskell compiler-errors fold type-mismatch map-function
1个回答
4
投票

我们来看看你的功能:

setElements :: [(Int, a)] -> [a] -> [a]
setElements = foldr (\t l-> setElement (fst t) (snd t) l) []

并回想一下foldr的类型:

foldr :: (a -> b -> b) -> b -> [a] -> b

在你使用foldr时,你有a作为(Int, a)b作为[a]。你只给它前两个参数。所以foldr (\t l-> setElement (fst t) (snd t) l) []有类型[(Int, a)] -> [a] - 而setElements应该有类型[(Int, a)] -> [a] -> [a]。请注意这些与GHC在错误消息中报告的“实际类型”和“预期类型”完全匹配。

要解决这个问题,我实际上会倒退一步。折叠是正确的想法 - 你的setElement函数已经根据索引和新值修改了原始列表(它的第三个参数),你想要的是获取编码这些数据的对列表,并继续应用此函数进行更新原始列表反复出现。 (当然这是Haskell所以数据是不可变的 - 你不是在实际上更新它,而是每次只返回一个新的列表。但有时像这样松散地说话更容易。)

这正是折叠的原因。让我们尝试写出来,而不是试图用“无点”方法过于花哨,而是完全应用它:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
    where myFunction = undefined

这里的undefined只是一个占位符 - 如果你试图使用该函数会导致运行时错误(但不会导致编译错误),我把它放在那里因为我们需要考虑这个,折叠函数通常是实施折叠的最棘手的部分。但是让我们检查一下我们理解其他术语在做什么:我们实际上是“走在一起”的列表是(Int, a)术语的列表,它告诉我们要插入什么以及在哪里 - 这就是我所谓的psp用于“对“)。因为我们从as列表开始 - 我在这里逻辑上称为as - 然后那应该是起始累加器值,这是foldr的第二个参数。

剩下的就是fold函数 - 它接受一对和一个列表,并根据对中的值更新列表。那么这就是你已经使用的功能:

\t l-> setElement (fst t) (snd t) l

或者,用模式匹配重写(我发现它更具可读性,因此我认为大多数Haskell开发人员更喜欢这种方式):

\(i, a) as -> setElement i a as

所以,用这个代替,我们得出以下定义:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
    where myFunction = \(i, a) as -> setElement i a as

现在这将编译并正常工作。但是当你有一个工作函数时,它总是值得退一步,看看你是否可以简化它的定义。实际上myFunction可以简化一点:

\(i, a) as -> setElement i a as

首先可以“减少”到

\(i, a) -> setElement i a

使用标准库函数,它只是uncurry setElement

在这个阶段我们显然不再需要一个where子句(实际上我们以前从未做过,但是它有助于任何lambda的可读性,这不是很简单),并且可以写:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr (uncurry setElement) as ps

事实上,虽然我不一定会推荐它,但如果我们正在打代码高尔夫,你甚至可以更进一步,只需写下:

setElements = flip . foldr . uncurry $ setElement

我个人认为能够以简洁的方式表达相对复杂的功能,如上所述,绝对是Haskell魅力的一部分。但是,在我看来,不要试图直接写出这样的东西,而是始终最好从一些非常具体的东西开始,展示你想要如何转换你的数据 - 而且,只有在完成这项工作之后,才能找到一个更简洁的代表,如果你想要。

© www.soinside.com 2019 - 2024. All rights reserved.