Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

You can still refactor the original function:

    func GetRenamedJsonFiles(path string) []File {
        files := GetFiles(path)
        jsonFiles := KeepJson(files)            // or `Filter(files, IsJson)`
        renamedFiles := RenameFiles(jsonFiles)  // or `Map(jsonFiles, RenameFile)`
        return renamedFiles
    }
In fact, a long chain of function calls is often hard to read and has to be splitted into several parts anyway. I can even claim that this "old" style forces you to name the outcome of each step. Also it is unclear whether `GetFiles` returns a lazy iterator or a plain slice from its name (I guess it's lazy, but only because you have said `Collect()` there).

It is not even like that "map" and "filter" don't have their places in this style. In fact function chaining is just a concise way to rephrase that! You can write in a functional style without having any function chaining, because the style is all about immutability and resulting composability. Mutability tends to not mix together---any such combination results in something more complex. As long as that can be eliminated, anything would work.



One potential downside I see with this approach is that it forces you to store and name intermediate results which may or may not be meaningful on their own.

Consider a slightly more complicated example that filters on multiple conditions, say file type, size, and last modified date. These filters could be applied sequentially, leading to names like jsonFiles, then bigJsonFiles, then recentlyModifiedBigJsonFiles. Or alternatively, names which drop that cumulative context.

Of course that can be extracted out into a standalone function that filters on all criteria at once or we can use a combinator to combine them or apply any number of other changes, but generally naming intermediate states can be challenging.


Your presented alternative is basically the same thing with named step outputs. I don’t see how chaining is something crazy or problematic in comparison. The only difference is chaining reduces syntactic “noise”. It’s easier to read.

One-time use variables that are consumed on the next line aren’t valuable. Chaining exists as a widely used pattern because it’s useful to not generate a ton of intermediary variables.

In fact, the typing point you bring up is actually less clear. In the iterator chain, the type is “iterator”, which is a standard type that can be produced from many sources as business requirements change. In your example, the types need be compared to the function parameters and methods need to be written explicitly use the same types.


> I can even claim that this "old" style forces you to name the outcome of each step.

Yeah, but that does not necessarily make something easier to understand. Which function do you think is easier to understand?

func quadraticFormula(a int, b int, c int) {

  return (-b+sqrt(b*b-4*a*c))/2*a;
}

Or naming intermediate steps (since we only get to apply one function at a time, and math operators are just functions, why should they be special)?

func quadraticFormula(a int, b int, c int) {

  minus_b := -b

  squared_b := b*b

  four_times_a := 4*a

  four_times_a_times_c := four_times_a*c

  inside_of_sqrt := squared_b - four_times_a_times_c

  sqrt_result := sqrt(inside_of_sqrt)

  numerator := minus_b + sqrt_result

  denominator := 2*a

  return numerator/denominator;
}

Sometimes naming intermediate steps makes code easier to read. Sometimes it doens't. It depends a lot if the intermediate step has a semantic meaning or not, if you want the reader to pause and do a mental checkpoint or if it makes more sense to keep going. Kind of like when you are writing English, and you decide to stop a sentence, or keep going, you don't want your sentences to be too long, but also not too large. That's why good code should follow guidelines, but not hard rules. The programmer should write it in a way that is easier for the reader to understand, and that depends on the context of what is being done.


Since you asked, I find the terse version to be completely unreadable*. If I wanted to understand it, I’d end up writing out all the steps for myself anyway.

There’s an asterisk here because you cherrypicked a function that is already widely known and understood. All we really need to understand that function is its name. If you chose complex niche business logic, or The Wave Function, it would have made for a more fair and instructive example.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: