Типы Haskell, разбивающие простую 'среднюю' функцию

Я играю вокруг с новичком Haskell, и я хотел записать среднюю функцию. Это походило на самую простую вещь в мире, правильно?

Неправильно.

Кажется, что система типов Haskell запрещает среднему числу работу над универсальным числовым типом - я могу заставить это работать над списком Интегралов или списком Fractionals, но не обоих.

Я хочу:

average :: (Num a, Fractional b) => [a] -> b
average xs = ...

Но я могу только добраться:

averageInt :: (Integral a, Fractional b) => [a] -> b
averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs)

или

averageFrac :: (Fractional a) => [a] -> a
averageFrac xs = sum xs / fromIntegral (length xs)

и второй, кажется, работает. Пока я не пытаюсь передать переменную.

*Main> averageFrac [1,2,3]
2.0
*Main> let x = [1,2,3]
*Main> :t x
x :: [Integer]
*Main> averageFrac x

<interactive>:1:0:
    No instance for (Fractional Integer)
      arising from a use of `averageFrac ' at <interactive>:1:0-8
    Possible fix: add an instance declaration for (Fractional Integer)
    In the expression: average x
    In the definition of `it': it = averageFrac x

По-видимому, Haskell действительно требователен в отношении его типов. Это имеет смысл. Но не, когда они могли оба быть [Цифра]

Я пропускаю очевидное приложение RealFrac?

Там путь состоит в том, чтобы принудить Интегралы в Fractionals, который не дросселирует, когда это получает Дробный вход?

Есть ли некоторый способ использовать Either и either сделать своего рода полиморфную среднюю функцию, которая работала бы над каким-либо видом числового массива?

Система типов Haskell напрямую запрещает этой функции когда-нибудь существующий?

Учащийся Haskell похож на изучение Исчисления. Это действительно сложно и на основе гор теории, и иногда проблемой является так mindbogglingly комплекс, что я даже не знаю достаточно для формулировки вопроса правильно, таким образом, любое понимание будет тепло принято.

(Кроме того, сноска: это базируется от проблемы домашней работы. Все соглашаются, что averageFrac, выше, получает точки, но у меня есть подлое подозрение, что существует способ заставить его работать и над Интегралом И НАД Дробными массивами),

68
задан Don Stewart 16 April 2011 в 19:19
поделиться

4 ответа

Итак, по сути , вы ограничены типом (/):

(/) :: (Fractional a) => a -> a -> a

Кстати, вы также хотите Data.List.genericLength

genericLength :: (Num i) => [b] -> i

Итак, как насчет удаления fromIntegral для чего-то более общего:

import Data.List

average xs = realToFrac (sum xs) / genericLength xs

который имеет только ограничение Real ( Int, Integer, Float, Double) ...

average :: (Real a, Fractional b) => [a] -> b

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

И обратите внимание на все плакаты, которые ловятся полиморфными числовыми литералами в Haskell. 1 не целое, это любое число.

Класс Real предоставляет только один метод: возможность преобразовать значение в классе Num в рациональное. Это именно то, что нам здесь нужно.

Итак,

Prelude> average ([1 .. 10] :: [Double])
5.5
Prelude> average ([1 .. 10] :: [Int])
5.5
Prelude> average ([1 .. 10] :: [Float])
5.5
Prelude> average ([1 .. 10] :: [Data.Word.Word8])
5.5
95
ответ дан 24 November 2019 в 14:16
поделиться

Да, система типов Haskell очень придирчива. Проблема здесь в типе fromIntegral:

Prelude> :t fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b

fromIntegral будет только принимать интеграл в качестве числа, а не любое другое число. (/), с другой стороны, принимает только дробные числа. Как заставить эти два вида работать вместе?

Ну, функция sum - хорошее начало:

Prelude> :t sum
sum :: (Num a) => [a] -> a

Sum принимает список из любых Num и возвращает Num.

Следующая проблема - длина списка. Длина - это Int:

Prelude> :t length
length :: [a] -> Int

Вам нужно преобразовать Int в Num. Это и делает fromIntegral.

Итак, теперь у вас есть функция, которая возвращает Num, и другая функция, которая возвращает Num. Есть некоторые правила преобразования типов чисел которые вы можете найти, но в основном на этом этапе вы готовы к работе:

Prelude> let average xs = (sum xs) / (fromIntegral (length xs))
Prelude> :t average
average :: (Fractional a) => [a] -> a

Давайте попробуем:

Prelude> average [1,2,3,4,5]
3.0
Prelude> average [1.2,3.4,5.6,7.8,9.0]
5.4
Prelude> average [1.2,3,4.5,6,7.8,9]
5.25
-6
ответ дан 24 November 2019 в 14:16
поделиться

Донс очень хорошо ответил на этот вопрос, я подумал, что могу добавить что-нибудь.

При вычислении среднего таким образом:

средний xs = realToFrac (sum xs) / genericLength xs

Ваш код будет выполнять двойной обход списка, один раз для вычисления суммы его элементов и один раз чтобы получить его длину. Насколько мне известно, GHC еще не может оптимизировать это и вычислить как сумму, так и длину за один проход.

Даже новичку не повредит подумать об этом и о возможных решениях, например, средняя функция может быть написана с использованием свертки, которая вычисляет как сумму, так и длину; на ghci:

:set -XBangPatterns

import Data.List

let avg l=let (t,n) = foldl' (\(!b,!c) a -> (a+b,c+1)) (0,0) l in realToFrac(t)/realToFrac(n)

avg ([1,2,3,4]::[Int])
2.5
avg ([1,2,3,4]::[Double])
2.5

Функция выглядит не так элегантно, но производительность лучше.

Дополнительная информация в блоге Дона:

http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-c-working-at-a-high-altitude-for- low-level-performance /

25
ответ дан 24 November 2019 в 14:16
поделиться

Поскольку dons проделал такую ​​хорошую работу, отвечая на ваш вопрос, я буду работать над тем, чтобы поставить под сомнение ваш вопрос ....

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

Здесь вы столкнулись с настройкой компилятора, называемой DMR: D прочитанное M ономорфное R ограничение. Когда вы передавали список прямо в функцию, компилятор не делал предположений о том, к какому типу относятся числа, он просто выводил, какие типы он мог быть на основе использования, а затем выбирал один, как только он больше не мог сузить поле. Это что-то вроде полной противоположности утиной печати.

В любом случае, когда вы назначили список переменной, DMR сработал. Поскольку вы поместили список в переменную, но не дали никаких подсказок о том, как вы хотите его использовать, DMR заставил компилятор выбрать тип в данном случае он выбрал тот, который соответствует форме и кажется подходящим: Integer . Поскольку ваша функция не может использовать целое число в своей операции / (ей нужен тип в классе Fractional ), она предъявляет ту самую жалобу: нет экземпляра Integer в классе Fractional . Есть параметры, которые вы можете установить в GHC, чтобы он не приводил ваши значения в единую форму («мономорфный», понимаете?) До тех пор, пока это не понадобится, но это немного усложняет понимание любых сообщений об ошибках.

С другой стороны, вы получили ответ на ответ Дона, который привлек мое внимание:

Меня ввела в заблуждение диаграмма на последней странице cs.ut.ee/~varmo/MFP2004/PreludeTour.pdf который показывает, что Floating НЕ наследует свойства от Real, и я предположил, что у них не было бы общих типов.

Haskell работает с типами не так, как вы привыкли. Real и Floating - это классы типов, которые работают больше как интерфейсы, чем классы объектов. Они говорят вам, что вы можете делать с типом, который находится в этом классе, но это не означает, что какой-то тип не может делать другие вещи, равно как и наличие одного интерфейса означает, что класс (n OO-стиля) не может есть другие.

Изучение Haskell похоже на изучение исчисления.

Я бы сказал, что изучение Haskell похоже на изучение шведского языка - есть много маленьких простых вещей (буквы, числа), которые выглядят и работают одинаково, но есть также слова, которые выглядят как будто они должны означать одно, тогда как на самом деле они означают что-то другое. Но как только вы овладеете им свободно, ваши постоянные друзья будут поражены тем, как вы можете изливать эту странную штуку, которая заставляет великолепных красоток делать удивительные трюки. Любопытно, что в Haskell с самого начала вовлечено много людей, которые также знают шведский язык. Может быть, эта метафора больше, чем просто метафора ...

7
ответ дан 24 November 2019 в 14:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: