isAlphaNum :: Char -> Bool
isAlphaNum = (||) <$> isAlpha <*> isNum
Я вижу, что это работает, но я не понимаю, откуда берутся экземпляры Applicative
(или Functor
).
isAlphaNum :: Char -> Bool
isAlphaNum = (||) <$> isAlpha <*> isNum
Я вижу, что это работает, но я не понимаю, откуда берутся экземпляры Applicative
(или Functor
).
Это экземпляр Applicative
для ((->) r)
, функционирует из общего типа. Он объединяет функции с одним и тем же первым типом аргумента в одну функцию, дублируя один аргумент для использования для всех из них. (<$>)
- составная функция, pure - const
, и вот что (<*>)
переводит на:
s :: (r -> a -> b) -> (r -> a) -> r -> b
s f g x = f x (g x)
Эта функция, возможно, более известна как комбинатор S.
Функтор ((->) r)
также является монадой Reader
, где общим аргументом является значение "среда", например:
newtype Reader r a = Reader (r -> a)
Я бы не сказал, что это общепринято для того, чтобы сделать функции точными, но в некоторых случаях это может улучшить ясность, когда вы привыкнете к идиоме. Например, приведенный вами пример, я могу читать очень легко, поскольку это означает, что "является символом буквы или цифры".
Вы получаете экземпляры так называемых статических стрелок (см. "Аппликативное программирование с эффектами" Конора МакБрайда и др.) бесплатно из пакета Control.Applicative
. Таким образом, любой тип источника в вашем случае Char
дает примерный экземпляр, где любой другой тип a
сопоставляется с типом Char -> a
.
При объединении любого из них, скажем, примените функцию f :: Char -> a -> b
к значению x :: Char -> a
, семантика - это то, что вы создаете новую функцию Char -> b
, которая будет кормить свой аргумент как в f
, так и x
вот так,
f <*> x = \c -> (f c) (x c)
Следовательно, как вы указываете, это делает ваш пример эквивалентным
isAlphaNum c = (isAlpha c) || (isNum c)
На мой взгляд, такие усилия не всегда необходимы, и было бы лучше, если бы у Haskell была более синтаксическая поддержка аппликаций (может быть, что-то вроде двухуровневых языков).
Следует отметить, что вы получаете аналогичный эффект, используя функции лифта, например:
import Data.Char
import Control.Applicative
isAlphaNum = liftA2 (||) isAlpha isNumber
Или, используя экземпляр monad ((- > ) r) вместо аппликативного:
import Data.Char
import Control.Monad
isAlphaNum = liftM2 (||) isAlpha isNumber
[Отступление]
Теперь, когда вы знаете, как распределить один аргумент с двумя промежуточными функциями и результат с помощью двоичной функции, существует связанный с этим случай, когда вы хотите распределить два аргумента одной промежуточной функцией и результаты в двоичную функцию:
import Data.Function
orFst = (||) `on` fst
-- orFst (True,3) (False, 7)
--> True
Этот шаблон является, например, часто используется для функции compare
.