Discriminated Union - Allow Pattern Matching but Restrict Construction - f#

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 -> // ...
| _ -> // ...

Related

Pattern matching against a string property

I'm de-serializing some mappings from JSON and later on I need to pattern match based on a string field of the de-serialized types like this:
let mappings = getWorkItemMappings
let result =
workItemMappings
|> Seq.find (fun (m: WorkItemMapping) -> m.Uuid = workTime.workItemUuid)
match mapping.Name with
Even if I complete the pattern match for all cases I still get Incomplete pattern matches on this expression.. Which is obvious to me due to the string type of the Name field.
Is there a way tell the compiler which values for the Name field are available?.
I think I could create a union type for the possible mapping types and try to de-serialize the JSON to this union type but I would like to if there's another option.
If you are pattern matching on a string value, the compiler has no static guarantee that it will only have certain values, because it is always possible to construct a string of a different value. The fact that it comes from JSON does not help - you may always have an invalid JSON.
The best option is to add a default case which throws a custom descriptive exception. Either one that you handle somewhere else (to indicate that the JSON file was invalid) or (if you check the validity elsewhere) something like this:
let parseFood f =
match f with
| "burger" -> 1
| "pizza" -> 2
| _ -> raise(invalidArg "f" $"Expected burger or pizza but got {f}")
Note that the F# compiler is very cautious. It does not even let you handle enum values using pattern matching, because under the cover, there are ways of creating invalid enum values! For example:
type Foo =
| A = 1
let f (a:Foo) =
match a with
| Foo.A -> 0
warning FS0104: Enums may take values outside known cases. For example, the value 'enum (0)' may indicate a case not covered by the pattern(s).
Very hard to understand what you're asking. Maybe this snippet can be of help. It demos how literal string constants can be used in pattern matching, and reused in functions. This gives some added safety and readability when adding and removing cases. If you prefer not to serialize a DU directly, then perhaps this is useful as part of the solution.
type MyDu =
| A
| B
| C
let [<Literal>] A' = "A"
let [<Literal>] B' = "B"
let [<Literal>] C' = "C"
let strToMyDuOption (s: string) =
match s with
| A' -> Some A
| B' -> Some B
| C'-> Some C
| _ -> None
let strToMyDu (s: string) =
match s with
| A' -> A
| B' -> B
| C'-> C
| s -> failwith $"MyDu case {s} is unknown."
let myDuToStr (x: MyDu) =
match x with
| A -> A'
| B -> B'
| C -> C'
// LINQPad
let dump x = x.Dump()
strToMyDuOption A' |> dump
strToMyDuOption "x" |> dump
myDuToStr A |> dump

How to access "base" in Discriminated Union method override?

I have a DU and I'm overriding the Equals method. Based on the current DU value, I would like to call the base equality method or my custom one. However, it's not letting me access "base". Any idea on how to work around this?
type Test =
| A of string
| B of int64
override this.Equals(other) =
let other' = other :?> Test
match other' with
| A str -> str = "a"
| B i -> base.Equals this other //how do I do this?
First, any F# discriminated union will have obj as base class, so just use obj.Equals.
Second, Equals is a .NET method, not an F# function, so its arguments must be given in a tupled form - i.e. Equals(x,y) instead of Equals x y.
Finally, if you implement a custom Equals, you also need to add [<CustomEquality; NoComparison>]
So:
[<CustomEquality; NoComparison>]
type Test =
| A of string
| B of int64
override this.Equals(other) =
let other' = other :?> Test
match other' with
| A str -> str = "a"
| B i -> obj.Equals(this, other)

How can I enforce the creation of a Discriminated Union value through a dedicated function?

How can I enforce the creation of a Discriminated Union value through a dedicated function?
Intent:
I want to rely on Creational Patterns to produce structures having valid data only.
Therefore, I believe that I will need to restrict the use of a DU value by making it read-only. However, it's not obvious to me how to accomplish that.
module File1 =
type EmailAddress =
| Valid of string
| Invalid of string
let createEmailAddress (address:System.String) =
if address.Length > 0
then Valid address
else Invalid address
module File2 =
open File1
let validEmail = Valid "" // Shouldn't be allowed
let isValid = createEmailAddress ""
let result = match isValid with
| Valid x -> true
| _ -> false
I tried the following:
type EmailAddress =
private
| Valid of string
| Invalid of string
However, setting the DU type as private breaks the ability to perform pattern matching on the result of the creation function.
This is just what springs to mind immediately.
You could use an active pattern to determine the cases you want to expose as an API to the outside world and then keep the internal representation of the DU completely private.
This would force you to use the publically exposed API to create the discriminated union but still allow pattern matching against the result - something like this:
module File1 =
type EmailAddress =
private
| Valid of string
| Invalid of string
let createEmailAddress (address:System.String) =
if address.Length > 0
then Valid address
else Invalid address
// Exposed patterns go here
let (|Valid|Invalid|) (input : EmailAddress) : Choice<string, string> =
match input with
| Valid str -> Valid str
| Invalid str -> Invalid str
module File2 =
open File1
let validEmail = Valid "" // Compiler error
let isValid = createEmailAddress "" // works
let result = // also works
match isValid with
| Valid x -> true
| _ -> false
Note that if you use the same pattern names, you may have to add the rather nasty type annotations shown above - these would be required to prevent a compiler error if the File2 module were not present - this could be relevant if you are exposing an API in a library but not making use of it. If you use different pattern names, that's obviously not an issue.
As you've discovered, the DU value names (Valid and Invalid in your example), used in pattern matches, are also the constructors of those respective cases. It is not possible to do what you're asking for, to hide one and expose the other. A different approach is needed.
One approach might be to do what Anton Schwaighofer suggests, and embed all the possible operations on your email addresses inside a dedicated module:
module EmailAddress =
type EmailAddress =
private
| Valid of string
| Invalid of string
let createEmailAddress (address:System.String) =
if address.Length > 0
then Valid address
else Invalid address
let isValid emailAddress =
match emailAddress with
| Valid _ -> true
| Invalid _ -> false
// Deliberately incomplete match in this function
let extractEmailOrThrow (Valid address) = address
let tryExtractEmail emailAddress =
match emailAddress with
| Valid s -> Some s
| Invalid _ -> None
See Scott Wlaschin's "Designing with types" series, and in particular http://fsharpforfunandprofit.com/posts/designing-with-types-more-semantic-types/ (and the gist he references at the end of that). I'd really recommend reading from the beginning of the series, but I've linked the most relevant one.
BUT... I would suggest a different approach, which is to ask why you want to enforce the use of those constructor functions. Are you writing a library for general-purpose use by beginning programmers, who can't be trusted to follow the directions and use your constructor function? Are you writing just for yourself, but you don't trust yourself to follow your own directions? OR... are you writing a library for reasonably-competent programmers who will read the comment at the top of the code and actually use the constructor functions you've provided?
If so, then there's no particular need to enforce hiding the DU names. Just document the DU like so:
module EmailAddress =
/// Do not create these directly; use the `createEmailAddress` function
type EmailAddress =
| Valid of string
| Invalid of string
let createEmailAddress (address:System.String) =
if address.Length > 0
then Valid address
else Invalid address
Then go ahead and write the rest of your code. Worry about getting your model right first, then you can worry about whether other programmers will use your code wrong.
It really depends on what you want to do. One way would to expose the states as member functions and act on those. This works in your case but could become cumbersome with 3 or more value constructors.
type EmailAddress =
private
| Valid of string
| Invalid of string
with
member this.IsValid() =
match this with
| Valid _ -> true
| _ -> false
member this.IsInvalid() = not <| this.IsValid()
Or you add a special map function
member this.Map (success, error) =
match this with
| Valid x -> Valid (success x)
| Invalid x -> Invalid (error x)
Adding to what the accepted answer implies, and what its commenters try to refute, my impression is that there is normally no need for type annotations. If you really consider hiding the representations of discriminated unions for binary compatible APIs as per F# Component Design Guidelines, a minimalistic and generic but complete reproduction could look like this:
module Foo =
type 'a Foo =
private | Bar of 'a
| Fred of string
let mkBar a = Bar a
let mkFred<'a> s : 'a Foo = Fred s
let (|Bar|Fred|) = function
| Bar a -> Bar a
| Fred s -> Fred s
The Union case constructors Bar and Fred are inaccessible from outside module Foo, and replaced by functions doubling as hooks for validation. For consumers, we have the Active recognisers Bar and Fred instead.
let bar = Foo.mkBar 42
let fred = Foo.mkFred<int> "Fred"
[Foo.mkBar 42; Foo.mkFred "Fred"]
|> List.filter (function Foo.Bar _ -> true | _ -> false)

How to turn this into statically resolved type parameters

I have a need to use statically resolved type parameters in a situation similar to the below:
[<Struct>]
type Wrapper<'T> =
val raw:'T
new(v:'T) = {raw = v}
type Value =
| Float of float
| Int of int
| String of string
with
member this.ToWrapper() :'T =
match this with
| Float f -> Wrapper<float>(f) // type is inferred as float
| Int i -> Wrapper<int>(i) // error
| String s -> Wrapper<string>(s) // error
How do I define and use a ToWrapper function (or set thereof) that can map a 'Value' type to any type within the Generic Wrapper<'T> where I know 'T will be either float | int | string?
The Wrapper<'T> type needs to be a Struct so interfaces aren't an option - as suggested in some other posts related to this.
It's not clear to me what are you trying to achieve. Are you trying to restrict the wrapped types to Int, String and Float?
1) If so you can check at runtime like this:
[<Struct>]
type Wrapper<'T> =
val raw:'T
new(v:'T) = {raw = v}
let wrap x =
match box x with
| :? float -> ()
| :? int -> ()
| :? string -> ()
| _ -> failwith "invalid type"
Wrapper x
let a = wrap 90
let b = wrap "hi"
let c = wrap true // fails at runtime
2) If you want to restrict at compile-time an easy way is to add static members as constructors:
[<Struct>]
type Wrapper<'T> =
val raw:'T
private new(v:'T) = {raw = v}
with
static member wrap (x:float) = Wrapper x
static member wrap (x:int) = Wrapper x
static member wrap (x:string) = Wrapper x
let a = Wrapper<_>.wrap 90
let b = Wrapper<_>.wrap "hi"
let c = Wrapper<_>.wrap true // doesn't compile
3) Or may be, using the DU inside the wrapper makes more sense for you:
type Value =
| Float of float
| Int of int
| String of string
[<Struct>]
type Wrapper =
val raw:Value
new(v:Value) = {raw = v}
Of all solutions 3) is the only one that really restricts your wrapper. Solutions 1) and 2) restrict the way you construct it.
From 2) you can use some tricks with statically resolved type parameters in order to come up with an inline function (not a method) that will wrap only on those types. Still that will not constraint the type itself, but since the constructor is private the code that consumes your type will be forced through your restricted constructors.
Statically resolved type parameters work with functions or methods but not on types, since they are a compile time F# feature, not a .NET type system feature.
You can't do this, because Wrapper<float> isn't the same type as Wrapper<int> (which also isn't the same type as Wrapper<string>). What would the return type of ToWrapper be? It can't be all three at once.

Pattern combining type test and literal

The active pattern in this question fails to compile after upgrading to VS 2012 RTM. It provides a way to do a type test and match a literal within a single pattern. For example:
let (|Value|_|) value =
match box value with
| :? 'T as x -> Some x
| _ -> None
let getValue (name: string) (r: IDataReader) =
match r.[name] with
| null | :? DBNull | Value "" -> Unchecked.defaultof<_>
| v -> unbox v
Can this be done without the active pattern? I realize a when guard could be used (:? string as s when s = "") but it can't be combined with other patterns.
kvb's variation (which doesn't do quite the same thing since it assumes the type test succeeds) can be modified to produce a similar pattern:
let (|Value|_|) x value =
match box value with
| :? 'T as y when x = y -> Some()
| _ -> None
However, there is a subtle performance difference. The original active pattern translates to:
public static FSharpOption<T> |Value|_|<a, T>(a value)
{
object obj = value;
if (!LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
{
return null;
}
return FSharpOption<T>.Some((T)((object)obj));
}
that is, it does a type test and cast. It's usage (match x with Value "" -> ...) translates to:
FSharpOption<string> fSharpOption = MyModule.|Value|_|<object, string>(obj);
if (fSharpOption != null && string.Equals(fSharpOption.Value, ""))
{
...
}
Most notably, the typed value returned from the pattern is matched using the typical compiler transformations for patterns (string.Equals for strings).
The updated pattern translates to:
public static FSharpOption<Unit> |Value|_|<T, a>(T x, a value)
{
object obj = value;
if (LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
{
T y = (T)((object)obj);
T y3 = y;
if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<T>(x, y3))
{
T y2 = (T)((object)obj);
return FSharpOption<Unit>.Some(null);
}
}
return null;
}
which uses generic equality and is less efficient than matching against a literal. The usage is a bit simpler since equality is baked into the pattern:
FSharpOption<Unit> fSharpOption = MyModule.|Value|_|<string, object>("", obj);
if (fSharpOption != null)
{
...
}
Anyway, it works. But I like the original better.
You should be able to use a parameterized active pattern:
let (|Value|_|) v x =
if unbox x = v then
Some()
else None
The usage should look exactly like what you've got now.
Edit
While I don't know if the breaking change was intentional, I believe that active patterns with generic return types unrelated to the input types should usually be avoided. When combined with type inference, they can easily mask subtle errors. Consider the following example, using your original (|Value|_|) pattern:
match [1] with
| Value [_] -> "Singleton"
| _ -> "Huh?"
It seems like this isn't something you would actually ever attempt - the name implies that Value should only be used with literals; parameterized active patterns enable exactly this scenario.

Resources