Currently i'm working in a game and use Event/Observables much, one thing i run into was to eliminate some redundant code, and i didn't found a way to do it. To explain it, let's assume we have following DU and an Observable for this DU.
type Health =
| Healed
| Damaged
| Died
| Revived
let health = Event<Health>()
let pub = health.Publish
I have a lot of this kind of structures. Having all "Health" Messages grouped together is helpfull and needed in some situations, but in some situations i only care for a special Message. Because that is still often needed i use Observable.choose to separate those message. I have then code like this.
let healed = pub |> Observable.choose (function
| Healed -> Some ()
| _ -> None
)
let damaged = pub |> Observable.choose (function
| Damaged -> Some ()
| _ -> None
)
Writing this kind of code is actually pretty repetitive and annoying. I have a lot of those types and messages. So one "rule" of functional programming is "Parametrize all the things". So i wrote a function only to just help me.
let only msg pub = pub |> Observable.choose (function
| x when x = msg -> Some ()
| _ -> None
)
With such a function in place, now the code becomes a lot shorter and less annoying to write.
let healed = pub |> only Healed
let damaged = pub |> only Damaged
let died = pub |> only Died
let revived = pub |> only Revived
EDIT:
The important thing to note. healed, damaged, died, revived are now of type IObservable<unit> not IObservable<Health>. The idea is not just to separate the messages. This can be easily achieved with Observable.filter. The idea is that the the data for each case additional get extracted. For DU case that don't carry any additional data this is easy, as i only have to write Some () in the Observable.choose function.
But this only works, as long the different cases in a DU don't expect additional values. Unlucky i also have a lot of cases that carry additional information. For example instead of Healed or Damaged i have HealedBy of int. So a message also contains additional how much something got healed. What i'm doing is something like this, in this case.
let healedBy = pub |> Observable.choose (function
| HealedBy x -> Some x
| _ -> None
)
But what i really want is to write it something like this
let healedBy = pub |> onlyWith HealeadBy
What i'm expecting is to get an Observable<int>. And i didn't found any way how to do it. I cannot write a function like only above. because when i try to evaluate msg inside a Pattern Matching then it is just seen as a variable to Pattern Match all cases. I cannot say something like: "Match on the case inside the variable."
I can check if a variable is of some specific case. I can do if x = HealedBy then but after that, i cannot extract any kind of data from x. What i'm really need is something like an "unsecure" extracting like option for example provide it with optional.Value. Does there exists any way to implement such a "onlyWith" function to remove the boilerplate?
EDIT:
The idea is not just separating the different messages. This can be achieved through Observable.filter. Here healedBy is of type IObservable<int> NOT IObservable<Health> anymore. The big idea is to separate the messages AND extract the data it carries along AND doing it without much boilerplate. I already can separate and extract it in one go with Observable.choose currently. As long as a case don't have any additional data i can use the only function to get rid of the boilerplate.
But as soon a case has additional data i'm back at writing the repetitive Observable.Choose function and do all the Pattern Matching again. The thing is currently i have code like this.
let observ = pub |> Observable.choose (function
| X (a) -> Some a
| _ -> None
)
And i have this kind of stuff for a lot of messages and different types. But the only thing that changes is the "X" in it. So i obviously want to Parameterize "X" so i don't have to write the whole construct again and again. At best it just should be
let observ = anyObservable |> onlyWith CaseIWantToSeparate
But the new Observable is of the type of the specific case i separated. Not the type of the DU itself.
The behaviour it appears you are looking for doesn't exist, it works fine in your first example because you can always consistently return a unit option.
let only msg pub =
pub |> Observable.choose (function
| x when x = msg -> Some ()
| _ -> None)
Notice that this has type: 'a -> IObservable<'a> -> IObservable<unit>
Now, let's imagine for the sake of creating a clear example that I define some new DU that can contain several types:
type Example =
|String of string
|Int of int
|Float of float
Imagine, as a thought exercise, I now try to define some general function that does the same as the above. What might its type signature be?
Example -> IObservable<Example> -> IObservable<???>
??? can't be any of the concrete types above because the types are all different, nor can it be a generic type for the same reason.
Since it's impossible to come up with a sensible type signature for this function, that's a pretty strong implication that this isn't the way to do it.
The core of the problem you are experiencing is that you can't decide on a return type at runtime, returning a data type that can be of several different possible but defined cases is precisely the problem discriminated unions help you solve.
As such, your only option is to explicitly handle each case, you already know or have seen several options for how to do this. Personally, I don't see anything too horrible about defining some helper functions to use:
let tryGetHealedValue = function
|HealedBy hp -> Some hp
|None -> None
let tryGetDamagedValue = function
|DamagedBy dmg -> Some dmg
|None -> None
The usual route in these situations is to define predicates for cases, and then use them for filtering:
type Health = | Healed | Damaged | Died | Revived
let isHealed = function | Healed -> true | _ -> false
let isDamaged = function | Damaged -> true | _ -> false
let isDied = function | Died -> true | _ -> false
let isRevived = function | Revived -> true | _ -> false
let onlyHealed = pub |> Observable.filter isHealed
UPDATE
Based on your comment: if you want to not only filter messages, but also unwrap their data, you can define similar option-typed functions and use them with Observable.choose:
type Health = | HealedBy of int | DamagedBy of int | Died | Revived
let getHealed = function | HealedBy x -> Some x | _ -> None
let getDamaged = function | DamagedBy x -> Some x | _ -> None
let getDied = function | Died -> Some() | _ -> None
let getRevived = function | Revived -> Some() | _ -> None
let onlyHealed = pub |> Observable.choose getHealed // : Observable<int>
let onlyDamaged = pub |> Observable.choose getDamaged // : Observable<int>
let onlyDied = pub |> Observable.choose getDied // : Observable<unit>
You can use reflection to do this I think. This might be pretty slow:
open Microsoft.FSharp.Reflection
type Health =
| Healed of int
| Damaged of int
| Died
| Revived
let GetUnionCaseInfo (x:'a) =
match FSharpValue.GetUnionFields(x, typeof<'a>) with
| case, [||] -> (case.Name, null )
| case, value -> (case.Name, value.[0] )
let health = Event<Health>()
let pub = health.Publish
let only msg pub = pub |> Observable.choose (function
| x when x = msg -> Some(snd (GetUnionCaseInfo(x)))
| x when fst (GetUnionCaseInfo(x)) = fst (GetUnionCaseInfo(msg))
-> Some(snd (GetUnionCaseInfo(x)))
| _ -> None
)
let healed = pub |> only (Healed 0)
let damaged = pub |> only (Damaged 0)
let died = pub |> only Died
let revived = pub |> only Revived
[<EntryPoint>]
let main argv =
let healing = Healed 50
let damage = Damaged 100
let die = Died
let revive = Revived
healed.Add (fun i ->
printfn "We healed for %A." i)
damaged.Add (fun i ->
printfn "We took %A damage." i)
died.Add (fun i ->
printfn "We died.")
revived.Add (fun i ->
printfn "We revived.")
health.Trigger(damage)
//We took 100 damage.
health.Trigger(die)
//We died.
health.Trigger(healing)
//We healed for 50.
health.Trigger(revive)
//We revived.
0 // return an integer exit code
It doesn't feel like you can get your onlyWith function without making some significant changes elsewhere. You can't really generalize the function you pass in for the HealedBy case while staying within the type system (I suppose you could cheat with reflection).
One thing that seems like a good idea would be to introduce a wrapper for the Healed type instead of having a HealedBy type:
type QuantifiedHealth<'a> = { health: Health; amount: 'a }
and then you can have an onlyWith function like this:
let onlyWith msg pub =
pub |> Observable.choose (function
| { health = health; amount = amount } when health = msg -> Some amount
| _ -> None)
I guess you can even go one step further while you're at it, and parameterize your type by both the label and the amount types to make it truly generic:
type Quantified<'label,'amount> = { label: 'label; amount: 'amount }
Edit: To reitarate, you keep this DU:
type Health =
| Healed
| Damaged
| Died
| Revived
Then you make your health event - still a single one - use the Quantified type:
let health = Event<Quantified<Health, int>>()
let pub = health.Publish
You can trigger the event with messages like { label = Healed; amount = 10 } or { label = Died; amount = 0 }. And you can use the only and onlyWith functions to filter and project the event stream to IObservable<unit> and IObservable<int> respectively, without introducing any boilerplate filtering functions.
let healed : IObservable<int> = pub |> onlyWith Healed
let damaged : IObservable<int> = pub |> onlyWith Damaged
let died : IObservable<unit> = pub |> only Died
let revived : IObservable<unit> = pub |> only Revived
The label alone is enough to differentiate between records representing "Healed" and "Died" cases, you no longer need to walk around the payload you would have in your old "HealedBy" case. Also, if you now add a Mana or Stamina DU, you can reuse the same generic functions with Quantified<Mana, float> type etc.
Does this make sense to you?
Arguably it's slightly contrived compared to a simple DU with "HealedBy" and "DamagedBy", but it does optimize the use case that you care for.
Related
I have a DU (don't worry about the specific types, but 2 normal options, or some errors)
type Content =
| Episode of EpisodeJSON.Root
| Program of string
| Errors of List<exn>
I have 2 functions (again the specifics don't matter, just the types):
let getEpisode : _ -> _ -> Async<Content> = ...
let getProgram : _ -> _ -> Async<Content> = ...
I want to write
let getContent : _ -> _ -> Async<Content> =
fun (contentBAPIClient: ContentBAPI.Client) id -> ...
such that, it tries to get the data from getEpisode, if there's an error, it tries to get it from getProgram, and if there is an error again, it returns both errors as the Error DU.
so, this works
let getContent : _ -> _ -> Async<Content> =
fun (contentBAPIClient: ContentBAPI.Client) id ->
async {
let! episodeMaybe = getEpisode contentBAPIClient id
let! programMaybe = getProgram contentBAPIClient id
return
match episodeMaybe with
| Errors xs ->
match programMaybe with
| Errors ys ->
Errors (List.append xs ys)
| program ->
program
| episode ->
episode
}
but I notice that getprogram is being executed even when the data is found via getepisode.
How do i structure this simple function to try getEpisode first, then only try getProgram if episode 'fails'
this works, still feels a bit clunky
let getContent : _ -> _ -> Async<Content> =
fun (contentBAPIClient: ContentBAPI.Client) id ->
async {
let! episodeMaybe = getEpisode contentBAPIClient id
match episodeMaybe with
| Errors xs ->
let! programMaybe = getProgram contentBAPIClient id
match programMaybe with
| Errors ys ->
return Errors (List.append xs ys)
| program ->
return program
| episode ->
return episode
}
This will get a lot easier if you use some library that lets you work with values representing asynchronous computations that may fail, i.e. type Async<Result<'TOk, 'TErrror>>.
A good choice would be something like FsToolkit.ErrorHandling. This defines the asyncResult computation expression as well as a number of primitives that you may find useful.
To get this to compile, I started with this:
#r "nuget: FsToolkit.ErrorHandling"
open FsToolkit.ErrorHandling
type Client = class end
let getEpisode contentBAPIClient id =
async { return Error ["no episode"] }
let getProgram contentBAPIClient id =
async { return Ok "fine" }
Now, to do what (I think) you are doing, you can use:
let getContent (contentBAPIClient: Client) id =
getEpisode contentBAPIClient id
|> AsyncResult.orElseWith (fun e1 ->
getProgram contentBAPIClient id
|> AsyncResult.mapError (fun e2 -> e1 # e2) )
Here, we try to return the episode, but if there is not one, we try to get program (using orElseWith). The only tricky thing is that we need to pass the errors from the first call, which are then appended to (potential) errors of the second call using mapError.
Note that you can also write:
let getContent (contentBAPIClient: Client) id =
asyncResult {
let! ep = getEpisode contentBAPIClient id
and! prog = getProgram contentBAPIClient id
return ep, prog }
This succeeds only if both calls succeed, so it can be quite useful in many scenarios, but does not exactly do the thing that you are trying to do.
I have issues with generation of data within my tests.
testProperty "calculate Operation against different operations should increase major" <| fun operationIdApi operationIdClient summaryApi summaryClient descriptionApi descriptionClient ->
( notAllEqual [
fun () -> assessEquality <| StringEquals(operationIdApi, operationIdClient)
fun () -> assessEquality <| StringEquals(summaryApi , summaryClient)
fun () -> assessEquality <| StringEquals(descriptionApi, descriptionClient)
]) ==> lazy (
let operationClient = createOpenApiOperation operationIdClient summaryClient descriptionClient
let operationAPI = createOpenApiOperation operationIdApi summaryApi descriptionApi
let actual = calculate operationAPI operationClient
Expect.equal actual (Fact.Semver.IncreaseMajor) "return IncreaseMajor"
)
The code that is actually tested is :
semver {
if operationAPI.OperationId<> operationClient.OperationId then yield! IncreaseMajor
if operationAPI.Summary <> operationClient.Summary then yield! IncreaseMajor
}
The test should fail when the data produced is same OperationId, same summary and different description.
But it does not and it led me to create my own generator or at least try to do so:
I wanted my test to be written like this :
testProperty "calculate Operation against different operations should increase major" <| fun (operationId:ElementSet<string>) (summary:ElementSet<string>) ->
Therefore I create a type accordingly:
type ElementSet<'a> =
| Same of 'a
| Different
and a generator for this type :
let setGen<'a> =
Gen.oneof [
gen {
let! v = Arb.generate<'a>
return Same(v)
}
gen { return Different}
]
type ElementSetGenerator =
static member ElementSet() =
Arb.fromGen setGen<'a>
do Arb.register<ElementSetGenerator>() |> ignore
I was then trying to extract the data to construct my object :
let createOpenApiOperation operationId summary=
let pi = OpenApiOperation(OperationId=operationId.Get, Summary=summary.Get)
pi
The Get method did not exist yet so I was about to implement it by adding a member to my ElementSet<'a>:
type ElementSet<'a> =
| Same of 'a
| Different
with member this.Get =
match this with
| Same s -> s
| Different -> Arb.generate<'a>// some random generation here
And this is where I am stuck. I would love to get some randomness here when I extract data. I wonder if this is the correct way to do so, or if I should have answered the problem earlier?
Thanks for your inputs.
I think I found it, the answer was to handle it at the beginning :
let setGen<'a when 'a:equality> =
Gen.oneof [
gen {
let! v = Arb.generate<'a>
return Same(v)
}
gen {
let! x,y =
Arb.generate<'a>
|> Gen.two
|> Gen.filter (fun (a,b)-> a <> b)
return Different(x,y)
}
]
and then to use two getter to access the values :
type ElementSet<'a> when 'a:equality=
| Same of 'a
| Different of 'a*'a
with member this.Fst = match this with | Same s -> s | Different (a, b)-> a
member this.Snd = match this with | Same s -> s | Different (a, b)-> b
this way I can access values within my test:
testProperty "calculate Operation against different operations should increase major" <| fun (operationId:ElementSet<NonWhiteSpaceString>) (summary:ElementSet<NonWhiteSpaceString>) (description:ElementSet<NonWhiteSpaceString>) ->
let operationClient = createOpenApiOperation operationId.Fst summary.Fst description.Fst
let operationAPI = createOpenApiOperation operationId.Snd summary.Snd description.Snd
let actual = calculate operationAPI operationClient
Expect.equal actual (Fact.Semver.IncreaseMajor) "return IncreaseMajor"
for the record I then have the creation of my stub as follows :
let createOpenApiOperation (operationId:NonWhiteSpaceString) (summary:NonWhiteSpaceString) (description:NonWhiteSpaceString)=
let pi = OpenApiOperation(OperationId=operationId.Get, Summary=summary.Get, Description=description.Get)
pi
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.
I am trying figure out if a generic type wrapped in a rop result is a list or not. This is what I tried but I got errors.
let checkType (result : RopResult<'tSuccess, 'errors>) =
match result with
| Success (s, msg) ->
match s with
| :? [] -> // error here
Sample
let isList<'s> () = true
let processList (ls : 'domain list) = true
let processType (s : 'domain) = true
let checkType (result : RopResult<'tSuccess, 'errors>) =
match result with
| Success (s, msg) ->
match s with
| s when isList<s>() -> processList s
| _ -> processType s
| Failure (x) -> false
I'll first explain the technicalities of how to get your code to work, and then try to convince you (as the other folks on this thread) that it may not be the right way to approach your problem.
Firstly, your match statement has a syntax error. You would write the type test and the cast in one swoop as
match s with
| :? List<int> as theIntList -> ...do something with theIntList ...
When you add that to your code, the F# compiler will complain "The runtime coercion or type test ... involves an indeterminate type. ... Further type annotations are needed". Fix that by being more specific about what kind of result your checkType is processing: it is some System.Object instance and the message, so you'd write:
let checkType (result : Result<obj*string, 'errors>) =
match result with
| Success (s, msg) ->
match s with
| :? List<int> as theIntList -> ... do something
Note that you can't change that to a generic thing like List<_> - F# will do the type test and the cast in one go, and would not hence know what to cast to. If you try to, you will see warnings that your List<_> has been inferred to be List<obj>
Having said all that: Using obj is not the idiomatic way to go, as others have tried to point out already. The answers of #robkuz and #TheInnerLight contain all you need: A map function, functions that operate on individual result types, which then becomes nicely composable:
let map f x =
match x with
| Success (s, msg) -> Success (f s, msg)
| Failure f -> Failure f
// This will automatically be inferred to be of type Result<(int list * string), 'a>
let myFirstResult = Success ([1;2], "I've created an int list")
// This will automatically be inferred to be of type Result<(string list * string), 'a>
let mySecondResult = Success (["foo"; "bar"], "Here's a string list")
// Process functions for specific result types. No type tests needed!
let processIntList (l: int list) = Seq.sum l
let processStringList = String.concat "; "
// This will automatically be inferred to be of type Result<(int * string), 'a>
let mapFirst = myFirstResult |> map processIntList
// This will automatically be inferred to be of type Result<(string * string), 'a>
let mapSecond = mySecondResult |> map processStringList
I am not sure if I really understand your problem.
In general if you have some polymorphic type (like your RopResult) and you want to process the polymorphic part of it a good approach in F# would be
to disentagle your code into a wrapper code and a processor code where your processor code is delivered via a higher order function for the processing part.
Example:
type RopResult<'tSuccess, 'tError> =
| Success of 'tSuccess
| Error of 'tError
let checkType (process: 'tSuccess -> 'tResult) (result : RopResult<'tSuccess, 'tError>) =
match result with
| Success s -> process s |> Success
| Error e -> Error e
and
let processList (ls : 'domain list) = true
let processType (s : 'domain) = true
and then you
checkType processList aListWrappedInResult
checkType processType aTypeWrappedInResult
Assuming you wanted to determine whether a supplied value was of a generic list type, you could do this:
let isList value =
let valueType = value.GetType()
match valueType.IsGenericType with
|true -> valueType.GetGenericTypeDefinition() = typedefof<_ list>
|false -> false
Example usage:
isList [5];;
val it : bool = true
isList ["a", "b"];;
val it : bool = true
isList "a";;
val it : bool = false
When working with something like RopResult, or more formally, Either, it's helpful to define the map function. The map function takes a function 'a -> 'b and gives you a function which operates in some elevated domain, e.g. RopResult<'a,'c> -> RopResult<'b,'c>.
This is analogous to List.map : ('a ->'b) -> 'a List -> 'b List.
We define it like this:
let map f v =
match v with
|Success sv -> Success (f sv)
|Failure fv -> Failure (fv)
You can then use isList on RopResults by simply doing:
ropResult |> map isList
Others here are warning you in the comments that there may be potential issues surrounding how you actually process the results once you've determined whether the type is a list or not. Specifically, you will need to ensure that the return types of your processList and processType functions are the same (although I would recommend revisiting the naming of processType and call it processValue instead. Since you are not operating on the type, I think the name is confusing).
Suppose I have this type:
type T = int option
and an observable of that type:
let o : IObservable<T> = // create the observable
I'm looking for a better way to express this:
o.Where(function | None -> false | Some t -> true)
.Select(function | Some t -> t)
An observable that only propagates the Some case.
There are several things that I don't like.
I'm using 2 operators
I'm pattern matching twice
The second pattern matching isn't exhaustive (makes visual studio show a warning and feels odd)
Too much code. The pattern repeats every time I need pattern matching.
Can't you use Observable.choose ? something like this :
let o1 : IObservable<int option> = // ...
let o2 = Observable.choose id o1
If you have a type that is not an option, say:
type TwoSubcases<'a,'b> = | Case1 of 'a | Case2 of 'b
and a partial active pattern:
let (|SecondCase|_|) = function
| Case1 _ -> None
| Case2 b -> Some b
then you can do:
let o1 : IObservable<TwoSubcases<int, float>> = // ...
let o2 : IObservable<float> = Observable.choose (|SecondCase|_|) o1
Thanks to #Lee I came up with a nice solution.
o.SelectMany(function | None -> Observable.Empty() | Some t -> Observable.Return t)
This works for any union type, not only Option.