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
.
Что происходит?!?
Ответы
Ответ 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.