Я новичок в 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.