learnxinyminutes-docs/es/fsharp.md
2024-12-08 23:20:53 -07:00

23 KiB

contributors translators filename
Scott Wlaschin
http://fsharpforfunandprofit.com/
Angel Arciniega
https://github.com/AngelsProjects
learnfsharp-es.fs

F# es un lenguaje de programación funcional y orientado a objetos. Es gratis y su código fuente está abierto. Se ejecuta en Linux, Mac, Windows y más.

Tiene un poderoso sistema de tipado que atrapa muchos errores de tiempo de compilación, pero usa inferencias de tipados que le permiten ser leídos como un lenguaje dinámico.

La sintaxis de F# es diferente de los lenguajes que heredan de C.

  • Las llaves no se usan para delimitar bloques de código. En cambio, se usa sangría (como en Python).
  • Los espacios se usan para separar parámetros en lugar de comas.

Si quiere probar el siguiente código, puede ir a tryfsharp.org y pegarlo en REPL.

// Los comentarios de una línea se escibren con una doble diagonal
(* Los comentarios multilínea usan parentesis (* . . . *)

-final del comentario multilínea- *)

// ================================================
// Syntaxis básica
// ================================================

// ------ "Variables" (pero no realmente) ------
// La palabra reservada "let" define un valor (inmutable)
let miEntero = 5
let miFlotante = 3.14
let miCadena = "hola"           // Tenga en cuenta que no es necesario ningún tipado

// ------ Listas ------
let dosACinco = [2;3;4;5]        // Los corchetes crean una lista con
                                 // punto y coma para delimitadores.
let unoACinco = 1 :: dosACinco   // :: Crea una lista con un nuevo elemento
// El resultado es [1;2;3;4;5]
let ceroACinco = [0;1] @ dosACinco   // @ Concatena dos listas

// IMPORTANTE: las comas no se usan para delimitar,
// solo punto y coma !

// ------ Funciones ------
// La palabra reservada "let" también define el nombre de una función.
let cuadrado x = x * x          // Tenga en cuenta que no se usa paréntesis.
cuadrado 3                      // Ahora, ejecutemos la función.
                              // De nuevo, sin paréntesis.

let agregar x y = x + y           // ¡No use add (x, y)! Eso significa
                              // algo completamente diferente.
agregar 2 3                       // Ahora, ejecutemos la función.

// Para definir una función en varias líneas, usemos la sangría.
// Los puntos y coma no son necesarios.
let pares lista =
   let esPar x = x%2 = 0     // Establece "esPar" como una función anidada
   List.filter esPar lista    // List.filter es una función de la biblioteca
                              // dos parámetros: una función que devuelve un
                              // booleano y una lista en la que trabajar

pares unoACinco               // Ahora, ejecutemos la función.

// Puedes usar paréntesis para aclarar.
// En este ejemplo, "map" se ejecuta primero, con dos argumentos,
// entonces "sum" se ejecuta en el resultado.
// Sin los paréntesis, "List.map" se pasará como argumento a List.sum.
let sumaDeCuadradosHasta100 =
   List.sum ( List.map cuadrado [1..100] )

// Puedes redirigir la salida de una función a otra con "|>"
// Redirigir datos es muy común en F#, como con los pipes de UNIX.

// Aquí está la misma función sumOfSquares escrita usando pipes
let sumaDeCuadradosHasta100piped =
   [1..100] |> List.map cuadrado |> List.sum  // "cuadrado" se declara antes

// Puede definir lambdas (funciones anónimas) gracias a la palabra clave "fun"
let sumaDeCuadradosHasta100ConFuncion =
   [1..100] |> List.map (fun x -> x*x) |> List.sum

// En F#, no hay palabra clave "return". Una función siempre regresa
// el valor de la última expresión utilizada.

// ------ Coincidencia de patrones ------
// Match..with .. es una sobrecarga de la condición de case/ switch.
let coincidenciaDePatronSimple =
   let x = "a"
   match x with
    | "a" -> printfn "x es a"
    | "b" -> printfn "x es b"
    | _ -> printfn "x es algo mas"   // guion bajo corresponde con todos los demás

// F# no permite valores nulos por defecto - debe usar el tipado de Option
// y luego coincide con el patrón.
// Some(..) y None son aproximadamente análogos a los envoltorios Nullable
let valorValido = Some(99)
let valorInvalido = None

// En este ejemplo, match..with encuentra una coincidencia con "Some" y "None",
// y muestra el valor de "Some" al mismo tiempo.
let coincidenciaDePatronDeOpciones entrada =
   match entrada with
    | Some i -> printfn "la entrada es un int=%d" i
    | None -> printfn "entrada faltante"

coincidenciaDePatronDeOpciones validValue
coincidenciaDePatronDeOpciones invalidValue

// ------ Viendo ------
// Las funciones printf/printfn son similares a las funciones
// Console.Write/WriteLine de C#.
printfn "Imprimiendo un int %i, a float %f, a bool %b" 1 2.0 true
printfn "Un string %s, y algo generico %A" "hola" [1;2;3;4]

// También hay funciones printf/sprintfn para formatear datos
// en cadena. Es similar al String.Format de C#.

// ================================================
// Mas sobre funciones
// ================================================

// F# es un verdadero lenguaje funcional - las funciones son
// entidades de primer nivel y se pueden combinar fácilmente
// para crear construcciones poderosas

// Los módulos se utilizan para agrupar funciones juntas.
// Se requiere sangría para cada módulo anidado.
module EjemploDeFuncion =

    // define una función de suma simple
    let agregar x y = x + y

    // uso básico de una función
    let a = agregar 1 2
    printfn "1+2 = %i" a

    // aplicación parcial para "hornear en" los parámetros (?)
    let agregar42 = agregar 42
    let b = agregar42 1
    printfn "42+1 = %i" b

    // composición para combinar funciones
    let agregar1 = agregar 1
    let agregar2 = agregar 2
    let agregar3 = agregar1 >> agregar2
    let c = agregar3 7
    printfn "3+7 = %i" c

    // funciones de primer nivel
    [1..10] |> List.map agregar3 |> printfn "la nueva lista es %A"

    // listas de funciones y más
    let agregar6 = [agregar1; agregar2; agregar3] |> List.reduce (>>)
    let d = agregar6 7
    printfn "1+2+3+7 = %i" d

// ================================================
// Lista de colecciones
// ================================================

// Il y a trois types de collection ordonnée :
// * Les listes sont les collections immutables les plus basiques
// * Les tableaux sont mutables et plus efficients
// * Les séquences sont lazy et infinies (e.g. un enumerator)
//
// Des autres collections incluent des maps immutables et des sets
// plus toutes les collections de .NET

module EjemplosDeLista =

    // las listas utilizan corchetes
    let lista1 = ["a";"b"]
    let lista2 = "c" :: lista1    // :: para una adición al principio
    let lista3 = lista1 @ lista2   // @ para la concatenación

    // Lista de comprensión (alias generadores)
    let cuadrados = [for i in 1..10 do yield i*i]

    //  Generador de números primos
    let rec tamiz = function
        | (p::xs) -> p :: tamiz [ for x in xs do if x % p > 0 then yield x ]
        | []      -> []
    let primos = tamiz [2..50]
    printfn "%A" primos

    // coincidencia de patrones para listas
    let listaDeCoincidencias unaLista =
        match unaLista with
        | [] -> printfn "la lista esta vacia"
        | [primero] -> printfn "la lista tiene un elemento %A " primero
        | [primero; segundo] -> printfn "la lista es %A y %A" primero segundo
        | _ -> printfn "la lista tiene mas de dos elementos"

    listaDeCoincidencias [1;2;3;4]
    listaDeCoincidencias [1;2]
    listaDeCoincidencias [1]
    listaDeCoincidencias []

    // Récursion en utilisant les listes
    let rec suma unaLista =
        match unaLista with
        | [] -> 0
        | x::xs -> x + suma xs
    suma [1..10]

    // -----------------------------------------
    // Funciones de la biblioteca estándar
    // -----------------------------------------

    // mapeo
    let agregar3 x = x + 3
    [1..10] |> List.map agregar3

    // filtrado
    let par x = x % 2 = 0
    [1..10] |> List.filter par

    // mucho más - consulte la documentación

module EjemploDeArreglo =

    // los arreglos usan corchetes con barras.
    let arreglo1 = [| "a";"b" |]
    let primero = arreglo1.[0]        // se accede al índice usando un punto

    // la coincidencia de patrones de los arreglos es la misma que la de las listas
    let coincidenciaDeArreglos una Lista =
        match unaLista with
        | [| |] -> printfn "la matriz esta vacia"
        | [| primero |] -> printfn "el arreglo tiene un elemento %A " primero
        | [| primero; second |] -> printfn "el arreglo es %A y %A" primero segundo
        | _ -> printfn "el arreglo tiene mas de dos elementos"

    coincidenciaDeArreglos [| 1;2;3;4 |]

    // La biblioteca estándar funciona como listas
    [| 1..10 |]
    |> Array.map (fun i -> i+3)
    |> Array.filter (fun i -> i%2 = 0)
    |> Array.iter (printfn "el valor es %i. ")

module EjemploDeSecuencia =

    // Las secuencias usan llaves
    let secuencia1 = seq { yield "a"; yield "b" }

    // Las secuencias pueden usar yield y
    // puede contener subsecuencias
    let extranio = seq {
        // "yield" agrega un elemento
        yield 1; yield 2;

        // "yield!" agrega una subsecuencia completa
        yield! [5..10]
        yield! seq {
            for i in 1..10 do
              if i%2 = 0 then yield i }}
    // prueba
    extranio |> Seq.toList

    // Las secuencias se pueden crear usando "unfold"
    // Esta es la secuencia de fibonacci
    let fib = Seq.unfold (fun (fst,snd) ->
        Some(fst + snd, (snd, fst + snd))) (0,1)

    // prueba
    let fib10 = fib |> Seq.take 10 |> Seq.toList
    printf "Los primeros 10 fib son %A" fib10

// ================================================
// Tipos de datos
// ================================================

module EejemploDeTipoDeDatos =

    // Todos los datos son inmutables por defecto

     // las tuplas son tipos anónimos simples y rápidos
     // - Usamos una coma para crear una tupla
    let dosTuplas = 1,2
    let tresTuplas = "a",2,true

    // Combinación de patrones para desempaquetar
    let x,y = dosTuplas  // asignado x=1 y=2

    // ------------------------------------
    // Los tipos de registro tienen campos con nombre
    // ------------------------------------

    // Usamos "type" con llaves para definir un tipo de registro
    type Persona = {Nombre:string; Apellido:string}

    // Usamos "let" con llaves para crear un registro
    let persona1 = {Nombre="John"; Apellido="Doe"}

    // Combinación de patrones para desempaquetar
    let {Nombre=nombre} = persona1    // asignado nombre="john"

    // ------------------------------------
    // Los tipos de unión (o variantes) tienen un conjunto de elección
     // Solo un caso puede ser válido a la vez.
    // ------------------------------------

    // Usamos "type" con barra/pipe para definir una unión estándar
    type Temp =
        | GradosC of float
        | GradosF of float

    // Una de estas opciones se usa para crear una
    let temp1 = GradosF 98.6
    let temp2 = GradosC 37.0

    // Coincidencia de patrón en todos los casos para desempaquetar (?)
    let imprimirTemp = function
       | GradosC t -> printfn "%f gradC" t
       | GradosF t -> printfn "%f gradF" t

    imprimirTemp temp1
    imprimirTemp temp2

    // ------------------------------------
    // Tipos recursivos
    // ------------------------------------

    // Los tipos se pueden combinar recursivamente de formas complejas
    // sin tener que crear subclases
    type Empleado =
      | Trabajador of Persona
      | Gerente of Empleado lista

    let jdoe = {Nombre="John";Apellido="Doe"}
    let trabajador = Trabajador jdoe

    // ------------------------------------
    // Modelado con tipados (?)
    // ------------------------------------

    // Los tipos de unión son excelentes para modelar el estado sin usar banderas (?)
    type DireccionDeCorreo =
        | DireccionDeCorreoValido of string
        | DireccionDeCorreoInvalido of string

    let intentarEnviarCorreo correoElectronico =
        match correoElectronico with // uso de patrones de coincidencia
        | DireccionDeCorreoValido direccion -> ()   // enviar
        | DireccionDeCorreoInvalido direccion -> () // no enviar

    // Combinar juntos, los tipos de unión y tipos de registro
     // ofrece una base excelente para el diseño impulsado por el dominio.
     // Puedes crear cientos de pequeños tipos que reflejarán fielmente
     // el dominio.

    type ArticuloDelCarrito = { CodigoDelProducto: string; Cantidad: int }
    type Pago = Pago of float
    type DatosActivosDelCarrito = { ArticulosSinPagar: ArticuloDelCarrito lista }
    type DatosPagadosDelCarrito = { ArticulosPagados: ArticuloDelCarrito lista; Pago: Pago}

    type CarritoDeCompras =
        | CarritoVacio  // sin datos
        | CarritoActivo of DatosActivosDelCarrito
        | CarritoPagado of DatosPagadosDelCarrito

    // ------------------------------------
    // Comportamiento nativo de los tipos
    // ------------------------------------

    // Los tipos nativos tienen el comportamiento más útil "listo para usar", sin ningún código para agregar.
     // * Inmutabilidad
     // * Bonita depuración de impresión
     // * Igualdad y comparación
     // * Serialización

     // La impresión bonita se usa con %A
    printfn "dosTuplas=%A,\nPersona=%A,\nTemp=%A,\nEmpleado=%A"
             dosTuplas persona1 temp1 trabajador

    // La igualdad y la comparación son innatas
     // Aquí hay un ejemplo con tarjetas.
    type JuegoDeCartas = Trebol | Diamante | Espada | Corazon
    type Rango = Dos | Tres | Cuatro | Cinco | Seis | Siete | Ocho
                | Nueve | Diez | Jack | Reina | Rey | As

    let mano = [ Trebol,As; Corazon,Tres; Corazon,As;
                 Espada,Jack; Diamante,Dos; Diamante,As ]

    // orden
    List.sort mano |> printfn "la mano ordenada es (de menos a mayor) %A"
    List.max mano |> printfn "la carta más alta es%A"
    List.min mano |> printfn "la carta más baja es %A"

// ================================================
// Patrones activos
// ================================================

module EjemplosDePatronesActivos =

    // F# tiene un tipo particular de coincidencia de patrón llamado "patrones activos"
    // donde el patrón puede ser analizado o detectado dinámicamente.

    // "clips de banana" es la sintaxis de los patrones activos

    // por ejemplo, definimos un patrón "activo" para que coincida con los tipos de "caracteres" ...
    let (|Digito|Latra|EspacioEnBlanco|Otros|) ch =
       if System.Char.IsDigit(ch) then Digito
       else if System.Char.IsLetter(ch) then Letra
       else if System.Char.IsWhiteSpace(ch) then EspacioEnBlanco
       else Otros

    // ... y luego lo usamos para hacer que la lógica de análisis sea más clara
    let ImprimirCaracter ch =
      match ch with
      | Digito -> printfn "%c es un Digito" ch
      | Letra -> printfn "%c es una Letra" ch
      | Whitespace -> printfn "%c es un Espacio en blanco" ch
      | _ -> printfn "%c es algo mas" ch

    // ver una lista
    ['a';'b';'1';' ';'-';'c'] |> List.iter ImprimirCaracter

    // -----------------------------------------
    // FizzBuzz usando patrones activos
    // -----------------------------------------

    // Puede crear un patrón de coincidencia parcial también
    // Solo usamos un guión bajo en la definición y devolvemos Some si coincide.
    let (|MultDe3|_|) i = if i % 3 = 0 then Some MultDe3 else None
    let (|MultDe5|_|) i = if i % 5 = 0 then Some MultDe5 else None

    // la función principal
    let fizzBuzz i =
      match i with
      | MultDe3 & MultDe5 -> printf "FizzBuzz, "
      | MultDe3 -> printf "Fizz, "
      | MultDe5 -> printf "Buzz, "
      | _ -> printf "%i, " i

    // prueba
    [1..20] |> List.iter fizzBuzz

// ================================================
// concisión
// ================================================

module EjemploDeAlgoritmo =

    // F# tiene una alta relación señal / ruido, lo que permite leer el código
    // casi como un algoritmo real

    // ------ Ejemplo: definir una función sumaDeCuadrados ------
    let sumaDeCuadrados n =
       [1..n]                   // 1) Tome todos los números del 1 al n
       |> List.map cuadrado     // 2) Elevar cada uno de ellos al cuadrado
       |> List.sum              // 3) Realiza su suma

    // prueba
    sumaDeCuadrados 100 |> printfn "Suma de cuadrados = %A"

    // ------ Ejemplo: definir una función de ordenación ------
    let rec ordenar lista =
       match lista with
       // Si la lista está vacía
       | [] ->
            []                              // devolvemos una lista vacía
       // si la lista no está vacía
       | primerElemento::otrosElementos ->  // tomamos el primer elemento
            let elementosMasPequenios =     // extraemos los elementos más pequeños
                otrosElementos              // tomamos el resto
                |> List.filter (fun e -> e < primerElemento)
                |> ordenar                  // y los ordenamos
            let elementosMasGrandes =       // extraemos el mas grande
                otrosElementos              // de los que permanecen
                |> List.filter (fun e -> e >= primerElemento)
                |> ordenar                  // y los ordenamos
            // Combinamos las 3 piezas en una nueva lista que devolvemos
            List.concat [elementosMasPequenios; [primerElemento]; elementosMasGrandes]

    // prueba
    ordenar [1;5;23;18;9;1;3] |> printfn "Ordenado = %A"

// ================================================
// Código asíncrono
// ================================================

module AsyncExample =

    // F# incluye características para ayudar con el código asíncrono
    // sin conocer la "pirámide del destino"
    //
    // El siguiente ejemplo descarga una secuencia de página web en paralelo.

    open System.Net
    open System
    open System.IO
    open Microsoft.FSharp.Control.CommonExtensions

    // Recuperar el contenido de una URL de forma asincrónica
    let extraerUrlAsync url =
        async {   // La palabra clave "async" y llaves
                  // crear un objeto "asincrónico"
            let solicitud = WebRequest.Create(Uri(url))
            use! respuesta = solicitud.AsyncGetResponse()
                // use! es una tarea asincrónica
            use flujoDeDatos = resp.GetResponseStream()
                // "use" dispara automáticamente la funcion close()
                // en los recursos al final de las llaves
            use lector = new IO.StreamReader(flujoDeDatos)
            let html = lector.ReadToEnd()
            printfn "terminó la descarga %s" url
            }

    // una lista de sitios para informar
    let sitios = ["http://www.bing.com";
                 "http://www.google.com";
                 "http://www.microsoft.com";
                 "http://www.amazon.com";
                 "http://www.yahoo.com"]

    // ¡Aqui vamos!
    sitios
    |> List.map extraerUrlAsync // crear una lista de tareas asíncrona
    |> Async.Parallel           // decirle a las tareas que se desarrollan en paralelo
    |> Async.RunSynchronously   // ¡Empieza!

// ================================================
// Compatibilidad .NET
// ================================================

module EjemploCompatibilidadNet =

    // F# puede hacer casi cualquier cosa que C# pueda hacer, y se ajusta
    // perfectamente con bibliotecas .NET o Mono.

    // ------- Trabaja con las funciones de las bibliotecas existentes  -------

    let (i1success,i1) = System.Int32.TryParse("123");
    if i1success then printfn "convertido como %i" i1 else printfn "conversion fallida"

    // ------- Implementar interfaces sobre la marcha! -------

    // Crea un nuevo objeto que implemente IDisposable
    let crearRecurso name =
       { new System.IDisposable
         with member this.Dispose() = printfn "%s creado" name }

    let utilizarYDisponerDeRecursos =
        use r1 = crearRecurso "primer recurso"
        printfn "usando primer recurso"
        for i in [1..3] do
            let nombreDelRecurso = sprintf "\tinner resource %d" i
            use temp = crearRecurso nombreDelRecurso
            printfn "\thacer algo con %s" nombreDelRecurso
        use r2 = crearRecurso "segundo recurso"
        printfn "usando segundo recurso"
        printfn "hecho."

    // ------- Código orientado a objetos -------

    // F# es también un verdadero lenguaje OO.
    // Admite clases, herencia, métodos virtuales, etc.

    // interfaz de tipo genérico
    type IEnumerator<'a> =
        abstract member Actual : 'a
        abstract MoverSiguiente : unit -> bool

    // Clase base abstracta con métodos virtuales
    [<AbstractClass>]
    type Figura() =
        // propiedades de solo lectura
        abstract member Ancho : int with get
        abstract member Alto : int with get
        // método no virtual
        member this.AreaDelimitadora = this.Alto * this.Ancho
        // método virtual con implementación de la clase base
        abstract member Imprimir : unit -> unit
        default this.Imprimir () = printfn "Soy una Figura"

    // clase concreta que hereda de su clase base y sobrecarga
    type Rectangulo(x:int, y:int) =
        inherit Figura()
        override this.Ancho = x
        override this.Alto = y
        override this.Imprimir ()  = printfn "Soy un Rectangulo"

    // prueba
    let r = Rectangulo(2,3)
    printfn "La anchura es %i" r.Ancho
    printfn "El area es %i" r.AreaDelimitadora
    r.Imprimir()

    // ------- extensión de método  -------

    // Al igual que en C#, F# puede extender las clases existentes con extensiones de método.
    type System.String with
       member this.EmpiezaConA = this.EmpiezaCon "A"

    // prueba
    let s = "Alice"
    printfn "'%s' empieza con una 'A' = %A" s s.EmpiezaConA

    // ------- eventos -------

    type MiBoton() =
        let eventoClick = new Event<_>()

        [<CLIEvent>]
        member this.AlHacerClick = eventoClick.Publish

        member this.PruebaEvento(arg) =
            eventoClick.Trigger(this, arg)

    // prueba
    let miBoton = new MiBoton()
    miBoton.AlHacerClick.Add(fun (sender, arg) ->
            printfn "Haga clic en el evento con arg=%O" arg)

    miBoton.PruebaEvento("Hola Mundo!")

Más información

Para más demostraciones de F#, visite el sitio Try F#, o sigue la serie why use F#.

Aprenda más sobre F# en fsharp.org.