F# 为什么编译器可以不关闭泛型?

F# 为什么编译器可以不关闭泛型?,f#,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.ReferenceEqua

我有以下代码:

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
[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>());
IComparable[]compariablearray=Program.func((IComparable[])fsharpTypeFunc.Specialize();

如果我删除
比较
约束
专门化
使用
对象
而不是
可比较

,那么,正如我从评论中收集到的,您的实际问题是:

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

首先,对逻辑上“相等”的值的引用身份的期望被大大高估了。如果您的程序依赖于引用标识,那么您就做错了。如果必须在任何地方都保留引用标识,那么最终将使用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
看到我创建了一个中间
类型Darray
后,“故障”如何消失了吗?您甚至可以将
int
替换为
IComparable
,它仍然是
true

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

新对象的创建不是在
func
内部进行的,而是每次引用
数组时进行的

试试这个:

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

这是因为
array
实际上不是一个对象,而是一个幕后函数。因为您没有指定数组的类型,所以它必须是泛型的,即具有用户想要的任何类型。这必须起作用:

let array = [||]
let a : int[] = array
let b : string[] = array
显然,
array
不能同时具有type
int[]
和type
string[]
,因此实现此类构造的唯一方法是将其编译为不接受值参数,而只接受单个类型参数的函数。有点像这样:

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

这正是编译器所做的。一个只接受类型参数的函数,在此上下文中可以称之为“类型函数”。事实上,这就是它在编译代码中的名称-
FSharpTypeFunc

Fyodor在回答中提到的关键问题是F处理泛型值的方式很棘手。您可以通过查看以下编译良好的代码来了解这一点:

let oops () =
  let array = [||]
  array.[0] <- 'a'
  array.[0] <- 1
给出一个类型错误:

错误FS0001:此表达式应具有类型 但是这里有类型 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
设func(a:'t[])=a
让相同的()=
设数组=();[||]
设actual=func数组
printfn“相同:%b”(Object.ReferenceEquals(数组,实际))
让我们不一样()=
让数组=[||]
设actual=func数组
printfn“相同:%b”(Object.ReferenceEquals(数组,实际))
notSame()//相同:false
same()//same:true

我想如果有人决定谈论F#,这将是一个很好的候选人!编译器可以不允许所有泛型值(这是其他ML语言所做的),但这将删除一些有用的构造,如
Array.empty
,并将它们替换为
Array.createEmpty()

,那么你的问题是什么?@FyodorSoikin为什么假定较旧版本的F#编译器不允许这样的代码,新的一个使它工作,但以一种非常不可预测的方式。你认为什么是不可预测的?返回的对象与传递的对象不同。为什么调用
new IComparable[0]
而不是
Array.Empty()
。为什么您认为返回的对象应该是同一个对象?这个函数为什么创建不可变对象的新实例还不清楚。若它是对
Array.Empty的调用,语义不会改变,但给定类型的空数组的文本将始终是同一个对象。不这样做有什么意义?而且,一旦显式指定数组的类型,一切都会按预期开始工作。虽然仍然通过
new T[0]
初始化,但Literal变成了一个Literal。是的,
数组。不久前框架中出现了Empty
,但看起来早期版本的F#编译器甚至不允许您首先使用此代码。;-)这种机制比数组更通用。这可能是F中继C之后最困难的部分。)始终试图理解函数代码如何转换为.NET OOP表示。“这是因为推理引擎意识到它无法将数组编译为泛型函数,而是根据第一次使用对类型进行了专门化。”这是为什么?这是泛型函数的一般特征吗?它们不能有副作用?我觉得我缺少了一些明显的东西…@sebhofer泛型函数可能有副作用,但泛型值不能。我不知道编译器究竟是如何检查这一点的,但我假设一个表达式的形式是
被解释为(纯语法)可能有副作用。有趣。有没有一个明显的原因可以解释为什么它们不能这样做?@sebhofer在泛型值中的副作用会导致输入错误。下面是一个例子:
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