Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Why isn't the pipe a construct that has caught on in 'proper' languages?


Clojure has something that you could call a pipe almost. `->` passes the output from one form to the next one.

This example has a nested hash map where we try to get the "You got me!" string.

We can either use `:a` (keyword) as a function to get the value. Then we have to nest the function calls a bit unnaturally.

Or we can use the thread-first macro `->`, which is basically a unix pipe.

   user=> (def res {:a {:b {:c "You got me!"}}})
   #'user/res
   
   user=> res
   {:a {:b {:c "You got me!"}}}
   
   user=> (:c (:b (:a res)))
   "You got me!"
   
   user=> (-> res :a :b :c)
   "You got me!"
Thinking about it, Clojure advocates having small functions (similar to unix's "small programs / do one thing well") that you compose together to build bigger things.


Clojure: The Pure Function Pipeline Data Flow

https://github.com/linpengcheng/PurefunctionPipelineDataflow


Why are you linking to the same github project over and over again?


Sorry, but the system does not support deletion now.


It has, in the form of function composition, as other replies show. However, the Unix pipe demonstrates a more interesting idea: composable programs on the level of the OS.

Nowadays, most of the user-facing desktop programs have GUIs, so the 'pipe' operator that composes programs is the user himself. Users compose programs by saving files from one program and opening them in another. The data being 'piped' through such program composition is sort-of typed, with the file types (PNG, TXT, etc) being the types and the loading modules of the programs being 'runtime typecheckers' that reject files with invalid format.

On the first sight, GUIs prevent program composition by requiring the user to serve as the 'pipe'. However, if GUIs were reflections / manifestations of some rich typed data (expressible in some really powerful type system, such as that of Idris), one could imagine the possibility of directly composing the programs together, bypassing the GUI or file-saving stages.


maybe I'm being overly pedantic, but people seem to be confused about this:

the pipes in your typical functional language (`|>`) is not a form of function composition, like

```

f >> g === x -> g(f(x))

```

but function application, like

```

f x |> g === g(f(x))

x |> f |> g // also works, has the same meaning

f |> g // just doesn't work, sorry :(

```


> the pipes in your typical functional language (`|>`) is not a form of function composition

What is a "typical functional language" in this case? I don't think I've come across this `|>` notation, or anything explicitly referred to as a "pipe", in the functional languages I tend to use (Haskell, Scheme, StandardML, Idris, Coq, Agda, ...); other than the Haskell "pipes" library, which I think is more elaborate than what you're talking about.


It is! It's the main way of programming in lazy functional programming languages like Haskell

And many programming languages have libraries for something similar:. iterators in rust / c++, streams in java/c#, thinks like reactive


Haskell was the only possible I found.

Iterators don't really fully capture what a pipe is though? Theres no parallelism.

And streams don't have the conceptual simplicity of a pipe?


> Iterators don't really fully capture what a pipe is though? Theres no parallelism.

Pipes are concurrent, not necessarily parallel. Iterators are concurrent, and can be parallel (https://docs.rs/rayon/0.6.0/rayon/par_iter/index.html).


Many functional languages have |> for piping, but chained method calls are also a lot like pipelines. Data goes from left to right. This javascript expression:

  [1, 2, 3].map(n => n + 1).join(',').length
Is basically like this shell command:

  seq 3 | awk '{ print $1 + 1 }' | tr '\n' , | wc -c
(the shell version gives 6 instead of 5 because of a trailing newline, but close enough)


But each successive 'command' is a method on what's constructed so far; not an entirely different command to which we delegate processing of what we have so far.

The Python:

   length(','.join(map(lambda n: n+1, range(1, 4)))
is a bit closer, but the order's now reversed, and then jumbled by the map/lambda. (Though I suppose arguably awk does that too.)


That's true. It's far from being generally applicable. But it might be the most "mainstream" pipe-like processing notation around.

Nim has an interesting synthesis where a.f(b) is only another way to spell f(a, b), which (I think) matches the usual behavior of |> while still allowing familiar-looking method-style syntax. These are equivalent:

  [1, 2, 3].map(proc (n: int): int = n + 1).map(proc (n: int): string = $n).join(",").len
  
  len(join(map(map([1, 2, 3], proc (n: int): int = n + 1), proc (n: int): string = $n), ","))
The difference is purely cosmetic, but readability matters. It's easier to read from left to right than to have to jump around.


C# extension methods provide the same syntax, and it is used for all of its LINQ pipeline methods. It's amazing how effective syntactic sugar can be for readability.


In Julia, this would be:

1:3 |> x->x+1 |> x->join(x,",") |> length


Small correction (or it won't run on my system):

  1:3 |> x -> map(y -> y+1, x) |> x -> join(x, ",") |> length
All those anonymous functions seem a bit distracting, though https://github.com/JuliaLang/julia/pull/24990 could help with that.


Ok, in Julia 1.0+ you just have to use the dot operator:

1:3 |> x->x.+1 |> x->join(x,",") |> length

Note the dot in x.+1, that tells + to operate on each element of the array (x), and not the array itself.


Ok... not sure which version of Julia you're using, but I'm on 0.5 and it works there... Maybe it changed in a later version


Maybe a bit nicer

    (1:3 .|> x->x+1) |> x->join(x,",") |> length


Elixir also has a pipe construct:

    f |> g(1)
would be equivalent to

    g(f, 1)


Others have show languages have some kind of pipe support, but not exactly as the shell.

The shell have the weird(?) behavior of ONE input and TWO outputs (stdout, stderr).

Also, can redirect in both directions. I think a language to be alike pipes, it need each function to be alike:

    fun open(...) -> Result(Ok,Err)
and have the option of not only chain the OK side but the ERR:

    open("file.txt") |> print !!> raise |> print
exist something like this???


OCaml has had the pipe operator |> since 4.01 [0]

It actually somewhat changes the way you write code, because it enables chaining of calls.

It's worth noting there's nothing preventing this being done before the pipe operator using function calls.

x |> f |> g is by definition the same as (g (f x)).

In non-performance-sensitive code, I've found that what would be quite a complicated monolithic function in an imperative language often ends up as a composition of more modular functions piped together. As others have mentioned, there are similarities with the method chaining style in OO languages.

Also, I believe Clojure has piping in the form of the -> thread-first macro.

[0] https://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasive...


IMO the really useful part of pipes is less the operator and more the lazy, streaming, concurrent processing model.

So lazy collections / iterators, and HoFs working on those.

The pipe operator itself is mostly a way to denote the composition in reading order (left to right instead of right to left / inside to outside), which is convenient for readability but not exactly world-breaking.


What you're describing sounds a lot like reactiveX - http://reactivex.io/

I only have experience using RxJS, but it's incredibly powerful.


To take any significant advantage of it you need to use data-driven, transformational approach of solving something. But funny thing is once you have that it's not really a big deal even if you don't have a pipe operator.


Monads are effectively pipes; the monad controls how data flows through the functions you put into the monad, but the functions individually are like individual programs in a pipe.


I'm not sure I agree with this. Function composition is more directly comparable to pipes, whereas I tend to think of monads as collapsing structure (i.e. `join :: m (m a) -> m a`)


Some of the most common monads (especially lists and other collection-like things) feel very much like pipes.

    list.select { |x|  x.foo > 10 }.map { |x| x.bar }...
Etc.

I wouldn't make the same argument about the IO monad, which I think more in terms of a functional program which evaluates to an imperative program. But most monads are not like the IO monad, in my experience at least.


> list.select { |x| x.foo > 10 }.map { |x| x.bar }...

Forgive me if I'm misreading this syntax, but to me this looks like plain old function composition: a call to `select` (I assume that's like Haskell's `filter`?) composed with a call to `map`. No monad in sight.

As I mentioned, monads are more about collapsing structure. In the case of lists this could be done with `concat` (which is the list implementation of monad's `join`) or `concatMap` (which is the list implementation of monad's `bind` AKA `>>=`).


Nope, it's not. It's Ruby, and the list could be an eager iterator, an actual list, a lazy iterator, a Mabye (though it would be clumsy in Ruby), etc.

And monads are not "more about collapsing structure". They are just a design pattern that follows a handful of laws. It seems like you're mistaking their usefulness in Haskell for what they are. A lot of other languages have monads either baked in or an element of the design of libraries. Expand your mind out of the Haskell box :)


The things you listed may form a monad, but your list/iterator transformation doesn't use monadic composition.

> list.select { |x| x.foo > 10 }.map { |x| x.bar }

Where's `bind` or `join` in that example?


> It's Ruby

Thanks for clarifying; I've read a bunch of Ruby but never written it before ;)

From a quick Google I see that "select" and "map" do work as I thought:

https://ruby-doc.org/core-2.2.0/Array.html#method-i-select

https://ruby-doc.org/core-2.2.0/Array.html#method-i-map

So we have a value called "list", we're calling its "select" method/function and then calling the "map" method/function of that result. That's just function composition; no monads in sight!

To clarify, we can rewrite your example in the following way:

    list.select { |x|  x.foo > 10 }.map { |x| x.bar }

    # Define the anonymous functions/blocks elsewhere, for clarity
    list.select(checkFoo).map(getBar)

    # Turn methods into standalone functions
    map(select(list, checkFoo), getBar)

    # Swap argument positions
    map(getBar, select(checkFoo, list))

    # Curry "map" and "select"
    map(getBar)(select(checkFoo)(list))

    # Pull out definitions, for clarity
    mapper   = map(getBar)
    selector = select(checkFoo)
    mapper(selector(list))
This is function composition, which we could write:

    go = compose(mapper, selector)
    go(list)
The above argument is based solely on the structure of the code: it's function composition, regardless of whether we're using "map" and "select", or "plus" and "multiply", or any other functions.

To understand why "map" and "select" don't need monads, see below.

> the list could be an eager iterator, an actual list, a lazy iterator, a Maybe (though it would be clumsy in Ruby), etc.

Yes, that's because all of those things are functors (so we can "map" them) and collections (so we can "select" AKA filter them).

The interface for monad requires a "wrap" method (AKA "return"), which takes a single value and 'wraps it up' (e.g. for lists we return a single-element list). It also requires either a "bind" method ("concatMap" for lists) or, my preference, a "join" method ("concat" for lists).

I can show that your example doesn't involve any monads by defining another type which is not a monad, yet will still work with your example.

I'll call this type a "TaggedList", and it's a pair containing a single value of one type and a list of values of another type. We can implement "map" and "select" by applying them to the list; the single value just gets passed along unchanged. This obeys the functor laws (I encourage you to check this!), and whilst I don't know of any "select laws" I think we can say it behaves in a reasonable way.

In Haskell we'd write something like this (although Haskell uses different names, like "fmap" and "filter"):

    data TaggedList t1 t2 = T t1 [t2]

    instance Functor (TaggedList t1) where
      map f (T x ys) = T x (map f ys)

    instance Collection (TaggedList t1) where
      select f (T x ys) = T x (select f ys)
In Ruby we'd write something like:

    class TaggedList
      def initialize(x, ys)
        @x  = x
        @ys = ys
      end

      def map(f)
        TaggedList.new(@x, @ys.map(f))
      end

      def select(f)
        TaggedList.new(@x, @ys.select(f))
      end
    end
This type will work for your example, e.g. (in pseudo-Ruby, since I'm not so familiar with it):

    myTaggedList = TaggedList.new("hello", [{foo: 1, bar: true}, {foo: 20, bar: false}])
    result = myTaggedList.select { |x|  x.foo > 10 }.map { |x| x.bar }

    # This check will return true
    result == TaggedList.new("hello", [false])
Yet "TaggedList" cannot be a monad! The reason is simple: there's no way for the "wrap" function (AKA "return") to know which value to pick for "@x"!

We could write a function which took two arguments, used one for "@x" and wrapped the other in a list for "@ys", but that's not what the monad interface requires.

Since Ruby's dynamically typed (AKA "unityped") we could write a function which picked a default value for "@x", like "nil"; yet that would break the monad laws. Specifically:

    bind(m, wrap) == m
If "wrap" used a default value like "nil", then "bind(m, wrap)" would replace the "@x" value in "m" with "nil", and this would break the equation in almost all cases (i.e. except when "m" already contained "nil").


tiny clarification: Function composition is just function composition. Pipes are a different syntax for function application.

see https://news.ycombinator.com/item?id=18971451


More specifically, a pipe is a monad that abstracts your machine's state. It's basically equivalent to Haskell's IO.


Hmmm, well that's why I like Ruby and other languages with functional approaches. Method chaining and blocks are very similar to pipes to me.

    cat /etc/passwd | grep root | awk -F: '{print $3}'

    ruby -e 'puts File.read("/etc/passwd").lines.select { |line| line.match(/root/) }.first.split(":")[2]'
A little more verbose, but the idea is the same.

https://alvinalexander.com/scala/fp-book/how-functional-prog...


awk -F: '/root/ {print $3}' < /etc/passwd


As others have mentioned, the pipe construct is present in many languages (or can be added).

A small additional bit of information: this style is called "tacit" (or "point-free") programming. See https://en.wikipedia.org/wiki/Tacit_programming

(Unix pipes are even explicitly mentioned in the articles as an example)


Julia has a pipe operator, which applies a function to the preceding argument:

julia> 1:5 |> x->x.^2 |> x->2x

5-element Array{Int64,1}:

  2
  8
 18
 32
 50


There is a tradition in C++ of overloading operator| for pipelining range operations. Your mileage may vary.


It doesn't look the same, bug go's up.Reader and io.Writer are the interfaces you implement if you want the equivalent of "reading from stdin"/"writing to stdout". Once implemented io.Copy is the actual piping operation.


It has, D's uniform functional syntax makes this as easy as auto foo = some_array.filter!(predicate).array.sort.uniq; for the unique elements of the sorted array that satisfy the predicate pred.


To add to the list, pipes are also heavily used in modern R


F# has a pipe operator


Haskell has the Conduit library. There's jq's notion of pipes (which isn't the same as Unix pipes, but still).


because a | b | c is equivalent to the very tradtional c(b(a())) and in FP it was infixed as `.`




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: