Hacker News new | past | comments | ask | show | jobs | submit login

What?

Let's say E[h] is "if h == 1 then 2 else 3", a is 1, and b is 2. Then before:

if (if x then 1 else 2) == 1 then 2 else 3

After:

if x then (if 1 == 1 then 2 else 3) else (if 2 == 1 then 2 else 3)

which trivially simplifies to if x then 2 else 3

Your proposed rewrite is "let a0 = if x then 1 else 2 in if a0 == 1 then 2 else 3" which is not a simplification at all - it makes the expression longer and actually introduces a variable indirection, making the expression harder to analyze. The call will require some sort of non-local analysis to pierce through the variable and do the duplication and elimination.




The ANF form is amenable to rewriting the “if” outside the “let”.

    if x then
      let a0 = a in E a0
    else
      let a0 = b in E a0
If a and b are simple variables or constants the new “let”s will be eliminated by ANF.

What happens to E isn’t so straightforward. In your example, though, it’s a small expression tree and would just be duplicated inline with the same obvious constant reductions available.

If it’s ‘large’ (heuristically) it might instead be assigned to a “let” binding outside the “if”, to avoid duplicating a large block of code.

CPS should be making that same determination on E.


Well, with CPS it just works. You start with

  let
    x k = if x then k 1 else k 2
    E k h = if h == 1 then k 2 else k 3
  in
    \k -> x (E k)
and then when you inline E into x it becomes

  let
    E k h = if h == 1 then k 2 else k 3
  in
    \k -> if x then E k 1 else E k 2
which can again be locally simplified to

  \k -> if x then k 2 else k 3
Unlike ANF, no blind duplication is needed, it is obvious from inspection that E can be reduced. That's why ANF doesn't handle this transformation well - determining which expressions can be duplicated is not straightforward.


It looks like the compiler would need to determine it can inline E twice before it can perform a constant case fold on the "if h == 1" expression, right? It's 'obvious' if you understand what E does, but that's not a program transform. Unless I'm very mistaken, this is two steps: inline E twice, then constant case fold (or constant "if" fold, if you aren't translating all the "if"s to "case"s).

If E were a different expression, perhaps "E h = let F i = if z == i + h then 7 else 8 in if y then F 1 else F 2", then the inlining choice (made twice, here) would be very poor - F would be inlined four times all up, and since "z" is a free variable here there's no constant case/if to fold, and everything would wind up worse.

The ANF transforms should look something like:

    let
      a0 = if x then 1 else 2
      E h = if h == 1 then 2 else 3
    in
      E a0
Departing from my previous analysis, I'd say that the next obvious step is the inlining of the now-only-called-once E:

    let
      a0 = if x then 1 else 2
    in
      if a0 == 1 then 2 else 3
But (either here or earler) that's not ANF, the "a0 == 1" isn't a simple constant or variable:

    let
      a0 = if x then 1 else 2
      a1 = a0 == 1
    in
      if a1 then 2 else 3
Let's take the time to rewrite all those conditionals and such as case statements:

    let
      a0 = case x of { True -> 1; False -> 2 }
      a1 = case a0 of { 1 -> True; _ -> False }
    in
      case a1 of { True -> 2; False -> 3 }
Now we can inline a0 into a1, since it's only called once:

    let
      a1 = case (case x of { True -> 1; False -> 2 }) of
               { 1 -> True; _ -> False }
    in
      case a1 of { True -> 2; False -> 3 }
A case-of-case transform applies to a1:

    let
      a1 = case x of
             True -> case 1 of { 1 -> True ; _ -> False }
             False -> case 2 of { 1 -> True ; _ -> False }
    in
      case a1 of { True -> 2; False -> 3 }
Two case of constant transforms on a1 apply:

    let
      a1 = case x of { True -> True ; False -> False }
    in
      case a1 of { True -> 2; False -> 3 }
a1 can be inlined and case-of-case once again applied:

    case x of
        True -> case True of { True -> 2; False -> 3 }
        False -> case False of { True -> 2; False -> 3}
Case-of-constant again leaves us with just:

    case x of { True -> 2; False -> 3 }
And that's our desired outcome. We took some risks doing case-of-case because of the duplicated outer case, but that can be dealt with using join points for the expressions that get duplicated; a bit more long winded but the same end result here due to the case-of-constant transform eliminating the duplicate immediately each time, followed by an inlining of an expression evaluated only once.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: