И так для любого выражения. В этом случае лучше просто выключить ограничение, добавив в начало
файла:
{-# Language NoMonomorphismRestriction #-}
Полиморфизм высших порядков
Когда мы говорили об ST нам встретилась функция с необычным типом:
runST :: (forall s. ST s a) -> a
Слово forall обозначает для любых. Любой полиморфный тип в Haskell подразумевает, что он определён
для любых типов. Например, когда мы пишем:
reverse :: [a] -> [a]
map
:: (a -> b) -> [a] -> [b]
На самом деле мы пишем:
reverse :: forall a. [a] -> [a]
map
:: forall a b. (a -> b) -> [a] -> [b]
262 | Глава 17: Дополнительные возможности
По названию слова forall может показаться, что оно несёт в себе много свободы. Оно говорит о том, что
функция определена для любых типов. Но если присмотреться, то эта свобода оказывается жёстким огра-
ничением. “Для любых” означает, что мы не можем делать никаких предположений о внутренней природе
значения. Мы не можем разбирать такие значения на составляющие части. Мы можем только подставлять
их в новые полиморфные функции (как в map), отбрасывать (как const) или перекладывать из одного ме-
ста в другое (как в swap или reverse). Мы можем немного смягчить ограничение, если укажем в контексте
функции какие классы определены для значений данного типа.
Все стандартные полиморфные типы имеют вид:
fun :: forall a b .. z. Expr(a, b, ... , z)
Причём Expr не содержит forall, а только стрелки и применение новых типов к параметрам. Такой тип
называют полиморфным типом первого порядка (rank). Если forall стоит справа от стрелки, то его можно
вынести из выражения, например, следующие выражения эквивалентны:
fun :: forall a.
a -> (forall b. b -> b)
fun :: forall a b. a -> (b -> b)
Так мы можем привести не стандартный тип к стандартному. Если же forall встречается слева от стрел-
ки, как в функции runST, то его уже нельзя вынести. Это приводит к повышению порядка полиморфизма.
Порядок полиморфизма определяется как самый максимум среди всех подвыражений, что стоят слева от
стрелки плюс один. Так в типе
runST :: (forall s. ST s a) -> a
Слева от стрелки стоит тип первого порядка, прибавив единицу, получим порядок для всего выражения.
Если вдруг нам захочется воспользоваться такими типами, мы можем включить одно из расширений:
{-# Language Rank2Types #-}
{-# Language RankNTypes #-}
В случае рангов произвольного порядка алгоритм вывода типов может не завершиться. В этом случае нам
придётся помогать компилятору расставляя типы сложных функций вручную.
Лексически связанные типы
Мы уже привыкли к тому, что когда мы пишем
swap :: (a, b) -> (b, a)
компилятор понимает, что a и b указывают на один и тот же тип слева и справа от стрелки. При этом типы
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)