F# es OCaml sobre .NET, y es la demostración de que un lenguaje funcional puede vivir en un ecosistema empresarial sin renunciar a nada: usa las mismas bibliotecas que C#, interopera sin fricción, y muchas de las mejores ideas de C# de la última década llegaron desde aquí.
🎯 Por qué está en este programa
F# es un primo de la familia .NET (Atlas), cuyo representante en el núcleo es C#, y también de la familia funcional tipada (ML).
Aporta al programa la inferencia de tipos completa al estilo Hindley-Milner —tipado estático sin escribir casi ningún tipo— y los tipos de datos algebraicos con emparejamiento exhaustivo (clase 100), que son las dos ideas de ML que hoy están llegando a todos los lenguajes.
| Año | 2005; 1.0 en 2010; hoy en F# 9, con .NET |
| Autoría | Don Syme, Microsoft Research Cambridge — también autor de los genéricos de .NET |
| Familia | .NET y funcional tipada (ML); descendiente directo de OCaml |
| Paradigma | Funcional primero, con OO completo cuando hace falta |
| Tipado | Estático con inferencia total; tipos algebraicos y unidades de medida |
| Memoria | La del CLR: recolección de basura |
| Ejecución | Bytecode IL sobre el CLR, con JIT o AOT nativo |
| Estado | 🟢 Vivo y minoritario; muy usado en finanzas y análisis de datos |
Don Syme trabajaba en Microsoft Research y tiene un mérito que conviene conocer: diseñó e implementó los genéricos de .NET —reificados, a diferencia de los de Java (clase 108)— y lo hizo, en parte, porque los necesitaba para poder llevar ML a la plataforma.
Es una historia poco común: una característica del ecosistema entero existe porque alguien quería hacer un lenguaje funcional en él.
F# apareció en 2005 como puerto de OCaml sobre .NET, y se convirtió en producto oficial de Microsoft en 2010. Desde entonces es de código abierto y multiplataforma, con la F# Software Foundation implicada en su gobierno.
Y su influencia sobre su hermano mayor es grande y está documentada: los registros, el
emparejamiento de patrones, los tipos con nulabilidad, async/await y las expresiones switch de
C# vienen, en buena parte, de F#. Es el laboratorio de la plataforma.
Uno, la inferencia Hindley-Milner:
let sumar a b = a + b // ← ni un tipo escrito
// el compilador deduce: int -> int -> int
let procesar lista =
lista |> List.filter (fun x -> x > 0) |> List.sum
// deduce: int list -> int
Tipado estático completo sin escribir tipos. Es más fuerte que la inferencia local de
C# con var o de Java: F# infiere las firmas de las funciones, no solo
las variables locales.
Dos, los tipos algebraicos con exhaustividad:
type Forma =
| Circulo of radio: float
| Rectangulo of ancho: float * alto: float
let area f =
match f with
| Circulo r -> System.Math.PI * r * r
| Rectangulo (a, b) -> a * b
// ← si falta un caso, el compilador AVISA
Y la combinación de las dos cosas es lo que hace productivo el estilo: se modela el dominio con tipos que hacen imposible representar un estado inválido, y el compilador comprueba que se han tratado todos los casos (clase 166).
Y tres, dos cosas que F# tiene y casi nadie más:
[<Measure>] type m
[<Measure>] type s
let velocidad = 100.0<m> / 9.58<s> // float<m/s>
// let error = 100.0<m> + 9.58<s> ← ✗ NO COMPILA
Las unidades de medida comprobadas en compilación, sin coste en ejecución. Es exactamente lo que la clase 166 señalaba con el Mars Climate Orbiter: el contrato de las unidades, en el tipo.
type Datos = CsvProvider<"ventas.csv"> // ← el TIPO se genera del fichero
let filas = Datos.Load("ventas.csv")
filas.Rows |> Seq.sumBy (fun r -> r.Importe) // con autocompletado y comprobación
Los proveedores de tipos leen un esquema —CSV, JSON, SQL, un servicio web— en tiempo de compilación y generan los tipos. Es metaprogramación (clase 123) aplicada al problema de la clase 170: el desajuste de impedancia resuelto generando el tipo desde el esquema.
task { } para interoperar con el async de C#, expresiones de índice, y mejoras
grandes de rendimiento del compilador.dotnet fsi: consola interactiva de primer nivel para explorar datos, al estilo de
Lisp (clase 124).dotnet fsi main.fsx < entrada.txt # ejecutar como guion
dotnet run # como proyecto
dotnet fsi # consola interactiva
dotnet test # pruebas, con Expecto o xUnit (clase 139)
let [| precio; cantidad; descuento |] =
stdin.ReadLine().Split(' ') |> Array.map float
let total = precio * cantidad * (1.0 - descuento)
printfn "Total: %.2f" total
Lo que hay que ver.
|> es el operador de canalización, y es la marca de la casa: x |> f es f x. Permite leer
la transformación de izquierda a derecha, en el orden en que ocurre —en lugar de anidada de
dentro afuera— y es lo que después adoptaron Elixir, R y JavaScript en
propuesta.let [| a; b; c |] = ... es emparejamiento de patrones sobre un arreglo, igual que en
Scala: si la línea no trajera tres campos, fallaría.1.0 con decimal no es un capricho: F# no convierte números implícitamente, así que
1 - descuento con un entero no compila. Es la misma disciplina de Go y
Rust (clase 100).printfn "Total: %.2f" está comprobado en compilación: el %.2f exige un float, y el
compilador lo verifica. Es más fuerte que el printf de C, que no comprueba nada
(clase 142), y que el de C#.main, ni llaves: la sangría marca el bloque, como en
Python.⏮️ Volver al Atlas · 🗂️ Todas las fichas · 🔗 Relacionadas: OCaml · C# · Haskell · Elm · Scala