为什么编译器可以与未关闭的泛型一起使用?

时间:2018-07-01 19:01:03

标签: f#

我有此代码:

open System

let func<'t when 't:comparison> (a: 't[]) = a

[<EntryPoint>]
let main argv =
    let array = [||]
    let actual = func array
    printfn "array = %A, actual = %A, same objects: %b" array actual (Object.ReferenceEquals(array, actual))
    Console.ReadKey()
    0

当我在LinqPad5中尝试时,出现一个合理的错误:

  

值限制。推断“实际”值具有   泛型val实际值:'_a []当'_a时:比较   将“实际”定义为简单的数据项,使其具有以下功能   显式参数,或者,如果您不希望它泛泛,则添加   类型注释。

但是,当我在Visual Studio中成功(!)编译并运行它(检查完整的.NET Framework和DotNetCore都进行了调试/发布)时,我得到以下输出:

  

array = [||],实际= [||],相同的对象:false

如果't[]是一个值类型,我可以期望得到此结果的唯一方法,但绝对不是。那么,WTF?!?

反编译的程序集包含以下代码:

[CompilationMapping(SourceConstructFlags.Module)]
public static class Program
{
  public static t[] func<t>(t[] a)
  {
    return a;
  }

  [EntryPoint]
  public static int main(string[] argv)
  {
    FSharpTypeFunc fsharpTypeFunc = (FSharpTypeFunc) new Program.array\u00409();
    IComparable[] comparableArray = Program.func<IComparable>((IComparable[]) fsharpTypeFunc.Specialize<IComparable>());
    FSharpFunc<object[], IComparable[]>.InvokeFast<bool, Unit>((FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>>) new Program.main\u004011(ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>>>((PrintfFormat<FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>>, TextWriter, Unit, Unit>) new PrintfFormat<FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>>, TextWriter, Unit, Unit, Tuple<object[], IComparable[], bool>>("array = %A, actual = %A, same objects: %b"))), (object[]) fsharpTypeFunc.Specialize<object>(), comparableArray, object.ReferenceEquals((object) (object[]) fsharpTypeFunc.Specialize<object>(), (object) comparableArray));
    Console.ReadKey();
    return 0;
  }

  [Serializable]
  internal sealed class array\u00409 : FSharpTypeFunc
  {
    [CompilerGenerated]
    [DebuggerNonUserCode]
    internal array\u00409()
    {
    }

    public override object Specialize<a>()
    {
      return (object) new a[0];
    }
  }

  [Serializable]
  internal sealed class main\u004011\u002D2 : FSharpFunc<bool, Unit>
  {
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [CompilerGenerated]
    [DebuggerNonUserCode]
    public FSharpFunc<bool, Unit> clo3;

    [CompilerGenerated]
    [DebuggerNonUserCode]
    internal main\u004011\u002D2(FSharpFunc<bool, Unit> clo3)
    {
      this.clo3 = clo3;
    }

    public override Unit Invoke(bool arg30)
    {
      return this.clo3.Invoke(arg30);
    }
  }

  [Serializable]
  internal sealed class main\u004011\u002D1 : FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>
  {
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [CompilerGenerated]
    [DebuggerNonUserCode]
    public FSharpFunc<IComparable[], FSharpFunc<bool, Unit>> clo2;

    [CompilerGenerated]
    [DebuggerNonUserCode]
    internal main\u004011\u002D1(FSharpFunc<IComparable[], FSharpFunc<bool, Unit>> clo2)
    {
      this.clo2 = clo2;
    }

    public override FSharpFunc<bool, Unit> Invoke(IComparable[] arg20)
    {
      return (FSharpFunc<bool, Unit>) new Program.main\u004011\u002D2(this.clo2.Invoke(arg20));
    }
  }

  [Serializable]
  internal sealed class main\u004011 : FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>>
  {
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [CompilerGenerated]
    [DebuggerNonUserCode]
    public FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>> clo1;

    [CompilerGenerated]
    [DebuggerNonUserCode]
    internal main\u004011(FSharpFunc<object[], FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>> clo1)
    {
      this.clo1 = clo1;
    }

    public override FSharpFunc<IComparable[], FSharpFunc<bool, Unit>> Invoke(object[] arg10)
    {
      return (FSharpFunc<IComparable[], FSharpFunc<bool, Unit>>) new Program.main\u004011\u002D1(this.clo1.Invoke(arg10));
    }
  }
}

此行似乎是罪魁祸首:

IComparable[] comparableArray = Program.func<IComparable>((IComparable[]) fsharpTypeFunc.Specialize<IComparable>());

如果我删除了comparison约束,则Specialize使用object而不是IComparable

2 个答案:

答案 0 :(得分:6)

所以,根据我从评论中收集的信息,您的实际问题是:

  

为什么返回的对象与传递的对象不同?

首先,对于逻辑上“相等”的值的引用标识的期望被大大高估了。如果您的程序依赖于引用身份,那么您做错了。如果必须强制在所有地方都保留引用身份,那么您最终会使用Java。

确实,请尝试以下操作:

> obj.ReferenceEquals( 5, 5 )
it : bool = false

> obj.ReferenceEquals( [1;2;3], [1;2;3] )
it : bool = false

嗯?

当然,在某些特殊情况下,您可能会以true结尾,例如:

> let l = [1,2,3]
> obj.ReferenceEquals( l, l )
it : bool = true

但这只是编译器选择用来表示您的代码的特定实现而引起的巧合。不要依赖它。

其次,您的函数确实,实际上返回了“相同”(在引用身份意义上)对象。试试这个:

   > let x =
         let array = [||]
         let typedArray : int[] = array
         let actual = func typedArray
         obj.ReferenceEquals( actual, typedArray )
   x : bool = true

看看创建中间typedArray后,“故障”如何消失了?您甚至可以将int替换为IComparable,它将仍然是true

秘密在于函数func实际上很好:它确实返回了“相同”对象。

创建新对象不是在func内部进行,而是每次您引用array时进行。

尝试一下:

> let x = 
     let array = [||]
     obj.ReferenceEquals( array, array )
x : bool = false

嗯? WTF?!

之所以会这样,是因为array实际上不是对象,而是幕后的功能。因为您没有指定array是什么类型,所以它必须是 generic -即具有用户希望它具有的任何类型。这必须起作用:

let array = [||]
let a : int[] = array
let b : string[] = array

很显然,array不能同时具有类型int[]和类型string[],因此实现这种构造的唯一方法是将其编译为 function 不带值参数,只有一个类型参数。有点像这样:

static a[] array<a>() { return new a[0]; }

然后使用该函数构造ab

var a = array<int>();
var b = array<string>();

这正是编译器所做的。一种仅接受类型参数的函数,在这种情况下可以将其称为“类型函数”。确实,这就是编译代码中所称的-FSharpTypeFunc

答案 1 :(得分:5)

Fyodor在回答中提到的关键是F#处理通用值的方式很棘手。通过查看以下可正常编译的代码,您可以看到这一点:

let oops () =
  let array = [||]
  array.[0] <- 'a'
  array.[0] <- 1

当然,您不能将'a'1放在同一阵列中!这里发生的是,编译器实际上将let array = [||]编译为一个泛型函数,当您访问新的空数组时(使用特定的实例化),它将返回一个空数组。

请注意,这仅适用于创建的没有任何副作用的简单值。例如,如果您想在每次访问数组时打印一条消息,则该消息将不起作用。以下:

let oops () =
  let array = printfn "Creating!"; [||]
  array.[0] <- 'a'
  array.[0] <- 1

给出类型错误:

  

错误FS0001:该表达式应具有类型       字符,但这里有类型       诠释

这是因为推理引擎意识到无法将array编译为通用函数,而是根据首次使用将其类型化。

在您的情况下,对类型进行专门化不会造成任何问题,因为您没有将泛型值用于多种不同类型。这意味着您可以通过在创建过程中添加副作用来使值相同-甚至只是忽略单位值()-足以使编译器将类型专门化:

let func<'t when 't:comparison> (a: 't[]) = a

let same () =
  let array = (); [||]
  let actual = func array
  printfn "same: %b" (Object.ReferenceEquals(array, actual))

let notSame () =
  let array = [||]
  let actual = func array
  printfn "same: %b" (Object.ReferenceEquals(array, actual))

notSame()  // same: false
same ()    // same: true

我想如果有人决定让Wat谈论F#,那将是一个很好的候选人!编译器可能只是禁止所有通用值(这是其他ML语言所做的事情),但是这将删除一些有用的构造,例如Array.empty并将其替换为Array.createEmpty ()