I'm just looking at my code, and I'm not certain if there is a way to generalize these code blocks:
The problem that I have is that I'm relatively new to functional paradigm / F#, and I'm not sure if there is a way of processing Union Cases beyond pattern matching / discrimination. This example may ask the question of why these are different cases if they follow the same logic, and the answer is that really I'm trying to use different Cases in a union for Domain Description purposes; I am still trying to find the balance in aiming to create some generalized modules, but make use of Type-safety for domain particularities. In this example, I am bind all possible fields for this Domain usage from a generalized Input module.
Essentially, is there some way of using the Particular Case as a parameter? How would it be passed/piped in?
let updateUserRegistrationInputFieldValue changeValue fieldValue =
match changeValue with
| ID cv -> match fieldValue with | ID _ -> ID cv | _ -> fieldValue
| Name cv -> match fieldValue with | Name _ -> Name cv | _ -> fieldValue
| Email cv -> match fieldValue with | Email _ -> Email cv | _ -> fieldValue
| Phone cv -> match fieldValue with | Phone _ -> Phone cv | _ -> fieldValue
let validateUserRegistrationInputField requirementsSelector fieldValue =
match fieldValue with
| ID fv -> validateInputValue fv (requirementsSelector fv)
| Name fv -> validateInputValue fv (requirementsSelector fv)
| Email fv -> validateInputValue fv (requirementsSelector fv)
| Phone fv -> validateInputValue fv (requirementsSelector fv)
This is is not critical, as it works and I also believe there is some value in the additional description of fields in these domain particular functions. However, I'm more just trying to see if there is a better practice then what I'm currently doing. The goal is to potentially allow me to build some more generalized functions that I could then keep Domain Description more exclusively in DU Type Definitions but that work with functions that are general.
Any thoughts or input are appreciated...
UPDATE: Fixed some typos, and clarified a little bit more of my goal. Also added the DU Definition that is used in the initial example.
type UserRegistrationInputValue =
| ID of InputStringValue
| Name of InputStringValue
| Email of InputStringValue
| Phone of InputStringValue
This type is used for both the 'changeValue' and 'fieldValue' parameters in the first function, while it is the fieldValue in the second one. (requirementsSelector is a function that takes this DU Type as a parameter)
The definition of the DU speaks to the level of Domain specificity I'm aiming to achieve, while relying on my generalized modules for processing/behavior. The named/defined functions in the example are being kept in my Domain, because they use the Domain specific types; however, I see, on my current course, repeating this code a lot for the different Domain definitions...
You can put multiple items into a tuple before pattern matching and match on them both at once. This gives you an "AND" pattern match:
let updateUserRegistrationInputFieldValue changeValue fieldValue =
match changeValue, fieldValue with
| ID cv, ID _ -> ID cv // matches when changeValue is ID **AND** fieldValue is ID
| Name cv, Name _ -> Name cv
| Email cv, Email _ -> Email cv
| Phone cv, Email _ -> Phone cv
| _ -> fieldValue
You can put vertical pipes | in pattern matches as an "OR" pattern match:
let validateUserRegistrationInputField requirementsSelector fieldValue =
match fieldValue with
| ID fv | Name fv | Email fv | Phone fv ->
validateInputValue fv (requirementsSelector fv)
If you do this then all of the patterns being "OR"ed need to bind values with the same names and types. In this case, fv must be the same type for all of the DU cases.
Related
I have the following active pattern:
let (|IgnoreCase|_|) (arg: string) (input: string)
if String.Equals(argument, input, StringComparison.OrdinalIgnoreCase)
then Some()
else None
Normally, you would have to use this pattern individually for each case and do something like this:
function
| IgnoreCase "string1"
| IgnoreCase "string2"
What I would like to do is be able to use other matching expressions with the active pattern, for example:
function
| IgnoreCase ("string1" | "string2") // OR pattern
| IgnoreCase ("string1" & "string2") // AND pattern
That returns the error "Invalid argument to parameterized pattern label". I know you can do that using discriminated unions, so it seems like it should be possible using active patterns.
The way you've written the active pattern, it takes a string argument and checks if the input matches the string - this way, there is no way of turning the argument into a pattern itself.
In this particular case, & does not really make sense - but if you just wanted to support |, you could pass a list of arguments:
let (|IgnoreCase|_|) (args: string list) (input: string) =
if args |> List.exists (fun arg ->
String.Equals(arg, input, StringComparison.OrdinalIgnoreCase))
then Some() else None
function
| IgnoreCase ["string1"; "string2"] -> 1
| _ -> 0
If you wanted something more sophisticated, you would need to structure the logic so that the active pattern returns something that can be further matched. One option might be to have pattern that turns the input into lower-case, because then you can specify composed pattern for the lower-cased string:
let (|AsLowerCase|_|) (input: string) =
Some(input.ToLowerInvariant())
function
| AsLowerCase("string1" | "string2") -> 1
| AsLowerCase("string1" & "string2") -> 1
| _ -> 0
This makes the syntax with & syntactically valid, but it will never match. You could however have other patterns like StartsWith and EndsWith so this would then make sense, e.g. to write AsLowerCase(StartsWith "foo" & EndsWith "bar")
Let's say there are two unions where one is a strict subset of another.
type Superset =
| A of int
| B of string
| C of decimal
type Subset =
| A of int
| B of string
Is it possible to automatically upcast a Subset value to Superset value without resorting to explicit pattern matching? Like this:
let x : Subset = A 1
let y : Superset = x // this won't compile :(
Also it's ideal if Subset type was altered so it's no longer a subset then compiler should complain:
type Subset =
| A of int
| B of string
| D of bool // - no longer a subset of Superset!
I believe it's not possible to do but still worth asking (at least to understand why it's impossible)
WHY I NEED IT
I use this style of set/subset typing extensively in my domain to restrict valid parameters in different states of entities / make invalid states non-representable and find the approach very beneficial, the only downside is very tedious upcasting between subsets.
Sorry, no
Sorry, but this is not possible. Take a look at https://fsharpforfunandprofit.com/posts/fsharp-decompiled/#unions — you'll see that F# compiles discriminated unions to .NET classes, each one separate from each other with no common ancestors (apart from Object, of course). The compiler makes no effort to try to identify subsets or supersets between different DUs. If it did work the way you suggested, it would be a breaking change, because the only way to do this would be to make the subset DU a base class, and the superset class its derived class with an extra property. And that would make the following code change behavior:
type PhoneNumber =
| Valid of string
| Invalid
type EmailAddress =
| Valid of string
| ValidButOutdated of string
| Invalid
let identifyContactInfo (info : obj) =
// This came from external code we don't control, but it should be contact info
match (unbox obj) with
| :? PhoneNumber as phone -> // Do something
| :? EmailAddress as email -> // Do something
Yes, this is bad code and should be written differently, but it illustrates the point. Under current compiler behavior, if identifyContactInfo gets passed a EmailAddress object, the :? PhoneNumber test will fail and so it will enter the second branch of the match, and treat that object (correctly) as an email address. If the compiler were to guess supersets/subsets based on DU names as you're suggesting here, then PhoneNumber would be considered a subset of EmailAddress and so would become its base class. And then when this function received an EmailAddress object, the :? PhoneNumber test would succeed (because an instance of a derived class can always be cast to the type of its base class). And then the code would enter the first branch of the match expression, and your code might then try to send a text message to an email address.
But wait...
What you're trying to do might be achievable by pulling out the subsets into their own DU category:
type AorB =
| A of int
| B of string
type ABC =
| AorB of AorB
| C of decimal
type ABD =
| AorB of AorB
| D of bool
Then your match expressions for an ABC might look like:
match foo with
| AorB (A num) -> printfn "%d" num
| AorB (B s) -> printfn "%s" s
| C num -> printfn "%M" num
And if you need to pass data between an ABC and an ABD:
let (bar : ABD option) =
match foo with
| AorB data -> Some (AorB data)
| C _ -> None
That's not a huge savings if your subset has only two common cases. But if your subset is a dozen cases or so, being able to pass those dozen around as a unit makes this design attractive.
Let us have a type definition for a tree with several types of binary nodes, among other types of nodes, i.e.
type Tree =
| BinaryNodeA of Tree * Tree
| BinaryNodeB of Tree * Tree
| [Other stuff...]
I want to manipulate this tree using a recursive function that could, e.g., swap subnodes of any kind of binary node (by constructing a new node). The problem that is driving me crazy: How to match all BinaryNodes so that Node flavor becomes "a parameter" so as to have generic swap that can be applied to any BinaryNode flavor to return swapped node of that flavor?
I know how to match all Trees that are BinaryNodes by using an active pattern:
let (|BinaryNode|_|) (tree : Tree) =
match tree with
| BinaryNodeA _ | BinaryNodeB _ -> Some(tree)
| _ -> None
But that's not good enough because the following does not seem achievable:
match tree with
| [cases related to unary nodes..]
| BinaryNode a b -> BinaryNode b a
In other words, I have not found way to use BinaryNode flavor as if it were parameter like a and b. Instead, it seems I have to match each BinaryNode flavor separately. This could have practical significance if there were large number of binary node flavors. Type Tree is AST for Fsyacc/Fslex-generated parser/lexer, which limits options to restructure it. Any ideas?
You just need to change the definition of your active pattern:
type Flavor = A | B
let (|BinaryNode|_|) (tree : Tree) =
match tree with
| BinaryNodeA(x,y) -> Some(A,x,y)
| BinaryNodeB(x,y) -> Some(B,x,y)
| _ -> None
let mkBinaryNode f t1 t2 =
match f with
| A -> BinaryNodeA(t1,t2)
| B -> BinaryNodeB(t1,t2)
Then you can achieve what you want like this:
match tree with
| [cases related to unary nodes..]
| BinaryNode(f,t1,t2) -> mkBinaryNode f t2 t1
But if this is a common need then it might make sense to alter the definition of Tree to include flavor instead of dealing with it using active patterns.
I am a new programmer in general, and as well to F#. I've ran into this particular problem several times, and have yet to solve it efficiently in my opinion. Here is the problem:
I have these example types:
type Retail1 = | Fashion | Auto | Sports
type Wholesale1 = | Fashion | Auto | Sports
type Events1 = | Wedding | Birthday
type Product =
| Retail of Retail1 | Wholesale of Wholesale1 | Events of Events1
| NoProduct
I want to convert the possibility of the first three types to the Product type via a function:
let convertToProduct (retail: Retail1 option)
(wholesale: Wholesale1 option) (events: Events1 option) =
// convert to Product here
if retail.IsSome then Retail retail
elif wholesale.IsSome then Wholsale wholseale
elif events.IsSome then Events events
else NoProduct
The way that I have handled it in the pass is just to chain a long if elif statement together to check for each condition and return the final type of Product, but this does not feel correct, or at the very least idiomatic to F#. What would be the recommended approach to this problem?
How about something like this:
let convertToProduct (retail: Retail1 option) (wholesale: Wholesale1 option) (events: Events1 option) =
match (retail, wholesale, events) with
|Some rt, None, None -> Retail rt
|None, Some wh, None -> Wholesale wh
|None, None, Some ev -> Events ev
|_ -> NoProduct
This exploits the fact that if you convert all the arguments into a tuple, you can do pretty concise pattern matching on the result.
Pattern matching is actually extremely powerful, you can find more details about the types of pattern matching you can perform in the MSDN documentation.
To check if a variable a has type T, I can use
if (a :? T)
But what if T is a universally-defined and has several subtypes T1,T2,T3 in which I'm only interested in checking if a has type T1? For example:
type Uni = Iu of int
| Flu of float
| Su of string
| Bu of bool
| Lu of Uni List
| Fu of (Uni -> Uni)
How can I check if a variable aString has type Su?
Help is appreciated.
You can use pattern matching with the underscore (wildcard) pattern to ignore the float value:
let isSu = function Su _ -> true | _ -> false
Although you can think of discriminated unions like Uni as class hierarchies with some base class and a number of subtypes, I do not think this is very helpful in this case. It is much better to think of them as types which can have one of several possible representations. So instead of "checking for a subtype" you are really just checking if a value is represented using the Su case.