У меня есть функция f op = (op 1 2, op 1.0 2.0)
, которая должна работать следующим образом:
f (+)
(3, 3.0)
Но без объявления типа f
это работает так:
f (+)
(3.0, 3.0)
И я борюсь с объявлением типа f
. Требуется оператор, который работает со всеми экземплярами Num
. Возможно ли это вообще в Haskell?
Проблема в том, что вы заставили оператор работать с типами Fractional
, применив его к дробным числам, таким как 1.0
и 2.0
. Ваш код проверяет тип, потому что Fractional
является подтипом Num
(это означает, что каждый экземпляр Fractional
также является экземпляром Num
).
Следующий эксперимент в GHCI должен прояснить:
Prelude> :t 0
0 :: Num p => p
Prelude> :t 0.0
0.0 :: Fractional p => p
Prelude> :t 0 + 0.0 -- Fractional taking advantage!
0 + 0.0 :: Fractional a => a
Итак, если вы хотите, чтобы он работал полностью с Num
, вам просто нужно избавиться от этих «.0»:
Prelude> f op = (op 1 2, op 1 2)
Prelude> f (+)
(3, 3)
Если вам действительно нужно поведение, при котором второй элемент возвращаемого кортежа — это Fractional
, а первый — более общий Num
, все становится немного сложнее.
Оператор (или фактически функция), которую вы передаете f
, должен отображаться как два разных типа одновременно. Обычно это невозможно в простом Haskell, поскольку каждая переменная типа получает фиксированное назначение в каждом приложении — это означает, что (+)
нужно решить, является ли она Float
, Int
или чем-то еще.
Однако это можно изменить. Вам нужно будет включить расширение Rank2Types
, написав :set -XRank2Types
в GHCi или добавив {-# LANGUAGE Rank2Types #-}
в самом верху файла .hs
. Это позволит вам написать f
таким образом, чтобы тип его аргумента был более динамичным:
f :: (Num t1, Fractional t2)
=> (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1.0 2.0)
Теперь проверка типов не будет присваивать op
какой-либо фиксированный тип, а вместо этого оставит его полиморфным для дальнейшей специализации. Поэтому его можно применять как к 1 :: Int
, так и к 1.0 :: Float
в одном и том же контексте.
Действительно,
Prelude> f (+)
(3,3.0)
Подсказка из комментариев: ограничение типа можно ослабить, чтобы сделать функцию более общей:
f :: (Num t1, Num t2)
=> (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1 2)
Он будет работать нормально во всех случаях, с которыми справлялась бы предыдущая версия f
, а также в некоторых других случаях, когда snd
возвращаемой пары может быть, например, Int
:
Prelude> f (+)
(3,3)
Prelude> f (+) :: (Int, Float)
(3,3.0)
Вы можете ослабить ограничение Fractional
на Num
. Он будет ужесточен дробным аргументом.
Да, я просто хотел воспроизвести поведение в примере. Но хороший улов! Включит это
+1, хотя я бы добавил, что оригинальный постер, возможно, искал более специализированный тип ранга 2, например f :: (forall a. Num a => a -> a -> a) -> (Int, Double)
или аналогичный, с конкретными типами в кортеже.
Вы можете добавить Ord
к ограничению Num
ранга 2 или даже увеличить его до Real
, но при этом оставить его с Integer
, Int
, Double
и т. д.
Вы пытались использовать
:type
в GHCi для проверки предполагаемого типаf
?