dotnetperls - F#- En los parámetros pasados a los métodos de C#, ¿son tuplas o qué?
tuple create c# (4)
He leído muchas veces que
Los conjuntos generados desde F # o cualquier otro lenguaje .NET son (casi) indistinguibles.
Entonces estaba experimentando con interoperabilidad F # y C # en .NET 4 (beta 2). Creé una nueva solución y un proyecto en C #, con la siguiente clase:
public class MyClass {
public static int Add(int a, int b) { return a + b; }
}
Luego, en un proyecto F #, después de hacer referencia al proyecto C #, probé:
MyClsas.Add(4, 5) |> printfn "%d" // prints 9 (no kidding!)
Hasta ahora tan bueno. Luego, otra frase que he leído muchas veces (quizás en libros diferentes) me vino a la mente:
Al pasar argumentos a funciones de otras bibliotecas .NET, utiliza una sintaxis como ".MethodName (parm1, parm2)", es decir, los parámetros se pasan como un Tuple.
Agregue eso a algo que una vez leí aquí en SO (pero no pude encontrarlo para vincularlo), en una pregunta donde el OP intentaba crear un uso como [ 4, 5, 6 ]
(cuando quiso decir [4; 5; 6]
):
"La coma es el ''operador de creación de tuplas'', para todo lo demás use el punto y coma".
Luego modifiqué mi clase a lo siguiente:
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; }
}
Ahora traté de usarlo en F #:
MyClass.Add(4, 5) |> printf "%d" // prints ... (keep reading!)
Entonces, sumando las tres citas anteriores, se puede concluir que:
- F # creará una tupla cuando vea
(4, 5)
- Luego llamará a la sobrecarga
Add(Tuple<int, int>)
- Así se imprimirá 4
Para mi sorpresa, imprimió 9 . ¿No es interesante?
¿Qué está pasando realmente aquí? Las citas anteriores y estas observaciones prácticas parecen estar en contradicción. ¿Puede justificar el "razonamiento" de F #, y tal vez apuntar a algunos documentos de MSDN si es posible?
¡Gracias!
EDITAR
(Para agregar más información (de la respuesta de Blindy))
Si lo haces:
MyClass.Add((4, 5)) |> printfn "%d" // prints 9
F # llama a la sobrecarga Add(Tuple<int, int>)
.
Sin embargo, si crea otro proyecto F # (por lo tanto, un ensamblaje diferente) con esto:
namespace MyFSharpNamespace
type MyFShapClass = class
static member Add x y = x + y
end
Puedes usarlo en C # como este
public static void Main(string[] args) {
MyFSharpNamespace.MyFSharpClass.Add(4, 5);
}
Hasta ahora tan bueno. Ahora, cuando intenta usarlo desde F # (desde otro proyecto, otro conjunto), tiene que hacer:
MyFSharpNamespace.MyFSharpClass.Add 4 5 |> printfn "%d"
Si pasa los argumentos como (4, 5)
F # no se compilará porque Add
es int -> int -> int
, y no (int * int) -> int
.
¿¡¿Que esta pasando?!?
Al pasar argumentos a funciones de otras bibliotecas .NET, utiliza una sintaxis como ".MethodName (parm1, parm2)", es decir, los parámetros se pasan como un Tuple.
Es más horrible que eso. Consulte la descripción del método de resolución de sobrecarga de la especificación de idioma .
Básicamente, lo que dice es que el argumento en una invocación de método no es realmente una tupla. Es una tupla sintáctica , es decir, una lista separada por comas de algo, pero los paréntesis son parte de la sintaxis de llamada de método, y también lo son las comas. Es por eso que, por ejemplo, oM(a=1, b=2)
no es una llamada de método con una tupla de dos booleanos, sino dos argumentos con nombre.
Entonces, normalmente, cada componente separado por comas simplemente se asigna a un argumento distinto. Por lo tanto, ¿por qué Add(1, 2)
llamadas Add(int, int)
sobrecarga, y Add((1, 2))
llamadas Add(Tuple<int, int>)
. No hay ambigüedad aquí.
Sin embargo, un caso especial que se inicia en su caso particular es el siguiente:
Si no hay argumentos reales nombrados, y solo hay un método candidato en
M
, aceptando solo un argumento no opcional, la descomposición dearg
en forma de tupla se ignora y hay unarg
real llamado que esarg
sí mismo.
Por lo tanto, cuando eliminó todas las sobrecargas excepto la de la tupla, de repente, todo lo que se encuentra dentro del paréntesis se trata como un constructor de tuplas en una llamada. Pero si, por ejemplo, tiene dos sobrecargas, Add(int)
y Add(Tuple<int,int>)
, entonces una llamada del formulario Add(1,2)
no se resolvería en absoluto.
Es sólo la magia del compilador.
let add a b = a+b
add
compila hacia abajo para add(a,b)
, lo que facilita la llamada desde C #. Sin embargo, los programas F # todavía lo ven como add ab
debido a un atributo en el IL.
Al llamar a las funciones de C # en F #, puede ayudar pensar que la función de C # tiene solo un parámetro: una tupla, cuyos elementos determinan la sobrecarga correcta. Así que puedes escribir:
// MyClass.Add(5,3) = 8
let eight = (5,3) |> MyClass.Add
No soy un experto en F #, por lo que puedo estar un poco fuera de base, pero supongo que el concepto F # de una tupla no se correlaciona con el tipo BCL System.Tuple
. Las tuplas son un principio fundamental de F # y están integradas en el idioma, pero C #, VB.NET y la mayoría de los otros lenguajes .NET no son compatibles de forma nativa con las tuplas. Dado que las tuplas pueden ser útiles en estos idiomas, la biblioteca está ganando soporte para ellos.
Me gustaría ampliar mi suposición de que una tupla F # está representada en la memoria de la misma manera que los parámetros se pasan a los métodos en C # y amigos. Es decir, son esencialmente matrices de valores de sus componentes. Cuando esta matriz de valores se inserta en la pila para una llamada de método, tendría el mismo efecto que empujar cada uno de sus componentes constituyentes en la pila, al igual que cuando se llama a ese método desde C #.
Entonces, su segundo ejemplo crea una tupla F #, la empuja en la pila y luego llama a la sobrecarga Add
que toma los tipos contenidos dentro de la tupla.
Esa es mi suposición, de todos modos. Habiendo utilizado probablemente F # más que yo, es posible que tenga más información más allá de esto. También puede obtener pistas adicionales mirando el Reflector código generado.
No tengo F # instalado en este momento, pero me parece que
MyClass.Add(4, 5) |> printf "%d"
imprimiría 9 mientras
MyClass.Add((4, 5)) |> printf "%d"
imprimiría .. 4 ¿verdad? Observe las paréntesis dobles, el par interno que marca una tupla y el par externo que marca la llamada de función.