-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Confused when using StateT Aff as my component's monad #386
Comments
Here is a minimal example: module Main where
import Prelude
import Halogen as H
import Halogen.HTML as HH
import Control.Monad.Aff (Aff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.State (StateT, evalStateT, get, put)
import Data.Maybe (Maybe(..))
import Halogen.Aff (awaitBody, runHalogenAff)
import Halogen.Aff.Effects (HalogenEffects)
import Halogen.VDom.Driver (runUI)
type AppEffects = HalogenEffects (console :: CONSOLE)
type App = StateT String (Aff AppEffects)
data Query a = Initialize a
app :: forall i o. H.Component HH.HTML Query i o App
app = H.lifecycleComponent
{ initialState: const unit
, render: const (HH.text "")
, eval
, receiver: const Nothing
, initializer: Just (H.action Initialize)
, finalizer: Nothing
}
where
eval :: Query ~> H.ComponentDSL Unit Query o App
eval (Initialize next) = do
H.lift $ put "Bar"
val <- H.lift get
H.liftEff $ log val
pure next
main :: Eff AppEffects Unit
main = runHalogenAff $ do
body <- awaitBody
runUI (H.hoist (flip evalStateT "Foo") app) unit body I would expect that this logs "Bar" to the console, but it logs "Foo". |
I'm not exactly sure why it behaves this way, but I think you'll find that the normal |
Another option would be to use |
Ah I see, since But still, I would think in the example it should behave differently because it's all happening in one eval. It's basically the same situation as in the following code, so the result should be the same if flip evalStateT "Foo" $ runExceptT do
lift $ put "Bar"
val <- lift get
lift $ lift $ log val |
I would expect it to behave properly in a single thread as well. |
Yeah, that's a good point. 😕 If it turns out there isn't a solvable bug here I guess I'll have to remove the |
Just an FYI eval :: Query ~> H.ComponentDSL Unit Query o App
eval (Initialize next) = do
H.lift $ put "Bar"
val1 <- H.lift get
val2 <- H.lift (put "Bar" *> get)
H.liftEff $ log val1
H.liftEff $ log val2
pure next Logs "Foo" and then "Bar", which indeed breaks the transformer laws. So we either need to figure out how to make it lawful, or drop the MonadTrans instance. I think we'd all prefer the former 😆 |
And this is clearly because it's not possible for |
👍 for removing |
I'm trying out a |
@themoritz we figured out what's wrong here at least. It doesn't matter whether the instance is law abiding or not in this case; the problem is interpreting the What needs to happen really is for I'm torn as to what to do now, as this example has illustrated a pretty severe limitation in the provided |
Where do you think that leaves us with the |
I've not given up on the proper fix yet, now I've had some more time to ruminate on it I have a few ideas of things to try to make the implementation work :) |
Although we'll still need to figure something out to stop |
Hi. Any progress on this one? As far as I understand this makes providing global state to components difficult? Because of type strictness passing around large amounts of data through component inputs or changing where some state is stored requires quite a lot of work and very quickly becomes tiring in halogen. Any know remedies for that? |
@mpodlasin StateT is not a solution to global state, as I stated above. It's possible to use a type AppEnv =
{ state :: Ref SomeGlobalState
}
newtype AppM eff a = AppM (ReaderT AppEnv (Aff eff) a)
runAppM :: forall eff. AppEnV -> AppM eff ~> Aff eff
runAppM env (AppM app) = runReaderT env app
-- derive all the instances
derive newtype instance ....
getState :: forall eff. AppM (ref :: REF | eff) SomeGlobalState
getState = AppM $ ReaderT \r -> readRef r.state
setState :: forall eff. SomeGlobalState -> AppM (ref :: REF | eff) Unit
setState s = AppM $ ReaderT \r -> writeRef r.state s And use |
@natefaubion Thanks a lot! I will try that approach. |
I'm going to close this as the specific issue was solved. I was leaving it open with the intention that one day driver might return a constrained |
I want to use a state monad to run my components in order to have some global app state:
StateT String (Aff (HalogenEffects eff))
But when in
eval
I do something likex
is still the old value. Is there a reason for this that I don't see? I'm using the current halogen master.The text was updated successfully, but these errors were encountered: