Не могу понять простого примера из статьи "Мягкое введение в
Haskell-2".
В п. 12.2 "Перегрузка чисел" дан пример вычисления среднего
арифметического с явным приведением:
average :: (Fractional a) => [a] -> a
average = sum xs / fromIntegral (length xs)
Мне понятно: деление требует, чтобы оба аргумента были Fractional, но
length дает результат в Int. Однако, мы используем функицю
fromIntegral, которая возвращает результат в Num. Почему такое
поведение функции fromIntegral адекватно???!!!
И как работает дальнейший механизм в этом выражении? Т.е. нам-то нужен
аргумент во Fractional, а как он из общего Num станет Fractional??
Почему не сделали явно указающей функции, что-то вроде
from_Integral_To_Fractional ??
Я не нашел в доступной литературе, где можно прочитать подробнее --
что-то есть в указанной статье, что-то есть в Описании Haskell-98, но
по данной теме -- недостаточно.
Может кто объяснит?
Давай взглянем на тип fromIntegral еще раз.
fromIntegral :: (Integral a, Num b) => a -> b
Это определение говорит, что для любого типа a (из Integral) и _для
любого типа b_ (из Num) функция fromIntegral может отобразить a в b.
Вывод типов при этом происходит как-то так:
1. xs :: a {- будем считать, что мы не указывали явно тип average --
пусть компилятор сам выводит -}
2. sum xs :: a
3. length xs :: Int
4. fromIntegral :: (Integral b, Num c) => b -> c
{- специально переименовал переменные типов, чтобы не было путаницы с
предыдущим a -}
5. Чтобы применить fromIntegral к length xs, надо унифицировать тип b из
п.4 с типом Int из п.3. Это возможно, т.к. Int принадлежить Integral.
Поэтому fromIntegral (length xs) :: Num c => c
Что это значит? Что для любого c из класса Num мы можем считать
fromIntegral (length xs) значением типа c.
6. (/) :: Fractional d => d -> d -> d
7. Следовательно, надо унифицировать переменную d с переменной a из п.2
(делимое), а также d с 'c' из п. 4. Первая унификация возможна,
потому что 'a' не связана никакими ограничениями, и унифицируется с
чем угодно. Так что a = d.
Вторая унификация возможна, потому что Num является надклассом
Fractional. Иными словами, какой бы d из класса Fractional мы ни
захотели бы, этот d точно будет принадлежать Num, и поэтому мы сможем
привести fromIntegral (length xs) к типу d. Так что c = d.
8. Итого, average :: Fractional d => [d] -> d
Если бы Fractional не являлся подклассом Num, то унификация все равно
была бы возможной, но в контекст пришлось включить бы оба класса, т.е.
после последней унификации мы бы получили
average :: (Num d, Fractional d) => [d] -> d
Теперь, надеюсь, понятно, почему именно Num надо отбросить, как менее
ограничивающий.
> Почему не сделали явно указающей функции, что-то вроде
> from_Integral_To_Fractional ??
Примерно потому же, почему мы пишем (+) вместо add_Double_To_Double. В
этом смысл полиморфизма. :)
> Я не нашел в доступной литературе, где можно прочитать подробнее --
> что-то есть в указанной статье, что-то есть в Описании Haskell-98, но
> по данной теме -- недостаточно.
>
> Может кто объяснит?
Надеюсь, теперь стало понятнее.
--
Roman I. Cheplyaka :: http://ro-che.info/
"Don't let school get in the way of your education." - Mark Twain
fromIntegral :: (Integral a, Num b) => a -> b
fromIntegral = fromInteger . toInteger
Никакой магии =)
Best regards,
Vladimir Ivanov
2010/10/4 Roman Cheplyaka <ro...@ro-che.info>:
> --
> Отправить сообщение в SPb HUG: spb...@googlegroups.com
> Отменить подписку: spbhug-un...@googlegroups.com
> Страница группы: http://groups.google.com/group/spbhug?hl=ru
Доп. вопрос по теме (точнее по пункту 7 Вашего поста):
функцию fromIntegral следует мыслить как особый вид полиморфной
функции, которая благодаря указаниям компилятора сама угадывает нужный
тип результата? Или каждый тип и подкласс в классе Num должны ее
перегружать -- и в соотвествии с указаниями компилятора выбирается
нужный вариант перегрузки? Я так полагаю, что второе более логично,
ведь возможно создание собственных типов в класее Num, и тогда эта
функция "расстерялась бы".
Пока готовил ответ -- еще раз просмотрел статью "Мягкое введение в
Haskell", действительно, там есть указание на то, что функция
fromInteger -- перегружается в новых типах (в статье -- на примере
Complex)
Разобрался. Спасибо!
On 4 окт, 03:07, Roman Cheplyaka <r...@ro-che.info> wrote:
> * nsk.coder <nsk.co...@gmail.com> [2010-10-03 10:45:41-0700]
Как указал Владимир Иванов, функция fromIntegral на самом деле
является композицией двух полиморфных функций -- fromInteger из
класса Num и toInteger из класса Integral. За счет этого и
достигается полиморфность по обоим аргументам.
Поэтому каждый новый тип, который хочет попасть в класс Num, должен
определить свою fromInteger, а если хочет попасть в Integral -- то
toInteger.
А механизму вывода типов все равно какие типы выводить -- аргумента
или результата. Просто полиморфность по аргументам нам кажется более
естественной. В конце концов не стоит забывать, что обычные числовые
литералы являются "перегруженными" и "возвращают" полиморфный
результат:
Prelude> :t 42
42 :: (Num t) => t