Понимание системы типов Haskell в контексте приложений

Я играю с Control.Applicative и понимаю, что не все понимаю в системе типов Haskell.

Вот мой эксперимент в Ghci:

λ :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

λ :t (<*>) (pure 2)
(<*>) (pure 2) :: (Num (a -> b), Applicative f) => f a -> f b

Тип первого аргумента <*> - f (a -> b).

  • Почему это выражение правильное?
  • Как ее можно объединить с (pure 2), если константа 2 не относится к типу a -> b?
  • Что означает Num (a -> b)? Как функция, имеющая тип a -> b, может быть экземпляром Num?

person z1naOK9nu8iY5A    schedule 02.12.2014    source источник
comment
Num (a -> b) в контексте не означает, что существует экземпляр Num для a -> b. Это означает, что если бы был экземпляр Num для a -> b, то выражение имело бы этот тип.   -  person Tom Ellis    schedule 02.12.2014
comment
Кроме того, константа является литералом и поэтому имеет тип 2 :: Num a => a.   -  person Zeta    schedule 02.12.2014
comment
попробуйте следующий: :t (<*>) (pure (2 :: Int))   -  person viorior    schedule 02.12.2014


Ответы (1)


Первый аргумент <*> должен быть f (a -> b). Итак, учитывая (<*>) (pure x), это хорошо типизировано при условии, что x - это какая-то функция.

Тип 2 - Num a => a. Другими словами, 2 может быть любым возможным типом, если это экземпляр Num.

Итак, в вашем выражении (<*>) (pure 2) это хорошо типизировано при условии, что тип 2 является типом функции, а этот тип функции имеет экземпляр Num.

Конечно, почти нет причин, по которым вы когда-либо хотели бы, чтобы функция имела экземпляр Num. Но компилятор этого не знает. Все, что он говорит, это то, что если был такой экземпляр, тогда выражение стало бы хорошо типизированным.

(Это похоже на ошибку, которую вы иногда видите, когда компилятор хочет, чтобы какой-то тип был экземпляром Integral и Fractional одновременно. Для человека это бессмысленная комбинация. Для машины это всего лишь два обычных класса ... )

person MathematicalOrchid    schedule 02.12.2014
comment
Иногда бывает полезно, чтобы функции вели себя как числа. Это не стандартный экземпляр, но в нем есть смысл. Что-то вроде instance (Num b) => Num (a -> b) where f + g = \ x -> f x + f y и т. Д. Это позволяет писать такие вещи, как (sin + cos) 0.1, с очевидным смыслом. - person augustss; 02.12.2014
comment
Как может 2 быть функциональным? Как бы вы это назвали? Являются ли числовые литералы частью класса Num по определению? - person z1naOK9nu8iY5A; 02.12.2014
comment
@augustss Я сказал почти. ;-) - person MathematicalOrchid; 02.12.2014
comment
Помните, что 2 - это просто синтаксис для некоторого экземпляра Num, поэтому вы можете рассматривать его как Int или Double с отказом. В случае Num b => Num (a -> b), 2 становится функцией, которая постоянно равна 2, или const 2. - person J. Abrahamson; 02.12.2014
comment
Спасибо. Не могли бы вы порекомендовать мне документ или спецификацию, подробно объяснив это? Никогда бы не подумал, что это будет истолковано как const 2 - person z1naOK9nu8iY5A; 02.12.2014
comment
Это не по умолчанию, но это приемлемо (читай: законопослушно). Приведенный выше фрагмент кода augustss является действительным (если частично) instance Num b => Num (a -> b). Соответствующая часть, которую вы запрашиваете, будет выглядеть как fromInteger a = const (fromInteger a). В отношении Applicatives вы можете рассматривать это как не что иное, как перенос методов Num в Reader Applicative, type Reader a b = (a -> b). Например, fromInteger a = pure (fromInteger a) и (+) = liftA2 (+). - person J. Abrahamson; 02.12.2014
comment
@ z1naOK9nu8iY5A Посмотрите на stackoverflow.com / questions / 26515102 / - person augustss; 02.12.2014
comment
@ z1naOK9nu8iY5A Кроме того, gist.github.com/tel/e1f040a4e42887e6ab7a не разъяснял, что _ говорю в моем предыдущем комментарии. - person J. Abrahamson; 02.12.2014