Выбрать главу

И так для любого выражения. В этом случае лучше просто выключить ограничение, добавив в начало

файла:

{-# 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)