Is it possible to add constant field values to F# discriminated unions?
Can I do something like this?
type Suit
| Clubs("C")
| Diamonds("D")
| Hearts("H")
| Spades("S")
with
override this.ToString() =
// print out the letter associated with the specific item
end
If I were writing a Java enum, I would add a private value to the constructor like so:
public enum Suit {
CLUBS("C"),
DIAMONDS("D"),
HEARTS("H"),
SPADES("S");
private final String symbol;
Suit(final String symbol) {
this.symbol = symbol;
}
#Override
public String toString() {
return symbol;
}
}
Just for completeness this is what is meant:
type Suit =
| Clubs
| Diamonds
| Hearts
| Spades
with
override this.ToString() =
match this with
| Clubs -> "C"
| Diamonds -> "D"
| Hearts -> "H"
| Spades -> "S"
The closest thing to your requirement is F# enums:
type Suit =
| Diamonds = 'D'
| Clubs = 'C'
| Hearts = 'H'
| Spades = 'S'
let a = Suit.Spades.ToString("g");;
// val a : string = "Spades"
let b = Suit.Spades.ToString("d");;
// val b : string = "S"
The problem with F# enums is non-exhaustive pattern matching. You have to use wildcard (_) as the last pattern when manipulating enums. Therefore, people tend to prefer discriminated unions and write explicit ToString function.
Another solution is to make a mapping between constructors and corresponding string values. This is helpful in case we need to add more constructors:
type SuitFactory() =
static member Names = dict [ Clubs, "C";
Diamonds, "D";
Hearts, "H";
Spades, "S" ]
and Suit =
| Clubs
| Diamonds
| Hearts
| Spades
with override x.ToString() = SuitFactory.Names.[x]
Pretty sure you can't, but is trivial to write a function that pattern matches and then compose the two things
Related
Suppose I have a DU like this:
type Fruit =
| Apple of int
| Banana of string
| Cherry of int * string
Now, I want to talk about the cases of the DU (not concrete items in the DU):
[<RequireQualifiedAccess>]
type FruitType =
| Apple
| Banana
| Cherry
module FruitType =
let ofFruit =
function
| Apple _ -> FruitType.Apple
| Banana _ -> FruitType.Banana
| Cherry _ -> FruitType.Cherry
type FruitSaladRecipe =
{
FruitsRequired : Set<FruitType>
}
As you can see, there is some code duplication here: every case of Fruit has an equivalent case in FruitType, and vice-versa.
Is there a more elegant way to write this?
Bonus points for not using reflection, this should be possible at compile-time.
This problem has come up a few times for me, but I will try to give a more concrete example.
Let's say you were building a form designer where the user can add custom elements like:
Drop downs
Text inputs
Sliders
In the menu for creating a new element, the first stage might be to pick a FormElementType:
[<RequireQualifiedAccess>]
type FormElementType =
| DropDown
| TextInput
| Slider
let! chooseFormElementType : UserInteraction<FormElementType> = ...
After choosing a FormElementType, the user will design an actual FormElement:
type FormElement =
| DropDown of string list
| TextInput of string option
| Slider of int * int
let createFormElement formElementType : UserInteraction<FormElement> =
match formElementType with
| FormElementType.DropDown ->
createDropDown
| FormElementType.TextInput ->
createTextInput
| FormElementType.Slider ->
createSlider
And then the new element can be added to a collection:
let form = Form.append formElement form
I have an F# Discriminated Union, where I want to apply some "constructor logic" to any values used in constructing the union cases. Let's say the union looks like this:
type ValidValue =
| ValidInt of int
| ValidString of string
// other cases, etc.
Now, I want to apply some logic to the values that are actually passed-in to ensure that they are valid. In order to make sure I don't end up dealing with ValidValue instances that aren't really valid (haven't been constructed using the validation logic), I make the constructors private and expose a public function that enforces my logic to construct them.
type ValidValue =
private
| ValidInt of int
| ValidString of string
module ValidValue =
let createInt value =
if value > 0 // Here's some validation logic
then Ok <| ValidInt value
else Error "Integer values must be positive"
let createString value =
if value |> String.length > 0 // More validation logic
then Ok <| ValidString value
else Error "String values must not be empty"
This works, allowing me to enforce the validation logic and make sure every instance of ValidValue really is valid. However, the problem is that no one outside of this module can pattern-match on ValidValue to inspect the result, limiting the usefulness of the Discriminated Union.
I would like to allow outside users to still pattern-match and work with the ValidValue like any other DU, but that's not possible if it has a private constructor. The only solution I can think of would be to wrap each value inside the DU in a single-case union type with a private constructor, and leave the actual ValidValue constructors public. This would expose the cases to the outside, allowing them to be matched against, but still mostly-prevent the outside caller from constructing them, because the values required to instantiate each case would have private constructors:
type VInt = private VInt of int
type VString = private VString of string
type ValidValue =
| ValidInt of VInt
| ValidString of VString
module ValidValue =
let createInt value =
if value > 0 // Here's some validation logic
then Ok <| ValidInt (VInt value)
else Error "Integer values must be positive"
let createString value =
if value |> String.length > 0 // More validation logic
then Ok <| ValidString (VString value)
else Error "String values must not be empty"
Now the caller can match against the cases of ValidValue, but they can't read the actual integer and string values inside the union cases, because they're wrapped in types that have private constructors. This can be fixed with value functions for each type:
module VInt =
let value (VInt i) = i
module VString =
let value (VString s) = s
Unfortunately, now the burden on the caller is increased:
// Example Caller
let result = ValidValue.createInt 3
match result with
| Ok validValue ->
match validValue with
| ValidInt vi ->
let i = vi |> VInt.value // Caller always needs this extra line
printfn "Int: %d" i
| ValidString vs ->
let s = vs |> VString.value // Can't use the value directly
printfn "String: %s" s
| Error error ->
printfn "Invalid: %s" error
Is there a better way to enforce the execution of the constructor logic I wanted at the beginning, without increasing the burden somewhere else down the line?
You can have private case constructors but expose public active patterns with the same names. Here's how you would define and use them (creation functions omitted for brevity):
module Helpers =
type ValidValue =
private
| ValidInt of int
| ValidString of string
let (|ValidInt|ValidString|) = function
| ValidValue.ValidInt i -> ValidInt i
| ValidValue.ValidString s -> ValidString s
module Usage =
open Helpers
let validValueToString = function
| ValidInt i -> string i
| ValidString s -> s
// 😎 Easy to use ✔
// Let's try to make our own ValidInt 🤔
ValidInt -1
// error FS1093: The union cases or fields of the type
// 'ValidValue' are not accessible from this code location
// 🤬 Blocked by the compiler ✔
Unless there's a particular reason that a discriminated union is required, given the particular use case you've provided it sounds like you don't actually want a discriminated union at all since an active pattern would be more useful. For example:
let (|ValidInt|ValidString|Invalid|) (value:obj) =
match value with
| :? int as x -> if x > 0 then ValidInt x else Invalid
| :? string as x -> if x.Length > 0 then ValidString x else Invalid
| _ -> Invalid
At that point, callers can match and be assured that the logic has been applied.
match someValue with
| ValidInt x -> // ...
| _ -> // ...
Right, I have this data type in Rascal:
data Type = Any() | Void() | Int() | Not(Type l) | And(set[Type] es) | Or(set[Type] es);
What I want to do is define another type like this:
data Primitive = Any() | Void() | Int();
And then be able to do things like this:
Primitive p = Any();
Type d = p;
Or, for example, match against Primitive when simplifying Type. Something like this:
public Type reduce(Not(Primitive p)) = p;
Currently, the only solution I can see is to expand the above rule for each case like so:
public Type reduce(Not(Any)) = Any();
public Type reduce(Not(Void)) = Void();
public Type reduce(Not(Int)) = Int();
I'm guessing there is a way to do this, but I didn't figure it out yet ... thoughts?
The short answer: although Abstract Data Types can be extended (i.e., their definition can be extended across modules) there is no direct inheritance.
Work arounds:
Solution A
data Type = Any() | Void() | Int() | Not(Type l) | And(set[Type] es) | Or(set[Type] es);
bool isPrim(Any()) = true;
bool isPrim(Void()) = true;
bool isPrim(Int()) = true;
default bool isPrim(Type t) = false;
Type reduce(Not(Type t)) = t when isPrim(t);
default Type reduce(Type t ) = t;
Here all constructors for Type are in a single ADT and the predicate isPrim selects the primitives. For example, reduce(Not(Void())) will reduce to Void().
Solution B
data Primitive = Any() | Void() | Int();
data Type = prim(Primitive p) | Not(Type l) | And(set[Type] es) | Or(set[Type] es);
Type reduce(Not(prim(Primitive p))) = prim(p);
default Type reduce(Type t ) = t;
Here the primitives are collected in a separate ADT Primitive and they are included in Type via the constructor prim. Now reduce(Not(prim(Void()))) will reduce to prim(Void()).
Final Notes
We would also prefer to have inheritance (without the extra constructor prim as in Solution B) but for various technical reasons we did not include it. Although desirable, I am not sure that we will ever do.
Note the functions preceded by default, they are the catch all case when the other declarations of a function do not match.
All functions are public, unless preceded by the key word private.
Nice question. Rascal does not feature user-defined sub-typing and typing for data types is nominal. That answers your question in theory, so how does that work in practise?
The answer for data types is slightly different for syntax types, so here follows both stories;
There many are different idioms to model a hierarchy of data-structures, we'll show only three here for the sake of simplicity;
Here's a way to extend a data-type with new features which does not involve adding new types, this produces an over-approximate model of what you intended:
// first the primitive types are defined (I've added Not here to make a point later):
data Type = Any() | Void() | Int() | Not(Type l);
// then the extension is added (perhaps in a different module)
data Type = And(set[Type] es) | Or(set[Type] es);
// the second definition adds its alternatives also to the child of `Not`.
The second way is more close to an actual extension, because the original Type is not extended, and no "junk" is added accidentally:
// we give the original type a unique name:
data Primitive = Any() | Void() | Int();
// For the extension the Primitive type is not polluted with the new constructors, but
// it was wrapped inside a singleton constructor `prim`
data Type = prim(Primitive super) | And(set[Type] es) | Or(set[Type] es);
Of course, this second solution will make you add prim constructors in possible pattern matches you might do, but the / deep match operator will allow you to ignore it where possible. For example:
bool evalt(prim(p)) = evalp(p);
bool evalp(Any()) = true;
bool evalp(Not(p)) = !evalp(p);
bool containsVoid(Type t) = /Void() := t;
Now for syntax types the story is similar but since chain rules in syntax types are invisible, it gives some additional flavor:
syntax Primitive = "any" | "void" | "int";
// notice the first chain rule or "injection" of Primitive into Type:
syntax Type = Primitive | left Type "∧" Type > left Type "∨" Type;
bool evalt((Type) `any`) = true; // the chain rule is parsed but invisible
People have been discussing to add implicit chaining to the abstract data-types as well, for its attractive to simulate sub-typing like so. I guess that would be like Scala's implicits. The jury is still out on that one.
I'm learning about creating composite* types in F# and I ran into a problem. I have this type and a ToString override.
type MyType =
| Bool of bool
| Int of int
| Str of string
with override this.ToString() =
match this with
| Bool -> if this then "I'm True" else "I'm False"
| Int -> base.ToString()
| Str -> this
let c = Bool(false)
printfn "%A" c
I get an error inside the ToString override that says "This constructor is applied to 0 argument(s) but expects 1". I was pretty sure this code wouldn't compile, but it shows what I'm trying to do. When I comment out the override and run the code, c is printed out as "val c : MyType = Bool false". When I step into that code, I see that c has a property Item set to the boolean false. I can't seem to access this property in the code however. Not even if I annotate c.
How should I be overriding ToString in this situation?
* I'm pretty sure these are called composite types.
When you are using a Discriminated Union (DU) (that's the appropriate name for that type), you need to unpack the value in the match statement like this:
type MyType =
| Bool of bool
| Int of int
| Str of string
with override this.ToString() =
match this with
| Bool(b) -> if b then "I'm True" else "I'm False"
| Int(i) -> i.ToString()
| Str(s) -> s
let c = Bool(false)
printfn "%A" c
The Item property that you're seeing is an implementation detail and is not intended to be accessed from F# code. Merely using this doesn't work because the DU is a wrapper around the value, so this refers to the wrapper, not to the contained value.
Here is my problem:
let foo =
match bar with
| barConfig1 -> configType1(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig2 -> configType2(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig3 -> configType3(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig4 -> configType4(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
I'd like to have the type of foo be determined by the match statement, but it always sets foo to the first type.
type bar =
|barConfig1
|barConfig2
|barConfig3
|barConfig4
In F#, there are no statements, only expressions, and each expression has to have a single concrete type. A match block is an expression as well, meaning that it has to have a single concrete type. What follows from that is that each case of the match has to have the same type as well.
That is, something like this is not valid F#:
let foo = // int? string?
match bar with // int? string?
| Int -> 3 // int
| String -> "Three" // string
In this case, the type inference mechanism will expect the type of the match to be the same as the type of the first case - int, and end up confused when it sees the string in the second. In your example the same thing happens - type inference expects all the cases to return a configType1.
A way around it would be by casting the values into a common supertype or interface type. So for your case, assuming the configTypes implement a common IConfigType interface:
let foo = // IConfigType
let arg = (devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
match bar with
| barConfig1 -> configType1(arg) :> IConfigType
| barConfig2 -> configType2(arg) :> IConfigType
| barConfig3 -> configType3(arg) :> IConfigType
| barConfig4 -> configType4(arg) :> IConfigType
If the output type has a limited number of cases, you can make that a discriminated union as well:
type ConfigType =
| ConfigType1 of configType1
| ConfigType2 of configType2
| ConfigType3 of configType3
| ConfigType4 of configType4``
let foo =
match bar with
| barConfig1 -> ConfigType1 <| configType1(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig2 -> ConfigType2 <| configType2(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig3 -> ConfigType3 <| configType3(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig4 -> ConfigType4 <| configType4(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)``
Alternately, if they all implement an interface or inherit some base class, you can upcast to that, as with scrwtp's answer.