cv.cm

Programación · Lenguajes de programación

Tutorial de F# para principiantes

7 min de lectura

Respuesta corta

F# es un lenguaje de tipado estático, funcional ante todo, para .NET. Infiere la mayoría de los tipos por ti, usa la sangría en lugar de llaves y convierte los datos inmutables, la coincidencia de patrones y el operador de pipeline |> en la forma normal de escribir código, sin perder el acceso completo a las bibliotecas de .NET. Instala el SDK de .NET y ejecuta un script con dotnet fsi hello.fsx.

Qué es F# y quién lo usa

F# lo diseñó Don Syme en Microsoft Research y hoy es un lenguaje de código abierto mantenido por Microsoft y la comunidad de la F# Software Foundation. Pertenece a la familia de lenguajes ML (como OCaml) y se ejecuta sobre .NET en Windows, macOS y Linux.

Los equipos usan F# para modelado financiero, análisis de datos, lógica de negocio con mucho dominio, backends web y scripting. Como compila al mismo lenguaje intermedio de .NET que C#, un proyecto de F# puede usar cualquier paquete de NuGet y puede ser llamado desde código C#.

Instala F# y ejecuta Hola, mundo

Instala el SDK de .NET desde Microsoft. F# está incluido, así que no hace falta una instalación aparte. Para experimentar rápido, escribe un archivo de script con la extensión .fsx y ejecútalo con F# Interactive (dotnet fsi).

// hello.fsx
printfn "Hello, world!"
dotnet fsi hello.fsx
# Hello, world!
dotnet new console -lang "F#" -o HelloApp
cd HelloApp
dotnet run

El segundo conjunto de comandos crea un proyecto de consola completo con un archivo .fsproj y Program.fs. En un proyecto, el orden de los archivos listados en el .fsproj importa: un archivo solo puede usar código de los archivos que están por encima.

Valores y tipos básicos

let enlaza un nombre a un valor. Los enlaces son inmutables a menos que los marques como mutable, y entonces los cambias con <-, no con =.

TipoEjemploNotas
int42entero de 32 bits
float3.14coma flotante de 64 bits (un double de .NET)
decimal19.99mDecimal exacto, ideal para dinero
string"hello"Cadena inmutable de .NET
char'a'Un solo carácter
booltrue, falseValores lógicos
unit()«Sin valor», como void
let name = "Ana"
let age = 30
let mutable count = 0
count <- count + 1
printfn "%s is %d, count = %d" name age count
// Ana is 30, count = 1
let total = float age + 0.5   // explicit conversion needed

F# nunca convierte números de forma implícita: 1 + 2.0 es un error de compilación. Convierte con funciones como float, int y string.

Control de flujo y funciones

if es una expresión que devuelve un valor, así que ambas ramas deben tener el mismo tipo. Existen los bucles (for, while), pero la mayor parte del código usa funciones de colecciones.

let sign n =
    if n > 0 then "positive"
    elif n < 0 then "negative"
    else "zero"
for i in 1 .. 3 do
    printfn "%d squared is %d" i (i * i)
let add x y = x + y          // int -> int -> int
let add5 = add 5             // partial application
printfn "%d" (add5 10)       // 15
let square = fun x -> x * x
[1; 2; 3; 4]
|> List.filter (fun x -> x % 2 = 0)
|> List.map square
|> printfn "%A"              // [4; 16]

Los argumentos se separan con espacios, no con comas. Cada función toma un argumento a la vez (currificación), así que add 5 devuelve una función nueva. El operador de pipeline x |> f significa f x y permite escribir transformaciones de datos de arriba abajo. La sangría es significativa: el cuerpo de una función o bucle debe ir indentado debajo.

Colecciones, registros y opciones

ColecciónLiteralNotas
Lista[1; 2; 3]Lista enlazada inmutable, la opción por defecto
Arreglo[| 1; 2; 3 |]Tamaño fijo, elementos mutables, indexación rápida
Secuenciaseq { 1 .. 10 }Perezosa, se calcula bajo demanda
MapaMap ["a", 1; "b", 2]Diccionario inmutable
Conjuntoset [1; 2; 2]Conjunto inmutable de valores únicos
type Person = { Name: string; Age: int }
let ana = { Name = "Ana"; Age = 30 }
let older = { ana with Age = 31 }       // copy with one change
let people = [ ana; { Name = "Ben"; Age = 17 } ]
let adult = people |> List.tryFind (fun p -> p.Age >= 18)
match adult with
| Some p -> printfn "First adult: %s" p.Name
| None -> printfn "No adults"
// First adult: Ana

Los registros son tipos inmutables con campos con nombre e igualdad estructural. F# no tiene null para sus propios tipos; un valor ausente es un Option: Some value o None. Funciones como List.tryFind devuelven un option, y el compilador te obliga a tratar ambos casos.

Características distintivas: uniones discriminadas y coincidencia de patrones

Una unión discriminada describe un valor que es exactamente uno de varios casos con nombre, cada uno con sus propios datos. match comprueba los casos, y el compilador te avisa si olvidas alguno.

type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
let area shape =
    match shape with
    | Circle r -> System.Math.PI * r * r
    | Rectangle (w, h) -> w * h
[ Circle 1.0; Rectangle (2.0, 3.0) ]
|> List.map area
|> List.iter (printfn "%.2f")
// 3.14
// 6.00

Otras características conocidas son la inferencia de tipos (rara vez escribes anotaciones de tipo), las unidades de medida (float<m> no se puede sumar con float<s>) y las expresiones de cómputo como task { ... } y async { ... } para código asíncrono.

Manejo de errores

Para los fallos esperados, el F# idiomático devuelve un Result: Ok value o Error info. Para los fallos inesperados están disponibles las excepciones de .NET con try ... with, failwith y raise.

let parseAge (s: string) =
    match System.Int32.TryParse(s) with
    | true, n when n >= 0 -> Ok n
    | true, _ -> Error "Age must not be negative"
    | false, _ -> Error "Not a number"
match parseAge "abc" with
| Ok n -> printfn "Age %d" n
| Error msg -> printfn "Error: %s" msg
// Error: Not a number
let safeDivide a b =
    try Some (a / b)
    with :? System.DivideByZeroException -> None
printfn "%A" (safeDivide 10 0)   // None

TryParse tiene un parámetro out en .NET; F# lo convierte en una tupla (success, value) que puedes comparar directamente con un patrón.

Un programa pequeño y completo: frecuencias de palabras

let text = "the cat and the hat and the bat"
text.Split(' ')
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.truncate 2
|> Array.iter (fun (word, n) -> printfn "%s %d" word n)
// the 3
// and 2

Array.countBy id agrupa palabras iguales y devuelve pares (word, count), snd toma el segundo elemento de cada par para ordenar, y el resultado se imprime con una lambda que desestructura la tupla.

Siguientes pasos

  • Microsoft Learn: la documentación de F#, incluidos «Tour of F#» y la referencia del lenguaje.
  • Practica en dotnet fsi, donde puedes pegar código y ver los resultados al instante; termina la entrada con ;;.
  • Aprende los módulos List, Array, Seq, Option y Result; la mayor parte del código diario se construye con ellos.
  • Echa un vistazo a los frameworks y bibliotecas web de F# en NuGet cuando domines lo básico.

Preguntas frecuentes

¿F# es solo para Windows?

No. F# se ejecuta sobre .NET, que es multiplataforma, así que puedes compilar y ejecutar programas de F# en Windows, macOS y Linux.

¿Cuál es la diferencia entre F# y C#?

Ambos se ejecutan en .NET y pueden usar las mismas bibliotecas. C# es orientado a objetos ante todo, con características funcionales añadidas, mientras que F# es funcional ante todo, con datos inmutables, inferencia de tipos y coincidencia de patrones por defecto.

¿Qué significa |> en F#?

x |> f es el operador de pipeline y significa f x. Pasa el valor de la izquierda como último argumento de la función de la derecha, de modo que las cadenas de transformaciones se leen de arriba abajo.

¿F# está relacionado con OCaml?

Sí. F# pertenece a la familia ML y su sintaxis básica es cercana a la de OCaml, pero está hecho para .NET, usa por defecto la sangría significativa y tiene características propias como las expresiones de cómputo.

Todos los tutoriales de programación