← Back to context

Comment by dmurray

5 years ago

> "with the level of abstraction in each function descending as we read on, each function calling out to others further down." ...

> Many functional languages actually enforce this.

Don't they enforce the opposite? In ML languages (I don't know F# but I thought it was an ML dialect), you can generally only call functions that were defined previously.

Of course, having a clear hierarchy is nice whether it goes from most to least abstract, or the other way around, but I think Martin is recommending the opposite from what you are used to.

Hmm, perhaps I am misreading this? Your understanding of ML languages is correct. I have always found “Uncle Bob” condescending and obnoxious so I can’t speak to the actual source material.

I am putting more emphasis on the “reading top-to-bottom” aspect and less on the level of abstraction itself (might be why I’m misreading it). My understanding was that Bob sez a function shouldn’t call any “helper” functions until the helpers have been defined - if it did, you wouldn’t be able to “read” it. But with your comment, maybe he meant that you should define your lower-level functions as prototypes, implement the higher-level functions completely, then fill in the details for the lower functions at the bottom. Which is situationally useful but yeah, overkill as a hard rule.

In ML and F# you can certainly call interfaces before providing an implementation, as long as you define the interface first. Whereas in C# you can define the interface last and call it all you want beforehand. This is what I find confusing, to the point of being bad practice in most cases.

So even if I misread specifically what (the post said) Bob was saying, I think the overall idea is what Bob had in mind.

  • It seems your idea is precisely the opposite of Robert Martin's. What he is advocating for is starting out the file with the high-level abstractions first, without any of the messy details. So, at the top level you'd have a function that says `DoTheThing() { doFirstPart(); doSecondPart();}`,then reading along you'd find out what doFirstPart() and doSecondPart() mean (note that I've used imperative style names, but that was a random choice on my part).

    Personally I prefer this style, even though I dislike many other ideas in Clean Code.

    The requirement to define some function name before you can call it is specific to a few languages, typically older ones. I don't think it's very relevant in this context, and there are usually ways around it (such as putting all declarations in header files in C and C++, so that the actual source file technically begins with the declarations from the compiler's perspective, but doesn't actually from a programmer perspective (it just begins with #include "header").

  • > I am putting more emphasis on the “reading top-to-bottom” aspect and less on the level of abstraction itself (might be why I’m misreading it). My understanding was that Bob sez a function shouldn’t call any “helper” functions until the helpers have been defined - if it did, you wouldn’t be able to “read” it. But with your comment, maybe he meant that you should define your lower-level functions as prototypes, implement the higher-level functions completely, then fill in the details for the lower functions at the bottom.

    Neither really, he's saying the higher-level abstractions go at the top of the file, meaning the helpers go at the bottom and get used before they're defined. No mention of prototypes that I remember.

    Personally, I've never liked that advice either - I always put the helpers at the top to build up the grammar, then the work is actually done at the bottom.

> In ML languages, you can generally only call functions that were defined previously.

Hum... At least not in Haskell.

Starting with the mostly dependent code makes a large difference in readability. It's much better to open your file and see what are the overall functions. The alternative is browsing to find it, even when it's on the bottom. Since you read functions from the top to the bottom, locating the bottom of the function isn't much of a help to read it.

1 - The dependency order does not imply on any ordering in abstraction. Both can change in opposite directions just as well as on the same.

ML languages are not functional ("functional" in the original sense of the word - pure functional). They are impure and thus don't enforce it.

  • As someone who almost exclusively uses functional languages: don’t do this. This kind of pedantic gatekeeping is not only obnoxious... it’s totally inaccurate! Which makes it 100x as obnoxious.

    “Functional” means “functions are first-class citizens in the language” and typically mean a lot of core language features designed around easily creasing android manipulating functions as ordinary objects (so C#, C++, and Python don’t really count, even with more recent bells-and-whistles). Typically there is a strong emphasis on recursive definitions. But trying to pretend “functional programming languages” are anything particularly specific is just a recipe for dumb arguments. And of course, outside of the entry point itself, it is quite possible (even desirable) to write side-effect-free idiomatic ISO C.

    The “original” functional language was LISP, which is impure as C and not even statically-typed - and for a long time certain Lisp/Scheme folks would gatekeep about how a language wasn’t a Real Functional Language if it wasn’t homoiconic and didn’t have fancy macros. (And what’s with those weird ivory tower Miranda programmers?) In fact, I think the “gate has swung,” so to speak, to the point that people downplay the certain advantages of Lisps over ML/Haskells/etc.

    • > for a long time certain Lisp/Scheme folks would gatekeep about how a language wasn’t a Real Functional Language if it wasn’t homoiconic and didn’t have fancy macros

      This never happened. You are confusing functional with the 2000s "X is a good enough Lisp" controversy, which had nothing to do with functional programming.

      > “Functional” means “functions are first-class citizens in the language”

      No, the word function has a clearly defined meaning. I don't know where you get your strange ideas from - you need to look at original sources. The word "functional" did not become part of the jargon until the 1990s. Even into the 1980s most people referred to this paradigm as "applicative" (as in procedure application), which is a lot more appropriate. The big problem with the Lisp community is that early on, when everyone used the words "procedures" or "subroutines," they decided to start calling them "functions," even though they could have side effects. This is probably the reason why people started trying to appropriate "functional" as an adjective from the ML and Haskell communities into their own languages. A lot of people assume that if you can write a for loop as a map, it makes it "functional." What you end up with is a bunch of inappropriate cargo-culting by people who do not understand the basics of functional programming.

    • Term "functional" has been watered down and has different meanings now. However, the original meaning comes from mathematics and is still in use. You might not like it, but that's how it is - hence why I deliberately disambiguated it in my response to make it clear.

      This has also nothing to do with gatekeeping.

      If you disagree with me, maybe you should go to Wikipedia first and change it there, because by what you say, Wikipedia does it wrong too.

      > In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.

      https://en.wikipedia.org/wiki/Functional_programming

      And functions links to:

      > The subroutine may return a computed value to its caller (its return value), or provide various result values or output parameters. Indeed, a common use of subroutines is to implement mathematical functions, in which the purpose of the subroutine is purely to compute one or more results whose values are entirely determined by the arguments passed to the subroutine. (Examples might include computing the logarithm of a number or the determinant of a matrix.) This type is called a function.

      > In programming languages such as C, C++, and C#, subroutines may also simply be called functions (not to be confused with mathematical functions or functional programming, which are different concepts).

      https://en.wikipedia.org/wiki/Subroutine