Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La programación funcional es un estilo de programación que enfatiza el uso de funciones e datos inmutables. La programación funcional con tipo es cuando la programación funcional se combina con tipos estáticos, como con F#. En general, los conceptos siguientes se enfatizan en la programación funcional:
- Funciones como construcciones principales empleadas
- Expresiones en lugar de instrucciones
- Valores inmutables sobre variables
- Programación declarativa sobre programación imperativa
A lo largo de esta serie, explorará conceptos y patrones en la programación funcional mediante F#. Durante el proceso, también aprenderá algo de F#.
Terminología
La programación funcional, al igual que otros paradigmas de programación, viene con un vocabulario que finalmente necesitará aprender. Estos son algunos términos comunes que verá todo el tiempo:
- Función : una función es una construcción que generará una salida cuando se proporciona una entrada. De manera más formal, asigna un elemento de un conjunto a otro. Este formalismo se eleva en el concreto de muchas maneras, especialmente cuando se usan funciones que operan en colecciones de datos. Es el concepto más básico (y importante) de la programación funcional.
- Expresión : una expresión es una construcción en el código que genera un valor. En F#, este valor debe enlazarse o omitirse explícitamente. Una expresión se puede reemplazar trivialmente por una llamada de función.
- Pureza: la pureza es una propiedad de una función de modo que su valor devuelto siempre sea el mismo para los mismos argumentos y que su evaluación no tenga efectos secundarios. Una función pura depende completamente de sus argumentos.
- Transparencia referencial : transparencia referencial es una propiedad de expresiones de modo que se pueden reemplazar por su salida sin afectar al comportamiento de un programa.
- Inmutabilidad - La inmutabilidad significa que un valor no se puede cambiar en su lugar. Esto contrasta con las variables, que pueden cambiar en contexto.
Ejemplos
En los ejemplos siguientes se muestran estos conceptos básicos.
Funciones
La construcción más común y fundamental de la programación funcional es la función . Esta es una función sencilla que agrega 1 a un entero:
let addOne x = x + 1
Su signatura de tipo es la siguiente:
val addOne: x:int -> int
La firma se puede leer como "addOne
acepta un int
denominado x
y generará un int
". Más formalmente, addOne
es mapear un valor del conjunto de enteros al conjunto de enteros. El token ->
indica esta asignación. En F#, normalmente puede consultar la signatura de función para tener una idea de lo que hace.
Entonces, ¿por qué es importante la firma? En la programación funcional tipada, la implementación de una función suele ser menos importante que la firma de tipo concreta. El hecho de que addOne
agregue el valor 1 a un entero es interesante en tiempo de ejecución, pero al construir un programa, el hecho de que acepta y devuelve un int
es lo que determina cómo realmente usarás esta función. Además, una vez que use esta función correctamente (con respecto a su firma de tipo), el diagnóstico de cualquier problema solo se puede realizar dentro del cuerpo de la addOne
función. Este es el impulso detrás de la programación funcional tipada.
Expresiones
Las expresiones son construcciones que se evalúan para obtener un valor. A diferencia de las instrucciones, que realizan una acción, se puede considerar que las expresiones realizan una acción que devuelve un valor. Las expresiones se usan casi siempre en programación funcional en lugar de instrucciones .
Considere la función anterior, addOne
. El cuerpo de addOne
es una expresión:
// 'x + 1' is an expression!
let addOne x = x + 1
Es el resultado de esta expresión que define el tipo de resultado de la addOne
función. Por ejemplo, la expresión que compone esta función se podría cambiar para que sea un tipo diferente, como :string
let addOne x = x.ToString() + "1"
Ahora, la signatura de la función es la siguiente:
val addOne: x:'a -> string
Dado que cualquier tipo en F# puede tener ToString()
llamado sobre él, el tipo de x
se ha hecho genérico (denominado Generalización automática), y el tipo resultante es string
.
Las expresiones no son solo los cuerpos de las funciones. Puede tener expresiones que generen un valor que utilice en otros contextos. Una común es if
:
// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0
let addOneIfOdd input =
let result =
if isOdd input then
input + 1
else
input
result
La if
expresión genera un valor denominado result
. Tenga en cuenta que podría omitirse result
completamente, haciendo que la if
expresión sea el cuerpo de la addOneIfOdd
función. Lo clave que hay que recordar sobre las expresiones es que generan un valor.
Hay un tipo especial, unit
, que se usa cuando no hay nada que devolver. Por ejemplo, considere esta función simple:
let printString (str: string) =
printfn $"String is: {str}"
La firma tiene este aspecto:
val printString: str:string -> unit
El unit
tipo indica que no se devuelve ningún valor real. Esto resulta útil cuando tienes una rutina que debe "realizar trabajo" aunque no tenga ningún valor que devolver como resultado de ese trabajo.
Esto contrasta con la programación imperativa, donde la construcción equivalente if
es una instrucción y la generación de valores a menudo se realiza con variables mutantes. Por ejemplo, en C#, el código podría escribirse de la siguiente manera:
bool IsOdd(int x) => x % 2 != 0;
int AddOneIfOdd(int input)
{
var result = input;
if (IsOdd(input))
{
result = input + 1;
}
return result;
}
Cabe destacar que C# y otros lenguajes de estilo C admiten la expresión ternaria, lo que permite la programación condicional basada en expresiones.
En la programación funcional, es raro mutar valores con instrucciones . Aunque algunos lenguajes funcionales admiten instrucciones y mutaciones, no es habitual usar estos conceptos en la programación funcional.
Funciones puras
Como se mencionó anteriormente, las funciones puras son funciones que:
- Evalúe siempre el mismo valor para la misma entrada.
- No tiene efectos secundarios.
Resulta útil pensar en funciones matemáticas en este contexto. En matemáticas, las funciones dependen solo de sus argumentos y no tienen ningún efecto secundario. En la función f(x) = x + 1
matemática , el valor de f(x)
solo depende del valor de x
. Las funciones puras en la programación funcional son las mismas.
Al escribir una función pura, la función solo debe depender de sus argumentos y no realizar ninguna acción que produzca un efecto secundario.
Este es un ejemplo de una función no pura porque depende del estado global y mutable:
let mutable value = 1
let addOneToValue x = x + value
La addOneToValue
función es claramente impure, ya que value
podría cambiarse en cualquier momento para tener un valor diferente a 1. Este patrón de depender de un valor global debe evitarse en la programación funcional.
Este es otro ejemplo de una función no pura, ya que realiza un efecto secundario:
let addOneToValue x =
printfn $"x is %d{x}"
x + 1
Aunque esta función no depende de un valor global, escribe el valor de x
en la salida del programa. Aunque no hay nada inherentemente incorrecto al hacer esto, significa que la función no es pura. Si otra parte del programa depende de algo externo al programa, como el búfer de salida, llamar a esta función puede afectar a esa otra parte del programa.
Al quitar la printfn
instrucción, la función es pura:
let addOneToValue x = x + 1
Aunque esta función no es intrínsecamente mejor que la versión anterior con la printfn
instrucción , garantiza que toda esta función devuelve un valor. Llamar a esta función cualquier número de veces produce el mismo resultado: simplemente genera un valor. La previsibilidad dada por la pureza es algo que muchos programadores funcionales se esfuerzan por.
Inmutabilidad
Por último, uno de los conceptos más fundamentales de la programación funcional tipada es la inmutabilidad. En F#, todos los valores son inmutables de forma predeterminada. Esto significa que no se pueden mutar en contexto, a menos que los marque explícitamente como mutables.
En la práctica, trabajar con valores inmutables significa que cambia el enfoque a la programación de , "Necesito cambiar algo", a "Necesito generar un nuevo valor".
Por ejemplo, agregar 1 a un valor significa generar un nuevo valor, no mutar el existente:
let value = 1
let secondValue = value + 1
En F#, el código siguiente no muta la value
función; en su lugar, realiza una comprobación de igualdad:
let value = 1
value = value + 1 // Produces a 'bool' value!
Algunos lenguajes de programación funcionales no admiten la mutación en absoluto. En F#, se admite, pero no es el comportamiento predeterminado para los valores.
Este concepto se extiende aún más a las estructuras de datos. En la programación funcional, las estructuras de datos inmutables, como conjuntos (y muchos más) tienen una implementación diferente de la esperada inicialmente. Conceptualmente, algo parecido a agregar un elemento a un conjunto no cambia el conjunto, genera un nuevo conjunto con el valor agregado. En segundo plano, esto suele lograrse mediante una estructura de datos diferente que permite realizar un seguimiento eficaz de un valor para que la representación adecuada de los datos se pueda dar como resultado.
Este estilo de trabajo con valores y estructuras de datos es fundamental, ya que le obliga a tratar cualquier operación que modifique algo como si crea una nueva versión de esa cosa. Esto permite que las cosas como la igualdad y la comparabilidad sean coherentes en los programas.
Pasos siguientes
En la sección siguiente se tratarán exhaustivamente las funciones, explorando diferentes formas de usarlas en la programación funcional.
El uso de funciones en F# explora las funciones profundamente, lo que muestra cómo puede usarlas en varios contextos.
Lectura adicional
La serie Thinking Functionally es otro excelente recurso para aprender sobre la programación funcional con F#. Trata los aspectos básicos de la programación funcional de una manera pragmática y fácil de leer, mediante el uso de características de F# para ilustrar los conceptos.