Modules

Table of contents

  1. Private variables
  2. Splitting a program into modules
  3. Dynamic loading

Private variables

To introduce private variables, one puts them into a begin-block. Only variables declared to be public are visible outside of such a block.

begin
   public sind, cosd

   use math: pi, sin, cos
   deg = pi/180

   function sind(x)
     return sin(x*deg)
   end
   function cosd(x)
     return cos(x*deg)
   end
end

print(sind(90))

Only sind and cosd are visible outside of begin ... end. The local variables deg, pi, sin, cos are invisible. It's actually a perfect black box. There is no way to determine from outside, whether the local variables existed or not.

It is also possible to nest such begin-blocks. To understand the semantics, it should be said that public is only a synonym for global and begin ... end is only a syntactic sugar for (fn|| ... end)().

Thus, the last example is identical to:

(fn||
   global sind, cosd

   use math: pi, sin, cos
   deg = pi/180

   function sind(x)
      return sin(x*deg)
   end
   function cosd(x)
      return cos(x*deg)
   end
end)()

print(sind(90))

Note that public makes a variable completely public, i.e. completely global. For intermediate visibility, a local variable may be returned from a block:

sind = begin
   deg = pi/180
   return |x| sin(x*deg)
end

For more than one variable this looks a little bit clumsy:

{sind,cosd} = begin
   deg = pi/180
   return {
      sind = |x| sin(x*deg),
      cosd = |x| cos(x*deg)
   }
end

Splitting a program into modules

Two or more programs might use the same functionality. In this case, if possible, this functionality should be moved into a separate module which can be reused. As an example, let tools be a module and let us put a function mean (average, arithmetic mean) in it.

# File "tools.moss"
# =================

function mean(a)
   return a.sum()/len(a)
end


# Main file "m.moss"
# ==================

use tools: mean

a = [1,2,3,4]
print(mean(a))

Dynamic loading

Sometimes, specific functionality is only needed irregularly. In certain cases it could be favourable to not load it until it is needed.

In Moss, loading modules is naturally dynamic. For example, we want to state a function that transforms a color name into its RGB value. Only if the name exceeds a certain range, the color will be taken from a larger table that is not loaded until needed.

# File "extended_colors.moss"
# ===========================

# This could be a very large table:
extended_color_table = {
   "magenta": [0.5,0,0.2],
   "brown": [0.4,0.2,0]
}

print("(Extended colors loaded)")


# Main file "m.moss"
# ==================

color_table = {
   "black": [0,0,0],
   "white": [1,1,1],
   "gray": [0.4,0.4,0.4],
   "red": [0.5,0.0,0],
   "green": [0,0.4,0],
   "blue": [0,0,0.8]
}
state = table{extended=false}
ColorException = table{}

function color(name)
   while true
      if name in color_table
         return color_table[name]
      else
         if state.extended
            raise table ColorException{
               value = "Unknown color: "+name}
         else
            use extended_colors: extended_color_table
            color_table.update(extended_color_table)
            state.extended = true
         end
      end
   end
end

while true
   s = input("A color name please: ")
   try
      print(color(s),"\n")
   catch e if e: ColorException
      print(e.value,"\n")
   end
end

Crucial is the line:

use extended_colors: extended_color_table

This line is executed at runtime, like an ordinary statement. In fact it is equivalent to:

extended_colors = load("extended_colors")
extended_color_table = extended_colors.extended_color_table

Please take in mind that any module may be loaded dynamically, not only static data.