Code Monkey home page Code Monkey logo

Comments (19)

alexmingoia avatar alexmingoia commented on June 8, 2024 2

I think the simplest solution is to not nest actions. Have a single application action type, state, and update function a la Redux. This is the architecture I use for larger applications.

from purescript-pux.

natefaubion avatar natefaubion commented on June 8, 2024 1

@chexxor The answer is not in SlamData. "Interpretation" is just a function as far as the library is concerned. When launching an application, requiring that the user provide m ~> Aff eff or forall a. m a -> Aff eff a means:

I, the library, need all effects to run in Aff, because that's how I do all my bookkeeping. I don't care that the user might be writing it in some m. What I do care about is that they can tell me how to turn it into some Aff so I can run it.

The forall a or ~> or NaturalTransformation is just a trick to enforce that the interpreter can't care about the return result; it can only care about how to interpret the effects.

If you look at that gist, you can see I've written some machinery that can launch Aff components (in which case the interpreter is simply fixed as id), or the user can provide an interpreter.

https://gist.github.com/natefaubion/9cde1d3f278fae28678ed714efb78496#file-startapp-purs-L127-L140
https://gist.github.com/natefaubion/9cde1d3f278fae28678ed714efb78496#file-startapp-purs-L150

And all I do is use that interpret parameter to run the effects in Aff
https://gist.github.com/natefaubion/9cde1d3f278fae28678ed714efb78496#file-startapp-purs-L198

That's what I mean by "trivial". I am not doing any pattern matching in the library. By being polymorphic, it can't do anything with the provided effects, which is a good thing. But if the user provides some function that can translate it into something the library does know about (namely Aff), then it can run it!

Does that help?

from purescript-pux.

natefaubion avatar natefaubion commented on June 8, 2024

We solve this in Halogen by remaining polymorphic over the base Monad for effects. By requiring all effects to be monomorphic over Aff, users are forced to come up with ad hoc parameter passing schemes that could otherwise be solved by mtl effects like MonadAsk, MonadState, etc. Only at the root do we require that the user provide some interpretation of m ~> Aff eff for their effect Monad. We use this at SlamData to provide app-wide configuration, global state, authentication, and API services in an abstract and testable way, with service dependencies expressed via type classes.

I think it would be great if Pux were written abstractly over effects, instead of requiring everyone to use Aff monomorphically. It's very easy to provide concrete type synonyms with Aff applied for simple use cases, but I'm convinced that large applications (like SlamData) absolutely need abstraction capabilities.

from purescript-pux.

chexxor avatar chexxor commented on June 8, 2024

from purescript-pux.

chexxor avatar chexxor commented on June 8, 2024

@natefaubion, is the code which implements that polymorphic thing feely available on slamdata's github repo? I have been wanting to add that to Pux, but I don't have enough understanding to implement it on my own.

from purescript-pux.

simg avatar simg commented on June 8, 2024

Here is what I believe @natefaubion is refering to:

https://gist.github.com/natefaubion/9cde1d3f278fae28678ed714efb78496#file-main-purs-L111-L138

@chexxor. I'd also be interested to see your code.

from purescript-pux.

natefaubion avatar natefaubion commented on June 8, 2024

@chexxor the machinery for such a change for a library is trivial. It's merely providing a function of m ~> Aff eff when you launch your application. Then the main driver must call that interpreter when dispatching actions.

from purescript-pux.

natefaubion avatar natefaubion commented on June 8, 2024

The code linked to by @simg is an example implementation of some custom effect Monad, which lets one lift Aff actions or read from some global environment that exposes a read-only User.

from purescript-pux.

chexxor avatar chexxor commented on June 8, 2024

@natefaubion, you say it's trivial, which is encouraging, but I can't make an image in my head how such an all-purpose interpreter works. Does this imply Free? Or do you literally mean just generic m and pattern-matching on the specific m which was used? I'll search slamdata code in a few hours to see if the answer is there.

from purescript-pux.

simg avatar simg commented on June 8, 2024

surely that isn't so much less modular as completely not modular?

I've just published what I've built so far with Pux: https://github.com/simg/pux-example just to help explain the challenges I'm coming across.

Is that completely the wrong approach or is Pux not intended for such large applications?

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

Modularity is not sacrificed. You can always separate functions or types out as needed.

In larger applications, having the action types and updates in one place is far easier for me to manage than deeply nested actions and update functions. Sometimes it makes sense to nest things and string types together, but I would recommend doing that as needed.

from purescript-pux.

simg avatar simg commented on June 8, 2024

I don't suppose you're able to provide any examples?

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

Not of a larger application. They're commercial projects so I can't easily share the code.

I will add a section and example to the guide about it later this week with the next release. Essentially, there's one big update function and a top-level action data type that has constructors for pretty much every action in the app. Essentially just put all your update logic in a root component.

from purescript-pux.

simg avatar simg commented on June 8, 2024

Thanks. I quite understand about not being able to release commercial code.

I'll await the updated guide with interest :)

from purescript-pux.

chexxor avatar chexxor commented on June 8, 2024

Here's how to do with Ref GlobalState, like I described above. Before I started using Ref, I had to have children send their updates to the global state through the update function. I showed that here, also. It was really awkward.

-- Layout.purs
init :: forall eff. Eff (dom :: DOM, ref :: REF | eff) State
init = do
  -- I store Global.State in its own module. It has it's own Actions, too.
  globalRef = newRef Global.initGlobal
  pure
    { globalRef
    -- Pass the globalRef to all children which want to use it.
    , child1: Child1.init globalRef
    , ...
    }

applyGlobalAction :: forall e. Global.GlobalAction -> EffModel State Action (ref :: REF | e) -> EffModel State Action (ref :: REF | e)
applyGlobalAction globalAction effmodel =
  { state: effmodel.state, effects: (liftEff $ updateGlobalRef effmodel.state.globalRef globalAction) : effmodel.effects }

update :: Action -> State -> EffModel State Action ClientEffects
update (Child1 action) state =
  -- All children which want to update the global state can
  --   simply modify the Ref.
  -- If you don't use a Ref, you have to
  --    use pass GlobalAction back from the child's update function,
  --    then apply it to the global state, which is stored at root.
    applyGlobalAction (snd updatedChild)
  $ mapEffects NavAction
  $ mapState (state {
      nav = _
    })
  $ fst updatedChild
  where
    updatedChild = Nav.update action state.nav
  -- When the global state changes, or when loading the child view,
  --   the root needs to send the new value to all children using
  --   an Child1.OnLoad Action, or similar.
update (PageView route@(Item assetId)) state =
  -- I stored the current route in global state, as well, so
  --   I need to update that when the route changes.
  applyGlobalAction (ChangeRoute route)
    $ mapEffects ItemAction
    $ mapState (
    state
      { itemState = _
      }
    )
    $ Item.update (Item.OnLoad route) state.itemState

-- Child1.purs

-- If not directly modifying the Ref, you'd have to modify the global state like this,
--  by passing a GlobalAction back in a Tuple with the EffModel.
-- It's relatively awkward.
update :: Action -> State -> Tuple (EffModel State Action ClientEffects) Global.GlobalAction
update (RequestLogout) state = Tuple (noEffects $ state) (Global.ChangeUser Nothing)
update (LoginAction action) state =
    -- If the child has a child which uses global state, you'd need to thread the GlobalAction
    --   through here like this while also storing its state.
    -- It's also awkward.
    lmap
      ( mapEffects Grandchild.Action <<<
        (mapState (state { grandchildState = _ }))
      )
      (GrandChild1.update action state.grandchildState)

from purescript-pux.

simg avatar simg commented on June 8, 2024

thanks @chexxor, that's very helpful

from purescript-pux.

simg avatar simg commented on June 8, 2024

Any updates on the updated docs with examples of larger apps?

from purescript-pux.

simg avatar simg commented on June 8, 2024

One thing that seems like it would be a challenge with a single, global set of actions is maintaining the state of form elements? Particularly if you have a lot of data entry forms?

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

I'm archiving most of the discussion threads. If you'd like to continue the discussion I'd prefer if you posted a question on StackOverflow or Gitter. Thanks.

from purescript-pux.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.