diff --git a/_sips/sips/2017-11-20-byname-implicits.md b/_sips/sips/2017-11-20-byname-implicits.md new file mode 100644 index 0000000000..d07dc197c4 --- /dev/null +++ b/_sips/sips/2017-11-20-byname-implicits.md @@ -0,0 +1,780 @@ +--- +layout: sip +discourse: true +title: SIP-NN - Byname implicit arguments + +vote-status: pending +permalink: /sips/:title.html +--- + +**Author: Miles Sabin** + +**Supervisor and advisor: Adriaan Moors** + +## History + +| Date | Version | +| ---------------|--------------------------------------------------------------------| +| Nov 20th 2017 | Initial SIP | + +## Introduction + +This SIP proposes extending the support for byname method arguments from just explicit arguments to +both explicit and _implicit_ arguments. + +The aim is to support similar use cases to shapeless's `Lazy` type, but without having to rely on a +third party library or fragile and non-portable macros. + +The primary use case for shapeless's `Lazy`, and the byname implicit arguments described below, is to +enable the implicit construction of recursive values. This has proven to be a vital capability for +type class derivation, where a type class instance for a recursive data type must typically itself be +recursive. For such a value to be constructible via implicit resolution it must be possible to "tie +the knot" implicitly. + +### Implementation status + +Byname implicits have been implemented in [Dotty](https://github.com/lampepfl/dotty/issues/1998) +with an earlier iteration of the divergence checking algorithm described below. A full +implementation of this proposal exists as a [pull request](https://github.com/scala/scala/pull/6050) +relative to the 2.13.x branch of the Lightbend Scala compiler and it is scheduled to be included in +the next [Typelevel Scala](https://github.com/typelevel/scala) release. As of [this +comment](https://github.com/scala/scala/pull/6050#issuecomment-347814587) the Scala and Dotty +implementations compile their test cases equivalently. + +## Proposal + +### Proposal summary + +This SIP proposes allowing implicit arguments to be marked as byname. At call sites they will be +eligible as parameters in recursively nested positions within their own implicit expansions. + +Consider the following example, + +```scala +trait Foo { + def next: Foo +} + +object Foo { + implicit def foo(implicit rec: Foo): Foo = + new Foo { def next = rec } +} + +val foo = implicitly[Foo] +assert(foo eq foo.next) +``` + +This diverges due to the recursive implicit argument `rec` of method `foo`. This SIP allows us to +mark the recursive implicit parameter as byname, + +```scala +trait Foo { + def next: Foo +} + +object Foo { + implicit def foo(implicit rec: => Foo): Foo = + new Foo { def next = rec } +} + +val foo = implicitly[Foo] +assert(foo eq foo.next) +``` + +When compiled, recursive byname implicit arguments of this sort are extracted out as `lazy val` +members of a local synthetic object at call sites as follows, + +```scala +val foo: Foo = scala.Predef.implicitly[Foo]( + { + object LazyDefns$1 { + lazy val rec$1: Foo = Foo.foo(rec$1) + // ^^^^^ + // recursive knot tied here + } + LazyDefns$1.rec$1 + } +) +assert(foo eq foo.next) +``` + +and the example compiles with the assertion successful. + +This general pattern is essential to the derivation of type class instances for recursive data +types, one of shapeless's most common applications. + +Byname implicits have a number of benefits over the macro implementation of `Lazy` in shapeless, + ++ the implementation of `Lazy` in shapeless is extremely delicate, relying on non-portable compiler + internals. As a language feature, byname implicits are more easily portable to other + compilers. + ++ the shapeless implementation is unable to modify divergence checking, so to solve recursive + instances it effectively disables divergence checking altogether. This means that incautious use + of `Lazy` can cause the typechecker to loop indefinitely. A byname implicits implementation is + able to both solve recursive occurrences and check for divergence. + ++ the implementation of `Lazy` interferes with the heuristics for solving inductive implicits in + this [Scala PR](https://github.com/scala/scala/pull/5649) because the latter depends on being able + to verify that induction steps strictly reduce the size of the types being solved for; the + additional `Lazy` type constructors make the type appear be non-decreasing in size. Whilst this + could be special-cased, doing so would require some knowledge of shapeless to be incorporated into + the compiler. Being a language-level feature, byname implicits can be accommodated directly in the + induction heuristics. + ++ in common cases more implicit arguments would have to be marked as `Lazy` than would have to be + marked as byname, due to limitations of macros. Given that there is a runtime cost associated with + capturing the thunks required for both `Lazy` and byname arguments, any reduction in the number is + beneficial. + +### Motivating examples + +Type class derivation is a technique for inferring instances of type classes for ADTs from a set of +primitive instances, and rules for combining them which are driven by the structure of the ADT. For +example, `Semigroup` is a type class which expresses that a type has an associative binary operation, + +```scala +trait Semigroup[A] { + def combine(x: A, y: A): A +} +``` + +If we have instances for basic types, + +```scala +object Semigroup { + implicit val intSemigroup: Semigroup[Int] = + new Semigroup[Int] { + def combine(x: Int, y: Int): Int = x + y + } + + implicit val stringSemigroup: Semigroup[String] = + new Semigroup[String] { + def combine(x: String, y: String): String = x + y + } + + implicit val unitSemigroup: Semigroup[Unit] = + new Semigroup[Unit] { + def combine(x: Unit, y: Unit): Unit = () + } +} +``` + +then we can manually write instances for, for example, tuples of types which have `Semigroup` +instances, + +```scala +implicit def tuple2Semigroup[A, B] + (implicit + sa: Semigroup[A], + sb: Semigroup[B]): + Semigroup[(A, B)] = + new Semigroup[(A, B)] { + def combine(x: (A, B), y: (A, B)): (A, B) = + (sa.combine(x._1, y._1), + sb.combine(x._2, y._2)) + } + +implicit def tuple3Semigroup[A, B, C] + (implicit + sa: Semigroup[A], + sb: Semigroup[B], + sc: Semigroup[C]): + Semigroup[(A, B, C)] = + nee Semigroup[(A, B, C)] { + def combine(x: (A, B, C), y: (A, B, C)): (A, B, C) = + (sa.combine(x._1, y._1), + sb.combine(x._2, y._2), + sc.combine(x._3, y._3)) + } + +// etc. ... +``` + +And we could round this out for all case classes, which have the same product-like structure. Of +course doing this manually requires huge amounts of repetitive boilerplate. + +Type class derivation is a mechanism for eliminating this boilerplate. The approach taken in +shapeless is to map ADTs to a sum of products representation (essentially a nested `Either` of +nested pairs), and define type class instances in terms of the representation type. + +shapeless provides a a type class `Generic` (see [Appendix 1](#appendix-1--shapeless-excerpts) for +its signature) and instances taking product types to nested pairs and sealed traits to nested +`Eithers` (while shapeless provides instances of this type class via a macro, that is independent +from this SIP, and any similar mechanism might be used) which we can use to provide instances for +arbitrary type classes without needing boilerplate for specific ADTs. + +For type classes like `Semigroup` which are meaningful for types which only have a product structure +this is straightforward, + +```scala +implicit def genericSemigroup[T, R] + (implicit + gen: Generic.Aux[T, R] + sr: Semigroup[R]): + Semigroup[T] = + new Semigroup[T] { + def combine(x: T, y: T): T = + gen.from(sr.combine(gen.to(x), gen.to(y))) + } +} + +// A case class with a Generic instance +case class Foo(i: Int, s: String) + +implicitly[Semigroup[Foo]] +``` + +A `Semigroup` instance for `Foo` is constructed by implicit resolution as follows, + +```scala +genericSemigroup( + generic[Foo], // type R inferred as (Int, (String, Unit)) + tuple2Semigroup( + intSemigroup, + tuple2Semigroup( + stringSemigroup, + unitSemigroup + ) + ) +) +``` + +Intuitively we are confident that the nested implicit resolutions will not diverge because once we +have mapped into the tuple representation type `(Int, (String, Unit))` each nested step of the +implicit resolution reduces the size of the required type. + +The need for shapeless's `Lazy` or byname implicit arguments only becomes apparent when we want to +derive type class instances for recursive ADTs. These come into play when we consider types which +are sums of products rather than just simple products. We can use a basic cons-list as an example, + +```scala +sealed trait List[+T] +case class Cons[T](hd: T, tl: List[T]) extends List[T] +case object Nil extends List[Nothing] +``` + +Here our data type, `List`, is the sum of two product types, `Cons` and `Nil`. The `Cons` constructor +contains a recursive occurrence of `List` as its tail. Working through a simple type class +derivation will illustrate a new issue to be solved. + +Let's attempt to derive a `Show` type class instance for `List` similarly to the way we derived +`Semigroup` above. In this case `Generic` will map `List` and its constructors as follows, + +```scala +List[T] -> Either[Cons[T], Either[Nil.type, Unit]] +Cons[T] -> (T, (List[T], Unit)) +Nil.type -> Unit +``` + +We define instances for the basic types, pairs, `Either` and types with a `Generic` instance like +so, + +```scala +trait Show[T] { + def show(x: T): String +} + +object Show { + def apply[T](implicit st: Show[T]): Show[T] = st + + implicit val showInt: Show[Int] = new Show[Int] { + def show(x: Int): String = x.toString + } + + implicit val showString: Show[String] = new Show[String] { + def show(x: String): String = x + } + + implicit val showUnit: Show[Unit] = new Show[Unit] { + def show(x: Unit): String = "" + } + + implicit def showPair[T, U] + (implicit + st: Show[T], + su: Show[U]): + Show[(T, U)] = new Show[(T, U)] { + def show(t: (T, U)): String = { + val fst = st.show(t._1) + val snd = su.show(t._2) + if(snd == "") fst else s"$fst, $snd" + } + } + + implicit def showEither[T, U] + (implicit + st: Show[T], + su: Show[U]): + Show[Either[T, U]] = new Show[Either[T, U]] { + def show(x: Either[T, U]): String = x match { + case Left(t) => st(t) + case Right(t) => su(u) + } + } + + implicit def showGeneric[T, R] + (implicit + gen: Generic.Aux[T, R], + sr: Show[R]): + Show[T] = new Show[T] { + def show(x: T): String = sr(gen.to(x)) + } +} + +val sl = Show[List[Int]] // diverges +assert( + sl.show(Cons(1, Cons(2, Cons(3, Nil)))) == "1, 2, 3" +) +``` + +with the aim of having the inferred instance for `List` render as asserted. + +However the right hand side of the definition of `sl` does not compile because the implicit +resolution involved is seen as divergent by the compiler. To see why this is the case, observe that +the chain of implicits required to produce a value of type `Show[List[Int]]` develops as follows, + +``` + Show[List[Int]] + + V + +Show[Either[Cons[Int], Nil.type]] + + V + + Show[Cons[Int]] + + V + + Show[(Int, List[Int])] + + V + + Show[List[Int]] +``` + +This chain of implicit expansions repeats, and would do so indefinitely if the compiler didn't detect +and reject expansions of this sort. Indeed, this is what we should expect because the value we are +attempting to construct is itself recursive, and there is no mechanism here to allow us to tie the +knot. + +If we were to try to construct a value of this sort by hand we would make use of lazy vals and +byname arguments, + +```scala +lazy val showListInt: Show[List[Int]] = + showGeneric( + generic[List[Int]], + showEither( + showGeneric( + generic[Cons[Int]] + showCons( + showInt, + showCons( + showListInt, + showUnit + ) + ) + ), + showUnit + ) + ) +``` + +where at least one argument position between the lazy val definition and the recursive occurrence of +`showListInt` is byname. + +This SIP proposes automating the above manual process by, + ++ allowing implicit arguments to byname. + ++ constucting a dictionary of lazy val definitions at call sites where byname arguments are referred + to recursively. + +To allow the above example to work as intended we modify the `Show` instance definition as follows, + +```scala +object Show { + def apply[T](implicit st: => Show[T]): Show[T] = st + + // other definitions unchanged ... + + implicit def showGeneric[T, R] + (implicit + gen: Generic.Aux[T, R], + sr: => Show[R]): + Show[T] = new Show[T] { + def show(x: T): String = sr(gen.to(x)) + } +} + +val sl = Show[List[Int]] // compiles +assert( + sl.show(Cons(1, Cons(2, Cons(3, Nil)))) == "1, 2, 3" +) +``` + +and now the definition of `sl` compiles successfully as, + +```scala +val sl: Show[List[Int]] = Show.apply[List[Int]]( + { + object LazyDefns$1 { + lazy val rec$1: Show[List[Int]] = + showGeneric( + generic[List[Int]], + showEither( + showGeneric( + generic[Cons[Int]] + showCons( + showInt, + showCons( + showListInt, + showUnit + ) + ) + ), + showUnit + ) + ) + } + LazyDefns$1.rec$1 + } +) +``` + +### Proposal details + +#### Divergence checking algorithm + +We want to ensure that the typechecking of implicit argument expansions terminates, which entails +that all valid implicit expansions must be finite and that all potentially infinite (henceforth +_divergent_) implicit expansions must be detected and rejected in finite time. + +The Scala Language Specification describes a divergence checking algorithm in [7.2 +Implicit +Parameters](https://www.scala-lang.org/files/archive/spec/2.11/07-implicits.html#implicit-parameters). +We summarize it here. + +In the expansion of an implicit argument, implicit resolution identifies a corresponding implicit +definition (the mechanism for selecting one definition where there are alternatives is not relevant +to the discussion of divergence) which might in turn have implicit arguments. This gives rise to a +tree of implicit expansions. If all paths from the root terminate with an implicit definition which +does not itself have further implicit arguments then we say that it _converges_. If it does not then +it _diverges_. + +To prevent divergent expansions the specification requires the Scala compiler to maintain a stack of +"open" implicit types and conservatively check that the _core_ type of new types to be added to the +end of that stack are not part of a divergent sequence (the core type of _T_ is _T_ with aliases +expanded, top-level type annotations and refinements removed, and occurrences of top-level +existentially bound variables replaced by their upper bounds). When an implicit argument is fully +resolved it is removed from the end of the stack. The stack represents the current path from the +root of the implicit expansion being explored, in effect it is the state corresponding to a depth +first traversal of the tree of implicit expanions. + +The criteria for detecting divergence are that the newly added core type must not _dominate_ any of +the types already on the stack, where a core type _T_ dominates a type _U_ if _T_ is equivalent to +_U_, or if the top-level type constructors of _T_ and _U_ have a common element and _T_ is more +_complex_ than _U_. The precise definition of the complexity of a type is not relevant here but +roughly corresponds to the size of the AST representing it: intuitively, if we represent types as a +tree with type constructors as internal nodes and types of kind \* as leaf nodes then a type _T_ is +more complex than a type _U_ if the tree representing _T_ has more nodes than the tree representing +_U_. Note in particular that given these definitions the domination relation is partial: there might +be pairs of distinct types with a common top-level type constructor and the same complexity, in +which case neither dominates the other. + +A sequence of types _Tn_ is called _non dominating_ if no _Ti_ is dominated by +any _Tj_, where _i_ < _j_. + +#### Divergence checking in the Scala Language Specification + +The essence of the algorithm described in the Scala Language Specification is as follows, + +> Call the sequence of open implicit types _O_. This is initially empty. +> +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> +> + Identify the definition _d_ which satisfies _T_. +> +> + If the core type of _T_ dominates any element of _O_ then we have observed divergence and we're +> done. +> +> + If _d_ has no implicit arguments then the result is the value yielded by _d_. +> +> + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+T_, and the result is the +> value yielded by _d_ applied to its resolved arguments. + +This procedure yields a tree of implicit expansions where the nodes are labelled with pairs __, +_T_ being the core of the type for which a value is being resolved implicitly and _d_ being the +implicit definition used to supply that value. The children (if any) of __ correspond to the +implicit arguments of _d_, and the tree is rooted at the outermost implicit argument, ie. an implicit +argument of an explicit method call. By construction all paths from the root of the tree are non +dominating. + +The following is an informal proof that given this procedure all implicit expansions either converge +or are detected as divergent. This claim is equivalent to the claim that the tree of implicit +expansions is finite. + +We make the following assumptions: in any given program there is, + +**P1**. a finite number of distinct types with complexity less than or equal to any given complexity _c_. + +**P2**. a finite upper bound on the number of implicit arguments of any definition. + +First we observe that in any given program all non dominiating sequence of types _Tn_ are +finite. The type _T0_ has some complexity _c_ and **P1** asserts that there are a finite +number of types with complexity less than or equal to _c_, so a standard pigeonhole argument tells us +that eventually the sequence must terminate or visit a type that has a complexity greater than _c_ ∎. + +We can show that the tree of implicit expansions is finite by showing that (a) all paths from the root +to a leaf are finite, and then that (b) there is a finite number of paths. (a) follows from the fact +that all paths from the root are non-dominating and the lemma above which shows that all such paths +are finite. (b) follows from **P2** above and (a): each node has a finite number of children, so can +only introduce a finite number of subpaths and given that all paths are finite we know they can branch +only finitely often ∎. + +#### Divergence checking in the Scala compiler + +The current Scala compiler implements this algorithm with one variation, which safely admits more +programs as convergent. When checking for divergence the Scala compiler only compares types for +dominance if they correspond to the same implicit definition. In effect this "stripes" the +divergence check across the set of relevant implicit definitions. + +This gives us the following, + +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> +> + Identify the definition _d_ which satisfies _T_. +> +> + If the core type of _T_ dominates the type _U_ of some element __ of _O_ then we have +> observed divergence and we're done. +> +> + If _d_ has no implicit arguments then the result is the value yielded by _d_. +> +> + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+_, and the result is +> the value yielded by _d_ applied to its resolved arguments. + +Once again this procedure yields a tree of implicit expansions where the nodes are labelled with pairs +__. Given a path from the root of the tree, we call the sequence of nodes which are labelled +with a given definition _d_, in path order, the _definitional subpath_ with respect to _d_. By +construction all definitional subpaths are non-dominating. + +We can adapt the previous informal proof to the Scala compiler implementation by showing that (a) +still holds with the additional assumption that in any given program there is, + +**P3**. a finite set of implicit definitions _D_. + +Each path in the tree consists of nodes labelled with some element of _D_ and so can be +decomposed into an interleaving of definitional subpaths with respect to each of those definitions. +These definitional subpaths are non-dominating and hence, by the earlier lemma, finite. **P3** asserts +that there are only a finite number of these finite paths, so we know that their interleaving must +also be finite ∎. + +The practical difference between these two algorithms is illustrated by the following, + +```scala +implicit def requiresPair[T](implicit tt: (T, T)): List[T] = + List(tt._1, tt._2) + +implicit def providesPair[T](implicit t: T): (T, T) = (t, t) + +implicit val singleInt: Int = 23 + +implicitly[List[Int]] +``` + +The tree of implicit expansions is in this case a single path, + +```scala + + + V + + + + V + + +``` + +Here, the complexity of `(T, T)` is greater than the complexity of `List[Int]` and so, without the +striping by definition, the more conservative algorithm given in the specification would report +divergence. Thanks to the striping the Scala compiler accepts this program. + +#### Divergence checking proposed in this SIP + +This SIP changes the above algorithm to accomodate byname cycles. It also limits the scope of strict +divergence checking across byname boundaries within the expansion -- similarly to striping by definition +in the current divergence checker, this safely admits more programs as convergent. + +Call the set of types and type constructors which are mentioned in a type it's _covering set_. We say +that a type _T_ _covers_ a type _U_ if the covering set of _U_ is a weak subset of the covering set of +_U_. For example, given the following types, + +```scala +type A = List[(Int, Int)] +type B = List[(Int, (Int, Int))] +type C = List[(Int, String)] +``` + +the corresponding covering sets are, + +``` +A: List, Tuple2, Int +B: List, Tuple2, Int +C: List, Tuple2, Int, String +``` + +and we say that `A` covers `B` but does not cover `C`. Note that by the definition given earlier `A` +is not more complex than `B` or `C`, and `B` is more complex than both `A` and `C`. + +We revise the definition of domination as follows: a core type _T_ dominates a type _U_ if _T_ is +equivalent to _U_, or if the top-level type constructors of _T_ and _U_ have a common element and _T_ +is more _complex_ than _U_ and _U_ _covers_ _T_. For intuition, observe that if _T_ is more complex +than _U_ and _U_ covers _T_ then _T_ is structurally larger than _U_ despite using only elements that +are present in _U_ + +This give us the following, + +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> +> + Identify the definition _d_ which satisfies _T_. +> +> + We inspect _O_ from the top to either the bottom if _T_ is byname; or to the topmost element +> of the form _ U>_ if _T_ is not byname. +> +> 1. if we find an element _e_ of the form __ such that at least one element between _e_ +> and the top of the stack is of the form _ U>_ then we have observed an admissable cycle +> and we're done. +> 2. if we find an element __ where the core type of _T_ dominates _U_ then we have observed +> divergence and we're done. +> +> + If _d_ has no implicit arguments then the result is the value yielded by _d_. +> +> + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+_, and the result is +> the value yielded by _d_ applied to its resolved arguments. + +An informal proof that this this procedure will either converge or are detect divergence is similar +the two given earlier. + +First we show that with the revised definition of domination all non dominating sequences of types are +finite, using the additional assumption that in any given program there is, + +**P4**. a finite number of type definitions. + +Call the complexity of type _T_, _c(T)_, the covering set of _T_, _cs(T)_, and the set of all types +and type constructors in the program _U_. From **P1** we know that the number of types with complexity +less than or equal to _c(T0)_ is finite, so eventually the sequence must reach a type +_Tp_ with a complexity greater than _c(T0)_. For this to be non dominating it +must introduce some type _Ap_ which is not a member of _cs(T0)_. Again from +**P1** we know that the number of types with complexity less that _c(Tp)_ is finite, so +eventually the sequence must reach a type _Tq_ with a complexity greater than +_c(Tp)_ and so to continue _Tq_ must introduce some type _Aq_ which +is not a member of _cs(T0)_ ∪ _Ap_. Continuing in this way the sequence can +increase in complexity while accumulating an expanding covering set _cs(T0)_ ∪ +_Ap_ ∪ _Aq_ ∪ _Ar_ ... which from **P4** we know must eventually +exhaust _U_. Call the type at which this happens _TU_. Once again from **P1** we know that +the number of types with complexity less than or equal to _c(TU)_ is finite and so will +eventually be exhausted. This time, however, the sequence cannot be extended, because there are no +more types available to be introduced to avoid dominating an earlier element of the sequence ∎. + +To show that all paths in the tree of implicit expansions are finite we must decompose them into +definitional subpaths as in the previous proof. All nodes are either byname or strict, and so each of +these definitional subpaths consist of zero or more byname nodes each separated by zero or more strict +nodes. + +Call the sequence consisting of only the byname nodes of a definitional subpath, in path order, it's +_byname subpath_. And call the set of (possibly empty) sequences of strict nodes separating each +byname node it's _strict subpaths_. By construction, the byname subpath is non-dominating and so by +the lemma above must be finite. Each of the strict subpaths is also non-dominating by construction and +hence also finite. Consequently each of the definitional subpaths are the concatenation of a finite +number of finite byname nodes separated by a finite sequence of strict nodes, which must in turn be +finite. + +Finally, as in the previous proof we rely on **P3** to show that there are only a finite number of +these finite definitional subpaths and hence that their interleaving must also be finite ∎. + +## Follow on work from this SIP + +Byname implicits significantly advance the state of the art, however they are not a complete +replacement for shapeless's `Lazy` pseudo type. Byname parameters don't generate stable paths for +dependent types. This means that the following shapeless idiom, + +```scala +trait Foo { + type Out + def out: Out +} + +object Test { + implicit def bar(implicit foo: Lazy[Foo]): foo.value.Out = + foo.value.out +} +``` + +cannot be directly translated as, + +```scala +trait Foo { + type Out + def out: Out +} + +object Test { + implicit def bar(implicit foo: => Foo): foo.Out = foo.out +} +``` + +because the path `foo` in `foo.Out` is not stable. Full parity with shapeless's `Lazy` would require +lazy (rather than byname) implicit parameters (see [this Dotty +ticket](https://github.com/lampepfl/dotty/issues/3005) for further discussion) and is orthogonal to +this SIP in that they would drop out of support for lazy parameters more generally, as described in +[this Scala ticket](https://github.com/scala/bug/issues/240). + +In the meantime we can work around this limitation using the Aux pattern, + +```scala +trait Foo { + type Out + def out: Out +} + +object Foo { + type Aux[Out0] = Foo { type Out = Out0 } +} + +object Test { + implicit def bar[T](implicit foo: => Foo.Aux[T]): T = foo.out +} +``` + +shapeless also provides a `Cached` type which has similar characteristics to `Lazy` and which also +shares resolved values between call sites. Future work might address instance sharing generally, +although it would be desirable for this to be an implementation level optimization rather than a +user visible language feature. + +## Appendix 1 -- shapeless excerpts + +Extracts from shapeless relevant to the motivating examples for this SIP, + +```scala +trait Lazy[+T] extends Serializable { + val value: T +} + +object Lazy { + implicit def apply[T](t: => T): Lazy[T] = + new Lazy[T] { + lazy val value = t + } + + implicit def mkLazy[I]: Lazy[I] = macro ... +} + +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} +```