-
Notifications
You must be signed in to change notification settings - Fork 25
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
Merge Effect
and ST
#31
Comments
I dislike this for more or less the same reason that I dislike #30, although this one bothers me quite a bit more. My objection to this is that I don’t think it makes a lot of sense unless you’re intimately familiar with how these things are all implemented. Mutable variables are just one of the many things you can do with Effect - why privilege that specific thing over all the others? It seems plausible that there is at least one other effectful concept which makes sense to model in a similar way to ST, but if we implement this, Effect will already be tightly welded to ST (and exclusively ST) so that won’t be an option. Also, what will a beginner who finds For me, an API surface that makes sense should really come before implementation concerns; I don’t think this proposal follows that principle. |
It's not really about mutation per se. It's more that it's tracking the token for observable effects. If IO/Effect is pseudo-semantically I think there's quite a bit of value in sharing as much as possible and I think there's something semantically that unites them. For example you can't use EffectFns in ST even though it's the one place that would benefit the most from it, and I'm not confident a proposal would be accepted to introduce them (due to complexity concerns or proliferation of types). |
Can you give an example of where you might want to make use of this idea outside of what's currently covered by |
Not that this was part of the original goal, but actually it could be The point of this is to be able to freely mix It seems to me that the cons are:
vs pros:
The con seems like even less of a serious concern given one of the conditions of implementing this is that synonyms would appear in error messages, so the only place to see |
But surely it won't be possible to do this, or indeed to reap any of the userland benefits of this proposal, without exposing the guts of how I think I might be able to get on board with this if we were to define
and set things up so that the |
I guess I just don't see what would be confusing about it, it doesn't seem much different than the likes of |
Ah right yeah, that is a good point. |
I guess we ought to make a decision here. I'm still reluctant; I think my problem with this is really that Effect is used quite heavily in basically every single PureScript app, while in my mind ST is, to be frank, mostly a curiosity and an example for showcasing rank-n types. Given this, I don't think the design of Effect should be influenced by what would be handy for users of ST; I don't think we should complicate the API for using mutable variables in Effect so that the same functions can be used in ST without coercions, even if it is in the relatively minor way being proposed here. Just because Effect is so much more important than ST is. Having As for the other pros you mentioned:
For me, having these things influence the API design is wrong. These are implementation concerns which I still don't think should be a factor here. I'm sure there are other ways of removing this duplication (e.g. making ST a newtype around Effect and making the compiler optimizations appropriately smart).
This is arguably a con rather than a pro, I think 😛 |
I just want to say that the migration from
Totally agreed. |
maybe even (I didnt think too much) type Effect = ST (global :: Global) type MyEffect = ST (global :: Global, mutableArray1 :: STMutableArray, mutableArray2 :: STMutableArray) |
I listed those pros as like, "bonus things we get if we do this", not reasons to do it, the implementations are already identical. I like it for the API as conceptually I think it makes sense too - everything you can do in ST is reasonable to do in Effect also. It's just whether it's allowed to happen locally or globally. |
For what it's worth, I think explaining
We've already got
This sounds like a better tradeoff for deduplicating code, merging ST and Effect, and ensuring
Based on Nate's comment above, perhaps this is why making ST a newtype around Effect wouldn't work?
If we did not implement this change and used |
Couldn't we also just add API that enables ST in a global context? Or is this the "make ST a newtype around Effect" idea that Harry proposed? |
I think there's definitely a tradeoff in how general you make things. When you make things really general, the benefit is that you only need to make sense of the function once and you can apply that knowledge in lots of different places. However, when you make things more targeted towards particular use cases, it means that the APIs require less effort to understand, you're able to write documentation which is easier to make sense of (since you can use more concrete examples without being misleading), and I think the APIs are also less likely to behave in surprising ways if they end up being applied in a setting where they don't really make sense. In this particular case, my opinion is that the difference between I don't actually think this issue is specific to learners (although it is likely to affect learners more than other people); I think it's potentially an issue for anyone who uses this API. Ergonomics really do matter; it shouldn't just be a case of saying "approach A allows people to do X in more situations than approach B therefore A is better." I should note that this proposal could improve the ergonomics quite significantly for people who are interested in using mutable variables in both Effect and ST, so I guess my objection here is dependent on my impression that people don't use ST very often, which could be wrong!
Not necessarily: I think the argument in favour of doing this is (more or less) that if we do what's proposed here, we'll end up with less API surface overall, and we won't run into cases where there's some abstraction which is usable only in Effect or only in ST even though it makes sense for both (such as EffectFns). Right now, you have to explicitly coerce to go between Effect and ST, which means that you have to go out of your way to make something which works in both settings, by doing something like the
I think using Coercible is basically the same as what I am advocating; the central question is "should there be explicit coercions or not".
We have that already; see #26 |
I'd actually forgotten about MonadST until just now. I kind of feel like MonadST solves all the same problems from the API surface perspective, but it's just not viable right now because it can't be optimized. So maybe we could alternatively solve these problems by providing MonadST operations for the case where people want to use the same code in both Effect and in ST, continuing to have Effect and ST be separate types needing explicit coercions, and instead working on making compiler optimizations smart enough to make MonadST viable? |
The main issue right now is needing STFn functions, which are provided for Effect, but not ST. Having MonadST does not help with that. It's not totally clear to me how newtyping would solve the STFn problem either. I could see solving magic-do if a particular bind implementation dereferences to the concrete Effect bind implementation (via |
Note, I'm not saying that to advocate for the approach in this thread. If we want to support parallel implementations of everything, sure, that's fine. All I personally care about is that I can write more efficient ST code when necessary. |
Right, I see. To be completely honest I don't love the application of EffectFn as a solution for performance problems, I personally prefer to think of it as an API marshalling helper thing like |
I'm not sure it's avoidable as long as these are packed behind curried FFI functions. Or at least it would be non-trivial and require a backend specific optimizer that understands the FFI. EffectFn and magic-do is the only reason halogen-vdom is usable. Currying unfortunately has significant overhead in tight loops.
Kind of. AFAIK there were never EffFn equivalents for ST bindings. It is true that you could write them yourself though. |
I think @hdgarrood explained the pros and cons quite clearly, but I don't think that the burden on learning that When starting to learn PureScript, you'd be only introduced to global effects. This means that In contrast, by using
|
Below is my understanding of the issue. I wish
Lastly, I'll explore the Affected Parties and Their ConcernsThe relevant parties identified so far are...
Similarities and DissimilaritiesFirst, Second, they are dissimilar in why they exist. In Third, both are always in curried forms: Fourth,
Other Related Problems Caused by These Types Being SeparateFirst, each API must be defined twice, so that one can use the API in both an
Second, we need two mutable reference types (i.e. While the above two problems were lessened with What Problem(s) are we trying to solve?In sum, there are actually two problems we are trying to solve:
If we're only trying to solve only Problem 1 (i.e uncurried foreign import data EffectFn1 :: Type -> Type -> Type
foreign import data STFn1 :: Region -> Type -> Type -> Type
foreign import mkEffectFn1 :: forall a r. (a -> Effect r) -> EffectFn1 a r
foreign import mkSTFn1 :: forall h a r. (a -> ST h r) -> STFn1 h a r
foreign import runEffectFn1 :: forall a r. EffectFn1 a r -> a -> Effect r
foreign import runSTFn1 :: forall h a r. STFn1 h a r -> a -> ST h r If we're trying to solve both Problems 1 and 2, then I believe merging foreign import data Region -- kind
foreign import data ST :: Region -> Type -> Type
foreign import data Global :: Region
newtype Effect a = Effect (ST Global a) -- Effect newtype constructor is not exported We can solve Problem 1 now and decide how we want to solve Problem 2 later. If we choose to solve Problem 2 by merging Note: if we did provide The
|
I'll come back to this with my own summary soon, what you have here is close, but not quite right I think. |
I think part of the problem with this discussion so far is it mixes ergonomic concerns and implementation concerns and switches back and forth between them. My primary motivation for this was the ergonomics. Ergonomics
We currently have the We could start implementing all
|
Would a good compromise here be doing |
Yeah, it's the wrong way around 😉 - |
This is a idea for the future, floated by @natefaubion when we were talking about #30.
ST
andEffect
have the same implementation, and it should be possible to coerce safely in either direction (at first I thoughtrunST
would essentially becomeunsafePerformEffect
when applied to anEffect
converted toST Global
, but actually that won't type check). Given that,Effect
could just be what is currentlyST Global
.This would mean we have fewer cases to deal with in the optimizer, no need to implement two sets of essentially identical instances and functions, and avoid the need to explicitly coerce or lift
ST
things to work inEffect
(useful forSTArray
, etc.).With this unified thing, the somewhat obvious name for the type is
Effect
, but we'd probably want to preserve theType -> Type
version ofEffect
we currently have so we don't have to perform anEff
-to-Effect
-worthy migration again, so we'd need to come up with something else.Nate also suggested that we'd want to wait until synonyms are preserved in error messages, since we don't want to reveal the underlying type for
Effect
to newbies unless we need to, which I agree with.The text was updated successfully, but these errors were encountered: