a и b не обязательно разные. Иногда нам хочется расширить действие контекста функции и распространить
его на всё тело функции. Например ранее в этой главе, когда мы имитировали числа через типы, для того
чтобы извлечь число из типа, мы пользовались трюком с функцией proxy:
instance Nat a => Nat (Succ a) where
toInt x = 1 + toInt (proxy x)
proxy :: f a -> a
proxy = undefined
Единственное назначение функции proxy~– это передача информации о типе. Было бы гораздо удобнее
написать:
instance Nat a => Nat (Succ a) where
toInt x = 1 + toInt (undefined :: a)
Проблема в том, что по умолчанию любой полиморфный тип в Haskell имеет первый ранг, компилятор
читает нашу запись как (x :: forall a. a), и получается, что мы говорим: x имеет любой тип, какой
захочешь! Не очень полезная информация. Компилятор заблудился и спрашивает у нас: “куда пойти?” А
мы ему: “да куда захочешь”. Как раз для таких случаев существует расширение ScopedTypeVariables. Оно
связывает тип, объявленный в заголовке класса/функции с типами, которые встречаются в теле функции.
В случае функций есть одно отличие от случая с классами. Если мы хотим расширить действие переменной
из объявления типа функции, необходимо упомянуть её в слове forall в стандартном положении (как для
типа первого порядка). У нас был ещё один пример с proxy:
Расширения | 263
dt :: (Nat n, Fractional a) => Stream n a -> a
dt xs = 1 / (fromIntegral $ toInt $ proxy xs)
where proxy :: Stream n a -> n
proxy = undefined
В этом случае мы пишем:
{-# Language ScopedTypeVariables #-}
…
dt :: forall n. (Nat n, Fractional a) => Stream n a -> a
dt xs = 1 / (fromIntegral $ toInt (undefined :: n))
Обратите внимение на появление forall в определении типа. Попробуйте скомпилировать пример без
него или переместите его в другое место. Во многих случаях применения этого рсширения можно избежать
с помощью стандартной функции asTypeOf, посмотрим на определение из Prelude:
asTypeOf :: a -> a -> a
asTypeOf x y = x
Фактически это функция const, оба типа которой одинаковы. Она часто используется в инфиксной форме
для фиксации типа первого аргумента:
q = f $ x ‘asTypeOf‘ var
Получается очень наглядно, словно это предложение обычного языка.
И другие удобства и украшения
Стоит упомянуть несколько расширений. Они лёгкие для понимания, в основном служат украшению
записи или для сокращения рутинного кода.
Директива deriving может использоваться только с несколькими стандартными классами, но если мы
определили тип-обёртку через newtype или просто синоним, то мы можем очень просто определить новый
тип экземпляром любого класса, который доступен завёрнутому типу. Как раз для этого существует расши-
рение GeneralizedNewtypeDeriving:
newtype MyDouble = MyDouble Double
deriving (Show, Eq, Enum, Ord, Num, Fractional, Floating)
Мы говорили о том, что обычные числа в Haskell перегружены, иногда возникает необходимость в пе-
регруженных строках, как раз для этого существует расширение OverloadedStrings. При этом за обычной
записью строк может скрываться любой тип из класса:
class IsString a where
fromString :: String -> a
Расширение TypeOperators позволяет определять инфиксные имена не только для конструкторов типов,
но и для самих типов, синонимов типов и даже классов:
data a :+: b = Left a | Right b
17.3 Краткое содержание
В этой главе мы затронули малую часть возможностей, которые предоставляются системой ghc. Haskell
является полигоном для испытания самых разнообразных идей. Это экспериментальный язык. Но в практиче-
ских целях в 1998 году был зафиксирован стандарт языка, его обычно называют Haskell98. Любое расшире-
ние подключается с помощью специальной прагмы Language. Новый стандарт Haskell Prime включит в себя
наиболее устоявшиеся расширения. Также мы рассмотрели несколько полезных классов и синтаксических
конструкций, которые, возможно, облегчают написание программ.
17.4 Упражнения
Это была справочная глава, присмотритесь к рассмотренным возможностям и подумайте какие нужны
вам, а какие нет. Возможно вы вовсе не будете ими пользоваться, но некоторые из них могут встретиться
вам в чужом коде или в библиотеках.
264 | Глава 17: Дополнительные возможности
Глава 18
Средства разработки
В этой главе мы познакомимся с основными средствами разработки больших программ. Мы научимся
устанавливать и создавать библиотеки, писать документацию.
18.1 Пакеты
В Haskell есть ещё один уровень организации данных, мы можем объединять модули в пакеты (package).
Также как и модули пакеты могут зависеть от других пакетов, если они пользуются модулями их этих па-
кетов. Одним пакетом мы уже пользовались и довольно часто, это пакет base, который содержит все стан-
дартные модули, например такие как Prelude, Control.Applicative или Data.Function. Для создания и
установки пакетов существует приложение cabal. Оно определяет протокол организации и распростране-
ния модулей Haskell.
Создание пакетов
Предположим, что мы написали программу, которая состоит из нескольких модулей. Пусть все модули
хранятся в директории с именем src. Для того чтобы превратить набор модулей в пакет, нам необходимо