代码之家  ›  专栏  ›  技术社区  ›  Timwi

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

  •  5
  • Timwi  · 技术社区  · 15 年前

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

    public void Method(string str) { /* ... */ }
    public void Method(int integer) { /* ... */ }
    

    但是,在CIL中不能声明 这种形式的。每个委托必须有一个特定的参数类型列表。然而,由于F#中的函数是一等公民,您似乎应该能够传递这样的函数,而将其编译为CIL的唯一方法是使用委托。

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

    4 回复  |  直到 15 年前
        1
  •  6
  •   Gabe Timothy Khouri    15 年前

    在编写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"));
    }
    

    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"))
    

    作为一个单一的功能, f 可以像任何其他值一样使用,所以在F#中我们可以写 卡尔夫 call 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; }
        }
    }
    

    签字人

    public static void call(FSharpFunc<Either, string> f);
    

    以及 f级 看起来像这样:

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

    当然你也可以实现同样的功能 或者

        2
  •  11
  •   Brian    15 年前

    这个问题有点模棱两可,所以我就漫谈一下F的真实性。

    在F#, 方法 someObj.MethodName someType.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.
    

    在F#中,let绑定函数值不能重载。所以:

    let foo(x:int) = ()
    let foo(x:string) = ()  // Duplicate definition of value 'foo'
    

    这意味着你永远不能有一个“不合格”的标识符 foo 这有过多的含义。每个这样的名称都有一个明确的类型。

    最后,这个疯狂的案例可能就是引发这个问题的原因。F#可以定义 inline 具有“静态成员约束”的函数,可以绑定到例如“所有类型” T 具有名为 Bar 内联

    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
    

    所以这些都是由编译器处理的,在我们需要生成代码时,我们再次静态地解析了我们在这个调用站点使用的特定类型。“类型”的 crazy

    (疯狂的东西的主要目的/理由是为了重载的数学运算符。没有 内联 + 例如,作为let绑定函数类型的运算符,可以“只在 int s“或”仅适用于 float + 操作员只在 内景 +. 浮动 + 是一个 在F#库中定义的函数,该函数适用于具有 +

        3
  •  4
  •   Community Mohan Dere    8 年前

    假设我理解这个问题,在F#中,您可以根据具有特定签名的成员的可用性来定义表达式。例如

    let inline f x a = (^t : (member Method : ^a -> unit)(x,a))
    

    x 类型 ^t 还有一个价值观 a 类型 ^a 哪里 ^t型 Method 接受 ^a unit ( void 在C#)中,并调用该方法。因为这个函数定义为 inline ,定义是在使用点内联的,这是它可以被赋予这种类型的唯一原因。因此,尽管你可以通过 f 作为第一类函数,您只能在 ^t型 是静态已知的,因此方法调用可以静态解析并插入到位(这就是为什么类型参数具有 ^ ' 西格尔)。

    这是一个传球的例子 f级 作为一级功能:

    type T() = 
      member x.Method(i) = printfn "Method called with int: %i" i
    
    List.iter (f (new T())) [1; 2; 3]
    

    这将运行该方法 方法 f级

    List.iter ((fun (x:T) a -> x.Method(a)) (new T())) [1; 2; 3]
    

    编辑

    考虑到这个问题的来龙去脉( C# - How can I “overload” a delegate? ),我似乎根本没有回答你真正的问题。相反,盖布似乎在谈论的是一个人可以很容易地定义和使用歧视工会。所以在另一个线程上提出的问题可以用F#这样回答:

    type FunctionType = 
    | NoArgument of (unit -> unit)
    | ArrayArgument of (obj[] -> unit)
    
    let doNothing (arr:obj[]) = ()
    let doSomething () = printfn "'doSomething' was called"
    
    let mutable someFunction = ArrayArgument doNothing
    someFunction <- NoArgument doSomething
    
    //now call someFunction, regardless of what type of argument it's supposed to take
    match someFunction with
    | NoArgument f -> f()
    | ArrayArgument f -> f [| |] // pass in empty array
    

    在低水平上,这里没有CIL魔法;只是 NoArgument ArrayArgument FunctionType 通过模式匹配很容易构造和解构。模式匹配表达式的分支在道义上等同于类型测试,然后是属性访问,但是编译器确保案例具有100%的覆盖率并且不重叠。你可以毫无问题地在C语言中编码完全相同的操作,但是会更加冗长,编译器也不会帮助你进行穷尽性检查等。

    此外,这里没有函数的特殊性;F#判别并集使定义具有固定数量的命名替代项的类型变得很容易,每个替代项都可以包含您想要的任何类型的数据。

        4
  •  1
  •   desco    15 年前

    我不太确定你是否正确理解了你的问题。。。F#编译器使用 FSharpFunc 因此,F#不使用委托,而是使用带有具体或泛型参数的特殊类型。 如果你的问题是关于 添加一些我不知道确切是什么但是它有加法运算符 那你需要用 内联 关键字和编译器将在调用位置发出函数体。@kvb的回答正好描述了这个案例。