Live data from Hacker News

Swift and the Legacy of Functional Programming

realm.io

91–100 of 188 posts

Re: Swift and the Legacy of Functional Programming

#91
post #80

Earlier quoted context omitted.

There's only one definition that matters: functional programming is programming with mathematical (pure) functions. As a consequence you get referential transparency and thus equational reasoning. But change this definition and the term becomes meaningless.

Though by this definition, Lisp, the granddaddy of functional programming languages, is not a functional programming language.

It indeed isn't. Here's my litmus test. Is 2^100 always equal to 2^100? Let's ask SBCL:

    * (eq (expt 2 100) (expt 2 100))
    
    NIL
Damn object identities, ruining muh equalities.

(Disclaimer: I'm not saying functional programming is the right approach for writing every program, but if a language can't even get arithmetic and relational operators right...)

Re: Swift and the Legacy of Functional Programming

#92

Yo, would be nice to have some functional programming language with decent syntax to program apple machines. Let's call it Dylan to honor the most recent winner of the Nobel prize for literature. Just kidding. The bottomline of this page and talk is that Swift is still not functional. But you can do cool things with it.

https://en.wikipedia.org/wiki/Dylan_(programming_language)

(for those who didn't get the joke... ;-)

Re: Swift and the Legacy of Functional Programming

#93

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.

persons = [person for person in (Person(name) for name in names) if person.isValid()] This is Python.

i've been struggling with python incomprehensions since time immemorial. map/filter are a breeze.

Re: Swift and the Legacy of Functional Programming

#94

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.

It's a matter of habit more than code readability. Five years ago, I would have found the second form easier to read. These days, not only do I find the first form much clearer but I find the second one a bit smelly because it mutates data. It's actually surprising how quickly you get used to the first form of code once the language you use supports it.

In this case, it's unfortunate that there's no, "will this name create a valid person object?" predicate. much much better to filter the names, then make the objects.

In this case, as long as append is O(1), i think the imperative version has a big benefit, it avoids building the name size list of persons. If you've got a billion names and 2 valid person objects, the imperative version is a big win. Of course that predicate i mentioned is the right way to go though.

I think the right way to do it is with a fold. But that's not built in, so hard to expect of novices.

I see what you're saying about mutation, i guess i have a higher tolerance for mutating stuff that you have the only reference to. I'm not really sure doing a += [validPerson] is much of a win. (but i think would be the right answer in a fold)

Re: Swift and the Legacy of Functional Programming

#95

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 with the exception of the anonymous variables. In scala, I would write val persons = names .map { name => Person(name = name) } .filter { person => person.isValid }

Dunno, it's fairly obvious what you're working with, being explicit doesn't buy that much (for me at any rate).

    val persons =
      names.map(Person.apply).
      filter(_.isValid)

Re: Swift and the Legacy of Functional Programming

#96
post #69

Earlier quoted context omitted.

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 dialect…

I guess you're right, it doesn't come so much from lambda calculus as I claimed, it was more inspired by it. Although to be fair, in the time the Lisp appeared, it was the closest thing (by a wide margin) to lambda calculus. I think it was a valiant effort to bridge the gap in that direction (and the design choices were influenced by the trade off that he also wanted a practical programming language).

Also, even languages like Haskell are not based only on theoretical lambda calculus, but they also have primitives for data types, which could be, in theory, represented by lambda expressions.

Re: Swift and the Legacy of Functional Programming

#97

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 st…

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

The first is less likely to require debugging in the first place.

> There are lots of data structures in this style of programming that don't have any names.

So you can only reason about things that have names? Now we know where idiomatic Java comes from.

> Who knows what kind of data structures map and filter create in order to do their work.

In most reasonable implementations, the only data structure being created is the final result (a functorial value in map's case, a sequence in filter's case). For example, in SML:

    fun map _ nil = nil
      | map f (x :: xs) = f x :: map f xs

    fun filter _ nil = nil
      | filter p (x :: xs) =
        if p x then x :: filter p xs
        else filter p xs
> But the core promise of functional programming—that you can stop thinking about the underlying procedures—never seems to fully pan out.

Functional programming doesn't promise freedom from procedures. It promises (and delivers) freedom from physical object identities when you only care about logical values.

---

@banachtarski:

Code that's likely to require debugging (say, because it implements tricky algorithms) should be isolated from the rest anyway, regardless of whether your program is written in a functional style or not. Say, in Haskell:

Bad:

    filter (\x -> tricky_logic_1) $
    map    (\x -> tricky_logic_2) $ xs
Good:

    -- Now trickyFunction1 and trickyFunction2 can be
    -- tested in isolation. Or whatever.
    trickyFunction1 x = ...
    trickyFunction2 x = ...
    
    filter trickyFunction1 (map trickyFunction2 xs)

Re: Swift and the Legacy of Functional Programming

#98
post #94

Earlier quoted context omitted.

It's a matter of habit more than code readability. Five years ago, I would have found the second form easier to read. These days, not only do I find the first form much clearer but I find the second one a bit smelly because it mutates data. It's actually surprising how quickly you get used to the first form of code once the language you use supports it.

In this case, it's unfortunate that there's no, "will this name create a valid person object?" predicate. much much better to filter the names, then make the objects. In this case, as long as append is O(1), i think the imperative version has a big benefit, it avoids building the name size list of persons. If you've got a billion names and 2 valid person objects, the imperative version is a big win. Of course that pr…

Most languages implement map and filter in terms of lazy sequences so they would not allocate an intermediate list (Scala is an exception but I believe you can request laziness).

Re: Swift and the Legacy of Functional Programming

#99

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.

In Python, the only good language, this could be expressed as:

    [person for person in (Person(name) for name in names) if person.isValid()]
or:

    filter(lambda p: p.isValid, map(Person, names))
or:

    persons = []
    for name in names:
        person = Person(name)
        if person.isValid():
            persons.append(person)

Re: Swift and the Legacy of Functional Programming

#100

Earlier quoted context omitted.

Haskell's syntax isn't actually that complicated. There are some common functions with operator names that can be hard to read if you're not used to them, but those are library defined. The syntax itself is actually fairly simple. And incidentally, this would probably be something like (EDIT: made example more realistic): filter personIsValid (map initPerson names) in Haskell. Which looks much cleaner than the lisp t…

Except in these two brief examples, Haskell employs syntactic sugar with hidden semantics that nobody but an acolyte would understand (periods and two kinds of brackets mean what?). At least the Lisp scoping here is explicit and employs minimal abstruse sugar. I'll admit Lisp's myriad nesting of brackets is not ideal either. But surely there are more elegant and intuitive notations for functional scoping than is seen…

Lisp provides its own solution to the nesting of parentheses: if you're writing an expression which is too deep, you can invent an ideal syntax which more direclty expresses what you want to say. Then teach Lisp to understand that syntax. Then there is a myriad of parentheses in the macros which implement the syntax; elsewhere, there are fewer parentheses.

Very simple example: once upon a time, in the early 1960's, Lisp only had the COND operator. There was no IF. Programmers often had to make two-way decisions using COND, writing things like (COND (condition then-expr) (T else-expr)). Too many parentheses. So they came up with the IF macro allowing (IF condition then-expr else-expr). This simply expanded to the COND. Six parentheses are reduced to two.

Like most other programmers, Lisp programmers care about not writing mountains of distracting, irrelevant code that is hard to understand. That's why the backquote syntax was invented for instance. Before the backquote syntax, macros were difficult to write.

Say you wanted to transform the syntax (foo bar) into (let ((bar (whatever))) (do-something bar)).

You had to write a macro function which took the object (foo bar) as a single argument, analyzed it, and then constructed the output. Suppose we already have the BAR part of the (foo bar) from in a variable called SYM. Then we have to do something like:

   (list 'let (list (list sym '(whatever))) (list 'do-something bar))
   ;; I'm not going to bother to get this right
With the invention of the backquote, this could be rewritten like this:

   `(let ((,sym (whatever))) (do-something ,sym))
A nice template which looks like the output that we want, and indicates the places where we want to stick the variable BAR symbol, held in SYM.

Obviously, backquote templates have parentheses. But the notation itself isn't parentheses; it consists of the backtick syntax, and the commma operator for interpolating values. Also a ,@ operator for splicing lists. In some Lisp dialects, the backtick is transformed into a nested list object. For instance `(a b ,c) might turn into (quasiquote a b (unquote c)) "under the hood".

Lispers also invented destructuring: being able to write the macro with a kind of pattern match for the syntax, so that the elements of the to-be-transformed-form are pulled apart into separate variables.

Lisp is not a finished language. New ideas continue, and new surface syntax like the backquote is not off the table. Usually, Lisp programmers would, I think, prefer that such new syntax integrate into Lisp by not "disturbing" surrounding syntax by involving it in ambiguity. Something tidy and simple that has a big payoff is best.

Lisp programmers are not tone-deaf to notational advantages, and do not regard macros as the one and only way to reduce verbosity.

I'm conducting my own one-man research program into improving Lisp and have come up with some great new ideas.

I have a Lisp dialect which is quite ergonomic, leading to terse programs for everyday "data munging" tasks (and continuing to get better).

Post reply on HN