Enforcing the order of declarations

Hello!

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 :slight_smile:

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.

So to sum up, to me, this is okay:

-- 3
wrapHtml content =
  "<html><body>" <> content <> "</body></html>"

-- 2
myHtml = 
  wrapHtml "Hello, world"

-- 1
main =
  putStrLn myHtml

This is okay:

-- 1
main =
  putStrLn myHtml

-- 2
myHtml = 
  wrapHtml "Hello, world"

-- 3
wrapHtml content =
  "<html><body>" <> content <> "</body></html>"

This is not okay:

-- 2
myHtml = 
  wrapHtml "Hello, world"

-- 3
wrapHtml content =
  "<html><body>" <> content <> "</body></html>"

-- 1
main =
  putStrLn myHtml

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.

4 Likes

I understand where you are coming from but I think you trying to solve a problem which doesn’t exist. My advice will be instead of trying to solve this problem try to convince yourself that it is not a problem.
Having said that, one way to keep stuff together is to use local bindings. In your case you could declare wrapHtml as a “child” of myHtml. If you are telling me that wrapHtml is used by something else (myHtml2) and you can’t define it as a local binding then you’ll have a problem : where would you put myHtml2 (especially if myHtml2 use also a wrapHtml2). Keeping things in order just becomes a (unnecessary) nightmare, so just don’t try.

1 Like

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)

1 Like

What I am saying is that it is only a problem if you want it to be problem : it is your decision.
So you have two options : either fix the problem (but my example shows that it becomes quickly impossible) or train yourself to not see it as a problem.

If you are happy with a 80/20 solution why not downgrade it to 60/40 and so on …

I also gave you an answer which was to use nested function as in

myHtml = 
  wrapHtml "Hello, world"
  where
      wrapHtml content = "<html><body>" <> content <> "</body></html>"

If you really want to force the order of declaration in your code you could insert $(return []) in you code (and use Template Haskell).

For example

f = g
$(return [])
g = f

Won’t compile but

f = g
g = f

will

2 Likes

Thank you :slight_smile:

I appreciate it but I think you didn’t understand my request, that’s okay.

I think this could possibly be implemented in hlint or stan, but I don’t know how much work that would be.

2 Likes

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.

1 Like

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

3 Likes

I would just note that you can enforce ordering with a sequence of let clauses as well, should you so desire.

let a = ...
in let b = ...
in let c = ...

alternately:

runIdentity $ do
  let a = ...
  let b = ...
  let c = ...
  return ...
1 Like

That was my first thought too, but I’m pretty sure OP wants this at the top level.

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:

image

This is not recommended for about 50 different reasons.

2 Likes

Dependency Analysis of Haskell Declarations. Related video about the work GHC performs to allow declarations to appear in any order.

Thanks, I’ll keep that in mind.

Indeed :slight_smile:

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!

As I already said you can use Template Haskell to stop mutual recursive declaration. Every time you insert a TH splice, the compiler stop and compile it without having access to the code below. Meaning it forces you to write in the “correct” order (because each sections of code can only see sections aboves). If you prefer each “section” (between two Template Haskell splices) acts an OCaml let-recursive block.
If you just instert empty TH splices after every functions, this should donthe trick.

I would say it’s idiomatic to accept that definitions can occur in any order, and I think that’s what @dixonary meant too.

3 Likes

I agree with @jaror; re: hlint; looks like you could start glancing around here - https://github.com/ndmitchell/hlint/blob/e760b31702a660d6938bddbc573105d42a0280ed/src/GHC/Util.hs#L87

But tracking all the dependencies might get complex; but it could be just a fun exercise in understanding how to hack on hlint anyway.

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)