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

дарта Haskell. Мы рассмотрим несколько наиболее часто используемых расширений. Расширения подключа-

ются с помощью специального комментария. Он помещается в начале модуля. Расширение действует только

в текущем модуле.

{-# LANGUAGE

ExtentionName1, ExtentionName2, ExtentionName3 #-}

Обратите внимание на символ решётка, обрамляющие комментарии. Слово LANGUAGE говорит компи-

лятору о том, что мы хотим воспользоваться расширениями с именами ExtentionName1, ExtentionName2,

ExtentionName3. Такой комментарий называется прагмой (pragma). Часто компилятор ghc в случае ошибки

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

Он говорит возможно вы имели в виду расширение XXX. Например попробуйте загрузить в интерпретатор

модуль:

module Test where

class Multi a b where

В этом случае мы увидим ошибку:

Prelude> :l Test

[1 of 1] Compiling Test

( Test. hs, interpreted )

Test. hs:3:0:

Too many parameters for class Multi’

(Use -XMultiParamTypeClasses to allow multi-parameter classes)

In the class declaration for ‘Multi’

Failed, modules loaded: none.

Компилятор сообщает нам о том, что у нас слишком много параметров в классе Multi. В рамках стандар-

та Haskell можно создавать лишь классы с одним параметром. Но за сообщением мы видим подсказку, если

мы воспользуемся расширением -XMultiParamTypeClasses, то всё будет хорошо. В этом сообщении имя рас-

ширения закодировано в виде флага. Мы можем запустить ghc или ghci с этим флагом и тогда расширение

будет активировано, и модуль загрузится. Попробуем:

Prelude> :q

Leaving GHCi.

$ ghci -XMultiParamTypeClasses

Prelude> :l Test

[1 of 1] Compiling Test

( Test. hs, interpreted )

Ok, modules loaded: Test.

*Test>

Модуль загрузился! У нас есть и другая возможность подключить модуль с помощью прагмы LANGUAGE.

Имя расширения записано во флаге после символов -X. Добавим в модуль Test расширение с именем

MultiParamTypeClasses:

{-# LANGUAGE MultiParamTypeClasses #-}

module Test where

class Multi a b where

Теперь загрузим ghci в обычном режиме:

*Test> :q

Leaving GHCi.

$ ghci

Prelude> :l Test

[1 of 1] Compiling Test

( Test. hs, interpreted )

Ok, modules loaded: Test.

254 | Глава 17: Дополнительные возможности

Обобщённые алгебраические типы данных

Предположим, что мы хотим написать компилятор небольшого языка. Наш язык содержит числа и логиче-

ские значения. Мы можем складывать числа и умножать. Для логических значений определена конструкция

if-then-else. Определим тип синтаксического дерева для этого языка:

data Exp = ValTrue

| ValFalse

| If Exp Exp Exp

| Val Int

| Add Exp Exp

| Mul Exp Exp

deriving (Show)

В этом определении кроется одна проблема. Наш тип позволяет нам строить бессмысленные выражения

вроде Add ValTrue (Val 2) или If (Val 1) ValTrue (Val 22). Наш тип Val включает в себя все хорошие вы-

ражения и много плохих. Эта проблема проявится особенно ярко, если мы попытаемся определить функцию

eval, которая вычисляет значение для нашего языка. Получается, что тип этой функции:

eval :: Exp -> Either Int Bool

Для решения этой проблемы были придуманы обобщённые алгебраические типы данных (generalised

algebraic data types, GADTs). Они подключаются расширением GADTs. Помните когда-то мы говорили, что

типы можно представить в виде классов. Например определение для списка

data List a = Nil | Cons a (List a)

можно мысленно переписать так:

data List a where

Nil

:: List a

Cons :: a -> List a -> List a

Так вот в GADT определения записываются именно в таком виде. Обобщение заключается в том, что

теперь на месте произвольного параметра a мы можем писать конкретные типы. Определим тип GExp

{-# LANGUAGE GADTs #-}

data Exp a where

ValTrue

:: Exp Bool

ValFalse

:: Exp Bool

If

:: Exp Bool -> Exp a -> Exp a -> Exp a

Val

:: Int -> Exp Int

Add

:: Exp Int -> Exp Int -> Exp Int

Mul