-
-
Notifications
You must be signed in to change notification settings - Fork 511
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
freeze Array.empty, closes #979 #1082
Conversation
would be a breaking change (breaks fp-ts itself) |
But freezing is also a breaking change: |
That's the very purpose of @sutarmin's suggestion, I guess. |
@gcanti But theoretically should not all returned arrays by fp-ts be frozen to avoid mutation? Is 'empty' to be considered a global constant like 'null'? |
@gkamperis I don't think so, why you say that? Personally I consider all arrays (readonly or not) immutable (except when I need local mutation, of course), but nothing stops you to do import * as A from 'fp-ts/lib/Array'
const as = A.takeLeft(2)([1, 2, 3, 4])
as.push(5)
Kind of. Maybe the best analogy is with |
@gcanti The question is a bit philosophical... I am saying this because immutability and purity go hand in hand. Theoretically any array (any value really) returned by an FP function should be immutable by the system and not left as a discipline on the developer. If the developer wants to mutate it they should explicitly take the step and in this case they can easily spread it. Once again, a theoretical/philosophical question...
So in the new "empty" implementation how can you modify it? it is frozen. By freezing it, it has become a global constant reference that any fp-ts function that returns an empty array can re-use safely (and a bit optimally as well). Don't get me wrong, I am not arguing against this change, I am just trying to figure out what the meaning of "empty" is. |
I agree, we could ban
across the whole codebase. My only concern is about ergonomics, does it worth it? |
@gcanti again as a theoretical conversation Immutability always introduces more difficult ergonomics. Since you are talking about removing the mutable version then you just need to state in the docs that all these structures are read-only. Similar to what you did for stack safety. It becomes a working assumption of the lib. |
@gcanti but having said all that I do also think that the lib is fine as it is. :) |
I get it by I doubt it should be the responsibility of the lib instead of the developer.
It should be restricted by its interface then, not by fragile and inconsistent convention. By inconsistent I mean exactly what @gkamperis suggests - we should freeze everything, not only arrays, and always use |
@raveclassic I kind of disagree... in this case maybe it should become a function similar to the but you do not need to prefix everything with I can see @gcanti point that "it doesn't matter if I return a mutable structure"... On the other hand if everything turns frozen then you might avoid dev code that mutates structures within a |
Hardly, because
Personally I'd like to avoid any kind of assumptions and/or conventions and to achieve promised correctness through strict interfaces not producing any side effects like unexpected exceptions. If we want an immutable structure then let's define immutability in its interface.
Not sure I'm following. What kind of state management are you talking about? The discussion is about a broken contract. Before it things went bad if someone suddenly mutated |
@gcanti There is some serious performance issue with freeze. Check automerge/automerge#177 But maybe it's not an issue for |
@raveclassic I think you are taking my comparison the wrong way. I am comparing concepts not behaviour. I get what you are saying. But the instigating issue is valid. Someone can get So changing RE the state management, I think I used the wrong words. I meant managing the immutability state. I meant that maybe fp-ts should not trouble itself on weather the data structures used with it are immutable or not as it is not really required. |
I agree but we can't, as I said above changing the type of This PR is just a way to prevent unwanted occasional mutations, but I'm fine to revert the change if you all disagree (moreover personally I've never experienced problems because I just don't mutate things and don't use unsafe libraries)
If we switch to Readonly* interfaces there's no need for freezing (which is not an option, cc @steida)
I'm mostly interested in this ^, that's my main concern. Admittedly after the introduction of the
and see how it goes. And if it ends up well we can remove the mutable counterparts in v3 |
+1 for the readonly modules. If we can prevent programming errors at compile time we should go this direction or we run in the problem that the interface behavior does not match the runtime behavior. |
is there any reason why if it is required to be a value then if it is not required to be a value converting it to a function resolves the mutation/freeze issue. Suppose everything works out, fast fwd to v3: prefixing everything with |
Ahhh I see now, thanks. Yeah, that would fit however with a light performance penalty. Still a breaking change though.
Thanks once again for explanation. I'd say fp-ts should only trouble itself on maintaining correctness of its interfaces and not expose such fragile implementation details to the outer world. Thanks for explanation.
Well I tend to revert it anyway. We faced this issue when we're working with a legacy javascript library that mutated its config built using
Agree. I'd avoid any hidden js "optimizations" 😄
This is related to #987.
That's a great suggestion actually!
Can't agree more! |
OK, this one got controversial, let me add my 5 cents here. As I said in #979 I understand that this is a compromise and I considered I really love the idea about the compile-time contract (the whole TS thing is about that, right?) and I'm looking forward to trying TL;DR I would consider moving from
It's not the only case where we had problems, so I would prefer a more universal solution. |
@sutarmin You are talking about a problem in a javascript library. Nobody forbids an arbitrary 3rd party javascript code to mess everything everywhere including fp-ts itself, that's javascript in the end. Since
Looks like a room for improvement in the end-user code, not in fp-ts core. Summing up
This is not a compromise actually, current implementation (before this change) fully satisfies the interface. You are trying to solve a problem which is out of scope of the lib because 1) if we want immutability, we can use |
Nope, before this change, one could easily do it in any library, even written in Typescript.
Actually,
Yes, this was considered in #979 as well, but it's not too reliable because it's easy to forget. And again: I think |
Then it's even more not the responsibility of
Breaking a valid contract with exceptions. That's not
But it's still up to developer to think about it, not for
Should we also freeze Monoids? Alternatives? All available instances in the whole lib? Anyway I still don't see any strong reasoning from your side which would make breaking the contract justified but some highly specific edge case without any details. Could you provide an example of such case where it's impossible to fix it on your side so that such change should be made to fp-ts core? |
It's always possible, but the idea was to improve overall DX in this case, to provide developer information right away that something unexpected just happened.
It's not quite the same, much more difficult to occasionally mutate such instances.
Agreed. The question is how reliable is the interface called Of course, explicit |
So no example?
It is. There's no "occasionally" or "intentionally". Either you can break things or not. Either consistently or not.
That's not the interface but an implementation detail. The interface says I am able to push to
When we can define such |
I beg to differ, but I'm stepping back at the moment, all my arguments are already in the thread. |
@raveclassic just an observation: I'm ok to revert this change (will send a PR), especially if we add Readonly* modules which hopefully will make Consider export interface Semigroup<A> {
readonly concat: (x: A, y: A) => A
} ^ this says nothing about the most important thing: associativity. |
|
No description provided.