elmish / Elmish.WPF

Static WPF views for elmish programs
Other
428 stars 71 forks source link

v4 release scope #253

Open TysonMN opened 4 years ago

TysonMN commented 4 years ago

Branch v4 contains these new features.

Would anyone like to see any other changes? Does anyone have any concerns with this list of changes?

I will edit this comment to keep this list of items current (such as to add, check off, and strikethrough).

cmeeren commented 4 years ago
TysonMN commented 4 years ago

Added to my first comment as

[ ] Remove everything currently marked with the Obsolete attribute

TysonMN commented 4 years ago

We have had the branch #143_#157_optimize_updateValue_using_refEq sitting around for 10 months. In contains a breaking change, but it wasn't very important, so we didn't do anything with it.

I suggest we include this change in v4. I rewrote and recommitted the changes from that branch in a new branch called #157_OneWaySeq_via_OneWaySeqLazy to make the changes easier to follow. I did not (yet) make changes to the tutorial or the release notes in that branch. I was planning on waiting for PR #255 to be completed first.

For reference, the issue discussing the change is #157 and PR #162 originally merged this change into the dormant branch.

Here is a self-contained explanation of the change. The oneWaySeq method changes from https://github.com/elmish/Elmish.WPF/blob/0ae868df7d590046899bd8b582bca174426481d2/src/Elmish.WPF/Binding.fs#L450-L461

to

static member oneWaySeq
    (get: 'model -> #seq<'a>,
     itemEquals: 'a -> 'a -> bool,
     getId: 'a -> 'id)
    : string -> Binding<'model, 'msg> =
  Binding.oneWaySeqLazy(get, refEq, id, itemEquals, getId)

As I said in https://github.com/elmish/Elmish.WPF/issues/157#issuecomment-543173555

Any way, here is how I see this change impacting different cases.

  1. If 'a is a mutable or if itemEquals or getId are impure, then the user might have to change their code to compensate for the proposed change.
  2. If 'a is immutable and 'itemEquals and getId are pure, then no change is required by the user. Behavior (in terms of correctness) is preserved. Furthermore,

    1. if 'a is "computed", then performance will be essentially unchanged, but
    2. if 'a is not "computed", then performance would be significantly better (directly related to how many elements are in 'a)

In response, you @cmeeren said in https://github.com/elmish/Elmish.WPF/issues/157#issuecomment-543281455

If what you are suggesting is simply replacing the current oneWaySeq with the code you posted above, I'm in. Feel free to create a PR. :)

It might be helpful to skim your other comments to remember what you thought back then.

cmeeren commented 4 years ago

Thanks! Yes, let's do this for v4.

TysonMN commented 4 years ago

I think the only other thing to consider for inclusion in version 4 is support for a lazy SubModelSeq binding. See issue #143 and especially the recent conversation starting in https://github.com/elmish/Elmish.WPF/issues/143#issuecomment-641532812. In reality, we can probably add this feature without a breaking change by copy-pasting the three methods that create this binding type and adding the functions needed to support laziness.

However, I would like to consider a more radical change to the SubModelSeq binding. I don't have this clear in my mind, so maybe the correct choice is to release v4 without waiting for this understanding to solidify. On the other hand, maybe we can release what we have so far as a v4 pre-release, which allows for more time.

Anyway, here is my idea about SubModelSeq. It was created to support a sequence representing a tree and when mapModel and mapMsgWithModel didn't exist. Now mapModel and mapMsgWithModel do exist and an issue exists about having an optimized binding specifically for trees (c.f. #236). Maybe we can simplify some things here.

I could say more (because I can always say more), but I will pause here for now. @cmeeren, what do you think about making a breaking change related to SubModelSeq in v4?

cmeeren commented 4 years ago

@cmeeren, what do you think about making a breaking change related to SubModelSeq in v4?

Depends on the change, of course. The fact that it's breaking is not itself a problem, though. So please go on. :)

TysonMN commented 4 years ago

(Different train of thought)

I want to try and add the requires equality type constraint to 'id for v4 as described in https://github.com/elmish/Elmish.WPF/issues/139#issuecomment-672815011. I think we could just "manually" add this type constraint, but that is "cheating". I want it exposed through the Binding API because it is inferred by F#.

TysonMN commented 4 years ago

(Another different train of thought)

When update throws an exception in Elmish 3.x, only the exception and message are logged. I want the model logged as well. In elmish/master, which is the release branch for v4, I don't think the exception is caught. It appears that the exception from update causes the Elmish dispatch loop to stop.

(After investigating this further...) I suggest we wrap update in try-with, log everything (the exception, message, and model), and then return the given model to

cmeeren commented 4 years ago

Are you sure that the loop stops in the v4 branch? I thought Elmish made sure of continuing the loop, while calling the error handler that we have added in v4:

https://github.com/elmish/Elmish.WPF/blob/78ae2c137de3fb17c74387f857cbe78c98da87ad/src/Elmish.WPF/WpfProgram.fs#L80

If you have tested and it does indeed stop, then yes, we should probably do as you say.

On a related note, we should definitely give the user a say in what happens when update throws. We can for example have an withUpdateExnHandler that is just exn -> unit, an/or withUpdateExnMsg with type exn -> 'msg that lets the user choose a message we dispatch when update throws.

TysonMN commented 4 years ago

I thought Elmish made sure of continuing the loop, while calling the error handler...

That is the behavior in Elmish 3.x. In Elmish 4.x, I just tested that the program crashes. I asked about this in https://github.com/elmish/elmish/issues/194#issuecomment-675092793.

cmeeren commented 4 years ago

Should we wait until Elmish 4 stable comes out before releasing v4? Do you know the timeframe for that?

TysonMN commented 4 years ago

I don't know their timeframe.

I think it would be good to wait for technical reasons (so we don't need to make another major release to support their major release) and for the current version symmetry (we are both on v3 now and both moving to v4 "soon"). However, I don't want to delay publishing a new NuGet package with the new features just because they are delaying.

I would be satisfied if we published a NuGet package from our our v4 branch at various points as a pre-release. Something like Elmish.WPF 4.0-beta.1.

cmeeren commented 4 years ago

@et1975 do you have a timeframe for final Elmish v4 release?

et1975 commented 4 years ago

Sorry, no time frame - I've been experimenting with Elm-like subscriptions, but got nothing definitive yet. Also I would not recommend coupling the Elmish.WPF major version component to the core elmish, all other packages in the ecosystem are bound only by semver guidelines, not which version of core they are built against.

TysonMN commented 4 years ago

@cmeeren, what do you think about making a breaking change related to SubModelSeq in v4?

Depends on the change, of course. The fact that it's breaking is not itself a problem, though. So please go on. :)

When I wrote that, the suggestion I was considering was to related to SubModelSeq being too complex. Its toMsg function has signature 'id * 'subMsg -> 'msg but the SubModelSeq sample just passes in snd. I would prefer that each binding is used to its full expression in at least one sample. This is not the case for SubModelSeq binding because of the toMsg arguemnt.

However, #266 is improving this situation. It uses a different value for toMsg than snd.

In fact, I now think I would like to make toMsg more expressive by adding 'bindingModel as an argument as described in Solution 2 in https://github.com/elmish/Elmish.WPF/pull/266#discussion_r472302177.

I would also like the function arguments in all the Binding methods to be fully curried (as I mentioned in https://github.com/elmish/Elmish.WPF/pull/266#discussion_r472307305). I think think of two advantages for pairing some inputs. The first is because passing snd is easier than passing fun _ a -> a. The second is because DU case constructors are uncurried functions. However, I think the extra verbosity in those cases would make things more readable. We could deprecate the overloads with uncurried function arguments.

cmeeren commented 4 years ago

Sorry, no time frame - I've been experimenting with Elm-like subscriptions, but got nothing definitive yet.

Gotcha. We'll release v4 when we're ready, and if updating to Elmish 4.0 causes breaking changes, we'll release v5 at that time.

Also I would not recommend coupling the Elmish.WPF major version component to the core elmish, all other packages in the ecosystem are bound only by semver guidelines, not which version of core they are built against.

That was not the intention; the version match in this specific case is purely coincidental. Semver guidelines is certainly the way to go and the way we're currently doing it.

In fact, I now think I would like to make toMsg more expressive by adding 'bindingModel as an argument as described in Solution 2 in #266 (comment).

I'll get back to you when I have read your comment. Might be a day or two until I can find the time.

I would also like the function arguments in all the Binding methods to be fully curried (as I mentioned in #266 (comment)). I think think of two advantages for pairing some inputs. The first is because passing snd is easier than passing fun _ a -> a. The second is because DU case constructors are uncurried functions. However, I think the extra verbosity in those cases would make things more readable. We could deprecate the overloads with uncurried function arguments.

I don't understand. Do you have an example? As you just said, passing toMsg=snd is no problem since it's how it's currently done in the SubModelSeq sample.

TysonMN commented 4 years ago

I would also like the function arguments in all the Binding methods to be fully curried (as I mentioned in #266 (comment)). I think think of two advantages for pairing some inputs. The first is because passing snd is easier than passing fun _ a -> a. The second is because DU case constructors are uncurried functions. However, I think the extra verbosity in those cases would make things more readable. We could deprecate the overloads with uncurried function arguments.

I don't understand. Do you have an example? As you just said, passing toMsg=snd is no problem since it's how it's currently done in the SubModelSeq sample.

Yes, sorry. I should have given an example from the start.

For example, in this function... https://github.com/elmish/Elmish.WPF/blob/9222c08ff30682719969a241ee063acab5afd923/src/Elmish.WPF/Binding.fs#L1740-L1746

...I am suggesting that

toBindingModel: 'model * 'subModel -> 'bindingModel

...be replaced with...

toBindingModel: 'model -> 'subModel -> 'bindingModel

...and...

toMsg: 'id * 'bindingMsg -> 'msg

...be replaced with

toMsg: 'id -> 'bindingMsg -> 'msg

("Replaced" here actually means (1) copy the method, (2) paste the method, (3) make the change in the pasted method, (4) mark the original method as obsolete.) ...

cmeeren commented 4 years ago

Thanks, I see. The reason for toMsg being the way it is, is because typically the DU constructor case for the child message has that signature, as you say. I do not agree that

the extra verbosity in those cases would make things more readable.

The whole point is that you can just pass the DU case. It's less noise. If you have an example that could convince me otherwise, feel free to share it.

Regarding toBindingModel, I can't remember why it was like that in the first place. Perhaps it was just some internal design that leaked out into the signature without my noticing. Do you have an example where currying would improve it?

TysonMN commented 4 years ago

...The second is because DU case constructors are uncurried functions....

The reason for toMsg being the way it is, is because typically the DU constructor case for the child message has that signature, as you say. I do not agree that

the extra verbosity in those cases would make things more readable.

The whole point is that you can just pass the DU case. It's less noise. If you have an example that could convince me otherwise, feel free to share it.

Ah! I was so trapped in in the debugging cycle this weekend that I didn't realize I could do that right now. Check out this commit that I just pushed to PR #266. Now I like the uncurried signature :D

TysonMN commented 4 years ago

Regarding toBindingModel, I can't remember why it was like that in the first place. Perhaps it was just some internal design that leaked out into the signature without my noticing,

I will review the internal design.

Do you have an example where currying would improve it?

If there is no advantage to the uncurried form, then the curried form is better because fun a b -> ... is easier to read than fun (a, b) -> .... I feel like the burden of proof should be on the side of uncurried forms justifying their existence.

TysonMN commented 4 years ago

Regarding toBindingModel, I can't remember why it was like that in the first place. Perhaps it was just some internal design that leaked out into the signature without my noticing,

I will review the internal design.

You added toBindingModel with paired input to four binding methods in the same commit in these four places.

cmeeren commented 4 years ago

Oh, I remember now. For SubModel bindings, as shown in the SubModel, SubModelOpt, and SubModelWin samples, you may typically want the binding model to be one of:

These examples become more verbose if you curry that argument. If you have compelling counter-arguments, freel free to share.

TysonMN commented 4 years ago

Thanks for pointing out those examples. I suppose I could have found them if I had looked harder. I guess I am fine with the status quo.

TysonMN commented 4 years ago

I would be satisfied if we published a NuGet package from our our v4 branch at various points as a pre-release. Something like Elmish.WPF 4.0-beta.1.

It would be useful to me if we published a prerelease before next week. Is it easy to do this with the exiting devops?

cmeeren commented 4 years ago

All branches are built, so as long as you tag a commit on the v4 branch, I think it'll work.

Prerelease version format on NuGet (and in the fsproj) is 4.0.0-alpha-1. If you think there's a chance there'll be more than 9 alphas (or betas), use two digits after alpha/beta, because NuGet sorts prereleases alphabetically and doesn't know that 4.0.0-alpha-10 is newer than 4.0.0-alpha-2.

Any strong opinions on alpha vs. beta?

TysonMN commented 4 years ago

Any strong opinions on alpha vs. beta?

None at all.

Prerelease version format on NuGet (and in the fsproj) is 4.0.0-alpha-1. If you think there's a chance there'll be more than 9 alphas (or betas), use two digits after alpha/beta, because NuGet sorts prereleases alphabetically and doesn't know that 4.0.0-alpha-10 is newer than 4.0.0-alpha-2.

One digit is fine. We don't need 10 prereleases.

cmeeren commented 4 years ago

Great! I suggest alpha for now. Feel free to do alpha releases from v4 when you want.

Note that since there's so many changes, it's fine if the ReleaseNotes in the fsproj just contains a link to the v4 RELEASE_NOTES.md. I haven't been able to get release notes to show up on NuGet.org or in VS anyway. (Am I missing something?)

TysonMN commented 4 years ago

Almost worked.

Quoting the build logs when I pushed the tag.

"NuGet" deployment has been skipped as environment variable has not matched ("appveyor_repo_tag" is "false", should be "true")

TysonMN commented 4 years ago

Actually, I don't need this on nuget.org. I just remembered that we have a folder as a NuGet package source for some other NuGet packages for conveniences just like this. I will do that instead.

TysonMN commented 4 years ago

Oh, now I see that the tag I created only exists locally. That explains it. I will still go with the local approach unless someone else requests a prerelease.

cmeeren commented 4 years ago

Yes, you have to push the tags, too (e.g. git push --tags, note that this only pushes tags, nothing else - I run that after each git push if I work on the command line). AppVeyor (and other CI systems, e.g. Azure Pipelines) builds tags separately from the tagged commit.

I will still go with the local approach unless someone else requests a prerelease.

👍

cmeeren commented 4 years ago

In fact, I now think I would like to make toMsg more expressive by adding 'bindingModel as an argument as described in Solution 2 in #266 (comment).

I have now read your comments. I do not see the full implications of making this change. Unfortunately I can't prioritize digging into this at the moment, nor do I know when I can. But at least it's backwards compatible. If you feel fairly sure that this will not drive users away from the pit of success (also here), feel free to go ahead and work on this. Also, is 'bindingModel the most general/flexible thing to pass, or is there a story for passing the 'model, too?

BentTranberg commented 4 years ago

Just a thought, for whatever it's worth: It's possible to mix curried style and tupled style.

TysonMN commented 4 years ago

FYI: I learned about profunctors from this tweet, so I modified the tutorial to mentioned this concept.

TysonMN commented 4 years ago

FYI: I modified the release notes to match the new behavior introduced by PR #272, which replaced mapMsgWithModel with just mapMsg.

Since we are no longer using mapMsgWithModel, I think it would be reasonable to remove this feature. The easiest way to do this is to make this function internal in the Binding and Bindings modules.

I plan to read and learn more about profunctors. As I do, I expect that this might give me more ideas for how to design the binding API. I don't want to make the same mistake I did with the wrapDispatch feature and expose a "bad" API.

TysonMN commented 3 years ago

I would like to release version 4 with the currently implemented features.

Features like mapModel/mapMsg and the ability to log to any sink are significant improvements. On the other side, achieving the "entire" composable binding API is a lot of work, and I don't see the end yet either. I think I will be able to find more motivation to work on it if I can see things more quickly merged into master. I also expect much of the composable binding API can be added without further breaking changes.

The next step in this direction is to merge master into v4. However, those sample renames we did recently are making that difficult. I am not sure of the correct way to resolve those conflicts.

cmeeren commented 3 years ago

I would like to release version 4 with the currently implemented features.

👍

The next step in this direction is to merge master into v4. However, those sample renames we did recently are making that difficult. I am not sure of the correct way to resolve those conflicts.

Neither am I. The easiest way to merge may be to take the versions from master, and manually apply any changes to the samples from v4.

TysonMN commented 3 years ago

I did a "manual" rebase of master onto v4 by cherry picking commits. The only downside is that commits that had conflicts are now shown as authored by me. I don't think we care about that.

I pushed v4. All the tests pass. I tested all the samples, and everything works. I have been using some previous version of v4 at work (by committing the NuGet package into your repo and referencing the containing folder as a local package source). I have not had any problem. Quite the opposite of course. I love the ability to map bindings and log to Seq.

Now I suggest we merge v4 into master, change the version to something like 4.0.0-beta-1, and do a release.

At this point, I no longer want to maintain two branches: i.e. no more changes to 3.x. By only having one branch again, I think my motivation to make progress will return.

We had some discussions in the past about how exactly the mapping API would look. (Some of that discussion might be in #295, but it is long, and I didn't reread it.) By creating a prerelease, we can still change the API before releasing 4.0.0. One thing we had considered is making mapMsgWithModel internal since none of the samples use it. However, I think we should keep it public, especially because I am using in my program at work.

Normally the intention of such a prerelease version is to do a feature freeze while finding and fixing bugs. Although some bug might exist, that is not my concern. This project is the most robust codebase (for its size) I have ever seen. In our case, the purpose of this prerelease is more for you (@cmeeren) and others to try the new (mapping) API, which is much more subjective.

Because I am not concerned about bugs, I don't mind continuing to add features into master while incrementing the prerelease version number. I also don't mind waiting until after 4.0.0 exists to make more code changes.

What do you think @cmeeren?

cmeeren commented 3 years ago

The only downside is that commits that had conflicts are now shown as authored by me. I don't think we care about that.

I don't.

Now I suggest we merge v4 into master, change the version to something like 4.0.0-beta-1, and do a release.

Sounds fine.

By only having one branch again, I think my motivation to make progress will return.

Nothing better! 🥳 I had no idea this was a pain point for you.

One thing we had considered is making mapMsgWithModel internal since none of the samples use it. However, I think we should keep it public, especially because I am using in my program at work.

This is fine. I have forgotten the discussion(s) on whether or not mapMsgWithModel should be public or internal, and I don't care enough to prioritize re-reading it. It would, at least at some point, be nice to have a sample that demonstrates a use-case where it is the best (or only) option.

In our case, the purpose of this prerelease is more for you (@cmeeren) and others to try the new (mapping) API, which is much more subjective.

👍

Because I am not concerned about bugs, I don't mind continuing to add features into master while incrementing the prerelease version number.

This sounds good to me!

Are you able and willing to make a changelog for this release? And briefly describe any relevant migrations?

TysonMN commented 3 years ago

I just pushed this potential release to commit to v4. I think the release notes are already up to date. I added one line about equality type constraints.

If you think we should elaborate more about the breaking changes and migrations, then I suggest we use create a "new release" using GitHub. Here is an example.

cmeeren commented 3 years ago

I left a comment: https://github.com/elmish/Elmish.WPF/commit/0b1603a3c70447f519389dda3c6cb2bf5ba3dccd#r46988514

Other than that, I think this is sufficient.

TysonMN commented 3 years ago

I don't remember why I closed this. Reopening now.

TysonMN commented 3 years ago

I updated the initial comment in this issue. I think it is now rather accurate.

marner2 commented 2 years ago

I am working on adding support for static view model classes. This feature should probably be a part of this list @TysonMN

marner2 commented 2 years ago

@TysonMN I made an issue that should be linked on the top instead of #452 for static view models: #494

marner2 commented 1 year ago

I merged in #560 which should mark a feature-complete version of the Static View Models feature. It needs mention in the docs yet, however.

marner2 commented 11 months ago

I'm proposing making a release in #585 with the current state of the beta.