F# Type Inference Odd Behavior With Records - f#

I am in the process of learning F# and I am trying to wrap my head around as to why, when I override the inferred type with the correct type, that it infers a different type with List.Filter. Code is worth a thousand words:
type Account =
{ account : int
label : string }
type Journal =
{ account : int
period : string
debit : int
credit : int }
let outputJournal (journals: Journal List) (account:Account) =
let filtered = List.filter (fun x -> x.account = account.account) journals
filtered
I need to filter a list of journals against the specified account. However, the outputJournal function outputs an error under the journals argument passed to List.filter. The error is as follows: "Type mismatch. Expecting a 'Account list' but given a 'List Journal'. The type 'Account' does not match the type 'Journal'".
I am confused as to why this is, seeing as I am (or so I thought) clearly trying to filter a list of Journals. Is there a way I can override the type inference to do what I mean or otherwise make my intentions more clear to the compiler (renaming the account field in either record is an option, but I would like to avoid it)?
Much appreciated. Thank you.

Type inference in F# proceeds stricktly top to bottom, left to right.
As a result, when you do x.account the compiler guesses that x is an Account so you get the error message.
To fix this, you can do two things
1) Annotate the type of x
let filtered = List.filter (fun (x:Journal) -> x.account = account.account) journals
2) change the order with a pipe operator (thanks Fyodor)
let filtered = journals |> List.filter (fun x -> x.account = account.account)
Because of the way type inference works, (2) is more common

Related

What are the alternatives to returning a discriminated union from a function?

I'm experimenting with rewriting a complicated piece of code using F#.
For this particular code base, discriminated unions help me a lot, so I'm focusing on using them as much as possible. Specifically, exhaustiveness checks on DUs is helping me avoid lots and lots of bugs.
However, I'm facing a repeating pattern of having to use match ... with to the extent that the clutter in the code is offsetting the benefit I'm getting from exhaustiveness check.
I simplified the pattern I'm dealing with as much as I can and tried to come up with an example that demonstrates the structure of the code I'm writing. The real code base is a lot more complicated and it is in a completely different domain but at the language level, this example represents the issue.
Let's say we want to get some about shoppers based on a classification of shoppers: they're either cat people or dog people. The key thing here is classifying some types (tuples) via DUs.
Here are the domain types:
type PetPerson =
|CatPerson
|DogPerson
type CatFood =
|Chicken
|Fish
type DogFood =
|Burger
|Steak
//some cat food, shopper's age and address
type CatFoodShopper = CatFoodShopper of (CatFood list * int * string)
//some dog food, shopper's age and number of children
type DogFoodShopper = DogFoodShopper of (DogFood list * int * int)
Leaving aside the horrible way we're feeding the poor animals, this domain model needs a function to map PetPerson to CatFoodShopper or DogFoodShopper
At this point, my initial thought is to define a Shopper type, since I cannot return two different types from the following function, based on the results of pattern matching:
type Shopper =
|CatFShopper of CatFoodShopper
|DogFShopper of DogFoodShopper
let ShopperViaPersonality = function
|CatPerson -> CatFShopper (CatFoodShopper ([Chicken;Fish], 32, "Hope St"))
|DogPerson -> DogFShopper (DogFoodShopper ([Burger;Steak], 45, 1))
This solves the problem but then I have lots of places in the code (really a lot) where I end up with a PetPerson and need to get a CatFoodShopper or a DogFoodShopper based on what the PetPerson value is. This leads to unnecessary pattern matching for cases I know I don't have at hand. Here is an example:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
|CatPerson as c -> //how can I void the following match?
match (ShopperViaPersonality c) with
|CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| _ -> failwith "should not have anything but CatFShopper"
|DogPerson as d -> //same as before. I know I'll get back DogFShopper
match (ShopperViaPersonality d) with
|DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
|_ -> failwith "should not have anything but DogFShopper"
As you can see, I have to write pattern matching code even when I know I'll be getting back a particular value. I have no way of concisely associating the CatPerson value to CatFoodShopper value.
In order to improve things at the call site, I considered using F#'s way of mimicking type classes via interfaces, based on lots of example available here:
type IShopperViaPersonality<'T> =
abstract member ShopperOf: PetPerson -> 'T
let mappingInstanceOf<'T> (inst:IShopperViaPersonality<'T>) p = inst.ShopperOf p
let CatPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|CatPerson -> CatFoodShopper ([Chicken;Fish], 32, "Hope St")
| _ -> failwith "This implementation is only for CatPerson"}
let CatPersonToShopper = mappingInstanceOf CatPersonShopper
let DogPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|DogPerson -> DogFoodShopper ([Burger;Steak], 45, 1)
| _ -> failwith "This implementation is only for DogPerson"}
let DogPersonToShopper = mappingInstanceOf DogPersonShopper
So I no longer have a Shopper type to represent both cat food shoppers and dog food shoppers, but instead an interface defines the mapping from PetPerson values to specific shopper types. I also have individual partially applied functions to make things even easier at the call site.
let UsePersonality1 (x:int) (y:PetPerson) =
match y with
|CatPerson as c ->
let (CatFoodShopper (lst,_,_)) = CatPersonToShopper c
"use lst and return string"
|DogPerson as d ->
let (DogFoodShopper (lst,_,_)) = DogPersonToShopper d
"use lst and return string"
This approach works better when using PetPerson values, but I'm now left with the task of defining these individual functions to keep things clean at the call site.
Note that this example is meant to demonstrate the trade off between using a DU and using an interface to return different types based on the classifying DU parameter, if I may call it that. So don't hang up on my meaningless use of return values etc.
My question is: are there any other ways I can accomplish the semantics of classifying a bunch of tuple (or record) types? If you're thinking active patterns, they're not an option because in the real code base the DUs have more than seven cases, which is the limit for active patterns, in case they would be of help. So do I have any other options to improve on the above approaches?
One obvious way to go about this is to call ShopperViaPersonality before matching PetPerson, not after:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match ShopperViaPersonality y with
| CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
Also note that if the sole purpose of ShooperViaPersonality is to support pattern matches, you may be better off making it an active pattern:
let (|CatFShopper|DogFShopper|) = function
| CatPerson -> CatFShopper ([Chicken;Fish], 32, "Hope St")
| DogPerson -> DogFShopper ([Burger;Steak], 45, 1)
Then you can use it like this:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
| CatFShopper (lst,_,_) -> "use lst and return some string "
| DogFShopper (lst, _,_) -> "use lst and return other string"
Logically, an active pattern is pretty much the same as a DU + a function, but on syntactic level, notice how much less nesting there is now.

Formulate an Arbitrary for pair of lists of ints of the same length

I need some help to do this exercise about generators in f#.
The functions
List.zip : ('a list -> 'b list -> ('a * 'b) list)
and
List.unzip : (('a * 'b) list -> 'a list * 'b list)
are inverse of each other, under the condition that they operate on lists
of the same length.
Formulate an Arbitrary for pair of lists of ints of the same length
I tried to write some code:
let length xs ys =
List.length xs = List.length ys
let samelength =
Arb.filter length Arb.from<int list>
It doesn't work, I get a type mismatch at length in samelength:
Error: type mismatch. Expecting a 'a list -> bool but given a 'a list -> 'b list -> bool. The type bool does not match the type 'a list -> bool.
Edit:
As suggested I tried to follow the outline of steps but I'm stuck.
let sizegen =
Arb.filter (fun x -> x > 0) Arb.from<int>
let listgen =
let size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
xs, ys
And of course I have the error type mismatch:
Error: type mistmatch. Expected to have type int but here has type Arbitrary<int>
Edit
I solved the exercise but it seems that my generator is not working when I do the test, it looks another one is invoked.
let samelength (xs, ys) =
List.length xs = List.length ys
let arbMyGen2 = Arb.filter samelength Arb.from<int list * int list>
type MyGeneratorZ =
static member arbMyGen2() =
{
new Arbitrary<int list * int list>() with
override x.Generator = arbMyGen2 |> Arb.toGen
override x.Shrinker t = Seq.empty
}
let _ = Arb.register<MyGeneratorZ>()
let pro_zip (xs: int list, ys: int list) =
(xs, ys) = List.unzip(List.zip xs ys)
do Check.Quick pro_zip
I get the error:
Error:  System.ArgumentException: list1 is 1 element shorter than list2
But why? My generator should only generate two lists of the same length.
If we look at the API reference for the Arb module, and hover over the definition of filter, you'll see that the type of Arb.filter is:
pred:('a -> bool) -> a:Arbitrary<'a> -> a:Arbitrary<'a>
This means that the predicate should be a function of one parameter that returns a bool. But your length function is a function of two parameters. You want to turn it into a function of just one parameter.
Think of it this way. When you write Arb.filter length Arb.from<int list>, what you're saying is "I want to generate an arbitrary int list (just one at a time), and filter it according to the length rule." But the length rule you've written takes two lists and compares their length. If FsCheck generates just a single list of ints, what will it compare its length to? There's no second list to compare to, so the compiler can't actually turn your code into something that makes sense.
What you probably wanted to do (though there's a problem with this, which I'll get to in a minute) was generate a pair of lists, then pass it to your length predicate. I.e., you probably wanted Arb.from<int list * int list>. That will generate a pair of integer lists, completely independent from each other. Then you'll still get a type mismatch in your length function, but you just have to turn its signature from let length xs ys = to let length (xs,ys) =, e.g. have it receive a single argument that contains a pair of lists, instead of having each list as a separate argument. After those tweaks, your code looks like:
let length (xs,ys) =
List.length xs = List.length ys
let samelength =
Arb.filter length Arb.from<int list * int list>
But there are still problems with this. Specifically, if we look at the FsCheck documentation, we find this warning:
When using Gen.filter, be sure to provide a predicate with a high chance of returning true. If the predicate discards 'too many' candidates, it may cause tests to run slower, or to not terminate at all.
This applies to Arb.filter just as much as to Gen.filter, by the way. The way your code currently stands, this is a problem, because your filter will discard most pairs of lists. Since the lists are generated independently of each other, it will most often happen that they have different lengths, so your filter will return false most of the time. I'd suggest a different approach. Since you've said that this is an exercise, I won't write the code for you since you'll learn more by doing it yourself; I'll just give you an outline of the steps you'll want to take.
Generate a non-negative int n that will be the size of both lists in the pair. (For bonus points, use Gen.sized to get the "current size" of the data you should generate, and generate n as a value between 0 and size, so that your list-pair generator, like FsCheck's default list generator, will create lists that start small and slowly grow larger).
Use Gen.listOfLength n to generate both lists. (You could even do Gen.two (Gen.listOfLength n) to easily generate a pair of lists of the same size).
Don't forget to write an appropriate shrinker for a pair of lists, because the exercise wants you to generate a proper Arbitrary, and an Arbitrary that doesn't have a shrinker is not very useful in practice. You can probably do something with Arb.mapFilter here, where the mapper is id because you're already generating lists of matching length, but the filter is your length predicate. Then use Arb.fromGenShrink to turn your generator and shrinker functions into a proper Arbitrary instance.
If that outline isn't enough for you to get it working, ask another question about wherever you're stuck and I'll be glad to help out however I can.
Edit:
In your edit where you're trying to write a list generator using sizegen, you have the following code that doesn't work:
let listgen =
let size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
xs, ys
Here sizegen is a Gen<int> and you're wanting to extract the int parameter from it. There are several ways to do this, but the simplest is the gen { ... } computation expression that FsCheck has provided for us.
BTW, if you don't know what computation expressions are, they're some of F#'s most powerful features: they are highly complex under the hood, but they allow you to write very simple-looking code. You should bookmark https://fsharpforfunandprofit.com/series/computation-expressions.html and https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html and plan to read them later. Don't worry if you don't understand them on your first, or second, or even fifth reading: that's fine. Just keep coming back to these two series of articles, and using computation expressions like gen or seq in practice, and eventually the concepts will become clear. And every time you read these series, you'll learn more, and get closer to that moment of enlightenment when it all "clicks" in your brain.
But back to your code. As I said, you want to use the gen { ... } computation expression. Inside a gen { ... } expression, the let! assignment will "unwrap" a Gen<Foo> object into the generated Foo, which you can then use in further code. Which is what you want to do with your size int. So we'll just wrap a gen { ... } expression around your code, and get the following:
let listgen =
gen {
let! size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
return (xs, ys)
}
Note that I also added a return keyword on the last line. Inside a computation expression, return has the opposite effect of let!. The let! keyword unwraps a value (the type goes from Gen<Foo> to Foo), while the return keyword wraps a value (the type goes from Foo to Gen<Foo>). So that return line takes an int list * int list and turns it into a Gen<int list * int list>. There's some very complex code going on under the hood, but at the surface level of the computation expression, you just need to think in terms of "unwrapping" and "wrapping" types to decide whether to use let! or return.

This expression was expected to have type 'unit' but here has type 'string'

I was attempting to convert this to F# but I can't figure out what I'm doing wrong as the error message (in title) is too broad of an error to search for, so I found no resolutions.
Here is the code:
let getIP : string =
let host = Dns.GetHostEntry(Dns.GetHostName())
for ip in host.AddressList do
if ip.AddressFamily = AddressFamily.InterNetwork then
ip.ToString() // big fat red underline here
"?"
A for loop in F# is for running imperative-style code, where the code inside the for loop does not produce a result but instead runs some kind of side-effect. Therefore, the expression block in an F# for loop is expected to produce the type unit, which is what side-effect functions should return. (E.g., printfn "Something" returns the unit type). Also, there's no way to exit a for loop early in F#; this is by design, and is another reason why a for loop isn't the best approach to do what you're trying to do.
What you're trying to do is go through a list one item at a time, find the first item that matches some condition, and return that item (and, if the item is not found, return some default value). F# has a specialized function for that: Seq.find (or List.find if host.AddressList is an F# list, or Array.find if host.AddressList is an array. Those three functions take different input types but all work the same way conceptually, so from now on I'll focus on Seq.find, which takes any IEnumerable as input so is most likely to be what you need here).
If you look at the Seq.find function's type signature in the F# docs, you'll see that it is:
('T -> bool) -> seq<'T> -> 'T
This means that the function takes two parameters, a 'T -> bool and seq<'T> and returns a 'T. The 'T syntax means "this is a generic type called T": in F#, the apostrophe means that what follows is the name of a generic type. The type 'T -> bool means a function that takes a 'T and returns a Boolean; i.e., a predicate that says "Yes, this matches what I'm looking for" or "No, keep looking". The second argument to Seq.find is a seq<'T>; seq is F#'s shorter name for an IEnumerable, so you can read this as IEnumerable<'T>. And the result is an item of type 'T.
Just from that function signature and name alone, you can guess what this does: it goes through the sequence of items and calls the predicate for each one; the first item for which the predicate returns true will be returned as the result of Seq.find.
But wait! What if the item you're looking for isn't in the sequence at all? Then Seq.find will throw an exception, which may not be the behavior you're looking for. Which is why the Seq.tryFind function exists: its function signature looks just like Seq.find, except for the return value: it returns 'T option rather than 'T. That means that you'll either get a result like Some "ip address" or None. In your case, you intend to return "?" if the item isn't found. So you want to convert a value that's either Some "ip address or None to either "ip address" (without the Some) or "?". That is what the defaultArg function is for: it takes a 'T option, and a 'T representing the default value to return if your value is None, and it returns a plain 'T.
So to sum up:
Seq.tryFind takes a predicate function and a sequence, and returns a 'T option. In your case, this will be a string option
defaultArg takes a 'T option and a default value, and returns a normal 'T (in your case, a string).
With these two pieces, plus a predicate function you can write yourself, we can do what you're looking for.
One more note before I show you the code: you wrote let getIP : string = (code). It seems like you intended for getIP to be a function, but you didn't give it any parameters. Writing let something = (code block) will create a value by running the code block immediately (and just once) and then assigning its result to the name something. Whereas writing let something() = (code block) will create a function. It will not run the code block immediately, but it will instead run the code block every time the function is called. So I think you should have written let getIP() : string = (code).
Okay, so having explained all that, let's put this together to give you a getIP function that actually works:
let getIP() = // No need to declare the return type, since F# can infer it
let isInternet ip = // This is the predicate function
// Note that this function isn't accessible outside of getIP()
ip.AddressFamily = AddressFamily.InterNetwork
let host = Dns.GetHostEntry(Dns.GetHostName())
let maybeIP = Seq.tryFind isInternet host.AddressList
defaultArg maybeIP "?"
I hope that's clear enough; if there's anything you don't understand, let me know and I'll try to explain further.
Edit: The above has one possible flaw: the fact that F# may not be able to infer the type of the ip argument in isInternet without an explicit type declaration. It's clear from the code that it needs to be some class with an .AddressFamily property, but the F# compiler can't know (at this point in the code) which class you intend to pass to this predicate function. That's because the F# compiler is a single-pass compiler, that works its way through the code in a top-down, left-to-right order. To be able to infer the type of the ip parameter, you might need to rearrange the code a little, as follows:
let getIP() = // No need to declare the return type, since F# can infer it
let host = Dns.GetHostEntry(Dns.GetHostName())
let maybeIP = host.AddressList |> Seq.tryFind (fun ip -> ip.AddressFamily = AddressFamily.InterNetwork)
defaultArg maybeIP "?"
This is actually more idiomatic F# anyway. When you have a predicate function being passed to Seq.tryFind or other similar functions, the most common style in F# is to declare that predicate as an anonymous function using the fun keyword; this works just like lambdas in C# (in C# that predicate would be ip => ip.AddressFamily == AddressFamily.InterNetwork). And the other thing that's common is to use the |> operator with things like Seq.tryFind and others that take predicates. The |> operator basically* takes the value that's before the |> operator and passes it as the last parameter of the function that's after the operator. So foo |> Seq.tryFind (fun x -> xyz) is just like writing Seq.tryFind (fun x -> xyz) foo, except that foo is the first thing you read in that line. And since foo is the sequence that you're looking in, and fun x -> xyz is how you're looking, that feels more natural: in English, you'd say "Please look in my closet for a green shirt", so the concept "closet" comes up before "green shirt". And in idiomatic F#, you'd write closet |> Seq.find (fun shirt -> shirt.Color = "green"): again, the concept "closet" comes up before "green shirt".
With this version of the function, F# will encounter host.AddressList before it encounters fun ip -> ..., so it will know that the name ip refers to one item in host.AddressList. And since it knows the type of host.AddressList, it will be able to infer the type of ip.
* There's a lot more going on behind the scenes with the |> operator, involving currying and partial application. But at a beginner level, just think of it as "puts a value at the end of a function's parameter list" and you'll have the right idea.
In F# any if/else/then-statement must evaluate to the same type of value for all branches. Since you've omitted the else-branch of the expression, the compiler will infer it to return a value of type unit, effectively turning your if-expression into this:
if ip.AddressFamily = AddressFamily.InterNetwork then
ip.ToString() // value of type string
else
() // value of type unit
Scott Wlaschin explains this better than me on the excellent F# for fun and profit.
This should fix the current error, but still won't compile. You can solve this either by translating the C#-code more directly (using a mutable variable for the localIP value, and doing localIP <- ip.ToString() in your if-clause, or you could look into a more idiomatic approach using something like Seq.tryFind.

what is use cases of F# explicit type parameters?

As I know, explicit type parameters in value definitions is a one way to overcome "value restriction" problem.
Is there another cases when I need to use them?
Upd: I mean "explicitly generic constructs", where type parameter is enclosed in angle brackets, i.e.
let f<'T> x = x
Polymorphic recursion is another case. That is, if you want to use a different generic instantiation within the function body, then you need to use explicit parameters on the definition:
// perfectly balanced tree
type 'a PerfectTree =
| Single of 'a
| Node of ('a*'a) PerfectTree
// need type parameters here
let rec fold<'a,'b> (f:'a -> 'b) (g:'b->'b->'b) : 'a PerfectTree -> 'b = function
| Single a -> f a
| Node t -> t |> fold (fun (a,b) -> g (f a) (f b)) g
let sum = fold id (+)
let ten = sum (Node(Node(Single((1,2),(3,4)))))
This would likely be rare, but when you want to prevent further generalization (§14.6.7):
Explicit type parameter definitions on value and member definitions can affect the process of type inference and generalization. In particular, a declaration that includes explicit generic parameters will not be generalized beyond those generic parameters. For example, consider this function:
let f<'T> (x : 'T) y = x
During type inference, this will result in a function of the following type, where '_b is a type inference variable that is yet to be resolved.
f<'T> : 'T -> '_b -> '_b
To permit generalization at these definitions, either remove the explicit generic parameters (if they can be inferred), or use the required number of parameters, as the following example shows:
let throw<'T,'U> (x:'T) (y:'U) = x
Of course, you could also accomplish this with type annotations.
Most obvious example: write a function to calculate the length of a string.
You have to write:
let f (a:string) = a.Length
and you need the annotation. Without the annotation, the compiler can't determine the type of a. Other similar examples exist - particularly when using libraries designed to be used from C#.
Dealing with updated answer:
The same problem applies - string becomes A<string> which has a method get that returns a string
let f (a:A<string>) = a.get().Length

Playing with F# types and getting lost

I have been doing a little reading on F# and decided to give it a try. I started with a somewhat involved example and I came up with and got lost immediately. I wonder if someone can share some thoughts on it.
I wanted to write a method called ComparisonStrategy<'T> that returns an instance of IEqualityComparer<'T>. It that takes in a variable length of ComparisonWhichAndHow<'T> instances. The type ComparisonWhichAndHow<'T> can either be:
One function of type ('T -> *), which is a method that selects a single field to compare
a 2-tuple of ('T -> 'U, IEqualityComparer<'U>) if you don't want the default Equals or GetHashCode to be used on 'U.
I have tried to draw this down on visual studio for a while now, but I can't even get the function declaration part right. I am somewhat positive I would be able to implement the method body if I can just get past this, but seems like I can't.
Edited:
This is the code I have tried so far.
I am trying to achieve the 2 following things.
Come up with a generic way of generating a equal method for each object.
Sometimes some business operations might require comparing some fields of 2 objects, and some fields of their children. Not a full comparison. I am trying to make writing those code more concise and simple
This is what I have so far:
module Failed =
open System.Collections.Generic
open System
type ComparsionOption<'T, 'U> =
| Compare of ('T -> 'U)
| CompareWith of ('T -> 'U) * IEqualityComparer<'U>
// TO USE: [<ParamArray>]
// TODO: this method returns a dummy for now
let CompareStrategy (opts : ComparsionOption<'T, _> array) =
EqualityComparer<'T>.Default
// How it's used
type Person(name : string, id : Guid) =
member this.Name = name
member this.ID = id
let fullCompare : EqualityComparer<Person> =
CompareStrategy [|Compare(fun (p : Person) -> p.Name);
CompareWith((fun (p : Person) -> p.ID), EqualityComparer<Guid>.Default)|] // error here
Looking at the problem from another perspective, it looks like you want to be able to construct objects that perform comparison in two different ways (which you specified) and then compose them.
Let's start by looking at the two ways to build an object that performs comparison. You can represent both by IEqualityComparer<'T>. The first one takes a function 'T -> Something and performs comparison on the result. You can define a function like this:
/// Creates a comparer for 'T values based on a predicate that
/// selects some value 'U from any 'T value (e.g. a field)
let standardComparer (f:'T -> 'U) =
{ new IEqualityComparer<'T> with
member x.Equals(a, b) =
(f a).Equals(b) // Call 'f' on the value & test equality of results
member x.GetHashCode(a) =
(f a).GetHashCode() } // Call 'f' and get hash code of the result
The function is 'T -> 'U using F# generics, so you can project fields of any type (the type just has to be comparable). The second primitive function also takes 'T -> 'U, but it also takes a comparer for 'U values instead of using the default:
/// Creates a comparer for 'T values based on a predicate & comparer
let equalityComparer (f:'T -> 'U) (comparer:IEqualityComparer<'U>) =
{ new IEqualityComparer<'T> with
member x.Equals(a, b) =
comparer.Equals(f a, f b) // Project values using 'f' and use 'comparer'
member x.GetHashCode(a) =
comparer.GetHashCode(f a) } // Similar - use 'f' and 'comparer'
Now you're saying that you'd like to take a sequence of values created in one of the two above ways to build a single comparison strategy. I'm not entirely sure what you mean by that. Do you want two objects to be equal when all the specified comparers report them as equal?
Assuming that is the case, you can write a function that combines two IEqualityComparer<'T> values and reports them as equal when both comparers report them as equal like this:
/// Creates a new IEqualityComparer that is based on two other comparers
/// Two objects are equal if they are equal using both comparers.
let combineComparers (comp1:IEqualityComparer<'T>) (comp2:IEqualityComparer<'T>) =
{ new IEqualityComparer<'T> with
member x.Equals(a, b) =
comp1.Equals(a, b) && comp2.Equals(a, b) // Combine results using &&
member x.GetHashCode(a) =
// Get hash code of a tuple composed by two hash codes
hash (comp1.GetHashCode(a), comp2.GetHashCode(a)) }
This is essenitally implementing all the functionality that you need. If you have some object Person, you can construct comparer like this:
// Create a list of primitive comparers that compare
// Name, Age and ID using special 'idComparer'
let comparers =
[ standardComparer (fun (p:Person) -> p.Name);
standardComparer (fun (p:Person) -> p.Age);
equalityComparer (fun (p:Person) -> p.ID) idComparer ]
// Create a single comparer that combines all of them...
let comparePerson = comparers |> Seq.reduce combineComparers
You could wrap this in a more object-oriented interface using overloaded methods etc., but I think that the above sample shows all the important components that you'll need in the solution.
BTW: In the example, I was using F# object expressions to implement all the functions.

Resources