2015-08-06 6 views
2

Я просто переживаю «Real World Haskell», и я делаю упражнения, которые приходят вместе с ним.Haskell fromIntegral - путаница типа

И я заметил что-то, что я считаю странным.

Возьмите эту функцию, например:

myAverage :: (Fractional a) => [a] -> Maybe a myAverage [] = Nothing myAverage xs = Just $ (mySum xs)/(fromIntegral $ myLength xs)

(/) функция хочет два аргумента, которые являются экземплярами Fractional. myLength возвращает Int, поэтому я использую функцию the fromIntegral, как мне сказали.

Но fromIntegral только гарантирует, что возвращаемое значение будет экземпляром Num.

Почему Num также «включает» экземпляр Fractional в этом случае. Разве это не Fractional?

Почему компилятор не жалуется, что у меня есть только Num от типа возврата, а не «правильный» Fractional.

Почему я не могу использовать Int напрямую, это также пример Num, не так ли?

В случае звонка mySum я получаю его. Он принимает список Num s, но я передаю его со списком Fractional s (также Num s, потому что они получены от него). Возвращаемый тип имеет тот же тип, что видно из аннотации типа (смотрите ниже): один элемент Fractional

Но в случае fromIntegral я не могу вывести себя (компилятор, очевидно, может :-)), что возвращаемый значение также является экземпляром Fractional. Зачем? Тип, который требуется, явно не Fractional.

Вся функция работает должным образом.

Просто для уточнения:

mySum :: (Num a) => [a] -> a myLength :: [a] -> Int

Спасибо,

Lazarus

+0

Каковы ваши 'mySum' и' myLength'? Хорошо. Я вижу их сейчас. – ErikR

+0

'mySum' просто добавляет все числа в список. 'myLength' подсчитывает элементы в списке. – Lazarus535

ответ

9

fromIntegral не производит какой-то неизвестный тип которого мы только знаем, что это экземпляр Num. fromIntegral производит какой бы тип мы (обычно неявно) запрашиваем его, если его экземпляр Num.

Что касается языков OO, вы должны думать об этом как generics/templates, а не о полиморфизме виртуального наследования/подтипа. То есть эквивалентная сигнатура Java для fromIntegral будет выглядеть как <A extends Integral, B extends Num> B fromIntegral(A i), а не Num fromIntegral(Integral i).

поэтому, если мы хотим получить Int, мы получаем Int. Если мы хотим Integer, мы получаем Integer. И если мы хотим получить Double, мы получим Double.Но если мы хотим String, нам не повезло, так как String не является экземпляром Num.

В этом случае мы хотим указать a, где a - тип элемента данного списка. Поэтому, если мы перейдем в список Double s, fromIntegral дает нам Double, и если мы перейдем в список Rational s, fromIntegral дает нам Rational.

+0

Приятное спасибо. Также аналогия с Java очень приятная. Будучи больше парнем C++, я бы реализовал такую ​​функцию с помощью специализированной специализации. Таким образом, существует одна универсальная версия и специализация для каждого конкретного типа, что фактически делает правильное преобразование. Does haskell реализовать это?Вы знаете, случайно? – Lazarus535

+0

@ Lazarus535 'fromIntegral' имеет ровно одно определение, которое является' fromIntegral = fromInteger. toInteger'. 'fromInteger' является частью класса« Num », поэтому он определяется отдельно для каждого типа« Num », а' toInteger' является частью класса «Integral», поэтому он снова определяется отдельно для каждого типа интеграла. – sepp2k

+0

Да. Это оно. Если функции, состоящие из 'fromIntegral', составлены из них, являются частью их соответствующих типов, чем в основном как специализирование шаблонов на C++. СПАСИБО. – Lazarus535

5

Вы спрашиваете:

Но fromIntegral только что ГАРАНТИИ возвращаемое значение будет экземпляром Num.

Это верно, но учитывать тип fromIntegral:

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

Так fromIntegral позволяет конвертировать Int, например, любого другого экземпляра Num.

В настоящее время давайте рассмотрим конкретные типы вместо классов классов. Предположим, вы определили myAverage как это:

myAverage :: [Double] -> Maybe Double 
myAverage [] = Nothing 
myAverage xs = Just $ mySum xs/(fromIntegral (myLength xs)) 

Double поддерживает разделение, и fromIntegral способен преобразовать Int вернулся из myLength в Double.

Такое же определение будет работать, если вы использовали Rational вместо Double, например:

myAverage :: [Rational] -> Maybe Rational 

Назад к типу классов и как его тип проверки:

myAverage :: Fractional a => [a] -> Maybe a 
... 
myAverage xs = Just $ mySum xs/(fromIntegral (myLength xs)) 

Вот как это происходит:

  1. a является фракционным, а фракционный является подклассом Num, поэтому a также является Числом.
  2. xs имеет тип [a], поэтому mySum xs имеет тип a
  3. myLength xs имеет тип Int
  4. fromIntegral может конвертировать Int в любой другой экземпляр Num, так что он может преобразовать Int в a
  5. mySum xs и fromIntegral выражения оба имеют тип a, который является фракционным, поэтому мы можем выполнять разделение.
+0

Спасибо за ответ. То, что меня смутило, было то, что в Haskell тип вычета работает в «обоих направлениях». – Lazarus535

Смежные вопросы