Подтвердить что ты не робот

Типы Haskell расстраивают простую "среднюю" функцию

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

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

Кажется, что система типа Haskell запрещает среднюю работу от общего числового типа - я могу заставить ее работать над списком Integrals или списком 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 очень разборчив по своим типам. В этом есть смысл. Но не тогда, когда они могут быть [Num]

Я пропустил очевидное применение RealFrac?

Есть ли способ принудить Integrals к Fractionals, который не задыхается, когда он получает вход Fractional?

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

Система типа Haskell прямо запрещает эту функцию из существующих?

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

(Кроме того, сноска: это основано на проблеме домашних заданий. Все согласны с тем, что averageFrac, выше, получает полные баллы, но у меня есть скрытое подозрение, что есть способ заставить его работать как с массивами Integral AND Fractional)

4b9b3361

Ответ 1

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

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

Кстати, вам также нужна Data.List.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

Таким образом, любой Real будет принимать любые фракции.

И обратите внимание, что все плакаты попадают в полиморфные числовые литералы в 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

Ответ 2

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

При вычислении среднего значения:

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:

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

Ответ 3

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

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

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

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

Теперь, на другой ноте, у вас был ответ на ответ dons, который привлек мое внимание:

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

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

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

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

Ответ 4

:m Data.List
let list = [1..10]
let average = div (sum list) (genericLength list)
average

Ответ 5

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

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

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

Хорошо, функция sum является хорошим началом:

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

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

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

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

Вам нужно преобразовать этот Int в Num, а также. Это то, что отIntegral делает.

Итак, теперь у вас есть функция, которая возвращает 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