Skip to content
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

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

Open
smoothdeveloper opened this issue Nov 16, 2023 · 10 comments
Open

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

smoothdeveloper opened this issue Nov 16, 2023 · 10 comments

Comments

@smoothdeveloper
Copy link
Contributor

smoothdeveloper commented Nov 16, 2023

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:

  • symmetry with shorthand lambda to update record rather than read

cons:

  • cannot refer to the record itself in the shorthand lambda
  • shorthand lambda feature encourage point free, it was ok compromise for atomic expressions, it may not be for other constructs
@Tarmil
Copy link

Tarmil commented Nov 16, 2023

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
Copy link

kerams commented Nov 16, 2023

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link

T-Gro commented Dec 5, 2023

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
Copy link
Contributor Author

@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
Copy link

T-Gro commented Dec 5, 2023

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
Copy link

drhumlen commented Dec 5, 2023

Hmm it's always worth considering:

  • How frequently will this feature actually be used in realistic production code
  • How much "pain" will it save each time

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
Copy link
Contributor Author

@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:

  • does it fit naturally in the language?
  • doesn't preclude language evolution by taking a syntactical opportunity that would serve some other feature better?
  • will the implementation remain "non-leaky" enough? (not like null reference types, which is very wanted, but will leak everywhere in the codebase, much more sensitive in terms of implementation choices)

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
Copy link
Contributor Author

Note that I also come across

(fun options -> options.SetterProperty <- value)
  • the shorthand syntax don't bind for this _.SetterPropery <- value, maybe it could in the future, it is used in lots of C# stuff from BCL AFAICS
  • I'm slightly tempted to say "object with set properties are special mutable records": { _ with SetterProperty <- value; AnotherOne <- anotherValue } (or similar concept, but { _ ... } being key here, but this may be very very far fetched.

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):

  • F# 'record -> 'record
  • C#: Action<SettingsWithSetters>

"Sadly" maybe more than:

  • F# |> RecordType.setFoo value
  • C#: .WithSetter(value) fluent chain

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants