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 Getters and Setters for Primitive Types To Validate and Coerce Values #517

Closed
5 of 6 tasks
financial-engineer opened this issue Nov 14, 2016 · 4 comments
Closed
5 of 6 tasks

Comments

@financial-engineer
Copy link

financial-engineer commented Nov 14, 2016

Allow Getters and Setters for Primitive Types To Validate and Coerce Values

I suggest adding support for getters and setters for primitive type extensions and user-defined types extending primitive types in order to improve F#'s ability to make invalid states unrepresentable at the code level.
This suggestion is an extention of suggestion #516 to primitive types.

Syntax for a user-defined type extending a primitive type:

type user-defined-type-name = primitive-type-name with
      member self-identifier.Value 
          with get() =  [ ... code... ] self-identifier
          and set(value) = [ ... code... ] self-identifier <- value
[ end ]

Syntax for a primitive type extension:

type primitive-type-name with
    // Value is suggested as a special name for the property intended to validate and/or coerce values 
    member self-identifier.Value 
          with get() = [ ... code... ] self-identifier
          and set(value) = [ ... code... ] self-identifier <- value
[ end ]

A language user is under no obligation to implement both a getter and a setter for the Value property. A language user can define the Value property with only getter or setter. It's up to a language user to define which logic (data validation or data coercion) contained in a getter or a setter.

For example,

let coerceString str =
    if str.Length = 0 then "Empty" else str
let validateString str =
   if str.Length = 0 then raise (InvalidOperationException("String must not be empty!")
   else str
type TrustedString = string with 
    member this.Value 
        with get () = coerceString this
        and  set v  = this <- validateString v
let trustedStr : TrustedString = "John Doe"

let coerceTemperature (x:int) =
    if x < 0 then 0 elif x > 100 then 100 else x
let validateTemperature(x:int) =
   if x < 0 || x > 100 then raise (InvalidOperationException("Invalid temperature!") else x
type Temperature = int with
    member this.Value 
        with get () = coerceTemperature this
        and  set v  = this <- validateTemperature v
let sensorTemperature : Temperature = 23

The get would be called each time a value is accessed, i.e.

let getTemperature (x:Temperature) = sensorTemperature

The set would be called during value construction, copy, or assignment to a mutable named value, i.e.

let sensorTemperature = sensonTemperature + 5 // value shadowing
let mutable kettleTemperature : Temperature = 14
kettleTemperature <- 18

Pros and Cons

The advantages of making this adjustment to F# are:

  • Primitive type values can always be within an acceptable, safe range of values. No more SQL injections in strings, to name a few. It would be a nice addition to units of measure in F#.
  • Removes need for boilerplate helper functions scattered in different places of code to achieve the same result

The disadvantages of making this adjustment to F# are:

  • Somewhat complicated implementation. Primitive types must remain primitive in respect of memory used to store values. The Value property for primitive types would be an abstraction used only to illustrate a language user's intents in F# code, rather than an actual property of a type. As far as I understand, getters and setters for primitive types have to be implemented as ordinary, standalone functions whose calls are inserted by the compiler at each point where primitive type values are being constructed, read, copied or assigned.
  • Potential for slight confusion over the difference between getters and setters for a class vs getters and setters for a primitive type extension (user-defined type extending a primitive type).

Extra information

Estimated cost (XS, S, M, L, XL, XXL): L (like in suggustion #516).

Related suggestions: Suggestion #516

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").

Please tick all that apply:

  • This is not a breaking change to the F# language design. It does not affect the existing codebases.
  • 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
@financial-engineer financial-engineer changed the title Allow Getters and Setters for Primitive Types To Validate Values Allow Getters and Setters for Primitive Types To Validate And Coerce Values Nov 14, 2016
@financial-engineer financial-engineer changed the title Allow Getters and Setters for Primitive Types To Validate And Coerce Values Allow Getters and Setters for Primitive Types To Validate and Coerce Values Nov 14, 2016
@Rickasaurus
Copy link

Rickasaurus commented Nov 15, 2016

I'm a little bit confused as to the goal with this. Is the primitive to avoid the overhead of a record? Would a single case struct union that can only be constructed via an exposed function fill a similar role?

@cloudRoutine
Copy link

This should be done with a type class not as a language feature

@dsyme
Copy link
Collaborator

dsyme commented Mar 1, 2017

I'm a little bit confused as to the goal with this. Is the primitive to avoid the overhead of a record? Would a single case struct union that can only be constructed via an exposed function fill a similar role?

I agree with this comment. There are ways of defining new types in F# that have enforced invariants, e.g. a simple class or struct type that checks on creation. That's the way to do this today.

@dsyme
Copy link
Collaborator

dsyme commented Mar 1, 2017

Many thanks for the suggestion. We won't be adding this new form of type definition to F#. Our recommendation is to use a new struct, union, record or class definition that enforces the invariant of the new type.

@dsyme dsyme closed this as completed Mar 1, 2017
@dsyme dsyme mentioned this issue Mar 24, 2017
6 tasks
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

4 participants