[Development] Redesigning QML value types
Volker Hilsheimer
volker.hilsheimer at qt.io
Wed Sep 21 10:27:09 CEST 2022
Thanks for writing this up, Ulf!
Working on porting Qt Location to Qt 6 right now, I’m looking very much forward to the improved support for value types :)
> On 20 Sep 2022, at 18:13, Ulf Hermann <ulf.hermann at qt.io> wrote:
>
> Hi,
>
> I'm currently trying to transform QML value types, such as font, rect,
> size, etc, into something less random and more predictable. On that
> occasion I'm wondering what semantics people actually expect from value
> types.
[…]
> 1. Value types are passed by value
> —————————————————
[…]
> However, since we've never implemented value type references for lists,
> lists of value types behave exactly this way. The following does in fact
> not modify the list, but only a copy of the object at index 10:
>
> property list<font> fonts: [ ... ]
> Component.onCompleted: fonts[10].pointSize = 33
>
> The same holds for nested value types. Assume a value type "outer" that
> has a property "a" of another value type, which has an integer property
> "b". The following does not write back:
>
> property outer o
> Component.onCompleted: o.a.b = 10
Not a fan. Within a scope, or at least within a statement, operating on copies is unexpected and confusing.
Esp with nested lists of structured value types, ie.
o.a[1].b[10] = 10
That turns into a lot of code when write-back has to be done explicitly.
> 2. Everything is a reference
> ----------------------------
>
> JavaScript doesn't really have value types. The doEvil() function thus
> receives a reference to the original 'f', not a copy. The modification
> of pointSize is written back to the original property.
>
> With font you might still wrap your head around it because font "looks
> like" an object with all its properties and internal logic. However, you
> can also pass a point or a rect around through various functions, and
> whenever you modify it, the result would be written back to the original
> place where it was retrieved from, possibly in a different file half an
> hour ago. I personally find that rather confusing.
Agree. It might be more natural for a Python, Ruby, or JavaScript developer, but I’d much prefer functions not to have implicit side effects by default. With EcmaScript 6 we can use destructuring assignment syntax to work with functions that return multiple values through an array or object, which makes it reasonably convenient to return multiple values from a function.
> A middle ground would be that value types are passed by value when
> calling or returning from a function, but are treated as references and
> written back on modification inside a function. Inside the same function
> qmlsc and qmlcachegen could keep track of where a value was loaded from
> and write it back when it's modified. I've played with the concept a
> bit, and it's possible to support nested value types and lists of value
> types this way. All the writing back would still be inefficient, though.
I like that approach, it’s easily defined when the value is copied, and I think it does the right thing in most cases.
Taking this one step further, could we treat lvalues as references:
// o.a.b gets modified
o.a.b = 10
But always copy rvalues:
// o.a.b does not get modified
var old_b = o.a.b // copy of rvalue
old_b = 15
// call_function gets a copy of o.a.b, which then gets modified explicitly
o.a.b = call_function(o.a.b)
?
> In addition to whatever course is chosen, we might add a detach() method
> on the JavaScript prototype used for all value types. With that you
> could intentionally detach a value from the property it was loaded from.
Makes sense. It would solve the problem that modifying several attributes of a value would require many (slow) write-backs.
Volker
More information about the Development
mailing list