Modifiers - Spicery/Nutmeg GitHub Wiki

Introducing Modifiers

In Nutmeg there are three visibility modifiers: private, public and common. These can only be used in certain scopes in Nutmeg:

  • In the top-level declarations of files
  • Class definitions, and
  • Let-expressions

The visibility rules that apply are:

  • Any variable declared as private is entirely local to that scope, as you might expect.
  • Variables declared as public or common are visible outside that scope, as if they were declared in the parent scope.
  • And variables declared as common cannot directly reference private variables. This is a modifier that is unique to Nutmug that is mainly useful when defining classes.

In order to apply one of these modifiers to a variable, simply place it immediately before the variable name or definition.

Examples

For example you could use this to write a definition for height but conceals the internal variables height_in_feet and feet_to_cm. (Note that private is the default inside a let expression.)

let
    height_in_feet := 6
    feet_to_cm := 12 * 2.54
    public height := height_in_feet * feet_to_cm 
end

You can also use this in the top-level of a file to conceal a helper function. This example shows the use of the @private annotation:

def factorial( n ):
    fact( n, 1 )
enddef

private
function fact( n, sofar ):
    if n <= 1:
        sofar
    else:
        fact( n-1, n*sofar )
    endif
endfunction

Nested Scopes

One obvious question arises: what happens when you nest these scopes? For example, suppose you write at the top-level of a file the following:

let
    public factorial := fact(% ?, 1 %)

    function fact( n, sofar ):
        if n <= 1:
            sofar
        else:
            fact( n-1, n*sofar )
        endif
    endfunction
end

Clearly we expect the factorial function to be visible throughout the file. But is it actually exported? This depends on what modifier is applied to the variables of a block. In this case, the factorial variable is given the default of the top-level - namely public - so it is exported!

Overriding the Default

What if we did not want factorial to be exported but only visible in the file? In that case we can override the default by applying a modifier to the whole block. In this case we would write:

private let
    public factorial := fact(% ?, 1 %)

    function fact( n, sofar ):
        if n <= 1:
            sofar
        else:
            fact( n-1, n*sofar )
        endif
    endfunction
end

Using Modifiers in classes

Private and Public

The private and public modifiers are most useful when defining classes. By marking the implementation fields as private it is possible to limit knowledge of the implementation to the class definition. For example, we could implement a circle using the bottom-left and top-right corner of it's bounding box:

class Circle:
    slot bottomLeft;
    slot topRight;
     
    public function ^radius():
        ( topRight.x - bottomLeft.x ) / 2
    end

    public function ^area():
        pi * ^radius**2
    end
endclass

If we decided to change the way the Circle is implemented, perhaps to the more familiar origin and radius, the program outside the class definition would be completely unaffected.

The Common Modifier

Perhaps you will have noticed that in the previous definition of Circle, were we to change the implementation, the definition of radius would need to be changed but the definition of area would continue working. Let's see that in action:

class Circle:
    slot origin;
    public slot radius;
     
    common function ^area():
        pi * ^radius**2
    end
endclass

The reason for this is that the original definition of radius needed access to the implementation variables topRight and bottomLeft. However the area was defined only in terms of the radius, insulating it from the change. Such a definition can be annotated as common rather than public.

Why would we mark a function as common rather than public? The motivation is that programmers are encouraged to keep the number of public methods under control. This is because more public methods you have the bigger the change you will need if/when the implementation changes. However common methods are unaffected by changes in the implementation, so there is no limit on the number of common-methods you give to a class.