Support custom default values for pointer fields. #28
Labels
No labels
Blocked on other issue
bug
duplicate
enhancement
good first issue
help wanted
invalid
performance
question
Requires API breakage
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
zenhack/haskell-capnp#28
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Right now we don't support custom default values for pointer fields in structs; fetching the value from a null pointer will always return the default value for the type, even if the field overrides it.
I'm intentionally deferring this for now, since it's fiddly to get right, especially if we don't want to require mutability (the C++ implementation just modifies the message to add the value on first access). Default pointer values are arguably a misfeature, but we should support them eventually for compatibility.
I just pushed
eb33f1f, which causes the schema compiler to just reject schema that use this feature, rather than generating code that does the wrong thing. Per the commit message, almost nobody is using these, so just not supporting them doesn't seem like a horrible strategy; maybe we'll support them someday, but I consider this very low priority.I'm going to switch this from bug to enhancement, given that we're no longer generating incorrect code, but cleanly not supporting the feature.
I have a thought on how to implement this:
ByteString, whose value is that of a capnp segment containing the value, where the first word is the landing pad of a far-pointer pointing to the value.fromByteStringmethod. This was not possible with the oldBlobinterfaces; the new interfaces enable this.invoicethe length of theByteString, since in the case of a mutable message this will make a full copy. In the immutable case it doesn't really matter, sincefromByteStringis O(1) and makes no copy, but it doesn't hurt.Note that this means in the immutable case, the
Untypedvalues would refer to a message other than the one used by the pointer they followed, since we can't add the segment to the message in-place. But I don't think this breaks anything. It may make sense to use thepersistent-vectorpackage instead of boxed vectors fromvectorin this case.Actually, pointing to a different message gets weird if someone tries to use the
HasMessageclass to fetch the underlying message. An alternative would be to point to the same message, but also add to the Struct/ListOf/etc types a direct pointer to the segment (which needn't be part of the message). This may also incidentally help with performance. But this way we can still return the correct message (which will be unmodified, and thus the null pointer that brought us to the default value will still have the correct value), and still get O(1) access for the value (as opposed to the O(n) copy needed by the mutable implementation and the implementations in other languages).capnp 0.7 adds a default value for a Text field in json.capnp, so we now can't upgrade without at least removing the hard-reject.
The default value is just
"", which we're actually reading a null pointer as anyway.Started working on this on a branch.
Current status: I'm hitting a snarl re: how to actually implement the different behaviors described above. It's not possible to do so as an independent function, because of parametricity. We could add a function to the Message type class, but I find that very unsatisfying.
The root of the problem here is that the message types are on the wrong side of the expression problem; we really only intend to ever have ConstMsg and MutMsg s, but we've structured things in a way where it's easier to add new
Messageinstances than new functions that work on all messages.It would be possible to swap out the message type class and separate types with a GADT:
This results in a much cleaner implementation than anything else I can come up with. However making the change is going to touch a very large percentage of the code in the libraries and generated code. I don't think it will actually be hard to do, just tedious. It would be a nightmare without a type system, and I don't think I'd even consider it, but we've got one.
I want to sleep on this a few more nights before pulling the trigger, and I think I want to get the rpc stuff merged so I don't have to deal with conflicts everywhere -- so I'm tabling this issue for the time being.
We'd still need some kind of parametrization (probably still a type class) to deal with the fact that we've got different constraints on the monad for the instances; MutMsg current requires PrimMonad. We also would need to export the data constructors to be able to actually take advantage of this change. hmm...
I'm tempted to change the error that the code generator emits to a warning and generate constants, as an intermediate step; this would allow at least using schema that use this feature at all, which would unblock a few things.
For a longer term solution, the best thing I can come up with is to add a method to the
Messagetype class like:I'm not in love with how the conditional logic for the two instances is in the type class's contract, but per the above the whole thing is a little weird given that we have a type class that is really only intended to ever have two instances. Everything else I've sketched is far more complex.
It still has the problem that in the ConstMsg case, it would no longer be pointing at the same message.
I actually counted, and between the 0.7 capnproto core schema and the current sandstorm schema (~9000 SLOC in .capnp schema), this feature is used exactly twice. In both cases, the field is of type Text and the custom default value is the empty string. We already decode null Texts as empty string; the only way to distinguish is to use the
has_*functions.I've pushed a commit that downgrades the error we were generating to a warning; it will still generate code now. It also points you to exactly which field is the problem.
I'm planning on closing this as WONTFIX; the only question left in my mind is whether we should specifically look for
... :Text = ""and not complain at all in those cases, or leave it as-is. Need to sleep on that.