Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Currently there's quite a bit of friction in implementing alternative/simpler versions of existing bindings, because it requires new functions with unique names. This can quickly lead to a mess of functions and confuse users (and get unnecessarily verbose).
For example, based in the changes described in #86, there's
An alternative is an overload-based syntax, in the form of static methods on a
let bindings () = [ "OneWay" |> Binding.oneWay(getValue) "OneWayOpt" |> Binding.oneWay(getValueOpt) "OneWayLazy" |> Binding.oneWay(getValue, equals, map) "OneWayLazyOpt" |> Binding.oneWay(getValueOpt, equals, map) "TwoWay" |> Binding.twoWay(getValue, setValue) "TwoWayOpt" |> Binding.twoWay(getValueOpt, setValueOpt) "TwoWayValidate" |> Binding.twoWay(getValue, setValue, validate) "Cmd" |> Binding.cmd(getMsg) "CmdIf" |> Binding.cmd(getMsg, getCanExecute) "CmdIfValid" |> Binding.cmd(getMsgResult) "CmdIfSome" |> Binding.cmd(getMsgOpt) "ParamCmd" |> Binding.paramCmd(getMsg[, uiTrigger = true|false]) "ParamCmdIf" |> Binding.paramCmd(getMsg, getCanExecute[, uiTrigger = true|false]) "ParamCmdIfValid" |> Binding.paramCmd(getMsgResult[, uiTrigger = true|false]) "ParamCmdIfSome" |> Binding.paramCmd(getMsgOpt[, uiTrigger = true|false]) "SubModel" |> Binding.subModel(getSub, toBindingModel, toMsg, getBindings) "SubModelOpt" |> Binding.subModel(getSubOpt, toBindingModel, toMsg, getBindings[, sticky=true|false]) "SubModelOpt" |> Binding.subModel(getSubOpt, toBindingModel, toMsg, getBindings[, sticky=true|false]) "SubBindings" |> Binding.subModel(getSub, getBindings) ]
The drawbacks I see are:
The benefits I see are:
Personally I like this, but I'd like to hear what others think before delving into this. Even just a positive reaction emoji is helpful to let me know that you agree. (If you disagree, please explain in a comment! :) )
I think I found another (minor) drawback.
I created a temporary PR in my fork of Elmish.WPF to clearly demonstrate the issue.
Among those three solutions, I prefer adding the type annotation. The main reason is that it is the most local solution (i.e. the scope of the additional code needed is as small as possible and no other code is adversely affected).
If #F ever gets shorthand function accessors to record properties, then it might become possible to directly refer to the sufficiently qualified property accessor function, which is essentially what I am doing elsewhere in my example (such as for the
Thanks for the heads-up! F#'s overload resolution certainly isn't perfect.
Personally I prefer opening modules in those situations. The module in question, at least the way I structure my code (and as shown in your PR), mostly contains stuff relevant for the bindings anyway (the
If you don't want that, you could simply place the types (
If I remember correctly, here is my experience when opening submodules.
I think that is worse than just adding type annotation in the first place.
Are you able to avoid that situation when you open the submodules?
I avoid that situation by only opening a single module with one
module A type Model type Msg let update module B type Model type Msg let update module ABindings open A let bindings module BBindings open B let bindings
I have never needed to open modules for multiple models when creating bindings, because the bindings I have created have always been specific to one model.
That said; sometimes type annotations are simply required with F#. The type inference has some quirks, particularly when combined with overloads. I haven't come across the problems you mention myself due to how I have structured my code. There may of course be valid use-cases that require another structure that brings about these type problems, but I don't know at the moment what use-cases those would be.
Another alternative is all unique function names (for example, in a one-to-one correspondence with the
Here is the comparison I see with the current overload-based syntax.
Roughly the same
Another idea that could be combined with the previous one is passing optional arguments with the same fluent-like syntax that Emlish uses with its
I am willing to "argue" for this in future comments, but the main reason that I am writing this comment is to get this idea out of my head.
Interestingly, that is one of the few posts of Mark's that I don't agree with. I don't find the syntax compelling, nor particularly discoverable without qualifying the union type name. Also it's more verbose than overloaded static methods.
(Since it's an early F# blog post of his, I am curious if he still holds the same view. I can't recall seeing that syntax much in later blog posts.)
Certainly an option, though many bindings would require several parameters immediately (on the first call) anyway, and there are several ways you could specify them (
In general, I find that the current overloaded syntax, while not perfect, is the most pleasant and concise I variant I have considered/tried. I do appreciate that you're writing this down! It's always nice to have more pros and cons. But just to go on record and be clear (to anyone who might read this), an alternative syntax would have to be pretty elegant, concise, and generally dev-friendly in order for me to consider another breaking change in the binding API.
This doesn't seem as bad to me now.
The compiler branches in order to determine the function/method to call. When overloading, the branching happens implicitly based on the types of the arguments. When using DUs as described by Mark, the bracing happens explicitly in a match of the DU instance.
Overloaded methods don't have (meaningful) differentiating identifiers (since the branching is implicit), and we could closely preserve this by having the DU case names be as short as possible. For example, they could be
With such short case names, I don't think the two disadvantage above are that bad. If there are always less than ten cases, then this only requires adding four additional charters. It would look something like