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
orcommon
are visible outside that scope, as if they were declared in the parent scope. - And variables declared as
common
cannot directly referenceprivate
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.