Читаем Учебник по Haskell полностью

Обратите внимание на рекурсивный вызов функции foldNat мы обходим всё дерево значения, заменяя

каждый конструктор. Определим знакомые функции через свёртку:

isZero :: Nat -> Bool

isZero = foldNat True (const False)

Посмотрим как вычисляется эта функция:

isZero Zero

=>

True

-- заменили конструктор Zero

isZero (Succ (Succ (Succ Zero)))

=>

const False (const False (const False True))

-- заменили и Zero и Succ

=>

False

Что интересно за счёт ленивых вычислений на самом деле во втором выражении произойдёт лишь одна

замена. Мы не обходим всё дерево, нам это и не нужно, а смотрим лишь на первый конструктор, если там

Succ, то произойдёт замена на постоянную функцию, которая игнорирует свой второй аргумент и рекурсив-

ного вызова функции свёртки не произойдёт, совсем как в исходном определении!

even, odd :: Nat -> Bool

even

= foldNat True

not

odd

= foldNat False not

Эти функции определяют чётность числа, сдесь мы пользуемся тем свойством, что not (not a) == a.

Определим сложение и умножение:

add, mul :: Nat -> Nat -> Nat

add a

= foldNat a

Succ

mul a

= foldNat Zero

(add a)

Свёртка | 193

Maybe

Вспомним определение типа для результата частично определённых функций:

data Maybe a = Nothing | Just a

Перепишем словно это класс:

data Maybe a b where

Nothing :: b

Just

:: a -> b

Этот класс принимает два параметра, поскольку исходный тип Maybe принимает один. Теперь несложно

догадаться как будет выглядеть функция свёртки, мы просто получим стандартную функцию maybe. Дадим

определение экземпляра функтора и монады через свёртку:

instance Functor Maybe where

fmap f = maybe Nothing (Just . f)

instance Monad Maybe where

return

= Just

ma >>= mf

= maybe Nothing mf ma

Списки

Функция свёртки для списков это функция foldr. Выведем её из определения типа:

data [a] = a : [a] | []

Представим, что это класс:

class [a] b where

cons

:: a -> b -> b

nil

:: b

Теперь получить определение для foldr совсем просто:

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

foldr cons nil = \x -> case x of

a:as

-> a ‘cons‘ foldr cons nil as

[]

-> nil

Мы обходим дерево значения, заменяя конструкторы методами нашего воображаемого класса. Опреде-

лим несколько стандартных функций для списков через свёртку.

Первый элемент списка:

head :: [a] -> a

head = foldr const (error ”empty list”)

Объединение списков:

(++) :: [a] -> [a] -> [a]

a ++ b = foldr (:) b a

В этой функции мы реконструируем заново первый список но в самом конце заменяем пустой список в

хвосте a на второй аргумент, так и получается объединение списков. Обратите внимание на эту особенность,

скорость выполнения операции (++) зависит от длины первого списка. Поэтому между двумя выражениями

((a ++ b) ++ c) ++ d

a ++ (b ++ (c ++ d))

Нет разницы в итоговом результате, но есть огромная разница по скорости вычисления! Второй гораздо

быстрее. Убедитесь в этом! Реализуем объединение списка списков в один список:

concat :: [[a]] -> [a]

concat = foldr (++) []

194 | Глава 12: Структурная рекурсия

Через свёртку можно реализовать и функцию преобразования списков:

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

map f = foldr ((:) . f) []

Если смысл выражения ((:) . f) не совсем понятен, давайте распишем его типы:

f

(:)

a

------->

b

------->

([b] -> [b])

Напишем функцию фильтрации:

filter :: (a -> Bool) -> [a] -> [a]

filter p = foldr (\a as -> foldBool (a:as) as (p a)) []

Тут у нас целых две функции свёртки. Если значение предиката p истинно, то мы вернём все элементы

списка, а если ложно отбросим первый элемент. Через foldr можно даже определить функцию с хвостовой

рекурсией foldl. Но это не так просто. Всё же попробуем. Для этого вспомним определение:

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

foldl f s []

= s

foldl f s (a:as)

= foldl f (f s a) as

Нам нужно привести это определение к виду foldr, нам нужно выделить два метода воображаемого

класса списка cons и nil:

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

foldr cons nil = \x -> case x of

a:as

-> a ‘cons‘ foldr cons nil as

[]

-> nil

Перенесём два последних аргумента определения foldl в правую часть, воспользуемся лямбда-

функциями и case-выражением:

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

foldl f = \x -> case x of

[]

-> \s -> s

a:as

-> \s -> foldl f as (f s a)

Мы поменяли местами порядок следования аргументов (второго и третьего). Выделим тождественную

функцию в первом уравнении case-выражения и функцию композиции во втором.

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

foldl f = \x -> case x of

[]

-> id

a:as

-> foldl f as . (flip f a)

Теперь выделим функции cons и nil:

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

foldl f = \x -> case x of

[]

-> nil

a:as

-> a ‘cons‘ foldl f as

where nil

= id

cons

= \a b -> b . flip f a

= \a

-> ( . flip f a)

Теперь запишем через foldr:

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

foldl f s xs = foldr (\a -> ( . flip f a)) id xs s

Перейти на страницу:

Похожие книги

1С: Бухгалтерия 8 с нуля
1С: Бухгалтерия 8 с нуля

Книга содержит полное описание приемов и методов работы с программой 1С:Бухгалтерия 8. Рассматривается автоматизация всех основных участков бухгалтерии: учет наличных и безналичных денежных средств, основных средств и НМА, прихода и расхода товарно-материальных ценностей, зарплаты, производства. Описано, как вводить исходные данные, заполнять справочники и каталоги, работать с первичными документами, проводить их по учету, формировать разнообразные отчеты, выводить данные на печать, настраивать программу и использовать ее сервисные функции. Каждый урок содержит подробное описание рассматриваемой темы с детальным разбором и иллюстрированием всех этапов.Для широкого круга пользователей.

Алексей Анатольевич Гладкий

Программирование, программы, базы данных / Программное обеспечение / Бухучет и аудит / Финансы и бизнес / Книги по IT / Словари и Энциклопедии
1С: Управление торговлей 8.2
1С: Управление торговлей 8.2

Современные торговые предприятия предлагают своим клиентам широчайший ассортимент товаров, который исчисляется тысячами и десятками тысяч наименований. Причем многие позиции могут реализовываться на разных условиях: предоплата, отсрочка платежи, скидка, наценка, объем партии, и т.д. Клиенты зачастую делятся на категории – VIP-клиент, обычный клиент, постоянный клиент, мелкооптовый клиент, и т.д. Товарные позиции могут комплектоваться и разукомплектовываться, многие товары подлежат обязательной сертификации и гигиеническим исследованиям, некондиционные позиции необходимо списывать, на складах периодически должна проводиться инвентаризация, каждая компания должна иметь свою маркетинговую политику и т.д., вообщем – современное торговое предприятие представляет живой организм, находящийся в постоянном движении.Очевидно, что вся эта кипучая деятельность требует автоматизации. Для решения этой задачи существуют специальные программные средства, и в этой книге мы познакомим вам с самым популярным продуктом, предназначенным для автоматизации деятельности торгового предприятия – «1С Управление торговлей», которое реализовано на новейшей технологической платформе версии 1С 8.2.

Алексей Анатольевич Гладкий

Финансы / Программирование, программы, базы данных