I’m looking for a way that could help me enforce a consistent order of declaration in my own code. I’ve happened to develop a strong taste for the style “declare before use”, I guess my brain works like a sinle-pass compiler
I understand the order of declarations is irrelevant in Haskell due to equational reasoning. But I don’t like to (unecessarily) jump around when reading source code. And overall, I find I make less of a mess when I conform to this kind of constraint.
For reference, this restriction is enforced by the OCaml (and F#) compiler, and can be applied to a TypeScript code base via this eslint rule.
I’d be interested to hear about possible solutions. If that could help, I’m not looking for something 100% strict btw, I’m fine mixing and matching let clauses with where clauses for instance, at the function scope.
Yes I understand you don’t think this is a problem. It is indeed a matter of personal preference.
I’ve explored many languages, having that constraint or not, before realizing this is the style that fits my brain better.
I showed a simple example just to illustrate but I do talk in more general terms, i.e. I also want my types to be declared in a predictable fashion., etc.
But an 80/20 solution would be just fine, I just want a bit of consistency.
From my point of view it’s not nightmarish at all, quite the contrary (with proper tooling)
Well, in the specific example you show of HTML generation (using the blaze library perhaps), the generation type is also a monad so within a Html context the order of declarations does indeed matter…
I would have a harder time imagining what declaring types in a certain order (within a single module) would achieve. On a project level the declaration order is induced by the module import graph of course.
Hi benjamin, Haskell is used heavily (AMOT) for writing compilers for recursively-defined languages – that have been the norm since, aww, Algol 1958, and including all modern languages.
‘Recursively-defined’ means that a block includes both declarations and expressions; and the expressions might include local declarations (let or where in Haskell) – which is a block; and those local declarations might include expressions; and those expressions might include a block; … ad infinitum.
We also get recursive data structures to represent (say) organic chemistry molecules. They’re (recursive structures) everywhere when you start looking for them.
How is your code going to process these structures? Deal with declarations first? but declarations might include expressions? Deal with expressions first? But expressions might include declarations?
So as others have pointed out, you’re making a rod for your own back, which’ll only annoy you with more complex applications.
(Spoken by an ex-COBOL/ex-Fortran programmer who’d studied just enough Algol at college to SCREAM at times FORWARD PROCEDURE JUSTGOFINDITFORYOURSELF.)
I understand why you would prefer that approach. The best way to approach this would be via a compiler plugin or, failing that, external tooling.
But I don’t think anyone will have written such a plugin before now, because thinking about programs in a top-to-bottom way is extremely unidiomatic in Haskell (not to mention it forbids certain common kinds of function definitions, such as those with mutual recursion defined at the top level)
For the sake of satisfying your curiosity, if you really want to enforce some line L such that only things before line L can be used in things after line L and not the other way round, it can be done with some really awful hacks. For example, if using Template Haskell you could add a meaningless splice, which enforces such a line as a side-effect of how it works:
Ignoring the special case of mutual recursion do you mean that it is more common to define your functions in “reverse order”? I wouldn’t mind, I just don’t want a mix of both styles, I think it’s messy. If a tool could help me with that I’d use it.
Regarding mutual recursion, if we consider this Haskell code:
even :: Int -> Bool
even 0 = True
even n = odd (n - 1)
odd :: Int -> Bool
odd 0 = False
odd n = even (n - 1)
OCaml can express the same thing, but forces you to declare both functions as a single unit, with the and keyword.
let rec even = function
| 0 -> true
| n -> odd (n - 1)
and odd = function
| 0 -> false
| n -> even (n - 1)
So I just want to note that it is possible to enforce an order of declarations and allow expressing mutual recursion.
Thanks, I take a mental note. I’ll first explore Haskell more fully as-is, maybe I’ll gain new insights as time passes.
Thanks for the template Haskell suggestion @maxigit, I may use this trick here and there to isolate common functions (as a step prior to creating a module)