Я играю вокруг с новичком 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, выше, получает точки, но у меня есть подлое подозрение, что существует способ заставить его работать и над Интегралом И НАД Дробными массивами),
Итак, по сути , вы ограничены типом (/):
(/) :: (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
Да, система типов 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
Донс очень хорошо ответил на этот вопрос, я подумал, что могу добавить что-нибудь.
При вычислении среднего таким образом:
средний 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
Функция выглядит не так элегантно, но производительность лучше.
Дополнительная информация в блоге Дона:
Поскольку 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 с самого начала вовлечено много людей, которые также знают шведский язык. Может быть, эта метафора больше, чем просто метафора ...