Alla fine di Come utilizzare Definisci classe Ho accennato che c'era qualcosa di interessante nel fatto che le istanze della classe JSL fossero difficili da copiare. se X fa riferimento a un'istanza, Y=X crea sia Y che X fare riferimento al stesso esempio. La stessa cosa accade quando si passa un'istanza a una funzione definita dall'utente: il valore della funzione è non una copia dell'istanza; esso si riferisce al stesso esempio. Quindi cambiare l'istanza all'interno della funzione la cambia anche per il chiamante. Se hai mai passato una matrice a una funzione e l'hai modificata nella funzione, aspettandoti quella del chiamante copia per essere aggiornato, sai che JSL passa gli argomenti alle funzioni utente per valore.
JMP passes arguments by making copies, but...JMP passes arguments by making copies, but...JMP passes arguments by making copies, but...JMP passes arguments by making copies, but...JMP passa gli argomenti facendo delle copie, ma...
Si noti che la matrice viene copiata, quindi le modifiche alla matrice all'interno della funzione vengono apportate alla copia, non all'originale. Si noti che viene copiato anche il grande punto nero di riferimento, ma non l'oggetto a cui fa riferimento. Ciò causa molti mal di testa ai programmatori JAVA principianti perché non hanno visto questa immagine. All'interno della funzione, puoi fare due cose molto diverse a variable un .
- puoi chiamare un metodo, come a:set(42);
- puoi assegnare un valore, come a=42;
Nel caso (1) si modifica il file argyle valore dell'oggetto da 3 a 42. Questo è probabilmente ciò che intendi fare. nel caso (2), si sovrascrive il grande punto nero accanto a un con 42. Probabilmente non intendevi farlo.
Il codice seguente usa oggetti che conservano riferimenti ad altri oggetti. Tutti gli oggetti hanno un metodo next(), quindi gli oggetti possono essere composti in modi interessanti. Il metodo next() produce il valore successivo in una sequenza di valori. Alcuni oggetti rappresentano una costante e producono sempre lo stesso valore successivo. Altri, come l'oggetto add, hanno altri due oggetti (il lato sinistro e il lato destro dell'operatore +). Add:next() chiama left:next() e right:next() e aggiunge le due risposte.
Il JSL completo è allegato. Eccolo con le immagini interfogliate. Iniziamo con la classe più semplice.
// number/matrix wrapper class
Define Class(
"vwrap",
m_v = .; // holds the number or matrix
_init_ = Method( {v},
If( Type( v ) == "Class",
Throw( "vwrap is for number or matrix, not class " || Char( v << getname ) )
);
m_v = v;
);
set = Method( {v},
If( Type( v ) == "Class",
Throw( "vwrap is for number or matrix, not class " || Char( v << getname ) )
);
m_v = v;
);
next = Method( {},
m_v; // return
);
);Vwrap è il nome della classe e avvolge un valore, un numero o una matrice. Ha un setter che rifiuterà i valori che non apprezza e un getter, chiamato next(), per restituire il valore successivo in una sequenza. Poiché è un po' brutto scrivere NewObject(Vwrap(42)), creiamo anche una funzione helper per creare Vwraps.
// helper function v(...) converts a scalar or matrix to a vwrap, or returns an instance unchanged
v = Function( {x},
If( Type( x ) == "Class",
If( !(x << Contains( "next" )),
Throw( "v expects number, matrix, or one of the classes it supports, not class " || (x << getname) )
);
x; // x supports a next function, it is probably OK
, // else
If( !Is Number( x ) & !Is Matrix( x ),
Throw( "v expects number or matrix, not " || Type( x ) )
);
New Object( vwrap( x ) ); // wrap the number or matrix
)
);v(...) verrà chiamato molto, a volte con un'istanza di classe, a volte con uno scalare o una matrice, e deve restituire l'istanza di classe invariata quando ne ottiene una. Potrebbe anche verificare che ci sia un next() nella classe allo stesso tempo, nel caso in cui ci siano altre classi che potrebbero confondersi.
Successivamente, una funzione in gran parte boiler plate per creare un grafico.
// global function gr(...) makes a graph
gr = Function( {Xvar, Yvar, title, n},
{Xs, Ys, dt}, // locals
Yvar = v( Yvar );
Xvar = v( Xvar );
Ys = J( n, 1, Yvar:next() );
Xs = J( n, 1, Xvar:next() );
dt = As Table( Ys || Xs, <<INVISIBLE></INVISIBLE>Puoi vedere le chiamate a v() per i parametri X e Y; quelli possono essere numeri semplici che verranno racchiusi o altre classi con un metodo next(). Quindi la funzione J() crea matrici di N valori, viene creata una tabella invisibile e viene eseguito Graph Builder, con un po' di magia per chiudere la tabella nascosta quando non è più necessaria. Proviamolo.
gr( 13, 42, "Value 13,42", 1 );
A single point, at x=13, y=42A single point, at x=13, y=42A single point, at x=13, y=42A single point, at x=13, y=42Un singolo punto, in x=13, y=42
Aspetta, andrà meglio... Creiamo una classe contatore che restituirà 0,1,2,3,... alle chiamate successive a next().
// counter class
Define Class(
"counter",
m_rate = .; // non zero increment
m_pos = empty(); // internal position
_init_ = Method( {rate},
m_rate = v( rate );
);
next = Method( {},
rate = m_rate:next();
// see peek() comment in "sin" class
If( Is Empty( m_pos ),
result = 0 * rate; // get scalar or matrix dimension on first call
m_pos = rate; // for next time
, // else
result = m_pos; // from last tim
m_pos += rate; // for next time
);
result; // return
);
);Il metodo _init_ prende una velocità che determina la velocità di incremento del contatore. Rate può essere una costante, in genere 1, oppure può essere un'altra istanza di classe. Ecco una funzione di supporto per creare un contatore e un primo grafico.
// global function to make a counter
ctr = Function( {rate = 1},
New Object( counter( rate ) )
);
gr( ctr(), ctr() , "count up A (equal steps)", 5 );Si noti che due istanze contatore separate sono state passate a gr(...) per gli assi X e Y.
Two counters, running in parallel, for five steps starting at zero.Two counters, running in parallel, for five steps starting at zero.Two counters, running in parallel, for five steps starting at zero.Two counters, running in parallel, for five steps starting at zero.Due contatori, in esecuzione in parallelo, per cinque passi a partire da zero.
Ora, per qualcosa di diverso: facciamo in modo che il tasso del contatore sia un altro contatore.
// this variation begins to show the power of composition
gr( ctr(), ctr( ctr() ), "count up B (first step is 0)", 5 );
The first step is zero because the rate counter's first result is zero.The first step is zero because the rate counter's first result is zero.The first step is zero because the rate counter's first result is zero.The first step is zero because the rate counter's first result is zero.Il primo passo è zero perché il primo risultato del contatore dei tassi è zero.
L'asse Y di questo grafico utilizza la composizione funzionale di due contatori. Aggiungiamo una classe sin().
// sin() wrapper class
Define Class(
"sin",
m_rate = .; // 0<STEP><PI. m_amp=".;"> Sin( m_pos += m_rate:next() ) // return
);
);</PI.></STEP>
Sin produce un'onda sinusoidale infinita a una frequenza e ampiezza determinate da costanti o altre istanze di classe. Mantiene anche una posizione interna. Puoi dire dai commenti che potrebbe aver bisogno di più pensiero, ma funziona per questa demo. Ecco una forma d'onda a ciclo singolo.
gr( ctr(), New Object( Sin( 2 * Pi() / 100, 3 ) ), "sin()", 101 );
Single cycle of sine waveSingle cycle of sine waveSingle cycle of sine waveSingle cycle of sine waveSingolo ciclo di onda sinusoidale
Simile al contatore con un incremento di un altro contatore, un'onda sinusoidale può avere una frequenza variabile.
gr( ctr(), New Object( Sin( New Object( Sin( .0002, 1 ) ), 3 ) ), "variable frequency", 1000 );
Ramping up the frequency using the first bit of another sine waveRamping up the frequency using the first bit of another sine waveRamping up the frequency using the first bit of another sine waveRamping up the frequency using the first bit of another sine waveAumento della frequenza utilizzando il primo bit di un'altra onda sinusoidale
E due diverse onde sinusoidali di frequenza possono essere tracciate l'una contro l'altra.
gr( New Object( Sin( 4 * Pi() / 99, 3 ) ), New Object( Sin( 6 * Pi() / 99, 3 ) ), "2X3 sin()", 100 );
Simple integer ratios make nice patternsSimple integer ratios make nice patternsSimple integer ratios make nice patternsSimple integer ratios make nice patternsSemplici rapporti interi creano bei modelli
Un'altra funzione di classe e di supporto; la classe aggiunge una o più altre classi insieme e l'offset helper aggiunge una costante... o un'altra funzione, probabilmente... non testata.
// add class
Define Class(
"add",
m_a = {};
//m_b = .;
_init_ = Method( {a, b = Empty()},
If( Is Empty( b ),
For( i = 1, i <= N Items( a ), i += 1,
Insert Into( m_a, v( a[i] ) )
),
m_a[1] = v( a );
m_a[2] = v( b );
)
);
next = Method( {},
result = m_a[1]:next();
For( i = 2, i <= N Items( m_a ), i += 1,
result = result + m_a[i]:next()
);
result;
);
);
// helper function uses add to offset another value by off
offset = function({off,x},
newobject(add(off,x))
);Con l'helper offset uno dei primi grafici che aveva un passo zero iniziale può essere rielaborato per avere un passo iniziale di uno.
// compared to a previous example, this one increments earlier
gr( ctr(), ctr( offset(1,ctr( 1 )) ), "count up C (first step is 1)", 5 );
Now all the steps are one bigger than before.Now all the steps are one bigger than before.Now all the steps are one bigger than before.Now all the steps are one bigger than before.Ora tutti i passaggi sono uno più grande di prima.
// building a square wave by summing odd harmonics
n = 100;
basefreq = 2 * Pi() / n;
freq1 = New Object( Sin( 1 * basefreq, 1 / 1 ) );
freq2 = New Object( Sin( 3 * basefreq, 1 / 3 ) );
freq3 = New Object( Sin( 5 * basefreq, 1 / 5 ) );
freq4 = New Object( Sin( 7 * basefreq, 1 / 7 ) );
sum = New Object( Add( {freq1, freq2, freq3, freq4} ) );
gr( ctr(), sum, "4 Odd Harmonics", n + 1 );
Summing odd harmonics at reduced amplitude approximates a square wave.Summing odd harmonics at reduced amplitude approximates a square wave.Summing odd harmonics at reduced amplitude approximates a square wave.Summing odd harmonics at reduced amplitude approximates a square wave.La somma di armoniche dispari ad ampiezza ridotta approssima un'onda quadra.
// same square wave, a lot more odd harmonics
n = 200;
basefreq = 2 * Pi() / n;
freqs = {};
For( i = 1, i <= 23, i += 2,
Insert Into( freqs, New Object( Sin( i * basefreq, 1 / i ) ) )
);
gr( ctr(), New Object( Add( freqs ) ), "12 Odd Harmonics", n + 1 );
More harmonics help square it upMore harmonics help square it upMore harmonics help square it upMore harmonics help square it upPiù armoniche aiutano a farla quadrare
// multiply class
Define Class(
"multiply",
m_a = {};
_init_ = Method( {a, b = Empty()},
If( Is Empty( b ),
For( i = 1, i <= N Items( a ), i += 1,
Insert Into( m_a, v( a[i] ) )
),
m_a[1] = v( a );
m_a[2] = v( b );
)
);
next = Method( {},
result = m_a[1]:next();
For( i = 2, i <= N Items( m_a ), i += 1,
result = result :*</img> m_a[i]:next()
);
result;
);
);
// multiply two waveforms
n = 1000;
freq1 = New Object( Sin( .1, 1 ) );
freq2 = New Object( Sin( 2 * Pi() / n, 1 ) );
prod = New Object( Multiply( freq1, freq2 ) );
gr( ctr(), prod, "multiply", n + 1 );
Multiply two frequencies; the lower frequency creates the envelope.Multiply two frequencies; the lower frequency creates the envelope.Multiply two frequencies; the lower frequency creates the envelope.Multiply two frequencies; the lower frequency creates the envelope.Moltiplicare due frequenze; la frequenza più bassa crea l'inviluppo.
// similar, but control amplitude of freq1 using another sin wave
n = 1000;
freq2 = New Object( Sin( 2 * Pi() / n, 1 ) ); // lo freq
freq1 = New Object( Sin( .1, freq2 ) ); // hi freq, control amplitude with freq2
gr( ctr(), freq1, "amp is sin", n + 1 );
Alternatively, control the amplitude of the higher frequency using the lower frequency.Alternatively, control the amplitude of the higher frequency using the lower frequency.Alternatively, control the amplitude of the higher frequency using the lower frequency.Alternatively, control the amplitude of the higher frequency using the lower frequency.In alternativa, controllare l'ampiezza della frequenza più alta utilizzando la frequenza più bassa.
Infine, crea un vwrap contenente una matrice di frequenze armoniche dispari e crea una nuova classe per sommare la matrice in uno scalare.
// try a matrix...use v(...) to make a vwrap to hold the matrix
n = 200;
mat = v( ((1 :: 49 :: 2) * (2 * Pi() / n)) );
// add the sin wrapper. sinmat:next() returns a row vector of 5 sin values
sinmat = New Object( Sin( mat, 1 / (1 :: 49 :: 2) ) );
// matrixSum assumes the argument class is returning a matrix and returns the scalar sum
Define Class(
"matrixSum",
m_mat = .;
_init_ = Method( {mat},
m_mat = v( mat )
);
next = Method( {},
Sum( m_mat:next() )
);
);
gr( ctr(), New Object( matrixSum( sinmat ) ), "25 Odd Harmonics", n + 1 );
It will take a lot more harmonics to really reduce the over-shoot and the ripples.It will take a lot more harmonics to really reduce the over-shoot and the ripples.It will take a lot more harmonics to really reduce the over-shoot and the ripples.It will take a lot more harmonics to really reduce the over-shoot and the ripples.Ci vorranno molte più armoniche per ridurre davvero l'overshoot e le increspature.
Bravo dottore: https://www.jmp.com/support/help/en/15.1/index.shtml#page/jmp/classes.shtml