9 ms·
Look, I love functional programming. I use it all the time and I procrastinate in the shower about the best to add currying to all my favorite languages. That b
by d0m 13y ago
Look, I love functional programming. I use it all the time and I procrastinate in the shower about the best to add currying to all my favorite languages. That being said, even after all those years, I still find this:
expr, res = "28+32+++32++39", 0
for t in expr.split("+"):
if t != "":
res += int(t)
print res
SO MUCH more readable than this:
from operator import add
expr = "28+32+++32++39"
print reduce(add, map(int, filter(bool, expr.split("+"))))
A couple years ago I created a library called Moka in python to make functional and nesting easier. That would be written this way:
(Moka(expr)
.do(string.split, '+')
.keep(lambda x: x != "")
.map(int)
.reduce(moka.add))
Still, the imperative version, althought it has state and feels hacky for a functional hacker, is still pretty damn clear and easy to read.
- ak217 13y agoexpr = '28+32+++32++39' print sum(int(i) for i in expr.split('+') if i is not '')
- llimllib 13y agoIt's really weird to me that the author prefers map(add, ...) to sum(generator expression).
- rbanffy 13y agoBecause with map you can use any function and sum maps __add__. It also illustrates passing functions as arguments. More subtly, a generator (or list comprehension) implies the order in which the results are generated and an implicit but observable changing state. If I'm trying to write (or, at least, express, since we are talking about Python) parallelism, map would be a better choice.
- Locke1689 13y agoMore subtly, a generator (or list comprehension) implies the order in which the results are generated and an implicit but observable changing state. If I'm trying to write (or, at least, express, since we are talking about Python) parallelism, map would be a better choice. This is definitely not true -- generator expressions can be expressed using lazy list semantics. By your logic every Haskell list comprehension would be stateful. I would encourage you to write the denotational or operational semantics of your expressions in these matters rather than simply going by intuition.
- rbanffy 13y ago> generator expressions can be expressed using lazy list semantics That does not change the fact the results are given in a certain order. Lazy list semantics means the data will be calculated as it's needed, therefore, the calculation cannot be parallelized.
- Locke1689 13y agoNot true. See http://hackage.haskell.org/packages/archive/parallel/3.1.0.1/doc/html/Control-Parallel-Strategies.html#v:parList http://hackage.haskell.org/packages/archive/parallel/3.1.0.1...
- rbanffy 13y agoI was talking about Python. A list comprehension is defined as having the behavior of a loop.
- Locke1689 13y agoOnce again, that doesn't semantically make a difference, unless the loop is side-effecting. However, even without side effects, Python cannot necessarily make any kind of guarantee about such computations since arrays are mutable and can be modified out from under the map. This is why parmap equivalents take iterables in Python, not arrays. It doesn't matter if you have lazy or eager semantics, only if your semantics are side-effecting.
- klibertp 13y agoIt's `reduce(add, map(int, ...))`. It's more general than `sum(generator)` - you could write `reduce(mul, ...)` or anything else, while with sum you'll get sum and nothing more. Also, I have no problem with `reduce(some_fun, (x for x in iterable))`, which works too. Generally, while `map` in python is much better written as list comprehension or generator expression, reduce has no equivalent short of explicit loop with accumulator variable.
- hcarvalhoalves 13y agoGenerator expressions are a nice addition to Python, but can't replace everything. It doesn't hold some properties, like partial application. In the end it's sugar that works well as one-line loops, but I don't find the result elegant when you mix and match with other high-order functions, lambdas, or when nested. It turns into a prefix/postfix/infix mess.
- vog 13y ago> It turns into a prefix/postfix/infix mess. Other functional languages like Haskell handle this "mess" quite well.
- _ZeD_ 13y agoI would remove ` is not ''`
- pekk 13y agoThis is why Moka was not as interesting as it could have been in another language.
- matchu 13y agoTwo important Python notes: 1. `is` really shouldn't be used for string comparison. It checks object identities rather than string equality, which is broken here for two reasons: first, because it depends on the interpreter to keep one true copy of `''` (which CPython seems to do, but doesn't have to by the spec), and, second, it doesn't even work consistently in CPython anyway if `expr` is a Unicode string, since `'' is not u''`. (Try replacing `expr` with `u'28+32+++32++39'` and you'll get an error.) 2. If an object is known to be a string, test non-emptiness by testing the string itself: `bool('') == False`, whereas `bool('any other string') == True`. It's more concise and is equally clear to someone comfortable with Python. I agree that the given example of functional programming is pretty ugly (though it might've been chosen just to illustrate the author's specific points), but I think the version below is really quite nice and won't break on the string comparison. expr = '28+32+++32++39' print sum(int(i) for i in expr.split('+') if i)
- justinpombrio 13y agoThere is something to be said for functional code which clearly shows the operations being performed in (reverse) order. In Haskell: (putStrLn . show . sum . map read . filter (not . null) . split '+') "28+32+++32++39" or, if you like list comprehensions, (putStrLn . show . sum) [read x | x <- (split '+' "28+32+++32++39"), not (null x)] But don't underestimate the power of syntax! It's much easier to see the structure of your (d0m's) first example than of the second at a glance. I won't comment on the readability of my Haskell code as I'm already far to conditioned. (Note that I had to define 'split' by hand; it is a gaping hole in the otherwise very comprehensive Data.List library.)
- hcarvalhoalves 13y agoI personally find Haskell's syntax a bad example in language syntax. It's super powerful, but with too many inconsistencies, idiosyncrasies and noise. That, on top of having to wrap your head around new or difficult concepts, makes it a chore. Most code I (try to) read is intractable.
- tome 13y ago> I personally find Haskell's syntax a bad example in language syntax. It's super powerful, but with too many inconsistencies, idiosyncrasies and noise. Interesting. Could you give an example? I find Haskell to have pretty much the most consistent, non-idiosyncratic and clean syntax of any language I know.
- bjourne 13y agoThe order of operations is incredibly haphazard. Here is GP:s example again decorated with in which order the expression is evaluated: {6}({9}putStrLn . {8}show . {7}sum) {5}[{2}read x | x <- ({1}splitOn "+" "3++4"), {4}not ({3} x)] Here is the same code expressed in Factor: "3++4++9" "+" split harvest [ string>number ] map sum present print Annotated with evaluation order: "3++4++9"{1} "+"{2} split{3} harvest{4} [ string>number{5} ]{6} map{7} sum{8} present{9} print{10}
- hcarvalhoalves 13y agoFunny. I've been mostly exposed to imperative programming (learned on BASIC and Pascal), but nowadays I find functional "style" much more intuitive. The problem with loops and control structures is that they don't tell, at a glance, what data structure you're operating on and what transformations you're doing. It gets exponentially worse with else clauses and nesting. It just feels bureaucratic to program like that. In a functional style, you have more expressive primitives to work with (map, reduce, filter, fold, ...). That turns the code more concise by focusing on the data structures rather than variable mangling. That said, I find the syntax of most languages (even functional-orientated ones) a chore. This is an area that could improve a lot.
- nimish 13y agoIt's often faster too. List/generator comps are the way to go in python. As much as I like the combinatory approach, python does not make it very readable or maintainable.
- tome 13y agoI love your Moka example. To me it's so much clearer than either of the others and it's basically what I would write in Haskell. The understandability-complexity of the Moka style scales linearly with the number of components, because it is written "one-dimensionally". The problem with the imperative style is that its understandability-complexity is superlinear in the number of components because any variable in a nested loop can interact with any other variable. I also like your use of "keep" as opposed to "filter". I can never remember if "filter" means "filter out" or "filter in"!
- d0m 13y agoThanks. The library was highly inspired from Arc where most of the keywords are short and sweet. (keep, fold, [] shortcut, etc.)
- 0101 13y agoI wrote a library to help with this as well. f = X.split('+') | where(X) | foreach(int) | sum print f(expr) http://0101.github.io/pipetools/ http://0101.github.io/pipetools/
- agumonkey 13y agoFew things : - functional is accumulative in thinking. I can evaluate each step gradually without doing anything more than function composition. In imperative programming its coupled. - functional is side-effect free, can't mis-assign res. - functional is abstract friendly, I can pass the predicate I want, same for accumulation/combination. I kinda agree that the imperative code is somehow easier to read (3 little chunks of lines instead of 3 nested composition) but to write, test or adapt the functional way as some advantages.
- fexl 13y agoI agree that functional programming is wonderful, but imperative programs can often look especially clear. Quite a conundrum. In Fexl (http://fexl.com http://fexl.com), you can reduce the nesting by using the ';' ("pivot") syntax. So instead of this: print (fold add 0 (map int (filter valid_int (split "+" exp))) You can eliminate the nesting as follows: print; fold add 0; map int; filter valid_int; split "+" exp You could simplify that form by redefining "int" to return 0 on any invalid input. So we redefine int as follows: \int=(\str valid_int str (int str) 0) Presumably the original int dies on invalid input. Actually I would prefer that the inner int function returns either no or (yes val), i.e. a "maybe", and then we could wrap the default 0 around that. But in any case we now have a robust, fault-tolerant int function, and we can now say: print; fold add 0; map int; split "+" exp