fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
346 stars 21 forks source link

`{ _ with ... }` for shorthand lambda to record update #1338

Open smoothdeveloper opened 1 year ago

smoothdeveloper commented 1 year ago

I suggest to define a shorthand lambda for record update expression

type Record = { a: int; b: string }
let setB value = { _ with b = value }
let data = { a=1; b="c"} 

if (data |> setB "d").b = "d" then
  printfn "F# is cool"

data
|> { _ with a = 2 }
|> // ...

let changeAValues f items = items |> Seq.map { _ with a = f a }

pros:

cons:

Tarmil commented 1 year ago

Ha, there was always the expectation that people would want to expand the "underscore lambda" syntax to more than member access. We may not want to go full Scala _ + _, but this small extension seems reasonable to me.

We could even have:

{ _ with a.b = value }

aka "What if we took F# 8's two biggest new features and smashed them together?" 😄

kerams commented 1 year ago
data
|> { _ with a = 2 }
|> // ...

{ data with a = 2 }
|> // ...

let setB value = { _ with b = value }

let setB value x = { x with b = value }

Do you have any other examples? These aren't particularly convincing.

smoothdeveloper commented 1 year ago

It just came spontaneously, I had to share. Let see.

One drawback is you can't refer again to the _ in the expressions, I think it would conflict with the shorthand lambda that just shipped.

I think it brings a sense of acceptance that there is let _ = and other places where we discard, then there is the shorthand lambdas for "dot chains" and records, and maybe more.

One thought is we could make an analyzer that scans codebases in the wild, for getting a statistical sense of "is this syntax sugar suggestion going to apply to many codebases and look good in actual code", here we'd search for lambdas that transform a record.

Keep the reasons coming for "why not", and discussing about the "ethos of shorthand lambda and _ for readability", I'll update the top post to summarize once there has been enough bashing / advocating.

smoothdeveloper commented 11 months ago

cannot refer to the record itself in the shorthand lambda

Pondering on this con, I think it plays nicely with nested record update, and it is kind of a smell to update one field based on the other one in cases that aren't trivial; the shorthand could be a cue that this is impossible just at a glance, so actually a good thing.

@kerams, your contention also applies to shorthand lambda for property access:

let getB x = x.B
let getB = _.B

I've added changeAValues in the code sample since your comment.

T-Gro commented 11 months ago

Am I right assuming the real benefit should arrive pretty much only at LOC with both a fun and a with as of now? i.e. somewhat close to what top of these search results show https://github.com/search?q=%22fun%22+%22%7B%22+%22with%22+language%3AF%23+&type=code ?

i.e. changing this:

    DotNetCli.Restore (fun p -> { p with Project = docsTutorialsProject })
    DotNetCli.Build (fun p -> { p with Project = docsTutorialsProject; Configuration = configuration })

into this:

    DotNetCli.Restore { _ with Project = docsTutorialsProject }
    DotNetCli.Build { _ with Project = docsTutorialsProject; Configuration = configuration }
smoothdeveloper commented 11 months ago

@T-Gro now you seem like making a compelling case even 🙂.

Without going too far, I think the symmetry with "get record property" with "udpate record property" has nice, erm, properties, but I don't want to nudge the language on "point free" nor "abusing of discard", unless there is real appeal and we feel it is conservative enough change.

T-Gro commented 11 months ago

I like how this proposal reads, even though the use case for "pipeline of updates" is likely a lot smaller than one for "pipeline of gets".

Personally, I would especially like if there was a way of somehow achieving symmetry here for tuples (and "tuples" inside DU cases) as well, but I do know that is too much to ask for at once :-))

drhumlen commented 11 months ago

Hmm it's always worth considering:

If a feature is seldomly used, I think the longer version is alright.

I've tried looking through our fairly large codebase and I couldn't find any immediate uses for it myself. Maybe I need to see more motivating examples 😄

smoothdeveloper commented 11 months ago

@drhumlen, what @T-Gro shown is one of the many functions in FAKE modules, that take a record and return an updated version; this is an idiom in FAKE and probably other, where you need a default setting, and give the user the ability to alter it by passing a 'record -> 'record function.

If a feature is seldomly used, I think the longer version is alright.

I think it is not necessarily the case, given the feature may be approved by principle only, it doesn't mean the vendors are going to work on it, it could overall be added by external contributor (just like lambda short hand, and many other features).

The main cutoff for a small feature like this, I believe:

There are features that were approved despite they may not represent something extensively used, but because they made sense.

Not saying any of this necessarily applies here.

smoothdeveloper commented 11 months ago

Note that I also come across

(fun options -> options.SetterProperty <- value)

It remains that those patterns are indeed widespread and idiomatic (due to being open ended and not requiring lots of implementation to expose in an API):

"Sadly" maybe more than:

despite codegeneration (such as myriad or source generators) enable this to be potentially low ceremony too.

So having shorthand and distinctive syntax for those constructs could be meaningful feature.