cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Choose Language Hide Translation Bar
KevW
Level II

Passing messages to "this" within class definition

Is it possible to pass a message to the instance of a class from within a method?

 

Here is an example of what I am trying to do:

 

Define Class(
	"example",
	x=1;
	test = Method( {name, method},
		<< Insert(name, method);
	);
	
);

t = New Object( example());
// This fails to insert the blah method
t:test("blah", Method({y}, show("Blah " || char(x) || char(y))));
t:blah(6);

//This succeeds in inserting and calling the blah method
t << Insert("blah", Method({y}, show("Blah " || char(x) || char(y))));
t:blah(6);

 I suspected that I might need to explicitly write "this << Insert", but that yields a different error:

Define Class(
	"example",
	x=1;
	test = Method( {name, method},
		this << Insert(name, method);
	);
	
);

t = New Object( example());

// This fails because "send expects scriptable object"
t:test("blah", Method({y}, show("Blah " || char(x) || char(y));));
t:blah(6);

I also considered explicitly calling Send (fails to add the method) and this:Send (fails to resolve the name Send).

I also considered directly calling this:Insert (fails to resolve the name Insert) and Insert (successfully adds the blah method, but fails to correctly plumb the calling mechanism, so no arguments get passed into the method).

 

Motivation:

I am doing this because I am trying to automatically create getter- and setter- methods for a list of class properties.  Ideally, I will be able to do the following:

Define Class(
	"example",
	_init_ = Method( {assocArray},
		For Each({property, updateMethod}, assocArray,
			Insert(key, Eval Expr(Method({value}, TrySetProperty(Expr(property), value, Expr(updateMethod)))));
		);
	);
	
	TrySetProperty=Method({parameter, value, expressionIfChanged},
        match(value,
            .,                                  ., 	//Do nothing if missing value
            this << Get Value(parameter),       ., 	//Do nothing if unchanged value
            this << Insert(parameter, value);      	//Otherwise update the value 
            Eval(expressionIfChanged);				//and eval the expression
        );
    );
	
);

properties = Associative Array({{"prop1", Expr(ThingToDoOnUpdate1)},{"prop2", Expr(ThingToDoOnUpdate2)}});
t = New Object( example(properties));

 

5 REPLIES 5
ErraticAttack
Level VI

Re: Passing messages to "this" within class definition

By default JMP doesn't provide nice ways to query the pseudo-namespaces directly (whether that's window, this, box, etc.,).

 

For classes, you might think about creating a wrapper for constructing classes -- that wrapper can then create the instance generator function, and during that instance generation, you can give the class a circular-reference (note that this can lead to memory leaks) -- ensure that you create a tracking system and provide / call a deleter method when the class should be deleted to remove the circular references and such.

 

Here's an example:

Names Default to Here( 1 );
New Namespace( "class" );

class:Get Init Args = Function( {_class},
	{Default Local},
	For( i = 1, i <= N Items( _class ), i++,
		If( Head Name( Arg( _class, i ) ) == "Glue",
			For( j = 1, j <= N Arg( Arg( _class, i ) ), j++,
				If( As Name( Head Name( Arg( Arg( Arg( _class, i ), j ), 1 ) ) ) == As Name( "_create_" ),
					Return( Arg( Arg( Arg( Arg( _class, i ), j ), 2 ), 1 ) )
				)
			)
		)
	);
	Return( {} )
);

class:Define Class = Function( {_class},
	{Default Local},
	class = Expr( Define Class() );
	Summation( i = 1, N Arg( _class ),
		Insert Into( class, Arg( _class, i ) )
	);
	init args = class:Get Init Args( _class );
	init redirects = Expr( __cl:_create_() );
	Summation( i = 1, N Items( init args ),
		If( Head Name( Arg( init args, i ) ) == "Assign",
			Insert Into( init redirects, Eval Expr( Name Expr( Expr( Arg( Arg( init args, i ), 1 ) ) ) ) )
		,
			Insert Into( init redirects, Eval Expr( Name Expr( Expr( Arg( init args, i ) ) ) ) )
		)
	);
	Eval( class );
	ns = Namespace( "class" );
	Eval( Substitute( Expr(
		ns[__class__] = Function( __args__,
			{__cl = New Object( __class__ )},
			__cl:self = __cl;
			__class_call__;
			__cl
		)
	),
		Expr( __class__ ), Arg( _class, 1 ),
		Expr( __args__ ), init args,
		Expr( __class_call__ ), Name Expr( init redirects )
	) );
	0
);

class:Define Class({
	"example",
	_init_ = Method( {}, 1 );
	_create_ = Method( {assocArray},
		For Each( {{key, setter}, _}, assocArray,
			self << Insert( key, Eval Expr( Method( {value}, self:TrySetProperty( Expr( key ), value, Expr( setter ) ) ) ) )
		)
	);
	Try Set Property = Method( {parameter, value, expression If Changed},
        Match( value,
            .,                                  ., 	//Do nothing if missing value
        ,  
            self[parameter],       .	//Do nothing if unchanged value
        ,
            self[paremeter] = value;      	//Otherwise update the value 
            Eval( expression If Changed );				//and eval the expression
        );
    );
    show = Function( {name},
    	show( name )
    )
});

//show( namespace( "class" ) );

cls = class:example( ["A" => expr( self:show( "EXPLETIVE" ) ), "B" => Expr( thing 2 )] );

show( cls << get keys );

Note though that JMP classes are just glorified namespaces with functions attached (namespaces where the attached functions are aware of what namespace they're in).  You can do something quite similar with just regular namespaces and meta-programming.  And with the regular namespaces JMP won't throw superfluous errors around about not being able to access class methods within functions and whatnot.

Jordan
KevW
Level II

Re: Passing messages to "this" within class definition

This does seem to be the kind of workaround JSL forces you to do.

 

There is a parody programming language called "DreamBerd" which makes fun of programming paradigms.  This workaround is very similar to how the parody language handles classes, but manages to be even more complex.

 

(Not saying this is your fault, its just a consequence of the limitations in JSL).

 

https://github.com/TodePond/DreamBerd

 


Classes

You can make classes, but you can only ever make one instance of them. This shouldn't affect how most object-oriented programmers work.

class Player {
   const var health = 10!
}

const var player1 = new Player()!
const var player2 = new Player()! //Error: Can't have more than one 'Player' instance!

This is how you could do this:

class PlayerMaker {
   function makePlayer() => {
      class Player {
         const var health = 10!
      }
      const const player = new Player()!
      return player!
   }
}

const const playerMaker = new PlayerMaker()!
const var player1 = playerMaker.makePlayer()!
const var player2 = playerMaker.makePlayer()!
KevW
Level II

Re: Passing messages to "this" within class definition

I think this could be handled better with custom message passing, but I can't really find any documentation about how to actually do that.  As far as I can tell, there is no info about implementing your own messages.

ErraticAttack
Level VI

Re: Passing messages to "this" within class definition

Messages just call methods for classes -- technically you can't call a function with arguments via a message, but since methods in JSL classes are expressions, the result of the message call is a callable function.  For all intents and purposes, you can just call class method via a message.

 

Notice the line Show( clx << Add( cly ) ); calling the Add method via a message.

 

Names Default To Here( 1 );
Define Class(
	"complex",
	real = 0;
	imag = 0;
	_init_ = Method( {a, b},
		real = a;
		imag = b;
	);
	Add = Method( {y},
	PRINT( "ADDING" );
		New Object( complex( real + y:real, imag + y:imag ) )
	);
	Sub = Method( {y},
		New Object( complex( real - y:real, imag - y:imag ) )
	);
	Mul = Method( {y},
		New Object( complex( real * y:real - imag * y:imag, imag * y:real + real * y:imag ) )
	);
	Div = Method( {y},
		t = New Object( complex( 0, 0 ) );
		mag2 = y:Magsq();
		t:real = real * y:real + imag * y:imag;
		t:imag = imag * y:real + real * y:imag;
		t:real = t:real / mag2;
		t:imag = t:imag / mag2;
		t;
	);
	Magsq = Method( {},
		real * real + imag * imag
	);
	Mag = Method( {},
		Sqrt( real * real + imag * imag )
	);
	_to string_ = Method( {},
		Char( real ) || " + " || Char( imag ) || "i"
	);
	_show_ = _to string_;
);
clx = New Object( complex( 1, 2 ) );
cly = New Object( complex( 2, 3 ) );

Show( clx << Add( cly ) );

clx << Delete;
cly << Delete;
Delete Classes( "complex" );
Jordan
KevW
Level II

Re: Passing messages to "this" within class definition

I keep forgetting that while JSL borrows from the best (in this case message passing in smalltalk), it never quite captures what was special about the original.