Más sobre funciones
Hemos presentado las bases sobre las cuales se asienta el lenguaje en términos de funciones:
- Funciones como ciudadanos de primera clase en el lenguaje
- Composición de funciones
- Aplicación parcial
Veamos algunos ejemplos más como para ir progresando
let fullName firstName lastName =
let s = firstName + lastName
s
let john = fullName "John" "Lennon"
printfn "%A" john
"JohnLennon"
Vemos que se pueden definir otros identificadores dentro de las funciones, tal como esperábamos. En este caso, aparentemente, la función concatenaría los strings firstName
y lastName
en un solo string, que devuelve como resultado a través de s
.
Sin embargo, un punto interesante a notar con la función fullName
es que hasta tanto la función no se aplica sobre dos strings, el compilador no tiene manera de saber qué tipo de datos quiero usar. Esto se debe a que el operador +
en F# se usa tanto para sumar números como para concatenar strings. Si bien el nombre de la función induce a pensar que se van a usar strings, el compilador no hace un análisis semántico…
let fullNameS firstName lastName =
let s = firstName + " " + lastName
s
En el caso de fullNameS
el compilador infiere correctamente los tipos como strings, debido a que claramente el operador +
se está usando para concatenar, al utilizarlo con ` + “ “ +`.
Acá hay que aclarar una particularidad de los Notebooks: si defino la función en una celda, y la utilizo en otra, la función va a quedar asociada a inputs (y outputs) de tipo entero. Para que el compilador infiera correctamente los tipos de datos, debo utilizar la función en la misma celda. A partir de allí, quedarán definidos los tipos de datos para todo el Notebook.
Por supuesto se pueden llamar funciones dentro de otras funciones:
let prettyPrint s =
printfn "%A" s
let hola name =
prettyPrint ("Hola " + name + " !")
let paul = fullNameS "Paul" "McCartney"
prettyPrint paul
"Paul McCartney"
hola paul
"Hola Paul McCartney !"
Usamos los paréntesis para separar los llamados a funciones que llaman a funciones:
hola (fullNameS "George" "Harrison")
"Hola George Harrison !"
Podemos también usar el operador pipe:
paul
|> hola
"Hola Paul McCartney !"
Pero atención cuando uno usa varios inputs, porque el piping sólo acepta el último argumento cuando hay más de uno:
"George" "Harrison"
|> fullNameS
|> hola
input.fsx (1,1)-(1,9) typecheck error This value is not a function and cannot be applied.
input.fsx (3,4)-(3,8) typecheck error Type mismatch. Expecting a
'(string -> string) -> obj'
but given a
'string -> unit'
The type 'string -> string' does not match the type 'string'
"Harrison"
|> fullNameS "George"
|> hola
"Hola George Harrison !"
Veamos un ejemplo un poco más claro. Volviendo a nuestra máquina expendedora, supongamos que se conoce el costo de un paquete de galletitas marca ACME, que es el precio que paga el vendedor. A ese costo le aplica un porcentaje de ganancia, y además otro porcentaje de impuestos que traslada al consumidor final. Veamos cómo calcular el precio del paquete:
let priceAfterTax tax value =
let price = value * (1.0 + tax/100.0)
price
let priceAfterEarning earning value =
let price = value * (1.0 + earning/100.0)
price
let price tax earning value =
let p = priceAfterTax tax value
let q = priceAfterEarning earning p
q
let cost = 45.0
let tax = 21
let earning = 10
let finalPrice = price tax earning cost
prettyPrint finalPrice
59.895
O uno podría usar pipe:
cost
|> priceAfterEarning earning
|> priceAfterTax tax
59.89500000000001
Otra posibilidad es generalizar y crear una sola función para calcular el porcentaje
let applyPercentage per value =
value * (1.0 + per/100.0)
let priceAfterEarning2 = applyPercentage earning
let priceAfterTax2 = applyPercentage tax
cost
|> priceAfterEarning2
|> priceAfterTax2
59.89500000000001
Que es equivalente a componer las funciones:
let finalPrice = priceAfterEarning2 >> priceAfterTax2
printfn "%A" (finalPrice cost)
59.895