-
Notifications
You must be signed in to change notification settings - Fork 21
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
Literals as types #656
Comments
Alternative approach could be: type CurrencyCode = CAD | USD | GBP
type CurrencyDetails = { Name: string; Number: int; Digits: int }
module Currency =
let details = function
| CAD -> { Name = "Canadian Dollar"; Number = 124; Digits = 2 }
| USD -> { Name = "United States Dollar"; Number = 840; Digits = 2 }
| GBP -> { Name = "Pound sterling"; Number = 826; Digits = 2 }
let { Name = name; Number = number } = CAD |> Currency.details
printfn "The number of currency: '%s' is: %i" name number
type CountryCode = CA | GB | US | VG | IO
type CountryDetails = { Name: string; Code: string; LongCode: string; Number: int; Independant: bool; Currencies : CurrencyCode list }
module Country =
let details = function
| CA -> { Name = "Canada"; Code = "CA"; LongCode = "CAN"; Number = 124; Independant = true; Currencies = [CAD] }
| GB -> { Name = "United Kingdom of Great Britain and Northern Ireland"; Code = "GB"; LongCode = "GBR"; Number = 826; Independant = true; Currencies = [CAD] }
| US -> { Name = "United States of America"; Code = "US"; LongCode = "USA"; Number = 840; Independant = true; Currencies = [USD] }
| VG -> { Name = "British Virgin Islands"; Code = "VG"; LongCode = "VGB"; Number = 92; Independant = false; Currencies = [USD] }
| IO -> { Name = "British Indian Ocean Teritory"; Code = "IO"; LongCode = "IOT"; Number = 86; Independant = false; Currencies = [USD; GBP] }
let { Name = countryName; Currencies = currencies } = IO |> Country.details
printfn "The '%s' country uses the currencies: %A" countryName currencies |
This proposal isn't needed. type Currency =
CAD | USD | GBP
member t.Name = match t with CAD -> "CAD" | USD -> "USD" | GBP -> "GBP"
member t.Number = match t with CAD -> 123 | USD -> 223 | GBP -> 444 |
@charlesroddie that's true, but there could be value in providing the suggested (or similar) syntactic sugar. If it would get used enough, I think it could be a valuable shorthand. Though I suggest we also add the type Currency =
| CAD
with Name = "CAD"
and Number = 123
| ... It reads kinda nicely, too. My question then would be, would we also want to be able to have the associated value depend on values contained by the DU case? For example, I'd find the following valuable: type Shape =
| Rectangle of width:int * height: int
with Area = width * height
| RightTriangle of base:int * height:int
with Area = width * height / 2 This would imply that this feature is actually a shorthand for properties (or maybe methods?). |
Continuing on my last comment, these shorthand properties could instead take the associated arguments as parameters, to handle things like nested tuples nicely, at the expense of slightly cluttering simpler examples (like type Geometry =
| Line of vec1:(float * float) * vec2:(float * float)
with Length((x1,y1),(x2,y2)) = sqrt (((x1 - x2) ** 2.) + ((y1 - y2) ** 2.)) That much said, I think this is interesting to consider. |
I propose using anonymous records now that we have them. type Planets =
| MERCURY of {|Mass=3.303e+23; Radius=2.4397e6|}
| VENUS of {|Mass=4.869e+24; Radius=6.0518e6|}
| EARTH of {|Mass=5.976e+24; Radius=6.37814e6|}
| MARS of {|Mass=6.421e+23; Radius=3.3972e6|}
| JUPITER of {|Mass=1.9e+27; Radius=7.1492e7|}
| SATURN of {|Mass=5.688e+26; Radius=6.0268e7|}
| URANUS of {|Mass=8.686e+25; Radius=2.5559e7|}
| NEPTUNE of {|Mass=1.024e+26; Radius=2.4746e7|} So, in this way no new syntax is needed, we can use what we already have. |
Mroe realistic would be a typescript-like combination of literals-as-types plus adhoc unions, e.g. |
Rescript used a special syntax for this purpose, the hashtag intentionally tries to distinguish syntax of literal types from literal values, still their semantics remain as in typescript. type color = [#red | #green | #blue] |
that's the way Scala embraced // the following constant can only store ints from 1 to 3
val three: 1 | 2 | 3 = 3
val one: 1 = 1 // val declaration
def foo(x: 1): Option[1] = Some(x) // param type, type arg
def bar[T <: 1](t: T): T = t // type parameter bound
foo(1: 1) // type ascription |
Adding an alternative. We'll use static data, then it typically gets moved to a DB after a few updates. I use this approach which works for both. type Item = { ... }
let items : Item list = ... // loaded or statically listed
let codeDict = items |> List.map (fun x -> x.Code, x) |> dict
// once you know the lookup can't fail
let item = codeDict.[code] If it's static data, Code could be a DU or module constants, so the indexer above is safe to use. But this doesn't work if data is moved to a DB -- data changes could cause crashes. For me, something like Code typically comes from outside my program (e.g. front end) and is validated by checking if it's a dictionary key. So using the indexer is not a problem. Contextually, this approach is for small amounts of frequently-used or expensive-to-compute data. Otherwise it's easier to load a specific item from db when needed. |
Closing preferring #1195 for now |
Add literals as types, als Typescript See also literal types in type script, https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types
Also relates to erased type-tagged unions #538
This language suggestion proposes adding Enumerate Unions. These are union types were rather than having different cases each of which defines any number of items the union is an enumeration of the same data for each case.
One example domain currencies. There is a fixed list of these and a handful data elements are associated with each currenty:
Syntax
A proposed syntax for EnumerateUnions is:
This is very succinct (4 lines vs about 16 using the current languate), easy to understand, models all the data and prevents the construction of invalid currencies.
Here is a more complex example modeling ISO currency https://en.wikipedia.org/wiki/ISO_4217 and country codes https://en.wikipedia.org/wiki/ISO_3166-1.
Again a lot of information is represented very clearly and compactly in a typesafe way.
Uses
Where would this be useful? All cases where there domains contain an infrequently changing enumeration of items with richer data than just strings or integers. These may be globally defined lists (currencies) or specific to the application. Examples are:
Related Proposals
Open Proposal #564
Is a proposal to ease access to data elements common to all cases. Those elements are then accessible as properties using dot notation.
This comment #564 (comment) suggests that commen field be defined explicitly like this:
with constructor like this:
EmptyCart(DiscountCode=code)
.The two proposals could perhaps be merged like this:
In this case if the value is defined in the defintion that value is fixed for all "instantiations" of the case. One issue with this (other than being more complex) is that it blurs the line between "constant values" and dynamic values.
In this syntax the simple currency example is as follows:
Closed Proposal #558
Suggests being able to define constant data for DU like so:
However it does not suggest a way to make it easier to access the common set of data.
Existing approaches to this
Unions without Data
This means the requirement that all currencies are enumerated but the data associated with Currencies is not modeled. The code can be obtained as a string using reflection but that is all.
Enums
In this case the integer value is available as the data representing the Currency but again not all the needed data.
Link unions cases with records via let
This supports all the data but is verbose. And to hide the Currency constructor to prevent the creation of invalid currencies will make it even more verbose.
Link unions cases with records via static members
Again a bit verbose and tedious. It also suffers from the disadvantage of needing to keep the Union of currencies in line with the list of details.
This could also be done using a module to hold a function with
AllDetails
.Pros and Cons
The advantages of making this adjustment to F# are that modeling complex static data in a typesafe way becomes very straight forward. See the example modeling Countries and Currencies above. Also see the examples of modeling the same domain with the current language definition.
The disadvantages of making this adjustment to F# are ...
Extra information
Estimated cost (XS, S, M, L, XL, XXL): no clue
Related suggestions: #564, #558
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: