Live data from Hacker News

Swift and the Legacy of Functional Programming

realm.io

101–110 of 188 posts

Re: Swift and the Legacy of Functional Programming

#101
post #35
post #26

There seem to be a lot of "pretend" functional languages around these days. I have recently been engaging with Scala. Having heard and read so much about how it embraces functional style I was kind of shocked to find the number of limitations and practical impediments to actually using it that way. I am starting to wonder if functional programming has finally fallen victim to the same problem that has afflicted all o…

Scala is about as functional as it gets - the only remotely mainstream language that's more so is Haskell, IME. What kind of issues did you have?

I didn't mean that the language itself isn't functional - it certainly wants to be. But when I tried to naively apply it using the idiomatic constructs that I found online I got a lot of performance and memory issues. In fact some code that I wrote in Groovy (which I thought would be slow) was much faster than the naive port I did to Scala (which is commonly said to hit nearly native java speed). When I dug into those by profiling it turned out that I needed to have a deep understanding of how the compiler was treating Scala constructs to avoid performance pitfalls. A good example is here:

https://issues.scala-lang.org/browse/SI-1338

Another is that I've almost never managed to use recursion in my algorithms because Scala seems to have very limited ability to successfully optimize tail recursive calls.

Another problem is all kinds of unexpected boxing, unboxing, and implicit conversions of collections that I wasn't expecting.

Again - all the language features are there, just in practice it isn't working out for me very well when I try to use them idiomatically. I'm still learning. But I also learned Haskell and the experience was very different - once I figured out the idiomatic way to do something it usually was also well optimized.

Re: Swift and the Legacy of Functional Programming

#102

Earlier quoted context omitted.

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

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

I'm all for functional languages but this scares me a bit. What do you do when you need to debug something and everything ends up being harder to debug but "less likely to need debugging." I've actually run into this situation a number of times and faced with a sea of linked compound expressions, debugging can be a daunting proposition.

Re: Swift and the Legacy of Functional Programming

#103
post #68

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 one to be far easier to read. You can basically just read it from top to bottom and know exactly what it does. "Take names, turn each one into a Person, keep the ones that are valid." The second one takes a lot more work. First, I have to read through the code and recognize that this is a loop that accumulates values into a new array. Then I have to pick it apart and see exactly where the accumulatio…

Is the first one two loops or one?

Re: Swift and the Legacy of Functional Programming

#104
post #94

Earlier quoted context omitted.

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).

hmm, i don't think lazyness is enough. for example, in the billion names example, let's say the first and last elements are valid. at the first step you'll wind up with something like

   validPerson : thunk
after n steps where n
   validPerson : invalid : invalid : invalid : ... : thunk
then finally at n = 1e9

  validPerson : invalid : ... : validPerson
because the intermediate calculations still need to happen.

getting that first answer is cheap and quick. getting the second answer requires evaluating those billion - 1 thunks.

maybe you're thinking of list fusion? AFAIK, only haskell does that.

Perhaps i'm misunderstanding your point. But even then, lisp, scheme, smalltalk, python and perl don't implement map and filter in a lazy way. Of course, i'm thinking in terms of haskell lazy, perhaps, again, i'm misunderstanding and you mean something else.

In retrospect, i think i'll revise my opinion and say map.filter is probably a bigger code smell than referentially transparent mutation.

Re: Swift and the Legacy of Functional Programming

#105

Earlier quoted context omitted.

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

> The first is less likely to require debugging in the first place. I'm all for functional languages but this scares me a bit. What do you do when you need to debug something and everything ends up being harder to debug but "less likely to need debugging." I've actually run into this situation a number of times and faced with a sea of linked compound expressions, debugging can be a daunting proposition.

Comment out all but the first and output the result. If it's what you expected, uncomment next line and output the result. Is it what you expected. Repeat... Debugging is just a specialized form of troubleshooting so the same rules apply. Bring the system to a known good, and increase until you find where it breaks.

Re: Swift and the Legacy of Functional Programming

#106

Earlier quoted context omitted.

> The first is less likely to require debugging in the first place. I'm all for functional languages but this scares me a bit. What do you do when you need to debug something and everything ends up being harder to debug but "less likely to need debugging." I've actually run into this situation a number of times and faced with a sea of linked compound expressions, debugging can be a daunting proposition.

Comment out all but the first and output the result. If it's what you expected, uncomment next line and output the result. Is it what you expected. Repeat... Debugging is just a specialized form of troubleshooting so the same rules apply. Bring the system to a known good, and increase until you find where it breaks.

You have a ton of these types of statements. Which one do you apply the treatment too? Your approach only allows doing this troubleshooting approach to a single place at a time easily.

Re: Swift and the Legacy of Functional Programming

#107

Earlier quoted context omitted.

I can't help thinking like a microprocessor. Even when mixing functional style into my code, my brain tries to get a grasp on how it's going to be processed. So the first example looks to me like two loops (hopefully the compiler does a better job on that), while the second one is obviously one loop. I am already trying to guess how a compiler would parallelize that...

The second one might be obviously a single loop, but it is not obvious that it is O(n) and depends on the implementation of "append". It also tells you nothing about how the memory gets accessed and I'm assuming here that the for loop works just as well over linked lists and over arrays. In other words your knowledge that this is a single loop can be very misleading when thinking of expected performance. Which is fin…

Excuse my ignorance but why is the first one parallelizable but not the second?

Re: Swift and the Legacy of Functional Programming

#108

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 much easier. It might be a question of getting used to it.

Re: Swift and the Legacy of Functional Programming

#109
post #104

Earlier quoted context omitted.

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).

hmm, i don't think lazyness is enough. for example, in the billion names example, let's say the first and last elements are valid. at the first step you'll wind up with something like validPerson : thunk after n steps where n validPerson : invalid : invalid : invalid : ... : thunk then finally at n = 1e9 validPerson : invalid : ... : validPerson because the intermediate calculations still need to happen. getting that…

I understood from your comment that the imperative version "avoids building the name size list of persons" that you thought the declaritive version would construct an intermediate List[Person] the same size as the source list of names. Most modern languages (e.g. C#, F#, Clojure, Rust) implement map and filter using lazy sequences rather than eagerly constructing intermediate collections (admittedly I don't have much experience with the languages you list).

The use laziness will avoid the need to construct a large intermediate collection of Person instances. Obviously you'll need to iterate the entire source collection to find all valid Persons but the same applies to the imperative version. The lazy version composes better however and avoids extra work if some downstream caller only requires the first n valid Persons.

Re: Swift and the Legacy of Functional Programming

#110
post #85

Earlier quoted context omitted.

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.

And the source code comments are the why.
Post reply on HN