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

Allow record to be a generic constraint #807

Open
robkuz opened this issue Nov 20, 2019 · 10 comments
Open

Allow record to be a generic constraint #807

robkuz opened this issue Nov 20, 2019 · 10 comments

Comments

@robkuz
Copy link

@robkuz robkuz commented Nov 20, 2019

Allow record to be a generic constraint

such that one can define

    let inline func< ^X when ^X : record> (x: ^X) =

this would allow the conversion and extension of any anon record in the following way

let inline appendFoo < ^X when ^X : record> (x: ^X) myFoo =
    {| x with foo = myFoo |}

The existing way of approaching this problem in F# is : Not possible atm

Pros and Cons

The advantages of making this adjustment to F# are allows for generic data pipelines

The disadvantages of making this adjustment to F# are : this needs implementation

Extra information

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

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this
@cartermp

This comment has been minimized.

Copy link
Member

@cartermp cartermp commented Nov 20, 2019

This seems quite compelling for the anonymous records case. But how would this work for normal records? I would think the name would have to change if it were scoped only to anonymous records.

Though I think this gets to the core issue of nominal vs. structural typing for anonymous records. Since they're nominal, two different records are fundamentally different to each other, even if one is just an additional label.

@robkuz

This comment has been minimized.

Copy link
Author

@robkuz robkuz commented Nov 20, 2019

@cartermp tbh and knowing the rather <cough> conservative approach of lang change in F# I wouldnt touch anything concerning nominal vs structural.
Obviously I would be more than happy if nominal records in F# where extensible.
But this suggestion isnt about that at all.
Its just that one can know that this is recordy stuff and that you can do record stuff with it now. Which of course works only with anon records

@smoothdeveloper

This comment has been minimized.

Copy link
Contributor

@smoothdeveloper smoothdeveloper commented Nov 20, 2019

@robkuz am I right that it would change the semantic of the first function?

type Rec<'a> = { F1: 'a; F2: 'a; data: string }

let inline map f r = { r with F1 = f r.F1; F2 = f r.F2 }
let inline map_ f r  = { F1 = f r.F1; F2 = f r.F2; data = r.data }
@robkuz

This comment has been minimized.

Copy link
Author

@robkuz robkuz commented Nov 20, 2019

@smoothdeveloper Maybe it is to late over here ... but why would that change the semantics of the first function? The second func map_ shouldnt work by the way as you would extend a nominal record. Which isnt possible under this proposal.

@drvink

This comment has been minimized.

Copy link

@drvink drvink commented Nov 20, 2019

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

This is not a small change by any means. The assumption that because anonymous records have been implemented that it would not be a lot of work to now add things like record extension or concatenation or restriction is incorrect: even just one of these features requires significant, non-trivial type system support, with none of the possible implementation strategies being any easier than the others--to say nothing of the fact that these features very much do not come "for free"; choice of representation for extensible records has direct consequences on memory usage and runtime performance for all record operations: not only the fancy stuff like extension and concatenation and restriction, but basic lookup (of labels) and selection (of the actual data) as well.

...and this is all assuming you want these features in a brand new green-fields language development effort. The situation for F# is even more daunting: an existing type system will need modification, and of course there must be consideration of the possible implications they may spell for interop scenarios.

Last but not least,

  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

should not be checked: these features were explicitly rejected in the anonymous records RFC and related discussion threads.

@realvictorprm

This comment has been minimized.

Copy link
Member

@realvictorprm realvictorprm commented Nov 20, 2019

No the changes should not be that difficult because the usage is restricted to be on inline functions. That means that all checks would be done at compile time which have no implications on memory or runtime performance. In the end it's just an extension on SRTP's to my understanding.

@realvictorprm

This comment has been minimized.

Copy link
Member

@realvictorprm realvictorprm commented Nov 20, 2019

Besides, I like that suggestion a lot.

@robkuz robkuz changed the title Allow record to be a generic constrained Allow record to be a generic constraint Nov 21, 2019
@robkuz

This comment has been minimized.

Copy link
Author

@robkuz robkuz commented Nov 21, 2019

@drvink sorry, I have the feeling you seriously misread my proposal. As @realvictorprm pointed out this is a something that works for SRTPs and has nothing to do with nominal vs structural.

The reason for this proposal is very simple. The following code

 let inline appendFoo x myFoo = {| x with foo = myFoo |}

fails at the moment (today) with the message that x has to be a record. Which of course is true as x could be any kind of type. a class type, an function type an interface type etc. So the solution is to allow to specify a constraint that says "but yes this is a record indeed" very much in the same way that you can define null or struct as constraints already.

Now there is not very much that this constraint would enable - as far as I can see only the extension of anon records and the conversion of nominal records into anon records. And it would be worthwhile discussing if this is needed at all as the compiler could figure out that the code operating on param x is using some record operations therefore x needs to be an record. But this is true for the struct and null constraints too as far as I can see.

So it would make sense to add this explict constrait to keep the lang balanced.

Having said all this.
I stay with: Yes this is a small lang feature. I dont see how this could be a big one.
And also yes: this has NOT been rejected as this proposal does not touch the extension of nominal records. In fact it does not touch the extension of anything at all. Just requirement to constrain a parameter to be a record.

@abelbraaksma

This comment has been minimized.

Copy link

@abelbraaksma abelbraaksma commented Nov 22, 2019

The reason for this proposal is very simple. The following code ... fails at the moment (today)

@robkuz, Isn't that essentially the same as the proposal in #759, which, considering the discussion, seems to be likely-to-be-declined? I mean, if that proposal were accepted, inference would already fix the expected types for you.

very much in the same way that you can define null or struct as constraints already.

You can define struct as a constraint, but that doesn't allow you to set its members, unless you also resolve to SRTP. Reading your original post, I think you suggest that when a constraint is record, that it will work synonymously with any record that has the fields that you reference in the body of the function.

However, given that your proposed signature uses ^X and not 'X, you seem to suggest this should work (only?) with SRTP? The title just says "generic constraint", and not one that is statically resolvable.

Assuming it would be statically resolvable, I was wondering how far we can get presently, using SRTP. When it comes to getting a value, it's rather trivial. But setting a value on either a struct or a record is hard (and for anonymous structs maybe impossible, I couldn't find a way), since structs and records use constructors for setting the values.

Some playing around:

module TestMe =
    type Foo = 
        struct 
            val A: int
            val B: int 
        end
        new(a, b) = {A = a; B = b}

    let inline getA< ^t when ^t: (member get_A: unit -> int)> (v: ^t) = 
        (^t: (member get_A: unit -> int) v)

    let inline setB v (x: ^t) = 
        let x = getA x
        (^t : (new : int -> int -> ^t) (x, v))

    let foo() = 
        let foo: Foo = Foo(2, 4)
        let bar = {| A = 10; B = 12 |}
        let zed = {| A = 42; C = 99|}

        let x = getA foo    // works with struct with 'A' member
        let y = getA bar    // works with anon record with 'A' member
        let z = getA zed    // works with other anon record with 'A' member

        let a = setB 10 foo
        let b = setB 10 bar  // doesn't work, FS0001	The type '{| A: int; B: int |}' does not support the operator '.ctor'
        ()
@robkuz

This comment has been minimized.

Copy link
Author

@robkuz robkuz commented Nov 22, 2019

@abelbraaksma reading can be done today with SRTPs as you show in your code.
So yes this is about writing. Not sure about #759

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.