подробнее о механизме приведения?

37 views
Skip to first unread message

nsk.coder

unread,
Oct 3, 2010, 1:45:41 PM10/3/10
to SPb Haskell User Group
Добрый день!

Не могу понять простого примера из статьи "Мягкое введение в
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, но
по данной теме -- недостаточно.

Может кто объяснит?

Roman Cheplyaka

unread,
Oct 3, 2010, 4:07:29 PM10/3/10
to spb...@googlegroups.com
* nsk.coder <nsk....@gmail.com> [2010-10-03 10:45:41-0700]

> Добрый день!
>
> Не могу понять простого примера из статьи "Мягкое введение в
> Haskell-2".
>
> В п. 12.2 "Перегрузка чисел" дан пример вычисления среднего
> арифметического с явным приведением:
>
> average :: (Fractional a) => [a] -> a
> average = sum xs / fromIntegral (length xs)
>
> Мне понятно: деление требует, чтобы оба аргумента были Fractional, но
> length дает результат в Int. Однако, мы используем функицю
> fromIntegral, которая возвращает результат в Num. Почему такое
> поведение функции fromIntegral адекватно???!!!
>
> И как работает дальнейший механизм в этом выражении? Т.е. нам-то нужен
> аргумент во Fractional, а как он из общего Num станет Fractional??

Давай взглянем на тип 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

Vladimir Ivanov

unread,
Oct 3, 2010, 5:12:09 PM10/3/10
to spb...@googlegroups.com
Ну и в дополнение к столь обстоятельному объяснению, чуть-чуть кода из GHC.Real:

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

nsk.coder

unread,
Oct 7, 2010, 12:39:24 AM10/7/10
to SPb Haskell User Group
Роман, большое спасибо за ответ! Прочитал внимательно.

Доп. вопрос по теме (точнее по пункту 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]

Roman Cheplyaka

unread,
Oct 7, 2010, 2:53:28 AM10/7/10
to spb...@googlegroups.com
* nsk.coder <nsk....@gmail.com> [2010-10-06 21:39:24-0700]

> Роман, большое спасибо за ответ! Прочитал внимательно.
>
> Доп. вопрос по теме (точнее по пункту 7 Вашего поста):
>
> функцию fromIntegral следует мыслить как особый вид полиморфной
> функции, которая благодаря указаниям компилятора сама угадывает нужный
> тип результата? Или каждый тип и подкласс в классе Num должны ее
> перегружать -- и в соотвествии с указаниями компилятора выбирается
> нужный вариант перегрузки? Я так полагаю, что второе более логично,
> ведь возможно создание собственных типов в класее Num, и тогда эта
> функция "расстерялась бы".

Как указал Владимир Иванов, функция fromIntegral на самом деле
является композицией двух полиморфных функций -- fromInteger из
класса Num и toInteger из класса Integral. За счет этого и
достигается полиморфность по обоим аргументам.

Поэтому каждый новый тип, который хочет попасть в класс Num, должен
определить свою fromInteger, а если хочет попасть в Integral -- то
toInteger.

А механизму вывода типов все равно какие типы выводить -- аргумента
или результата. Просто полиморфность по аргументам нам кажется более
естественной. В конце концов не стоит забывать, что обычные числовые
литералы являются "перегруженными" и "возвращают" полиморфный
результат:

Prelude> :t 42
42 :: (Num t) => t

Reply all
Reply to author
Forward
0 new messages