DEV Community


Discussion on: Why I don't believe in pure functional programming anymore

yujiri8 profile image
Ryan Westlund Author

List comprehensions are a good example -- elegant syntax for the easy 20% of cases, which you pay for in the 80% where map would have been simpler.

Eh? If you ask me, list comprehensions are more readable than map in almost every case. But Python does have map in its prelude - as well as filter, and reduce is in the functools module. (I think there's also some currying stuff in there, which I've never needed.)

Also, comprehensions have serious performance benefits, which I attribute to them not constructing a lambda object and not iterating twice when you do both map/filter in one.

By contrast, Ruby's solution is having a lightweight syntax for passing code into functions ("blocks"). Any function can be called with a block -- it's not opt-in, or even opt-out, it's just part of what it means to be a function. So map, filter, and reduce with a closure is very readable, and the standard lib stuff concerned with enumerables is all effortlessly higher-order.

I've been digging into Crystal lately, which is basically statically typed Ruby. I'm finding the block solution a bit awkward and I'm not sure if it's even as powerful as just allowing functions to be passed to each other. My understanding is that you can't directly pass a function as an argment in Crystal or Ruby because just referencing its name calls it with 0 arguments. Crystal and Ruby have multiple different kinds of these things - blocks, procs, stabby lambdas - where Python just has functions and lambdas. The block syntax, while nice, seems restricted in use case to me because (unless I'm mistaken) it only allows passing a single block.

FWIW though I do see that Python's lambda story is a bit weak because they can only return something, and inline defs (which are prohibited at least in Crystal) don't handle scoping in the most desirable way. My ideal story in this department is Javascript (though I kind of hate to admit it, since I hate everything else about Javascript).

Not only is that great when you build lists, it's great everywhere you use higher-order functions. As another specific example -- you don't need things like Python's context managers, which are sort of heavyweight. Regular ruby functions can already do that.

Yeah, I realized yesterday that blocks are the equivalent of with, but I'm not sure if I like them more. I don't think context managers are heavyweight though. The contextlib.contextmanager decorator can turn a function into one; the function does its setup in try and yields the resource, and its cleanup in finally.

Also, re: Rust, lack of default arguments (and apparently overloading too) sounds really annoying. Python's evaluation of those at function-define-time is a really, really stupid gotcha, but at least Python has them. Even C has vararg functions. Good lord.

Lol. I agree that the evaluation of defaults at compile time is a stupid gotcha, but as for vararg functions, I'm not actually sold on them. What's the benefit compared to functions that take arrays?

Thread Thread
johncip profile image
jmc • Edited

Python does have map et al, but lacks the anonymous inner functions which would make them more useful for building & reducing collections.

A few things:

  • Blocks aren't merely the equal of with -- they're more like lambdas where return acts on the outer function. So (like lambdas) they cover the with case, the collection-wrangling cases, and many others.
  • You can pass multiple blocks -- proc makes blocks, lambdas, and even named functions first-class. But the "default block" has more convenient syntax.
  • I don't think it's fair to say that Python has one way to do it (lambdas) while other languages have multiple. I'd say Python has zero, unfortunately. Python's lambdas are anonymous inner expressions while every other language uses the term to mean anonymous inner functions. Likewise, some variants of BASIC have "user-defined functions" which are limited to single statement. Beats not having them, but still not as useful as real functions.

[I edited the above to group them and clarify what I mean by lambdas]

One more example -- there's no "reduce" comprehension in Python. So you're back to loops there. They give you a sum function, but you don't have the thing that would let you write your own sum function elegantly.

I should probably stop... it's hard to talk about these things convincingly because often the problem with Python isn't with what it has, but the fact that it needed to have those things in the first place. Along those lines, yes the generator-style context managers are lighter-weight than classes, but languages with anonymous functions don't need to provide "generator-style context managers" in the first place. Anyway, I don't want to come off as hating Python. It's just behind the times in some ways.

as for vararg functions, I'm not actually sold on them. What's the benefit compared to functions that take arrays?

Not much, since the varargs is syntactic sugar. But it lets you be explicit about intent -- sometimes you really are passing in single argument that happens to be an array. Varargs let you say "this isn't an array, but a bunch of discrete arguments that I will figure out what to do with at runtime."

Thread Thread
yujiri8 profile image
Ryan Westlund Author

One more example -- there's no "reduce" comprehension in Python. So you're back to loops there. For the specific case of adding, they give you a sum function. But you don't have the thing that would let you write your own (at least in a functional style).

Huh? I just mentioned functools.reduce. It works with any function or lambda. What does Ruby's reduce get that Python's doesn't?

Thread Thread
yujiri8 profile image
Ryan Westlund Author

Oh - is it the ability to reduce from the outer function (halting iteration)?

I guess that could be useful... in some really obscure situation or something...

Thread Thread
johncip profile image
jmc • Edited

Oh I just meant Python's comprehensions -- they cover some of the places you'd use map & filter, but there's no comprehension for going down to a scalar, AFAIK. (That said, since reduce can be used to build collections, list / dict / set comprehensions do end up overlapping with it.)

And to be clear, when I said "you're back to loops" I meant for the places where you wouldn't bother to create a named inner function, or to compose named functions before calling them. (It's not that you don't have options, but I'd argue that they're not idiomatic or lightweight.)

What does Ruby's reduce get that Python's doesn't?

While reduce works the same everywhere, it's most useful in languages with anonymous inner functions. It's true of higher-order functions in general, not just reduce.

Ruby goes a step further by providing a shorthand syntax for doing this (but is not unique there -- Clojure has the #() shorthand, and JS has ->).

FWIW, since you mentioned it -- I've only felt the need to halt from inside a reducer function in Clojure. I forget why, but it probably had to do with starting from an infinite stream. It'd be an optimization to prevent the creation of intermediate collections.