RFCP logo

REBOL for COBOL programmers

Defactoring dense code

August 30, 2017

In programming, the term "refactoring" refers to re-working code to make
it better without changing what it does. That could mean making it
better-looking, more compact, easier to maintain, and so on.

This document is an exercise in "defactoring," which is nothing but my
personal attempt to make some compact code worse. Sometimes really
nice tight code is a bit of a visual shock to the uninitiated, and the
first reaction can be something like, "Where to I start." This attempts
to show you what you are looking at when you see real REBOL code.

This exercise was produced by starting with a simple example from
Nick Antonaccio's personal programming web site and mangling it up
a bit to show some parts of its structure. This is NOT taking his
example and making it better. It is taking his example and making
it worse, which could actually show you why denser is better.

Contents:

1. Target audience and references
2. Defactoring example
2.1 Starting point
2.2 Touch up the indenting
2.3 Break up some of the multi-function lines
2.4 Make more descriptive words
2.5 Contemplate the result
2.6 See if comments help or hurt

1. Target audience and references

The target audience for this document would be a be a beginnner who is having a bit of trouble learning REBOL from the examples available on the internet.

References:

http://personal-programming.com/personal-programming.html

REBOL style guide

http://withoutwritingcode.com/without-writing-code.html

http://business-programming.com/business_programming.html

2. Defactoring example

First note that "defactoring" is not any sort of official programming activity. I made up the word. Also, the activity I am about to show, of making code "worse" to better understand it, probably is not something that is regularly done. So use the idea presented here it you find it helpful, otherwise ignore it.

2.1 Starting point

The starting point it a sample program that generates a calendar for a year and displays it in the REBOL console. It looks like this:

REBOL []
do [if "" = y: ask "Year (ENTER for current):^/^/" [prin y: now/year]
foreach m system/locale/months [
  prin rejoin ["^/^/     " m "^/^/ "]
  foreach day system/locale/days [prin join copy/part day 2 " "]
  print ""  f: to-date rejoin ["1-"m"-"y]  loop f/weekday - 1 [prin "   "]
  repeat i 31 [
    if attempt [c: to-date rejoin [i"-"m"-"y]][
      prin join either 1 = length? form i ["  "][" "] i
      if c/weekday = 7 [print ""] 
    ]
  ]
] ask "^/^/Press ENTER to Continue..."]

If you are new to REBOL, this might look a bit hard to follow at first glance. Let's do a few things to it to change its appearance, and then ponder the result.

2.2 Touch up the indenting

First, let's change the indentation to the four spaces suggested in the REBOL style guide. The result is this:

REBOL []
do [if "" = y: ask "Year (ENTER for current):^/^/" [prin y: now/year]
foreach m system/locale/months [
    prin rejoin ["^/^/     " m "^/^/ "]
    foreach day system/locale/days [prin join copy/part day 2 " "]
    print ""  f: to-date rejoin ["1-"m"-"y]  loop f/weekday - 1 [prin "   "]
    repeat i 31 [
        if attempt [c: to-date rejoin [i"-"m"-"y]][
          prin join either 1 = length? form i ["  "][" "] i
          if c/weekday = 7 [print ""] 
        ]
    ]
] ask "^/^/Press ENTER to Continue..."]

Not much change there. Adjusting the indenting is groundwork for what follows.

2.3 Break up some of the multi-function lines

Next we will break to new lines after some of the left brackets, as suggested in the style guide. We won't do it all the time because sometimes that is just too much; sometimes things are clearer if they can be kept to one line. We will do what seems helpful, and get this:

REBOL []
do [
    if "" = y: ask "Year (ENTER for current):^/^/" [
        prin y: now/year
    ]
    foreach m system/locale/months [
        prin rejoin ["^/^/     " m "^/^/ "]
        foreach day system/locale/days [
            prin join copy/part day 2 " "
        ]
        print ""  
        f: to-date rejoin ["1-"m"-"y]  
        loop f/weekday - 1 [
            prin "   "
        ]
        repeat i 31 [
            if attempt [c: to-date rejoin [i"-"m"-"y]][
                prin join either 1 = length? form i ["  "][" "] i
                if c/weekday = 7 [
                    print ""
                ] 
            ]
        ]
    ] 
    ask "^/^/Press ENTER to Continue..."
]

2.4 Make more descriptive words

Now let's change some of the shorter words to something that would be more helpful to someone looking at this cold.

REBOL []
do [
    if "" = year: ask "Year (ENTER for current):^/^/" [
        prin year: now/year
    ]
    foreach month system/locale/months [
        prin rejoin ["^/^/     " month "^/^/ "]
        foreach day system/locale/days [
            prin join copy/part day 2 " "
        ]
        print ""  
        firstofmonth: to-date rejoin ["1-"month"-"year]  
        loop firstofmonth/weekday - 1 [
            prin "   "
        ]
        repeat daycounter 31 [
            if attempt [currentdate: to-date rejoin [daycounter"-"month"-"year]][
                prin join either 1 = length? form daycounter ["  "][" "] daycounter
                if currentdate/weekday = 7 [
                    print ""
                ] 
            ]
        ]
    ] 
    ask "^/^/Press ENTER to Continue..."
]

2.5 Contemplate the result

That's as far as we will go with changing the code. A person used to another programming language could do things to make REBOL look like a more familiar language, but twisting REBOL into being something it is not would negate some of its power. For example, consider this line:

prin join either 1 = length? form daycounter ["  "][" "] daycounter

You could do something like this:

day-as-string: to-string daycounter
day-length: length? day-as-string
either 1 = day-length [
    prin join "  " day-as-string
] [
    prin join " " day-as-string
]

The above form might be more comforting to a COBOL programmer, but consider the downside. In a compiled language, the overhead of another variable is the space to store its value, and references are made by an address fixed at compile time. In an interpreted language, another variable involves some sort of structure to hold its value and other attributes, and a reference at run time involves looking it up in some sort of table, and then taking action based on its attributes. In other words, a lot more overhead.

REBOL's feature of functions passing values to other functions makes it possible for programs to be more efficient in operation and also in use of space, as in more code on fewer lines.

So the questions or ideas to ponder are:

Which way do you like your code? Tight, or verbose. Both work.

Is it easier to understand more verbose code that might be "clearer" but takes more space, or tighter code that might be denser but takes less space? Sometimes something shorter can be digested in one mental "bite" whereas something longer can be less clear just because of its length.

In REBOL, code can be passed around as data. A REBOL script can generate parts of itself and execute them. In such situations, short code is better. The Nick Antonaccio demos have a lot of this.

2.6 See if comments help or hurt

Finally, let's add a bunch of comments and see what that looks like.

REBOL []
do [
;;  -- Ask for the year.  If one is entered, it will show up on the
;;  -- console when it is typed.  If no year was entered and we are
;;  -- printing the current year, then display the current year so
;;  -- that some year shows up on the top of the calendar.
    if "" = year: ask "Year (ENTER for current):^/^/" [
        prin year: now/year
    ]
;;  -- Month names are stored in system/locale/months, and for each
;;  -- on of them we want to print a calendar for the month.
    foreach month system/locale/months [
;;      -- Print some line feeds and the month name
        prin rejoin ["^/^/     " month "^/^/ "]
;;      -- Day names are store in system/locale/days, and for each
;;      -- one of them we want to print the first two characters 
;;      -- followed by a blank, to form the days at the top of the
;;      -- calendar for the month.  The days start with Monday.
;;      -- After we have printed seven day names, we have to print a
;;      -- line feed before we start printing the day numbers.
        foreach day system/locale/days [
            prin join copy/part day 2 " "
        ]
        print ""  
;;      -- Generate a date for the first of the month.
;;      -- This will allow us to get the weekday (a number from
;;      -- 1 to 7) for that day.  We will use that number to do
;;      -- a loop, to print enough blanks so that when we begin
;;      -- printing the day numbers we will be under the correct
;;      -- heading for that day.  Note the subtraction of 1 from
;;      -- the day number.  Monday is day 1, and we will do the
;;      -- loop zero to six times, so if it is Monday we will not
;;      -- print anything at all.
        firstofmonth: to-date rejoin ["1-"month"-"year]  
        loop firstofmonth/weekday - 1 [
            prin "   "
        ]
;;      -- Now we will print the day numbers, staring at 1 and doing
;;      -- a maximum of 31 times.  The "repeat" function automatically
;;      -- starts the counter at 1.  
        repeat daycounter 31 [
;;          -- Use the counter to generate a date for that day.
;;          -- Do this conversion in an "attempt" block so that if we are
;;          -- trying day 31 in a 30-day month the conversion will fail.
            if attempt [currentdate: to-date rejoin [daycounter"-"month"-"year]][
;;              -- Print the loop counter as the day.  Join it with one or
;;              -- two blanks depending on the length of the counter (one
;;              -- or two digits). 
                prin join either 1 = length? form daycounter ["  "][" "] daycounter
;;              -- If we just printed the date of day number 7 (Sunday),
;;              -- print a line feed to go to the next line.
                if currentdate/weekday = 7 [
                    print ""
                ] 
            ]
        ]
    ] 
;;  -- We can't just quit here or the console window will close,
;;  -- so ask for a keystroke which will keep the console open.
    ask "^/^/Press ENTER to Continue..."
]

Now a final question comes to mind. Is this better? In this case, "better" probably means easier to understand. Is it easier to understand a certain amount of code put into a larger file with exhaustive comments, or is it easier to understand the same amount of code without the comments so you can see the whole picture at a glance?

As they like to say in math classes, the answer is left as an exercise.