Как бы я реализовал эту функцию сгиба?
Даны два типа данных: Цвет и Растение.
data Color = Red | Pink | White | Blue | Purple | Green | Yellow
deriving (Show, Eq)
data Plant =
Leaf
| Blossom Color
| Stalk Plant Plant
deriving (Show, Eq)
Теперь я должен реализовать функцию fold_plant
следующего типа:
(x -> x -> x) -> (Color -> x) -> x -> Plant -> x
Насколько я понимаю, функция сгиба состоит в том, что она берет список и для каждой итерации удаляет первый элемент из списка и что-то делает с этим элементом.
По-видимому fold_plant Stalk Blossom Leaf
это личность на растениях.
Теперь я знаю, что в Haskell вы делаете такие функции:
fold_plant :: (x -> x -> x) -> (Color -> x) -> x -> Plant -> x
fold_plant = do something
Но с этого момента я не знаю, как работает функция сгиба на растении.
3 ответа
Если мы посмотрим на сигнатуру функции:
fold_plant :: (x -> x -> x) -> (Color -> x) -> x -> Plant -> x
-- \_____ _____/ \____ _____/ | | |
-- v v v v v
-- stalk blossom leaf tree output
Мы видим, что есть stalk
часть, а также blossom
часть и leaf
часть. Мы назовем stalk
функция s
и blossom
функция b
здесь и leaf
часть l
, Чтобы упростить (и оптимизировать) функцию, мы распаковываем эти три параметра, а затем вызываем рекурсивный метод:
fold_plant s b l = fold_plant'
where fold_plant' = ...
Теперь вопрос, конечно, что делать с fold_plant'
, Учитывая, что мы видим Leaf
нам не нужно выполнять никаких действий со значениями, мы просто возвращаем наш конечный результат l
:
fold_plant' Leaf = l
На случай, если мы найдем (Blossom c)
с c
цвет, мы должны выполнить отображение из c :: Color
для x
с b
часть, чтобы получить новое значение:
fold_plant' (Blossom c) = b c
Наконец, в случае, если у нас есть Stalk
нам придется выполнить рекурсию: сначала позвоним fold_plant'
на левом стебле, а затем мы называем fold_plant'
и построить s
с двумя результатами:
fold_plant' (Stalk lef rig) = s (fold_plant' lef) (fold_plant' rig)
Таким образом, мы можем объединить все это в следующую функцию:
fold_plant s b l = fold_plant'
where fold_plant' Leaf = l
fold_plant' (Blossom c) = b c
fold_plant' (Stalk lef rig) = s (fold_plant' lef) (fold_plant' rig)
Сгиб - это функция, которая берет фрагмент данных в структуре и сворачивает его в другой фрагмент данных. Обычно мы делаем это, чтобы "уменьшить" коллекцию до одного значения. Вот почему, если вы посмотрите на другие языки, такие как Lisp, Smalltalk, Ruby, JavaScript и т. Д., Вы обнаружите, что эта операция называется reduce
, который является бедным двоюродным братом в Хаскелле.
Я говорю, что это плохой родственник, потому что ваша интуиция в списках верна, но в Haskell мы гораздо более абстрактны и общие, поэтому наши функции свертывания могут работать с любым типом структуры, тип которой мы сказали haskell, что означает сворачивание.
Итак, мы можем поговорить об "использовании сложения со сложением, чтобы превратить список чисел в значение суммы", или мы можем поговорить об "использовании функции, чтобы взять семейное древо имен и свернуть его в список", и так далее. и так далее. Каждый раз, когда у нас появляется идея изменить структуру чего-либо на одно значение или, возможно, на другой структурированный набор значений, это сворачивается.
"Канонический" способ представить это в Хаскеле foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
но это проще, чем вы думали использовать "список a
"как Foldable f => t a
напечатайте в начале, потому что это немного легче понять. Таким образом, у нас есть специализированный тип foldr :: (a -> b -> b) -> b -> [a] -> b
, Но каковы a
а также b
? и что (a -> b -> b)
и что делают эти три аргумента?
Давайте специализируемся на Int
значения для обоих a
а также b
: foldr :: (Int -> Int -> Int) -> Int -> [Int] -> Int
вау... это делает это... интересно не так ли? так foldr
принимает функцию двух целых, как, скажем, (+)
функция, и это занимает один Int
значение (это начальное значение, которое он будет использовать в качестве целевого результата, и список Int
значения... тогда он будет производить один Int
от этого... то есть, что берет Int -> Int -> Int
функция и применяет его к единому Int
и первый из [Int]
, затем применяет эту функцию к этому результату и следующему значению [Int]
и так далее и тому подобное, пока не осталось больше [Int]
осталось... то вот что возвращается.
Это буквально сворачивание функции по структуре данных.
Это все хорошо для списков, которые представляют собой одну прямую линию, но у вас есть дерево, а не список. Так как там работает? Что ж, давайте посмотрим, как мы можем специализироваться foldr
получить пару старших и младших чисел из списка Int
вместо? foldr :: (Int -> (Int, Int) -> (Int, Int)) -> (Int, Int) -> [Int] -> (Int, Int)
, Итак, мы берем функцию, которая принимает Int
и пару, и мы помещаем в нее исходную пару вместе с первым Int
от нашего [Int]
, Это возвращает нам новую пару, и мы затем делаем тот же процесс для следующего из [Int]
и затем мы продолжим этот процесс до тех пор, пока у нас не останется пара в конце.
foldToMinMax = foldr (\newNum (minnum,maxnum) -> (min minnum newNum, max maxnum newNum)) (maxBound :: Int, minBound :: Int)
Так что теперь все становится немного яснее.
А как насчет этого дерева цветов? Что ж, вам нужно написать себе функцию сворачивания, которая будет принимать два значения, одно из которых будет совпадать с начальным значением и значением результата, а другое - с типом вашего дерева, и создавать значение типа результата. Если бы я использовал псевдокод для написания типов более наглядно, я бы написал что-то вроде: foldr :: (contentOfCollectionType -> resultType -> resultType) -> resultType -> (collectionWrapper contentOfCollectionType) -> resultType
Но вам не нужно использовать foldr
здесь, на самом деле, вы не можете использовать его, если вы все равно не сделаете какой-нибудь причудливый экземпляр класса типов. Вы можете полностью написать свою собственную функцию свертывания, используя простую рекурсию. Это то, что они после.
Если вы хотите узнать о рекурсии, фальцовке и тому подобном, и пока не разбираетесь в этом, я рекомендую книгу, в которой помогал автору. http://happylearnhaskelltutorial.com/ Это объясняет это гораздо более подробно и с множеством четких примеров. Если вы понимаете основы, вам следует быстро освоиться с тем моментом, когда вы хотите узнать о рекурсии и фолде... но если вы этого не сделаете, вам будет очень полезно понять основы, потому что вы должны знать их, прежде чем перейти к другим вещам.
Я должен отметить, что в вашем конкретном фолде также есть функция конвертации. Это вещь, которая преобразует Color
для x
, Функция, с которой вам было поручено работать в качестве функции свертывания, "разбивает икс" (т.е. занимает два x
ценит и производит другое x
значение, очень похожее на (+)
в наших примерах выше). Он может работать только на деревьях, потому что мы также даем ему эту функцию, чтобы повернуть Color
в x
, который эффективно извлекает значимые данные из дерева и помещает их в форму, которую может использовать функция свертывания.
Здесь очень красивый рисунок на работе.
Удачи!
Складывание - это суть решения рекурсивных задач:
data Plant = data Result r =
Leaf RLeaf
| Blossom Color | RBlossom Color
| Stalk Plant Plant | RStalk r r
-- recursive data -- non-recursive data: `r`, not `Result r`!
Решение рекурсивных задач заключается в простом объединении результатов рекурсивной обработки составляющих исходной структуры:
-- single-step reduction semantics:
-- reduction_step :: ..... -> Result r -> r
reduction_step :: (r -> r -> r) -> (Color -> r) -> r -> Result r -> r
reduction_step s b l RLeaf = l
reduction_step s b l (RBlosom c) = b c
reduction_step s b l (RStalk x y) = s x y
Но чтобы добраться до этой точки, нам нужно вернуться к составным частям нашей первоначальной структуры, которые имеют ту же природу, что и вся структура, и, следовательно, fold_plant
Процедура, которую мы стремимся создать, может быть применена к ним, как если бы она уже была написана (рекурсия!):
recurse_into :: (r -> r -> r) -> (Color -> r) -> r -> Plant -> Result r
recurse_into s b l Leaf = RLeaf
recurse_into s b l (Blossom c) = RBlossom c
recurse_into s b l (Stalk lt rt) = RStalk (fold_plant s b l lt) (fold_plant s b l rt)
так что, наконец, наша складка - это просто композиция из двух,
fold_plant :: (r -> r -> r) -> (Color -> r) -> r -> Plant -> r
fold_plant s b l plant = reduction_step s b l -- Result r -> r
(recurse_into s b l plant) -- Plant -> Result r
Следуйте за типами и убеждайте себя, что все соединяется как следует.
Конечно, временное определение данных может быть отменено, а определение функции свернуто, но это общая процедура, которой необходимо следовать.
(см. рекурсивные схемы).