Skip to content

Conversation

thomashoneyman
Copy link
Contributor

This PR gives the application monad a Parallel instance. The instance requires a related applicative to the app monad, so I've created ExampleA over ParAff to make this possible. We do the same thing in our application, and I believe it's what SlamData does as well.

This instance makes it possible to write functions constrained by one or more DSLs that also is running a parallel computation, and then to use the application monad to run it.

@thomashoneyman
Copy link
Contributor Author

I can rebase these commits if you want (or feel free to squash & merge) -- I'm not sure why the same duplicates are showing up here.

I have added parallel constraints on two components to prove that this works, but I wasn't sure what to actually run in parallel to show it off. I can think and add an implementation too if there's something you think would be a good fit for your demo.

@eviefp
Copy link
Owner

eviefp commented Jun 30, 2018

Thanks, this seems quite interesting, but it's a bit confusing. How does Parallel actually work, or what does it bring?

@thomashoneyman
Copy link
Contributor Author

thomashoneyman commented Jul 1, 2018

Parallel lets you represent parallel async computations. The main two ways we use this are either to race computations or perform several of them concurrently. This comes from

  • parTraverse, which allows you to perform a collection of effects in parallel
  • parOneOf, which allows you to do the same thing, but only take the effect which completes first

We commonly need to make a set of network requests and we don't want to perform one, wait for it to finish, then do the next, and so on. Instead, you can take an array of endpoints to hit and then use parTraverse (performRequest :: Request -> Aff Result) (requests :: Array Request) to perform them all in parallel rather than one after the other.

Now, so long as you're in Aff you don't need the parallel type class to do this in Halogen. Aff already has all the instances you need. But this forces you to write any of these functions directly in Aff -- not in our nice polymorphic m with its DSLs.

For example, with a snippet from your code:

component ::  f m
  . Monad m
 => Parallel f m
 => H.Component HH.HTML Query DialogOptionsLite DialogResult m
component
  = H.component
    { initialState: identity
    , render
    , eval
    , receiver: const Nothing
    }

  where

  render :: State -> H.ComponentHTML Query
  render dialogOptions = ...

  myParallelFunction :: m (Array Int)
  myParallelFunction = parTraverse pure [0]

  eval :: Query ~> H.ComponentDSL State Query DialogResult m
  eval (CloseDialog idx next) = do
    arr <- H.lift myParallelFunction
    H.raise <<< DialogResult $ idx
    pure next

You can't write myParallelFunction above without the parallel type class constraint. You could write it if you made sure to stay only in Aff, but in this case we'd like to be using our various DSLs and so we want to use some m with constraints.

However, the application monad (without this PR) can't do any of this parallel stuff unless we give it an instance of Parallel. You couldn't write this code and then run the application with runExampleM.

@eviefp
Copy link
Owner

eviefp commented Jul 1, 2018

That's pretty cool, thanks for the explanation. I'll play with the branch tomorrow for a bit.

Maybe one idea would be to keep the instance in the Monad module, and only use it in one sample component that would document how to do this? As opposed to using it in all, even if it's not actually needed and confusing people. What do you think?

@thomashoneyman
Copy link
Contributor Author

You know what -- on second thought, I realize that the Parallel type class doesn't need to be a constraint all the way up to the component type signature -- I could have defined that function using a different m, like this:

  myParallelFunction :: forall f m0. Parallel f m0 => m0 (Array Int)
  myParallelFunction = parTraverse pure [0]

Then, since HalogenM can perform parallel computations like this, you can freely use this in your eval function without needing an instance for your custom application monad.

Now, any place you want to do this without running the computation in eval will still be out of luck and you would need the constraint on the app monad. But for an example project like this that use case seems out of scope.

Perhaps there could be a link to this PR as a "further reading" sort of thing, but I think this will cause more confusion than it will help folks building their apps.

I've changed my mind, and I think I'm in favor of not merging this PR. Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants