F# SAFE how to deal with growth of message DU variants? - f#

In F# SAFE stack there is a DU type, which defines all types of messages flowing between server and client. In sample application there are <10 of them, I looked on provided examples - all of them have not many possible types. But what if the application is not small - there will be hundreds of types in DU; which will become hard to maintain. So I decided to divide this type in sub-types and put logic in corresponding files.
Here is my simplified type:
type Msg2 =
| LoginMsg of LoginState
| RegisterMsg of RegisterState
| ViewUpdateMsg of ViewUpdateState
Login state is defined in another file:
type LoginState =
| Login
| LogInResult of LoginResult
Login module works with logins:
let workWithLogin (model: Model) (msg: LoginState) (todosApi: ITodosApi) : Model * Cmd<LoginState> =
match msg with
| LoginState.Login ->
let result =
Cmd.OfAsync.perform todosApi.login model.InputData LoginState.LogInResult
model, result
| LoginState.LogInResult data ->
{ model with
LoginState = getVal data.Message
Token = data.Token },
Cmd.none
Note that it returns Model * Cmd<LoginState>.
Now I need to fix my update function:
let update2 (msg: Msg2) (model: Model) : Model * Cmd<Msg2> =
match msg with
| LoginMsg n ->
let ret = workWithLogin model n todosApi
model, snd ret
| RegisterMsg n -> ...
| ViewUpdateMsg n -> ...
The problem here is that I get Cmd<LoginState> from login module and need to convert it to Cmd<Msg2> somehow.
So I either need to create new Msg2 type or convert DU variant LoginMsg of LoginState to Msg2. I dont understand how to get LogInResult data from Cmd<LoginState>.
How can I fix this? How the problem with many message types is solved in big projects?

What you've done with wrapping in "sub"-messages is the right way to go, and you should probably do the same thing with the model - i.e. let Login have it's own model. To convert between different Cmd<'msg> there is a Cmd.map. In your case you can:
let update2 (msg: Msg2) (model: Model) : Model * Cmd<Msg2> =
match msg with
| LoginMsg n ->
let (loginModel, loginCmd) = workWithLogin model n todosApi
{ model with Login = loginModel }, Cmd.map LoginMsg loginCmd
You can see some API description of Cmd here: https://elmish.github.io/elmish/cmd.html

Related

Is it possible to have F# able to recognize an overlap of DU and use the right one itself?

type GenericResult =
| Ok
| Error of string
type LoginResult =
| Ok
| UserNotFound
| WrongPassword
let check something:GenericResult =
match something with
//| true -> Ok // error:This expression was expected to be of type "GenericREsult" but here has type "LoginResult"
| true -> GenericResult.Ok // I'm forced to specify GenericResult.Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok // here I don't need to specify the DU because this is defined after
| _ -> WrongPassword
I'd like to use just "Ok" in both the methods, without the need to specify the DU.
I see that in case of clashing of the value the last one is the "predefined".
Ideally I'd like to have a sort of inheritance
to reuse part of a DU in another DU.
For example:
type GenericResult =
| Ok
| Error of string
type LoginResult =
//| GenericResult.Ok
| UserNotFound
| WrongPassword
type SaveResult =
| Created
| Updated
//| GenericResult.Error
let checkLogin something: LoginResult | GenericResult.Ok =
match something with
| true -> Ok
| _ -> WrongPassword
[EDIT]
The real scenario where I feel the need for this feature is this with 3 different results from 3 different logic classes.
There will be in the future more cases so the multiplication of duplicated DU values will increase.
// DUs ordered from the most specific to the most generic
type BalanceUpdateResult =
| Created
| Updated
| InvalidRequest of string
type DeleteResult =
| Ok
| InvalidRequest of string
type Result<'T> =
| Ok of 'T
| NotValid of string
| Error of string
The goal is to have a clean match syntax in the consumer, where the value of the DU will evenctually be used to raise an exception or to return the created value, for example.
// balance update function (result is BalanceUpdateResult):
match result with
| Created -> this.createOkWithStatus 201
| Updated -> this.createOkWithStatus 200
| InvalidRequest error -> this.createErrorForConflict error
// company creation function (result is Result<Company>):
match result with
| Result.Ok newItem ->
context.Logger.Log $"Company created. New Id:{newItem.Id}, Name:{newItem.Name}."
this.createCreated newItem
| NotValid error -> base.createErrorForConflict error
| Error error -> base.createError error
Here, for example, InvalidRequest is not accepted in the second case because it belongs to the wrong DU.
Having to specify the DU everywhere results in a mess like the following example (see the many Result<_>.):
interface ICompanyLogic with
member this.Create(company:Company):Result<Company> =
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid validCompany ->
match companyRepository.Exists(validCompany.Name) with
| true -> Result<_>.NotValid($"A company with name \"{validCompany.Name}\" already exists.")
| _ ->
let newCompany = assignNewId validCompany
companyRepository.Create(newCompany)
Result<_>.Ok(newCompany)
member this.Update (company:Company):Result<Company> =
let checkNameExists company =
match companyRepository.GetByName company.Name with
| Some c when c.Id <> company.Id -> NotValid $"A company with name \"{company.Name}\" already exists."
| _ -> Valid company
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid c -> match checkNameExists c with
| Valid c -> companyRepository.Update c; Result<_>.Ok c
| NotValid msg -> Result<_>.NotValid msg
I think the best way to achieve what you are trying to do would be to start with a generic Result type that has a type parameter representing the error type:
type Result<'TError> =
| Ok
| Error of 'TError
This allows you to use different types for representing errors, including string, but also another DU to capture more specific error types. You can then define GenericResult and LoginResult as two type aliases:
type LoginError =
| UserNotFound
| WrongPassword
type GenericResult = Result<string>
type LoginResult = Result<LoginError>
To report a login error, you would now use Error WrongPassword to wrap the specific error in the generic Error constructor. The implementation of your two functions looks as follows:
let check something:GenericResult =
match something with
| true -> Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok
| _ -> Error WrongPassword
Unlike TypeScript union type, F# DU are meant to be composed and not extensible - see Thomas answer for a solution using this approach.
Since F# does not offer a direct solution, you may consider renaming cases like InvalidRequest in order to be more specific and to help differentiate them when reading the code. With these specific names, you can also merge all result types into a big Event DU like what's usually done in an event sourced system:
type Event =
// BalanceUpdateResult
| BalanceCreated
| BalanceUpdated
| BalanceUpdateError of string
// DeleteResult
| DeleteOk
| DeleteError of string
// ...
Ok, as explained by Romain multiple DUs cannot solve my problem.
I decided to use the built-in type Result<'T,'TError>.
It allows me to avoid create many DUs that inevitably will have clash of names, forcing the use the full DU prefix in the code.
I solved the problem that drove me to create custom DUs with the inspiring example from Thomas reply.
(with Result<,>) I have the possibility to have dinstinct Errors or Oks.
(note the Result<unit,_> and the Result<BalanceUpdateRequest,_>)
type ICompanyLogic =
abstract member Create:Company -> Result<Company, string> // CreateResult
abstract member Update:Company -> Result<Company, string> // UpdateResult
abstract member Delete:string -> Result<unit,string> // DeleteResult
type BalanceUpdateResult =
| Created
| Updated
type IBalanceLogic =
abstract member CreateOrUpdate: request:BalanceUpdateRequest -> Result<BalanceUpdateResult, string>
Apart BalanceUpdateResult all the other DUs where replaced buy the Result<'T,'TError>.
I just maintained a couple one for specific tasks:
type CompanyValidation = Valid of Company | NotValid of string
type ValidateResult = Valid | NotValid of string
In the end with this solution:
I don't need to define many DUs
I can customize the Result... within as many values I want (storing a sub-DU in the Ok or Error union case)
I don't need to use prefix or use synonims to avoid clash (code result much cleaner)

Result bind with different types?

I've made a simple Computation Expression workflow for dealing with Results.
[<RequireQualifiedAccess>]
module Result =
type Builder() =
member __.Bind(x, f) = x |> Result.bind f
member __.Return(x) = x
member __.ReturnFrom(x) = Ok x
let workflow = Builder()
I also use different types to represent different kids of errors:
type ValidationError<'a> = { Obj:'a; Message:string }
type InvalidOperationError = { Operation:string; Message:string }
The problem arises when two results have different error types.
LetterString.create : string -> Result<LetterString, ValidationError<string>>
Username.create : string -> Result<Username, ValidationError<string>>
PositiveDecimal.create : decimal -> Result<PositiveDecimal, ValidationError<decimal>>
let user =
Result.workflow {
let! name = LetterString.create "Tom"
let! username = Username.create "Tom01098"
// Error occurs here.
let! balance = PositiveDecimal.create 100m
return! {
// User record creation elided.
}
}
FS0001 Type mismatch. Expecting a
'Result<PositiveDecimal,ValidationError<string>>'
but given a
'Result<PositiveDecimal,ValidationError<decimal>>'
I have already tried using a DU type of all errors:
type Error<'a> =
| ValidationError of Obj:'a * Message:string
| InvalidOperationError of Operation:string * Message:string
This has a similar problem when the generic parameter 'a is different between errors. It also loses the exact type of error in the type signature of the function.
The expected result is that the entire workflow has a unified error type, preferably as specific as possible in terms of type.
Can be solved by removing the generic parameter and using a single Error DU. Unfortunately this loses the signature I wanted, but it will have to do.

Pattern matching casting types

I am a newbie in F# and have been following guides to try to make a piece of code work but it hasn't.
I create types of single and coop sports through inheritance.
Then I use pattern matching to know the type and, if it is a coop sport, get also the number of players. Then rank each accordingly.
However, I have been getting errors. I followed Microsoft examples on this and I don't really understand the errors. I don't have a functional programming background.
type Sport (name: string) =
member x.Name = name
type Individual(name: string) =
inherit Sport(name)
type Team(name: string, numberOfPlayers : int) =
inherit Sport(name)
member x.numberOfPlayers = numberOfPlayers
let MK = new Individual("Combate Mortal")
let SF = new Individual("Lutadores de Rua")
let Tk = new Individual("Tekken Chupa")
let MvC = new Team("Marvel Contra Capcom", 3)
let Dbz = new Team("Bolas do Dragao", 3)
let interpretSport (sport:string) (players:int) =
match sport with
| "Combate Mortal" -> printfn "Rank1"
| "Lutadores de Rua" -> printfn "Rank2"
| "Tekken Chupa" -> printfn "Rank3"
| "Bolas do Dragao" -> printfn "Rank4. No of players: %d " players
| "Marvel Contra Capcom" -> printfn "Rank5. No of players: %d" players
| _ -> printfn "not a sport in our list..."
let matchSport (sport:Sport) =
match sport with
| :? Individual -> interpretSport(sport.Name)
| :? Team as teamSport -> interpretSport(teamSport.Name,teamSport.numberOfPlayers)
| _ -> printfn "not a sport"
matchSport(MK)
matchSport(SF)
matchSport(Tk)
matchSport(MvC)
matchSport(Dbz)
1st error when calling function with more than 1 argument:
2nd error when printing:
The question has already been answered, but because the asker says he is a newby in F#, maybe it's worth to iterate a little.
To begin, you define a function with two parameters:
let interpreteSport (sport:string) (player:int) =
In F#, there is no notion of optional parameters in the same sense that they exist in C#, so if you declare a function with two parameters, and you want to invoke it, and get its return value, you must supply all the parameters you put in its definition.
So in the first branch of your match expression, when you write:
:? Individual -> interpretSport(sport.Name)
you are making an error, passing only one parameter to a function that takes two.
But wait! Why the compiler don't alert you with an error saying you are calling a function with one parameter when it expects two?
Because it turns out that what you write, even if it does not call the interpreteSport function as you believed, it's a perfect valid expression in F#.
What it returns is an expression called "partially applied function", that is, a function that has received its first parameter, and is waiting for another one.
If you assign the result of such an expression to a value, let's say:
let parzFun = interpretSport sport.Name
you can then pass this value around in your code and, when you are ready to supply the missing parameter, evaluate it like this:
let result = parzFun 1
That's what the compiler is telling you when it talks about 'int -> unit': function signatures in F# are given in this form:
a -> b -> c -> d -> retval, where a, b, c, d etc. are the types of the parameters, and retVal the return value.
Your interpreteSport function has a signature of: string -> int -> unit, where unit is the special type that means 'no value', similar to C# void, but with the big difference that unit is an expression that you can correctly assign to a value, while void is just a keyword, and you cannot assign a variable to void in C#.
OK, so, when you call your function passing only the first parameter (a string), what you obtain is an expression of type int -> unit, that is another function that expects and integer and returns unit.
Because this expression is in a branch of a match expression, and because all the branches of a match expression must return the same type, the other 2 branches are also expected to return an int -> unit function, what it's not, and that explain your second error.
More on this in a moment, but before, we must look at the first error reported by the compiler, caused by this line of code:
:? Team as teamSport -> interpretSport(teamSport.Name,teamSport.numberOfPlayers)
Here, you are thinking your are calling your function with 2 parameters, but your are actually not: when you put 2 values in parenthesis, separated by a comma, you are creating a tuple, that is, a single value composed of two or more values. It's like your are passing again only the first parameter, but now with the wrong type: the first parameter of you function is a string, and you are instead passing a tuple: ('a * 'b) is how F# represents tuples: that means a single value composed of a value of type 'a (generic, in your case string) and another of type 'b (generic, in your case integer).
To call your function correctly you must call it so:
:? Team as teamSport -> interpretSport teamSport.Name teamSport.numberOfPlayers
But even if you limit yourself to this correction you will have all the same the second error because, remember, the first expression of your match returns a partially applied funcion, so int -> unit (a function that expects an integer and returns a unit) while your second and your third expressions are now of type unit, because they actually call two functions that return unit (interpreteSport and printfn). To completely fix your code, as has already been said in other answers, you must supply the missing integer parameter to the first call, so:
let matchSport (sport:Sport) =
match sport with
| :? Individual -> interpretSport sport.Name 1
| :? Team as teamSport -> interpretSport teamSport.Name teamSport.numberOfPlayers
| _ -> printfn "not a sport"
If this is a F# learning exercise then it's best to avoid classes and inheritance completely. The fundamental idiomatic F# types are records and discriminated unions.
The intent of your code is not clear to me at all, but I have attempted to refactor to remove the use of classes:
type Players =
| Individual
| Team of numberOfPlayers:int
type Sport = { Name : string; Players : Players }
let MK = { Name = "Combate Mortal"; Players = Individual }
let SF = { Name = "Lutadores de Rua"; Players = Individual }
let Tk = { Name = "Tekken Chupa"; Players = Individual }
let MvC = { Name = "Marvel Contra Capcom"; Players = Team 3 }
let Dbz = { Name = "Bolas do Dragao"; Players = Team 3 }
let interpretSport (sport:Sport) =
let players =
match sport.Players with
| Individual -> ""
| Team numberOfPlayers -> sprintf ". No of players: %d" numberOfPlayers
let rank =
match sport.Name with
| "Combate Mortal" -> Some 1
| "Lutadores de Rua" -> Some 2
| "Tekken Chupa" -> Some 3
| "Bolas do Dragao" -> Some 4
| "Marvel Contra Capcom" -> Some 5
| _ -> None
match rank with
| Some r -> printfn "Rank%d%s" r players
| None -> printfn "not a sport in our list..."
Your function interpretSport has two arguments but your first call to it has only one. Try calling it like so:
| :? Individual -> interpretSport sport.Name 1
Also, the second call uses tupled parameters but the function is declared to take curried parameters. Try calling it like so:
| :? Team as teamSport -> interpretSport teamSport.Name teamSport.numberOfPlayers
There are several small problems with your code. The most obvious is in matchSport, you are calling interpretSport in an uncurried style, with an argument tuple. The call should look like:
Team as teamSport -> interpretSport teamSport.Name teamSport.numberOfPlayers
However, that's a problem, because on the first case of the pattern matching you call interpretSport with only one argument, so you partially apply it and you get a type int -> unit, but when you fully apply it on the second case you get unit, and of course all types of the pattern matching cases must match. The cheapest solution would be to add a 1 to your call on the first case like this:
Individual -> interpretSport sport.Name 1
But you probably want to use the sports you bound before (maybe in a list you give as a parameter) to do the checking. It is in general a bad idea (in functional programming and elsewhere) to hard-code that many strings, you probably want to do some kind of association List, or Map of the Sports to the Ranks, then fold over the sports List and match when found with individual or team and then print whatever the Map gives you for that case. This would be shorter and more extensible.

Is there a generic function that identifies the cases of a nested discriminated union?

I created a nested Discriminated Union (DU) as follows:
type OptimizationPeriod = | All
| Long
| Short
type OptimizationCriterion = | SharpeRatio of OptimizationPeriod
| InformationRatio of OptimizationPeriod
| CalmarRatio of OptimizationPeriod
and also a non-nested DU:
type Parallelism = Sequential | PSeq
I have a JSON configuration file with strings that define the DU cases. The following function manages to identify the case of the non-nested Parallelism DU :
let stringToDUCase<'t> (name: string) : 't =
let dUCase =
Reflection.FSharpType.GetUnionCases( typeof<'t> )
|> Seq.tryFind (fun uc -> uc.Name = name)
|> Option.map (fun uc -> Reflection.FSharpValue.MakeUnion( uc, [||] ) :?> 't)
match dUCase with
| Some x -> x
| _ -> let msg = sprintf "config.json - %s is not a case in DU %A" name typeof<'t>
failwith msg
Note: I certainly copied it from somewhere as the function is a bit over my head, apologies to the author for not remembering where it came from.
Unfortunately this function fails to identify the case for the nested DU:
stringToDUCase<OptimizationCriterion> config.Trading.Criterion
System.Exception: config.json - SharpeRatio All is not a case in DU FractalTypes.OptimizationCriterion
Two questions:
1) I was able to write a function that deals specifically with the OptimizationCriterion DU and is able to identify the case. Is there a generic function along the lines of stringToDUCase that could do the same?
2) Would it be better to use a tuple of type OptimizationCriterion*OptimizationPeriod instead of a nested DU? (I probably would have to call stringToDUCase twice, but that is not a problem)
An "empty" DU case like All is just a value, but a "non-empty" DU case like SharpeRatio is actually a function that takes one value and returns the type. In this case, SharpeRatio has the type OptimizationPeriod -> OptimizationCriterion.
Your existing stringToDUCase function always passes an empty array into MakeUnion (implying an empty DU case). So here's a modified version of the function that works for any DU case:
let stringToParamDUCase<'t> (name: string) =
Reflection.FSharpType.GetUnionCases(typeof<'t>)
|> Seq.tryFind (fun uc -> uc.Name = name)
|> Option.map (fun uc ->
fun (parameters:obj []) -> Reflection.FSharpValue.MakeUnion(uc, parameters) :?> 't)
|> Option.defaultWith (fun () ->
failwith (sprintf "config.json - %s is not a case in DU %A" name typeof<'t>))
Note that it returns a function of obj [] -> 't. I've also simplified the error handling a little bit.
This is how you might use it:
let myOptimizationPeriod = stringToParamDUCase<OptimizationPeriod> "All" [||]
let f = stringToParamDUCase<OptimizationCriterion> "SharpeRatio"
let myOptimizationCriterion = f [|All|]
I think the existing answer should answer your question directly. However, I think it is worth making two additional points. First, it might be easier if you represented your OptimizationCriterion as a record, because all your DU cases contain the same value:
type OptimizationPeriod =
| All | Long | Short
type OptimizationRatio =
| SharpeRatio | InformationRatio | CalmanRatio
type OptimizationCriterion =
{ Ratio : OptimizationRatio
Period : OptimizationPeriod }
This happens to solve your problem too, because now you only need DUs without parameters, but I think it is also better design, because you avoid duplicating the second parameter.
Second, I don't think you really need to go with a fancy custom reflection-based function for deserialization. If you want to store your data in a JSON, you should either use standard library (Newtonsoft.JSON or Chiron will do just fine), or you can write this directly using something like JsonValue from F# Data, but using custom reflection code is a quick way leading to unmaintainable code.

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)

Resources