How I miss immutable types...
Monkey Forums/Monkey Programming/How I miss immutable types...
| ||
I just had to mention it. |
| ||
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 |
| ||
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!) |
| ||
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. |
| ||
Structs are immutable. That's what I had in mind! |
| ||
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 |
| ||
@ziggy: Structs are immutable What? struct foo { int bar; }; foo.bar = 1; |
| ||
@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. |
| ||
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. |
| ||
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? |
| ||
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 ;) |