Live data from Hacker News

Swift and the Legacy of Functional Programming

realm.io

81–90 of 188 posts

Re: Swift and the Legacy of Functional Programming

#81
post #58
post #44

Earlier quoted context omitted.

Take it from the other side: why would a language that allows to create higher-order functions/closures not be called functional?

Because that makes the term meaningless. You may as well ask "why don't we call any language that lets you do two actions in sequence 'procedural'?"

That doesn't make the term meaningless, it just means that "functional programming" is a victim of its own success.

Re: Swift and the Legacy of Functional Programming

#82

I'm curious, does every else find this let persons = names .map(Person.init) .filter { $0.isValid } easier to read than this? var persons: [Person] = [] for name in names { let person = Person(name: name) if person.isValid { persons.append(person) } } I understand and appreciate the value of compact code, but I find the first one harder to read. A lot of inferred/token based coding is harder for me to mentally parse.

I think the readability is a bit of a wash.

But the second one is more debuggable than the first, which I think is even more important than readability.

In the first case, you need to rewrite the control structure to even be able to inspect anything:

    let allPersons = names.map(Person.init)
    {log allPersons[0].name}
    {breakpoint}
    let persons = allPersons.filter { $0.isValid }
There are lots of data structures in this style of programming that don't have any names. Who knows what kind of data structures map and filter create in order to do their work. Are we allocating 2 arrays? 1? None? In the procedural style, everything that exists in memory has a name.

It's also totally unclear what the order of operations is. Are Person.init and $0.isValid alternated? Is the `map` run in full before the `filter` starts? No way to know.

People talk shit about procedural programming, as if it's antiquated. But the core promise of functional programming—that you can stop thinking about the underlying procedures—never seems to fully pan out. So when you inevitably need to start digging under the hood to figure out what your declarative code actually means in practice, you end up having to think procedurally anyway. Now you're thinking procedurally, but your code is declarative, and the runtime is trying as hard as it can to prevent you from knowing what's exactly happening moment to moment.

I think there are specific cases where a declarative interface is the right abstraction. CSS is a good example. But these are narrowly defined domains with relatively clear semantics that get frequent use, so the time it takes to learn the semantics will pay off.

The idea of littering your entire codebase thickly with declarative APIs, each of which has unique control structures that must be understood in order to read code, is not a good approach in my opinion.

This is what Rails is, and it creates a situation where you are captive to your tools: you can do a lot very easily, but you cannot stray far beyond the declarations that your library author overlords have chosen for you, or you quickly find yourself in a space where in order to not shoot yourself in the foot you need to have a huge body of internals in your head.

Re: Swift and the Legacy of Functional Programming

#83

I'm curious, does every else find this let persons = names .map(Person.init) .filter { $0.isValid } easier to read than this? var persons: [Person] = [] for name in names { let person = Person(name: name) if person.isValid { persons.append(person) } } I understand and appreciate the value of compact code, but I find the first one harder to read. A lot of inferred/token based coding is harder for me to mentally parse.

I find it easier to read but I'd struggle to come up with it myself from zero

Re: Swift and the Legacy of Functional Programming

#84
post #69
post #42

Earlier quoted context omitted.

The term "functional programming" is overloaded, but I think there's a sensible way to split the term into two halves. "Purely functional programming" is writing programs to resemble mathematical functions, with referential transparency and absence of side-effects and so forth. Also know as "what Haskell does." "Function-oriented programming" is writing programs using functions as your main tool for abstraction, enca…

Actually, both of those families come from lambda calculus, except in different way. Lisp comes from untyped lambda calculus (and adds things like car, cdr and eq on top of it), while Haskell (and ML) comes primarily from typed lambda calculus. I offer a definition of "functional programming" as "based on semantics of lambda calculus".

Lisp does not come from lambda calculus. Anonymous functions in Lisp get their LAMBDA name from lambda calculus, that's all. MacCarthy admitted that he didn't even understand lambda calculus properly, which is why early Lisp was dynamically scoped: lambdas didn't capture lexical variables. Whereas lambda calculus is lexical. Lexical scoping was adopted in later Lisp dialects and into Common Lisp, making those dialects retroactively related to lambda calculus. (Emacs Lisp shows the traditional behavior of dynamic scoping; therefore it couldn't be said to be related to lambda calculus.)

Lambda calculus is a formalization of number theory which builds up numbers from functions. An important result is that lambda calculus is Turing complete. It shows that we can boostrap numbers and number theory from almost out of nothing, using Church numerals.

Lisp has never built numbers out of Church numerals; it had ordinary integers.

Also, lambda calculus, typed or not, does not have its own syntax as a data type. It doesn't have symbols. You don't quote a symbol and pass it to a function and so on.

So that's basically it; there is a link between Lisp and lambda calculus due to the use of the word lambda in a related way, and a somewhat stronger link between lexically scoped Lisps (which came later) and lambda calculus.

Re: Swift and the Legacy of Functional Programming

#85

I'm curious, does every else find this let persons = names .map(Person.init) .filter { $0.isValid } easier to read than this? var persons: [Person] = [] for name in names { let person = Person(name: name) if person.isValid { persons.append(person) } } I understand and appreciate the value of compact code, but I find the first one harder to read. A lot of inferred/token based coding is harder for me to mentally parse.

I find the first example much more intuitive. It declares intent, rather than the mechanics of delivery. And that's where we need to be heading as programmers.

Re: Swift and the Legacy of Functional Programming

#86

Earlier quoted context omitted.

That's because your code assumes those convenience functions exist while the parent's code writes out the anonymous functions. The direct translation of yours' to Clojure would be: (filter valid-person? (map init-person names)) Meanwhile, the direct Haskell translation of the parent comment is: filter (\p -> isValid p) (map (\n -> Person n) names)

No one would ever write the latter, because (\p -> isValid p) is equivalent to isValid. However you're right that the function names would probably be a little longer in practice, and I've edited my example to reflect that. (But they're not convenience functions, they just have longer names due to Haskell's more limited namespacing)

Not a Haskell expert, but maybe monomorphism restriction can require you to perform an eta abstraction. Just saying.

Re: Swift and the Legacy of Functional Programming

#87
post #85

I'm curious, does every else find this let persons = names .map(Person.init) .filter { $0.isValid } easier to read than this? var persons: [Person] = [] for name in names { let person = Person(name: name) if person.isValid { persons.append(person) } } I understand and appreciate the value of compact code, but I find the first one harder to read. A lot of inferred/token based coding is harder for me to mentally parse.

I find the first example much more intuitive. It declares intent, rather than the mechanics of delivery. And that's where we need to be heading as programmers.

This. So much. Declaring intent (what) is much more useful than declaring the mechanics (how). I don't typically care about the "how", just the "what". And the more readable that is, the better.

Re: Swift and the Legacy of Functional Programming

#88
post #12

Earlier quoted context omitted.

I always thought FP == pure functions. I guess not?

I would call a language like Haskell "purely functional" and a language like OCaml "impurely functional". A functional programming language to me is a programming language where functions are in charge of data. In a language like Java, data is in charge of functions (broadly speaking). In a language like Prolog, relations are in charge of data. It's all about what perspective the programmer has between the abstractio…

You have it backwards. In Java, collections of procedures (aka objects) are in charge of data. In Haskell and OCaml, data exists independently of the procedures that will operate on it.

Re: Swift and the Legacy of Functional Programming

#89
post #55
post #46

Earlier quoted context omitted.

I definitely hear what you're saying. In this particular case I find the more succinct map/filter a little easier to grok, but as soon as you have a bunch more clauses with some flatMap() and reduce(), the "functional" way can get out of hand quickly. In simple cases, I prefer (Python's) list comprehensions. In more complex cases, I prefer the loop(s).

I find Python's list comprehensions mind-numbing in many simple cases, such as [x for x in xs if some_condition(x)] rather than just xs.filter(some_condition). We're repeating the variable name three times and zero of those times convey any useful information, and we don't find out until the very end of the line that the everything except "[", "]" and "some_condition" was ignorable. And in cases where a data-deriving…

This is the one case where python's list comprehension syntax isn't so efficient. Don't worry about it - be consistent and use it nevertheless. It doesn't matter much.

In the cases where x gets also transformed or you do some unpacking the syntax is very efficient and easy to read: [f(y) for x, y in items if g(x)]. Basically it's a poor man's relations programming (think databases). It's brilliant. (And definitely easier to read and write than the Haskell list comprehension syntax).

Post reply on HN