Self-closure and mutation: Inheritance for Javascript functions, with a stream application

version 2009-06-26 - by Guillaume Lathoud (glathoud _at_ yahoo _dot_ fr)

Cite this as:
"Self-closure and mutation: Inheritance for Javascript functions, with a stream application", by Guillaume Lathoud, version 2009-06-26, http://geocities.com/glathoud/publications/mutate_article/mutate_article.2009-06-26.html

1. Why?

Javascript objects have prototype inheritance. But new can only return an object, not a function. Could Javascript functions inherit from each other as well? I am proposing here a solution, called "mutation", and applying it to numerical streams, whose generic, high-level optimization turned out to be interesting enough to devote two sections to it.

2. Contents

In the "mutation" inheritance framework, a function me is at once copied and changed, using mainly self-closure (Section 6):

    ret = function() { return ( fun || me ).apply( ret, arguments ); };
where ret is defined with a closure on itself, so that the original or overriding implementation (respectively me or fun), can access ret's member fields and methods through this.<member> . Members are copied from me and can be changed.

As an example, Sections 4 and 5 build "abstract" stream functions S, SM, SMC, through successive mutations. One more mutation is used to build "concrete" numerical streams, e.g. the Fibonacci series. The recursive definition of a series is implemented very naturally through the above-described this.<member> access.

Separating abstraction levels - abstract streams versus concrete streams - permits to implement generic optimization algorithms:

Those who get interested may well check articles like "Why Functional Programming Matters" or books like the SICP book. As for Javascript, the hurried ones could have a look at "Functional programming with Dojo - Quick tips", and the less hurried ones could read "Functional fun in JavaScript with Dojo".

Panama sloth

3. A lazy, procrastinating article

The SICP video lectures from Abelson and Sussman propose wishful thinking as a method to progressively break down complexity while implementing.

One starts to write code for what is ultimately wanted, assuming some functions already implemented - although they do not even exist yet. For example:

function wanted( x ) {
    if ( assumed_1( x ) ) {
        return assumed_2( x );
    } else {
        return assumed_3( x );
    }
}
Having written this, the code structure of wanted is clear, as well as what the not-yet-implemented assume_1, assume_2 and assume_3 should do. This makes it easier for the mind to concentrate on only one layer of abstraction at a time (wanted), then move on to the next layer (assume_1, assume_2 and assume_3).

The organisation of present article follows the wishful thinking pattern: The concrete, final stream example - what we want - is first exposed (Section 4), assuming the SMC stream function and the mutate inheritance method to exist. SMC and mutate are described in Sections 5 and 6, respectively.

In addition, Annex A uses SMC to build Pascal and Euler streams of streams, and Annex B contains the full Javascript code.

Notes:

4. Concrete streams: series

The present section details what we expect from a convenient stream function, called SMC, and its underlying inheritance method, called mutate. Throughout the present section, SMC and mutate are simply supposed to exist - they will be described in Sections 5 and 6, respectively. (The article is organized following the wishful thinking pattern described in Section 3.)

Take the Fibonacci series:

1 1 2 3 5 8 13 21 34 55 ...
defined by:Sunflower, by Steven Shogrunden
x_0 = 1
x_1 = 1
x_{n+2} = x_{n+1} + x_{n}
Let us assume a stream function SMC that permits to implement such a recursive definition as is, using the SMC.mutate() method [note 1]:
fibo = SMC.mutate( function next() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ]; // Recursive definition of the next term
} );
function next () { ... } defines how to compute the next value of the fibo stream. Section 5 explains how SMC.mutate transforms next into fibo [note 2].

Repetitively calling fibo() yields the terms of the series:

fibo = SMC.mutate( function next() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ]; // Recursive definition of the next term
} );
a = fibo();   // Does: a = 1
a = fibo();   // Does: a = 1
a = fibo();   // Does: a = 2
a = fibo();   // Does: a = 3
a = fibo();   // Does: a = 5
a = fibo();   // Does: a = 8
a = fibo();   // Does: a = 13
a = fibo();   // Does: a = 21

Mechanical Sunflower

SMC.mutate() builds the fibo Function and adds methods to it, including:

fibo.at( 32 )     // Access the 33th term of the Fibonacci series.

fibo.copy()       // Copy fibo, the copy starts again at term 0.

fibo.tee()        // Copy fibo, the copy starts at the current term.
at, copy and tee do not affect values returned by fibo():
fibo = SMC.mutate( function next() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ]; // Recursive definition of the next term
} );
a = fibo();   // Does: a = 1
a = fibo();   // Does: a = 1
a = fibo();   // Does: a = 2
a = fibo();   // Does: a = 3
a = fibo();   // Does: a = 5
a = fibo();   // Does: a = 8

a = fibo.at( 4 );     // Does: a = 5, this does not affect subsequent calls to fibo()
a = fibo();   // Does: a = 13
a = fibo();   // Does: a = 21

a = fibo.at( 17 );    // Does: a = 2584, this does not affect subsequent calls to fibo()
a = fibo();   // Does: a = 34
a = fibo();   // Does: a = 55

fibo2 = fibo.copy();  // fibo2 starts again at term 0,
                      // this does not affect subsequent calls to fibo()
a = fibo2();  // Does: a = 1
a = fibo2();  // Does: a = 1
a = fibo2();  // Does: a = 2
a = fibo2();  // Does: a = 3

a = fibo();   // Does: a = 89
a = fibo();   // Does: a = 144
a = fibo();   // Does: a = 233
a = fibo();   // Does: a = 377

fibo3 = fibo.tee();   // fibo3 starts at the current term of fibo,
                      // this does not affect subsequent calls to fibo()
                      // this does not affect subsequent calls to fibo2()

a = fibo3();   // Does: a = 610
a = fibo3();   // Does: a = 987
a = fibo3();   // Does: a = 1597

a = fibo2();  // Does: a = 5
a = fibo2();  // Does: a = 8
a = fibo2();  // Does: a = 13

a = fibo();   // Does: a = 610
a = fibo();   // Does: a = 987
a = fibo();   // Does: a = 1597
a = fibo();   // Does: a = 2584
a = fibo();   // Does: a = 4181
Michigan from above, by Alexander CohnObserve that streams (like fibo) are unaffected by calls to their "manipulator" methods (at, copy, tee). Therefore, there is no need to introduce "generators" and "iterators" , and copying the stream is much safer (e.g. as compared to Python itertools.tee [note 3] ).

Section 5 further discusses the above example, explaining the underlying implementations SM and SMC.

5. Abstract streams: SM and SMC

The present section describes first the SM function object, which permits to create streams (S) with memory (M). Then comes the SMC function object, which also permits to copy (C) streams. Refer to Annex B for full implementations.

SM and SMC both rely on their mutate method. As explained in Section 3, I am following the "wishful thinking" pattern: Throughout the present section, mutate is simply supposed to exist. The implementation of mutate will be described in Section 6.

In the following Fibonacci stream implementation:

fibo = SM.mutate( function next() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ]; // Recursive definition of the next term
} );

SM.mutate() creates the methods fibo.n and fibo.previousn, as well as a number of other useful methods for recursive definitions (excerpt from Annex B):

SM = ...
...
at:    function ( /*Integer*/q ) { return ( _mem.length > q ) ? _mem[ q ] : undefined; },
 
first: function () { return this.at( 0 ); },

n:     function () { return _mem.length; },

nextn: /* copy from F.nextn */
       function( /*Integer*/ n ) { var a, ret = []; for ( a = 0; a < n; a++ ) { ret.push( this() ); } return ret; }
                
previous: function () { var r = this.previousn( 1 ); return r ? r[ 0 ] : undefined; },  

previousn: function( /*Integer*/q ) { var a = this.n(); return this.slice( a - q, a ); },

slice: function( /*Integer*/s, /*Integer?*/e ) {
       /* Similar to Array.prototype.slice(),   // Note that e === undefined is allowed
       ...
       }
...
These methods make easy to directly implement recursive definitions of series. Moreover, a stream with memory can be randomly accessed with the at and slice methods without affecting the results of the "next term please" calls like fibo(), as in the example of Section 4:
...
a = fibo();   // Does: a = 8

a = fibo.at( 4 );     // Does: a = 5, this does not affect subsequent calls to fibo()
a = fibo();   // Does: a = 13
a = fibo();   // Does: a = 21

a = fibo.at( 17 );    // Does: a = 2584, this does not affect subsequent calls to fibo()
a = fibo();   // Does: a = 34
...

First dog clone, by Woo-Suk Hwang

The SMC object builds further, adding methods to copy (C) streams at will (excerpt from Annex B):

SMC = ...
...
copy:      function() { return this.copyn( 1 )[ 0 ]; },  // Copy this stream, starting again at the beginning

copyn:     function( /*Integer*/nstreams ) { return _copy( n0, nstreams ); },  // n0: start again at the beginning

rest:      function( /*Integer?*/q ) { 
               if ( q === undefined ) { q = 1; }  // Default: like cdr in LISP
               return _rest( n0 + q );
           },

tee:       function () { return this.teen( 1 )[ 0 ]; },  // Branch this stream at its current point

teen:      function ( /*Integer*/nstreams ) { return _copy( n0 + p, nstreams ); }   // n0 + p: current point
...
These methods affect neither the original stream nor its copies, as in the example of Section 4:
...
a = fibo();   // Does: a = 55

fibo2 = fibo.copy();  // fibo2 starts again at term 0,
                      // this does not affect subsequent calls to fibo()
a = fibo2();  // Does: a = 1
...

a = fibo();   // Does: a = 89
...

fibo3 = fibo.tee();   // fibo3 starts at the current term of fibo,
                      // this does not affect subsequent calls to fibo()
                      // this does not affect subsequent calls to fibo2()

a = fibo3();   // Does: a = 610
...

a = fibo2();  // Does: a = 5
...

a = fibo();   // Does: a = 610
...

Annex B proposes an copy-safe, optimized implementation of SMC.mutate, which uses SM.mutate to create a single, private object called _stream :

// stream with memory, copiable

SMC = S.mutate( {

    mutate: function ( /*...*/ ) {
        
        // Non-copiable stream (will *not* be returned).

        var _stream = SM.mutate.apply( SM, arguments );

        // Stream copier: _copy() and _rest(): layer of abstraction on
        // top of _stream .

        var _copy, _rest;

        ...

        // Returned stream (public)

        return _rest( 0 );  // Safe copy of the private _stream

    }

}, function () { console.error( "Your next() function, to be implemented" ); return; } );
SMC.mutate returns a safe copy of _stream :
        // Returned stream (public)

        return _rest( 0 );  // Safe copy of the private _stream
"Safe" means that the returned stream and any of its public copies can be called without affecting each other. Public copies are produced by methods copy, tee, ...

Optimization: Each term of the series will be computed only once, by _stream, thus avoiding any unnecessary memory duplication or useless computation repetition.

Finally, note that SMC() itself should not be called:

function () { console.error( "Your next() function, to be implemented" ); return; }
SMC is akin to abstract classes in other frameworks and languages.

Annex A goes one dimension further, using SMC to produce streams of streams, as in Pascal's triangle, and Euler's super-acceleration applied to Pi's approximation.

Section 6 goes one level of abstraction higher (or deeper), explaining the mutate inheritance mechanism used to create SM and SMC themselves.

The Hand of God, by Michelangelo

6. Inheritance for Javascript Functions: Self-Closure and Mutation

In Javascript's prototype inheritance framework, new produces Object instances. Objects can have methods, but they are not themselves callable.

On the other hand, Javascript functions are "callable objects" [note 4]. But new cannot return a function, so the prototype framework cannot be used for functions to inherit from other functions.

To achieve inheritance for Javascript functions, I am proposing a some_function.mutate method that produces a mutated copy of some_function:

mutated_copied_function = some_function.mutate(
    function ( a, b, c ) { /* modified implementation ... */ },
    {
       modified_member_1: ...,
       new_member_2: ...,
       ...
    } );
A summarized implementation of mutate follows (excerpt of Annex B):
...

F.mutate = function F_mutate( /* ... many Objects? and/or only one Function? */ ) {

    ...
    // fun = < the one Function in arguments, if any >
    // object = < mixin of all Objects in arguments, if any >
    ...

    // Function implementation: use "me" by default, or "fun" if provided.
    // In both cases: "this === ret" at execution time.
    
    var me = this;
    ret = function() { return ( fun || me ).apply( ret, arguments ); };

    // Copy my member fields and methods ("me"),
    // then overwrite or create new members ("object").
    
    return this.mixin( ret, [ me, object ] );

};
The returned function ret is by default implemented with a copy of the original me, or (optionally) a new implementation fun:
    ret = function() { return ( fun || me ).apply( ret, arguments ); };
Note the self-closure on ret:
ret = function () { ... .apply( ret, ... ); }
Consequently, when executing:
ret()
the context object this will always be ret itself:
this === ret
This handily permits to call ret's methods from within the implementation (fun or me). See for example fibo and next in the stream example of Section 5.

In comparison to Javascript's prototype framework:

Functions are callable objects: while the object returned by new is not callable, the proposed mutate method returns a function, which is per definition itself callable - as used in the stream examples.

Encapsulation: mutate at once copies a function and changes the copy. In contrast, when using prototype, changes can only happen before or after calling new - by modifying objects up the prototype chain, or the object returned by new.

Increased safety: modifying a prototype may have unintended consequences on objects down the prototype chain. In contrast, after b = a.mutate( ... ), modifying a affects neither b nor b's descendants, since a.mutate( ... ) copies all members of a.

Increased speed: when calling b.method(), since method is always a direct member of b, the Javascript engine does not track back a prototype chain.

Copy cost: compared to new, the proposed mutate method may need more memory for "duplicate" members, since a.mutate( ... ) copies all members of a.


Conclusion

If you went that far, you might as well have a look at functional languages like Lisp or Haskell - if unknown to you. Functional programming has powerful, concrete applications, including in Javascript: "Functional programming with Dojo - Quick tips"


Notes

[note 1] In javascript Functions are "callable Objects", which includes "Object" and can thus have member fields and methods

[note 2] The word next is not mandatory. The function could be anonymously defined: fibo = SMC.mutate( function () { ... } );

[note 3] I have been happily using Python itertools in conjunction with libs like numpy and scipy, since they make possible to quickly build reliable scientific simulations. I do not intend to despise the value of Python itertools, but rather to explore equivalent possibilities in a language with closures like Javascript.

[note 4] Try for example in a Javascript console (Firebug, Rhino, etc.) to evaluate:

Function.prototype
Function.prototype.toSource()
Function.prototype.prototype
(function () {}).prototype
... and meditate about it!


References

The links worked as of June 2009.


Pictures

The pictures are all copyright of their respective authors. The picture links worked as of June 2009. But the web being what it is, the links will sooner or later die, so here is some additional information:

Panama sloth.
http://www.oas.org/children/animals/PanamaSouthA.html
Panama sloth



A close-up of a sunflower, by Steven Shogrunden, Summer 2002, Penticton, British Columbia, Canada.
http://www.suncitydesign.com/portfolio/photos/sunflower.htm
Sunflower, by Steven Shogrunden



Mechanical sunflower, 3D-Art.
http://wallpapers.free-review.net/wallpapers/12/Mechanical_sunflower.jpg
Mechanical Sunflower



Michigan from Above, by Alexander Cohn, 2008, Michigan, USA.
http://cohnphoto.com/0207elements/8simpleviewer-from%20above/index.html
Michigan from above, by Alexander Cohn



First dog clone, by Woo-Suk Hwang, 2005.
http://news.nationalgeographic.com/news/2005/08/photogalleries/dogclone/
First dog clone, by Woo-Suk Hwang



Michelangelo di Lodovico Buonarroti Simoni, The creation of Adam (detail), circa 1511, ceiling of the Sistine Chapel, Vatican City.
http://images.worldgallery.co.uk/i/prints/rw/lg/1/6/Michelangelo-The-hands-of-God-and-Man-163501.jpg
The Hand of God, by Michelangelo



Fibonacci Numbers in Nature & the Golden Ratio, by R. Berdan, 2004.
http://www.world-mysteries.com/sci_17.htm
Pascal & Fibonnaci



Cover of "Structure and Interpretation of Computer Programs", 1984, MIT Press.
http://mitpress.mit.edu/sicp/full-text/book/book.html
Structure and Interpretation of Computer Programs



Citroen 2cv v8, by etaku, 2007, Swiss Drag Race Challenge 2007 Turtmann, Switzerland.
http://rides.webshots.com/photo/2345203360101143058leXvBL
Swiss dragster



IBM Type 80 horizontal sorter, circa 1930.
http://www.officemuseum.com/data_processing_machines.htm
Punch Card Machine




A. Streams of streams

Pascal's triangle

Let C_{n,k} be the number of possibilities to choosekelements amongnwhere0 <= k <= n. One can define C_{n,k} recursively:

For all k > 0    C_{0,k} = 0
For all n >= 0   C_{n,0} = 1

C_{n+1,k+1} = C_{n,k+1} + C_{n+1,k}
which yields a 2-D table of C_{n,k} values called "Pascal's triangle":
k       |   0   1   2   3   4   5   6   ...   
-------------------------------------------
C_{0,k} |   1   0   0   0   0   0   0   ...   
        |                                   
C_{1,k} |   1   1   0   0   0   0   0   ...   
        |                                  
C_{2,k} |   1   2   1   0   0   0   0   ...   
        |                                  
C_{3,k} |   1   3   3   1   0   0   0   ...   
        |                                  
C_{4,k} |   1   4   6   4   1   0   0   ...   
                                          
...     
Each row of the table can be seen as a series, defined by the previous row. This leads to a stream of rows or "stream of streams":
var SMC_pascal = SMC.mutate( function next_row() {

    var g_prev = this.previous();  // Previous row
    var row = this.n(); // Index of the next row

    var g = SMC.mutate( function next_term() {

        var col = this.n();  // Index of the next column

        if ( ! g_prev ) { return ( col === 0 ) ? 1 : 0; }  // First row
        if ( col > row ) { return 0; }  // Optimization (not mandatory)
        return ( g_prev.at( col - 1 ) || 0 ) + ( g_prev.at( col ) || 0 );  // Recursion
    } );
    return g;
} );
Each call to SMC.mutate generates a stream, one for rows, one for columns. With activated verbosity (verbose = true), you can see that only the needed terms are computed, and only once, because of the generic memory optimization in the SMC.mutate method.

And for the curious:

Pascal & Fibonnaci

Pi approximation, Euler acceleration and super-acceleration

This example is taken from the SICP book. Please refer to its section 3.5.3 for more details.

Structure and Interpretation of Computer Programs

Consider the following "infinite sum" definition of Pi:

Pi = 4 * ( 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + ... )
We can approximate Pi with a finite sum A_n, using the first n+1 terms:
A_0 = 4
A_{n+1} = A_n + 4 * (-1)^( n + 1 ) / ( 2n + 3 )
As n grows, the error |A_n - Pi| tends towards zero.Below is a stream implementation:
// Stream of integers, starting at 0
var SMC_int = SMC.mutate( function next() {
    if ( this.n() < 1 ) { return 0; }
    return this.previous() + 1;
} ); 

// Stream of Pi approximations
var SMC_pi = SMC.mutate( function () {
    if ( this.n() < 1 ) {
    
        // Initialization: stream of integers
        this._a = SMC_int.copy();

        // Initialization: stream of (-1)^(n+1) for n = 0, 1, 2 ...
        this._b = SMC.mutate( function next() { return -( this.previous() || -1 ); } );
    }

    this._a();  // Skip even numbers
    var x = this._a() * this._b();
    if ( this.n() < 1 ) { return 4; }  // A_0
    return this.previous() + 4 / x;    // A_n for n > 0
} );

a = SMC_pi();  // Does: a = 4                 
a = SMC_pi();  // Does: a = 2.666666666666667 
a = SMC_pi();  // Does: a = 3.466666666666667 
a = SMC_pi();  // Does: a = 2.8952380952380956
a = SMC_pi();  // Does: a = 3.3396825396825403
a = SMC_pi();  // Does: a = 2.9760461760461765
a = SMC_pi();  // Does: a = 3.2837384837384844
a = SMC_pi();  // Does: a = 3.017071817071818 
a = SMC_pi();  // Does: a = 3.2523659347188767
a = SMC_pi();  // Does: a = 3.0418396189294032
a = SMC_pi();  // Does: a = 3.232315809405594 

Swiss dragsterThe convergence is pretty slow. Fortunately, Euler devised an acceleration method for sums of terms with alternating signs. From the series (A_n) we build the accelerated series (B_n) :
                    ( A_{n+2} - A_{n+1} )^2
B_n = A_{n+2}  -                             
                  A_n - 2 * A_{n+1} + A_{n+2}

which can be seen as a generic stream transform:
function SMC_euler_accel( /*SMC*/ s ) {
    var s2 = s.copy();

    // Return an accelerated stream
    return SMC.mutate( function next() {
        while ( s2.n() < 2 ) { s2(); } // Initialization
        s2(); // Iter
        
        // Mathematical formula
        var x = s2.previousn( 3 );
        return x[2] - Math.pow( x[2] - x[1], 2 ) / ( x[0] - 2*x[1] + x[2] );
    } );
}
Let us apply the Euler transform to our stream:
pi_accel = SMC_euler_accel( SMC_pi );

a = pi_accel(); // Does: a = 3.166666666666667
a = pi_accel(); // Does: a = 3.1333333333333337
a = pi_accel(); // Does: a = 3.1452380952380956
a = pi_accel(); // Does: a = 3.13968253968254
a = pi_accel(); // Does: a = 3.1427128427128435
a = pi_accel(); // Does: a = 3.1408813408813416
a = pi_accel(); // Does: a = 3.142071817071818
a = pi_accel(); // Does: a = 3.1412548236077655
a = pi_accel(); // Does: a = 3.1418396189294033
a = pi_accel(); // Does: a = 3.141406718496503
a = pi_accel(); // Does: a = 3.1417360992606667
Compared to the original series, the acceleration is visible. Still, it may last quite long to reach a given precision, say for example 10^(-10).

Let us now repeat the Euler transform again, and again, and so on, indefinitely - that is super-acceleration. The result is a stream of streams:

function SMC_euler_super_accel( /*SMC*/ s ) {
    var s2 = s.copy();

    // Stream of streams (the many rows of the tableau)
    var tableau = SMC.mutate( function next() {
        if ( this.n() < 1 ) { return s2; } // Initialization

        // Single stream (1 row of the tableau)
        return SMC_euler_accel( this.previous() ); // Iter
    } );
We now pick the first term of each stream to generate a super-accelerated stream:
    // Pick the first term of each stream ( = of each row)
    return SMC.mutate( function next() {
        return tableau().first(); 
    } );
}
Let us approximate Pi, using super-acceleration on the original SMC_pi stream:
pi_super_accel = SMC_euler_super_accel( SMC_pi );

a = pi_super_accel(); // Does: a = 4                   
                      // Note: a === SMC_pi.at( 0 )

a = pi_super_accel(); // Does: a = 3.166666666666667
                      // Note: a === pi_accel.at( 0 )

a = pi_super_accel(); // Does: a = 3.142105263157895  
a = pi_super_accel(); // Does: a = 3.141599357319005  
a = pi_super_accel(); // Does: a = 3.1415927140337785 
a = pi_super_accel(); // Does: a = 3.1415926539752927 
a = pi_super_accel(); // Does: a = 3.1415926535911765 
a = pi_super_accel(); // Does: a = 3.141592653589778  
a = pi_super_accel(); // Does: a = 3.1415926535897953 
a = pi_super_accel(); // Does: a = 3.141592653589795  
a = pi_super_accel(); // Does: a = NaN

// pi_super_accel.at(9) - Math.PI === 1.7763568394002505e-15
Two observations:

Each call to SMC.mutate generates a stream, one for rows, one for columns. With activated verbosity (verbose = true), you can see that only the needed terms are computed, and only once, because of the generic memory optimization in the SMC.mutate method.


B. Full code

Punch Card Machine

The full code follows. It outputs log messages on both:



The code can be fully downloaded from:
http://geocities.com/glathoud/publications/mutate_article/mutate.2009-06-26.js

A browser can execute the code, by opening the following URL:
http://geocities.com/glathoud/publications/mutate_article/mutate.2009-06-26.html



/*global console F S SM SMC*/

verbose = false;

// Output logs and errors to the main page, and to the console (if any).

log = function log () {

    // Firebug console, or equivalent
    if ( window.console && console.log ) { console.log.apply( console, arguments ); }

    // For all browsers
    var s = Array.prototype.join.call( arguments, ' ' );
    var n = document.createElement( 'span' );
    n.innerHTML = '<code>' + s + '</code>';
    document.body.appendChild( document.createElement( 'br' ) );
    document.body.appendChild( n );
};

error = function error() {   

    // Firebug console, or equivalent
    if ( window.console && console.error ) { console.error.apply( console, arguments ); }

    // For all browsers
    var s = Array.prototype.join.apply( arguments );
    var n = document.createElement( 'span' );
    n.className = 'error';
    n.innerHTML = 'Error: <code>' + s + '</code>';
    document.body.appendChild( document.createElement( 'br' ) );
    document.body.appendChild( n );

};


// F, F.mutate etc. (explained in Section 5)

F = function F( /*String*/ methodname, /*Object*/ object /* , ... arguments*/) {

    var a, args = [];
    for ( a = 2; a < arguments.length; a++ ) { args.push( arguments[ a ] ); }
    object[ methodname ].apply( object, args );
};

F.mixin = function F_mixin( /*Object*/ destination, /* Array of Objects */ source, /* boolean */ do_not_force ) {
    var a, arr, b, c;

    for ( a = 0; a < source.length; a++ ) {
        b = source[ a ];
        for ( c in b ) { if ( b.hasOwnProperty( c ) ) {
            destination[ c ] = ( do_not_force && destination[ c ] ) || b[ c ];
        }}
    }
    return destination;
};

F.copy = function F_copy() { return this.mutate(); };

F.mutate = function F_mutate( /* ... many Objects? and/or only one Function? */ ) {
    // Heavily inspired from Crockford's mutate(), see http://www.crockford.com

    var a, b, c, n_object = 0, fun = undefined, n_function = 0, n_other = 0, object = {}, ret, x;

    for ( a = 0; a < arguments.length; a++ ) {

        b = arguments[ a ];

        if ( typeof b === "object" ) {
            n_object++;
            object = this.mixin( object, [ b ] );

        }
        else if ( typeof b === "function" ) { n_function++; fun = fun || b; }
        else { n_other++; break; }
    }

    if ( n_function > 1 ) {
        error( "F.mutate(): At most one function." );
        return;
    }

    if ( n_other > 0 ) {
        error( "F.mutate(): Only objects and/or one function." );
        return;
    }

    // Function implementation: use "me" by default, or "fun" if provided.
    // In both cases: "this === ret" at execution time.
    
    var me = this;
    ret = function() { return ( fun || me ).apply( ret, arguments ); };

    // Copy my member fields and methods ("me"),
    // then overwrite or create new members ("object").
    
    return this.mixin( ret, [ me, object ] );

};

// stream without memory, not copiable

/*abstract*/ S = F.mutate( {
    nextn: function( /*Integer*/ n ) { var a, ret = []; for ( a = 0; a < n; a++ ) { ret.push( this() ); } return ret; }
}, function() { /*To be implemented*/ } );


// example streams


function S_int_gen() {
    var f = S.mutate( function() {
        if ( ! f._init ) {
            f._next_value = 0;
            f._init = true;
        }
        var ret = f._next_value;
        f._next_value++;
        return ret;
    } );
    return f;
}

function S_fibo_gen() {

    var f = S.mutate( function() {

        var v;

        if ( f._v === undefined ) {
            f._v = [ 0, 1 ];
            return 1;
        }

        var ret = f._v[ 0 ] + f._v[ 1 ];
        f._v.splice( 0, 1 );
        f._v.push( ret );

        return ret;
    } );

    return f;

}

// Stream with memory, not copiable. Useful to implement e.g. recursive
// definitions like Fibonacci: this.previous() etc. can be called from
// the "next" function.

SM = S.mutate( {
    mutate: function( /*... args ...*/ ) {

        // Find the "next" function

        var next;
        var args = [];

        for ( var a = 0; a < arguments.length; a++ ) {
            var toa = typeof arguments[ a ];
            if ( toa !== 'function' ) {
                if ( toa !== 'object' ) {
                    error( "MS.mutate(): Only objects and/or one function." );
                    return;
                }

                args.push( arguments[ a ] );
                continue;
            }
            if ( next && arguments[ a ] ) {
                error( "MS.mutate(): At most one function." );
                return;
            }
            next = next || arguments[ a ];
        }
        
        next = next || this;  // Default: next() returns: undefined

        // Private stream: Here are the methods "this.xxx()" that next()
        // may use in its implementation

        var _mem = [];
        return S.mutate(
            function () {
                var x = next.apply( this );
                _mem.push( x );
                if ( verbose ) { log('SM next, x:', x, '_mem.length:', _mem.length, ', _mem:', _mem); }
                return x;
            },
            {
                at:    function ( /*Integer*/q ) { return ( _mem.length > q ) ? _mem[ q ] : undefined; },

                mutate: null,  // Forbidden in next(),  xxx todo: may provide an implementation

                first: function () { return this.at( 0 ); },

                n:     function () { return _mem.length; },

                /* nextn:  copy from F.nextn */
                
                previous: function () { var r = this.previousn( 1 ); return r ? r[ 0 ] : undefined; },  

                previousn: function( /*Integer*/q ) { var a = this.n(); return this.slice( a - q, a ); },

                slice: function( /*Integer*/s, /*Integer?*/e ) {  // Note that e === undefined is allowed

                    if ( verbose ) { log( 'SM.slice(',s,',',e,')' ); }

                    if ( s < 0 ) { return undefined; }  // Differs here from Array.slice()
                    if ( e < 0 ) { return undefined; }  // Differs here from Array.slice()
                    if ( e < s ) { return undefined; }  // Differs here from Array.slice()

                    if ( _mem.length < s + 1 ) { return undefined; }  // Differs here from Array.slice()
                    if ( _mem.length < e ) { return undefined; }  // Differs here from Array.slice()

                    return _mem.slice( s, e );
                }

            } );
    }
}, function () { error( "Your next() function, to be implemented" ); return; } );



var SM_int = SM.mutate( function() {

    log( 'SM_int, this.n():', this.n() );
    if ( this.n() < 1 ) { return 0; }

    log( 'SM_int, return this.previous() + 1, this.previous():', this.previous() );
    return this.previous() + 1;
} ); 


var SM_fibo = SM.mutate( function() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ];  // Recursive definition of the next term
} );


// stream with memory, copiable

SMC = S.mutate( {

    mutate: function ( /*...*/ ) {
        
        // Non-copiable stream (will *not* be returned).

        var _stream = SM.mutate.apply( SM, arguments );

        // Stream copier: _copy() and _rest(): layer of abstraction on
        // top of _stream .

        var _copy, _rest;

        _copy = function(/*Integer*/n0, /*Integer*/nstreams ) {  // Generate multiple copies of _stream, starting at element n0
            var r = [];
            for ( var a = 0; a < nstreams; a++ ) {
                r.push( _rest( n0 ) );
            }
            return r;
        };

        _rest = function(/*Integer*/n0) {        // Generate a copy of _stream, starting at element n0

            // The index of the next element is: n0 + p
            var p = 0;  

            // Stream creation and duplication: "public" methods
            
            return S.mutate( 
                function () { return this.at( p++ ); },
                {
                    at:        function( /*Integer*/q ) {
                        if ( verbose ) { log('SMC.at(' + q + ')'); }
                        var n0q = n0 + q;
                        while ( _stream.n() < n0q + 1 ) { _stream(); }   // Auto-generation
                        return _stream.at( n0q );
                    },

                    mutate:     function() { 
                        error( 'In this particular case, not very interesting. Don\'t you think?' );
                        return;
                    },

                    copy:      function() { return this.copyn( 1 )[ 0 ]; },  // Copy this stream, starting again at the beginning

                    copyn:     function( /*Integer*/nstreams ) { return _copy( n0, nstreams ); },  // n0: start again at the beginning

                    first:     function() { return this.at( 0 ); },

                    n:         function() { return p; },

                    nextn:     function( /*Integer*/q ) { return this.slice( p, p + q ); },

                    previous:  function() { 

                        if ( verbose ) { log('SMC.previous: p:', p); }

                        return ( p > 0 ) ? this.at( p - 1 ) : undefined; 

                    },

                    previousn: function( /*Integer*/q ) { return ( p >= q ) ? this.slice( p - q, p ) : undefined; },

                    rest:      function( /*Integer?*/q ) { 
                        if ( q === undefined ) { q = 1; }  // Default: like cdr in LISP
                        return _rest( n0 + q );
                    },

                    slice:     function( /*Integer*/s, /*Integer?*/e ) { 
                        if ( s < 0 ) { return undefined; }
                        if ( e < 0 ) { return undefined; }

                        var s2, e2;
                        s2 = n0 + s;
                        e2 = n0 + e;
                        while ( ( _stream.n() < s2 + 1 ) || ( _stream.n() < e2 + 1 ) ) { _stream(); }  // Auto-generation
                        return _stream.slice( s2, e2 ); 
                    },

                    tee:       function () { return this.teen( 1 )[ 0 ]; },  // Branch this stream at its current point

                    teen:      function ( /*Integer*/nstreams ) { return _copy( n0 + p, nstreams ); }   // n0 + p: current point 
                } );
        };
        
        // Returned stream (public)

        return _rest( 0 );  // Safe copy of the private _stream

    }
}, function () { error( "Your next() function, to be implemented" ); return; } );

//

var SMC_int = SMC.mutate( function next() {
    if ( this.n() < 1 ) { return 0; }
    return this.previous() + 1;
} ); 

var SMC_fibo = SMC.mutate( function next() {
    if ( this.n() < 2 ) { return 1; } // Initialization
    var a = this.previousn( 2 ); // Fetch the last two terms
    return a[ 0 ] + a[ 1 ];  // Recursive definition of the next term
} );

var SMC_pascal = SMC.mutate( function next() {
    var g_prev = this.previous();
    var row = this.n();
    var g = SMC.mutate( function () {
        var col = this.n();
        if ( ! g_prev ) { return ( col === 0 ) ? 1 : 0; }  // First row
        if ( col > row ) { return 0; }  // Optimization (not mandatory)
        return ( g_prev.at( col - 1 ) || 0 ) + ( g_prev.at( col ) || 0 );  // Recursion
    } );
    return g;
} );

function test_SMC_pascal() {
    log( '---------- test_SMC_pascal ----------' );
    var b = SMC_pascal(); log( b.nextn( 10 ) );
    var c = SMC_pascal(); log( c.nextn( 10 ) );
    var d = SMC_pascal(); log( d.nextn( 10 ) );
    var e = SMC_pascal(); log( e.nextn( 10 ) );
    var s;
    s = "SMC_pascal.at( 13 ).at( 3 )"; log( s, ':', eval( s ) );  // Test: "Recursion"
}

test_SMC_pascal();

// Pascal's triangle using factorials

var SMC_fact = SMC.mutate( function next() {
    return ( this.n() || 1 ) * ( this.previous() || 1 );
} );
function fact( n ) { return SMC_fact.at( n ); }

function pascal_fact( n, k ) {
    return fact( n ) / ( fact( n - k ) * fact( k ) );
}

function test_pascal_fact() {
    log( '---------- test_pascal_fact() ----------' );
    var s;
    s = 'pascal_fact( 3, 1 )'; log( s, ':', eval( s ) );
    s = 'pascal_fact( 4, 1 )'; log( s, ':', eval( s ) );
    s = 'pascal_fact( 4, 2 )'; log( s, ':', eval( s ) );
    s = 'pascal_fact( 13, 3 )'; log( s, ':', eval( s ) );
}

test_pascal_fact();

// Euler's accelerator (for series of terms with alternating signs)
// See the LISP example in the SICP book.

var SMC_pi = SMC.mutate( function () {
    if ( this.n() < 1 ) {
        this._a = SMC_int.copy();
        this._b = SMC.mutate( function next() { return -( this.previous() || -1 ); } );
    }

    this._a();
    var x = this._a() * this._b();
    if ( this.n() < 1 ) { return 4; }
    return this.previous() + 4 / x;
} );

function SMC_euler_accel( /*SMC*/ s ) {
    var s2 = s.copy();

    // Return an accelerated stream
    return SMC.mutate( function next() {
        while ( s2.n() < 2 ) { s2(); } // Initialization
        s2(); // Iter
        
        // Mathematical formula
        var x = s2.previousn( 3 );
        return x[2] - Math.pow( x[2] - x[1], 2 ) / ( x[0] - 2*x[1] + x[2] );
    } );
}

function SMC_euler_super_accel( /*SMC*/ s ) {
    var s2 = s.copy();

    // Stream of streams (the many rows of the tableau)
    var tableau = SMC.mutate( function next() {
        if ( this.n() < 1 ) { return s2; } // Initialization

        // Single stream (1 row of the tableau)
        return SMC_euler_accel( this.previous() ); // Iter
    } );

    // Pick the first term of each stream ( = of each row)
    return SMC.mutate( function next() {
        return tableau().first(); 
    } );
}

function test_SMC_euler_super_accel() {
    log( '---------- test_SMC_euler_super_accel() ----------' );
    var pi = SMC_pi.copy(); // copy to make sure we start at the first element
    var pi_accel = SMC_euler_accel( pi );  // Note that we do not have to copy "pi", see SMC.mutate()'s implementation above
    var pi_super_accel = SMC_euler_super_accel( pi );
    var prec = 16;
    for ( var a = 0; a < 11; a++ ) {
        log( pi().toPrecision( prec ), pi_accel().toPrecision( prec ), pi_super_accel().toPrecision( prec ) );
    }
}

test_SMC_euler_super_accel();
test_SMC_euler_super_accel(); // Works exactly the same


Produced on 2009-06-30-07h28m17s using mutate_article.2009-06-26.py
All versions (open)