Типы Хаскелл неприятно средняя функция простой''
Я играю с новичком Haskell, и я хотел написать среднюю функцию. Это казалось самой простой вещью в мире, не так ли?
неправильно.
похоже, что система типов Хаскелла запрещает среднему работать с общим числовым типом-я могу заставить его работать со списком интегралов или списком Фракционалов, но не с обоими.
хочу:
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
по-видимому, Хаскелл очень разборчив в своих типах. В этом есть смысл. Но не тогда, когда они оба могут быть [Num]
я пропустил очевидное применение RealFrac?
есть ли способ, чтобы заставить интегралов в Fractionals, что не поперхнуться, когда оно получает частичный ввод?
есть ли способ, чтобы использовать Either и either чтобы сделать своего рода полиморфная средняя функция, которая будет работать на любом числовом массиве?
система типов Haskell прямо запрещает эту функцию когда-либо существовать?
обучение курс Как учить матанализ. Это действительно сложно и основано на горах теории, и иногда проблема настолько умопомрачительно сложна, что я даже не знаю достаточно, чтобы правильно сформулировать вопрос, поэтому любое понимание будет тепло принято.
(также сноску: это на основе задания. Все согласны с тем, что averageFrac, выше, получает полные очки, но у меня есть тайное подозрение, что есть способ заставить его работать как на интегральных, так и на дробных массивах)
5 ответов:
так принципиально, вы ограничены типом (/):
(/) :: (Fractional a) => a -> a -> aкстати, вам также нужны данные.Список.genericLength
genericLength :: (Num i) => [b] -> iтак как насчет удаления fromIntegral для чего-то более общего:
import Data.List average xs = realToFrac (sum xs) / genericLength xsкоторый имеет только реальное ограничение (Int, Integer, Float, Double)...
average :: (Real a, Fractional b) => [a] -> bтак что возьмем любой реальный в любой дробный.
и обратите внимание, что все плакаты пойманы полиморфными числовыми литералами в Хаскель. 1 - это не целое число, это любое число.
реальный класс предоставляет только один метод:возможность превратить значение в классе 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
на этот вопрос очень хорошо ответили Доны, я подумал, что могу что-то добавить.
при расчете среднего таким образом :
average 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: The D прочитанные M onomorphic R estriction. Когда вы передали список прямо в функцию, компилятор не сделал предположения о том, какой тип чисел был, он просто предположил, какие типы он может быть основан на использовании, а затем выбрал один раз, когда он больше не мог сузить поле. Это вроде как прямая противоположность утиному набору, там.
в любом случае, когда вы назначили список переменной, DMR включился. С тех пор как ты поместите список в переменную, но без каких-либо намеков на то, как вы хотите его использовать, DMR заставил компилятор выбрать тип, в этом случае он выбрал тот, который соответствовал форме и, казалось, соответствовал:
Integer. Поскольку ваша функция не может использовать целое число в своем/операция (требуется тип вFractionalкласс), он делает ту самую жалобу: нет экземпляраIntegerнаFractionalкласса. Есть параметры, которые вы можете установить в GHC, чтобы он не заставлял ваши значения в одну форму ("моно-морфический", получить его?) пока это не нужно, но это делает любые сообщения об ошибках немного сложнее выяснить.меня ввела в заблуждение диаграмма на последней странице cs.ut.ee/~varmo / MFP2004 / PreludeTour. pdf это показывает, что плавающий не наследует свойства от реального, и я тогда предположил, что у них не было бы общих типов.
Хаскелл ли типов по-разному от того, что вы привыкший.
RealиFloatingклассы типов, которые работают больше как интерфейсы, а не классы объектов. Они говорят вам, что вы можете сделать с типом, который находится в этом классе, но это не означает, что какой-то тип не может делать другие вещи, так же как наличие одного интерфейса означает, что класс(N OO-style) не может иметь других.обучение курс Как учить матанализ
Я бы сказал, что изучение Haskell похоже на изучение шведского языка - есть много маленьких, простых вещи (буквы, цифры), которые выглядят и работают одинаково, но есть также слова, которые выглядят, как они должны означать одно, когда они на самом деле означают что-то другое. Но как только вы освоите его, ваши постоянные друзья будут поражены тем, как вы можете извергать этот странный материал, который заставляет великолепных красавиц делать удивительные трюки. Любопытно, что есть много людей, участвующих в Haskell с самого начала, которые также знают шведский язык. Может быть, эта метафора больше, чем просто метафора...
да, система типов Хаскелла очень разборчива. Проблема здесь заключается в типе fromIntegral:
Prelude> :t fromIntegral fromIntegral :: (Integral a, Num b) => a -> bfromIntegral will только примите Интеграл как a, а не какой-либо другой вид числа. ( / ), с другой стороны, принимает только дробные. Как вы собираетесь заставить их работать вместе?
Ну, функция sum-это хорошее начало:
Prelude> :t sum sum :: (Num a) => [a] -> aSum берет список любого числа и возвращает число.
ваша следующая проблема-длина из списка. Длина-это 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
Comments