Re: [Haskell-cafe] (no subject)

On Sun, Apr 24, 2016 at 08:07:40PM +0000, haskell-cafe-bounces@haskell.org wrote:
From int-e@gmx.de Sun Apr 24 21:13:18 2016 Date: Sun, 24 Apr 2016 21:13:18 +0200 From: Bertram Felgenhauer
To: haskell-cafe@haskell.org Subject: Re: [Haskell-cafe] Is it possible to make lazy combinators for IO? Message-ID: <20160424191319.GA32509@24f89f8c-e6a1-4e75-85ee-bb8a3743bb9f> References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.5.24 (2015-08-30) Status: RO Content-Length: 1283 Lines: 37 David Feuer wrote:
What I'm looking for is more limited than lazy IO or unsafeInterleaveIO, but it seems quite possible that there's no way to get just what I'm looking for with the IO type proper using GHC's implementation of IO. Lazy IO allows evaluation to drive action. When a thunk is forced, it may trigger I/O (spooky action at a distance). What I'm talking about is separating what actions are performed from what values are calculated from them. Here's a partial concept which won't actually compile because of the lazy pattern matches:
data MyIO a = forall b . MyIO (b -> a) (IO b) instance Functor MyIO where fmap f ~(MyIO t m) = MyIO (f . t) m instance Applicative MyIO where pure a = MyIO (const a) (pure ()) MyIO t1 m1 <*> ~(MyIO t2 m2) = MyIO (\(r1, r2) -> t1 r1 (t2 r2)) ((,) <$> m1 <*> m2) instance Monad MyIO where ??? instance MonadFix MyIO where ???
I believe that using this interface `unsafeInterleaveIO` could be implemented as follows, making it just as powerful as lazy IO:
data Box a = Box a
unsafeInterleaveMyIO :: MyIO a -> MyIO a unsafeInterleaveMyIO act = do act' <- Box `fmap` act return $ case act' of Box !r -> r
Have I missed anything?
Since MyIO and its associate functions don't contain any unsafe primitives it seems highly unlikely you can implement unsafeInterleaveIO with them! In fact I can't see how MyIO is any different to IO. All you can do with the function field is fmap it over the IO action field. Tom

First off, sorry for the botched email earlier; I should know better than to send an emails manually. (long story...) Tom Ellis wrote:
On Sun, Apr 24, 2016 at 08:07:40PM +0000, haskell-cafe-bounces@haskell.org wrote: [snip]
David Feuer wrote:
What I'm looking for is more limited than lazy IO or unsafeInterleaveIO, but it seems quite possible that there's no way to get just what I'm looking for with the IO type proper using GHC's implementation of IO. Lazy IO allows evaluation to drive action. When a thunk is forced, it may trigger I/O (spooky action at a distance). What I'm talking about is separating what actions are performed from what values are calculated from them. Here's a partial concept which won't actually compile because of the lazy pattern matches:
data MyIO a = forall b . MyIO (b -> a) (IO b) instance Functor MyIO where fmap f ~(MyIO t m) = MyIO (f . t) m instance Applicative MyIO where pure a = MyIO (const a) (pure ()) MyIO t1 m1 <*> ~(MyIO t2 m2) = MyIO (\(r1, r2) -> t1 r1 (t2 r2)) ((,) <$> m1 <*> m2) instance Monad MyIO where ??? instance MonadFix MyIO where ???
I believe that using this interface `unsafeInterleaveIO` could be implemented as follows, making it just as powerful as lazy IO:
data Box a = Box a
unsafeInterleaveMyIO :: MyIO a -> MyIO a unsafeInterleaveMyIO act = do act' <- Box `fmap` act return $ case act' of Box !r -> r
Have I missed anything?
Since MyIO and its associate functions don't contain any unsafe primitives it seems highly unlikely you can implement unsafeInterleaveIO with them!
In fact I can't see how MyIO is any different to IO. All you can do with the function field is fmap it over the IO action field.
Note that the code for the Monad instance is missing. The desired semantics as I understood them were that in `x >>= y`, `y` could access the "data part" that is produced by the embedded function (first component of MyIO) of `x` before the IO action associated with `x` was performed; the IO action would be triggered when that function forces its argument. This "early access" to the data part would then allow `mfix` to be lazier than it currently is. The point I'm trying to make is that these are exactly the semantics that `unsafeInterleaveIO` provides, and to my mind this is best demonstrated by implementing that function in the MyIO interface. Cheers, Bertram

On Sun, Apr 24, 2016 at 11:20:24PM +0200, Bertram Felgenhauer wrote:
Tom Ellis wrote:
On Sun, Apr 24, 2016 at 08:07:40PM +0000, haskell-cafe-bounces@haskell.org wrote: [snip]
David Feuer wrote:
What I'm looking for is more limited than lazy IO or unsafeInterleaveIO, but it seems quite possible that there's no way to get just what I'm looking for with the IO type proper using GHC's implementation of IO. Lazy IO allows evaluation to drive action. When a thunk is forced, it may trigger I/O (spooky action at a distance). What I'm talking about is separating what actions are performed from what values are calculated from them. Here's a partial concept which won't actually compile because of the lazy pattern matches:
data MyIO a = forall b . MyIO (b -> a) (IO b) instance Functor MyIO where fmap f ~(MyIO t m) = MyIO (f . t) m instance Applicative MyIO where pure a = MyIO (const a) (pure ()) MyIO t1 m1 <*> ~(MyIO t2 m2) = MyIO (\(r1, r2) -> t1 r1 (t2 r2)) ((,) <$> m1 <*> m2) instance Monad MyIO where ??? instance MonadFix MyIO where ???
I believe that using this interface `unsafeInterleaveIO` could be implemented as follows, making it just as powerful as lazy IO:
data Box a = Box a
unsafeInterleaveMyIO :: MyIO a -> MyIO a unsafeInterleaveMyIO act = do act' <- Box `fmap` act return $ case act' of Box !r -> r
Have I missed anything?
Since MyIO and its associate functions don't contain any unsafe primitives it seems highly unlikely you can implement unsafeInterleaveIO with them!
In fact I can't see how MyIO is any different to IO. All you can do with the function field is fmap it over the IO action field.
Note that the code for the Monad instance is missing. The desired semantics as I understood them were that in `x >>= y`, `y` could access the "data part" that is produced by the embedded function (first component of MyIO) of `x` before the IO action associated with `x` was performed; the IO action would be triggered when that function forces its argument. This "early access" to the data part would then allow `mfix` to be lazier than it currently is.
I see. Well there is no benefit in splitting MyIO up into two parts. It will suffice to do data MyIO a = MyIO (IO a) Then perhaps you want something like instance Monad MyIO where return = MyIO . return ~(MyIO m) >>= ~(MyIO f) = MyIO (unsafeInterleaveIO m >>= f)
The point I'm trying to make is that these are exactly the semantics that `unsafeInterleaveIO` provides, and to my mind this is best demonstrated by implementing that function in the MyIO interface.
So your idea is that unsafeInterleaveIO can be captured as a monad? Interesting. Tom

Bertram Felgenhauer wrote:
Tom Ellis wrote:
On Sun, Apr 24, 2016 at 08:07:40PM +0000, haskell-cafe-bounces@haskell.org wrote:
David Feuer wrote:
What I'm looking for is more limited than lazy IO or unsafeInterleaveIO, but it seems quite possible that there's no way to get just what I'm looking for with the IO type proper using GHC's implementation of IO. Lazy IO allows evaluation to drive action. When a thunk is forced, it may trigger I/O (spooky action at a distance). What I'm talking about is separating what actions are performed from what values are calculated from them. Here's a partial concept which won't actually compile because of the lazy pattern matches:
data MyIO a = forall b . MyIO (b -> a) (IO b) instance Functor MyIO where fmap f ~(MyIO t m) = MyIO (f . t) m instance Applicative MyIO where pure a = MyIO (const a) (pure ()) MyIO t1 m1 <*> ~(MyIO t2 m2) = MyIO (\(r1, r2) -> t1 r1 (t2 r2)) ((,) <$> m1 <*> m2) instance Monad MyIO where ??? instance MonadFix MyIO where ???
I believe that using this interface `unsafeInterleaveIO` could be implemented as follows, making it just as powerful as lazy IO:
data Box a = Box a
unsafeInterleaveMyIO :: MyIO a -> MyIO a unsafeInterleaveMyIO act = do act' <- Box `fmap` act return $ case act' of Box !r -> r
Have I missed anything?
Since MyIO and its associate functions don't contain any unsafe primitives it seems highly unlikely you can implement unsafeInterleaveIO with them!
In fact I can't see how MyIO is any different to IO. All you can do with the function field is fmap it over the IO action field.
Note that the code for the Monad instance is missing. The desired semantics as I understood them were that in `x >>= y`, `y` could access the "data part" that is produced by the embedded function (first component of MyIO) of `x` before the IO action associated with `x` was performed; the IO action would be triggered when that function forces its argument. This "early access" to the data part would then allow `mfix` to be lazier than it currently is.
I see what I missed here: The IO part of `y` should also force the IO part of `x` to be performed, and that is not captured by `unsafeInterleaveIO`, since it preserves the order of the IO actions. In fact such a monad already exists in the form of the lazy ST monad. So... import Control.Monad.ST.Lazy as LST import Control.Monad.ST.Lazy.Unsafe as LSTU import Control.Monad.IO.Class import Control.Monad.Fix newtype MyIO a = MyIO (LST.ST RealWorld a) deriving (Functor, Applicative, Monad, MonadFix) instance MonadIO MyIO where liftIO = MyIO . LSTU.unsafeIOToST runMyIO :: MyIO a -> IO a runMyIO (MyIO f) = stToIO f main = runMyIO $ do l <- (2:) `fmap` liftIO readLn m <- replicateM (head l) (liftIO readLn) liftIO (print (l :: [Int],m :: [Int])) Cheers, Bertram

Spectacular! That looks like just what I wanted! Unless there are some
hidden gotchas, it might be worth making a package of that. The bit
that's strangest to me is how stToIO (seems to) make sure that all the
actions actually get performed.
David
On Sun, Apr 24, 2016 at 6:08 PM, Bertram Felgenhauer
I see what I missed here: The IO part of `y` should also force the IO part of `x` to be performed, and that is not captured by `unsafeInterleaveIO`, since it preserves the order of the IO actions. In fact such a monad already exists in the form of the lazy ST monad. So...
import Control.Monad.ST.Lazy as LST import Control.Monad.ST.Lazy.Unsafe as LSTU import Control.Monad.IO.Class import Control.Monad.Fix
newtype MyIO a = MyIO (LST.ST RealWorld a) deriving (Functor, Applicative, Monad, MonadFix)
instance MonadIO MyIO where liftIO = MyIO . LSTU.unsafeIOToST
runMyIO :: MyIO a -> IO a runMyIO (MyIO f) = stToIO f
main = runMyIO $ do l <- (2:) `fmap` liftIO readLn m <- replicateM (head l) (liftIO readLn) liftIO (print (l :: [Int],m :: [Int]))
Cheers,
Bertram

David Feuer wrote:
Spectacular! That looks like just what I wanted! Unless there are some hidden gotchas, it might be worth making a package of that.
I can think of one nasty surprise: if liftIO is implemented using `unsafeIOToST` then it becomes dupable in the `unsafeDupablePerformIO` sense. It's quite delicate but something along the lines of r <- newIORef "" replicateM 2 $ forkIO $ <some code polling r> runMyIO $ do ((), _) <- mfix $ \(x, y) -> do x <- liftIO $ writeIORef r y y <- getLine return (x,y) last [1..] `seq` return () could trigger the `getLine` operation from different threads simultaneously. The upshot is that `liftIO` should employ `noDuplicate#` to prevent this scenario. Cheers, Bertram For reference:
newtype MyIO a = MyIO (LST.ST RealWorld a) deriving (Functor, Applicative, Monad, MonadFix)
instance MonadIO MyIO where liftIO = MyIO . LSTU.unsafeIOToST
runMyIO :: MyIO a -> IO a runMyIO (MyIO f) = stToIO f
main = runMyIO $ do l <- (2:) `fmap` liftIO readLn m <- replicateM (head l) (liftIO readLn) liftIO (print (l :: [Int],m :: [Int]))
participants (3)
-
Bertram Felgenhauer
-
David Feuer
-
Tom Ellis