The ref keyword coming to FUZE
-
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.
-
@PB____
i would say you got it. i think a better example however would be to use two independent variables and see how they behave. i think you understand where i am going with this, but for the sake of everyone else who might find this helpful.function addOneCopy(input) input += 1 return void function addOneRef(ref input) input += 1 return void a = 1 b = 1 addOneCopy(a) addOneRef(b) print("var a is: ", a, "\n") // <-- should print 1 print("var b is: ", b, "\n") // <-- should print 2 update()
as you can see the first function takes the value of
a
and copies it toinput
then increments that value by 1 and returns out of the function.
the results of which leaving the original value ofa
untouched.the second function takes the location of (aka makes a reference back to )
b
and assigns it toinput
then increments the value stored at the location and returns out of the function.
the result of which increments the value ofb
by 1. -
@thedos
Good point, if the reference remains intact on assignment, then it would make sense to use the ref keyword for primitive data types as well, and then using an int as an example would simplify the example in code :)An off topic side note:
The
++
operator doesn't currently exist in FUZE, and it's arguable if you would want this in a loosely typed language like FUZE. To make my point:var value1 = "1" var value2 = value1++ // what are the values of value1 and value2?
What would be the values after running this code?
value1
could be2
or"11"
or even worse:"2"
value2
could be"1"
or (and although dirty, this expectation is not invalid)1
If the types don't match, there is just a lot going on with these operators that even some professional developers aren't always aware of. So leaving the operator out, even though it is quite convenient to use, seems like an understandable decision in my opinion.
-
@PB____
good catch on++
, guess my head wasn't quite out of c++ syntax. i updated my post to correct that.to answer your question on the incremental operator, it should throw an error. using
++
on a string isn't something that is possible as far as i am aware. although you could probably implement++
most loosely typed languages (python, etc) also utilize the+=
for the reasons you mentioned.for sake of conversation however, if they were both variables were
int
, then in your scenariovalue1
would be2
andvalue2
would in fact be1
. to havevalue2
be2
then it would need to bevalue2 = ++value1
-
guess my head wasn't quite out of c++ syntax
That's why I thought it would be interesting to have this discussion. Since there will be a variety of backgrounds and thus expectations to how things behave. But I also think it will help to improve acceptance to the new language feature.
The natural response when you are confronted with something that looks familiar by association, but does not behave as expected, is disgust. It's a defensive response for situations where something is off, but you're not sure what. Of course "disgust" is a strong word, and we don't normally value by one indicator. I just think it helps to have an open mind towards the update :)
++ operator discussion
I think this is an interesting discussion, especially since FUZE does not have this operator. I do feel that this discussion is off topic, but it seems most natural to respond in here anyway.My argument regarding this operator was indeed within the context of FUZE, that is loosely typed. I absolutely agree that the
++
operator works perfectly well inC++
. But FUZE is an entirely different animal.A problem I see with throwing an error for incorrect types, is that the debugging capabilities in FUZE are not the best in the industry yet. One could also wonder similar questions about floats and even vectors I guess.
Also because FUZE is loosely typed, a mismatch in type could occur in very specific scenarios that might not occur during play testing. Since FUZE in general is used for entertainment, not business applications, it would be more fun if the game would continue with an unforeseen bug, rather than crash with an error. However, during development, you'd want to know that something is wrong.
In JavaScript
var value2 = value1++
would be equivalent to:value1 = Number(value1) var value2 = value1 value1 = value1 + 1
So here the ++ operator would first cast value1 to a Number, before assigning it to value2. Then after assigning to value2, value1 is modified again for the increment. As I said, this feels dirty, but as an expectation by a developer, not invalid.
I guess the scenario
value2 = ++value1
is more simple in any case. Because, after the fact, both value1 and value2 should be equal, both by type and by value.For these reasons, I am, mildly, opposed to introducing the
++
/--
operators at all. But if the operators would be introduced to FUZE, I'd probably use them. But I don't need them.But at this point, I do feel like I'm rambling on and am off topic.
EDIT: I've edited this post a couple of times, but mostly to improve how the message comes across. The message itself generally stayed the same.
I would also like to add, that although I say I am opposed to introducing ++, I would be happy to use it at the same time :-)