> I value readability and usefulness for real code. There are some places where map() and filter() make sense, and for other places Python has list comprehensions. I ended up hating reduce() because it was almost exclusively used (a) to implement sum(), or (b) to write unreadable code.
No. A functional language is a language that primarily uses functions as defined in mathematics for it's programming language. Functions defined in mathematics are single lined expressions where no mutation takes place.
In traditional languages programs are a list of procedures that change and mutate state.
A list comprehension is functional in every way as it is a single expression. It does not change state. It is functionally equivalent to map and filter and even flatmap.
If you need an official source you just need to know that list comprehensions are borrowed from haskell. That's where the term came from and that's where the feature was borrowed from.
I don't really see how list comprehensions aren't functional- it's just a different notation for dealing with maps, filters, and cartesian products. Python borrowed list comprehensions from Haskell in the first place.
(not GP and I agree with you, and wanted to add some additional thoughts that may help others.) The comprehensions are great and have a functional flavor, sure. But when you hit the corner cases in Python you feel that the addition they provide to Python are not taking the language close to Haskell. Lambda functions within the Python comprehensions have limited power, reductions don’t work as nicely with comprehensions as map or filter; more generally, when you compose comprehensions things feel messy without the ability to pipe previous results to a next function, so you are using lots of intermediates and have to think of useful names which is hard. The end result can be good and closer to pseudo code, which is fantastic. However Python does not satisfy the need of many people used to lisp and often takes much longer to get right in those complex corner cases where Haskell or lisp would make validation and testing much easier.
> Lambda functions within the Python comprehensions have limited power,
lambda functions in python have as much expressive power as any function.
>comprehensions things feel messy without the ability to pipe previous results to a next function
compose :: (a -> b) -> (b -> c) -> (a -> c)
is definable in python Along with type checking. This is basically your pipe operator. Python has very much the capability to come rather close, closer than javascript to the flavor of functional programming found in ML languages like haskell.
The benefit of using python is that you can cheat a lot too which is more challenging to do in language as strict as haskell.
Here:
from typing import Callable, TypeVar, List, Any
from numbers import Number
A = TypeVar("A")
B = TypeVar("C")
C = TypeVar("C")
def compose(f: Callable[[A], B], g: Callable[[B], C]) -> Callable[[A], C]:
return lambda x: g(f(x))
def curry(f: Callable[[Any, ...], Any], x: Any) -> Callable[[Any, ...], Any]:
return lambda *args: f(x, *args)
def compose_many(fs: tuple[Callable[[Any], Any], ...]) -> Callable[[Any], Any]:
return (lambda x: x) if len(fs) == 0 else fs[0] if len(fs) == 1 else compose(compose(fs[0], fs[1]), compose_many(*fs[2:]))
MultAllType = Callable[[List[Number], Number], List[Number]]
multAll: MultAllType = lambda x, y: [i * y for i in x]
SubTractAllType = Callable[[List[Number], Number], List[Number]]
subtractAll: SubTractAllType = lambda x, y: [i - y for i in x]
quadrupleAndSubtractTwoFromAll = compose_many(
curry(multAll, 2),
curry(multAll, 2),
curry(subtractAll, 2))
as literals:
quadrupleAndSubtractTwoFromAll = compose_many(
lambda x: [i * 2 for i in x],
lambda x: [i * 2 for i in x],
lambda x: [i - 2 for i in x])
Python gets a bad rep for being noob language but it's capable of functional programming with very very good type checking. You can cheat too and go no types like lisp as I've shown in the example above.
Overall I would say it's superior to typescript in terms of functional programming due to exhaustive pattern matching (which I did not demonstrate above.)
In terms of FP in python versus FP in lisp it's mostly just opinionated differences. Python and lisp sort of even out because python has optional type checking and lisp has macros. I would say though that python leans more towards the ML flavor of FP which makes it less comparable to lisp. Apples and Oranges.
Lambda functions in Python cannot contain statements (you couldn’t do a try/except block, or a while loop); they cannot have assignments, or multiple expressions. They don’t feel syntactically as expressive as other functions in Python.
Your composition and curry are OK, and related tools exist in standard libraries. The lack of a first class support is a minor annoyance but not a showstopper, and the solutions are not as natural as genuine pipe notation (you can’t simply write left to right and generate text autoregressively). The lambda limit was more real in my mind and I hope Python eventually corrects it.
No function in functional programming allows more than one statement.
That is literally the definition of FP. That the definition of a functions be a singular statement as opposed to a procedural function is a function made up of several statements where each statement has the potential to assign or reassign values. If you write a function with procedures you are not doing functional programming. That's why python lambdas are defined that way.
That being said python doesn't support the let...in expression which is common to do in fp, but neither does JavaScript.
JavaScript just supports anonymous functions that aren't functional. If you make the procedures const then you get something similar to let in which is arguably a trick to make it work.
You can do tricks in python to achieve the same thing with lambdas. In JavaScript you do tricks to make anonymous functions behave like lambdas with let-in, in python you can also do a trick to get let-in behavior in a lambda.
f = lambda x:(
y := 1,
w := 3,
y + w + x)[-1]
Arguably though you shouldn't do this. The pythonic philosophy is that Any function past a certain level of complexity you should just use def rather then inline it. This philosophy still applies in FP.
Ultimately these are minor syntactic and stylistic differences involving trivial sugar.
I don't think python will "correct it" because from their perspective there is nothing to correct. Make your function a def. That's the pythonic way.
The first class support for for pipe operators and what not are not present in JavaScript or python. It's not showstoppers. But in python it is possible to get composition working with magic method operating overloading. It's just not first class.
Fp in js never involved the algebraic operator style promoted by haskell. In this sense it's more lisp style and python is similar to js in this respect.
> lambda functions in python have as much expressive power as any function.
There is no fundamental reason why Python doesn’t have let, and there is no fundamental reason to not be able to catch exceptions within a lambda. Python already fixed the unneeded print statement in its history so I think there is hope. (I don’t think js is relevant here; I was trying to explain why lispers or people who like Haskell may revisit their improved expressive environments).
Lisp, JavaScript, and Python are all dynamically typed, so I made the suggestion with that apparent preference in mind.
The example is interesting. Here's how I might have done something similar in JavaScript. Currying is part of the JavaScript language, and I composed a list of curried functions using reduce.
const multiply = a => b => a * b
const subtract = b => a => a - b
const quadrupleAndSubtractTwo = a => [multiply(2), multiply(2), subtract(2)].reduce((x, f) => f(x), a)
const quadrupleAndSubtractTwoFromAll = numbers => numbers.map(quadrupleAndSubtractTwo)
> quadrupleAndSubtractTwoFromAll([3, 4, 5, 6, 7])
> [ 10, 14, 18, 22, 26 ]
You didn't implement the fundamental operator of functional programming. Composition of functions. That is the key operation of functional programming.
Reduce map are all available in python as well. Basically your implementation is doable in python as well with the same level of tenseness. The main difference as I said earlier is typescript lacks exhaustive pattern matching and sum types.
But anyway no point in this. Python can match the terseness of JavaScript.
The only reason my implementation was long was because I used types and recursion. Python can have the same terseness as your old style untyped javascript if needed:
remember the reduce function can be built out of recursion. What you see in JS is just a convenience function for reduce, if a language has recursion, reduce can easily be built in one line.
def reduce(f, xs, x):
return x if len(xs) == 0 else reduce(f, xs[1:], f(xs[0], x))
Thus reduce isn't really a "feature".
Python is superior to JS for FP because it can match the expressiveness of JS while adding the additional feature of exhaustive pattern matching and list comprehensions, which JS or TS can't do. This makes JS explicitly inferior.
Recursion in Python suffers from limited utility due to the lack of TCO and the strict (but adjustable) recursion depth limit. JS, for all its problems, at least includes TCO these days. Shame Python has decided to stay on the worse path there.
You're seriously misinformed. All of what you're saying is categorically false on all counts and exhibits fundamental misunderstanding about the topic at hand. You are not just wrong from one perspective, you are wrong from every possible valid perspective conceivable.
First of all the discussion at hand is not the language implementation, the discussion at hand is about the language specification. TCO is a implementation feature, whether the engine/compiler/interpreter implements said feature is irrelevant to the conversation which is about the specification NOT the implementation. So on this count you are wrong. More accurately off topic.
Second there are several python interpreters. The most popular is CPython. CPython does not include TCO deliberately because Guido specified he wanted stack traces at all times for easier debugging. However PYPY which uses a JIT does use TCO. Stackless python another interpreter by it's very name also does something very similar to TCO. So additionally you are completely wrong again from this angle.
Third there are two popular JavaScript engines in use by browsers and nodejs today. V8 and spider monkey. Spider monkey Does not implement TCO. V8 Did implement TCO for a small amount of time, however, this was later removed due to complexities when debugging practical issues. Thus V8/Spidermonkey and therefore Nodejs and Chrome and Firefox do NOT support TCO. Edge supported TCO with chakra but after a shift to V8 edge also no longer supports TCO. So on this count you are wrong again.
Overall to sum up everything here, you are just wrong, wrong, wrong.
The statement still stands, as a language specification python is more superior feature-wise for FP then JS or TS.
> First of all the discussion at hand is not the language implementation, the discussion at hand is about the language specification. TCO is a implementation feature, whether the engine/compiler/interpreter implements said feature is irrelevant to the conversation which is about the specification NOT the implementation. So on this count you are wrong. More accurately off topic.
Additionally python with a type checker supports sum types AND exhaustive pattern matching similar to haskell and rust.
Typescript does not support this as far as I know. Outside of types the differences between the two languages are just sugar.
Lisp on the other hand is something truly different.