General higher order function

Is there a reason why I can use a generic function with different type arguments when I pass it as a local value, but not when it is passed as a parameter? For instance:

let f = id let g (x,y) = (fx, fy) g ( 1, '2') 

works fine, but if I try to pass a function as a parameter

 let gf (x,y) = (fx, fy) g id ( 1, '2') 

it fails because it accepts version f <int> and it tries to apply it twice.

I found a workaround, but it forces me to write the function that I pass twice:

 let g f1 f2 (x,y) = (f1 x, f2 y) g id id ( 1, '2') 

This is the second time I am facing this problem.

Why does it behave this way, should it not be the same if the function is a local value or if it is passed as a parameter?

Is there a way to do this without duplicating a function?

Hacking, perhaps using explicit type restrictions, inline magic, quotes?

+6
source share
5 answers

Here is the built-in magic. Take the kvb code and define one gmap function that handles all cases:

 let inline gmap f (x, y) = f $ x, f $ y type One = One with static member ($) (One, x) = 1 // Example1 ConvertAll type Id = Id with static member ($) (Id , x) = x // Example2 PassThrough type SeqSingleton = SeqSingleton with static member ($) (SeqSingleton , x) = seq [x] type ListSingleton = ListSingleton with static member ($) (ListSingleton, x) = [x] type ListHead = ListHead with static member ($) (ListHead, x) = List.head x // Usage let pair1 = gmap One ("test", true) let pair2 = gmap Id ("test", true) let pair3 = gmap SeqSingleton ("test", true) let pair4 = gmap ListSingleton ("test", true) let pair5 = gmap ListHead (["test";"test2"], [true;false]) let pair6 = ("test", true) |> gmap ListSingleton |> gmap ListHead (* results val pair1 : int * int = (1, 1) val pair2 : string * bool = ("test", true) val pair3 : seq<string> * seq<bool> = (["test"], [true]) val pair4 : string list * bool list = (["test"], [true]) val pair5 : string * bool = ("test", true) val pair6 : string * bool = ("test", true) *) 

UPDATE

It is also possible to use the even more general gmap function defined here , then it will also work with n-uples (n <lt, 9).

+7
source

As rkhayrov mentioned in a comment, type inference is not possible when you can have more ranked types. In your example you have

 let gf (x,y) = (fx, fy) 

Here are two possible types for g that are incompatible (written as F # / Haskell hybrid syntax):

  • forall 'b,' c, 'd. ((forall 'a.' a β†’ 'b) β†’' c * 'd β†’' b * 'b)
  • forall 'c, d. (forall 'a.' a β†’ 'a) β†’' c * 'd β†’' c * 'd)

Given the first type, we can call g (fun x -> 1) ("test", true) and get (1,1) . Given the second type, we could call g id ("test", true) and get ("test", true) . None of them are more general than the other.

If you want to use higher ranked types in F #, you can, but you must be explicit and use an intermediate nominal type. Here is one way to encode each of the above features:

 module Example1 = type ConvertAll<'b> = abstract Invoke<'a> : 'a -> 'b let g (f:ConvertAll<'b>) (x,y) = (f.Invoke x, f.Invoke y) //usage let pair = g { new ConvertAll<int> with member __.Invoke(x) = 1 } ("test", true) module Example2 = type PassThrough = abstract Invoke<'a> : 'a -> 'a let g (f:PassThrough) (x,y) = (f.Invoke x, f.Invoke y) //usage let pair = g { new PassThrough with member __.Invoke(x) = x } ("test", true) 
+6
source

I assume this is the expected behavior:

In the first case, you call two different versions of f (one with int , one with char ), in the second case, you use the same for both, and the compiler crosses it (top-bottom, left-right - remember?) To be int->int

The problem is that the generic version will be translated in a specific compiler. I do not see a workaround for this - even with the built-in, but maybe someone can work here here;)

+2
source

I don’t think that even built-in magic will work here, for example, an attempt

  let inline gf (x:^a,y:^b) = (fx,fy);; 

in FSI fails because the compiler reports that ^b should be the same as ^a

Reflecting on this, the failure becomes a little more obvious, since we take the function f and apply it to x, it must have a signature of type ^a -> 'c , which will fail if we try to apply this function to an element of type ^b .

If you are thinking about this problem, I cannot define a function that accepts int->string and expects it to work for char->string without overloads, and you cannot pass overloads as arguments.

0
source

The reason for this is that when f is passed as a parameter to g, then the compiler will try to infer the type f. Based on the use of the function f in the body of the function g, the compiler reports that the function is applied to both x and y, which means that both parameters x and y must be of the same type and this is the type of parameter f. In your code example, you pass x as an integer, and this makes the compiler conclude that y will also be of type integer.

UPDATE:

You can do something like:

 let g (f:obj -> obj) (x : 'a,y : 'b) = (fx :?> 'a ,fy :?> 'b) g id (1,"1") |> printfn "%A" 

The f function must make sure that it returns the same main type that it receives as the obj type

0
source

Source: https://habr.com/ru/post/896028/


All Articles