2009-10-10 16 views
11

Como novato en lenguajes funcionales (comencé a tocar Erlang hace un par de semanas, el primer lenguaje funcional que pude tener).Consejos sobre el aprendizaje "Cómo pensar funcional"?

Empecé a escribir algunos algoritmos pequeños (como left_rotate_list, bubble_sort,merge_sort etc.). Con frecuencia me perdía en decisiones como "¿Debería usar una lista de ayuda para el almacenamiento de resultados intermedios?" y "¿debería crear una función auxiliar para hacer esto?"

Después de un tiempo, encontré que la programación funcional (paciencia conmigo si lo que estoy hablando no tiene ningún sentido) alienta un diseño de "arriba hacia abajo", es decir, cuando hago merge_sort, primero escribe todo el fusionar los pasos de clasificación y denominarlos como funciones auxiliares individuales; y luego implementa esas funciones auxiliares una por una (y si necesita dividir aún más esas funciones auxiliares, hágalo en el mismo enfoque).

Esto parece contradecir un poco el diseño de OO, en el que puede comenzar desde abajo para construir la estructura de datos básica, y luego ensamblar la estructura de datos y los algoritmos en lo que desea.

Gracias por los comentarios. Sí, quiero obtener consejos sobre cómo "pensar en lenguaje funcional" (como "pensar en Java", "pensar en C++").

+0

¿Cuál es la pregunta? :-) – csl

+0

¿Desea sugerencias para "una guía de programación funcional para programadores OOP"? – Mahin

+0

Mahin, actualice la pregunta. Gracias por responder – chen

Respuesta

4

Después de un tiempo, encontré que la programación [...] funcional fomenta un diseño de "arriba hacia abajo".

No estoy seguro de que esta es una declaración precisa. Hace poco estuve tratando de enseñarme a mí mismo la programación funcional, y descubrí que un tipo de programación de "abajo hacia arriba" realmente me ayuda. Para usar su ejemplo de tipo de combinación:

  • Primero mire la carcasa base. ¿Cómo se ordena una matriz de elementos 0/1?
  • A continuación, mire la base + 1, base + 2, ... casos. Eventualmente, debería ver un patrón (división en subproblemas, solución de subproblemas, combinación de subsoluciones) que le permita escribir un caso recursivo general que eventualmente llegue al caso base.
  • La división en subproblemas es fácil, pero la combinación de las subsoluciones es un poco más difícil. Necesita una forma de combinar dos matrices ordenadas en una matriz ordenada.
  • Ahora junta todo. Felicidades, acabas de escribir el tipo de fusión. :)

Podría estar haciendo un mal uso del término, pero este me parece un diseño de abajo hacia arriba. La programación funcional es diferente de la programación orientada a objetos, pero no debería tener que abandonar totalmente las técnicas de diseño existentes cuando se cambia entre las dos.

10

Una respuesta es que la programación funcional es programar el uso de funciones, ya que están definidas en matemáticas (en resumen, elementos libres de efectos secundarios que mapean valores del dominio al codominio). Para traducir en realidad que en "forma de pensar" es la parte que agitaban la mano que es difícil de ser exhaustiva acerca, pero voy a probar algunos de mis pensamientos:

  1. La definición es más importante que la eficiencia. Es decir, una implementación obviamente correcta de una función de la que uno puede entender todo el comportamiento es mejor que una optimización compleja y difícil de razonar. (Y debe preferirse el mayor tiempo posible, hasta que exista evidencia de que uno debe romper esta agradable propiedad.)
  2. Una función matemática no tiene efectos secundarios. Un programa útil debe tener efectos secundarios. Un programador funcional es consciente de los efectos secundarios, como algo muy peligroso y complicado, y diseña el programa como un grupo de funciones que toma los valores de salida de un efecto secundario y crea valores de entrada para el próximo efecto secundario.

El número uno está asociado con el vago: "código elegante". Las comprensiones de listas pueden presentar ecuaciones muy sucintas y matemáticas como definiciones de funciones. Basta con mirar el tipo rápido implementado con LC. Así es como defino la elegancia, lo conciso y aclaro todos los comportamientos. No es ese código perl: golf en el que a menudo eres conciso y críptico.

Número dos es algo que uso día a día en toda la programación. Divida el código en funciones (métodos, rutinas, etc.) del estado actual que son cálculos libres de efectos secundarios que dan entradas a la siguiente acción a tomar (incluso si la siguiente acción es). Cuando se devuelve el valor, regrésalo a una rutina que realice la acción que se describe, luego vuelve a comenzar.

En mi cabeza diagramamos un proceso de Erlang como un gráfico de máquina de estado, donde cada vértice es un efecto secundario y una función cuya salida es qué borde elegir del vértice. La alta consideración de los efectos secundarios es algo que el paradigma de la programación funcional me enseñó. Especialmente en Erlang, ya que los efectos secundarios realmente importan en la concurrencia, y Erlang hace que la concurrencia esté muy disponible.

Del mismo modo, algunas tribus aisladas tienen solo una palabra para los números superiores a 3, o no hay palabras para "mío"/"tuyo". Parece que los lenguajes populares no tienen palabras para "esto causará un efecto secundario", pero lo tiene la programación funcional. Te está forzando a estar consciente de ello todo el tiempo, y eso es algo bueno.

2

A menudo me encontraba perdido en decisiones como "¿Debería usar una lista de ayuda para el almacenamiento de resultados intermedios?" y "¿debería crear una función auxiliar para hacer esto?"

Mi consejo para esto: lea The Little Schemer. Puedes seguirlo en Erlang. Es un buen libro para hacerte una idea de esto.

5

Después de un tiempo, encontré que la programación [...] funcional fomenta un diseño de "arriba hacia abajo".

Bueno, realmente no se trata de un diseño de "arriba hacia abajo" o de "abajo hacia arriba". Se trata de centrarse en el "qué" del problema en cuestión, en lugar del "cómo". Cuando comencé con la programación funcional, descubrí que seguía recordando construcciones imperativas como el ciclo for anidado en C. Luego descubrí rápidamente que tratar de traducir mi pensamiento imperativo a constructos funcionales era muy difícil. Trataré de darte un ejemplo más concreto. Implementaré un programa equivalente en C y Haskell e intentaré rastrear mi proceso de pensamiento en ambos casos. Tenga en cuenta que he sido explícitamente detallado con el propósito de explicación.

En C:

#include <stdio.h> 

int main(void) 
{ 
    int i, inputNumber, primeFlag = 1; 
    scanf("%d", &inputNumber); 
    for(i = 2; i <= inputNumber/2; i ++) 
    { 
     if (inputNumber % i == 0) 
     { 
      primeFlag = 0; 
      break; 
     } 
    } 
    if (primeFlag == 0) printf("False\n"); 
    else printf ("True\n"); 
    return 0; 
} 

Rastro de mi proceso de pensamiento:

  1. Piense en los pasos. Primero, acepte un número del usuario. Deje que este número se llame inputNumber. scanf() escrito.
  2. Algoritmo básico: Un número es primo a menos que se demuestre lo contrario.primeFlag declarado y establecido igual a 1.
  3. Compruebe primeNumber contra cada número desde 2 hasta primeNumber/2. for loop started. Se declaró una variable de bucle i para marcar primeNumber en contra.
  4. Para refutar nuestra afirmación inicial de que el número es primo, marque primeNumber contra cada i. En el momento en que encontramos incluso uno i que divide primeNumber, establezca primeFlag en 0 y break. Cuerpo de bucle escrito.
  5. Después de pasar por nuestro riguroso proceso de comprobación en el circuito for, verifique el valor de primeFlag y repórtelo al usuario. printf() escrito.

En Haskell:

assertPrime :: (Integral a) => a -> Bool 
assertPrime x = null divisors 
    where divisors = takeWhile (<= div x 2) [y | y <- [2..], mod x y == 0] 

Rastro de mi proceso de pensamiento:

  1. un número es primo si no tiene divisores pero una y la misma. Entonces, null divisors.
  2. ¿Cómo construimos divisors? Primero, anotemos una lista de posibles candidatos. Anotó el rango de Texas de 2 a número/2.
  3. Ahora, filtra la lista y selecciona solo los elementos que realmente son divisores del número. Escribió el filtro mod x y == 0

Quiero obtener consejos sobre cómo "pensar en un lenguaje funcional"

Ok, en primer lugar, pensar "qué", no "cómo". Esto puede requerir mucha práctica para acostumbrarse. Además, si anteriormente fue un programador de C/C++ como yo, ¡deje de preocuparse por la memoria! Los lenguajes modernos tienen un recolector de basura, y está escrito para que lo use, así que ni siquiera intente modificar las variables en su lugar. Otra cosa que me ayudó personalmente fue: escribir definiciones similares a las del inglés en su programa para abstraer las funciones que hacen el trabajo pesado.

+0

¿No es su ejemplo funcional un código imperativo con un disfraz funcional? algo como esto describe más tu intención? 'assertPrime x = x primos elem x',' primos x = tamiz [1..x] ', donde el tamiz es el primer generador ingenuo? – Zed

+2

No entiendo, ¿cómo es imprescindible mi pieza? Nuestros enfoques del problema son diferentes, pero mi código es Haskell puro no monádico. Sin pasos, y sin efectos secundarios. Su enfoque primero genera una lista de números primos y luego verifica si el elemento dado se encuentra en la lista. Por otro lado, mi enfoque funcional es similar a mi enfoque imperativo: lo hice a propósito para comparar e ilustrar. – artagnon

0

Es importante acostumbrarse a pensar que los datos se pueden usar como código, y viceversa.

Normalmente usted construye un programa (datos) usando varias operaciones primitivas (plegado, anidamiento, enhebrado, distribución, ..., y algunos son productos internos generalizados, productos externos, etc.), y utiliza este programa (datos) para manipular otros datos.

Después de un rato, me encontré con que la programación funcional [...] fomenta un diseño "de arriba hacia abajo ".

Estoy de acuerdo.

Cuestiones relacionadas