Notifications
Clear all

[Closed] Lambdas in Maxscript – First Attempt

I tried to implement some way to allow a function in maxscript to call another function for which it doesn’t
know its parameters. In C# you can use Lambdas to do that.

Here an example in C#:

CheckAge(person, 
 onAdult: () => Checkout(person, shoppingCardItems)
 onChild () => PrintNotAllowedError() )

/// Here the code of the CheckAge method

public void CheckAge(Person p, Action onAdult, Action onChild)
{
   if(p.Age > 18)
      onAdult();
   else 
      onChild();
}


EDIT: igonore this solution, it is crappy. Please scroll down to my answer to this post.
——————————— old ————————————-

Here is my attempt to mimic a same behavior in maxscript.

I just post it here in case someone is interessed or has some own ideas how to
implement Lambdas in maxscript. I would be eager to know it.

(
	
global LambdaObject
global lambda1, lambda2, lambda3, lambda4
global Call

(	

	fn Call fPointer =
	(

		if classof fPointer == LambdaObject then
			fPointer.exec()
		else if classof fPointer == MAXScriptFunction then
			fPointer()

	)
	
	struct LambdaObject 
	(
		functionPointer,
		lambdaParameter= #(),
		
		fn CallOrGet arg =
		(
			if classof arg == LambdaObject then
				arg.exec
			else
				arg
		),
		
		fn exec =
		(

			if lambdaParameter.count == 0 then
				functionPointer()	
			else if lambdaParameter.count == 1 then					
				functionPointer (CallOrGet lambdaParameter[1])					
			else if lambdaParameter.count == 2 then
				functionPointer (CallOrGet lambdaParameter[1]) (CallOrGet lambdaParameter[2])			
			else if lambdaParameter.count == 3 then
				functionPointer (CallOrGet lambdaParameter[1]) (CallOrGet lambdaParameter[2]) (CallOrGet lambdaParameter[3])		
			else if lambdaParameter.count == 4 then
				functionPointer (CallOrGet lambdaParameter[1]) (CallOrGet lambdaParameter[2])	(CallOrGet lambdaParameter[3])	(CallOrGet lambdaParameter[4])					
		)
		
	)
	
	
		
	-- // lambda
	fn lambda1 c p1 = 
	(
		l = LambdaObject()
		l.functionPointer = c
		l.lambdaParameter = #()
		l.lambdaParameter[1] = p1
		l
	)
	
	fn lambda2 c p1 p2 = 
	(
		l = LambdaObject()
		l.functionPointer = c
		l.lambdaParameter = #()
		l.lambdaParameter[1] = p1
		l.lambdaParameter[2] = p2
		l
	)
	
	fn lambda3 c p1 p2 p3 = 
	(
		l = LambdaObject()
		l.functionPointer = c
		l.lambdaParameter = #()
		l.lambdaParameter[1] = p1
		l.lambdaParameter[2] = p2
		l.lambdaParameter[3] = p3
		l
	)
	
	fn lambda4 c p1 p2 p3 p4 = 
	(
		l = LambdaObject()
		l.functionPointer = c
		l.lambdaParameter = #()
		l.lambdaParameter[1] = p1
		l.lambdaParameter[2] = p2
		l.lambdaParameter[3] = p3
		l.lambdaParameter[4] = p4
		l
	)
	OK
)


	fn CheckAge person isAdult: isChild: =
	(
		if person.age > 18 then 
			Call isAdult
		else 
			Call isChild
		
		OK		
	)
	
	fn CheckName str isPeter isNotPeter  =
	(
		if  str == "Peter" then 
			Call isPeter
		else 
			Call isNotPeter
		
		OK		
	)
	
	
	fn CheckHasEnoughMoney person isRich isPoor =
	(
		if person.money > 1000 then 
			Call isRich
		else 
			Call isPoor
		OK		
	)
	
	fn PrintchildError =
	(
		print "This product is not for children"
	)
	
	fn PrintString str = print str
	
	
	struct Person 
	(
		name,age,money
	)

	
	fn checkandprint p =
	(
		

		
		successmessage = "You can buy this"
		
		
		CheckAge p \
			isAdult:(lambda3 CheckHasEnoughMoney p \
					/*is rich*/ (lambda3 CheckName p.name \
						/*is peter*/ (lambda1 PrintString "Peter you are not allowed to shop here") \
						/*is not peter*/ (lambda1 PrintString successmessage)) \
					/*is poor*/ (lambda1 PrintString ("Sorry you are poor"))) \
			isChild:PrintchildError		
	)
	
	p = Person()
	p.name = "Peter"
	p.age = 120
	p.money = 200
	checkandprint p

	

)

The readability of the code isn’t that good yet, because of two reasons:

  • no function overloading ( so i needed lambda1, lambda2 for differenct parameter counts)
  • and the syntax of maxscript wouldn’t allow me to use named arguments ( so i used comment blocks but its ugly)

Also this is not working correctly and i dont’t know why:

	fn printallStrings s1 s2 s3 = (print s1+s2+s3)

	test = (lambda3 printallStrings "hi" "my" "name" )
	Call test

	-- NOT WORKING: just calls first lambaparameter
	CheckAge p \
		isAdult:test \			
		isChild:PrintchildError	

		
	CheckAge p \
		isAdult:(lambda1 PrintString "hi") \			
		isChild:PrintchildError	

PS: Yes i am aware of the performance overhead. But for many scipts performance is not the
problem but maintainablity/readability is more important.

35 Replies

Ok, found a solution for the missing overloading of functions.
I forgot that i can use the struct initializing to get the parameters.

Also the code should now work. So ignore the solution above.

Only thing i am still missing is a way to use named arguments in function calls inside lambda

 
-- test calls some with 1 parameter and some with 2 parameter
CheckAge p1 \
	isNotAlive:	(lambda print "you arent't even alive, c'mon man!")\	
	isAdult:		(lambda PrintGreeting "welcome" p1)\				
	isChild:		(lambda print "is child")	
	
-- test lambda inside of lambda
CheckAge p1 \
	isAdult:(lambda 
              CheckMoney p1 400 \
	            (lambda print "you can buy it") \
		    (lambda print "you don't have enough money to buy this"))\				
	isChild:(lambda print "you are not old enough to buy this") \
	isNotAlive:	(lambda print "you arent't even alive, c'mon man!")	
						

Full code:

struct lambda
(
	functionpointer,
	p1,p2,p3,p4,
	
	fn exec = 
	(
		if p4 != undefined then
			functionpointer p1 p2 p3 p4
		else if p3 != undefined then
			functionpointer p1 p2 p3
		else if p2 != undefined then
			functionpointer p1 p2
		else if p1 != undefined then
			functionpointer p1
		else
			functionpointer()
	)
)

fn Call fPointer =
(
	if classof fPointer == Lambda then
		fPointer.exec()
	else if classof fPointer == MAXScriptFunction then
		fPointer()
)


-- Test lambdas
fn CheckMoney person price hasMoney notEnoughMoney =
(
		if person.money >= price then 
			Call hasMoney
		else
			Call notEnoughMoney
			
)

fn CheckAge person isAdult: isChild: isNotAlive: =
(
		if person.age > 18 then 
			Call isAdult
		else if person.age > 0 then
			Call isChild
		else
			Call isNotAlive
				
)

fn PrintGreeting greeting person = (print (greeting + " " +person.name))

struct Person  (name, age , money)

	
p1 = Person "peter" 30 200

-- test calls some with 1 parameter and some with 2 parameter
CheckAge p1 \
	isNotAlive:	(lambda print "you arent't even alive, c'mon man!")\	
	isAdult:		(lambda PrintGreeting "welcome" p1)\				
	isChild:		(lambda print "is child")	
	
-- test lambda inside of lambda
CheckAge p1 \
	isAdult:(lambda CheckMoney p1 400 \
		(lambda print "you can buy it") \
		(lambda print "you don't have enough money to buy this"))\				
	isChild:(lambda print "you are not old enough to buy this") \
	isNotAlive:	(lambda print "you arent't even alive, c'mon man!")	
						

why is this ‘complexity’?

maxscript has well done ‘function’ passing mechanics…

fn average method a b power: = (method a b power:power)

fn linear a b = (a+b)/2.0
fn square a b = sqrt(a*a + b*b)
fn powered a b power:4 = pow (pow a power + pow b power) (1.0/power)

/*
average linear 2 4
average square 2 4
average powered 2 4 power:3
*/


to allow a function in maxscript to call another function for which it doesn’t
know its parameters

your average can just execute methods that takes two parameters. So you restrict what kind of function gets executed next.

can you pass an array of arguments?

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

exactly…

Then I can just execute functions that take one array as argument.
Also I don’t think that solution will be more readable than my last solution.

I want to achieve that function ‘A’ calls function ‘B’ without knowing anything about function ‘B’.

edit: the goal is to get loose coupling

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

but you have to specify an action and a list of arguments anyway

No, just an action, not the agruments.

My C# example from above:



public void main ()
{
   var shoppingcarditems = new List<string>();
   shoppingcarditem.Add("Apple");
   shoppingcarditem.Add("Orange");

   CheckAge(person, 
      onAdult: () => Checkout(person, shoppingcarditems)
      onChild () => PrintNotAllowedError() )
}


public void CheckAge(Person p, Action onAdult, Action onChild)
{
   if(p.Age > 18)
      onAdult();
   else 
      onChild();
}

CheckAge calls Checkout without knowing that it also gets passed shoppingcarditems into it from outside ( here the main function). Only the main function knows the Checkout function. The CheckAge function doens’t know other functions.

fn stringUnion method data &out: = 
(
	out = method data 
)

fn easyadd data = 
(
	s = ""
	for d in data do s += d
	s	
)
fn camelcase data = 
(
	s = ""
	for d in data do 
	(
		d = tolower d
		d[1] = toupper d[1]
		s += d
	)
	s
)

/*

stringUnion easyadd #("dogs", "and", "cats", "are", "not", "friends") out:&s0
stringUnion camelcase #("dogs", "and", "cats", "are", "not", "friends") out:&s1

s0
s1


*/


struct Job1
(
	data, 
	result,
	fn action = (result = (data[1] + data[2]))
)
struct Job2
(
	data, 
	result,
	fn action = (result = (data[1] as string + " + " + data[2] as string + " = " + (data[1] + data[2]) as string))
)

fn doAJob job = 
(
	job.action()
	ok
)

j1 = Job1 data:#(1,4)
doAJob j1
j1.result

j2 = Job2 data:#(1,4)
doAJob j2
j2.result


fn doAJobExt job data: = 
(
	if data != unsupplied do job.data = data 
	job.action()
	ok
)

struct Job3
(
	data, 
	result,
	fn action = 
	(
		act = if data[2] >= 0 then " + " else " - "  
		result = (data[1] as string + act + (abs data[2]) as string + " = " + (data[1] + data[2]) as string)
	)
)

j3 = Job3 data:#(0,0)
doAJobExt j3
j3.result
doAJobExt j3 data:#(1.3, -0.8)
j3.result


Page 1 / 4