The trick is to cheat: come up with the answer I want first, and then work backwards to fill in the rest!
What I do is to add a new step at the beginning of composition. In this step, we start out with a "composition target." We then fold the composition operands' components in two phases: first the ones that are part of the target, and then the rest.
What's the target type? At compile time, it's empty; at run time, it's the compile-time type.
As always, I'll illustrate with an example. In fact, I'll use the same example as before. Here's the background stuff:
type List[A] =
... (put/get)
size -> Int
type Box[A] = ... (put/get)
type Container:
isEmpty -> Bool -- abstract method
type Container Box[A]:
@override isEmpty -> Bool = TODO
intsList : List[Int] = list(1, 2, 3)
intsBox : Box[Int] = box(4)
tmp = intsBox <?> Container
intsContainer : Container = tmp
... and here's the composition:
composed = intsContainer <?> intsList
composedSize = composed2.size
The problem we had before was that
intsContainer
is only a Container
at compile-time, but it's a Box[Int] <?> Container
at runtime. When Container
is composed with List[Int]
at compile time, no conflicts are found, so the resulting type is Container <?> List[Int]
. But at run time, Box[Int]
and List[Int]
collide and cancel each other out, and the resulting object is only a Container
, which doesn't have a size
method.The composition target saves the day. Everything works the same at compile time, leading to a type of
Container <?> List[Int]
. At run time, we take the following steps:- Decompose both operands, so that the composition is
(Box[Int] <?> Container) <?> List[Int]
. - The target type is
Container <?> List[Int]
, so take those components out of the composition and fold them into the resulting object. We now have:- A folded object of
Container <?> List[Int]
- Remaining object of
(Box[Int] <?> ∅) <?> ∅
, which simplifies to justBox[Int]
.
- A folded object of
- Fold the rest of the components (just
Box[Int]
).Box[Int]
collides withList[Int]
, so discard it — and there's nothing else to fold.
So the resulting object is a
Container <?> List[Int]
, which is exactly what we expected at compile time!I wrote above that the target type at compile time is empty, but even that can be improved upon: it's the closest available explicit type declaration, if any. So, if you had:
boxContainer = intsBox <?> Container
composed2 = boxContainer <?> intsList
... then it would result in a compile-time error, since
intsBox : Box[Int]
collides with intsList : Box[Int]
(they both define put
and get
methods). But if you had:composed3 : List[Int] = boxContainer <?> intsList
... then the target type is
List[Int]
, meaning that this gets folded in first. When the conflict with Box[Int]
is detected in the second folding stage, Box[Int]
is discarded. The resulting type of the composition (this is all at compile time, remember) is List[Int] <?> Container
, which is then "upcast" to List[Int]
when it's assigned to composed3
.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.