Почему это аппликативное утверждение не лениво оценивается, и как я могу понять, почему? - PullRequest
6 голосов
/ 30 июня 2019
abc :: IO (Int)
abc = do
  print "abc"
  pure $ 10

xyz :: IO (Int)
xyz = undefined

main :: IO () 
main = do
  x <- (((+) <$> abc <*> abc) <* xyz)
  print x

Почему в приведенном выше описании оценивается xyz?Я предполагаю, что из-за ленивого характера Хаскелла ему не нужно оценивать xyz (и, следовательно, не достигать undefined)?

Мое предположение основано на типе <*:

Prelude> :t (<*)
(<*) :: Applicative f => f a -> f b -> f a

Далее с:

    -- | Sequence actions, discarding the value of the first argument.
    (*>) :: f a -> f b -> f b
    a1 *> a2 = (id <$ a1) <*> a2

И:

(<$)        :: a -> f b -> f a
(<$)        =  fmap . const

И, следовательно, f b никогда не используется.


Есть ли способ, которым я могу понять / исследовать, почему это оценивается строго?Было бы полезно посмотреть в скомпилированном ядре GHC?


Благодаря обсуждению в комментариях кажется (пожалуйста, кто-то поправит меня, если я ошибаюсь), это из-за Monad реализация IO, потому что следующие два утверждения, кажется, оценивают по-разному:

Идентичность:

runIdentity $ const <$> (pure 1 :: Identity Int) <*> undefined
1

IO:

const <$> (pure 1 :: IO Int) <*> undefined
*** Exception: Prelude.undefined

1 Ответ

4 голосов
/ 30 июня 2019

(<*) не использует значения b из f b, но использует эффекты f, поэтому он должен проверить второй аргумент.

Почему[putStrLn "Hello!" *> putStrLn "World!"] выполнить оба, а const (print "test") (print "test2") нет?

В типе const ...

const :: a -> b -> a

... и a, иb являются полностью параметрическими, и больше не с чем иметь дело.Однако с (<*) ситуация несколько иная.Для начала, (<*) - это метод Applicative, поэтому любой, кто пишет Applicative экземпляр для IO, может поставить бетон ...

(<*) :: IO a -> IO b -> IO a

..Реализация, которая использует специфичные для IO функции для объединения эффектов из двух аргументов любым способом, который будет сочтен необходимым.

Более того, даже если (<*) не был методом Applicative, его тип...

(<*) :: Applicative f => f a -> f b -> f a

... таков, что, хотя a и b являются полностью параметрическими, f нет из-за ограничения Applicative.Его реализация может использовать другие методы Applicative, которые могут, и в большинстве случаев будут использовать эффекты от обоих аргументов.

Обратите внимание, что это не является проблемой IO.Например, здесь (<*) @Maybe не игнорирует эффекты своего второго аргумента:

GHCi> Just 1 <* Just 2
Just 1
GHCi> Just 1 <* Nothing
Nothing
...