Misunderstanding of basics

Monkey Forums/Monkey Programming/Misunderstanding of basics

Ken(Posted 2013) [#1]
Hi All,

Clearly I'm not understanding something regarding using Monkey...could someone clue me in? (I'm an Objective C guy by day, and have used Java/C++ etc in the past, so I have that working for (or maybe against) me).

When I create an object in Monkey, and pass parameters in, and copy them, do I get pass-by-value or pass-by-reference?

I'm looking at the warpy/bezier/bezier.monkey example.

I imagined that I'd add a dx/dy component for each point, and allow those points to bounce around the screen, redrawing the bezier curve each update.

So the top of my bezier class looks like this:
-------------------------------
Class bezier
Field points#[]
Field mx#[]
Field my#[]
Field inc#

Method New(points#[],mx#[],my#[],inc#=-1)
Self.points = points
Self.mx = mx
Self.my = my

If inc=-1
Local dx#=points[6]-points[0]
Local dy#=points[7]-points[1]
Local d#=Sqrt(dx*dx+dy*dy)
inc=1/d
Endif
Self.inc = inc
End
-------------------------------

I added a move method for the bezier:
-------------------------------
Method move()
Print ("Move with points " + points[1]+","+points[2])
For Local idx=1 To 4
Local xidx = idx*2
Local yidx = xidx+1

Self.points[xidx] += Self.mx[idx]

If Self.points[xidx]<0.0 Then
Self.points[xidx]=0.0
Self.mx[idx] *=-1
Endif
If Self.points[xidx]>DeviceWidth() Then
Self.points[xidx]=DeviceWidth()
Self.mx[idx] *=-1
Endif

Self.points[yidx] += Self.my[idx]
If Self.points[yidx]<0.0 Then
Self.points[yidx]=0.0
Self.my[idx] *=-1
Endif
If Self.points[yidx]>DeviceHeight() Then
Self.points[yidx]=DeviceHeight()
Self.my[idx] *=-1
Endif
Next
End
-------------------------------

The this is inside my OnUpdate():
-------------------------------
For Local bz:=Eachin beziers
bz.move()
Next
-------------------------------

It seems to be working for me now, which is a blast. After various other RAD scripting languages, the Monkey code (translated) runs like stink.

However, if I have one bezier curve, the end points bounce as expected. If I continue on and make more bezier curves (Monkey runs so fast I can make *lots*, even on my iOS device), then it almost feels as if it's pass-by-reference, as endpoints that are nowhere near the edge sometimes end up bouncing.

In fact, if I create 20 bezier curves, the end points seem to move en masse, altogether. When one bounces off the right hand side, all bounce towards the left. When one bounces off the top, they all bounce, as if they were bouncing off the top.

I'm sure I've done something silly here...I just can't see what. Anyone?

-Ken


Jesse(Posted 2013) [#2]
objects are passed by reference and variables are passed by value.


Nobuyuki(Posted 2013) [#3]
q: are arrays passed by reference, or just type primitives? I keep forgetting, and I'm sure it would be relevant to this thread.


Gerry Quinn(Posted 2013) [#4]
Objects are the simplest. A pointer to the object is always passed.

Primitives are passed by value. For ints and bools this is straightforward: a copy of the value is passed. Strings don't work exactly this way, but they act like they do. (This is because strings are immutable. Even though a pointer is actually passed, if the string is changed the pointer becomes invalid, so the called function can never change the original string.)

Arrays are kind of in between. Arrays themselves are immutable, but the things they contain are not. So you can pass an array and change the values in it, and the changes will happen to the original array. But if you resize the array, it gets reallocated, and any further changes you make will not affect the original array.


Samah(Posted 2013) [#5]
Actually all variables are passed by value. When you pass an object, you're passing a pointer to the object, as a value. Please note that this isn't just semantics, there is a real difference.

When we talk about passing something by reference, we mean that altering the argument in the method will affect the original variable that was passed in, like using a C++ pointer. If you do that with an object argument in monkey, it will change the local variable to point to a new object, but it won't affect the original pointer that was passed into the method.

The only real way to do by-reference in Monkey is to pass an array and modify its contents.

Edit: Code sample:
[monkeycode]Local a:Int = 1
Local b:Int = 2
Local c:Foo = Null

Function Test:Void(d:Int, e:Int, f:Foo)
d = 5
e = 6
f = New Foo
End

' here's what happens with standard monkey functionality (d/e/f are all byval)
Test(a,b,c)
' a = 1
' b = 2
' c = Null

' here's what happens if they are all byref
Test(a,b,c)
' a = 5
' b = 6
' c = (an instance of Foo)[/monkeycode]
If monkey were byref for objects as you are implying, the first example would have c as an instance of Foo. Instead it is null. This is important.

Edit 2:
To you C/C++/Objective-C guys, this is the same concept as * vs ** when you're talking about memory blocks, not primitives. For objects you're passing in (void *), but to alter the variable the pointer came from, it needs to be (void **), which Monkey is not. In this example, byval would be (int, int, void *), and byref would be (int *, int *, void **).


therevills(Posted 2013) [#6]
And heres a runnable example in Monkey from Samah's code:
Strict

Function Main:Int()
	Local a:Int = 1
	Local b:Int = 2
	Local c:Foo = Null

	Test(a,b,c)

	Print a
	Print b
	If c <> Null Then
		Print "not null"
	Else
		Print "null"
	End
	
	Return 0
End

Function Test:Void(d:Int, e:Int, f:Foo)
  d = 5
  e = 6
  f = New Foo
End

Class Foo
	Field x%
End


Which outputs:
1
2
null


Ken(Posted 2013) [#7]
Thanks very much for the answers.

The signal to noise ratio here seems incredibly high!

-Ken


Gerry Quinn(Posted 2013) [#8]
"Actually all variables are passed by value. When you pass an object, you're passing a pointer to the object, as a value. Please note that this isn't just semantics, there is a real difference."

For me this way of describing (I know it is standard in the Java world at least) it is poor semantics. Nothing can be passed logically except a bit pattern, and if you say that any bit pattern is a value, then to say that all variables are passed by value is to say nothing at all, and therefore is not worth saying. It's *important* whether you pass a copy of something or a pointer to it.

People can mean different things by "passing by reference". It's best to describe it in terms of pointers to things and copies of things IMO. that way everyone can get a clear picture of what happens.


muddy_shoes(Posted 2013) [#9]
I'm not sure if it's standard in the Java world. I've met plenty of Java coders that wouldn't have a clue about the difference between pass by value or pass by reference.

What's true in the Java world and, abstractly, in Monkey is that when you define a variable (be it a Local or a Field or a Global) of an object type then you are defining a reference to that type. All objects are passed as references by value because references are all you ever have.

The fact that "Local obj:Object" defines a reference to an Object and not an actual Object is important. You see the importance every time someone gets confused how to declare and populate a 2D Array.


AdamRedwoods(Posted 2013) [#10]
"Actually all variables are passed by value. When you pass an object, you're passing a pointer to the object, as a value. Please note that this isn't just semantics, there is a real difference."

yeah, i agree, this would be confusing. a pointer is a value, but a pointer is used differently than a value.

Function Main:Int()
	Local a:Int = 1
	Local b:Int = 2
	Local c:Foo = New Foo

	Test(a,b,c)

	Print a
	Print b
	Print c.x
	
	Return 0
End

Function Test:Void(d:Int, e:Int, f:Foo)
  d = 5
  e = 6
  f.x = 1337
End

Class Foo
	Field x%
End



Ken(Posted 2013) [#11]
Ah, okay, now I feel I'm getting down to brass tacks here.

On line 31, I have a switch inside the bezier New method that either (True) copies the elements of the arrays, or (False) just copies the pointer to the arrays.

As an aside, is there a built-in way to copy an array? I realise that I should only Resize once, since I know how big the arrays should be...but is there a built-in to do this that's more efficient?

At any rate, I'm confused though, because both points and mx,my are treated the same way, the behaviour seems to be different.

When I negate the mx, then the mx for that point for all bezier curves is negated. Set line 31 to False and run the program, creating 5-20 curves, and you'll see what I mean. They kind of wobble back and forth, all together. When the last point for one curve hits the right size, all the last points bounce towards the left. When the first point of any curve hits the bottom, the curves all head upwards as if they'd bounced.

When I click through to create a set of points for use in a bezier curve, they are put into the points# array. If the pointer to this has been copied, why aren't the points of all the already existing arrays modified too as the new points go in, so that no matter how many bezier's you create, they're all on their own, even though they bounce together?

There's something important here I'm still not grasping.

Anyone?

-Ken

'Draw bezier curves

'A bezier curve is defined by four control points. 
'The first control point is the start of the curve.
'The second control point defines the initial direction of the curve (relative to the start)
'The third control point defines the final direction of the curve (relative to the end point)
'The fourth control point is the end of the curve.

'The curve is parameterised by a variable t. That means, any value of t between 0 and 1 corresponds to a point on the curve.
't=0 corresponds to the start of the curve, and t=1 corresponds to the end of the curve.

'We can only draw an approximation to the bezier curve for various difficult maths reasons.
'There are a few algorithms for drawing bezier curves. 
'This one works by working out the positions of points on the curve for various values of t, and joining them up with straight lines.


Import mojo


'The bezier class represents a single bezier curve.
'It is defined by four control points, which are stored in the points array
'inc is the 'step size' used for drawing - a smaller number means a more accurate curve
Class bezier
	Field points#[]
	Field mx#[]
	Field my#[]
	Field inc#
	
	Method New(points:Float[], mx:Float[], my:Float[], inc:Float = -1)
	
		If True Then
			For Local q:Float = EachIn points
				Self.points = Self.points.Resize(Self.points.Length + 1)
				Self.points[Self.points.Length - 1] = q
			Next		
			For Local q:Float = EachIn mx
				Self.mx = Self.mx.Resize(Self.mx.Length + 1)
				Self.mx[Self.mx.Length - 1] = q
			Next
			For Local q:Float = EachIn my
				Self.my = Self.my.Resize(Self.my.Length + 1)
				Self.my[Self.my.Length - 1] = q
			Next
		Else
			Self.points = points
			Self.mx = mx
			Self.my = my
		EndIf
		
		If inc=-1	'if inc is not given, make a guess based on the distance between the two end points
			Local dx#=points[6]-points[0]
			Local dy:Float = points[7] - points[1]
			Local d#=Sqrt(dx*dx+dy*dy)
			inc = 1 / d
		Endif
		Self.inc = inc
	End
	
	Method xpos#(t#)
		'work out the x co-ordinate of the point on the curve corresponding to the given value of t
		Local nt#=1-t
		Return nt*nt*nt*points[0] + 3*nt*nt*t*points[2] + 3*nt*t*t*points[4] + t*t*t*points[6]
	End Method
	
	Method ypos#(t#)
		'work out the y co-ordinate of the point on the curve corresponding to the given value of t
		Local nt#=1-t
		Return nt*nt*nt*points[1] + 3*nt*nt*t*points[3] + 3*nt*t*t*points[5] + t*t*t*points[7]
	End Method
	
	Method move()
		'Print ("Move with points " + points[1]+","+points[2])
		For Local idx=0 To 3
			Local xidx = idx*2
			Local yidx = xidx+1
			
			Self.points[xidx] += Self.mx[idx]
			
			If Self.points[xidx]<0.0 Then
				Self.points[xidx]=0.0
				Self.mx[idx] *=-1
			Endif
			If Self.points[xidx]>DeviceWidth() Then
				Self.points[xidx]=DeviceWidth()
				Self.mx[idx] *=-1
			Endif
			
			Self.points[yidx] += Self.my[idx]
			If Self.points[yidx]<0.0 Then
				Self.points[yidx]=0.0
				Self.my[idx] *=-1
			Endif
			If Self.points[yidx]>DeviceHeight() Then
				Self.points[yidx]=DeviceHeight()
				Self.my[idx] *=-1
			Endif						
		Next 
	End
	
	Method draw()
		'draw the control points
		SetColor 0,0,255
		DrawPoints points
		'draw lines showing how the second and third control points define the direction of the curve
		SetAlpha .3
		DrawLine points[0],points[1],points[2],points[3]
		DrawLine points[4],points[5],points[6],points[7]
		SetAlpha 1
		
		'draw the curve
		'it will work by increasing t from 0 to 1, 
		'calculating the co-ordinates of the corresponding point on the curve at each step,
		'and drawing a line between it and the previous calculated point 
		SetColor 255,255,255
		Local ox#=points[0], oy#=points[1]	'the first point on the curve is just the first control point
		
		Local x#,y#
		Local t#=0
		
		While t<1
			t+=inc		'add inc to t, so moving along the curve a little bit
			If t>1 t=1
			
			'calculate the position of the corresponding point on the curve
			x=xpos(t)	
			y=ypos(t)
			
			'draw a line between the previous point and the point we just calculated
			DrawLine ox,oy,x,y
			
			'the point we just calculated now becomes the 'previous' point
			ox=x
			oy=y
		Wend
	End
End

'Draw a circle at each of the points in the given array
Function DrawPoints(points#[])
	For Local i=0 To points.Length-1 Step 2
		If points[i]>0
			DrawCircle points[i],points[i+1],3
		Endif
	Next
End

Class BezierApp Extends App
	Field points#[8],i			'This array stores the control points the user is currently placing
	
	Field dx#[4]
	Field dy#[4]
	
	Field beziers:List<bezier>		'This list will store all the beziers that have been drawn
	
	Method OnCreate()
		SetUpdateRate 60
		beziers = New List<bezier>
	End
	
	Method OnUpdate()
	
		If JoyHit( JOY_BACK ) Error ""
		
		For Local bz:=Eachin beziers
			bz.move()
		Next
		
		'when the user clicks, place a control point
		If TouchHit(0)
			points[i]=TouchX()	'store where the user clicked
			points[i+1]=TouchY()
			dx[i/2] = Rnd(-10,10)
			dy[i/2] = Rnd(-10,10)
			i+=2
			If i=8				'if four points have been placed, we can create a bezier
				beziers.AddLast(New bezier(points,dx,dy))
				
				points = New Float[8]	're-initialise the points array
				i=0
			Endif
		Endif
	End
	
	Method OnRender()
		Cls
		
		'draw all the beziers that have been created
		For Local b:bezier=Eachin beziers
			b.draw
		Next
		
		'draw the control points the user is currently placing
		SetColor 255,255,0
		DrawPoints points

		SetColor 255,255,255
		DrawText "Click to place control points",0,0
	End
End

Function Main()
	New BezierApp
End



Loofadawg(Posted 2013) [#12]
As an aside, is there a built-in way to copy an array? I realise that I should only Resize once, since I know how big the arrays should be...but is there a built-in to do this that's more efficient?


Do you mean like this?

arraycopy = array


I am really going to feel dumb if I misunderstood you, but as an example:

Function Main:Int()
	Local s:String
	Local n:Int
	Local grid1:Int[][]
	Local grid2:Int[][]
	
	grid1 = grid1.Resize(4)
	
	n = 0
	For Local i:Int = 0 Until grid1.Length
		n += 1
		grid1[i] = New Int[i].Resize(n)
	End
	
	n = 0
	Print "The contents of grid1:"
	For Local i:Int = 0 Until grid1.Length
		s = ""
		For Local j:Int = 0 Until grid1[i].Length
			n += 1
			grid1[i][j] = n
			s = s + " " + grid1[i][j]
		Next
		Print s
	Next

	grid2 = grid1 ' Make a copy of the grid1
	
	Print "The contents of grid2:"
	For Local i:Int = 0 Until grid2.Length
		s = ""
		For Local j:Int = 0 Until grid2[i].Length
			s = s + " " + grid2[i][j]
		Next
		Print s
	Next
	
	Return 0
End



Now if someone could write a quick code example of the following statement by Gerry demonstrating the pointer becoming invalid..

Strings don't work exactly this way, but they act like they do. (This is because strings are immutable. Even though a pointer is actually passed, if the string is changed the pointer becomes invalid, so the called function can never change the original string.)


How do you go about proving that?


Gerry Quinn(Posted 2013) [#13]



Loofadawg(Posted 2013) [#14]
Hey Gerry.
First of all, thank you for the response.

Function Main:Int()
	Local name:Int = 7
	Print "Who are you?~n"
	Print "I am "+name
	
	TryToChangeNumber( name )	

	Print "~nI am not a number,"
	Print "I am a free man! (who happens to have a number for a name)"
	Print "I am "+name
	Return 0
End

Function TryToChangeNumber( number:Int )
	number = 6
	Print "~nYou are now number "+number
End


Don't INTs behave the same way as in the above?

Now I understand string methods do not modify strings, they just pass a pointer to another sting that contains the mods.

Maybe I am thinking to hard of what you were saying. I was thinking you were implying something was different in the way strings were versus BOOL and INTs.


In any case, your previous example before the last demonstrated it perfectly and your last one was just plain funny so I had to respond with a prisoner reference.

Cheers!


Gerry Quinn(Posted 2013) [#15]
Yes, Ints behave the same way as Strings. But Ints behave that way because they are copied before being passed as parameters to functions/methods.

Strings can often be big, so they are not copied before being passed as parameters. Nevertheless, they act as if they were copied, because of their immutability.

Internally, strings work like objects. But externally, they work like primitives, i.e. Ints or Bools, that really get copied when they are passed as parameters.


Loofadawg(Posted 2013) [#16]
Gotcha! Thanks for the explanation.


Gerry Quinn(Posted 2013) [#17]
Bonus complication: arrays are a little bit different again from everything else!