How I miss immutable types...

Monkey Forums/Monkey Programming/How I miss immutable types...

ziggy(Posted 2013) [#1]
I just had to mention it.


Ken(Posted 2013) [#2]
I'm still rather vague on the benefits of immutables. I use them in Objective-C regularly...I just don't appreciate them other than "That's the way it's done in Objective-C".

What is it you miss?

-Ken


ziggy(Posted 2013) [#3]
Imagine this scenario:
Class MyObject
     Method GetPos:Vector2D() 
         Return currentPos
     End
     Method Render()
          DrawMe(currentPos.X, currentPos.Y)
     End
     Private 
     Field currentPos:Vector2D 'Imagine this is inmutable
End

If you call the GetPos to get the object positon, and modify any of its atributes (such as the X field or property), subsequent Renders can be performed on a wrong location.

Mojo does prevent this by using directly primitive data types, or arrays:
Class MyObject
     Method GetPosX:Int() 
         Return currentPos.X
     End
     Method GetPosY:Int() 
         Return currentPos.Y
     End
     Method Render()
          DrawMe(currentPos.X, currentPos.Y)
     End
     Private 
     Field currentPos:Vector2D
End

But when I have to do this, I find it very unlegant. Specially with things like a Vector class that can encapsulate lots functionality you cannot use as much as you wish unles you return "copies" of already existing objects in function calls (Degrading performance due the GC?)

Anyway, whenever I have to do this, I find it slightly unelegant.

I can live perfectly well without them, but life would be better if Monkey had them! (and delegates too, of course!)


Erik(Posted 2013) [#4]
I really miss Structs from C#. They are allocated on the stack, always passed by value and so on.

Since monkey is focused on games, perhaps at least we could have a hardcoded Vector2 type, that has all the operator overloading and is passed by value.


ziggy(Posted 2013) [#5]
Structs are immutable. That's what I had in mind!


Ken(Posted 2013) [#6]
I think I understand your point...because the Vector2D contains multiple items, instead of giving you a copy, it gives you a pointer. If you need to change an element in what was passed to you, the original element is changed.

Did I understand that right?

Sounds like the array problem I had:
http://www.monkeycoder.co.nz/Community/posts.php?topic=4987

Other than programmatically stepping through the array/data structure, is there a way to say "Give me a copy, not a reference"?

There's CopyBytes, and various Peek* things, but I don't see a way, given a random data structure, to say sizeof(x) so that you'd know how many bytes to copy.

-Ken


Samah(Posted 2013) [#7]
@ziggy: Structs are immutable

What?

struct foo {
  int bar;
};
foo.bar = 1;



ziggy(Posted 2013) [#8]
@samah: I was meaning that they're always passed by value instead of being passed by reference, as any primitive type. maybe not the right word by my side. any function returns a copy of them so they are not modified unpredictibly by the fuction using your method results. if I don't remember wrong this is done in the stack so it's fast.


Gerry Quinn(Posted 2013) [#9]
The ideal to my mind is C++ in which you can declare things any way you want in terms of references, const references, copies, pointers, whatever. But that seems to be considered too complicated these days.

One thing I do a lot is defensive copying, anywhere it's not going to cause speed problems. If I have a method like:

MyWindow.SetRect( rect:Rect )

it's going to contain something like

screenRect = new Rect( rect )
or
screenRect.Set( rect )

[the second copies values into a pre-existing rect]

I never use:
screenRect = rect
in such situations.


I also use a coding standard in which if I pass objects as parameters to a function, the parameter name must be preceded by an underscore if it cannot be treated as const. Obviously Monkey doesn't enforce this, but there is nothing to stop a programmer from doing it (unless he accidentally breaks it with a bug).

So if I were to use the write the above method in the possibly unsafe way, it would be:
Method SetRect( _rect:Rect )
screenRect = _rect
End

whereas I can write
Method SetRect( rect:Rect )
screenRect = New Rect( rect )
End

When I see the underscore, I know that I can't call it with a rect I need later. [To be fair, this is an implied situation where it would rarely if ever cause problems as MyWindow.screenRect soesn't look like the kind of thing that would change of its own accord, but it's just an example of my rule.]

Additionally, I like to precede non-const class methods with an underscore, and I try to have mostly const methods. So the above methods would be called _SetRect() instead of SetRect().

Obviously copying can be inefficient, so I don't do it obsessively everywhere, but there are often cases where large chunks of code are executed only occasionally (e.g. setting up screen layouts) and it does no harm to avoid any risk of reference-created bugs in such cases.


marksibly(Posted 2013) [#10]
It's not so much that monkey doesn't have immutable types, it's just that they're not that efficient to implement for things like Vector2D.

Vector2D itself could easily be made immutable, eg:

Class Vector2D

	Method New( x:Float,y:Float,z:Float )
		_x=x
		_y=y
		_z=z
	End
	
	Method Plus:Vector2D( v:Vector2D )
		Return New Vector2D( _x+v._x,_y+v._y,z+v._z )
	End
...etc...


...but of course, all those 'News' would be a killer.

I tried to solve this with the bananas/mak/gles20cube Vec3 and Mat4 classes by using a virtual stack based tmp (temporary) pool, but I think it turned out to be a little too complex to be generally useful.

One approach would be to have client code provide 'out' values for ops, eg:

   Method Add:Void( v:Vector2D,out:Vector2D )
      out.x=x+v.x
      out.y=y+v.y
      out.z=z+v.z
   End
End


Which isn't too bad. Vector2D remains immutable, but client code now has to provide storage for results.

Another idea might be to have Vector2D and MutableVector2D, ala ObjC. eg:

Class MutableVector2D Extends Vector2D

	Method New( x:Float,y:Float,z:Float )
		_x=x
		_y=y
		_z=z
	End
	
	Method New( v:Vector2D )
		_x=v.x
		_y=v.y
		_z=v.z
	End
	
	Method Set:MutableVector2D( v:Vector2D )
		_x=v.x
		_y=v.y
		_z=v.z
		Return Self		'so we can chain ops
	End
	
	'fast but hard
	Method Add:MutableVector2D( v:Vector2D )
		_x+=v.x
		_y+=v.y
		_z+=v.z
		Return Self		'so we can chain ops
	End
	
	...etc...

End

Class Entity

	Method SetPos:Void( v:Vector2D )
		_pos.Set v
	End
	
	Method GetPos:Vector2D()
		Return _pos
	End
	
	Private
	
	Field _pos:=New MutableVector2D
	
End


Client code can then easily 'read' GetPos value, eg: GetPos().X, but can't write to it. Easily anyway - they can still potentially downcast the returned Vector2D to MutableVector2D.

Also, client code can do 'slow' math with GetPos using Plus() or 'fast' math by initing a MutableVector2D with GetPos, eg:

Local speed:=New Vector2D(...)
entity.SetPos entity.GetPos().Plus( speed )   		'involves a New...

'...Or...

Local pos:=New MutableVector2D( entity.GetPos() )	'copy GetPos
pos.Add(blah).Subtract(etc).Multiply(doh)     		'no New in here at least...
entity.SetPos pos							'or here...


In some cases, the 'Newing' ops like Plus:Vector2D(...) could use a simple circular buffer to allocate tmps. The problems start when client code assigns these tmps to vars that persistent longer than it takes the circular buffer to 'wrap around'. If client code always remembered to use eg: MutableVector2D.Set instead of assignment - and didn't use 'too complex' (eg: recursive) expressions - it'd work in most cases I think. Blitz3D actually does this with it's matrix class, where tmp matrix objects are allocated from a 64 element buffer for matrix math ops. But in c++, assigning these tmps to a var invokes a copy.

A language tweak could actually be added to enforce this, say, a variation on C++ 'const methods' that indicate the value returned by a method may not be assigned to a var (which is not quite what const methods are in C++), eg:

Method Plus:Vector2D( v:Vector2D ) Const
  Return AllocTmp( x+v.x,y+v.y,z+v.z )   'tmp comes from a 'big enough' circular buffer
End
'
Local t:=x.Plus( y )  'ERROR! Const method results can't be assigned to vars!
Local t:=New MutableVector2D
t.Set x.Plus( y )        'OK!


Then you could write code like:

Class Vector2D   'immutable!

	Method New( x:Float,y:Float,z:Float )
		_x=x
		_y=y
		_z=z
	End
	
	Method Plus:Vector2D( v:Vector2D ) Const
		Return New Tmp( _x+v._x,y+v._y,z+v._z )
	End
	

	Function Tmp:Vector2D( x:Float,y:Float,z:Float ) Const
		'allocate a tmp from a circular buffer...
	End
	
	...etc...

End

Class MutableVector2D Extends Vector2D

	Method Set:Void( v:Vector2D )
		_x=v.x
		_y=v.y
		_z=v.z
	End

End

Class Entity

	Method SetPos:Void( v:Vector2D )
		_pos.Set v
	End
	
	Method GetPos:Vector2D()
		Return _pos
	End
	
	Private
	
	Field _pos:=New MutableVector2D
	
End

entity.SetPos entity.GetPos().Plus( Vector2D.Tmp( xs,ys,zs ) )


With the only 'News' occuring when the entity is created.

This can already be done, it's just that client code has to be careful not to assign tmps to vars for 'too long'. Lacking Const, perhaps some method name prefix like 'op' could be used?


Belimoth(Posted 2013) [#11]
I've been looking at the translator code and to my limited understanding it seems possible to add something like C#'s Struct to Monkey.

How hard would it be to have something like...

Struct MyStruct
	Field x:Int, y:Int
	
	Method New( x:Int, y:Int)
		Self.x = x; Self.y = y
	End
End

Function DoStuff:Void( param:MyStruct )
	'stuff
End

...

Local myStruct := New MyStruct(2, 3)
DoStuff( myStruct )


Translate to...

Function DoStuff:Void( param_MyStruct_x:Int, param_MyStruct_y:Int )
	'stuff
End
...

Local myStruct_MyStruct_x:Int = 2, myStruct_MyStruct_y:Int = 3
DoStuff( myStruct_MyStruct_x, myStruct__MyStruct_y)


I am going to attempt it myself but there are probably some pitfalls that I can't foresee.

Maybe even a certain IDE could do this before passing the code to Monkey ;)