Не бойся! Это просто, когда мы go по маленьким шагам и видим, куда нас это приведет. В следующий раз попробуйте сделать это самостоятельно.
(<*) = liftA2 const
и liftA2 f x = (<*>) (fmap f x)
, поэтому
ws :: Parser ()
ws = pure () <* many (satisfy isSpace)
= {- (<*) = liftA2 const -}
liftA2 const (pure ()) (many $ satisfy isSpace)
= {- liftA2 f x = (<*>) (fmap f x) -}
fmap const (pure ()) <*> many (satisfy isSpace)
= {- by Applicative laws -}
(pure const <*> pure ()) <*> many (satisfy isSpace)
= {- by homomorphism law of Applicatives -}
pure (const ()) <*> many (satisfy isSpace)
= {- with Applicative Do -}
do { f <- pure (const ())
; r <- many (satisfy isSpace)
; pure (f r) }
= {- by Law of `pure` being the identity, i.e. no-op, effects-wise -}
do { let f = const ()
; r <- many (satisfy isSpace)
; pure (f r) }
= {- by substitution -}
do { r <- many (satisfy isSpace)
; pure (const () r) }
=
do { _ <- many (satisfy isSpace)
; pure () }
=
do { many (satisfy isSpace)
; pure () }
= {- Monad Comprehension illustration -}
[ () | _ <- many (satisfy isSpace) ]
(с пометкой Applicative Do) ; чтобы получить представление о том, что это делает. Аппликативные L aws перечислены и по вышеуказанной ссылке.)
Так что он делает то, что делает many (satisfy isSpace)
, за исключением того, что вычисленное значение, которое он возвращает, всегда ()
.
В чистых функциях f a = ()
полностью игнорирует свой второй аргумент. Мы могли бы назвать это как f (1 / 0)
, и это не волновало бы. Но Applicative Functors express вычислений и <*>
объединяет вычисления, полностью известные до объединения, так что каждый из них получает выполненных , независимо от того, используется ли его вычисленное значение позже или нет.