Подтвердить что ты не робот

F # - О параметрах, переданных методам С#, - это кортежи или что?

Я много раз читал, что

Собрания, сгенерированные из F # или любого другого языка .NET, (почти) неразличимы.

Затем я экспериментировал с F # и С# interop на .NET 4 (бета 2). Я создал новое решение и проект С# со следующим классом:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
}

Затем, в проекте F #, после ссылки на проект С#, я попытался:

MyClsas.Add(4, 5) |> printfn "%d" // prints 9 (no kidding!)

Пока все хорошо. Тогда мне пришло в голову другое предложение, которое я прочитал много раз (возможно, в разных книгах):

При передаче аргументов в функции из других библиотек .NET вы используете синтаксис типа ".MethodName(parm1, parm2)", то есть параметры передаются в качестве кортежа.

Добавьте это к чему-то, что я когда-то читал здесь на SO (но не смог найти ссылку на него), на вопрос, где OP пытался создать такое, как [ 4, 5, 6 ] (когда он имел в виду [4; 5; 6]):

"Запятая - это оператор создания набора кортежей, поскольку все остальное использует" двоеточие ".

Затем я изменил свой класс на следующее:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
    public static int Add(Tuple<int, int> a) { return a.Item1; }
}

Теперь я попытался использовать его на F #:

MyClass.Add(4, 5) |> printf "%d" // prints ... (keep reading!)

Итак, добавив три приведенные выше цитаты, можно сделать вывод, что:

  • F # создаст кортеж, когда увидит (4, 5)
  • Затем он вызовет перегрузку Add(Tuple<int, int>)
  • Итак, он напечатает 4

К моему удивлению, напечатано 9. Разве это не интересно?

Что на самом деле происходит здесь? Вышеуказанные цитаты и эти практические наблюдения, похоже, находятся в противоречии. Можете ли вы обосновать F # "рассуждение" и, возможно, укажете на некоторые документы MSDN, если это возможно?

Спасибо!

ИЗМЕНИТЬ

(чтобы добавить дополнительную информацию (из ответа Blindy))

Если вы выполните:

MyClass.Add((4, 5)) |> printfn "%d" // prints 9

F # вызывает перегрузку Add(Tuple<int, int>).

Однако, если вы создаете еще один проект F # (например, другую сборку) с этим:

namespace MyFSharpNamespace
type MyFShapClass = class
    static member Add x y = x + y
    end

Вы можете использовать его на С#, как этот

public static void Main(string[] args) {
    MyFSharpNamespace.MyFSharpClass.Add(4, 5);
}

Пока все хорошо. Теперь, когда вы пытаетесь использовать его из F # (из другого проекта, другой сборки), вам нужно сделать:

MyFSharpNamespace.MyFSharpClass.Add 4 5 |> printfn "%d"

Если вы передадите аргументы как (4, 5) F # не будет компилироваться, потому что Add есть int -> int -> int, а не (int * int) -> int.

Что происходит?!?

4b9b3361

Ответ 1

При передаче аргументов в функции из других библиотек .NET вы используете синтаксис типа ".MethodName(parm1, parm2)", то есть параметры передаются в качестве кортежа.

Это более отвратительно. См. Описание прокси-разрешения перегрузки метода из спецификация языка.

В основном это говорит о том, что аргумент в вызове метода на самом деле не является кортежем. Это синтаксический кортеж, означающий список, разделенный запятыми, но скобки являются частью синтаксиса вызова метода, а также являются запятыми. Это почему, например, o.M(a=1, b=2) не является вызовом метода с кортежем из двух булевых, а скорее двумя именованными аргументами.

Итак, как правило, каждый компонент, разделенный запятой, просто сопоставляется с отдельным аргументом. Следовательно, почему Add(1, 2) вызывает Add(int, int) перегрузку, а Add((1, 2)) вызывает Add(Tuple<int, int>). Здесь нет никакой двусмысленности.

Однако особый случай, который подходит для вашего конкретного случая, таков:

Если нет именованных фактических аргументов, и есть только один метод-кандидат в M, принимающий только один необязательный аргумент, тогда разложение формы arg на кортеж игнорируется и существует один из них фактический arg, который сам arg.

Итак, когда вы удалили все перегрузки, кроме кортежа, внезапно все, что находится внутри круглых скобок, эффективно рассматривается как конструктор кортежа в вызове. Но если вы, например, имеют две перегрузки, Add(int) и Add(Tuple<int,int>), тогда вызов формы Add(1,2) не будет разрешаться вообще.

Ответ 2

У меня нет F #, установленного прямо сейчас, но мне кажется, что

MyClass.Add(4, 5) |> printf "%d"

будет печатать 9, тогда как

MyClass.Add((4, 5)) |> printf "%d"

принт.. 4 правый? Обратите внимание на двойные скобки, внутреннюю пару, маркирующую кортеж, и внешнюю пару, обозначающую вызов функции.

Ответ 3

Это просто магия компилятора.

let add a b = a+b 

add скомпилируется до add(a,b), что упрощает вызов с С#. Тем не менее, программы F # по-прежнему видят его как add a b из-за атрибута в IL.

При вызове функций С# в F #, это может помочь думать о том, что функция С# имеет только один параметр - кортеж, элементы которого определяют правильную перегрузку. Поэтому вы можете написать:

// MyClass.Add(5,3) = 8
let eight = (5,3) |> MyClass.Add

Ответ 4

Я не эксперт F #, поэтому я могу быть немного не в базе, но я бы предположил, что концепция F # кортежа не коррелирует с типом BCL System.Tuple. Кортежи - основной принцип F # и встроены в язык, но С#, VB.NET и большинство других языков .NET не поддерживают кортежи. Поскольку кортежи могут быть полезны на этих языках, библиотека получает поддержку для них.

Я бы предпочел предположить, что кортеж F # представлен в памяти очень точно так же, как параметры передаются методам на С# и друзьям. То есть они представляют собой массивы значений их компонентов. Когда этот массив значений помещается в стек для вызова метода, он будет иметь тот же эффект, что и толкание каждого из его составных компонентов в стек, как при вызове этого метода из С#.

Итак, ваш второй пример создает кортеж F #, толкает его в стек, затем вызывает перегрузку Add, которая берет типы, содержащиеся в кортеже.

Это мое предположение, во всяком случае. Предположительно, использовав F # больше, чем я, вы можете получить больше информации, кроме этого. Вы также можете получить дополнительные подсказки, посмотрев на сгенерированный код Reflector.