Macros

What are Macros?

Macros are words that build other words. The advantages of using macros are:
  • Building new control words
  • Avoiding repetitive blocks of code
  • Making code more efficient
  • Help build domain-specific languages
  • Besides the 
    CREATE
    and
    DOES>
    words that are often indispensable in macros, Smojo extends macro writing with the notion of nestable quotations, lexical closures, lambdas and references, which we will see it later.

    Read Time, Run Time and Compile Time

  • Read Time refers to the time when the textual source code for that word is read in, parsed and converted into executable code (a set of instructions that can be run in the computer). Read Time occurs only once.
  • Run Time refers to the time when the executable code for that word is run or executed. Since a word could be run multiple times, Run Time could occur more than once.
  • Compile Time refers to any time when executable code is created. This could occur towards the end of Read Time or at Run Time.
  • For closures, Smojo executes “immediate” words during Read Time only, except for 
    [
    and
    ]
    (CLOSE/OPEN BRACKET).
    [ ]
    themselves are run only during the Compile Time for that closure.

    Example 1
    	: say-hi "Hello!" . cr ; immediate
    	
    	: test
    		\ outer closure	
    		[: 	
    			
    			\ fired immediately at TEST's Read Time				
    			say-hi 	
    			
    			\ fired at the outer closure's Compile time (TEST's Run Time)			
    			[ "How are you?" . cr ] 	
    			
    			\ inner closure
    			[: 
    				
    				\ fired immediately at TEST's Read Time				
    		     		say-hi 
    				
    				\ fired at the inner closure's Compile time (outer closure's Run Time)		
    		     		[ "I am fine" . cr ] 	
    			;]
    	    	;]
    	;
    	
    Example 2

    NIF,
    compiles the appropriate XT ('p, 'z or 'n) into the enclosing closure depending on the value on the stack:

    	: nif, { 'p 'z 'n } 
    	    dup 0 > -> drop 'p 	|
    	        0 < -> 'n	|
    	    otherwise  'z	|.
    	    compile, ;
    	
    	: too-hot "too hot!" . ;
    	: too-cold "too cold!" . ;
    	: just-nice "just nice ..." . ;
    	
    	: goldilocks ( tempC -- xt )
    	    26 - { t }
    	    [: 					
    		[ t 	
    		     ['] too-hot 	
    		     ['] just-nice
    		     ['] too-cold
    			    nif, ] 	
    	    ;]
    	;
    	
    	26 goldilocks decompile
    	[0] JUST-NICE
    	ok
    	

    Remember that immediate words will run immediately within a closure definition regardless of nesting level, except for compilation state-change words 
    [
    and
    ]
    , which will run only when their enclosing context is being compiled. This is the reason to use
    [']
    within
    [
    ...
    ]
    in the example above – we continue to be in compile state even after
    [
    is encountered because it is within a quotation, and
    [']
    is therefore the right tick to use.
    [']
    is immediate, and will execute during the compilation of
    GOLDILOCKS
    .

    Macro Writing

    Nestable Quotations

    Nestable quotations are the words 
    [:
    and
    ;]
    .
    	: hw [: "Hello World" . cr ;] ;
    	
    	hw execute
    	Hello World
    	ok
    	

    Lexical Closures

    Lexical closures are closures in which specific variable bindings apply throughout that closure and quotations nested within it.
    	: inc { x } [: x 1 + . ;] ;
    	
    	41 inc decompile
    	[0] Literal<41>
    	[1] 1+
    	[2] .
    	ok
    	

    x is inclined into the closure when 
    INC
    is run. The decompilation shows that the lexical variable "x" has been replaced with the value 41 at run time.

    Lambdas

    In the previous example, a lexical closure permanently binds a given name to a value. It is also possible to bind variables on the fly. Also, whenever a lexical variable name clashes with that of a local one, the lexical variable wins.

    	: twox { x } [: { x } x . ;] ;
    	
    	32 twox decompile
    	[0] write local: >x<
    	[1] Literal<32>
    	[2] .
    	ok
    	

    The binding of the outer lexical variable x = 32 is always used first when the closure is generated.

    References

    Lexical variables are frozen during compilation of the closure and local variables are always reset across multiple invocations of a closure. So to save a state, we use references. One way to create references is as shown below.

    	: ref ( x -- r ) [: [ , ] ;] ;
    	
    	: elephant
    		ref { x } [: x @ 1+ dup x ! “Number is now:” . . cr ;] ;
    	
    	41 elephant 
    	dup execute
    	Number is now: 42
    	ok
    	dup execute
    	Number is now: 43
    	ok
    	

    Every time 
    ELEPHANT
    is executed, the value in the internal state is increased by one.

    Quiz

    Question 1

    Try out Example 1. What should you do to have "I am fine" printed out?



    Question 2

    For Example 2, why is 
    COMPILE,
    used in
    NIF,
    ? Will the program still work if
    COMPILE,
    is removed?



    Question 3

    For Example 2, what will be printed out if we run the following? Why?

    	26 goldilocks .
    	


    Question 4

    Are there other ways to create references? If yes, how?



    Question 5

    How do we reset the reference for 
    ELEPHANT
    in the example above?



    Question 6

    Instead of using reference, could we declare a variable as shwon below for the above word 
    ELEPHANT
    instead? What is the difference?

    	41 variable count
    	
    	: elephant
    		[: count @ 1+ dup count ! "Number is now:" . .  cr ;] ;	
    	
    	elephant 
    	dup execute
    	Number is now: 42
    	ok
    	dup execute
    	Number is now: 43
    	ok
    	


    Next: Dictionary