Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to make the following example fully polymorphic in F#?

Tags:

f#

type Mul = Mul with member inline __.Op(a: ^a,b: ^a) = a*b
type Div = Div with member inline __.Op(a: ^a,b: ^a) = a/b
type Add = Add with member inline __.Op(a: ^a,b: ^a) = a+b
type Sub = Sub with member inline __.Op(a: ^a,b: ^a) = a-b

let inline op x a b =
    (^a: (member Op: ^b * ^b -> ^b) x,a,b)

let inline tup2 a b c d = op Mul a b, op Mul c d
let inline tup2' f a b c d = op f a b, op f c d

let a = tup2 1 2 3.0f 4.0f
//let b = tup2' Mul 1 2 3.0f 4.0f //Gives a type error.

I am wondering if there is a way to make the types do what I want in the example above or if I have finally reached the limitation of F#'s type system. Actually, there is a way to make the above work and that is to put all the types into one DU and then pattern match on the DU type like the following:

type Operation = 
    | Mul 
    | Add
    | Sub
    | Div
    member inline t.Op a b = 
        match t with 
        | Mul -> a * b
        | Add -> a + b
        | Sub -> a - b
        | Div -> a / b
let inline map' (f: Operation) a b c d =
    (f.Op a b, f.Op c d)
map' Mul 1 2 3.0f 4.0f

But assuming the first example worked, it would be a more dynamic solution. It is a pity something like passing a higher order function by name inside an argument and having it inlined on the spot to make it generic is not possible.

like image 254
Marko Grdinić Avatar asked Mar 11 '23 06:03

Marko Grdinić


1 Answers

This limitation of most modern type systems is well explained in kvb's answer to this question.

Here's a workaround, based on the hack suggested there. In fact it's very similar to your code but less verbose.

type Mul = Mul with static member inline ($) (Mul, a: ^a) = fun (b: ^a) -> a*b
type Div = Div with static member inline ($) (Div, a: ^a) = fun (b: ^a) -> a/b
type Add = Add with static member inline ($) (Add, a: ^a) = fun (b: ^a) -> a+b
type Sub = Sub with static member inline ($) (Sub, a: ^a) = fun (b: ^a) -> a-b

let inline tup2' f a b c d = (f $ a) b, (f $ c) d

let b = tup2' Mul 1 2 3.0f 4.0f

The idea is that instead of defining a function you define a type with a single method (which you already did) in this case it will be an operator which would mean apply.

So instead of doing f x you will write f $ x.

UPDATE

As said before, your code is not far from the solution suggested in that answer. Here's a working example which is even closer to your original code:

type Mul = Mul with static member inline Op(Mul, a: ^a,b: ^a) = a*b
type Div = Div with static member inline Op(Div, a: ^a,b: ^a) = a/b
type Add = Add with static member inline Op(Add, a: ^a,b: ^a) = a+b
type Sub = Sub with static member inline Op(Sub, a: ^a,b: ^a) = a-b

let inline op x a b = ((^a or ^b): (static member Op: ^a * ^b  * ^b -> ^b) (x, a, b))

let inline tup2 a b c d = op Mul a b, op Mul c d
let inline tup2' f a b c d = op f a b, op f c d

let a = tup2 1 2 3.0f 4.0f
let b = tup2' Mul 1 2 3.0f 4.0f //Gives NO type error.

So that's basically your original code but using static methods and using an or in the constraints. By doing this the compiler doesn't solve the constraint early and so it works.

I used the operator because it's less verbose and in this case I like how it reads, since Haskell $ means function application.

like image 178
Gus Avatar answered May 10 '23 07:05

Gus