You can use existing .NET API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open System

type TrialLib = 
    static member SinCos(input : float, scale: float, sinus : byref<int>, cosinus : byref<int>) =
        sinus <- Convert.ToInt32(Math.Sin(input) * scale)
        cosinus <- Convert.ToInt32(Math.Cos(input) * scale)

let sinCosMethod = typeof<TrialLib>.GetMethod("SinCos")

let makeFunction<'T when 'T :> Delegate> methodInfo = 
    Delegate.CreateDelegate(typeof<'T>, methodInfo) :?> 'T

type SinCos = delegate of float*float*byref<int>*byref<int> -> unit
let f = (makeFunction<SinCos> sinCosMethod).Invoke   
let sinus = ref 0
let cosinus = ref 0
f(0.0, 100.0, sinus, cosinus)
By on 5/19/2010 4:01 AM ()

the real big picture is that i want to implement a compiler for a statical typed graphical dataflow language.

all static functions in a referenced assembly would result in a node. nodes can be dropped on a canvas and can be connected.

the compiler should generate either f# code or alternatively f# quotations or maybe Linq Expression Trees. ([link:msdn.microsoft.com]

for now i go for quotations and build them programmatically over the Expr module while traversing the node graph. at the end i have an expression tree which i can evaluate.
however quotations have some unpredictable limitations which are not very good documented. e.g. i can't quote a function with byref parameters.

1
2
3
4
5
type TrialLib =
    static member SinCos(input : float, scale: float, sinus : byref<int>, cosinus : byref<int>) =
        sinus <- Convert.ToInt32(Math.Sin(input) * scale)
        cosinus <- Convert.ToInt32(Math.Cos(input) * scale)
<@ TrialLib.SinCos @>;;

fails: error FS0462: Quotations cannot contain this kind of type

however

1
2
3
4
type TrialLib =
    static member SinCos2(input : float, scale: float) =
        Convert.ToInt32(Math.Sin(input) * scale), Convert.ToInt32(Math.Cos(input) * scale)
<@ TrialLib.SinCos2 @>;; 

works:

1
2
3
4
5
6
7
val it : Quotations.Expr<(float * float -> int * int)> =
  Lambda (tupledArg,
        Let (arg00, TupleGet (tupledArg, 0),
             Let (arg01, TupleGet (tupledArg, 1),
                  Call (None,
                        System.Tuple`2[System.Int32,System.Int32] SinCos2(Double, Double),
                        [arg00, arg01]))))

and anyway i want to get rid of byref parameters and only allow (tupled) return values.

in order to achieve that, i would need to generate a function value without byref params which i can then convert to an expression with Expr.Value.

so the question would be how to generate the function value by analyzing the method info. i could use FSharpValue.MakeFunction. however i would need to generate the function body:

1
2
3
4
5
let sincos x1 y1 = 
    let refx2 = ref 0
    let refy2 = ref 0 
    TrialLibCSharp.SinCos(x1, y1, refx2, refy2)
    (!refx2, !refy2) 

note that i cannot know the signature of a function at compile time and the code above is only a snapshot of what should be generated programmatically either by FSharpValue.MakeFunction, some call of a f# compiler service or some tricky delegate technique like desco proposed.

so since i want to build a sort of compiler i need function objects or expressions which i compose first and evaluate later. if i would want to build an interpreter i probably could just call invoke at a proper place.

please feel invited to comment the big picture. should i skip the quotations and try to get into the fsi source code (note: could be very hard) or should i skip the whole compiler idea and write and interpreter which traverses the graph in each evaluation step?

thanks for your votes!
sebastian

By on 5/19/2010 9:43 AM ()

If you don't have any limitations on framework version, then you can use extended version of Expression Trees from .NET 4.0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public static class FunctionUtils
{
    private static readonly Type[] TupleTypes = new[]
                                                    {
                                                        typeof(Tuple<>),
                                                        typeof(Tuple<,>),   
                                                        typeof(Tuple<,,>),   
                                                        typeof(Tuple<,,,>),   
                                                        typeof(Tuple<,,,,>),   
                                                        typeof(Tuple<,,,,,>),   
                                                        typeof(Tuple<,,,,,,>),   
                                                    };

    public static Delegate MakeFunction(MethodInfo methodInfo)
    {
        var functionType = GetFunctionType(methodInfo);
        
        var parameters = GetParameters(methodInfo).Select(Expression.Parameter).ToArray(); 
        var locals = GetByRefParams(methodInfo).Select(Expression.Variable).ToArray();

        var block = Expression.Block(
            locals,
            Expression.Call(null, methodInfo, parameters.Concat(locals)),
            Expression.New(functionType.Item2.GetConstructor(functionType.Item2.GetGenericArguments()), locals)
            );
        var lambda = Expression.Lambda(
            Expression.GetFuncType(functionType.Item1.Concat(new[] {functionType.Item2}).ToArray()),
            block,
            parameters
            );
        return lambda.Compile();
    }

    public static Tuple<Type[], Type> GetFunctionType(MethodInfo methodInfo)
    {
        var parameters = GetParameters(methodInfo);
        var byRefParams = GetByRefParams(methodInfo);

        return Tuple.Create(parameters, TupleTypes[byRefParams.Length - 1].MakeGenericType(byRefParams));
    }

    private static Type[] GetParameters(MethodInfo methodInfo)
    {
        return methodInfo.GetParameters()
            .TakeWhile(p => !p.ParameterType.IsByRef)
            .Select(p => p.ParameterType)
            .ToArray();
    }

    private static Type[] GetByRefParams(MethodInfo methodInfo)
    {
        return methodInfo.GetParameters()
            .SkipWhile(p => !p.ParameterType.IsByRef)
            .Select(p => p.ParameterType.GetElementType())
            .ToArray();
    }
}
public static class TrialLibCSharp
{
    //testing out params
    public static void SinCos(int input, int scale, out int sinus, out int cosinus)
    {
        sinus = Convert.ToInt32(Math.Sin(input) * scale);
        cosinus = Convert.ToInt32(Math.Cos(input) * scale);
    }
}
var method = typeof (TrialLibCSharp).GetMethod("SinCos");
var compiled = FunctionUtils.MakeFunction(method);
Console.WriteLine(compiled.DynamicInvoke(0, 100)); // (0, 100)
By on 5/19/2010 12:51 PM ()

hi !

desco, thank you very much for your help!
i didn't dig very much into it, but don't want to miss mono compatibility. so i am not so sure about .Net 4.0...

but concerning this little topic with the out or ref params: it gets really nasty when you try to do that without extended version of Expression Trees. so is the only way to solve that specific problem?

What i tried is to call invoke with byref params. But there seems to be no way to create mutable reference cells and pass them to Invoke. I tried to get a byref<'a>[] as a obj[] into invoke. I also didn't succeed in generating instances holding default values for primitive types like int...

1
2
3
4
5
6
7
8
9
let byrefs = Array.map (fun (t:Type) -> ref (0:>obj)) byreftypes
let contents = Array.map (fun (ref:'a ref) -> box(&ref.contents)) byrefs
let returnvalue = m.Invoke( null, Array.append (Array.append otherinputs [|last|]) contents ) 
let returnelements = GetReturnElements returnvalue 
let outputelements = Array.append returnelements byrefelements
if outputelements.Length > 1 then
     FSharpValue.MakeTuple(outputelements, outputtype) 
else 
     outputelements.[0]    

however:
Error 5 A type instantiation involves a byref type. This is not permitted by the rules of Common IL.

so everything i tried didn't help or just didn't compile.

then i looked again at your delegate technique and wondered if this is the key to solve the problem. however i can't see how to generate a delegate type. There is a TypeBuilder in Reflection.Emit, but i don't know. The docs of MulticastDelegate also say that you can't inherit from that type (only compilers can do that?!). Oh well.

So far my research about the idea of creating a function value before (being able of) quoting it.

I just wonder why creating fsharp code on a lexical level is so easy (you need a text editor) and creating fsharp code on a META or AST level is so hard. Maybe i should just go and generate strings (f# source code)...

does somebody know will quotations get richer in future releases of f#?
any other ideas?

thanks,
sebastian

By on 5/20/2010 12:24 PM ()

byref types cannot be used as an arguments of type instantiations (according to ECMA 335, 9.4 - Instantiating generic types). However in your case you need to unwrap byref type (obtained from the ParameterInfo) into ordinary type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
open System
open System.Reflection

open Microsoft.FSharp.Reflection

type TrialTest() = 
    static member SinCos(a : float, b : float, c : byref<int>, d : byref<int>) = 
        c <- int <| Math.Sin(a) * b
        d <- int <| Math.Cos(a) * b

let sinCosMethod = typeof<TrialTest>.GetMethod("SinCos")
let invoke (args : obj[]) (methodInfo : MethodInfo)= 
    let outParamTypes = 
        methodInfo.GetParameters() 
        |> Seq.filter(fun p -> p.ParameterType.IsByRef) 
        |> Seq.map(fun p -> p.ParameterType.GetElementType())
        |> Seq.toArray

    let argList = Array.append args (Array.zeroCreate outParamTypes.Length)
        
    methodInfo.Invoke(null, argList) |> ignore

    let resultType = FSharpType.MakeTupleType(outParamTypes)
    let resultValues = Array.sub argList args.Length outParamTypes.Length
    FSharpValue.MakeTuple(resultValues, resultType)

let result = invoke [|box 0.0; box 100.0|] sinCosMethod
printfn "%A" result // (0, 100)

Also you can use types from System.Reflection.Emit to generate wrapper

that will convert Method(args..., byref output args...) : unit into

Method(args) : Tuple<output args> and use it through delegate. Also functionality of ExpressionTree V2 can be found in DLR ([link:dlr.codeplex.com])

By on 5/21/2010 2:02 AM ()

desco,

thank you very much!
you helped a lot!

By on 5/21/2010 8:13 AM ()

Well if your methods have all the same signature, you can define an F# wrapper (untested pseudocode):

1
2
3
4
5
let methodInfo = ...


let myFun (arg1 : type1) (arg2 : type2) =
  methodInfo.Invoke(arg1, arg2) :> returnType

and myFun is your function object.

If you want to be generic (any method signature)... well I don't think that can be done 'statically' because F#'s type checking would get thrown out of the window.
The signature for function objects is created at compile time, and you don't know the method signature at that time.

It'd be interesting to know what you are trying to do (bigger picture). Do you need a function object? Or can you just pass the method info around and invoke it dynamically when u need the result?

By on 5/18/2010 1:43 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper