Hacker News new | ask | show | jobs
by pmontra 3398 days ago

    $ irb
    [1, 2, 3, 4].
      select {|n| n % 2 == 0}.
      map {|n| n * n}.
      reduce {|sum, n| sum + n}
    # 20
vs

    $ iex
    [1, 2, 3, 4] \
    |> Enum.filter(fn(n) -> rem(n, 2) == 0 end) \
    |> Enum.map(fn(n) -> n * n end) \
    |> Enum.reduce(0, fn(n, sum) -> sum + n end)
That Ruby code is so compact that I split it on multiple lines only to make the comparison easier.

Even Python is more compact even if it's harder to read because of the reverse order (I'm sure that there is a nested comprehension for that but it's beyond my comprehension skills)

    from functools import reduce
    reduce(lambda x, sum: sum + x, \
    map(lambda x: x * x, \
    filter(lambda x: x % 2 == 0, \
    [1, 2, 3, 4])))
    # 20
The problem here is having to type Module.function(value) vs object.method

OO languages have a more compact notation because objects act as namespaces. Elixir has alias but it doesn't help much and must be used wisely. alias Enum, as: E would only confuse people and gain little.

Then Elixir has too many do end compared to Ruby (coming from Ruby I feel them unnecessary), but that's not a big deal.

Moving from syntax to programming patterns, having to define twice my functions in GenServer (internal API and public API) is too much boilerplate. I'd like to have time to study macros really well and end up with a DryGenServer that lets me def the external API with the internal implementation. Maybe I'll defmacro a defasync and a defsync, that generate the standard "double" GenServer functions.

5 comments

I'd like to share some solutions. This is a compact notation for Elixir using the capture operator `&` and importing all Enum functions:

  import Enum

  [1,2,3,4] \
  |> filter(& rem(&1,2) == 0 ) \
  |> map(& &1*&1) \
  |> reduce(0, & &1 + &2)

Also ExActor (https://github.com/sasa1977/exactor) library does exactly what you want: generates "standard double" GenServer functions and significantly reduces GenServer boilerplate.

I've discovered that it's significantly easier for me to get in the flow state of mind while programming in Elixir. Comfortable and powerful. Definitely a worthwhile investment of time and effort.

I mean, if you want something really compact, why not APL?

      +/{(0=2|⍵)×⍵*2}⍳4
20

Edit: My point here, besides simply the joy of trolling, is that "compactness" isn't a great metric for analyzing a programming language, or a framework even. My APL example isn't very compact for APL, yet it's certainly /way/ more compact than the Python, Elixir, or Ruby versions.

Still, it's hardly comprehensible to me as a beginner APL programmer -- and I wrote it, just now. It's probably /completely/ incomprehensible to anyone who hasn't written APL before. Is APL still "better", going only by compactness?

The same in J:

    +/*~((0&=&(2&|))@:[#])(1+i.4)
    20
It's longer than APL version, but still cute! :)
Why not `import Enum` to get rid of the redundant `Enum.` part? As imports are lexically scoped there is no need to worry about name clashes (e.g. Enum vs. Stream):

        import Enum
        [1, 2, 3, 4]
        |> filter(fn(n) -> rem(n, 2) == 0 end)
        |> map(fn(n) -> n * n end)
        |> reduce(0, fn(n, sum) -> sum + n end)
While we're at it, why not use a `&` operator to shorten the lambdas:

        import Enum
        [1, 2, 3, 4]
        |> filter( &(rem(&1, 2) == 0) )
        |> map( &(&1 * &1) )
        |> reduce(0, &(&1 + &2))
...is it that much worse than Ruby then? It's a bit different - the syntaxes are different, after all - but it doesn't look much worse, I think.

Elixir, as well as Erlang, are peculiar languages. To achieve succinct code you need to phrase your code in a slightly different way, using pattern matching and guards, an occasional macro and alias/import commands (in Elixir's case).

And the separation between the external interface and an internal implementation of any GenSomething is a valuable thing! Even if in most cases the external interface does relatively little, it's important to have them separated. That is because the external interface functions execute in the caller process, while the implementation code executes in the OTP process. If the caller submits an invalid value to be called/cast to your server, do you want to crash the caller or the server? With the usual pattern, you get to make this choice. And let's be honest - in the simplest case, it's three one-line functions (start, call, cast) per GenServer - it's not that bad of an overhead. There's also Agent module for when you don't need a full GenServer, using it eliminates all the overhead (in terms of lines of code) you normally get when using GenServer.

Yeah, you have a point with the separation between client and server process. I guess the client must crash nearly every time. Still... I can't help feeling that there is something to improve in the syntax, to make it clear the context those functions run in.
I know you want to show it as parallel as possible, but: "pythonic" python (sum admittedly is a shortcut that only works for the special case +):

    sum(x * x for x in [1,2,3,4] if x % 2 == 0)
Works in Elixir, too...

    Enum.sum(for x <- [1, 2, 3, 4], rem(x, 2) == 0, do: x * x)
Edit: BTW: I'm not sure, but I think Erlang had list comprehensions even earlier than Python.
You can omit == 0 in Python because 0 is falsy like in C and add a not after the if. That gains another character

   sum(x * x for x in [1,2,3,4] if not x % 2)
and it matches this Ruby 2.4.0 (which added Array#sum)

   [1,2,3,4].map{|x| x.even? ? x * x : 0}.sum
You understood correctly that the point was not sheer compactness. I only wanted to provide a context to judge the Module.function syntax compared to the object.method one. The filter, map, reduce example was accidental and I'm sure there are clever ways to compact that example further in both languages.

So the pythonic way is be

    reduce_function(map_function(x) for x in input if filter_condition)
I prefer to write it in the order it runs (filter -> map -> reduce) but that's it. Thanks.
I was going to comment along these lines - while functools exists, where appropriate I'd say iterators and/or list comprehensions are more pythonic. Note that if one wants to work with simple reduce functions, there's the operator module to help:

  from functools import reduce
  from operator import add

  # I'd also say this makes for more readable code,
  # documenting intent - but many will probably say that
  # basic arithmetic should be clear enough:

  def is_even(n): return n % 2 == 0
  def square(n): return n*n

  # Don't do this for summing integers, just use "sum":
  reduce(add,
    (square(n) for n in
      range(1,5) if is_even(n)))
  > 20
Note the use of range() rather than literal list - if you have a list, that should probably be passed in by name.

Anyway, the point wasn't so much bikeshedding or code golfing - just expanding on what I think is "more pythonic" take on it.

And to be clear, I'd probably prefer:

  sum(x * x for x in range(1,5) if x % 2 == 0)
for this particular example. And for more complex "real world" cases, I'd probably prefer to define my "reduce" function directly ie:

  def my_sum(iterator): return reduce(add, iterator)
  int_sequence = range(1,5)

  my_sum(square(n)
    for n in int_sequence
      if is_even(n))
  > 20
Note that python has map and filter as built-ins, so it's also possible to do:

  sum(map(lambda x:x*x,filter(lambda n:n%2==0,range(1,5))))
  # Which I find rather unreadable, but gets a bit better
  # without lambdas:
  sum(map(square, filter(is_even,int_sequence)))

(Phew, please excuse my personal dive into new/old list-comprehension/functional python -- but at least now I'm more clear on why everyone keep adding "threading" macros/syntax to their functional languages :-)

[ed: And if one wants threading-like syntax along with some parallelization and lazy-ness in python, there's a module for that: https://github.com/EntilZha/PyFunctional

  from functional import seq
  (seq(range(1,5))
    .map(square)
    .filter(is_even)
    .reduce(add)
  )
Just FYI.]
Right, and then in perl5:

    sum map $_**2, grep !($_ % 2), 1, 2, 3, 4
I find the python and perl versions more aesthetically pleasing than the ruby one, tbh.

    scala>
    List(1,2,3,4).
      filter(_ % 2 == 0).
      map(x => x * x).
      reduce(_ + _)