Skip to content

Commit f916b31

Browse files
authored
Move Abstract Methods section out of main proposal into alternatives
1 parent f2b5b35 commit f916b31

File tree

1 file changed

+115
-114
lines changed

1 file changed

+115
-114
lines changed

content/unroll-default-arguments.md

Lines changed: 115 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -464,120 +464,6 @@ take into account field default values, and this change is necessary to make it
464464
use them when the given `p: Product` has a smaller `productArity` than the current
465465
`CaseClass` implementation
466466

467-
### Abstract Methods
468-
469-
Apart from `final` methods, `@unroll` also supports purely abstract methods. Consider
470-
the following example with a trait `Unrolled` and an implementation `UnrolledObj`:
471-
472-
```scala
473-
trait Unrolled{ // version 3
474-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String
475-
}
476-
```
477-
```scala
478-
object UnrolledObj extends Unrolled{ // version 3
479-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b
480-
}
481-
```
482-
483-
This unrolls to:
484-
```scala
485-
trait Unrolled{ // version 3
486-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b)
487-
def foo(s: String, n: Int, b: Boolean): String = foo(s, n)
488-
def foo(s: String, n: Int): String
489-
}
490-
```
491-
```scala
492-
object UnrolledObj extends Unrolled{ // version 3
493-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l
494-
def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0)
495-
def foo(s: String, n: Int) = foo(s, n, true)
496-
}
497-
```
498-
499-
Note that both the abstract methods from `trait Unrolled` and the concrete methods
500-
from `object UnrolledObj` generate forwarders when `@unroll`ed, but the forwarders
501-
are generated _in opposite directions_! Unrolled concrete methods forward from longer
502-
parameter lists to shorter parameter lists, while unrolled abstract methods forward
503-
from shorter parameter lists to longer parameter lists. For example, we may have a
504-
version of `object UnrolledObj` that was compiled against an earlier version of `trait Unrolled`:
505-
506-
507-
```scala
508-
object UnrolledObj extends Unrolled{ // version 2
509-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b
510-
def foo(s: String, n: Int) = foo(s, n, true)
511-
}
512-
```
513-
514-
But further downstream code calling `.foo` on `UnrolledObj` may expect any of the following signatures,
515-
depending on what version of `Unrolled` and `UnrolledObj` it was compiled against:
516-
517-
```scala
518-
UnrolledObj.foo(String, Int)
519-
UnrolledObj.foo(String, Int, Boolean)
520-
UnrolledObj.foo(String, Int, Boolean, Long)
521-
```
522-
523-
Because such downstream code cannot know which version of `Unrolled` that `UnrolledObj`
524-
was compiled against, we need to ensure all such calls find their way to the correct
525-
implementation of `def foo`, which may be at any of the above signatures. This "double
526-
forwarding" strategy ensures that regardless of _which_ version of `.foo` gets called,
527-
it ends up eventually forwarding to the actual implementation of `foo`, with
528-
the correct combination of passed arguments and default arguments
529-
530-
```scala
531-
UnrolledObj.foo(String, Int) // forwards to UnrolledObj.foo(String, Int, Boolean)
532-
UnrolledObj.foo(String, Int, Boolean) // actual implementation
533-
UnrolledObj.foo(String, Int, Boolean, Long) // forwards to UnrolledObj.foo(String, Int, Boolean)
534-
```
535-
536-
As is the case for `@unroll`ed methods on `trait`s and `class`es, `@unroll`ed
537-
implementations of an abtract method must be final.
538-
539-
#### Are Reverse Forwarders Really Necessary?
540-
541-
This "double forwarding" strategy is not strictly necessary to support
542-
[Backwards Compatibility](#backwards-compatibility): the "reverse" forwarders
543-
generated for abstract methods are only necessary when a downstream callsite
544-
of `UnrolledObj.foo` is compiled against a newer version of the original
545-
`trait Unrolled` than the `object UnrolledObj` was, as shown below:
546-
547-
```scala
548-
trait Unrolled{ // version 3
549-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b)
550-
// generated
551-
def foo(s: String, n: Int, b: Boolean): String = foo(s, n)
552-
def foo(s: String, n: Int): String
553-
}
554-
```
555-
```scala
556-
object UnrolledObj extends Unrolled{ // version 2
557-
def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b
558-
// generated
559-
def foo(s: String, n: Int) = foo(s, n, true)
560-
}
561-
```
562-
```scala
563-
// version 3
564-
UnrolledObj.foo("hello", 123, true, 456L)
565-
```
566-
567-
If we did not have the reverse forwarder from `foo(String, Int, Boolean, Long)` to
568-
`foo(String, Int, Boolean)`, this call would fail at runtime with an `AbstractMethodError`.
569-
It also will get caught by MiMa as a `ReversedMissingMethodProblem`.
570-
571-
This configuration of version is not allowed given our definition of backwards compatibility:
572-
that definition assumes that `Unrolled` must be of a greater or equal version than `UnrolledObj`,
573-
which itself must be of a greater or equal version than the final call to `UnrolledObj.foo`. However,
574-
the reverse forwarders are needed to fulfill our requirement
575-
[All Overrides Are Equivalent](#all-overrides-are-equivalent):
576-
looking at `trait Unrolled // version 3` and `object UnrolledObj // version 2` in isolation,
577-
we find that without the reverse forwarders the signature `foo(String, Int, Boolean, Long)`
578-
is defined but not implemented. Such an un-implemented abstract method is something
579-
we want to avoid, even if our artifact version constraints mean it should technically
580-
never get called.
581467

582468
### Hiding Generated Forwarder Methods
583469

@@ -811,6 +697,121 @@ evolution. It does so with the same Scala-level syntax and semantics, with the s
811697
and limitations that common schema/API/protocol-evolution best-practices have in the broader
812698
software engineering community.
813699

700+
### Abstract Methods
701+
702+
Apart from `final` methods, `@unroll` also supports purely abstract methods. Consider
703+
the following example with a trait `Unrolled` and an implementation `UnrolledObj`:
704+
705+
```scala
706+
trait Unrolled{ // version 3
707+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String
708+
}
709+
```
710+
```scala
711+
object UnrolledObj extends Unrolled{ // version 3
712+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b
713+
}
714+
```
715+
716+
This unrolls to:
717+
```scala
718+
trait Unrolled{ // version 3
719+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b)
720+
def foo(s: String, n: Int, b: Boolean): String = foo(s, n)
721+
def foo(s: String, n: Int): String
722+
}
723+
```
724+
```scala
725+
object UnrolledObj extends Unrolled{ // version 3
726+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l
727+
def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0)
728+
def foo(s: String, n: Int) = foo(s, n, true)
729+
}
730+
```
731+
732+
Note that both the abstract methods from `trait Unrolled` and the concrete methods
733+
from `object UnrolledObj` generate forwarders when `@unroll`ed, but the forwarders
734+
are generated _in opposite directions_! Unrolled concrete methods forward from longer
735+
parameter lists to shorter parameter lists, while unrolled abstract methods forward
736+
from shorter parameter lists to longer parameter lists. For example, we may have a
737+
version of `object UnrolledObj` that was compiled against an earlier version of `trait Unrolled`:
738+
739+
740+
```scala
741+
object UnrolledObj extends Unrolled{ // version 2
742+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b
743+
def foo(s: String, n: Int) = foo(s, n, true)
744+
}
745+
```
746+
747+
But further downstream code calling `.foo` on `UnrolledObj` may expect any of the following signatures,
748+
depending on what version of `Unrolled` and `UnrolledObj` it was compiled against:
749+
750+
```scala
751+
UnrolledObj.foo(String, Int)
752+
UnrolledObj.foo(String, Int, Boolean)
753+
UnrolledObj.foo(String, Int, Boolean, Long)
754+
```
755+
756+
Because such downstream code cannot know which version of `Unrolled` that `UnrolledObj`
757+
was compiled against, we need to ensure all such calls find their way to the correct
758+
implementation of `def foo`, which may be at any of the above signatures. This "double
759+
forwarding" strategy ensures that regardless of _which_ version of `.foo` gets called,
760+
it ends up eventually forwarding to the actual implementation of `foo`, with
761+
the correct combination of passed arguments and default arguments
762+
763+
```scala
764+
UnrolledObj.foo(String, Int) // forwards to UnrolledObj.foo(String, Int, Boolean)
765+
UnrolledObj.foo(String, Int, Boolean) // actual implementation
766+
UnrolledObj.foo(String, Int, Boolean, Long) // forwards to UnrolledObj.foo(String, Int, Boolean)
767+
```
768+
769+
As is the case for `@unroll`ed methods on `trait`s and `class`es, `@unroll`ed
770+
implementations of an abtract method must be final.
771+
772+
#### Are Reverse Forwarders Really Necessary?
773+
774+
This "double forwarding" strategy is not strictly necessary to support
775+
[Backwards Compatibility](#backwards-compatibility): the "reverse" forwarders
776+
generated for abstract methods are only necessary when a downstream callsite
777+
of `UnrolledObj.foo` is compiled against a newer version of the original
778+
`trait Unrolled` than the `object UnrolledObj` was, as shown below:
779+
780+
```scala
781+
trait Unrolled{ // version 3
782+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b)
783+
// generated
784+
def foo(s: String, n: Int, b: Boolean): String = foo(s, n)
785+
def foo(s: String, n: Int): String
786+
}
787+
```
788+
```scala
789+
object UnrolledObj extends Unrolled{ // version 2
790+
def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b
791+
// generated
792+
def foo(s: String, n: Int) = foo(s, n, true)
793+
}
794+
```
795+
```scala
796+
// version 3
797+
UnrolledObj.foo("hello", 123, true, 456L)
798+
```
799+
800+
If we did not have the reverse forwarder from `foo(String, Int, Boolean, Long)` to
801+
`foo(String, Int, Boolean)`, this call would fail at runtime with an `AbstractMethodError`.
802+
It also will get caught by MiMa as a `ReversedMissingMethodProblem`.
803+
804+
This configuration of version is not allowed given our definition of backwards compatibility:
805+
that definition assumes that `Unrolled` must be of a greater or equal version than `UnrolledObj`,
806+
which itself must be of a greater or equal version than the final call to `UnrolledObj.foo`. However,
807+
the reverse forwarders are needed to fulfill our requirement
808+
[All Overrides Are Equivalent](#all-overrides-are-equivalent):
809+
looking at `trait Unrolled // version 3` and `object UnrolledObj // version 2` in isolation,
810+
we find that without the reverse forwarders the signature `foo(String, Int, Boolean, Long)`
811+
is defined but not implemented. Such an un-implemented abstract method is something
812+
we want to avoid, even if our artifact version constraints mean it should technically
813+
never get called.
814+
814815
## Minor Alternatives:
815816

816817

0 commit comments

Comments
 (0)