[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