-
Notifications
You must be signed in to change notification settings - Fork 70
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
Change Event
and Behavior
to have a representational role
#221
base: master
Are you sure you want to change the base?
Conversation
What if you unfold newtype Cached m a = Cached (m a)
newtype Event a = E_ (Moment (Pulse a)) Also: Semigroup, Monoid, Num can be derived for |
@Icelandjack I need to confirm but I think we might need cached to be |
Last I looked - and my understanding could be way off here - the So if I had to speculate wildly, I'd guess |
Let's give it a try then! I'm very keen to chase one fewer pointer if possible. I'll look into this. What @Icelandjack proposes is much nicer than my Yoneda hacking, as cute as it is. |
Alas, Observable allows us to use an
uses the same event internally for Observable sharing must use reactive-banana/reactive-banana/src/Reactive/Banana/Prim/Cached.hs Lines 33 to 36 in 4dcf4b0
The fact that The need for observable sharing likely also rules out other solutions, such as a Yoneda embedding like |
How important is this issue of type roles? If it is a minor improvement, I would recommend against complicating the representation of If I understand correctly, the issue seems to be that |
Not hugely, though I refer back to my original example. This is code I wrote and expected to work, and was a bit disappointed to find I was going to have to hand-write the
Yep, your understanding is correct. I'll see if I can make |
Thanks for the clarification, that is subtle indeed... so subtle that I'm not sure I understand it yet! Simplifying the machinery a tiny bit, let's say I decorate an IO action with an {-# NOINLINE cache #-}
cache :: IO a -> IO a
cache action =
unsafePerformIO do
putStrLn "hello, world!"
pure action I should still expect this function to return a thunk that, when forced to WHNF, prints "hello, world" and returns the IO action passed in. So regardless of how many pointers to the returned action exist, the thunk will only be updated in place once. Sorry for dragging this on, feel free to ignore this comment |
If values of type I remember recording a remark on this in the HeinrichApfelmus/optimize-monad-trans repository at one point:
|
In the documentation on role annotations, a data type that is identical to |
Does the "state hack" thwart the sharing attempt in this case? I don't see how yet: As I understand it, the "state hack" is to label all lambdas binding expensive :: Int -> Int
expensive n = n
{-# NOINLINE expensive #-}
stateHack :: Int -> State# RealWorld -> (# State# RealWorld, Int #)
stateHack n =
let i = expensive n
in (\s -> (# s, i #)) where stateHack
= \ (n_avF :: Int) (s_avH :: State# RealWorld) ->
(# s_avH, expensive n_avF #) and we see that our state lambda was eta-expanded outside of our let. Compare this with a non-state argument: normal :: Int -> () -> (# (), Int #)
normal n =
let i = expensive n
in (\s -> (# s, i #)) and we see that normal
= \ (n_avI :: Int) ->
let {
i_sFV :: Int
[LclId]
i_sFV = expensive n_avI } in
\ (s_avK :: ()) -> (# s_avK, i_sFV #) We aren't discussing the usual "state hack" problem of a let expression returning an So, let's instead consider a variant of expensiveIO :: Int -> State# RealWorld -> (# State# RealWorld, Int #)
expensiveIO n s = (# s, n #)
{-# NOINLINE expensiveIO #-}
stateHackIO :: Int -> (State# RealWorld -> (# State# RealWorld, Int #))
stateHackIO n =
-- newAction :: IO (IO Int)
let newAction :: State# RealWorld -> (# State# RealWorld, State# RealWorld -> (# State# RealWorld, Int #) #)
newAction st0 = case expensiveIO n st0 of
(# st1, i #) -> (# st1, \s -> (# s, i #) #)
in case runRW# newAction of
(# _, a #) -> a In this variant of Peeking at the core reveals that we get the desired sharing: CorestateHackIO
= \ (n_axw :: Int) ->
case runRW#
@ ('TupleRep '[ 'TupleRep '[], 'LiftedRep])
@ (# State# RealWorld,
State# RealWorld -> (# State# RealWorld, Int #) #)
(\ (st0_axy [OS=OneShot] :: State# RealWorld) ->
case expensiveIO n_axw st0_axy of { (# ipv_syX, ipv1_syY #) -> -- execute `expensiveIO` and bind result to `ipv1_syY`
(# ipv_syX,
\ (s_axB :: State# RealWorld) -> (# s_axB, ipv1_syY #) #) -- `ipv1_syY` is captured in the returned IO function
})
of
{ (# ipv_sz1, ipv1_sz2 #) ->
ipv1_sz2
} Now consider a cache example using cache :: IO a -> IO a
cache action =
unsafePerformIO do
ref <- newIORef Nothing
let newAction = do
readIORef ref >>= \case
Nothing -> do
res <- action
writeIORef ref (Just res)
pure res
Just res -> pure res
pure newAction We are returning an Corecache
= \ (@ a_axD) (action_awc :: IO a_axD) ->
case runRW#
@ ('TupleRep '[ 'TupleRep '[], 'LiftedRep])
@ (# State# RealWorld, IO a_axD #)
(\ (s_aE8 [OS=OneShot] :: State# RealWorld) ->
case noDuplicate# @ RealWorld s_aE8 of s'_aE9 { __DEFAULT ->
case newMutVar#
@ (Maybe a_axD) @ RealWorld (GHC.Maybe.Nothing @ a_axD) s'_aE9
of
{ (# ipv_aET, ipv1_aEU #) -> -- Our IORef is created here
(# ipv_aET,
(\ (s1_aEK :: State# RealWorld) -> -- And here is our State# lambda capturing the IORef
case readMutVar# @ RealWorld @ (Maybe a_axD) ipv1_aEU s1_aEK of
{ (# ipv2_XF8, ipv3_XFa #) ->
case ipv3_XFa of {
Nothing ->
case (action_awc
`cast` (GHC.Types.N:IO[0] <a_axD>_R
:: IO a_axD
~R# (State# RealWorld -> (# State# RealWorld, a_axD #))))
ipv2_XF8
of
{ (# ipv4_XFi, ipv5_XFk #) ->
case writeMutVar#
@ RealWorld
@ (Maybe a_axD)
ipv1_aEU
(GHC.Maybe.Just @ a_axD ipv5_XFk)
ipv4_XFi
of s2#_aGj
{ __DEFAULT ->
(# s2#_aGj, ipv5_XFk #)
}
};
Just res_axr -> (# ipv2_XF8, res_axr #)
}
})
`cast` (Sym (GHC.Types.N:IO[0] <a_axD>_R)
:: (State# RealWorld -> (# State# RealWorld, a_axD #))
~R# IO a_axD) #)
}
})
of
{ (# ipv_aEb, ipv1_aEc #) ->
ipv1_aEc
} This can perhaps be seen even more plainly in the STG: STG
|
Fixes #219. The main problem is that in
newtype Event a = E (Prim.Event a)
, we haveand that
Cached m a
definition forcesa
to benominal
.This PR works around this nominal role by using a Yoneda-trick, which restores the role to be representational (at the cost of carrying a mapping function around).