Заменить элементы в списке - PullRequest
0 голосов
/ 01 мая 2019

Мне нужно реализовать функцию, которая заменяет элементы в списке - индекс для замены - это fst в кортеже и snd в кортеже это то, что заменить его. и меня просят использовать функцию foldr или map.

например:

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) [] 
   |

Как я могу исправить ошибку?

1 Ответ

4 голосов
/ 01 мая 2019

Давайте посмотрим на вашу функцию:

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]. И вы только даете ему первые 2 аргумента. Так что 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) терминов, которые говорят нам, что вставить и где - это то, что я назвал ps (p для "пары"). И поскольку мы начинаем со списка a s - который я здесь логически назвал as - тогда это должно быть начальное значение аккумулятора, которое является вторым аргументом foldr.

Таким образом, все, что остается, - это функция сгиба, которая берет пару и список и обновляет список в соответствии со значениями в паре. Это функция, которую вы уже используете:

\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

может быть сначала "уменьшено на eta" до

\(i, a) -> setElement i a

, который, используя стандартную библиотечную функцию, просто uncurry setElement.

На данном этапе нам явно не нужно предложение where (фактически мы никогда не делали этого раньше, но, тем не менее, оно помогает удобочитаемости для любой лямбды, которая не является достаточно тривиальной) и может просто написать:

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

На самом деле, хотя я не обязательно рекомендую это, если мы играем в гольф, вы можете пойти еще дальше и просто написать:

setElements = flip . foldr . uncurry $ setElement

Лично я считаю, что способность выражать относительно сложные функции в сжатой форме, как указано выше, определенно является частью привлекательности Haskell. Но вместо того, чтобы пытаться написать что-то подобное прямо сейчас, по моему мнению, всегда лучше начать с чего-то очень конкретного, показывающего, как вы хотите преобразовать свои данные - и, только после того, как вы это заработаете, ищите более сжатое представление, если вы хочу.

...