Ленивая версия liftM2 - PullRequest
0 голосов
/ 10 июня 2018

Чтобы ответить на другой вопрос , я написал

andM :: (Monad m) => m Boolean -> m Boolean -> m Boolean
andM m1 m2 = do
  x <- m1
  case x of
    True -> m2
    False -> return False

, который не оценивает m2, если не нужно, в отличие от liftM2 (&&) m1 m2IO).

Есть ли способ написать liftM2Lazy того же типа, что и liftM2 (или более ограниченным каким-либо образом?), Который сохраняет лень в целом?Так, например, liftM2Lazy (&&) неотличим от andM, liftM2Lazy (||) от orM (с очевидным определением) и т. Д .?

Ответы [ 3 ]

0 голосов
/ 10 июня 2018

Нет, в общем, это невозможно - для преобразования ленивых функций в ленивые монадические функции требуется преобразование источника в источник.

В частности, IO и в тех случаях, когда это известно заранее который аргументирует функцию, в которой ленива (и насколько "глубоко" она ленива - то есть, насколько глубоко в возвращаемой структуре нужно оценить, чтобы определить, нужно ли выполнять другое действие), можноиспользуйте функции IO для перехвата исключений и unsafeInterleave для написания универсальной функции подъема.Но такие функции настолько специфичны и их так легко использовать неправильно, что я подозреваю, что вам лучше вообще их не писать.

0 голосов
/ 10 июня 2018

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

import System.IO.Unsafe

liftIO2Lazy :: (a -> b -> c) -> IO a -> IO b -> IO c
liftIO2Lazy f io1 io2 = do
    x <- unsafeInterleaveIO io1
    y <- unsafeInterleaveIO io2
    return $ f x y

Результат будет ленивым в тех же аргументах, в которых f ленив, поэтому он работает даже для функций, которые не следуют той же логике короткого замыкания слева направо, что и && и ||:

ioTrue = putStrLn "TRUE" >> return True
ioFalse = putStrLn "FALSE" >> return False

liftIO2Lazy (&&) ioTrue ioFalse
-- Prints both messages
liftIO2Lazy (||) ioTrue ioFalse
-- Only prints TRUE
liftIO2Lazy (flip (||)) ioTrue ioFalse
-- Only prints FALSE
liftIO2Lazy (const (const 42)) ioTrue ioFalse
-- No output
0 голосов
/ 10 июня 2018

Подход с использованием ложки , что немного обманывает:

liftM2Lazy f m1 m2 =
  case teaspoon $ f undefined undefined of
    Just res -> return res
    Nothing ->
      do x1 <- m1
         case teaspoon $ f x1 undefined of
           Just res -> return res
           Nothing ->
             do x2 <- m2
                return $ f x1 x2
...