Я новичок в Haskell, здесь возникают проблемы с <*>
:
((==) <*>) :: Eq a => (a -> a) -> a -> Bool
Как я могу понять это и как его можно вывести?
Я новичок в Haskell, здесь возникают проблемы с <*>
:
((==) <*>) :: Eq a => (a -> a) -> a -> Bool
Как я могу понять это и как его можно вывести?
Отказ от ответственности: это не идиоматический код Haskell.
Первое, что имеет преимущество, - это "секция оператора" <*>
. Когда вы видите, что оператор применяется только к одному аргументу, который вызывает раздел. Здесь приведен пример более общей секции оператора:
(1 +) :: Int -> Int
Это функция, которая частично применяет +
к 1, оставляя место для одного последнего аргумента. Это эквивалентно:
\x -> 1 + x
Итак, в примере кода <*>
частично применяется к (==)
, поэтому мы расширим это:
((==) <*>)
= \g -> (==) <*> g
Далее вам нужно понять, что делает <*>
. Это член класса типа Applicative
:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Это означает, что <*>
перегружен для работы с любым типом, который реализует Applicative
. Один экземпляр типа Applicative
- ((->) r)
:
instance Applicative ((->) r) where
pure :: a -> ((->) r) a
(<*>) :: (->) r (a -> b) -> (->) r a -> (->) r b
Скобки вокруг (->)
означают, что они используются в префиксной форме (что необходимо для синтаксических соображений при определении экземпляров класса, подобных этому). Если вы расширите его до формы infix, вы получите:
instance Applicative ((->) r) where
pure :: a -> r -> a
(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
В вашем конкретном примере первым аргументом <*>
является оператор (==)
, который имеет следующий тип:
(==) :: Eq e => e -> e -> Bool
Поэтому, если мы передадим его в (<*>)
, то компилятор может больше узнать о типах r
, a
и b
в сигнатуре для (<*>)
:
(<*>) :: (r -> a -> b ) -> (r -> a) -> (r -> b)
(==) :: Eq e => e -> e -> Bool
| | |
| | +-> `b` must be `Bool`
| |
| +------> `a` must be `e`
|
+-----------> `r` must also be `e`
Итак, когда мы поставляем (==)
в качестве первого аргумента (<*>)
, мы получаем этот предполагаемый тип:
((==) <*>) :: Eq e => (e -> e) -> (e -> Bool)
Вы можете оставить правые круглые скобки, потому что (->)
является право-ассоциативным и измените e
на a
, чтобы получить окончательную подпись, которую вы получили:
((==) <*>) :: Eq a => (a -> a) -> a -> Bool
Но что это на самом деле делает? Чтобы понять, что нам нужно увидеть, как (<*>)
определен для экземпляра Applicative
((->) r)
:
(f <*> g) r = f r (g r)
Если мы заменим f
на (==)
, получим:
((==) <*> g) r = (==) r (g r)
Когда мы окружаем (==)
круглыми скобками, это означает, что мы используем его в префиксной нотации. Это означает, что если мы удалим круглые скобки и изменим их на нотацию infix, получим:
((==) <*> g) r = r == (g r)
Это эквивалентно:
((==) <*>) = \g r -> r == g r
Таким образом, это означает, что ваша функция принимает два параметра: g
и r
, а затем видит, если r
равно g r
.