Apuntes
Lenguaje de programación
- Familia: ML (SML u OCaml)
Notación matemática: la sintaxis de su código fuente se basa en la declaración de tipos y valores. En apariencia se definen ecuaciones, igualdades o relaciones:
> let (x:byte) = 123uy;; val x : byte = 123uy > let (x:string) = "Hola mundo!";; val x : string = "Hola mundo!" > let x = 2 * 5;; val x : int = 10 > let y = x = 10;; val y : bool = true > let z = if 4 = 2 + 3 then 10 else 20;; val z : int = 20 > let z = if (4 = (2 + 3)) then 10 else 20;; val z : int = 20
Compilado e Interpretado: el código fuente puede ser compilado (extensión fs) o bien interpretado (extensión fsx). A partir del código fuente, el compilador (
fsc.exe
ofsharpc
) generará un archivo binario ejecutable mientras que el interprete (fsi.exe
ofsharpi
) permitirá interactuar con él.Tipado Estático: los tipos de datos son identificados (inferidos) en tiempo de compilación manteniendose durante la ejecución del programa. Debido a la existencia de la inferencia de tipo no se requiere especificar a estos.
> "Hola" + " " + "mundo";; val it : string = "Hola mundo" > 5 + 10 + 15;; val it : int = 30 > 5.4 + 10.3 + 15.3;; val it : float = 31.0
Evaluación de expresiones: en el código fuente se establecen expresiones las cuales son evaluadas o resueltas estrictamente. Las expresiones se escriben mediante ecuaciones.
Funcional: las funciones son ciudadanos de primera clase por lo que pueden ser usados como parámetros o valores regresados por otras funciones.
> fun () -> "Hola" + " " + "mundo";; val it : unit -> string = <fun:clo@22-6> > (fun () -> "Hola" + " " + "mundo")();; val it : string = "Hola mundo" > (fun x -> 2 * x)(6);; val it : int = 12 > (fun f -> f 6)(fun x -> 2 * x);; val it : int = 12 > ((fun x -> fun y -> x - y)(5))(8);; val it : int = -3
Expresiones
Literales:
Unidad:
()
o unit, representa a un valor o resultado sin sentido o significado. Similar al concepto de void.> ();; val it : unit = ()
Números:
-567
,45
,6.91
,-5.348
- byte: desde
0
hasta255
. Sufijo: uy - sbyte: desde
-128
hasta127
. Sufijo: y - int16: desde
-32768
hasta32767
. Sufijo: s - uint16: desde
0
hasta65535
. Sufijo: us - int: desde
-2,147,483,648
hasta2,147,483,647
. - uint32: desde
0
hasta4,294,967,295
. Sufijo: u - int64: desde
-9,223,372,036,854,775,808
hasta9,223,372,036,854,775,807
. Sufijo: L - uint64: desde
0
hasta18,446,744,073,709,551,615
. Sufijo: UL - float32: de 32-bits (desde
-3.402823e38
hasta3.402823e38
) Sufijo: f - float: de 64-bits (desde
-1.79769313486232e308
hasta1.79769313486232e308
)
- byte: desde
- Booleanos:
- bool:
true
yfalse
.
- bool:
- Caracteres:
- char:
'a'
,'\\'
,'@'
,' '
,'\t'
.
- char:
- Cadenas de caracteres:
- string:
"Hola mundo!"
,"a"
,"\\"
,"Hola\t\t\tmundo\t\t\t!"
.
- string:
Variables:
- Nombres dado a valores:
x
,y
,altura
,base
.
Colecciones:
Tuplas: agrupación de valores literales de distinto tipo en orden.
> (10, "Hola", true, '@');; val it : int * string * bool * char = (10, "Hola", true, '@')
Listas: agrupación de valores literales de un mismo tipo en orden.
Lista de cadenas de caracteres (
string list
):> ["Hola"; " "; "mundo"; "!"];; val it : string list = ["Hola"; " "; "mundo"; "!"]
Lista de números enterors (
int list
):> [10; 20; 30; 40; 50];; val it : int list = [10; 20; 30; 40; 50]
Lista de booleanos (
bool list
):> [true; false; false; true];; val it : bool list = [true; false; false; true]
Arreglos: agrupación de valores literales de un mismo tipo, indexados y en orden.
Arreglo de cadenas de caracteres (
string[]
):> [|"Hola"; " "; "mundo"; "!"|];; val it : string [] = [|"Hola"; " "; "mundo"; "!"|]
Arreglo de números enterors (
int[]
):> [|10; 20; 30; 40; 50|];; val it : int [] = [|10; 20; 30; 40; 50|]
Acceso a uno de los elementos en base a su indice:
> [|10; 20; 30; 40; 50|].[2];; val it : int = 30 > [|10; 20; 30; 40; 50|].[2] + [|10; 20; 30; 40; 50|].[4];; val it : int = 80
Arreglo de booleanos (
bool[]
):> [|true; false; false; true|];; val it : bool [] = [|true; false; false; true|]
Registros: agrupación de pares llave-valor de distinto tipo sin orden.
Definición:
> type Cuadrado = { lado : float };; type Cuadrado = {lado: float;}
Uso:
> {lado = 3.5};; val it : Cuadrado = {lado = 3.5;} > let unCuadradoPequeño = {lado = 2.5};; val unCuadradoPequeño : Cuadrado = {lado = 2.5;} > let unCuadradoMásGrande = {lado = 7.2};; val unCuadradoMásGrande : Cuadrado = {lado = 7.2;}
Definición:
> type Triangulo = { baseDelTriangulo : float; alturaDelTriangulo : float; };; type Triangulo = {baseDelTriangulo: float; alturaDelTriangulo: float;}
Uso:
> {baseDelTriangulo = 50.0; alturaDelTriangulo = 3.0};; val it : Triangulo = {baseDelTriangulo = 50.0; alturaDelTriangulo = 3.0;} > let unTriangulito = {baseDelTriangulo = 0.5; alturaDelTriangulo = 0.3};; val unTriangulito : Triangulo = {baseDelTriangulo = 0.5; alturaDelTriangulo = 0.3;} > let unTriangulon = {baseDelTriangulo = 5000.0; alturaDelTriangulo = 30000.0};; val unTriangulon : Triangulo = {baseDelTriangulo = 5000.0; alturaDelTriangulo = 30000.0;}
Definición:
> type Alumno = { clave : string; nombre : string; apellidos : string; edad : int; sexo : char; };; type Alumno = {clave: string; nombre: string; apellidos: string; edad: int; sexo: char;}
Uso:
> { clave = "a123"; nombre = "Juan"; apellidos = "Perez Perez"; edad = 13; sexo = 'm'; };; val it : Alumno = {clave = "a123"; nombre = "Juan"; apellidos = "Perez Perez"; edad = 13; sexo = 'm';} > let unaBodoquita = { clave = "a123"; nombre = "Juana"; apellidos = "Perez Perez"; edad = 14; sexo = 'f'; };; val unaBodoquita : Alumno = {clave = "a123"; nombre = "Juana"; apellidos = "Perez Perez"; edad = 14; sexo = 'f';}
Comentarios:
Los caracteres
//
inician el comentario hasta el fin de la línea en que se encuentran:// Este es un comentario, de una sola línea
Para comentarios de varías líneas, iniciar con
(*
y terminar con*)
.(* Este es un comentario de varías líneas *)
Lectura de evaluación de expresiones
Siendo F# un lenguaje de programación con un sistema de tipos fuerte y la existencia en él de la inferencia de tipos, se vuelve importante el saber leer las respuestas dadas por el interprete con respecto a los tipos de datos.
El valor de eso tiene tipo byte con signo (signed byte) y es el -12:
> -12y;; val it : sbyte = -12y
El valor de eso tiene tipo byte sin signo (unsigned byte) y es el 130:
> 130uy;; val it : byte = 130uy
El valor de eso tiene tipo cadena de caracteres (string) y es "Hola mundo":
> "Hola mundo!";; val it : string = "Hola mundo!"
El valor de eso tiene tipo booleano (bool) y es true:
> true && true;; val it : bool = true
El valor de eso tiene tipo booleano (bool) y es false:
> not true;; val it : bool = false
El valor de eso tiene tipo carácter (char) y es el carácter 1:
> '1';; val it : char = '1'
Tuplas
El valor de eso tiene tipo bool seguido de int seguido de string seguido de float y es true seguido de 9 seguido de ! seguido de 4.3:
> (true, 9, "!", 4.3);; val it : bool * int * string * float = (true, 9, "!", 4.3)
Funciones
Función anónima
Su definición se establece mediante la palabra reservada fun y al carecer de nombre su uso práctico implica su aplicación inmediata, ser usada como parámetro/devolución de otras funciones o bien ser enlazada (binding) en un ámbito.
Importante: el tema de tipo (tipado o sistema de tipos en Teoría de Lenguajes de Programación) cobra una mayor importancia ya que se entendería a una función como la relación/asociación entre un elemento de un conjunto-tipo, con algún otro elemento de otro conjunto-tipo.
> fun (x:byte) -> (x + x):byte;;
val it : x:byte -> byte = <fun:clo@9-20>
Al ser aplicada la función anónima con 10uy
(unsigned byte) como valor de tipo byte
, se obtiene a 20uy
como
valor de tipo byte
:
> (fun (x:byte) -> (x + x):byte)(10uy);;
val it : byte = 20uy
Siguiendo la misma noción de tipo, aplicando la misma función anónima con 10
(signed int) se obtiene un
error de tipo:
> (fun (x:byte) -> (x + x):byte)(10);;
(fun (x:byte) -> (x + x):byte)(10);;
-------------------------------^^
error FS0001: This expression was expected to have type
byte
but here has type
int
Considerando lo anterior, se puede definir y aplicar la siguiente función anónima:
> (fun (x:int) -> (x + x):int)(10);;
val it : int = 20
> (fun (x:int) -> (x + x):int)(-10);;
val it : int = -20
Lectura:
Es importante saber leer la firma o definición de una función:
Función que va de byte a byte:
Aplicaciones:
> (fun (x:byte) -> (x + x):byte)(127uy);; val it : byte = 254uy > (fun (x:byte) -> (x + x):byte)(128uy);; val it : byte = 0uy > (fun (x:byte) -> (x + x):byte)(129uy);; val it : byte = 2uy
Función que va de int a int:
> fun (x:int) -> (x + x):int;; val it : x:int -> int = <fun:clo@52-4> > abs;; val it : (int -> int) = <fun:it@3>
Aplicaciones:
> (fun (x:int) -> (x + x):int)(128);; val it : int = 256 > (fun (x:int) -> (x + x):int)(129);; val it : int = 258 > abs -234;; val it : int = 234
Función que va de char a int:
> (fun (x:char) -> int x);; val it : x:char -> int = <fun:clo@18-1> > (fun (x:char) -> int x)('A');; val it : int = 65 > (fun (x:char) -> int x)('B');; val it : int = 66 > (fun (x:char) -> int x)('a');; val it : int = 97 > (fun (x:char) -> int x)('b');; val it : int = 98
Función que va de string a string:
> fun (x:string) -> (x + x):string;; val it : x:string -> string = <fun:clo@3-18>
Aplicaciones:
> (fun (x:string) -> (x + x):string)("Hola");; val it : string = "HolaHola" > (fun (x:string) -> (x + x):string)("mundo!");; val it : string = "mundo!mundo!"
Función que va de int a float:
> float;; val it : (int -> float) = <fun:it@53-2>
Aplicaciones:
> float 10;; val it : float = 10.0 > float -37;; val it : float = -37.0
Función que va de un objeto generico/literal simple a string:
> string;; val it : (obj -> string) = <fun:it@58-4>
Aplicaciones:
> string 10;; val it : string = "10" > string true;; val it : string = "True" > string -0.44;; val it : string = "-0.44"
Función que va de string a int:
> fun (x:string) -> (x.Length):int;; val it : x:string -> int = <fun:clo@4-5>
Aplicaciones:
> (fun (x:string) -> (x.Length):int)("Hola mundo!");; val it : int = 11 > (fun (x:string) -> (x.Length):int)("Adios mundo!");; val it : int = 12 > (fun (x:string) -> (x.Length):int)("Hola" + "mundo!");; val it : int = 10
Función que va de bool a string:
> fun (x:bool) -> (if x = true then "Hola" else "Adios"):string;; val it : x:bool -> string = <fun:clo@8-6>
Aplicaciones:
> (fun (x:bool) -> (if x = true then "Hola" else "Adios"):string)(true);; val it : string = "Hola" > (fun (x:bool) -> (if x = true then "Hola" else "Adios"):string)(false);; val it : string = "Adios"
Error de tipo:
> (fun (x:bool) -> (if x = true then "Hola" else "Adios"):string)(-345);; (fun (x:bool) -> (if x = true then "Hola" else "Adios"):string)(-345);; ----------------------------------------------------------------^^^^ error FS0001: This expression was expected to have type bool but here has type int
Función que va de int a bool:
> (fun (x:int) -> (x > 0):bool);; val it : x:int -> bool = <fun:clo@26-2>
Aplicaciones:
> (fun (x:int) -> (x > 0):bool)(-38);; val it : bool = false > (fun (x:int) -> (x > 0):bool)(943);; val it : bool = true
Función que va de string a bool:
> (fun (x:string) -> (if x.Length <> 0 then false else true):bool);; val it : x:string -> bool = <fun:clo@43-5> > (fun (x:string) -> (if x.Length <> 0 then false else true):bool)("Hola");; val it : bool = false > (fun (x:string) -> (if x.Length <> 0 then false else true):bool)("");; val it : bool = true
Función de orden superior
Es una función (anónima o no-anónima) la cual puede recibir como parámetro alguna función, regresar una función como resultado o bien ambos casos.
> fun (baseDelTriangulo:float) -> (fun (alturaDelTriangulo:float) -> (baseDelTriangulo * alturaDelTriangulo) / 2.0);;
val it : baseDelTriangulo:float -> alturaDelTriangulo:float -> float = <fun:clo@3-13>
Lo anterior se entiende como una función que va de float a una función que va de float a float, o de otra forma:
> fun (baseDelTriangulo:float) ->
(fun (alturaDelTriangulo:float) ->
(baseDelTriangulo * alturaDelTriangulo) / 2.0);;
val it : baseDelTriangulo:float -> alturaDelTriangulo:float -> float = <fun:clo@3-14>
Se entiende que una función recibe la baseDelTriangulo
, esta regresa una función que espera la alturaDelTriangulo
y esta
última función es quien computaría la expresión (baseDelTriangulo * alturaDelTriangulo) / 2.0)
:
Aplicación de la función que recibe la
baseDelTriangulo
:> (fun (baseDelTriangulo:float) -> (fun (alturaDelTriangulo:float) -> (baseDelTriangulo * alturaDelTriangulo) / 2.0))(50.0);; val it : (float -> float) = <fun:it@19-5>
Aplicación de la función que recibe la
alturaDelTriangulo
:> ((fun (baseDelTriangulo:float) -> (fun (alturaDelTriangulo:float) -> (baseDelTriangulo * alturaDelTriangulo) / 2.0))(50.0))(3.0);; val it : float = 75.0
Bindings
Un binding (enlace o vínculo) es el establecimiento de un nombre con un valor dentro de cierto ámbito.
let
Establece el enlace entre un identificador y un valor dentro de cierto ámbito: el identificador será el nombre de la variable y el valor será el resultado de la evaluación de una expresión.
> let elAntecesorDe = fun (x:int) -> (x - 1):int;;
val elAntecesorDe : x:int -> int
> let elSucesorDe = fun (x:int) -> (x + 1):int;;
val elSucesorDe : x:int -> int
> let unSaludoPara = fun (nombre:string) -> ("Hola " + nombre):string;;
val unSaludoPara : nombre:string -> string
> elAntecesorDe 10;;
val it : int = 9
> elSucesorDe 20;;
val it : int = 21
> unSaludoPara "mundo!";;
val it : string = "Hola mundo!"
Es posible usar a let para enlazar de forma local a variables:
Definición: Sea elAntecesorYElSucesorDe una función que va de int a int seguido de int:
> let elAntecesorYElSucesorDe = fun (x:int) -> let elAntecesorDe = fun (x:int) -> (x - 1):int let elSucesorDe = fun (x:int) -> (x + 1):int (elSucesorDe x, elAntecesorDe x);; val elAntecesorYElSucesorDe : x:int -> int * int
Aplicación: El valor de eso tiene tipo int seguido de int y es el 16 seguido de el 14:
> elAntecesorYElSucesorDe 15;; val it : int * int = (16, 14)
Lectura de definición y aplicación de funciones
La definición y aplicación de funciones en F# puede parecer confusa en ocasiones debido a:
- Presencia de un sistema de tipo fuerte.
- Existe la inferencia de tipos.
- Sintaxis basada en la notación matemática, por lo que:
- Suele evitarse el abuso de paréntesis.
- Existe la misma idea de precedencia de operadores, encontrada en otros lenguajes de programación.
Si en LISP uno abusa de los paréntesis, en ML uno suele evitarlos ;-)
Un documento que establece una critica abierta a LISP es Why calculating is better than scheming por Philip Wadler, uno de los diseñadores del lenguaje de programación Haskell y contribuyente al lenguaje de programación Java. Lectura: Por que calcular es mejor que esquematizar?.
Función anónima con anotación del tipo de parámetro y anotación del tipo de valor devuelto:
> fun (x:float) -> (x * x):float;; val it : x:float -> float = <fun:clo@46-11> > (fun (x:float) -> (x * x):float)(2.5);; val it : float = 6.25
Misma función: no se requiere anotar el tipo de valor devuelto por la inferencia de tipo a partir del tipo de parámetro y el uso de la función ____*:
> (fun (x:float) -> x * x)(2.5);; val it : float = 6.25
Misma función: no se requieren de los paréntesis en el parámetro ya que la definición de la función espera un único parámetro. La asociación de operandos y operadores es de izquierda a derecha:
> (fun (x:float) -> x * x) 2.5;; val it : float = 6.25
Esta última forma sería "la más común".
Función anónima que recibe un solo parámetro que contiene un float seguido de un float, regresando un float:
> fun (x:float, y:float) -> (x * y):float;; val it : x:float * y:float -> float = <fun:clo@48-16> > (fun (x:float, y:float) -> (x * y):float)(2.5, 2.0);; val it : float = 5.0
Misma función:
> (fun (x:float, y:float) -> x * y)(2.5, 2.0) val it : float = 5.0
Función anónima que recibe dos parámetros, primero un float y despúes otro float:
> fun (x:float) (y:float) -> (x * y):float;; val it : x:float -> y:float -> float = <fun:clo@50-18>
A lo anterior se le puede conocer como Función Currificada o Función en forma Curry (en honor de Haskell Curry). Su lectura sería: función que va de float (
x
) a una función que va de float (y
) a float (x * y
).La currificación (palabra inventada) de una función es una técnica mediante la cual una función de n parámetros es transformada en una función que recibe el primer parámetro regresa una función que espera el próximo parámetro y esta a su vez regresa una función que espera el siguiente parámetro hasta obtener los n parámetros.
Otra forma de observar lo anterior es: todas las funciones son definidas con un solo parámetro.
Misma función:
> (fun (x:float) (y:float) -> (x * y)) 2.5;; val it : (float -> float) = <fun:it@50-4> > (fun (x:float) (y:float) -> (x * y)) 2.5 2.0;; val it : float = 5.0
Función currificada sin anotación de los tipos de parámetros y sin anotación del tipo del valor devuelto tomando ventaja de la inferencia de tipos:
> (fun x y z -> 1.0 * x * y * z) 2.5 2.0 3.0;; val it : float = 15.0
La currificación de funciones implica la existencia de closures: la tercera y última función, la que espera a
z
, aún tiene acceso al parámetrox
de la primera función.> (fun x y z -> 1.0 * x) 2.5 2.0 3.0;; val it : float = 2.5
F# previene el ocultamiento (shadowing) de los parámetros:
> (fun x x x -> 1.0 * x) 2.5 2.0 3.0;; error FS0038: 'x' is bound twice in this pattern
Aunque es posible aún contemplar el ocultamiento de la siguiente forma, poco común:
> (fun x -> fun x -> fun x -> 2.0 * x) 2.5 2.0 3.0;; val it : float = 6.0
Función con nombre: la función se llama areaDeUnTriangulo y va de un float a una función que va de un float a float. Ambas expresiones definen la misma función: observa lo impreso por el interprete:
> let areaDeUnTriangulo = fun baseDelTriangulo -> fun alturaDelTriangulo -> (baseDelTriangulo * alturaDelTriangulo) / 2.0 val areaDeUnTriangulo : baseDelTriangulo:float -> alturaDelTriangulo:float -> float
O bien:
> let areaDeUnTriangulo baseDelTriangulo alturaDelTriangulo = (baseDelTriangulo * alturaDelTriangulo) / 2.0 val areaDeUnTriangulo : baseDelTriangulo:float -> alturaDelTriangulo:float -> float
Aplicación de la función:
> areaDeUnTriangulo 5.0 30.0;; val it : float = 75.0
Aspectos de la inferencia de tipo
Sin lugar a duda la inferencia de tipo ayuda a una escritura mucho más ligera del código en F#. Sin embargo es necesario prestar atención cuando se establecen algunas expresiones para las cuales no se puede resolver con exactitud su tipo, debido a la falta de información.
> let f x = if x then "hola" else "adios";;
val f : x:bool -> string
En este caso, la función f va de bool a string. Sin embargo:
> let f x = x;;
val f : x:'a -> 'a
La anterior función es la función de identidad y no se ha podido inferir el tipo de x
en
f al faltar la información de lo que se ha de hacer con x
. El interprete solo logra indicar una
variable de tipo: 'a
. Una variable de tipo indica que puede ser cualquier tipo (generic type) el que
ocupe su lugar:
> f -234;;
val it : int = -234
> f true;;
val it : bool = true
> f "Hola mundo!";;
val it : string = "Hola mundo!"
> f (false, 4.2);;
val it : bool * float = (false, 4.2)
> f abs;;
val it : (int -> int) = <fun:it@7-5>
Por cuestiones "culturales" en algunos lenguajes de programación a las variables de tipo se les suele pronunciar como las
letras del alfabeto griego clásico, de tal forma que:
'a
es alfa, 'b
es beta, 'c
es gama, 'd
es delta, etc.
Ejemplos:
Función (
f
) que va de una función (g
) que va de bool a alfa a alfa:> let f g = g true;; val f : g:(bool -> 'a) -> 'a
Aplicaciones:
> f (fun x -> not x);; val it : bool = false > f (fun x -> if x then ":)" else ":(");; val it : string = ":)" > f (fun x -> (x, x));; val it : bool * bool = (true, true)
Función (
f
) que va dex
que tiene tipo alfa a una función que va deg
que va de alfa a beta a beta:> let f x = fun g -> g x;; val f : x:'a -> g:('a -> 'b) -> 'b
Aplicaciones:
> (f -5)(fun x -> 2 * x);; val it : int = -10 > (f -5)(fun x -> 3 * x);; val it : int = -15 > (f -5)(fun x -> if x < 0 then true else false);; val it : bool = true > (f true)(fun x -> if x then ":)" else ":(");; val it : string = ":)"
Sin embargo se ha de considerar la correcta aplicación de este tipo de funciones:
> f -5;; f -5;; ^^^^ error FS0030: Value restriction. The value 'it' has been inferred to have generic type val it : ((int -> '_a) -> '_a) Either make the arguments to 'it' explicit or, if you do not intend for it to be generic, add a type annotation.
Forward Pipe
Forwar Pipe (|>
) es un operador que dado una expresión a su izquierda y una función a su derecha, reenvía el valor de la
expresión a su izquierda como parámetro de la función a su derecha.
Partiendo de las siguientes funciones:
> let elTripleDe x = 3 * x;; val elTripleDe : x:int -> int > let laMitadDe x = x / 2;; val laMitadDe : x:int -> int
Haciendo uso de forwar pipe (
|>
):> elTripleDe 10 |> laMitadDe;; val it : int = 15 > elTripleDe 40 |> laMitadDe val it : int = 60
La mitad de 100 es 50, la mitad de 50 es 25, la mitad de 25 es 12:
> laMitadDe 100 |> laMitadDe |> laMitadDe val it : int = 12
Otras expresiones:
> [|3; 4; 5|].[0] |> fun x -> x > 0;; val it : bool = true > [|3; -4; 5|].[1] |> fun x -> x > 0;; val it : bool = false > let elTripleDelTripleDelPrimero (xs : int[]) = xs.[0] |> elTripleDe |> elTripleDe val elTripleDelTripleDelPrimero : xs:int [] -> int > elTripleDelTripleDelPrimero [|6; 7; 8|];; val it : int = 54
Funciones de orden superior
F#, al igual que otros lenguajes de programación funcional, cuenta con algunas funciones de orden superior.