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

imply record labels from expressions #653

Open
dsyme opened this issue Mar 14, 2018 · 26 comments
Open

imply record labels from expressions #653

dsyme opened this issue Mar 14, 2018 · 26 comments

Comments

@dsyme
Copy link
Collaborator

dsyme commented Mar 14, 2018

I propose we allow both records and anonymous records to imply record labels from a limited range of expressions, e.g. for anonymous records:

{| x.Name; x.Title |}

is the same as

{| Name=x.Name; Title=x.Title |}

The same would apply for records. The label names would be implied by a expr.Name lookup only.

It would be natural to do this at the same time as anonymous records.

The existing way of approaching this problem in F# is to write the label names explicitly.

Pros and Cons

The advantages of making this adjustment to F# are succinctness

The disadvantages of making this adjustment to F# are you have to know the rule, and it makes the selection of type a little more implicit, especially in the case of records.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Related suggestions: #207

@gusty
Copy link

gusty commented Mar 14, 2018

Would it be the same way as it is in C#?

@0x53A
Copy link
Contributor

0x53A commented Mar 14, 2018

When implementing this, please make Refactor-Rename smart enough to add the label.

Example:

type R = { X : int }

let p = Point(1,2) // with properties X / Y
let r : R = { p.X }

if I now rename R.X to R.Z, then it should change the last line to let r : R = { Z = p.X }. Vice versa if you rename p.X.

@jwosty
Copy link
Contributor

jwosty commented Mar 15, 2018

Personally, I'm not a fan of this. A rule like this doesn't seem very novice-friendly and is not immediately intuitive to me. Just my two cents though.

@baronfel
Copy link
Contributor

As a counterpoint, javascript/typescript these days makes it very common to (de)construct values in this way. It's common to build objects like

let thing = { foo, bar, baz }

and get an object with "foo", "bar", "baz" keys whose values are the values bound to those names at that point.

Consequently, deconstructing is very nice in this simple case, while still allowing for the current more customizable syntax.

In short, I <3 this suggestion.

@TIHan
Copy link

TIHan commented Mar 16, 2018

I'm not sure how I feel about this feature. I personally prefer to be explicit with the fields. Also, @0x53A raises a very good point; Refactor-Rename would need to be aware of this.

I understand the intention is to be succinct, but we can agree that isn't always a good thing. My fear is that devs will be more inclined to name their properties or fields a certain way, even if it's not correct, so that they will be able to construct records in this succinct manner.

@Rickasaurus
Copy link

This seems kinda confusing to me without much gain. To a beginner it might look an awful lot like you're making an array given the syntactic similarities, when it's very different.

@Alxandr
Copy link

Alxandr commented Apr 18, 2018

I'd say this is common enough in other languages (both C# and JS has similar features).

@jwosty
Copy link
Contributor

jwosty commented Jun 1, 2018

What should happen if you write this?

{ foo=x.Name; x.Title }

@jpierson
Copy link

jpierson commented Apr 10, 2019

@jwosty it depends on what types map to { foo=x.Name; x.Title }. Without the | characters that would appear to be a regular record and thus the type would have to be predefined so I would expect there would be a type such as type FooAndTitle = { foo: string; Title: string; } assuming that foo and Title are indeed string typed properties or fields otherwise a compiler error would be expected based on my experience and understanding.

Now if we are talking about {| foo=x.Name; x.Title |} instead then the expected result I would definitely expect to appear as a new anonymous record value having a property foo and Title with the values assigned from x.Name and x.Tilte respectively.

@charlesroddie
Copy link

charlesroddie commented Apr 10, 2019

This breaks the expectation that a binding should access a value rather than a name.
Rename the binding, and the behaviour changes.

There is an unsafe periphery of F#/.Net in which these expectations do not hold (reflection: nameof, typeof...) but this should not be moved into the core of the language.

@7sharp9
Copy link
Member

7sharp9 commented Apr 10, 2019

I think this is somewhat similar to record punning in ocaml.

@jpierson
Copy link

@7sharp9, looking over some of the documentation on records in OCaml it does indeed look like there is direct overlap with the concept discussed here and field and label punning. It also appears that ReasonML enjoys a similar sytactic shortcut as well as perhaps PureScript, JavaScript as of ES6, and C# with anonymous types of course. It looks like some of these implementations differ based on whether they work beyond variables/fields or work on extended property get accessors like in C#.

References:

@kspeakman
Copy link

kspeakman commented Sep 13, 2019

I like this idea, but case sensitivity of names should be addressed. I think it would be weird for pre-defined records if it was case sensitive. For example, this would happen:

type MyRecord =
    {
        Foo : string
        Bar : int
    }

let createMyRecord foo bar = { foo; bar }
// error FS0039: The record label 'foo' is not defined.

For anonymous records, shouldn't it also construct fields with Pascal case to be consistent with naming guidelines?

@mrakgr
Copy link

mrakgr commented May 6, 2020

I've wanted to suggest this feature for years now. I've been using it in my own language which is syntactically similar to F#, and I always miss being able to pun when I use records in F#. I hope to see this in the language at some point.

@abelbraaksma
Copy link
Member

abelbraaksma commented May 6, 2020

I'd expect this to work with struct DUs, where it is currently required to add redundant labels that you'll never use in practice. Having this for records is nice, but could arguably add to confusion, but for struct DUs at least it would remove such confusion and redundancy.

@dsyme
Copy link
Collaborator Author

dsyme commented Oct 28, 2022

I've done a little typescript for a project and found this kind of "punning" (if that's the right term) quite natural.

I am going to remark this as approved-in-principle. We should do this.

@baronfel
Copy link
Contributor

There's an open question around casing-sensitivity that @mrakgr raised here. I think that for the initial version accepting only exact matches would be sufficient. This aligns with expectations for users familiar with JS/TS, and it reduces the 'rules' that the developer has to internalize.

@dsyme
Copy link
Collaborator Author

dsyme commented Oct 28, 2022

I think that for the initial version accepting only exact matches would be sufficient. This aligns with expectations for users familiar with JS/TS, and it reduces the 'rules' that the developer has to internalize.

Yes, exact matches only

@kspeakman
Copy link

kspeakman commented Oct 28, 2022

If I'm understanding the exact match comments right, these still wouldn't work after this feature is implemented:

type MyRecord = { A: string; B: int }
let f a b = { a; b } // used for validation
let f { a } = ... // destructure what I need
let f { a; b } = { b; ...} // convert record type, often to subset of original
match msg with // specific DUs have record values in every case
| MsgCase { a; b } -> ...

I'd have to use the existing verbose syntax for these. Or use upper case value names against the naming guidelines. Addressing the record copying case { x.A } would help some, but the others are my primary use cases where the current syntax is noisy or overcrowding the other code.

@mrakgr
Copy link

mrakgr commented Oct 29, 2022

Since this issue is having some action, let me plug my own language Spiral. Compared to the last time I posted here, it is finished, and you can check it out on VS Code marketplace if you want some inspiration.

@Tarmil
Copy link

Tarmil commented Oct 29, 2022

The casing thing is really unfortunate and artificially reduces the usefulness of this feature, but at the same time automatically uppercasing/lowercasing things in the language feels very wrong.

In fact, there's similar weirdness in C# when it automatically gives a name to tuple items: sometimes the name will be lowercase, sometimes uppercase, depending on the expression you put there. Add this to my reasons for disliking .NET's casing conventions 🤷

@kspeakman
Copy link

kspeakman commented Oct 29, 2022

Maybe it's just my usage pattern. I use x.A syntax in a narrow set of circumstances. Typically mapping between types. For logic, local names are less troublesome when I reorganize the code. For example, split off some functionality to be reusable. I don't want a dependency on the external type. Went thru and removed record dot syntax enough times that I'm careful where I introduce it.

@dsyme
Copy link
Collaborator Author

dsyme commented Oct 29, 2022

The casing thing is really unfortunate and artificially reduces the usefulness of this feature, but at the same time automatically uppercasing/lowercasing things in the language feels very wrong.

Yes, I agree. That said, I'm assuming that in Fable and Python F# the use of lowercase record field names is much more common - and you see it in some .NET F# as well. So I don't think the feature is without utility.

@kspeakman
Copy link

kspeakman commented Oct 29, 2022

Ah, so this would represent an unofficial shift in the naming guidelines. At least, I would use camel case property names to have access to this feature for the use cases I mentioned.

@cartermp
Copy link
Member

so this would represent an unofficial shift in the naming guidelines

We can always mine the quarry of guidelines, carve the stone of records, and etch the new guidelines into it 🙂

@kspeakman
Copy link

kspeakman commented Oct 30, 2022

@cartermp I'm into it. :) We inherited most of the guidelines from C# anyway.

mrakgr added a commit to mrakgr/The-Spiral-Language that referenced this issue May 22, 2023
… latest AyaTri chapter and then I will get started.

I've been looking forward today's session.

9am. Let's start.

///

Compare F# to Typescript.
In F#, the compiler places most of the types for you, but Typescript requires you to onerously annotate everything by hand, so F sharps type system is a lot less intrusive.
Typescript's is more powerful, but because placing all the types is difficult what you'll see in the wild is people not bothering to put the types in.

///

///

And programs with complex typing are harder to understand, not easier.
In the future, there will be all kinds of languages with advanced type systems, but understanding programs written in them won't necessarily be easier those written in a language like F#.

///

I'll cut these out.

9:45am. 3 minutes of ranting should be enough.

11:10am. https://www.brightball.com/articles/the-time-i-accidentally-ended-up-combating-fraud-for-a-year

I'll read this later.

```
"profiles": {
    "Watch": {
        "commandName": "Executable",
        "executablePath": "dotnet",
        "workingDirectory": "$(ProjectDir)",
        "hotReloadEnabled": true,
        "hotReloadProfile" : "aspnetcore",
        "commandLineArgs": "watch run",
        "launchBrowser": true,
        "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
```

```
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "hotReloadEnabled": true,
      "hotReloadProfile": "aspnetcore",
      "commandLineArgs": "watch run",
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
      "applicationUrl": "http://localhost:5037",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "hotReloadEnabled": true,
      "hotReloadProfile": "aspnetcore",
      "commandLineArgs": "watch run",
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
      "applicationUrl": "https://localhost:7240;http://localhost:5037",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "hotReloadEnabled": true,
      "hotReloadProfile": "aspnetcore",
      "commandLineArgs": "watch run",
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
```

Let me back this up. I am not sure how hot reload could be made work with IIS, but nevermind that.

12:45pm. Let me stop here so I can take a break.

Sigh, it just takes so long to get going with anything new.

1:15pm. Got a Youtube comment.

///

I think transcribing from any typescript code to F# should be possible, maybe at the cost of paying for API ergonomics. Perhaps the problem is that ts2fable isn't mature? Then that's a solvable problem that isn't intrinsic to F#. F# honestly needs love/funding.
(note: I haven't used F# nor Fable, but I am looking to use it)

///

///

The problem with transcribing TS's types to F# is that TS has a lot more powerful type system, and a lot of what would be expressible in it, simply isn't in F#. All of type level mappings, type literals, type conditionals, nested variadic args. Moreover, F#'s (.NET's) and JS's object systems are different.

You'll always be faced with incompatibilities in such a situation, and TS which was made to be a superset of JS will always have an edge when interacting with it.

Currently, I am trying out Bolero & Blazor, so depending on how that turns out, it might be better to use those for web dev with F#. I need to spend some time using them before I decide whether I want to recommend it or not.

I'd recommend F# in general as it is a lot easier to use (and a better language) than TS, but after experiencing Fable, but as far as webdev is concerned, I am not sure whether it would be worth the users time to use it over TS for their own projects. Maybe if you are an experienced webdev who is familiar with the libraries on the TS side, you could get use out of Fable, but for somebody coming in, definitely not. Bolero might be worth a try.

///

1:35pm. Let me resume.

Lagoon by Luke Bergs & LiQWYD | https://soundcloud.com/liqwyd/
License: www.liqwydmusic.com/how-to-use
Creative Commons CC BY 3.0

Essence by Luke Bergs | https://soundcloud.com/bergscloud/
Creative Commons CC BY-SA 3.0

Blessed by Luke Bergs | https://soundcloud.com/bergscloud/
Creative Commons CC BY-SA 3.0

Enjoy Yourself by Steven O'Brien | https://www.steven-obrien.net/
Creative Commons CC BY-ND 4.0

Music promoted by https://www.chosic.com/free-music/all/
https://creativecommons.org/licenses/by-nd/4.0/

4:20pm. It is rendering.

4:40pm. https://youtu.be/HYCXCviAXYo
VN Compiler. How to use Blazor components with Bolero. Introducing Blazor.Diagrams. (Pt. 1)(Restart)

Here it is. Let me post it on the F# sub.

4:50pm. Do I feel like going on anymore for the day?

5:05pm. https://blazor-diagrams.zhaytam.com/customization

https://www.syncfusion.com/sales/communitylicense

Oh, the community license for this is free.

6:45pm. https://youtu.be/ws5-CUzY7_w
Blazor Context Menu Component | Right Click Menu

https://youtu.be/ws5-CUzY7_w?t=39

Lol.

https://medium.com/it-dead-inside/lets-learn-blazor-mudblazor-to-the-rescue-711129babc18

///

These come in two general camps:

The overpriced corporate suite I’ll not name names, but you don’t have to look far. These are companies selling razor / Blazor components in a suite of libraries you can just download and go. Don’t get me wrong. A lot of them are very professionally done (great documentation, easy to use, pretty customisable and decent-looking), it’s just the pricetag which is too much to bear. I’m an independent developer. I don’t have the kind of corporate coin they’re after for a developer seat.
The underwhelming open source project This is in no way a cut on anyone who’ve put in the time and effort to build free-to-use razor / Blazor components, but honestly most developers are TERRIBLE at things like styling, documentation, and in the land of open source, bug fixing. You get what you pay for, and for a whole lot of these open source Blazor component libraries, you are definitely paying for what you’re getting. Maybe not in terms of money, but definitely in terms of your time and frustration. A lot of the time these components are so bland and clunky, you end up spending all your time customising them to fit your needs.
So you either shell out huge amounts of money, or you fight with open source / free components which are not up to snuff.

Again, a love / hate relationship. Like so many devs in the Blazor space, I wanted a fully-featured library of components which looked and reacted amazing in my app, and was either free or at most reasonably-priced.

So I was blown away when I stumbled upon what seems to me to be the most robust, powerful and well-documented set of Blazor components out there, all open source and free to use.

Introducing MudBlazor.

8:40pm.

///

Thank you so much for doing this. You put into words the exact experience i've wanted, which is basically "hey how do I stick with what I know to get a frontend up".

I'm curious what improvements you'd like on record syntax.

///

///

I signed up for SyncGusion community license a few hours ago, and now I am looking up how to do context menus in Blazor.

https://github.com/MudBlazor/MudBlazor/

This is pretty cool.

> In the last couple weeks, MudBlazor has undergone a huge overhaul, including a fully-retrofited website and documentation, complete with example code and markdown for each component. There are dotnet new templates for you to get started and heaps of examples to cover your situations.

Where is the component list. I can't find anything in the docs.

https://mudblazor.com/components/list#api

Here it is, why did I have to type this into Google?

https://mudblazor.com/components/menu#custom-activator

Here is the MudMenu.

I can put this on any interface element.

https://mudblazor.com/components/menu#placement

This is really nice. This could be super useful.

With MudBlazor, I wouldn't need to download a package just for the context menu.

It never occured to me before, but I can just place thing thing on the outermost element and it will trigger whenever anything underneath gets clicked.

8:40pm. https://old.reddit.com/r/fsharp/comments/13osxyl/vn_compiler_how_to_use_blazor_components_with/jl6faxn/?context=3

///

> I'm curious what improvements you'd like on record syntax.

Basically, what I've made [in Spiral](https://github.com/mrakgr/The-Spiral-Language#records). There is even a [F# issue](fsharp/fslang-suggestions#653) to improve the syntax.

Let me summarize it a bit.

Instead of writing...

    let f {a=a; b=b} = a+b
    let g (a,b) = {a=a; b=b}

You can write the following (in Spiral).

    let f {a b} = a+b
    let g (a,b) = {a b}

This is one of the places where even JS has better syntax than F#, and the language could be easily extended to support something like that. Spiral also has nested updates.

    type E = { q : int }
    type W = { b : E }
    type Q = { a : W }
    let r : Q = {a={b={q=0}}}
    let x : E = {r.a.b with q = 10}

In F#, that `{r.a.b with q = 10}` would return the innermost record, but in Spiral it nests the updates and return a record of type `Q` as it was originally. And this is much more useful behavior.

Also Spiral supports the update form where instead of assigning to the record, you pass a lambda to grab its argument.

    {r.a.b with q #=fun x => x+2 }

This is also useful as it makes it easy to grab whatever the field you are accessing is.

    {r.a.b with q #=((+) 2) }

Since it is a function you can partially apply it.

    {r.a with b #=fun b => {b with q #= ((+) 2)} }

By going up a level you can also easily update the outer field and so on.

Anyway, Haskell has crappy records, and F# leaves a lot on the table in regards to ergonomics with them. I'd be using them a lot more instead of tuples if the syntax for them was better.

///

Let me close here."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests