jqdc2224
jqdc2224

Reputation: 119

Set.union F# trouble

I have a Set<String*String>, and I'm trying to write a function that takes all the elements of that Set and return a Set<String>. My idea is to use Set.fold, and have an empty set accumulator and take the union of the two sets, but I'm running into problems. Here is the code:

type Chart = Set<Country*Country>;;

let countriesInChart (chart : Chart) =
  Set.fold(fun (x,y) set -> Set.union [x;y] set ) chart []

But I get this error

        Set.fold(fun (x,y) set -> Set.union [x;y] set ) chart [];;
  --------------------------------^^^^^^^^^^^^^^^^^^^
error FS0001: This expression was expected to have type
    'a * 'b    
but here has type
    Set<'c>    

Upvotes: 0

Views: 444

Answers (2)

TeaDrivenDev
TeaDrivenDev

Reputation: 6629

Look at your types and function signatures.

Set.fold takes a 'State -> 'T -> 'State as the folder function. 'State is the type that you're folding into and that will be the eventual return value, so in this case, you want it to be of type Set<Country>.

That means your lambda can't be right, because the first argument is a tuple. So we should probably switch the arguments of that lambda:

let countriesInChart (chart : Chart) =
    Set.fold(fun set (x,y) -> Set.union [x;y] set ) chart []

Compiling that gives us

(96,39): error FS0001: This expression was expected to have type
    Set<'a>
but here has type
    'b list

(96,39) in this case is the Set.union function, and of course that is not used correctly, because it requires two sets, but we're passing it one set and a list. We can create a set from the list using Set.ofList:

let countriesInChart (chart : Chart) =
    Set.fold(fun set (x,y) -> [x; y] |> Set.ofList |> Set.union set) chart []

Again, we're getting a different error, so we're probably making progress:

(96,80): error FS0001: This expression was expected to have type
    Set<(Country * Country) * (Country * Country)>
but here has type
    'a list

(96,80) is the empty list at the end of the line - and of course, that's wrong, because the third argument to Set.fold needs to be Set<'T>. The set replacement for an empty list would be Set.empty, so let's go with that:

let countriesInChart (chart : Chart) =
    Set.fold(fun set (x,y) -> [x; y] |> Set.ofList |> Set.union set) chart Set.empty

It compiles! But as you found, it returns Set<Country * Country> instead of just Set<Country>.

Cases like this are when type inference makes it a little harder to see what's going on, so we should go ahead and add type annotations where we know exactly what the types need to be. The most obvious place is the return type of the function:

let countriesInChart (chart : Chart) : Set<Country> =
    Set.fold(fun set (x,y) -> [x; y] |> Set.ofList |> Set.union set) chart Set.empty

Now the error is:

(96,74): error FS0001: Type mismatch. Expecting a
    Set<Country>    
but given a
    Chart    
The type 'Country' does not match the type 'Country * Country'

That error is for the second argument of Set.fold, and the reason is that once again, the arguments are in the wrong order. The signature of Set.fold is ('State -> 'T -> 'State) -> 'State -> Set<'T> -> 'State. If we look at what we already have, 'State in this case is Set<Country>, and 'T is Country * Country. That means Set.empty needs to be the second and chart the last argument, and so we arrive at

let countriesInChart (chart : Chart) =
    Set.fold(fun set (x,y) -> [x; y] |> Set.ofList |> Set.union set) Set.empty chart

The most important rule of functional programming is this: Let the types guide you! ;-)

Upvotes: 4

egerhard
egerhard

Reputation: 1109

Try this one:

let f (chart: Chart) =
  Set.fold (fun (x:Set<string>) (a,b) -> x |> Set.add a |> Set.add b) Set.empty chart

I'm not sure if the type annotation is necessary but it does force the output to be a Set<string>.

Upvotes: 0

Related Questions