The ref keyword coming to FUZE
-
Some things are not entirely clear to me on how the
ref
keyword will work when it comes to FUZE. When the update video came out, I thought the update would soon follow, so back then I decided to respond supportive. However, as we're still waiting, I'm kind of still curious on how the keyword will behave exactly. And I do think this is a keyword that deserves some discussion.First of all, in many languages (but not all of them), objects and arrays are accessed by reference by default. However, the main reasoning behind this default is memory and performance (to prevent creating a lot of useless copies of the same array or object). However, we're living in a different time now, where the resources to do this are plentiful. For example, the following snippet of JavaScript creates 5000 unique objects, adds them to an array and prints how many milliseconds that took. On my laptop from 2016, this takes less than 1 millisecond (meaning it sometimes prints 0 to the console, as if no time has passed while doing this, while running in my browser):
//JavaScript snippet: (() => { // module var start = new Date(); var arr = []; for(var i = 0; i<5000; i++) { arr.push({i}); } var end = new Date() console.log(+end - start); })();
So if you look at this decision with this in mind, and thinking about young learning programmers. I do actually think that being pure about passing an array by value, is more intuitive to them. Especially when you offer the option to reference the original array, so they are made aware that this is something that you need to think about.
So as I said before, I like this addition to the language.
However, the
ref
keyword may introduce other expectations, of which I'm not sure that FUZE will meet them in the update (I don't know).For example, what will the following code print out?
var list = [1,2,3] change(list) print(list) // what will this output? function change(ref lst) lst = [4,5,6] return void
If this would print out
[4,5,6]
I think that would be really cool.Then it could be used for more advanced stuff as well:
string strVal = input("enter a number", false) int result // no value yet? if toInt(strVal, result) then print(result, " is a valid number") else print(strVal, " is not a valid number") endif function toInt(strVal, ref intVal) intVal = int(strVal) int isNaN = str(intVal) == "nan" return !isNaN
On the other hand, if
ref
is only a keyword to decide between copying structs, vectors and arrays, or not, then I think that's still a valuable addition to the language, that enables learning programmers to think about the difference between the two. I mean in the end when they switch to different languages, they will learn how that language works. And things will be different anyway (what ever language they learn).So in my opinion, I don't mind which ever direction FUZE has chosen to go with this. But I'm curious about peoples expectations concerning the announced keyword and where the language is actually going :)
-
Memory and performance are still important. I’m wishing ref was already implemented to help with my project, for those reasons and to improve readability.
As far as I know, your first example is exactly what should happen. I don’t understand your second thing though, so I can’t comment on that.
-
@PB____
my two cents for what it's worth. the switch (and there for fuze) uses c++ under the hood which does not pass objects by reference hence the purpose of pointers. since fuze is a "basic" language built on top of an object oriented language that has no native memory management, the issues we have been seeing on this topic are artifacts on how the underlying c++ language handles the fuze interpreted code. theref
keyword is most likely being used as a translation for&
in c++. i could be completely wrong, but would make sense. -
I hope this is purely optional, it looks way too complex for me.
-
Will I be able to have
a = 1 b = 2 c = [ref a, ref b]
? -
@toxibunny That seems like it would work, then your array values would always be up to date with a and b.
-
I think that usage would be as Dave specified as a by-variable basis in this post: link.
That response was over 6 weeks ago though, so there might be some new insights by now, but I wouldn't expect to be able to use ref other than in the signature of a user-defined function.
-
@SteveZX81 I believe so. From Dave's explanation at 20:00 its to address a bug that was making FUZE behave unpredictably when copying values between certain types of variable.
Instead of just reading the value into another variable it was creating a kind of link ("a reference") which meant the original would mimic the new variable, which you probably didn't intend - especially if you're new to coding and you've never heard of "passing by reference".
But then wait a sec, what if you DID want this to happen, thus the "ref" keyword that allows you to say "yes I do want to modify the original, not just copy its values".
They're giving you a choice to avoid confusing people who are new to coding vs those who expect the latter to happen.
Hope that helps (even just a little bit)
-
@PB____ Hard to say as FUZE seems to be modelled/inspired by BASIC but with features borrowed from other languages. So I guess its down to how the development team choose to make it work.
Coming from a C# background, I would probably find "ref" most useful in functions. I can see it being useful for games like real time strategy where you have hundreds of units (tanks, soldiers, etc) that you want to track individually, sometimes you might want to clone "unit a" (a copy) othertimes you want a handle that represents "unit a" - so you can affect it. Having the choice is good 😊
-
@spt-games I agree, having a choice is good, and I also think you're spot on with your response to Steve. If it's fuzzy what this is about, your code will probably keep working just fine. And should something break, then it would probably be an easy fix.
I'm not sure if I was clear about the nuances that I used in my example codes.
- To me it's clear that if you use
ref
: then changing properties/elements on the ref variable will have the same effect the original.
What's not clear to me is:
- What happens when you assign a value to the variable (in stead of changing a property or element of the ref argument)?
- Will the reference be lost: like when assigning a string to an int variable, it just becomes a different type of variable
- Will the reference be kept: and would the original variable outside of the function be assigned the new value
For example in C# it would be possible to assign to the original variable by using ref.
- If the reference is kept, will it then be required to have initially assigned a value to the variable?
If you could assign from within the function, then you could use this as an additional output of your function. (in C# you would use theout
keyword for this).
Sorry that this was a half post earlier (I accidentally hit tab and then space while typing).
- To me it's clear that if you use
-
I reckon for simplicity they will go for the former. FUZE's language likes simplicity over headache inducing complexity (I am looking at you C language). For example:
/* Would update whatever actual variable "thePerson" refers to */
thePerson.favouriteFood = "Pizza"/* Reference to the original variable is lost and thePerson becomes an integer. Original variable unaffected. */
thePerson = 2 -
Memory references are an important concept in programming. 'Handles', 'indexes', 'pointers' and even 'variable names' are all different kinds of memory references. Ultimately the underlying idea is the same: There is a place where my data is stored and this 'reference' is how I keep track of that place.
When passing this data to functions, there is no difference (in behavior) between value and reference parameter passing conventions if all you do is read the data. However there is a significant behavioral difference when you write back to the parameter variables.
Some functions only purpose is to compute a return value by reading the parameters without altering them. However for other functions the purpose is specifically to write to and change the data that are referenced by the parameters.
It is possible to write to these locations without reference passing by instead storing the data in global arrays. You can then pass an index to any desired element by value and use this index like a reference to the data in the global array. However writing functions that specifically access global arrays is not considered good practice because the function is dependent on the name of the global variable. It will be difficult to reuse the function in a later program that needs it but has differently named or even name conflicting global variables.
So the feature is definitely useful. It allows the writing of generalized code free of ties to the global space of variable names.
Making reference variables as @toxibunny suggests is another whole feature. Implementing good memory management with reference variables is a complex thing, that might require a sophistcated garbage collector to detect and free structures with cyclical references.
Ultimately what is needed is that whatever features and behaviors are implemented are well defined and documented so that no one has to do trial and error guesswork to figure out what a program will do. (you still can if you wan't to though ;-P)
-
@Gothon Yours is a much better explanation! Especially about having to use global variables in the meantime. Functions are currently good for breaking up code and making it re-usable in a program, but as you suggested you can "tightly couple them" to specific global variables which means potentially a lot of modification when you want to use it in another program, instead of a nice black box that just does the thing you need, but how it does it and on what doesn't matter.
-
@toxibunny said in The ref keyword coming to FUZE:
Will I be able to have
a = 1 b = 2 c = [ref a, ref b]
?No. Unless things have changed since I was last aware, the ref keyword can only be used as a modifer for function parameters. Nowhere else.
-
@Martin Thanks for clearing that up. Its good to know this in advance.
-
Thanks for the responses, I think this is an interesting discussion, but I'm still not sure if everybody is on the same page :)
To prevent confusion, I'd like to summarize in code some things that are clear and not clear to me about how ref will behave in FUZE:
var list = ["a"] print(list[0]) // prints "a" var otherList = noRef(list) print(list[0]) // still prints "a" otherList[0] = "b" print(list[0]) // still prints "a" var refList = withRef(list) print(list[0]) // now prints "c" refList[0] = "d" print(list[0]) // I'm not sure if this will print "c" or "d" now. list = ["e"] assignRef(list, ["f"]) print(list[0]) // I'm not sure if this will print "e" or "f" print(refList[0]) // Bonus question... function noRef(values) values[0] = "b" return values function withRef(ref refValues) refValues[0] = "c" return refValues function assignRef(ref valueList, var toAssign) valueList = toAssign return void
I think it's clear that ref will only be used in the function signature. So no
list = [ref a, ref b]
. But the underlying mechanics pose a question:var list = [[1]] var element = list[0] element[0] = 2 print(list[0][0]) // should this print 1 or 2? var linkedList = [ .value = 1, .previous = [], .next = [], .hasPrevious = false, .hasNext = false ] linkedList.next = [ .value = 2, .previous = linkedList, .next = [], .hasPrevious = true, .hasNext = false ] linkedList.hasNext = true linkedList.next.previous.value = 3 print(linkedList.value) // does this print 1 or 3? Would this behavior be consistent with the previous print example?
So even though you're not using the ref keyword, there are some expectations about references when they are used in an array or a struct. If a property or element is always a copy, then it's not possible to make a proper linked list for example (since you need multiple references to the same instance in that case).
Of course I have my expectations about how these things will be handled, but at this point, I still don't know. And I don't need to have the answers before the release, but I thought a discussion about these questions would do no harm.
-
@PB____
i think the thing to remember here is what fuze actually is and how it works under the hood on the actual hardware. as i mentioned before the underlying codebase for the switch is c++, which arrays and structs are copied. the key thing to differentiate however, is when speaking to the assignment operator (aka=
) vs. passing into a function.the default behavior is to do a shallow copy in which member elements are duplicated and initialized with their source value. this gets tricky when one of those members might point to something in dynamic memory. by virtu of the shallow copy process the value of that "location" is what is copied, not the actual "data" stored in that location. this is where deep copy comes in and it will actually allocate new memory space and take the source data from that original memory location and duplicate it, essentially "decoupling" it away from the original location and its data, to its own exact duplicate of that information.
when passing something into a function like
void myFunc(int x)
, it is also copied by value. however you can change this behavior to pass by reference by simply changing it to bevoid myFunc(int& x)
. so nowx
inside the function will actually be a reference back to the original value that was passes in.this is why my suspicion as i mentioned in my earlier reply is that
ref
is just an alias for&
under the hood (which in itself is just syntax sugar for*
as well) -
@thedos
I do get what your saying, but I think differently about it. You can write any type of compiler or interpreter in C++ and the language it interprets can be completely different from how C++ behaves. In the end, it should matter how the FUZE team thinks it lands with their user base and targeted audience. However, it's interesting to see the different expectations people have with this.I like your point about shallow versus deep copy, to put it in code:
var data = [["0"]] dataCopy = makeCopy(data) data[0][0] = "1" print(dataCopy[0][0]) // if passing by value creates a shallow copy, this would print "1", if it's a deep copy, it prints "0". function makeCopy(value) return value
And if I understood you correctly about
ref
being an alias to&
, then I think this is a scenario that I had already written out in code:var data = [0] assign(data, [1]) print(data[0]) // I think you're saying you expect this to print: 1 function assign(ref reference, value) reference = value return void
It could be that I misunderstood the second scenario though, my C++ is a little rusty.
In the end it's interesting to see peoples expectations about this. Since the nuances in it appear to be different per person.
-
As for me, it does not really matter as long as it is clearly communicated what kind of behavior is actually supported. One can always get around how things work by writing the appropriate code yourself.
Besides, I trust the Fuze team to have thought this over a lot and come up with a good implementation.
-
@vinicity
I completely agree. I'm not trying to divide here. If anything, I think it's good if people feel like they've been heard.I do hope people don't get too high expectations though from the discussion.
As @spt-games said:
I reckon for simplicity they will go for the former. FUZE's language likes simplicity over headache inducing complexity (I am looking at you C language).
So I think it's better at this time not to anticipate any fancy patterns to use in FUZE if you were not able to do them before the update. But rather think of this as a fix for inconsistencies. Maybe the fix also leads to new things you can do, but maybe not.