Если я бета-уменьшу следующее выражение:
foldr (mappend . Sum) 1 [2]
= (mappend . Sum) 2 (foldr (mappend . Sum) 1 [])
= (mappend . Sum) 2 1
= mappend (Sum 2) 1
...
Глядя на тип:
// mappend (<>) :: Monoid a => a -> a -> a
Мы видим, что в последней строке ошибка типа, потому что константа 1 должна принадлежать классу Monoid (а это не так).
Однако ghci не жалуется.
Почему этот тип выражения проверяет?
Да, конечно. Для меня оба числовых литерала имели разные типы.
Краткий ответ: 1 интерпретируется как Sum a, поэтому тип вашей папки:
foldr (mappend . Sum) 1 [2] :: Num a => Sum a
Где 2 имеет тип a, а 1 имеет тип Sum a.
Sum a является экземпляром Num, если a является экземпляром Num, действительно, исходный код говорит [src]:
newtype Sum a = Sum { getSum :: a }
deriving ( Eq -- ^ @since 2.01
, Ord -- ^ @since 2.01
, Read -- ^ @since 2.01
, Show -- ^ @since 2.01
, Bounded -- ^ @since 2.01
, Generic -- ^ @since 4.7.0.0
, Generic1 -- ^ @since 4.7.0.0
, Num -- ^ @since 4.7.0.0
)
Таким образом, это означает, что если вы пишете целочисленный литерал, такой как 1, он может быть построен как Sum a для любого a, являющегося экземпляром Num, поэтому 1 :: Sum Integer есть Sum 1.
Таким образом, это означает, что 1 в вашем foldr имеет тип Sum a, и поэтому, например:
mappend (Sum 2 :: Sum Integer) (1 :: Sum Integer)
-> Sum (2 + 1)
-> Sum 3
Спасибо за прекрасный ответ, но я чего-то не понимаю. В моем примере 1 является синтаксическим сахаром для fromInteger 1, так что это настоящая причина, по которой выражение foldr (mappend . Sum) 1 проверяет тип. Это правильно ?
@F.Zer: да, верно. Для каждого литерала int в основном ставится fromInteger впереди. Но это для литерала, а не для целочисленного выражения. Так что 1 + (2 :: Int) будет Int, а не чем-то вроде fromIntegral (1+2 :: Int) и т. д.
В документации для типа Sum вы найдете этот контрольный пример:
Num a => Num (Sum a)
(что, конечно, имеет смысл в том, что он должен быть там - мало того, что Sum a "морально тот же тип", что и a, Sum в любом случае явно предназначен для использования только с числовыми типами).
Поскольку числовые литералы, такие как 1 в Haskell, могут быть любого типа, являющегося экземпляром класса Num, в mappend (Sum 2) 1 нет ошибки типа, потому что компилятор автоматически преобразует его в mappend (Sum 2) (Sum 1). (Поскольку в этом экземпляре Num для Sum a fromInteger 1 будет Sum 1.)
Крайне важно знать тип ваших терминов, два числовых литерала не одного типа: foldr ((<>) . Sum) (1 :: Sum Int) [2 :: Int]