El pensamiento funcional: El pensamiento funcional, Parte 3

Filtrado, pruebas de unidad, y técnicas para la reutilización del código

El autor de las series El Pensamiento Funcional, Neal Ford, continúa su visita guiada de las construcciones y paradigmas de la programación funcional. Usted verá el número de código de clasificación en Scala y echará un vistazo a las pruebas de unidad en el mundo funcional. A continuación, aprenderá sobre la aplicación parcial y currying — dos enfoques funcionales que facilitan la reutilización del código— y vea cómo encaja la recurrencia en la manera funcional de pensar.

Neal Ford, Application Architect, ThoughtWorks Inc.

Photo of Neal FordNeal Ford es un arquitecto de software y Meme Wrangler en ThoughtWorks, una consultoría de TI a nivel mundial. También diseña y desarrolla aplicaciones, materiales didácticos, artículos de revistas, cursos y presentaciones de video/DVD, y es el autor o editor de libros que abarcan una variedad de tecnologías, incluyendo las más recientes. El programador productivo . Se centra en el diseño y la construcción de aplicaciones empresariales a gran escala.También es un orador de renombre internacional en conferencias de desarrolladores en todo el mundo.Eche un vistazo a suWeb site.



02-04-2012

Sobre esta serie

Esta serie tiene como objetivo reorientar su perspectiva hacia una mentalidad funcional, ayudándole a ver los problemas comunes de otra manera y a encontrar formas de mejorar su codificación del día a día de.Explorar los conceptos de programación funcional, la infraestructura que permite la programación funcional en el lenguaje Java, los lenguajes de programación funcionales que se ejecutan en la JVM, y algunas futuras direcciones de tendencia de diseño del lenguaje.La serie está orientada a los desarrolladores que conocen Java y saben cómo funcionan sus abstracciones, pero tienen poca o ninguna experiencia en la utilización de un lenguaje funcional.

En la primera y en la segunda cuota del Pensamiento funcional Inspeccioné algunos temas de programación funcional y como están relacionados con Java,™ y sus lenguajes afines.Esta cuota continúa la exploración, enseñando una versión de Scala del número clasificador de cuotas anteriores y comenta algunos temas con tinte académico, tales como aplicaciones parciales , de currying y parámetros derecurrencia.

Número clasificador en Scala

He estado guardando una versión de Scala del número clasificador para el final porque es la que contiene menos misterio sintáctico, al menos para los desarrolladores de Java.(Repasando las necesidades del clasificador: Dado cualquier entero positivo mayor que 1, se debe clasificar como perfecto,, abundante, o deficiente.. Un número perfecto es un número cuyos factores, excluyendo el propio número como un factor, se suma a la serie.Una suma de factores de un número abundante es mayor que el número, y una suma de factores de un número deficiente de los factores es menor.)El listado 1 muestra la versión de Scala:

Listado 1. Número clasificador en Scala
package com.nealford.conf.ft.numberclassifier

object NumberClassifier {

  def isFactor(number: Int, potentialFactor: Int) =
    number % potentialFactor == 0

  def factors(number: Int) =
    (1 to number) filter (number % _ == 0)

  def sum(factors: Seq[Int]) =
    factors.foldLeft(0)(_ + _)

  def isPerfect(number: Int) =
    sum(factors(number)) - number == number

  def isAbundant(number: Int) =
    sum(factors(number)) - number > number

  def isDeficient(number: Int) =
    sum(factors(number)) - number < number
}

Incluso si usted nunca ha visto Scala hasta ahora, este código debe ser bastante legible. Como antes, los dos métodos de interés son los factors() y la sum(). El método factors () toma la lista de números del 1 al número de destino y la aplica al método integrado de Scala filter() , utilizando el bloque de código en el lado derecho como el criterio de filtrado (también conocido como predicado.). El bloque del código se aprovecha del parámetro implícito de Scala, el cual permite un marcador sin nombre (el carácter _ ) cuando no se necesita una variable con nombre.Gracias a la flexibilidad sintáctica de Scala, usted puede llamar al método defilter() de la misma manera que llama a un operador.Si lo prefiere, (1 a número).filtro((número % _ == 0)) también funciona.

El método sum() utiliza las hasta ahora familiar operación Fold left (en Scala, implementado como el método foldLeft() ). No es necesario que nombre las variables en este caso, así que utilizo. _ como un marcador, que aprovecha la sintaxis simple y limpia para definir el bloque de código.El método foldLeft() realiza la misma tarea que el método que se llama similaridad de la biblioteca funcional de Java (ver Recursos), el cual aparece en la primera cuota:

  1. Tome un valor inicial y combínelo a través de una operación en el primer elemento de la lista.
  2. Con el resultado obtenido aplicar la misma operación con el siguiente elemento.
  3. Siga haciendo esto hasta que se agote la lista.

Esta es una versión generalizada de cómo aplicar una operación como la adición a una lista de números: comience con cero, sume el primer elemento, tome el resultado y súmelo a la segunda, y continúe hasta que se termine la lista.

Prueba de unidad

A pesar de que no he mostrado las pruebas de unidades de las versiones anteriores, todos los ejemplos contienen pruebas.Una efectiva biblioteca de pruebas de unidad denominada ScalaTest está disponible para Scala (ver Recursos). Listado 2 muestra la prueba de la primera prueba de unidad que escribí para verificar el método isPerfect() del Listado 1:

Listado 2. Una prueba de unidad para el número clasificador de Scala
@Test def negative_perfection() {
  for (i <- 1 until 10000)
    if (Set(6, 28, 496, 8128).contains(i))
      assertTrue(NumberClassifier.isPerfect(i))
    else
      assertFalse(NumberClassifier.isPerfect(i))
}

Pero al igual que ustedes, estoy tratando de aprender a pensar de manera más funcional, y el código en El Listado 2 me molesta de dos formas. Primero, se repite al hacer algo, lo cual demuestra un pensamiento imperativo. Segundo, no me importa por el catch-all binario de la sentencia Si . ¿Qué problema estoy intentando solucionar? Tengo que asegurarme de que mi número clasificador no identifica un número imperfecto como perfecto. El listado 3 muestra la solución para este problema, expresado de forma diferente:

Listado 3. Prueba alternativa para la clasificación de números perfectos
@Test def alternate_perfection() {
  assertEquals(List(6, 28, 496, 8128),
              (1 until 10000) filter (NumberClassifier.isPerfect(_)))
}

El listado 3 reafirma que los únicos números del 1 al 100.000 que son perfectos son los que están en la lista de números perfectos conocidos. Pensando funcionalmente se extienden no sólo al código, sino al modo de pensar sobre probarlos también.


Aplicación parcial y currying

El enfoque funcional que he mostrado a las listas de filtrado es común entre los lenguajes de programación funcional y bibliotecas.Utilizando la capacidad de pasar de código como parámetro (en cuanto al método de filter() en el Listado 3) Ilustra el pensamiento sobre la reutilización de código de una forma diferente.Si usted viene de un mundo tradicional de diseño de patrones impulsados orientado a objetos, compare este enfoque con el patrón de diseño del Método plantilla de la Banda de los cuatro libro Design Patterns (ver Recursos). El patrón Método plantilla define el esqueleto de un algoritmo en una clase base, utilizando métodos abstractos y alterando temporalmente para aplazar los detalles individuales a las clases infantiles. Utilizando la composición, el enfoque funcional le permite pasar funcionalidad a los métodos que aplican esa funcionalidad de forma adecuada.

Otra forma de conseguir la reutilización de código es a través de currying. Llamado así por el matemático Haskell Curry (para quienes el lenguaje de programación Haskell también se denomina así), currying transforma una función de argumento múltiple, para que se pueda llamar como una cadena de función de argumento único.Estrechamente relacionada la aplicación parcial, una técnica para asignar un valor fijo a uno o más argumentos a una función, lo que produce otra función de menor aridad (el número de parámetros a la función).Para entender la diferencia, empiece por buscar el código Groovy en el Listado 4, que ilustra currying:

Listado 4. Currying en Groovy
def product = { x, y -> return x * y }

def quadrate = product.curry(4)
def octate = product.curry(8) 

println "4x4: ${quadrate.call(4)}"
println "5x8: ${octate(5)}"

Enel Listado 4, defino elproducto como un bloque de código que acepta dos parámetros.Utilizando el método incorporado de Groovy curry() , se utiliza un producto como el bloque de compilación para dos nuevos bloques de código: quadrate y octate. Groovy hace una llamada a un bloque de código fácil: se puede ejecutar de forma explícita el método de llamada() o utilizar el azúcar sintáctico de nivel del lenguaje suministrado para colocar un conjunto de paréntesis que contengan cualquier parámetro después del nombre del bloque del código (como en octate(5), por ejemplo).

La aplicación parcial es una técnica más amplia que se parece a currying, pero no limita la función resultante de un único argumento. Groovy utiliza el método curry() para gestionar las aplicaciones currying y parcial, como aparece en el Listado 5:

Listado 5. La aplicación parcial frente a currying, ambas utilizando el método de Groovy curry()
def volume = { h, w, l -> return h * w * l }
def area = volume.curry(1)
def lengthPA = volume.curry(1, 1) //partial application
def lengthC = volume.curry(1).curry(1) // currying

println "The volume of the 2x3x4 rectangular solid is ${volume(2, 3, 4)}"
println "The area of the 3x4 rectangle is ${area(3, 4)}"
println "The length of the 6 line is ${lengthPA(6)}"
println "The length of the 6 line via curried function is ${lengthC(6)}"

El volumen del bloque de código enEl Listado 5 calcula el volumen cúbico de un sólido rectangular utilizando la conocida formula.Entonces se crea un área de bloque código (que calcula un área rectangular) fijando la primera dimensión del volumen(h, por altura) como 1. Para utilizar el volumen como un bloque de compilación para un bloque de código que devuelve la longitud de un segmento de línea, que puede realizar la aplicación parcial o currying. lengthPA utiliza la aplicación parcial fijando cada uno de los dos primeros parámetros en 1. lengthC aplica currying dos veces para dar el mismo resultado.La diferencia es sutil, y el resultado final es el mismo, pero si usted utiliza los términos currying y aplicación parcial indistintamente al alcance del oído de un programador funcional, contando con ser corregido.

La programación funcional le da nuevos y diferentes bloques de compilación para lograr los mismos objetivos que los lenguajes imperativos consiguen con otros mecanismos.Las relaciones entre estos bloques de compilación están bien pensadas.Antes, se mostró la composición como un mecanismo de reutilización del código.No hay que sorprenderse de que se puedan combinar currying y la composición.Considere el código Groovy en el Listado 6:

Listado 6. Composición de la aplicación parcial
def composite = { f, g, x -> return f(g(x)) }
def thirtyTwoer = composite.curry(quadrate, octate)

println "composition of curried functions yields ${thirtyTwoer(2)}"

En el Listado 6, se crea un bloque de código compuesto que se compone de dos funciones.Por medio de ese bloque de código, se crea un bloque de código thirtyTwoer , a través de una aplicación parcial para componer los dos métodos juntos.

Utilizando una aplicación parcial y currying, se consiguen objetivos similares a los mecanismos como el patrón de diseño del Método plantilla. Por ejemplo, usted puede crear un bloque de código incrementador compilándolo encima de un bloque de código sumador , como aparece en el Listado 7:

Listado 7. Diferentes bloques de compilación
def adder = { x, y -> return x + y }
def incrementer = adder.curry(1)

println "increment 7: ${incrementer(7)}"

Por supuesto, Scala es compatible con currying, como se ilustra en el fragmento de la documentación Scala que aparece en el Listado 8:

Listado 8. Currying en Scala
object CurryTest extends Application {

  def filter(xs: List[Int], p: Int => Boolean): List[Int] =
    if (xs.isEmpty) xs
    else if (p(xs.head)) xs.head :: filter(xs.tail, p)
    else filter(xs.tail, p)

  def dividesBy(n: Int)(x: Int) = ((x % n) == 0)

  val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
  println(filter(nums, dividesBy(2)))
  println(filter(nums, dividesBy(3)))
}

El código del Listado 8 muestra como implementar un método dividesBy() por medio de un filter() . Se pasa una función anónima para el método filter() , utilizando currying para fijar el primer parámetro del método dividesBy() al valor utilizado para crear el bloque de código.Cuando se pasa el bloque de código creado mediante la transferencia de mi número de destino como un parámetro, Scala aumenta curry hasta una nueva función.


Filtrado vía recurrencia

Otro tema estrechamente relacionado con la programación funcional es la recurrencia, que (según Wikipedia) es el "proceso de repetición de los elementos de una forma auto-similar." En realidad, es una forma de ciencia de computación para repetir las cosas a través de una llamada al mismo método desde sí mismo (siempre con cuidado de asegurar que tiene una condición de salida). Muchas veces, la recurrencia conduce a una fácil comprensión del código, porque el núcleo del problema es la necesidad de hacer lo mismo una y otra vez a una lista decreciente.

Considere el filtrar una lista.Utilizando un enfoque iterativo, se acepta un criterio de filtrado y bucle sobre los contenidos, filtrando los elementos que no quiero. El listado 9 muestra una implementación sencilla de filtrado con Groovy:

Listado 9. Filtrado en Groovy
def filter(list, criteria) {
  def new_list = []
  list.each { i -> 
    if (criteria(i))
      new_list << i
  }
  return new_list
}

modBy2 = { n -> n % 2 == 0 }

l = filter(1..20, modBy2)
println l

El método filter() en el Listado 9 acepta una lista y un criterio (un bloque de código que especifica como filtrar la lista) y se repite en la lista, añadiendo cada elemento a una nueva lista si coincide con el predicado.

Ahora miremos de nuevo el Listado 8, que es una implementación recurrente de la funcionalidad de filtrado en Scala.Sigue un patrón común en los lenguajes funcionales relacionados a una lista.Una visualización de una lista se compone de dos partes: el artículo en la parte delantera de la lista (la cabecera), y todos los demás artículos.Muchos lenguajes funcionales tienen métodos específicos para repetir sobre las listas utilizando este idioma.El método de filter() primero verifica si la lista está vacía — la condición de salida es de vital importancia para este método.Si la lista está vacía, simplemente vuelve. De lo contrario, utiliza la condición predicado (p) transferida como un parámetro.Si esta condición se cumple (es decir, quiero este artículo en mi lista), se devuelve una nueva lista construida de la toma de la actual cabecera y un resto filtrado de la lista.Si la condición predicado falla, se devuelve una lista nueva que consta de tan sólo el resto filtrado (eliminando el primer elemento).Los operadores de la construcción de la lista de Scala, realizan las condiciones a devolución para ambos casos muy legible y fácil de entender.

Mi consejo es que no utilice la recurrencia por ahora — ni si siquiera forma parte de su caja de herramientas.Sin embargo, parte de la razón radica en el hecho de que los lenguajes más imprescindibles tienen un soporte mediocre para ello, haciéndolos más difícil de utilizar de lo que debería ser.Añadiendo una sintaxis y apoyo claro, los lenguajes funcionales hacen de la recurrencia una candidata a la reutilización del código único.


Conclusión

Esta cuota completa mi estudio de los dispositivos del mundo del pensamiento funcional. Casualmente, la mayor parte de este artículo fue sobre el filtrado, enseñando muchas formas de utilizarlo e implementarlo.Pero esto no sorprende demasiado.Muchos paradigmas funcionales se compilan alrededor de las listas, porque gran parte de la programación se reduce a tratar con listas de cosas.Tiene sentido el crear lenguajes e infraestructuras que han sobrealimentado recursos de listas.

En la próxima cuota, profundizaré sobre uno de los bloques de compilación de programación funcional: la inmutabilidad.

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=807254
ArticleTitle=El pensamiento funcional: El pensamiento funcional, Parte 3
publish-date=04022012