language-agnostic programming-languages concurrency shell pipe

language agnostic - ¿Por qué no veo operadores de tuberías en la mayoría de los idiomas de alto nivel?



language-agnostic programming-languages (13)

En la programación de shell de Unix, el operador de tuberías es una herramienta extremadamente poderosa. Con un pequeño conjunto de utilidades principales, un lenguaje de sistemas (como C) y un lenguaje de secuencias de comandos (como Python), usted puede construir scripts de shell extremadamente compactos y potentes, que son paralelizados automáticamente por el sistema operativo.

Obviamente, este es un paradigma de programación muy poderoso, pero no he visto canalizaciones como abstracciones de primera clase en ningún otro lenguaje que no sea un script de shell. El código necesario para replicar la funcionalidad de los scripts que usan tuberías parece ser siempre bastante complejo.

Entonces, mi pregunta es ¿por qué no veo algo similar a las tuberías de Unix en lenguajes modernos de alto nivel como C #, Java, etc.? ¿Existen lenguajes (aparte de los scripts de shell) que admiten canalizaciones de primera clase? ¿No es una forma conveniente y segura de expresar algoritmos concurrentes?

En caso de que alguien lo mencione, miré al operador de tubería F # (operador de tubería delantera), y se parece más a un operador de aplicaciones de función. Aplica una función a los datos, en lugar de conectar dos flujos juntos, por lo que puedo decir, pero estoy abierto a las correcciones.

Posdata : Al hacer algunas investigaciones sobre la implementación de coroutines, me doy cuenta de que existen ciertos paralelos. En una publicación del blog, Martin Wolf describe un problema similar al mío pero en términos de coroutines en lugar de tuberías.


¡Jaja! Gracias a mi Google-fu, he encontrado una respuesta SO que puede interesarle. Básicamente, la respuesta va contra el argumento de "no sobrecargar a menos que realmente tengas que hacerlo" al sobrecargar al operador en modo bit a bit para proporcionar una tubería similar a una concha, lo que resulta en un código Python como este:

for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7): print i

Lo que hace, conceptualmente, es canalizar la lista de números del 2 al 99 ( xrange(2, 100) ) a través de una función de tamiz que elimina los múltiplos de un número dado (primero 2, luego 3, luego 5, luego 7). Este es el comienzo de un generador de números primos, aunque generar números primos de esta manera es una idea bastante mala. Pero podemos hacer más:

for i in xrange(2,100) | strify() | startswith(5): print i

Esto genera el rango, luego los convierte de números a cadenas y luego filtra todo lo que no comienza con 5.

La publicación muestra una clase padre básica que le permite sobrecargar dos métodos, map y filter , para describir el comportamiento de su canalización. Entonces, strify() usa el método de map para convertir todo en una cadena, mientras que sieve() usa el método de filter para eliminar cosas que no son múltiplos del número.

Es bastante inteligente, aunque tal vez eso signifique que no es muy Pythonic, pero demuestra lo que está buscando y una técnica para obtenerlo que probablemente se pueda aplicar fácilmente a otros idiomas.


¿Estás mirando el operador F # |>? Creo que realmente quieres el >> operador.


Creo que la razón más fundamental es porque C # y Java tienden a usarse para construir más sistemas monolíticos. Culturalmente, simplemente no es común querer hacer cosas parecidas a una tubería, solo haces que tu aplicación implemente la funcionalidad necesaria. La noción de construir una multitud de herramientas simples y luego juntarlas de manera arbitraria simplemente no es común en esos contextos.

Si observas algunos de los lenguajes de scripting, como Python y Ruby, hay algunas herramientas bastante buenas para hacer cosas parecidas a pipas desde esos scripts. Echa un vistazo al módulo de subproceso de Python, por ejemplo, que te permite hacer cosas como:

proc = subprocess.Popen(''cat -'', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,) stdout_value = proc.communicate(''through stdin to stdout'')[0] print ''/tpass through:'', stdout_value


Gracias a todas las excelentes respuestas y comentarios, aquí hay un resumen de lo que aprendí:

Resulta que hay un paradigma completo relacionado con lo que me interesa en la llamada programación basada en flujo . Un buen ejemplo de un lenguaje diseñado especialmente para la programación basada en flujos son las tuberías de Hartmann . Las tuberías de Hartamnn generalizan la idea de flujos y tuberías utilizados en Unix y otros sistemas operativos, para permitir múltiples flujos de entrada y salida (en lugar de un solo flujo de entrada y dos flujos de salida). Erlang contiene poderosas abstracciones que facilitan la expresión de procesos concurrentes de una manera que se asemeja a las tuberías. Java proporciona java.sun.com/javase/6/docs/api/java/io/PipedInputStream.html y PipedOutputStream que se pueden usar con subprocesos para lograr el mismo tipo de abstracciones de una manera más detallada.


Las bibliotecas de streaming basadas en coroutines han existido en Haskell desde hace bastante tiempo. Dos ejemplos populares son conduit y pipes .

Ambas bibliotecas están bien escritas y bien documentadas, y son relativamente maduras. El marco web de Yesod se basa en conductos, y es bastante rápido . Yesod es competitivo con Node en el rendimiento, incluso superándolo en algunos lugares.

Curiosamente, todas estas bibliotecas son de un solo hilo por defecto. Esto se debe a que el único caso de uso motivador para las tuberías es el de los servidores, que están vinculados a la E / S.


Me divertí mucho construyendo funciones de tuberías en Python. Tengo una biblioteca que escribí, pongo el contenido y una muestra here . Lo mejor para mí fue el procesamiento XML, descrito en este artículo de Wikipedia .


Objective-C tiene la clase NSPipe . Lo uso con bastante frecuencia.


Por lo general, simplemente no lo necesita y los programas se ejecutan más rápido sin él.

Básicamente la tubería es un patrón de consumidor / productor. Y no es tan difícil escribir a esos consumidores y productores porque no comparten mucha información.

  • Tubería para Python: pypes
  • Mozart-OZ puede hacer tuberías utilizando puertos y subprocesos.

Puede encontrar algo así como tuberías en C # y Java, por ejemplo, donde toma un flujo de conexión y lo coloca dentro del constructor de otro flujo de conexión.

Entonces, tienes en Java:

new BufferedReader(new InputStreamReader(System.in));

Es posible que desee buscar encadenamientos de entrada o flujos de salida.


Puede realizar operaciones de canalización en Java al encadenar / filtrar / transformar iteradores. Puedes usar los iteradores de guayaba de Google.

Diré que incluso con la muy útil biblioteca de guayaba y las importaciones estáticas, sigue siendo un montón de código Java.

En Scala es bastante fácil hacer su propio operador de tubería.


Puedes hacer paralelismo de tipo pipeline fácilmente en Erlang. A continuación se muestra una copia / pegado descarada de mi blogpost de enero de 2008.

Además, Glasgow Parallel Haskell permite la composición de funciones paralelas, lo que equivale a lo mismo, brindándole paralelismo implícito.

Ya piensa en términos de tuberías. ¿Qué tal "gzcat foo.tar.gz | tar xf -"? Es posible que no lo sepa, pero el shell ejecuta el descomprimir y descomprimir en paralelo: el stdin leído en tar solo se bloquea hasta que gzcat envía los datos a stdout.

Bueno, muchas tareas pueden expresarse en términos de tuberías, y si puede hacer eso, obtener un cierto nivel de paralelización es simple con el código auxiliar de David King (incluso a través de nodos Erlang, es decir, máquinas):

pipeline:run([pipeline:generator(BigList), {filter,fun some_filter/1}, {map,fun_some_map/1}, {generic,fun some_complex_function/2}, fun some_more_complicated_function/1, fun pipeline:collect/1]).

Básicamente, lo que está haciendo aquí es hacer una lista de los pasos: cada paso se implementa en una diversión que acepta como entrada cualquiera que sea el resultado de los pasos anteriores (las diversiones se pueden definir incluso en línea, por supuesto). Ve a ver la entrada del blog de David para el código y una explicación más detallada.


Si todavía estás interesado en una respuesta ...

Puedes mirar el factor, o la alegría más antigua, para el paradigma concatenativo. en los argumentos y los argumentos de salida son implícitos, volcados a una pila. entonces la siguiente palabra (función) toma esos datos y hace algo con ellos.

La sintaxis es postfix.

Impresión "123"

donde la impresión toma un argumento, lo que esté en la pila.


magrittr package magrittr proporciona algo similar al operador de tuberías de F # en R:

rnorm(100) %>% abs %>% mean

Combinado con el paquete dplyr , trae una herramienta de manipulación de datos ordenada:

iris %>% filter(Species == "virginica") %>% select(-Species) %>% colMeans