Почему компилятор в порядке с не закрытым универсальным? - PullRequest
0 голосов
/ 01 июля 2018

У меня есть этот код:

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: сравнение либо определить «фактический» как простой термин данных, сделать его функцией с явные аргументы или, если вы не планируете, чтобы он был общим аннотация типа.

Тем не менее, когда я успешно (!) Скомпилировал и запустил его (проверил наличие полной версии .NET Framework и DotNetCore для отладки / выпуска) в Visual Studio, я получаю следующий вывод:

массив = [||], фактический = [||], те же объекты: ложь

Единственный способ, которым я мог ожидать этого результата, если бы '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 голосов
/ 02 июля 2018

Ключевым моментом, о котором Федор упомянул в своем ответе, является то, что способ 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: ожидалось, что это выражение будет иметь тип символ, но здесь есть тип 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

Думаю, если кто-нибудь решит поговорить с Ват о F #, это будет хорошим кандидатом! Компилятор может просто запретить все общие значения (что делают другие языки ML), но это приведет к удалению некоторых полезных конструкций, таких как Array.empty, и замене их на Array.createEmpty ().

0 голосов
/ 02 июля 2018

Итак, как я понял из комментариев, ваш фактический вопрос был таким:

Почему возвращаемый объект отличается от переданного?

Прежде всего, ожидание ссылочной идентичности для значений, которые логически "равны", значительно переоценено. Если ваша программа основана на ссылочной идентичности, вы делаете это неправильно. Если вам нужно, чтобы ссылочная идентичность сохранялась повсюду, вы получите 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[], поэтому единственный способ реализовать такую ​​конструкцию - это скомпилировать ее как функцию , которая не принимает никаких значений параметров , но параметр одного типа. Вроде как это:

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

А затем используйте эту функцию для построения a и b:

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

И это именно то, что делает компилятор. Функция, которая принимает только параметры типа, в этом контексте ее можно назвать «функцией типа». И действительно, именно так это называется в скомпилированном коде - FSharpTypeFunc.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...