Skip to content

Spread operator for F# #1253

@dsyme

Description

@dsyme

I propose we consider the merits of a spread operator for F#

...expr

It would be used for

  • building records and anonymous records from spreadable things (subsuming with and this suggestion)
  • building object expressions from spreadable things (subsuming this suggestion)
  • implementing interfaces from spreadable things (subsuming this suggestion)
  • perhaps other things
Records
type A = {
    X: int
    Y: int
}
type B = {
    Z: int
}
type C = {
    X: int
    Y: int
    Z: int
    Extra: string
}
let a = { X = 1; Y = 2 }
let b = { Z = 3 }
let c = { ...a; ...b Extra = "four" }
Anonymous records
let a = {| X = 1; Y = 2 |}
let b = {| Z = 3 |}
let c = {| ...a; ...b; Extra = "four" |}
Object expressions
type IA =
    abstract A: int -> int
type IAB =
    abstract A: int -> int
    abstract B: int -> int

let a =
    { new IA with
        member _.A x = x + 1 }
let b =
    { new IB with
        ...a
        member _.B x = x - 1 }
Interface implementations
type IA =
    abstract A: int -> int
type IAB =
    abstract A: int -> int
    abstract B: int -> int

type A() =
    interface IA with
        member _.A x = x + 1
type B(a: IA) =
    interface IB with
        ...a
        member _.B x = x - 1 

The existing way of approaching this problem in F# is explicit re-delegation.

Technical details?

Fundamental technical questions are

  • What is considered spreadable?
    • Certainly objects, records, anon records
    • But what of tuples, unions and anything with methods or properties
  • What spreadable items are provided by a spreadable thing?
    • Assume spreadable things always have nominal known type
    • Certainly record fields
    • Certainly object methods, properties.
    • Possibly object events (very rarely used though)
    • What of extension methods and properties?
    • What about F# modules?
    • Is any type information propagated into the spreadable things from the thing being constructed?
  • What requirements emerge from the target of spreadable items?
    • Assume target of spreadable items always have nominal known type
  • How exactly are the spreadable items used to satisfy the requirements?
  • How about generics?
    • Are values of a SRTP-constrained type considered spreadable?
    • Are values of a class-constrained generic type considered spreadable?

For example

  • Can you spread a record into an object expression? (Yes)
  • Can you spread an object into a record? (Yes)
  • If an object has a generic method M<T> : T -> T, can that be used to implement a requirement M: int -> int through type specialization? (TBD)

Patterns?

Should a spread operator be allowed in patterns? e.g.

match r with 
| {| A = a; ...rest |} -> ...

Initially there would I think be no need for this.

Types?

How about type syntax? e.g.

type A = {
    X: int
    Y: int
}
type B = {
    Z: int
}
type C = {
    ...A;
    ...B;
    Extra: string
}
let a = { X = 1; Y = 2 }
let b = { Z = 3 }
let c = { ...a; ...b Extra = "four" }

Spreading record types into other record types seems tempting, but note this would not give rise to subtyping. But then can you spread object types into record types? Seems like a step too far?

Collections?

Spread operators in other languages usually work with

  • collections
  • tuples
  • spreading a spreadable-thing as a group of named parameters x.SomeMethod(...record_of_args)
  • ParamArray/params (to explicitly pass a collection as a ParamArray arg, which is currently done via inference)

Bat all of this is necessarily desirable for a spread operator in F#, though each should be considered and if they aren't supported good diagnostics should be given directing people to the right way to do things.

Pros and Cons

The advantages of making this adjustment to F# are conceptual efficiency, introduction of a littler structural matching

The disadvantages of making this adjustment to F# are

  • additional feature
  • some subtle corner cases
  • won't work 100% as expected from other languages.
  • it may lead to a cascading set or requirements for using spread operators and related structural manipulations in generic code.

Extra information

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

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

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions