Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
29
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Функция read

Функция read в некотором смысле обратна функции show и используется тогда, когда нужно строку преобразовать в значение определенного типа. Вдумайтесь в ее тип:

read :: Read a => String -> a

Оценили? Функция получает строку, а возвращает значение произвольного (принадлежащего какому-то классу Read) типа a. Как такое может быть? Какой тип в итоге функция вернет?

Попробуем вызвать эту функцию, чтобы преобразовать строку в число:

Prelude> read "54"

ERROR - Unresolved overloading

*** Type : Read a => a

*** Expression : read "54"

Если вы прочитали прошлую главу про классы, то можете догадаться, что не нравится интерпретатору. В случае с функцией show он знал, какую конкретно перегруженную функцию незаметно от пользователя нужно вызвать: если вызывали show 5, он вызывал какую-нибудь функцию типа primitiveIntToString, если вызывали show True, он вызывал что-то вроде primitiveBoolToString. А если вызывали show sin, то он ругался, потому что не находил нужной примитивной функции, так как тип Double -> Double к классу Show не относится.

Короче говоря, в случае show было ясно, какой конкретно тип нужно преобразовать в строку. В случае функции read, интерпретатор не знает, значение какого типа нужно прочитать из строки. А как он это может узнать? А по тому, как мы будем с этим значением работать! Вот, например:

read "54" + 1 → 55

В данном случае контекст позволяет интерпретатору выявить, что преобразовать "54" нужно именно в число, потому что дальше с этим значением что-то складывается. Ну и, в конце концов, можно и напрямую попросить значение отнести к конкретному типу:

read "54" :: Integer → 54

read "54" :: Double → 54.0

Функция error

После функции read, тип функции error будет уже вполне понятен:

error :: String -> a

Эта функция принимает строку, а возвращает значение вообще произвольного типа. Раз так, то вставить ее можно в любую функцию – она везде по типу подойдет:

head [] = error “Program error: {head []}”

head (x:xs) = x

myDiv _ 0 = error "Program error: {primQrmInteger _ 0}"

myDiv x y = div x y

И в первом, и во втором случае функция error подходит по типу, а значение какого конкретно типа возвращается – уже не важно, потому что при вычислении значения этой функции, программа прерывается с ошибкой.

Побочные эффекты и функция trace

Говорят, что функция производит (имеет) побочный эффект, если ее результат может зависеть не только от ее собственных параметров, но и от ее окружения. Побочный эффект имеют функции, которые в процессе своих вычислений читают или модифицируют какие-то глобальные переменные, производят ввод-вывод (потому что это тоже есть чтение портов или модификация какой-нибудь видеопамяти). Если функцию с побочным эффектом вызвать дважды с одними и теми же входными значениями, она вполне может вернуть разный результат – потому что ее окружение, от которого она зависит, могло измениться.

Так вот, все функции, с которыми мы имели дело до сих пор в Haskell, побочных эффектов не имеют, и иметь не могут. Вы в принципе не сможете из своей функции что-то вывести на экран в процессе вычислений.

Это, конечно, ужасно неудобно, потому что как тогда отлаживать свои функции? Однако есть мнение, и автор с ним согласен, что потребность в трассировке в Haskell возникает гораздо реже – и именно потому, что большинство функций принципиально не могут иметь побочных эффектов.

Ну и еще, конечно, из-за строгой системы типов, через которую поначалу сложно продраться, но благодаря которой очень часто функция скомпилированная (с десятого раза) работает так, как нужно с первого раза. А какое соотношение у вас, в вашем сиплюсплюсе?

Однако возможности для трассировки все равно есть. Для этого нужно к вашему модулю подключить модуль Trace (или просто подгрузить этот модуль в командную строку командой :l Trace или :l Debug.Trace или import Debug.Trace, в зависимости от используемой среды), и тогда у вас появится функция trace:

trace :: String -> a -> a

Эта функция берет определенную строку, значение произвольного типа a, и возвращает то же самое значение, предварительно распечатав в консоли переданную строку:

Trace> trace "five" 5

five5

Trace> xx where xx = [(trace (show x) x)^2 | x <- [1..3]]

[11,24,39]

Что происходит в последней строчке? Генератор x <- [1..3] создает значение 1, вызывается функция trace (show 1) 1, которая распечатывает строку "1" и возвращает тоже 1, число 1 возводится в квадрат и выводится на экран. Потом генератор x <- [1..3] создает значение 2, вызывается функция trace (show 2) 2, которая распечатывает строку "2" и возвращает тоже 2, число 2 возводится в квадрат и выводится на экран, и так далее.

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

  • что является результатом вычисления выражения xx?

  • как выводится на экран результат вычисления выражения xx?

  • что выводится на экран в процессе вычисления выражения xx?

Это все очень разные вопросы, и вот, например, откуда это хорошо видно:

Trace> xx ++ xx where xx = [(trace (show x) x)^2 | x <- [1..3]]

[11,24,39,1,4,9]

Trace> sum xx where xx = [(trace (show x) x)^2 | x <- [1..3]]

12314

В последней строке "12314", выведенной на экран, "123" вывела функция trace при обработке списка [1,2,3], а "14" – это значение выражения sum [1,4,9]. Думаете, при таком нетривиальном поведении функции trace в связке с ленивыми функциями, вам будет легко найти глюк в своей функции? Ну-ну...