Autogenerated lazy record constructors (like type alias in Elm) #533

Open
gentauro opened this Issue Feb 5, 2017 · 8 comments

Projects

None yet

4 participants

@gentauro
gentauro commented Feb 5, 2017

Autogenerated lazy record constructors

I propose we introduced the concept (and keyword) alias like it's used in Elm Type Aliases. This approach would allow records to be lazy constructed. In the following example I will show what happens if we don't assign all the properties of a record when it's constructed:

type Order =
  { buyer : Person
  , seller : Person
  , shipping : (DateTime * Transport)
  }

let order = { buyer: ramon; seller: don; shipping: ... }

As I don't really know which shipping date or type of transport I'm going to use, I really can't initialize the record, so I have to make a modification to the design:

type Order =
  { buyer : Person
  , seller : Person
  , shipping : (DateTime * Transport) option
  }

let order = { buyer: ramon; seller: don; shipping: None }

If we had the lazy and built-in record constructors, we could maintain the initial design, and construct our record only waiting for shipping information:

type alias Order =
  { buyer : Person
  , seller : Person
  , shipping : (DateTime * Transport)
  }

let order = Order ramon don
(later in code)
let order' = order (date,transport)

The reason we mimic the alias keyword, is that we don't want this to happen for all of the records in F#, so we think of it as an ad-hoc approach, just like using the keyword lazy for lazy evaluated code.

The existing way of approaching to solving this issue right now in F#, is by writing a non-idiomatic (Pascal-case) function with the same name as the type or as @vivainio suggested on Twitter, by creating a curried method on the record:

type Order =
  { buyer : Person
  , seller : Person
  , shipping : (DateTime * Transport)
  }
let Order : Person -> Person -> (DateTime * Transport) =
  fun buyer seller shipping -> { buyer: buyer; seller: seller; shipping: shipping }

let order = Order ramon don
(later in code)
let order' = order (date,transport)

Tweet with suggested idea and picture of code

Both task can be tedious and monotone and are not error-prone, so It would be ideal if a machine could perform this task.

Pros and Cons

The advantages of making this adjustment to F# are easy readability and usage, sound design, less code to write, therefore less errors.

The disadvantages of making this adjustment to F# are introducing a keyword would probably have to a HUGE design consideration, therefore, if there is another approach that would be welcome as well

Extra informtion

Estimated cost (XS, S, M, L, XL, XXL). I can't evaluate this, but I would be willing to work on it ๐Ÿ‘

Affadavit (must be submitted)

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 would be willing to help implement and/or test this
  • I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@isaacabraham

I can see this also potentially having good utility when working with monadic bind e.g. set the values of each fields of a record if they are successfully parsed from a weakly-typed string. Currently you have to manually create a curryable builder function.

@Alxandr
Alxandr commented Feb 8, 2017

Can't we just get Record constructors that are curried? In which case you'd get this for free. This would also make it a lot easier to translate code from haskell which has this.

type Person = {
  age: int
  name: string }

// way's to construct
let p1 = { age = 2, name = "p" }
let p2 = Person 2 "p" // <- order has to be same as record name order in definition
let mkTodler = Person 2 // <- normal F# partial application
let p3 = mkTodler "p"
@cartermp
Member
cartermp commented Feb 8, 2017

@Alxandr I think that's a very good approach. No idea how much work that would be implementation-wise, but I think it's an elegant solution.

@Alxandr
Alxandr commented Feb 9, 2017

It should just be (as you already stated) the compiler pretending that there is a function defined as

Person : int -> string -> Person

i would think?

@Alxandr
Alxandr commented Feb 9, 2017

And also, to not break compatibility, precedence should be given to any other function that already exist with the same name.

@gentauro
gentauro commented Feb 9, 2017

And also, to not break compatibility, precedence should be given to any other function that already exist with the same name.

@Alxandr I don't think your approach would break any code as defining a function with Pascal-case is bad practice ... I only showed it as an example that F# actually would be able to do it.

But I really like your approach, it might be easier to get into F# ๐Ÿ‘

@Alxandr
Alxandr commented Feb 9, 2017

@gentauro I agree, but just because it's bad practice doesn't mean nobody's ever done it. So I just added this clause so that you guarantee backwards compatibility.

@gentauro
gentauro commented Feb 9, 2017

Would this approach actually help TypeProviders to generate idiomatic Records instead of current .NET Objects? /cc @tpetricek?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment