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

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

s = someLongLongComputation 10

someLongLongComputation :: Num a => a -> a

Здесь значение s содержит результат вычисления какой-то большой-пребольшой функции. Перед компи-

лятором стоит задача вывода типов. По тексту можно определить, что у s и res некоторый числовой тип.

Проблема в том, что поскольку компилятор не знает какой тип у s конкретно в выражении s + s, он вы-

нужден вычислить s дважды. Это привело разработчиков Haskell к мысли о том, что все выражения, которые

выглядят как константы должны вычисляться как константы, то есть лишь один раз. Это ограничение называ-

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

пользователь не укажет обратное в типе или не подскажет компилятору косвенно, подставив неопределённое

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

eqToOne = eq one

eq = (==)

one :: Int

one = 1

Только в этом случае мы не получим общего типа для eq: компилятор постарается вывести значение,

которое не содержит контекста. Поэтому получится, что функция eq определена на Int. Эта очень спорная

особенность языка, поскольку на практике получается так, что ситуации, в которых она мешает, возникают

гораздо чаще. Немного забегая вперёд, отметим, что это поведение компилятора по умолчанию, и его можно

изменить. Компилятор даже подсказал нам как это сделать в сообщении об ошибке:

Probable fix: give these definition(s) an explicit type signature

or use -XNoMonomorphismRestriction

Мы можем активировать расширение языка, которое отменяет это ограничение. Сделать это можно

несколькими способами. Мы можем запустить интерпретатор с флагом -XNoMonomorphismRestriction:

Prelude> :q

Leaving GHCi.

$ ghci -XNoMonomorphismRestriction

Prelude> let eq = (==)

Prelude> :t eq

eq :: Eq a => a -> a -> Bool

или в самом начале модуля написать:

{-# Language NoMonomorphismRestriction #-}

Расширение будет действовать только в рамках данного модуля.

3.5 Рекурсивные типы

Обсудим ещё одну особенность системы типов Haskell. Типы могут быть рекурсивными, то есть одним из

подтипов в определении типа может быть сам определяемый тип. Мы уже пользовались этим в определении

для Nat

data Nat = Zero | Succ Nat

Видите, во второй альтернативе участвует сам тип Nat. Это приводит к бесконечному числу значений. Та-

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

типов приводят к рекурсивным функциям. Помните, мы определяли сложение и умножение:

(+) a Zero

= a

(+) a (Succ b) = Succ (a + b)

(*) a Zero

= Zero

(*) a (Succ b) = a + (a * b)

54 | Глава 3: Типы

И та и другая функция получились рекурсивными. Они следуют по одному сценарию: сначала определяем

базу рекурсии~– тот случай, в котором мы заканчиваем вычисление функции, и затем определяем путь к

базе~– цепочку рекурсивных вызовов.