Live data from Hacker News

What makes code hard to read: Visual patterns of complexity (2023)

seeinglogic.com

201–210 of 383 posts

Re: What makes code hard to read: Visual patterns of complexity (2023)

#201
post #167

Earlier quoted context omitted.

> The functions are self contained in that their dependencies/inputs are the arguments provided or other pure functions and the outputs are entirely in the return type. Is this just a fancy way of saying static functions?

Nope, pure functions are referentially transparent. The key idea is that you can replace the function invocation with a value and it shouldn’t change the program. A regular static function could refer to a file, a database, or it could change some global memory, etc. So, replacing the static function (that causes side-effects) with a pure value wouldn’t result in the same program. Side-effects are usually declarative…

> Nope, pure functions are referentially transparent. The key idea is that you can replace the function invocation with a value and it shouldn’t change the program.

[Edit: This is wrong: And idempotent.] Generally you can expect that you can call them as many times as you like and get the exact same result. It _feels_ very safe.

> This isn't just a Haskell thing though. I'll write code this way in C# (and have built a large pure-FP framework for C# to facilitate this approach [1]).

I think that habit from Haskell is also what allowed me to pick up Rust pretty easily. You don't run afoul of the borrowchecker much if you don't expect to mutate a lot of stuff, and especially at a distance.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#202
post #87
post #58

Shoutout to the pipe operator in R. The code equivalent of "and then." It helps to unnest functions and put each action on one line. I know R is more for stats and data, but I just think it's neat.

For anyone interested in this as design, it’s called method chaining.

I wrote a book dedicated to writing Pandas code in this style, Effective Pandas 2.

I've seen many who complain about this style of coding, but once they try it, they are sold. I love reading reviews about how adopting this made their code easier to write, debug, read, and collaborate.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#203

You really should try to pack an unbroken thought as a single line of code as much as possible. That’s the idea behind chaining multiple functions together on one line instead of spreading it out over several lines. Eyes go horizontally more naturally than up and down, it fits our vision’s natural aspect ratio. And stop making deep nestings. Making a single function call per line assigning output to a variable each t…

I agree with you on piping but writing each method on its own line makes the code very approachable (also easier to work with).

Consider this code (from a course I'm teaching this week):

    (df
      .pipe(lambda df_: print(df_.columns) or df_)
      .groupby('activity_id', observed=True)
      [non_agg_cols]
      .apply(lambda g: g.assign(distance=calculate_distance_np(g)), include_groups=True)
      .pipe(fix_index)
      .pipe(lambda df_: print('DONE!') or df_)
    )
vs:

    (df.pipe(lambda df_: print(df_.columns) or df_).groupby('activity_id', observed=True) [non_agg_cols].apply(lambda g: g.assign(distance=calculate_distance_np(g)), include_groups=True).pipe(fix_index).pipe(lambda df_: print('DONE!') or df_))

Re: What makes code hard to read: Visual patterns of complexity (2023)

#204

Earlier quoted context omitted.

fwiw, once Python's introduced there's the third option on the table, comprehensions, which will also be suggested by linters to avoid lambdas: authors_of_long_books: set[Author] = {book.author for book in books if book.page_count > 1000} These are somewhat contentious as they can get overly complex, but for this case it should be small & clear enough for any Python programmer.

I tried scaling up the original into an intentionally convoluted nonsensical problem to see how a more complicated solution would look like for each approach. Do these look right? And which seems the most readable? # Functional approach var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books .filter(book => book.pageCount > 100 and book.language == "Chinese" and book.subject == "History" and…

Your FP example is needlessly complicated. No one who does FP regularly would write it like that.

  var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books
    .filter(book => 
       book.pageCount > 100 and 
       book.language == "Chinese" and 
       book.subject == "History" and
       book.author.mentions > 10_000
    )
    .flatMap(book => book.author.pets)
    .filter(pet => pet.is_furry)
    .map(pet => pet.favoriteFood)
    .distinct()
Or in Scala:

  val favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = (for {
    book  100 &&
      book.language == "Chinese" &&
      book.subject == "History &&
      book.author.metnions > 10_000
    pet 
Though, most Scala programmers would prefer higher-order functions over for-comprehensions for this.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#205

Earlier quoted context omitted.

That's when you're doing the job, not what the mental representation of the solution. I strongly believe if you ask her to describe the task, she would go: 1. (Take the books)->(that have 200 pages or more)->(and mark down the name of the authors)->(only once)

I respectfully disagree. And I think one of the core reason SWEs struggle with functional-style of programming is that it is neither intuitive nor how general-joe-doe’s brain works.

I haven't really encountered software engineers who really struggle with functional style in almost 20 years of seeing it in mainstream languages. It's just another tool that one has to learn.

Even the people arguing against functional style are able to understand it.

Strangely, this argument is quite similar to arguments I encounter when someone wants to rid the codebase of all SQL and replace it with ORM calls.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#206

Earlier quoted context omitted.

I tried scaling up the original into an intentionally convoluted nonsensical problem to see how a more complicated solution would look like for each approach. Do these look right? And which seems the most readable? # Functional approach var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books .filter(book => book.pageCount > 100 and book.language == "Chinese" and book.subject == "History" and…

I'm more partial to the first one because it keeps a linear flow downwards, and a uniform structure. The second one kind of drifts off, and reshuffling parts of it is going to be … annoying. IME the dot style lends itself much better to restructuring. Depending on language you might also have some `.flat_map` option available to drop the `.reduce`.

[deleted]

Re: What makes code hard to read: Visual patterns of complexity (2023)

#207

Earlier quoted context omitted.

That's not true. The lambdas used in the functional version are each called once for every item in the list.

No sane optimizer is going to emit the functional code as a gajillion function calls.

It's not? How could that possibly work when the lambda could throw and it could throw on the nth invocation and your stack trace has to be correct?

If I run this in the JS console I get two anonymous stack frames. The first being the console itself.

    [1, 2, 3].filter(x => [][0]())

Re: What makes code hard to read: Visual patterns of complexity (2023)

#208
This is an interesting article, but also rather unsatisfying. It very quickly jumps to conclusions and goes right back to opinion. I agree with several of those opinions, but opinion was explicitly not the point of the article.

> Prefer to not use language-specific operators or syntactic sugars, since additional constructs are a tax on the reader.

I don't think this follows from the metric. If a function contains three distinct operators, a language-specific operator that replaces all three of them in one go would reduce the "effort" of function. It's highly scenario-specific.

> Chaining together map/reduce/filter and other functional programming constructs (lambdas, iterators, comprehensions) may be concise, but long/multiple chains hurt readability

I don't think this follows either. One effect of these constructs when used right is that they replace other operators and reduce the "volume". Again this can go both ways.

> ...case in point, these code snippets aren’t actually equivalent!

That's a very language-specific diagnosis, and arguably points at hard-to-read language design in JS. The snippet otherwise doesn't look like JS, but I'm not aware of another language for which this would apply. Indeed it is also commonly known as a "null-safe operator", because most languages don't have separate "null" and "undefined".

> variable shadowing is terrible

> long liveness durations force the reader to do keep more possible variables and variables in their head.

These can arguably be contradictory, and that is why I am a huge fan of variable shadowing in some contexts: By shadowing a variable you remove the previous instance from scope, rather than keeping both available.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#209

Earlier quoted context omitted.

You have literally just described the set of objects asked for: the unique authors of the books with more than 1,000 pages. I don't understand how you expect to get any simpler than that. The functional style isn't even requiring you to describe how to accomplish it, it almost verbatim simply describes the answer you're trying to get. If your entire objection is that you might want intermediate-named variables… you c…

The problem is that it's easy to overdo it. When you are writing the code, you already know what it's supposed to do, and adding a few more things to the chain is convenient and attractive. But when you are reading unfamiliar code, you often wish that the author was more explicit with their code. Not just with what the code is actually doing, but what it's trying to do and what are the key waypoints to get there. Wit…

> The problem is that it's easy to overdo it.

Welcome to all features of every programming language?

Sacrificing readability, optimization, and simplicity for the 95% case because some un-principled developers might overdo it in the 5% case (when the cost of fixing it is trivially just inserting variable assignments) is… not a good trade-off.

Re: What makes code hard to read: Visual patterns of complexity (2023)

#210

Earlier quoted context omitted.

I tried scaling up the original into an intentionally convoluted nonsensical problem to see how a more complicated solution would look like for each approach. Do these look right? And which seems the most readable? # Functional approach var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books .filter(book => book.pageCount > 100 and book.language == "Chinese" and book.subject == "History" and…

I'm more partial to the first one because it keeps a linear flow downwards, and a uniform structure. The second one kind of drifts off, and reshuffling parts of it is going to be … annoying. IME the dot style lends itself much better to restructuring. Depending on language you might also have some `.flat_map` option available to drop the `.reduce`.

True! Good point on the restructuring, I haven't thought about it in that way.

I think I like the second approach because the loop behavior seems clearest, which helps me analyze the time complexity or when I want to skim the code quickly.

A syntax like something below would be perfect for me if it existed:

  var favoriteFoodsOfFurryPetsOfFamousAuthorsOfLongChineseBooksAboutHistory = books[i].author.pets[j].favoriteFood.distinct()
    where i = pagecount > 100,
              language == "Chinese",
              subject == "History",
              author.mentions > 10_000
    where j = is_furry == True
Post reply on HN