为什么编译器没有关闭通用?

Pav*_*nin 4 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
Run Code Online (Sandbox Code Playgroud)

当我在LinqPad5中尝试它时,我得到一个可推理的错误:

价值限制.值'actual'被推断为具有泛型类型val actual:'_a [] when'_a:comparison将'actual'定义为一个简单的数据项,使其成为具有显式参数的函数,或者,如果您不打算它是通用的,添加一个类型注释.

但是,当我在Visual Studio中成功(!)编译并运行它(检查完整的.NET Framework和DotNetCore都是Debug/Release)时,我得到了这个输出:

array = [||],actual = [||],相同的对象: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));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这条线似乎是罪魁祸首:

IComparable[] comparableArray = Program.func<IComparable>((IComparable[]) fsharpTypeFunc.Specialize<IComparable>());
Run Code Online (Sandbox Code Playgroud)

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

Fyo*_*kin 6

所以,正如我从评论中收集的那样,你的实际问题是:

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

首先,对逻辑上"平等"的价值的指称同一性的期望被大大高估了.如果您的程序依赖于引用标识,那么您做错了.如果必须强制在任何地方保留引用标识,那么最终会使用Java.

的确,试试这个:

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

> obj.ReferenceEquals( [1;2;3], [1;2;3] )
it : bool = false
Run Code Online (Sandbox Code Playgroud)

咦?

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

> let l = [1,2,3]
> obj.ReferenceEquals( l, l )
it : bool = true
Run Code Online (Sandbox Code Playgroud)

但这仅仅是编译器选择代表您的代码的特定实现产生的巧合.不要依赖它.

其次,您的函数实际上确实返回"相同"(在引用标识中)对象.试试这个:

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

一旦我创建了一个中间体,看看"故障"是如何消失的typedArray?你甚至可以替换intIComparable,它仍然是true.

秘诀是函数func实际上很好:它确实返回"相同"的对象.

新对象的创建不在内部func,而是在每次引用时发生array.

试试这个:

> let x = 
     let array = [||]
     obj.ReferenceEquals( array, array )
x : bool = false
Run Code Online (Sandbox Code Playgroud)

咦?WTF?

发生这种情况,因为array它实际上不是一个对象,而是幕后的一个功能.因为您没有指定什么类型array,它必须是通用的 - 即具有用户希望它具有的任何类型.这一定必须奏效:

let array = [||]
let a : int[] = array
let b : string[] = array
Run Code Online (Sandbox Code Playgroud)

显然,array不能有类型int[]和类型string[]在同一时间,所以只有这样才能实现这样的构建体是编译它作为一个函数,它没有值的参数,但单一类型的参数.有点像这样:

static a[] array<a>() { return new a[0]; }
Run Code Online (Sandbox Code Playgroud)

然后使用该函数构造ab:

var a = array<int>();
var b = array<string>();
Run Code Online (Sandbox Code Playgroud)

这正是编译器所做的.一个只接受类型参数的函数,可以在此上下文中将其称为"类型函数".事实上,这就是编译代码中所称的内容FSharpTypeFunc.


Tom*_*cek 5

Fyodor在他的回答中提到的关键是F#处理泛型值的方式很棘手.您可以通过查看以下编译良好的代码来查看:

let oops () =
  let array = [||]
  array.[0] <- 'a'
  array.[0] <- 1
Run Code Online (Sandbox Code Playgroud)

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

请注意,这仅适用于没有任何副作用的简单值.例如,如果您希望每次访问该数组时都打印一条消息,那么它将无法工作.下列:

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

给出类型错误:

错误FS0001:此表达式应该具有char类型,但此处的类型为int

这是因为推理引擎意识到它不能编译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
Run Code Online (Sandbox Code Playgroud)

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