Строгий fmap с использованием только Functor, а не Monad - PullRequest
10 голосов
/ 24 февраля 2012

В последнее время мое внимание привлекло одно раздражение от ленивого ввода-вывода

import System.IO
import Control.Applicative

main = withFile "test.txt" ReadMode getLines >>= mapM_ putStrLn
  where getLines h = lines <$> hGetContents h

Из-за ленивого ввода-вывода вышеуказанная программа ничего не печатает. Поэтому я предположил, что это можно решить с помощью строгой версии fmap. И действительно, я придумал именно такой комбинатор:

forceM :: Monad m => m a -> m a
forceM m = do v <- m; return $! v

(<$!>) :: Monad m => (a -> b) -> m a -> m b
f <$!> m = liftM f (forceM m)

Замена <$> на <$!> действительно облегчает проблему. Однако я не удовлетворен. <$!> имеет ограничение Monad, которое кажется слишком узким; его спутник <$> требует только Functor.

Есть ли способ написать <$!> без ограничения Monad? Если да, то как? Если нет, то почему? Я пытался бросить строгость повсюду, но безрезультатно (следующий код не работает как нужно):

forceF :: Functor f => f a -> f a
forceF m = fmap (\x -> seq x x) $! m

(<$!>) :: Functor f => (a -> b) -> f a -> f b
f <$!> m = fmap (f $!) $! (forceF $! m)

1 Ответ

8 голосов
/ 24 февраля 2012

Я не думаю, что это возможно, а также монадическое forceM не работает для всех монад:

module Force where

import Control.Monad.State.Lazy

forceM :: Monad m => m a -> m a
forceM m = do v <- m; return $! v

(<$!>) :: Monad m => (a -> b) -> m a -> m b
f <$!> m = liftM f (forceM m)

test :: Int
test = evalState (const 1 <$!> undefined) True

И оценка:

Prelude Force> test
1

forceM нужен достаточно строгий (>>=), чтобы фактически вызвать результат его аргумента. Functor даже не имеет (>>=). Я не понимаю, как можно написать эффективный forceF. (Это не доказывает, что это невозможно, конечно.)

...