f# types inline

Uso de `inline` en F#



types (4)

La palabra clave en inline en F # me parece que tiene un propósito diferente al que estoy acostumbrado en, por ejemplo, C. Por ejemplo, parece afectar el tipo de una función (¿cuáles son los "parámetros de tipo resueltos estáticamente"? No todos son F # tipos resueltos estáticamente?)

¿Cuándo debo usar las funciones en inline ?


¿Cuándo debo usar las funciones en inline ?

La aplicación más valiosa de la palabra clave en inline en la práctica es la integración de funciones de orden superior en el sitio de la llamada, donde los argumentos de sus funciones también están en línea para producir una pieza de código totalmente optimizada.

Por ejemplo, la inline en la siguiente función de fold hace 5 veces más rápido:

let inline fold f a (xs: _ []) = let mutable a = a for i=0 to xs.Length-1 do a <- f a xs.[i] a

Tenga en cuenta que esto se parece poco a lo que hace inline en la mayoría de los otros idiomas. Puede lograr un efecto similar utilizando la metaprogramación de plantillas en C ++, pero F # también puede alinearse entre los ensamblados compilados porque la inline se transmite a través de metadatos .NET.


Debe usar inline cuando necesite definir una función que debe tener su tipo (re) evaluado en el sitio de cada uso, en lugar de una función normal, que tendrá su tipo evaluado (deducido) solo en el sitio de primer uso , y luego ser considerado como tipificado estáticamente con esa primera firma de tipo inferida en cualquier otro lugar posterior.

En el caso en línea, la definición de la función es efectivamente genérica / polimórfica, mientras que en el caso normal (sin línea), la función está tipificada de forma estática (ya menudo implícitamente).

Entonces, si usas en línea, el siguiente código:

let inline add a b = a + b [<EntryPoint>] let main args = let one = 1 let two = 2 let three = add one two // here add has been compiled to take 2 ints and return an int let dog = "dog" let cat = "cat" let dogcat = add dog cat // here add has been compiled to take 2 strings and return a string printfn "%i" three printfn "%s" dogcat 0

construirá y compilará para producir el siguiente resultado:

3 dogcat

En otras palabras, la misma definición de la función de adición se ha utilizado para producir una función que se suma a los enteros y una función que concatena dos cadenas (de hecho, la sobrecarga del operador subyacente en + también se logra bajo el capó utilizando inline).

Mientras que este código es idéntico, excepto que la función de agregar ya no se declara en línea:

let add a b = a + b [<EntryPoint>] let main args = let one = 1 let two = 2 let three = add one two // here add has been compiled to take 2 ints and return an int let dog = "dog" let cat = "cat" let dogcat = add dog cat // since add was not declared inline, it cannot be recompiled // and so we now have a type mismatch here printfn "%i" three printfn "%s" dogcat 0

no compilará, fallando con esta queja:

let dogcat = add dog cat ^^^ - This expression was expected to have type int but instead has type string

Un buen ejemplo de dónde es apropiado usar inline, es cuando desea definir una función genérica para invertir el orden de la aplicación de argumentos de una función con 2 argumentos, por ejemplo

let inline flip f x y = f y x

como se hace en la respuesta de @pad a esta pregunta .


La palabra clave en inline indica que una definición de función debe insertarse en línea en cualquier código que la use. La mayoría de las veces, esto no tendrá ningún efecto sobre el tipo de función. Sin embargo, en casos raros, puede llevar a una función que tiene un tipo más general, ya que hay restricciones que no pueden expresarse en la forma compilada del código en .NET, pero que se pueden aplicar cuando la función está en línea.

El caso principal donde esto se aplica es el uso de operadores.

let add a b = a + b

tendrá un tipo inferido monomórfico (probablemente int -> int -> int , pero podría ser algo como float -> float -> float si tiene un código que usa esta función en ese tipo). Sin embargo, al marcar esta función en línea, el compilador F # inferirá un tipo polimórfico:

let inline add a b = a + b // add has type ^a -> ^b -> ^c when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)

No hay forma de codificar esta restricción de tipo en una forma de primera clase en código compilado en .NET. Sin embargo, el compilador F # puede imponer esta restricción en el sitio en el que integra la función, de modo que todos los usos de los operadores se resuelvan en el momento de la compilación.

Los parámetros de tipo ^a , ^b , y ^c son "parámetros de tipo resueltos estáticamente", lo que significa que los tipos de los argumentos deben ser conocidos de forma estática en el sitio donde se utilizan esos parámetros. Esto contrasta con los parámetros de tipo normal (por ejemplo, ''a , ''b , etc.), donde los parámetros significan algo así como "algún tipo que se suministrará más adelante, pero que puede ser cualquier cosa".


Las pautas de diseño de componentes F # solo mencionan un poco sobre esto. Mi recomendación (que se alinea bien con lo que se dice allí) es:

  • No usar en inline
    • Excepción: podría considerar el uso de la inline cuando escriba bibliotecas matemáticas para que sean consumidas por otro código F # y desee escribir funciones que sean genéricas sobre diferentes tipos de datos numéricos.

Hay muchos otros usos "interesantes" de restricciones de miembros en línea y estáticas para tipos de escenarios que funcionan como las plantillas de C ++. Mi consejo es evitar todo eso como la plaga.

La respuesta de @kvb profundiza sobre qué son las "restricciones de tipo estático".