Skip to content

Commit fb9d4e9

Browse files
committed
Address Erik's feedback
1 parent 1167412 commit fb9d4e9

File tree

1 file changed

+78
-142
lines changed

1 file changed

+78
-142
lines changed

doc/1.4/language.md

Lines changed: 78 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -3447,195 +3447,131 @@ the implementations are provided by hierarchically unrelated templates such that
34473447
`default` can't be used (see [Resolution of
34483448
overrides](#resolution-of-overrides).) In particular, this typically allows for
34493449
ergonomically resolving conflicts introduced when multiple orthogonal templates
3450-
are instantiated, as long as all conflicting implementations are overridable,
3451-
and one of the following is true:
3452-
* The implementations can be combined together by calling each one of them, as
3453-
long as that can be done without risking e.g. side-effects being duplicated.
3454-
* The implementations in all but (optionally) one of the involved templates call
3455-
to a "base" method that would serve the same purpose as `default`, but whose
3456-
definition may be *overridden* such that the conflicting implementations can
3457-
be resolved by overriding these base methods and leveraging template-qualified
3458-
method implementation calls in order to define the chain of how each
3459-
implementation gets called.
3460-
3461-
This requires that the names of the base methods are unique to each template
3462-
involved, and also some thought as to how the implementations in the chain
3463-
should be ordered.
3464-
3465-
If the implementations in some templates call `default` instead of an
3466-
overridable base method, consider modifying those templates (if possible) to
3467-
each use a base method instead — a template-qualified
3468-
method implementation call can be used as the default implementation of each
3469-
base method in order to make it act like `default` in the typical case where
3470-
no conflicting templates are in play.
3471-
* The implementations can be combined by choosing one particular template's
3472-
implementation to invoke (typically the one most complex), and then adding
3473-
code around that implementation call in order to replicate the behaviour of
3474-
the implementations of the other templates. Ideally, the other templates would
3475-
provide methods that may be leveraged so that their behaviour may be
3476-
replicated without the need for excessive boilerplate.
3477-
3478-
These cases are ordered by difficulty to resolve in ascending order. In
3479-
practice, most conflicts between unrelated templates can be resolved by
3480-
modifying the templates such that the second case may apply.
3450+
are instantiated, as long as all conflicting implementations are overridable.
34813451

3482-
> [!NOTE]
3483-
> The examples given below apply regardless of whether the method
3484-
> implementations in the original templates are `shared` or not. However, the
3485-
> implementations in the template defined to resolve the conflicts may need to
3486-
> be non-`shared` if some of the implementations involved are not `shared`;
3487-
> see the final paragraph of this subsection.
3488-
3489-
The following is an example of the first case:
3452+
The following example demonstrates the most common kind of conflict that
3453+
hierarchically unrelated templates may introduce, and how template-qualified
3454+
method implementation calls may be leveraged to resolve it. Consider the
3455+
following templates:
34903456
```
3491-
template alter_write is write {
3492-
method write(uint64 written) {
3493-
default(alter_write(written));
3494-
}
3495-
3496-
shared method alter_write(uint64 curr, uint64 written) -> (uint64);
3497-
}
3498-
3499-
template gated_write is alter_write {
3500-
method write_allowed() -> (bool) default {
3457+
template gated_write is write {
3458+
method write_allowed(uint64 val) -> (bool) default {
35013459
return true;
35023460
}
35033461
3504-
method alter_write(uint64 curr, uint64 written) -> (uint64) default {
3505-
return write_allowed() ? written : curr;
3506-
}
3507-
}
3508-
3509-
template write_1_clears is alter_write {
3510-
method alter_write(uint64 curr, uint64 written) -> (uint64) default {
3511-
return curr & ~written;
3462+
method write(uint64 val) default {
3463+
if (write_allowed(val)) {
3464+
default(val);
3465+
}
35123466
}
35133467
}
35143468
3515-
template gated_write_1_clears is (gated_write, write_1_clears) {
3516-
method alter_write(uint64 curr, uint64 written) default {
3517-
local uint64 new = this.templates.write_1_clears.alter_write(
3518-
curr, written);
3519-
return this.templates.gated_write.alter_write(curr, new);
3469+
template write_1_clears is write {
3470+
method write(uint64 val) default {
3471+
default(get() & ~val);
35203472
}
35213473
}
3522-
3523-
// Resolve the conflict introduced whenever the two orthogonal templates are
3524-
// instantiated by also instantiating gated_write_1_clears when that happens
3525-
in each (gated_write, write_1_clears) { is gated_write_1_clears; }
35263474
```
35273475

3528-
The following is an example of the second case:
3476+
If one would like to instantiate both templates for a particular `field`,
3477+
attempting to do so would cause DMLC to reject the model, as the choice of
3478+
`write` implementation then becomes ambiguous.
3479+
The typical solution to implementation conflicts between templates —
3480+
making one template inherit from the other — is not appropriate in this
3481+
situation, as the operation of each template is orthogonal to the other,
3482+
and they may be used individually in other contexts.
3483+
3484+
Instead, what one may do is to modify one or both templates to offer an
3485+
overridable "base" method that is called instead of `default` within the
3486+
template's implementation of `write`. This additional flexibility enables a way
3487+
to situationally resolve the conflict: if both templates are in play, override
3488+
the base method of one template to call the `write` implementation of the
3489+
other. This effectively defines the chain in which the conflicting
3490+
implementations are to be called from one another, combining their behaviour.
3491+
3492+
The default implementations of the base methods can be to invoke the `write`
3493+
implementation of their parent template, which makes calling them the same as
3494+
calling `default`. This captures the regular case where no conflicting templates
3495+
are in play.
3496+
3497+
The below shows this approach being applied to the example above, modifying
3498+
`gated_write` to offer a base method, and leveraging a new template and an
3499+
`in each` declaration to automatically resolve the conflict wherever it would
3500+
occur in the model.
35293501
```
35303502
template gated_write is write {
3531-
method write_allowed() -> (bool) default {
3503+
method write_allowed(uint64 val) -> (bool) default {
35323504
return true;
35333505
}
35343506
35353507
method write(uint64 val) default {
3536-
if (write_allowed()) {
3537-
base_write_of_gated_write(val); // instead of calling default()
3508+
if (write_allowed(val)) {
3509+
base_write_of_gated_write(val); // instead of default()
35383510
}
35393511
}
35403512
35413513
method base_write_of_gated_write(uint64 val) default {
3542-
// If no conflicting template is in play such as to override this
3543-
// base method, then its behavior is as though the write implementation
3544-
// called default() instead
3514+
// This is the implementation that default() would have resolved to
35453515
this.templates.write.write(val);
35463516
}
35473517
}
35483518
3549-
template write_1_clears is (write, get) {
3550-
method write(uint64 val) default {
3551-
default(get() & ~val);
3552-
}
3519+
template write_1_clears is (write, get) { // unchanged
3520+
...
35533521
}
35543522
35553523
template gated_write_1_clears is (gated_write, write_1_clears) {
3524+
// Resolve the conflict by providing an unambiguously most specific
3525+
// write implementation
35563526
method write(uint64 val) default {
35573527
// This makes gated_write the first link in the call chain...
35583528
this.templates.gated_write.write(val);
35593529
}
35603530
3561-
// And by overriding gated_write's base method, we make write_1_clears
3531+
// ... and by overriding gated_write's base method, we make write_1_clears
35623532
// the second (and final) link in the chain
3563-
//
3564-
// If even more conflicting templates could be in play, then one could
3565-
// consider converting write_1_clears to also leverage a base method
3566-
// instead of calling default and extend the call chain; on the other
3567-
// hand, the way write_1_clears manipulates the written value may violate
3568-
// the expectations of implementations later on in the chain, so it might
3569-
// make most sense to always place write_1_clears as the final link, and
3570-
// have all other templates leverage base methods instead.
35713533
method base_write_of_gated_write(uint64 val) default {
35723534
this.templates.write_1_clears.write(val);
35733535
}
35743536
}
35753537
35763538
in each (gated_write, write_1_clears) { is gated_write_1_clears; }
35773539
```
3540+
> [!NOTE]
3541+
> The example given above applies regardless of whether the method
3542+
> implementations in the original templates are `shared` or not. However, the
3543+
> implementations in the template defined to resolve the conflicts might need to
3544+
> be non-`shared` if some of the implementations involved are not `shared`;
3545+
> see the final paragraph of this subsection.
35783546
3579-
The following is an example of the third case:
3580-
```
3581-
template very_complex_register is register {
3582-
method write_register(uint64 written, uint64 enabled_bytes,
3583-
void *aux) default {
3584-
... // An extremely complicated implementation
3585-
}
3586-
}
3587-
3588-
template gated_register is register {
3589-
method write_allowed() -> (bool) default {
3590-
return true;
3591-
}
3592-
3593-
method on_write_attempted_when_not_allowed() default {
3594-
log spec_viol: "%s was written to when not allowed", qname;
3595-
}
3596-
3597-
method write_register(uint64 written, uint64 enabled_bytes,
3598-
void *aux) default {
3599-
if (write_allowed()) {
3600-
default(written, enabled_bytes, aux);
3601-
} else {
3602-
on_write_attempted_when_not_allowed();
3603-
}
3604-
}
3605-
}
3606-
3607-
template very_complex_gated_register is (very_complex_register,
3608-
gated_register) {
3609-
// No sensible way to combine the two implementations by calling both.
3610-
// Even if there were, calling both implementations would cause each field
3611-
// of the register to be written to multiple times, potentially duplicating
3612-
// side-effects, which is undesirable.
3613-
// Instead, very_complex_register is chosen as the base implementation
3614-
// called, and the behaviour of gated_register is replicated around that
3615-
// call.
3616-
method write_register(uint64 written, uint64 enabled_bytes,
3617-
void *aux) default {
3618-
if (write_allowed()) {
3619-
this.templates.very_complex_register.write_register(
3620-
written, enabled_bytes, aux);
3621-
} else {
3622-
on_write_attempted_when_not_allowed();
3623-
}
3624-
}
3625-
}
3626-
3627-
in each (gated_register, very_complex_register) {
3628-
is very_complex_gated_register;
3629-
}
3630-
```
3547+
This approach can be applied to resolve conflicts across more than two
3548+
conflicting templates; however, this scales poorly if used to support arbitrary
3549+
combinations of many conflicting templates. It should instead be applied only
3550+
for the specific conflicts that become observed and can't easily be resolved
3551+
through other means.
3552+
3553+
Note that the order of implementations in the call chain might matter. If, in
3554+
the example above, `write_1_clears` were instead modified to offer a base method
3555+
and been made the first link in the chain, then what it would have passed down
3556+
as the written value to `gated_write` would not have been the original value
3557+
— but rather, the register's current value with bits cleared, which might
3558+
violate the expectations of `gated_write`'s implementation.
3559+
3560+
The approach given above is not the only way in which template-qualified method
3561+
implementation calls may be utilized to resolve conflicts, only the most
3562+
commonly applicable one. In fact, the most simple approach, if viable, is to
3563+
have the implementation introduced to resolve the conflict simply call each
3564+
conflicting implementation in turn, if that doesn't cause side-effects to be
3565+
duplicated in an undesirable way. It may even be viable to call only one
3566+
particular implementation, if it makes sense to prefer it ahead of every other.
36313567

36323568
A template-qualified method implementation call is resolved by using
36333569
the method implementation provided to the object by the named template.
3634-
If no such implementation is provided (whether it be because the template does
3635-
not specify one, or specifies one which is not provided to the object due to its
3570+
If no such implementation exists (whether it be because the template does not
3571+
specify one, or specifies one which is not provided to the object due to its
36363572
definition being eliminated by an [`#if`](#conditional-objects)), then the
36373573
ancestor templates of the named template are recursively searched for the
3638-
highest-rank (most specific) implementation provided by them. If the ancestor
3574+
highest-ranking (most specific) implementation provided by them. If the ancestor
36393575
templates provide multiple hierarchically unrelated implementations, then the
36403576
choice is ambiguous and the call will be rejected by the compiler. In this case,
36413577
the modeller must refine the template-qualified method implementation call to
@@ -3644,7 +3580,7 @@ name the ancestor template whose implementation they would like to use.
36443580
A template-qualified method implementation call done via [a value of template
36453581
type](#templates-as-types) functions differently compared to compile-time
36463582
object references. In particular, `this.templates` within the bodies of `shared`
3647-
methods functions differently. The specified template must be an ancestor
3583+
methods functions differently. The specified template must either be an ancestor
36483584
template of the value's template type, the <tt>object</tt> template, or the
36493585
template type itself; furthermore, the specified template **must provide or
36503586
inherit a `shared` implementation of the named method**. It is not sufficient

0 commit comments

Comments
 (0)