52700.fb2 Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 292

Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 292

компилятор понимает, что 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)

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. При этом за обычной