maoe / lifted-async

Run lifted IO operations asynchronously and wait for their results
http://hackage.haskell.org/package/lifted-async
BSD 3-Clause "New" or "Revised" License
29 stars 13 forks source link

Lifted variant of Concurrently #4

Closed jonsterling closed 10 years ago

jonsterling commented 10 years ago

I've been needing a lifted variant of Concurrently, but I'm not sure about the right way to write it. What I currently have is this:

newtype Concurrently m a = Concurrently { runConcurrently :: m a }

instance MonadBaseControl IO m => Functor (Concurrently m) where
  fmap f (Concurrently a) = Concurrently $ f <$> a

instance MonadBaseControl IO m => Applicative (Concurrently m) where
  pure = Concurrently . return
  Concurrently fs <*> Concurrently as =
    Concurrently $ (\(f, a) -> f a) <$> concurrently fs as

instance MonadBaseControl IO m => Alternative (Concurrently m) where
  empty = Concurrently . liftBaseWith . const $ forever (threadDelay maxBound)
  Concurrently as <|> Concurrently bs =
    Concurrently $ either id id <$> race as bs

But this is less than desirable, since it requires -XUndecidableInstances. Can you think of a better way to go about this? If we are able to come up with something, I'd be delighted to open a pull request.

maoe commented 10 years ago

Ah, I completely forgot about Concurrently because I've never used it directly. Thanks for reminding me of it.

I'm not quite sure how bad UndecidableInstances are, but I came up with something which doesn't need it but uses TypeFamilies and KindSignatures instead:

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}

newtype Concurrently (b :: * -> *) m a = Concurrently { runConcurrently :: m a }

instance (base ~ IO, Functor m) => Functor (Concurrently base m) where
  fmap f (Concurrently a) = Concurrently $ f <$> a

instance (base ~ IO, MonadBaseControl base m) => Applicative (Concurrently base m) where
  pure = Concurrently . pure
  Concurrently fs <*> Concurrently as =
    Concurrently $ uncurry ($) <$> concurrently fs as

instance (base ~ IO, MonadBaseControl base m) => Alternative (Concurrently base m) where
  empty = Concurrently . liftBaseWith . const $ forever (threadDelay maxBound)
  Concurrently as <|> Concurrently bs =
    Concurrently $ either id id <$> race as bs

Or if we prefer FlexibleInstances over the type equality constraints:

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE FlexibleInstances #-}

newtype Concurrently (b :: * -> *) m a = Concurrently { runConcurrently :: m a }

instance Functor m => Functor (Concurrently IO m) where
  fmap f (Concurrently a) = Concurrently $ f <$> a

instance MonadBaseControl IO m => Applicative (Concurrently IO m) where
  pure = Concurrently . pure
  Concurrently fs <*> Concurrently as =
    Concurrently $ uncurry ($) <$> concurrently fs as

instance MonadBaseControl IO m => Alternative (Concurrently IO m) where
  empty = Concurrently . liftBaseWith . const $ forever (threadDelay maxBound)
  Concurrently as <|> Concurrently bs =
    Concurrently $ either id id <$> race as bs

What do you think?

maoe commented 10 years ago

The FlexibleInstances approach doesn't work because b is ambiguous. The former approach should work.

maoe commented 10 years ago

@jonsterling Just pushed the patch. Could you try this with your use cases?

jonsterling commented 10 years ago

Great, I'll try it out once I get to my office! Thanks!

jonsterling commented 10 years ago

This seems to work nicely! Though it's not clear to me that it does in fact require -XTypeFamilies...

EDIT: Ah, I see. Either -XTypeFamilies or -XGADTs will do. Nvm!

maoe commented 10 years ago

Thanks. Just released it as v0.2.0.

jonsterling commented 10 years ago

Excellent! Thanks for the quick turnaround :)