[erlang-questions] Re: erlang improvement - objective c (or smalltalk) syntax

Richard O'Keefe ok@REDACTED
Mon Jun 8 02:20:25 CEST 2009


On 5 Jun 2009, at 10:56 am, Steve Davis wrote:

> It would be interesting to see whether 100-200 lines of code  
> transcribed to this style would be more or less confusing than the  
> original.

Let's find out.
I'm going to take 90 raw lines (not SLOC) from ErlyWeb.
I chose this block because there were several functions
with lots of arguments.

digits1(Float, Exp, Frac) ->
     Round = ((Frac band 1) =:= 0),
     case Exp >= 0 of
         true ->
             BExp = 1 bsl Exp,
             case (Frac /= ?BIG_POW) of
                 true ->
                     scale((Frac * BExp * 2), 2, BExp, BExp,
                           Round, Round, Float);
                 false ->
                     scale((Frac * BExp * 4), 4, (BExp * 2), BExp,
                           Round, Round, Float)
             end;
         false ->
             case (Exp == ?MIN_EXP) orelse (Frac /= ?BIG_POW) of
                 true ->
                     scale((Frac * 2), 1 bsl (1 - Exp), 1, 1,
                           Round, Round, Float);
                 false ->
                     scale((Frac * 4), 1 bsl (2 - Exp), 2, 1,
                           Round, Round, Float)
             end
     end.

scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) ->
     Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
     %% Note that the scheme implementation uses a 326 element look-up  
table
     %% for int_pow(10, N) where we do not.
     case Est >= 0 of
         true ->
             fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est,
                   LowOk, HighOk);
         false ->
             Scale = int_pow(10, -Est),
             fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est,
                   LowOk, HighOk)
     end.

fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) ->
     TooLow = case HighOk of
                  true ->
                      (R + MPlus) >= S;
                  false ->
                      (R + MPlus) > S
              end,
     case TooLow of
         true ->
             [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)];
         false ->
             [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk,  
HighOk)]
     end.

generate(R0, S, MPlus, MMinus, LowOk, HighOk) ->
     D = R0 div S,
     R = R0 rem S,
     TC1 = case LowOk of
               true ->
                   R =< MMinus;
               false ->
                   R < MMinus
           end,
     TC2 = case HighOk of
               true ->
                   (R + MPlus) >= S;
               false ->
                   (R + MPlus) > S
           end,
     case TC1 of
         false ->
             case TC2 of
                 false ->
                     [D | generate(R * 10, S, MPlus * 10, MMinus * 10,
                                   LowOk, HighOk)];
                 true ->
                     [D + 1]
             end;
         true ->
             case TC2 of
                 false ->
                     [D];
                 true ->
                     case R * 2 < S of
                         true ->
                             [D];
                         false ->
                             [D + 1]
                     end
             end
     end.

I couldn't find anything else that called scale, fixup, or generate.
It appeared that the LowOk and HighOk arguments are always equal to
the initial Round.  So before I made the change to the new argument
style I thought I'd clean up the code a bit.

I ended up with this, where I may very well have introduced some
mistakes.  However, 90 raw lines (72 SLOC) turned into
50 raw lines (37 SLOC), or about half, before introducing any
split procedure names.

Here is the cleaned up code.

digits1(Float, Exp, Frac) ->
     LSB = Frac band 1,
     if Exp >= 0 ->
         Bexp = 1 bsl Exp,
         if Frac == ?BIG_POW -> M1 = 4, M2 = 2
          ; Frac /= ?BIG_POW -> M1 = 2, M2 = 1
         end,
         scale(Frac * Bexp * M1, M1, Bexp * M2, Bexp, LSB, Float)
      ; Exp < 0 ->
         if Exp == ?MIN_EXP  -> M1 = 2, S2 = 1
          ; Frac /= ?BIG_POW -> M1 = 2, S2 = 1
          ; true             -> M1 = 4, S2 = 2
         end,
         scale(Frac * M1, 1 bsl (S2 - Exp), S2, 1, LSB, Float)
     end.

scale(R, S, MPlus, MMinus, LSB, Float) ->
     Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
     %% Note that the scheme implementation uses a
     %% 326 element look-up table for int_pow(10, N) where we do not.
     if Est >= 0 ->
         P1 = MPlus,   M1 = MMinus,   R1 = R,   S1 = S*int_pow(10, Est)
      ; Est <  0 ->
         X = int_pow(10, -Est),  % scale factor
         P1 = MPlus*X, M1 = MMinus*X, R1 = R*X, S1 = S
     end,
     if R1 + P1 >= S1 + LSB ->
         Digit = Est + 1, R2 = R1,    P2 = P,     M2 = M1
      ; true ->
         Digit = Est,     R2 = R1*10, P2 = P1*10, M2 = M1*10
     end,
     [Digit | generate(R2, S1, P2, M2, LSB)].

generate(R2, S1, P2, M2, LSB) ->
     Dig2 = R2 div S1,
     R3 = R2 rem S1,
     if R3+LSB > M2 ->
         if R3+P2 < S1+LSB ->
             [Dig2 | generate(R3 * 10, S1, P2 * 10, M2 * 10, LSB)]
          ; true ->
             [Dig2 + 1]
         end
       ; true -?
         if R3+P2 < S1+LSB ; R3*2 < S1 ->
             [Dig2]
          ; true ->
             [Dig2+1]
         end
     end.

Now we obviously don't need split procedure names all the time,
only when there are enough arguments to be confusing.  In this
case, the original argument names did not help me very much.
So the pp names in the split procedure names may not be very
convincing.

digits1(Float, Exp, Frac) ->
     LSB = Frac band 1,
     if Exp >= 0 ->
         Bexp = 1 bsl Exp,
         if Frac == ?BIG_POW -> M1 = 4, M2 = 2
          ; Frac /= ?BIG_POW -> M1 = 2, M2 = 1
         end,
         scale(Frac * Bexp * M1) by (M1)
	    plus(Bexp * M2) minus(Bexp) lsb(LSB) float(Float)
      ; Exp < 0 ->
         if Exp == ?MIN_EXP  -> M1 = 2, S2 = 1
          ; Frac /= ?BIG_POW -> M1 = 2, S2 = 1
          ; true             -> M1 = 4, S2 = 2
         end,
         scale(Frac * M1) by(1 bsl (S2 - Exp))
	    plus(S2) minus(1) lsb(LSB) float(Float)
     end.

scale(R0) by(S0) plus(P0) minus(M0) lsb(LSB) float(Float) ->
     Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
     %% Note that the scheme implementation uses a
     %% 326 element look-up table for int_pow(10, N) where we do not.
     if Est >= 0 ->
         P1 = P0,   M1 = M0,   R1 = R0,   S1 = S0*int_pow(10, Est)
      ; Est <  0 ->
         X = int_pow(10, -Est),  % scale factor
         P1 = P0*X, M1 = M0*X, R1 = R0*X, S1 = S0
     end,
     if R1 + P1 >= S1 + LSB ->
         Digit = Est + 1, R2 = R1,    P2 = P,     M2 = M1
      ; true ->
         Digit = Est,     R2 = R1*10, P2 = P1*10, M2 = M1*10
     end,
     [Digit | generate(R2, S1) plus(P2) minus(M2) lsb(LSB)].

generate(R2, S1) plus(P2) minus(M2) lsb(SB) ->
     Dig2 = R2 div S1,
     R3   = R2 rem S1,
     if R3+LSB > M2 ->
         if R3+P2 < S1+LSB ->
             [Dig2 | generate(R3*10, S1) plus(P2*10) minus(M2*10)  
lsb(LSB)]
          ; true ->
             [Dig2 + 1]
         end
       ; true -?
         if R3+P2 < S1+LSB ; R3*2 < S1 ->
             [Dig2]
          ; true ->
             [Dig2+1]
         end
     end.

This is about the best I can do without actually understanding the
code.  The code _did_ get longer: from 50 raw lines to 52, but then
it's still substantially shorter than the original, which suggests
that style issues other than the use or non-use of split function
names may have more influence on code bulk, RSI, &c.

As for the suggestion that an IDE can help you by showing the
argument names or other form of prompt, how can that help you
when you are writing a call to a function that has not been
(re)written yet?  How does the IDE help you when you are reading
a paper (or PDF or HTML) listing?




More information about the erlang-questions mailing list