*Exp>runPrint e2
”if (True) {False}{True}”
При таком подходе нам не пришлось ничего менять в выражениях, мы просто заменили тип выражения
и оно автоматически подстроилось под нужный результат. Подробнее об этом подходе можно почитать на
сайте http://okmij.org/ftp/tagless-final/course/course.html или в статье Жака Каре (Jacques Carette), Олега Киселёва (Oleg Kiselyov) и Чунг-Че Шена (Chung-chieh Shan) Finally Tagless, Partially Evaluated .
Расширения | 257
Семейства типов
Семейства типов позволяют выражать зависимости типов. Например представим, что класс определяет
не только методы, но и типы. Причём новые типы зависят от конкретного экземпляра класса. Посмотрим,
например, на определение линейного пространства из библиотеки vector -space:
class AdditiveGroupv where
zeroV
::v
( ^+^)
::v ->v ->v
negateV ::v ->v
class AdditiveGroupv => VectorSpacev where
type Scalarv
:: *
( *^)
:: Scalarv ->v ->v
Линейное пространство это математическая структура, объектами которой являются вектора и скаля-
ры. Для векторов определена операция сложения, а для скаляров операции сложения и умножения. Кроме
того определена операция умножения вектора на скаляр. При этом должны выполнятся определённые свой-
ства. Мы не будем подробно на них останавливаться, вкратце заметим, что эти свойства говорят о том, что
мы действительно пользуемся операциями сложения и умножения. В классе VectorSpaceмы видим новую
конструкцию, объявление типа. Мы говорим, что есть производный тип, который следует из v. Далее через
двойное двоеточие мы указываем его вид. В данном случае это простой тип без параметров.
Вид (kind) это тип типа. Простой тип без параметра обозначается звёздочкой. Тип с параметром обозна-
чается как функция * -> *. Если бы тип принимал два параметра, то он обозначался бы * -> * -> *. Также
параметры могут быть не простыми типами а типами с параметрами, например тип, который обозначает
композицию типов:
newtype Of g a = O{ unO ::f (g a) }
имеет вид ( * -> *) ->( * -> *) -> * -> *.
Определим класс векторов на двумерной сетке и сделаем его экземпляром класса VectorSpace. Для нача-
ла создадим новый модуль с активным расширением TypeFamiliesи запишем в него классы для линейного
пространства
{-# Language TypeFamilies #-}
module Point2D where
class AdditiveGroupv where
...
Теперь определим новый тип:
data V2 = V2 Int Int
deriving( Show, Eq)
Сделаем его экземпляром класса AdditiveGroup:
instance AdditiveGroup V2 where
zeroV
= V20 0
( V2x y)
^+^( V2x’ y’)
= V2(x +x’) (y +y’)
negateV ( V2x y)
= V2( -x) ( -y)
Мы складываем и вычитаем значения в каждом из элементов кортежа. Нейтральным элементом от-
носительно сложения будет кортеж, состоящий из двух нулей. Теперь определим экземпляр для класса
VectorSpace. Поскольку кортеж состоит из двух целых чисел, скаляр также будет целым числом:
instance VectorSpace V2 where
type Scalar V2 = Int
s *^( V2x y) = V2(s *x) (s *y)
Попробуем вычислить что-нибудь в интерпретаторе:
258 | Глава 17: Дополнительные возможности
*Prelude> :l Point2D
[1 of1] Compiling Point2D
( Point2D.hs, interpreted )
Ok, modules loaded : Point2D.
*Point2D> letv =
V21 2
*Point2D>v ^+^v
V22 4
*Point2D>3 *^v ^+^v
V24 8
*Point2D>negateV $3 *^v ^+^v
V2( -4) ( -8)
Семейства функций дают возможность организовывать вычисления на типах. Посмотрим на такой клас-
сический пример. Реализуем в типах числа Пеано. Нам понадобятся два типа. Один для обозначения нуля,
а другой для обозначения следующего элемента:
{-# Language TypeFamilies, EmptyDataDecls #-}
module Nat where
data Zero
data Succa
Значения этих типов нам не понадобятся, поэтому мы воспользуемся расширением EmptyDataDecls, ко-
торое позволяет определять типы без значенеий. Значениями будут комбинации типов. Мы определим опе-
рации сложения и умножения для чисел. Для начала определим сложение:
Читать дальше