определена функция типа a ->b и у нас есть значение типа a, то мы можем получить значение типа b.
Декомпозиция и сопоставление с образцом
Декомпозиция применяется слева от знака равно, при этом наша задача состоит в том, чтобы опознать
дерево определённого вида и выделить из него некоторые поддеревья. Мы уже пользовались декомпозицией
много раз в предыдущих главах, давайте выпишем примеры декомпозиции:
not :: Bool -> Bool
not True
= ...
not False
= ...
xor :: Bool -> Bool -> Bool
xor a b = ...
show :: Showa =>a -> String
show ( Timeh m s) = ...
addZero :: String -> String
addZero (a :[])
= ...
addZero as
= ...
( *)
a
Zero
= ...
( *)
a
( Succb)
= ...
48 | Глава 3: Типы
Декомпозицию можно проводить в аргументах функции. Там мы видим строчную запись дерева, в узлах
стоят конструкторы (начинаются с большой буквы), переменные (с маленькой буквы) или символ безразлич-
ной переменой (подчёркивание).
С помощью конструкторов, мы указываем те части, которые обязательно должны быть в дереве для дан-
ного уравнения. Так уравнение
not True
= ...
сработает, только если на вход функции поступит значение True. Мы можем углубляться в дерево значе-
ния настолько, насколько нам позволят типы, так мы можем определить функцию:
is7 :: Nat -> Bool
is7
( Succ( Succ( Succ( Succ( Succ( Succ( Succ Zero)))))))
= True
is7
_
= False
С помощью переменных мы даём синонимы поддеревьям. Этими синонимами мы можем пользоваться в
правой части функции. Так в уравнении
addZero (a :[])
мы извлекаем первый элемент из списка, и одновременно говорим о том, что список может содержать
только один элемент. Отметим, что если мы хотим дать синоним всему дереву а не какой-то части, мы просто
пишем на месте аргумента переменную, как в случае функции xor:
xor a b = ...
С помощью безразличной переменной говорим, что нам не важно, что находится у дерева в этом узле.
Уравнения в определении синонима обходятся сверху вниз, поэтому часто безразличной переменной поль-
зуются в смысле “а во всех остальных случаях”, как в:
instance Eq Nat where
( ==) Zero
Zero
= True
( ==) ( Succa) ( Succb) =a ==b
( ==) _
_
= False
Переменные и безразличные переменные также могут уходить вглубь дерева сколь угодно далеко (или
ввысь дерева, поскольку первый уровень в строчной записи это корень):
lessThan7 :: Nat -> Bool
lessThan7
( Succ( Succ( Succ( Succ( Succ( Succ( Succ _)))))))
= False
lessThan7
_
= True
Декомпозицию можно применять только к значениям-константам. Проявляется интересная закономер-
ность: если для композиции необходимым элементом было значение со стрелочным типом (функция), то в
случае декомпозиции нам нужно значение с типом без стрелок (константа). Это говорит о том, что все функ-
ции будут полностью применены, то есть константы будут записаны в виде строчной записи дерева. Если мы
ожидаем на входе функцию, то мы можем только дать ей синоним с помощью с помощью переменной или
проигнорировать её безразличной переменной.
Как в
name
( Succ( Succ Zero))
= ...
name
( Zero : Succ Zero : [])
= ...
Но не
name
Succ
= ...
name
( Zero :)
= ...
Отметим, что для композиции это допустимые значения, в первом случае это функция Nat -> Nat, а во
втором это функция типа [ Nat] ->[ Nat].
Ещё одна особенность декомпозиции заключается в том, что при декомпозиции мы можем пользоваться
только “настоящими” значениями, то есть конструкторами, объявленными в типах. В случае композиции мы
могли пользоваться как конструкторами, так и синонимами.
Например мы не можем написать в декомпозиции:
name
(add Zero Zero)
= ...
name
(or (xor a b) True)
= ...
В Haskell декомпозицию принято называть сопоставлением с образцом (pattern matching). Термин намекает
на то, что в аргументе мы выписываем шаблон (или заготовку) для целого набора значений. Наборы значений
Читать дальше