F#如何编译可以将多种不同参数类型带入IL的函数?

Tim*_*mwi 5 f# cil compilation

我对F#几乎一无所知.我甚至不知道语法,所以我不能举例.

在注释线程中提到F#可以声明可以接受多种可能类型的参数的函数,例如字符串或整数.这与C#中的方法重载类似:

public void Method(string str) { /* ... */ }
public void Method(int integer) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

但是,在CIL中,您无法声明此表单的委托.每个代理必须具有一个特定的参数类型列表.因为F#中的函数是一等公民,所以看起来你应该能够传递这样的函数,并且将它编译成CIL的唯一方法是使用委托.

那么F#如何将其编译成CIL?

Bri*_*ian 11

这个问题有点含糊不清,所以我只是絮絮叨叨F#的真实情况.

在F#中,方法可以重载,就像C#一样.方法总是由形式的限定名访问someObj.MethodNamesomeType.MethodName.必须有可以在编译时静态解决重载的上下文,就像在C#中一样.例子:

type T() =
    member this.M(x:int) = ()
    member this.M(x:string) = ()
let t = new T()
// these are all ok, just like C#
t.M(3)
t.M("foo")
let f : int -> unit = t.M
let g : string-> unit = t.M
// this fails, just like C#
let h = t.M // A unique overload for method 'M' could not be determined 
            // based on type information prior to this program point.
Run Code Online (Sandbox Code Playgroud)

在F#中,let-bound函数值不能重载.所以:

let foo(x:int) = ()
let foo(x:string) = ()  // Duplicate definition of value 'foo'
Run Code Online (Sandbox Code Playgroud)

这意味着你永远不会有一个foo具有超载意义的"不合格"标识符.每个这样的名称都有一个明确的类型.

最后,这个疯狂的案例可能是提出问题的案例.F#可以定义inline具有"静态成员约束"的函数,这些约束可以绑定到例如" T具有名为的成员属性的所有类型Bar"或诸如此类的东西.这种通用性无法编码到CIL中.这就是为什么利用此功能的功能必须是inline,以便在每个呼叫站点,内联生成特定于该类型的呼叫站点使用的代码.

let inline crazy(x) = x.Qux(3) // elided: type syntax to constrain x to 
                               // require a Qux member that can take an int
// suppose unrelated types U and V have such a Qux method
let u = new U()
crazy(u) // is expanded here into "u.Qux(3)" and then compiled
let v = new V()
crazy(v) // is expanded here into "v.Qux(3)" and then compiled
Run Code Online (Sandbox Code Playgroud)

所以这些东西都由编译器处理,当我们需要生成代码时,我们再一次静态地解决了我们在这个调用站点使用的特定类型."类型" crazy不是可以用CIL表示的类型,F#类型系统只检查每个调用点以确保满足必要条件并将代码内联到该调用站点,这与C++模板的工作方式非常相似.

(疯狂的东西的主要目的/理由是重载数学运算符.没有这个inline特性,+运算符,例如,作为一个let-bound函数类型,可以"只在ints上工作"或"只在floats上工作"或一些ML风格(F#是OCaml的亲戚)就是这样做的,例如+运算符只适用于ints,而一个单独的运算符(通常是命名的+.)在floats上工作.而在F#中,+是一个inline在F#库中定义的函数.适用于任何具有+运算符成员或任何原始数字类型的类型.内联也可以具有一些潜在的运行时性能优势,这对于某些数学/计算域也很有吸引力.)

  • @Gabe - 虽然模式匹配和有区别的联合是很棒的功能,但我认为它们与函数是正交的.如果你有一个类型为"T - > U"的函数,那么无论"T"是否是一个有区别的联合类型,语言都是一样的. (2认同)

Gab*_*abe 6

当您编写C#并且需要一个可以使用多个不同参数集的函数时,您只需创建方法重载:

string f(int x)
{
    return "int " + x;
}
string f(string x)
{
    return "string " + x;
}
void callF()
{
    Console.WriteLine(f(12));
    Console.WriteLine(f("12"));
}
// there's no way to write a function like this:
void call(Func<int|string, string> func)
{
    Console.WriteLine(func(12));
    Console.WriteLine(func("12"));
}
Run Code Online (Sandbox Code Playgroud)

callF函数是微不足道的,但我的函数的伪造语法call不起作用.

当您编写F#并且需要一个可以使用多个不同参数集的函数时,您可以创建一个可以包含所有不同参数集的区分联合,并创建一个采用该联合的函数:

type Either = Int of int
            | String of string
let f = function Int x -> "int " + string x
               | String x -> "string " + x

let callF =
    printfn "%s" (f (Int 12))
    printfn "%s" (f (String "12"))

let call func =
    printfn "%s" (func (Int 12))
    printfn "%s" (func (String "12"))
Run Code Online (Sandbox Code Playgroud)

作为一个单一的功能,f可用于像任何其他的价值,所以在F#中,我们可以写 callFcall f,并且都做同样的事情.

那么F#如何实现Either我在上面创建的类型呢?基本上是这样的:

public abstract class Either
{
    public class Int : Test.Either
    {
        internal readonly int item;
        internal Int(int item);
        public int Item { get; }
    }
    public class String : Test.Either
    {
        internal readonly string item;
        internal String(string item);
        public string Item { get; }
    }
}
Run Code Online (Sandbox Code Playgroud)

call功能的签名是:

public static void call(FSharpFunc<Either, string> f);
Run Code Online (Sandbox Code Playgroud)

而且f看起来是这样的:

public static string f(Either _arg1)
{
    if (_arg1 is Either.Int)
        return "int " + ((Either.Int)_arg1).Item;
    return "string " + ((Either.String)_arg1).Item;
}
Run Code Online (Sandbox Code Playgroud)

当然你可以Either在C#(duh!)中实现相同的类型,但它不是惯用的,这就是为什么它不是前一个问题的明显答案.