Restrict generic type to inherit generic type in f #

let mapTuple f (a,b) = (f a, f b)

      

I am trying to create a function that applies a function f

to both elements in a tuple and returns the result as a tuple. F # type inference says it mapTuple

returns a tuple 'b*'b

. It also assumes that a

both b

are of the same type.

I want to be able to pass two different types as parameters. You think it won't work because they both need to be passed as parameters to f

. So I thought that if they inherited from the same base class, it might work.

Below is a less general function for what I am trying to accomplish.

let mapTuple (f:Map<_,_> -> Map<'a,'b>) (a:Map<int,double>,b:Map<double, int>) = (f a, f b)

      

However, it gives a type mismatch error.

How should I do it? Is this what I am trying to make even possible in F #?

+3


source to share


3 answers


At the risk of stating the obvious, a good enough solution might be to have mapTuple

one that takes two functions instead of one:

let mapTuple fa fb (a, b) = (fa a, fb b)

      



If your original f

is generic, passing it like fa

that fb

will give you two concrete function instances with the types you are looking for. In the worst case, you just need to pass the same function twice when a

and b

are of the same type.

+2


source


Gustavo is mostly right; what you are asking for requires a higher rank. Nevertheless,

  • .NET (and by extension F #) supports (encoding) higher-ranked types.
  • Even in Haskell, which maintains a "good" way of expressing such types (after you've included the correct extension), they won't be deduced for your example.

Digging at point 2 might be valuable: given map f a b = (f a, f b)

why doesn't Haskell make a more general type than map :: (t1 -> t) -> t1 -> t1 -> (t, t)

? The reason is that when you include higher-ranked types, it is usually not possible to infer one "most general" type for a given expression. Indeed, there are many possible higher rank signatures for map

given its simple definition above:

  • map :: (forall t. t -> t) -> x -> y -> (x, y)

  • map :: (forall t. t -> z) -> x -> y -> (z, z)

  • map :: (forall t. t -> [t]) -> x -> y -> ([x], [y])



(plus infinitely many). But note that they are all incompatible with each other (none is more general than the other). Given the first, you can call map id 1 'c'

, given the second, which you can call map (\_ -> 1) 1 'c'

, and given the third, you can call map (\x -> [x]) 1 'c'

, but those arguments are only valid for each of these types, not the other.

Thus, even in Haskell, you need to specify the specific polymorphic signature you want to use - this might come as a surprise if you are moving from a more dynamic language. In Haskell, this is relatively clean (the syntax is what I used above). However, in F # you will have to go through an extra hoop: there is no pure syntax for the "forall" type, so you will have to create an additional nominal type instead. For example, to code the first type above in F #, I would write something like this:

type Mapping = abstract Apply : 'a -> 'a

let map (m:Mapping) (a, b) = m.Apply a, m.Apply b

let x, y = map { new Mapping with member this.Apply x = x } (1, "test")

      

Note that unlike Gustavo's suggestion, you can define the first argument map

as an expression (instead of forcing it to be a member of some particular type). On the other hand, there are clearly a lot more templates out there than would be ideal ...

+5


source


This issue has to do with rank-n types that are supported in Haskell (via extensions), but not in the .NET type system.

One way to find this constraint is to pass a type using one method instead of a function, and then define a built-in map function with static constraints, for example, let's say I have some common functions: toString

and toOption

, and I want to be able to map them to a tuple of different types:

type ToString = ToString with static member inline ($) (ToString, x) = string x
type ToOption = ToOption with static member        ($) (ToOption, x) = Some x

let inline mapTuple f (x, y) = (f $ x, f $ y)

let tuple1 = mapTuple ToString (true, 42)
let tuple2 = mapTuple ToOption (true, 42)

// val tuple1 : string * string = ("True", "42")
// val tuple2 : bool option * int option = (Some true, Some 42)

      

toString

returns the same type, but works on arbitrary types. toOption

will return two generics of different types.

By using the binary operator, type inference creates static constraints for you, and I use $

because in Haskell it means application, so the nice thing is that for haskellers f $ x

read, x is already applied to f.

+4


source







All Articles