0

I am encountering this Function type that I need to pass to a JQueryAnimationOptions object. I usually would pass a lambda to callbacks but these seem to be incompatible. I looked up every sample I could find in the FunScript repo. and couldn't find any workaround.

It also said the the Function is actually an interface (for what?) when used as a return statement Error: Invalid use of interface type.

So how to pass a callback argument with this Function type?

enter image description here

the code:

[<FunScript.JS>]
module Main

open FunScript
open FunScript.TypeScript

let sayHelloFrom (name:string) = 
    Globals.window.alert("Hello, " + name)

let jQuery (selector:string) = Globals.jQuery.Invoke selector

let main() = 
    let options = createEmpty<JQueryAnimationOptions>()
    options.duration <- 3000
    options.complete <- (fun _ -> sayHelloFrom("F#")) 
    let properties = createEmpty<Object>()
    properties.Item("opacity") <- 1
    let mainContent = jQuery "#mainContent"
    mainContent.animate(properties, options) |> ignore
    mainContent.click(fun e -> sayHelloFrom("F#") :> obj)
ildjarn
  • 62,044
  • 9
  • 127
  • 211
Zaid Ajaj
  • 680
  • 8
  • 16

2 Answers2

2

This works more or less as you would expect when passing lambdas between F# and C#. In F#, functions can be curried, while in C# (and JavaScript) cannot. So when you need to send a lambda from F# to C# you need to convert it first. In F# this is done by wrapping the lambda like this:

open System.Linq
open System.Collections.Generic

let ar = [|1;2;3|]
let f = fun (x: int) (y: int) -> x + y
let acc = ar.Aggregate( System.Func<int,int,int>(f) )

Actually, the F# compiler can deduce the types most of the times, so you only need to write: System.Func<_,_,_>(f). Furthermore, when passing a F# lambda to a method expecting a C# lambda, the compiler makes the wrapping automatically for you. Then the previous example becomes:

let ar = [|1;2;3|]
let acc = ar.Aggregate( fun x y -> x + y )

(Of course, in this case it would be better to use the idiomatic Array.reduce. This is just a contrived example.)

This works exactly the same when interacting with JS using FunScript. The only thing you need to be aware of is how F# lambdas get translated to JS. To allow currying, a lambda with two or more parameters like fun x y -> x + y becomes:

function (x) {
    return function (y) {
        return x + y;
    }
}

Which may be a problem because the native JS will expect the following signature: function (x, y). In that case, you would have to wrap the lambda with System.Func<_,_,_>() as when interacting with C# (remember this is done automatically if you pass the lambda to a method).

However, lambdas with just one parameter don't suppose any problem: fun x -> x*x becomes function (x) { return x*x; }. In that case you don't need to wrap them (it doesn't hurt to do it anyway) and it's enough just to use unbox to appease the F# compiler when necessary. Just please be aware the FunScript compiler ignores unbox in the final JS code so there'll be no type check at all at runtime.

I hope the explanation is clear. Please add a comment if it isn't and I'll edit the answer.

  • Interesting, so assuming I'm not wraping my lambda's, does FunScript evaluate these functions like this: `f(5)(2)`?. Secondly, shouldn't the above function be: `function(x) { return function(y) { return x + y; } }`? – Zaid Ajaj Jan 08 '15 at 23:33
  • Ups, you're right. Thanks for pointing that out! And also yes, FunScript evaluates lambdas with 2 args like that: f(5)(2). This is the case for most of the functions in the List module, for example. – Alfonso Garcia-Caro Jan 09 '15 at 16:31
  • One last quistion about currying, wouldn't a curried function like `int -> (int -> int)` in F# be roughly defined as something like `Func>` in C#? Or is that not considered a "curried" function? – Zaid Ajaj Jan 11 '15 at 01:48
  • The function signatures in F#, OCaml or Haskell are right-associative: `int->int->int` is the same as `int->(int->int)`, that's why you can curry the function (The C# equivalent would be `Func` but it doesn't allow currying). You can read it as either: 1) apply two ints and get an int: `f 1 2 = 3`; or 2) partially apply one int and get a curried function `f 1 = fun y -> 1 + y` (though this won't compile cause functions are not equatable in F#) – Alfonso Garcia-Caro Jan 12 '15 at 09:10
0

Nevermind , I found the solution, I had to unbox the lambda:

options.complete <- unbox<Function> (fun _ -> sayHelloFrom("F#"))
Zaid Ajaj
  • 680
  • 8
  • 16