Давайте посмотрим на вашу функцию:
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. Но вместо того, чтобы пытаться написать что-то подобное прямо сейчас, по моему мнению, всегда лучше начать с чего-то очень конкретного, показывающего, как вы хотите преобразовать свои данные - и, только после того, как вы это заработаете, ищите более сжатое представление, если вы хочу.