¿Usando colas para desacoplar funciones / evitar llamadas directas?

8

Una especie de pregunta de novato de programación funcional aquí:

He estado leyendo las transcripciones de algunas de las charlas de Rich Hickey, y en varias de sus más conocidas, recomienda usar colas como una alternativa a que las funciones se llamen entre sí. (Por ejemplo, en diseño, composición y rendimiento y en Simple Made Easy ).

No entiendo esto, en varios aspectos:

  1. ¿Está hablando de poner datos en una cola y luego hacer que cada función los use? Entonces, en lugar de que la función A llame a la función B para llevar a cabo su propio cálculo, solo tenemos que la función B abofetea su salida en una cola y luego la función A la toma. O, alternativamente, ¿estamos hablando de poner funciones en una cola y luego aplicarlas sucesivamente a los datos (seguramente no, porque eso implicaría una mutación masiva, ¿verdad? ¿Y también la multiplicación de colas para funciones de arias múltiples, o como árboles o algo así? )

  2. ¿Cómo hace eso para simplificar las cosas? Mi intuición sería que esta estrategia crearía más complejidad, porque la cola sería una especie de estado, y luego debe preocuparse "¿y si alguna otra función se coló y puso algunos datos encima de la cola?"

Una respuesta a una pregunta de implementación sobre SO sugiere que la idea es crear un montón de colas diferentes. Entonces cada función pone su salida en su propia cola (??). Pero eso también me confunde, porque si está ejecutando una función una vez, entonces ¿por qué necesita una cola para su salida cuando podría tomar esa salida y darle un nombre como (var, átomo, entrada en una gran tabla hash, lo que sea). Por el contrario, si una función se ejecuta varias veces y pega su salida en una cola, entonces se ha infligido nuevamente el estado y debe preocuparse por el orden en que se llama todo, las funciones posteriores se vuelven menos puras, etc.

Claramente no entiendo el punto aquí. ¿Alguien puede explicar un poco?

Paul Gowder
fuente
No vi una sola referencia a una cola en su primer enlace, aunque le concederé que la publicación es increíblemente larga y podría haberla perdido. Parece que es más una charla sobre arte que sobre programación.
Robert Harvey
Las colas se mencionan brevemente dos veces en el segundo artículo, pero no se exponen. En cualquier caso, la idea de utilizar una cola de mensajería para comunicarse entre aplicaciones o módulos ha existido por un tiempo. Parece poco probable que haga esto en una sola aplicación a menos que esté creando una tubería de procesamiento o un motor de estado.
Robert Harvey
Está en el párrafo debajo de la diapositiva titulada "Separar tiempo / orden / flujo" ("Puede separar los sistemas, por lo que hay menos llamadas directas. Puede usar colas para hacer eso")
Paul Gowder
3
No vale la pena dar una respuesta completa, pero en realidad solo está dando una descripción borrosa del concepto de grupos de trabajo y programación impulsada por eventos. Por lo tanto, empaqueta una llamada de función en un Jobobjeto genérico , lo inserta en una cola y hace que uno o más subprocesos de trabajo trabajen en esa cola. El Jobentonces despacha más Jobs en la cola al finalizar. Los valores de retorno se reemplazan por devoluciones de llamada en ese concepto. Es una pesadilla depurar y verificar, ya que carece de una pila de llamadas, y eficiente y flexible por la misma razón.
Ext3h
Gracias. ¡Quizás mi verdadero problema es que no entiendo los mensajes! (Je, ¿es hora de ir a aprender smalltalk? :-))
Paul Gowder

Respuestas:

6

Es más un ejercicio de diseño que una recomendación general. Por lo general, no va a poner una cola entre todas sus llamadas a funciones directas. Eso sería ridículo. Sin embargo, si no diseña sus funciones como si se pudiera insertar una cola entre cualquiera de las llamadas a funciones directas, no puede justificar justificadamente que ha escrito código reutilizable y composable. Ese es el punto que Rich Hickey está haciendo.

Esta es una razón importante detrás del éxito de Apache Spark , por ejemplo. Usted escribe código que parece que está haciendo llamadas directas a funciones en colecciones locales, y el marco traduce ese código en mensajes que pasan en colas entre nodos del clúster. El tipo de codificación simple, composable y reutilizable que Rich Hickey defiende lo hace posible.

Karl Bielefeldt
fuente
¿Pero no es eso solo un cambio en el proceso de enlace del método? Al final del día, una llamada de función es solo una llamada de función, ¿verdad? Lo que sucede después de eso depende de lo que hace la función. Por lo tanto, no se trata tanto de hacer llamadas a funciones como de cómo se diseña la infraestructura subyacente.
Robert Harvey
1
Para decirlo de otra manera, ¿qué cambio haría en sus llamadas de función para que sean "aptas para la cola"?
Robert Harvey
55
Recuerde, el código en el otro extremo de la cola no necesariamente tiene acceso a la misma memoria e IO. Las funciones amigables con la cola están libres de efectos secundarios y esperan entradas y producen salidas que son inmutables y fácilmente serializables. Esa no es una prueba tan fácil de cumplir en muchas bases de código.
Karl Bielefeldt
3
Ah, entonces "programación funcional amigable" entonces. Tiene un poco de sentido, ya que Rich Hickey lo está discutiendo.
Robert Harvey
0

Una cosa a tener en cuenta es que la programación funcional le permite conectar funciones entre sí indirectamente a través de objetos mediadores que se encargan de obtener argumentos para alimentar las funciones y enrutar de manera flexible sus resultados a los destinatarios que desean sus resultados. Supongamos que tiene un código de llamada directa directo que se parece a este ejemplo, en Haskell:

myThing :: A -> B -> C
myThing a b = f a (g a b)

Bueno, usando de Haskell Applicativeclase y sus <$>y <*>operadores podemos volver a escribir mecánicamente ese código para esto:

myThing :: Applicative f => f A -> f B -> f C
myThing a b = f <$> a <*> (g <$> a <*> b)

... donde ahora myThingya no está llamando directamente fy g, sino más bien de conectarlos a través de algunos mediadores de tipo f. Entonces, por ejemplo, fpodría ser algún Streamtipo proporcionado por una biblioteca que proporciona una interfaz a un sistema de colas, en cuyo caso tendríamos este tipo:

myThing :: Stream A -> Stream B -> Stream C
myThing a b = f <$> a <*> (g <$> a <*> b)

Sistemas como este existen. De hecho, puede ver las secuencias de Java 8 como una versión de este paradigma. Obtienes un código como este:

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Aquí está utilizando las siguientes funciones:

  • t -> t.getType() == Transaction.GROCERY
  • comparing(Transaction::getValue).reversed()
  • Transaction::getId
  • toList()

... y en lugar de hacer que se llamen directamente, está utilizando el Streamsistema para mediar entre ellos. Este ejemplo de código no llama a la Transaction::getIdfunción directamente, Streamsino a las transacciones que sobrevivieron a la anterior filter. Puede pensar en el Streamcomo un tipo de cola muy mínimo que combina funciones indirectamente y enruta valores entre ellas.

sacundim
fuente