diff --git a/dist/shapeless-guide.pdf b/dist/shapeless-guide.pdf index 19a62b4..deda6b8 100644 Binary files a/dist/shapeless-guide.pdf and b/dist/shapeless-guide.pdf differ diff --git a/project/plugins.sbt b/project/plugins.sbt index 8e3aa8a..a34cb48 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.6") +addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.5-SNAPSHOT") diff --git a/src/meta/metadata.yaml b/src/meta/metadata.yaml index a930ff3..1aee050 100644 --- a/src/meta/metadata.yaml +++ b/src/meta/metadata.yaml @@ -63,7 +63,7 @@ pages: - poly/index.md - poly/poly.md - poly/map-flatmap-fold.md - - poly/type-class.md + - poly/product-mapper.md - poly/summary.md - nat/index.md @@ -72,6 +72,8 @@ pages: - nat/ops.md - nat/summary.md + - conclusion/index.md + - notes/todos.md - links.md ... diff --git a/src/pages/conclusion/index.md b/src/pages/conclusion/index.md new file mode 100644 index 0000000..792ce07 --- /dev/null +++ b/src/pages/conclusion/index.md @@ -0,0 +1,32 @@ +# Conclusion #{-} + +With Part II's look at `shapeless.ops`, +we have arrived at the end of this guide to shapeless. +We hope you found it useful for understanding +this fascinating and powerful library. + +As functional programmers +we value abstraction above all else. +Concepts like functors and monads +arise from years of programming research: +writing code, spotting patterns, +and making abstractions to remove redundancy. +Shapeless raises the bar for abstraction in Scala. +Tools like `Generic` and `LabelledGeneric` +provide an interface for abstracting over data types +that we previously thought unique and distinct. + +There have traditionally been two barriers to entry +for aspiring new shapeless users. +The first is the wealth of theoretical knowledge +and implementation detail +required to understand the bigger picture. +Hopefully this guide has done its job in this regard. +The second barrier is the fear and uncertainty +surrounding a library that is seen +as "academic" and "advanced". +We can overcome this by sharing knowledge +and showing each other the use cases, +advantages, and disadvantages of its use. +So please share this book with a friend... +and let's scrap some boilerplate together! diff --git a/src/pages/intro/acknowledgements.md b/src/pages/intro/acknowledgements.md index bb22a05..3cfd642 100644 --- a/src/pages/intro/acknowledgements.md +++ b/src/pages/intro/acknowledgements.md @@ -1,8 +1,7 @@ ## Acknowledgements -Thanks to Miles Sabin, Travis Brown, -my colleagues at [Underscore][link-underscore], -and all our [fellow space-farers on Github][link-contributors] +Thanks to Miles Sabin, Richard Dallaway, Noel Welsh, Travis Brown, +and our [fellow space-farers on Github][link-contributors] for their invaluable help and feedback. Special thanks to Sam Halliday for this excellent workshop diff --git a/src/pages/intro/index.md b/src/pages/intro/index.md index ad80d50..24eb73d 100644 --- a/src/pages/intro/index.md +++ b/src/pages/intro/index.md @@ -6,9 +6,10 @@ We assume shapeless 2.3.2 and either Typelevel Scala 2.11.8+ or Lightbend Scala 2.11.9+ / 2.12.1+. Shapeless is large library, -so rather than cover everything it has to offer, +so rather than cover everything it has to offer we will concentrate on a few compelling use cases -and use them to build a picture of the tools and patterns available. +and use them to build a picture +of the tools and patterns available. Before we start, let's talk about what generic programming is and why shapeless is so exciting to Scala developers. diff --git a/src/pages/intro/thisbook.md b/src/pages/intro/thisbook.md index 57dbcfb..740c6cc 100644 --- a/src/pages/intro/thisbook.md +++ b/src/pages/intro/thisbook.md @@ -66,16 +66,17 @@ we provide a theoretical primer in three chapters: our own version of Scalacheck's `Arbitrary`.
-*Source code for examples* +*Source code and examples* -We've placed source code for -many of the examples in this guide -in the accompanying Github repo: +This book is open source. +See the inside cover for licensing information. +You can find the Markdown source on Github: -`https://github.com/underscoreio/shapeless-guide-code` +`https://github.com/underscoreio/shapeless-guide` + +There are also complete implementations of +each of the major examples in this repo. +See the README for details: -The `exercises` branch contains -skeleton Scala files with `TODOs` to fill in, -and the `solutions` branch contains -completed implementations. +`https://github.com/underscoreio/shapeless-guide-code`
diff --git a/src/pages/nat/index.md b/src/pages/nat/index.md index 2d2511b..2a77a0f 100644 --- a/src/pages/nat/index.md +++ b/src/pages/nat/index.md @@ -3,8 +3,11 @@ From time to time we need to count things at the type level. For example, we may need to know the length of an `HList` or the number of terms we have expanded so far in a computation. +We can represent numbers as values easily enough, +but if we want to influence implicit resolution +we need to represent them as the type level. This chapter covers the theory behind counting with types, -and provides some use cases related to type class derivation. +and provides some compelling use cases for type class derivation. ## Representing numbers as types @@ -12,7 +15,7 @@ Shapeless uses "Church encoding" to represent natural numbers at the type level. It provides a type `Nat` with two subtypes: `_0` representing zero, -and `Succ[N]` representing the successor of `N`: +and `Succ[N]` representing `N+1`: ```tut:book:silent import shapeless.{Nat, Succ} @@ -23,8 +26,7 @@ type Two = Succ[One] // etc... ``` -shapeless provides aliases for the first 22 `Nats` -as `Nat._N`: +Shapeless provides aliases for the first 22 `Nats` as `Nat._N`: ```tut:book:silent Nat._1 @@ -48,8 +50,9 @@ toInt.apply() ``` The `Nat.toInt` method provides -a convenient shorthand for calling `nat.apply()`: +a convenient shorthand for calling `nat.apply()`. +It accepts the instance of `ToInt` as an implicit parameter: ```tut:book -Nat.toInt[Succ[Succ[Succ[Nat._0]]]] +Nat.toInt[Nat._3] ``` diff --git a/src/pages/nat/length.md b/src/pages/nat/length.md index fd3ffa4..b8e5b55 100644 --- a/src/pages/nat/length.md +++ b/src/pages/nat/length.md @@ -1,7 +1,7 @@ -## Sizing generic representations +## Length of generic representations One use case for `Nat` is -determining the size of `HLists` and `Coproducts`. +determining the length of `HLists` and `Coproducts`. Shapeless provides the `shapeless.ops.hlist.Length` and `shapeless.ops.coproduct.Length` type classes for this. @@ -23,8 +23,7 @@ val coproductLength = coproduct.Length[Double :+: Char :+: CNil] Instances of `Length` have a type member `Out` that represents the length as a `Nat`. -We can either summon a `ToInt` ourselves -to turn the `Nat` into an `Int`: +We can either summon an instance of `ToInt` ourselves: ```tut:book implicitly[ToInt[hlistLength.Out]].apply() @@ -45,6 +44,9 @@ and exposes it as a simple `Int`: trait SizeOf[A] { def value: Int } + +def sizeOf[A](implicit size: SizeOf[A]): Int = + size.value ``` To create an instance of `SizeOf` we need three things: @@ -71,9 +73,6 @@ implicit def genericSizeOf[A, L <: HList, N <: Nat]( We can test our code as follows: ```tut:book:silent -def sizeOf[A](implicit size: SizeOf[A]): Int = - size.value - case class IceCream(name: String, numCherries: Int, inCone: Boolean) ``` diff --git a/src/pages/nat/random.md b/src/pages/nat/random.md index d3ba2a9..7f86b62 100644 --- a/src/pages/nat/random.md +++ b/src/pages/nat/random.md @@ -1,8 +1,8 @@ ## Case study: random value generator Property-based testing libraries like [ScalaCheck][link-scalacheck] -use type classes to generate random data for use in unit tests. -For example, ScalaCheck has the `Arbitrary` type class +use type classes to generate random data for unit tests. +For example, ScalaCheck provides the `Arbitrary` type class that we can use as follows: ```tut:book:silent @@ -23,7 +23,7 @@ This makes shapeless integration via libraries like In this section we will create a simple `Random` type class to generate random values of user-defined ADTs. -We will show how `Length` and `Nat` form +We will show how `Length` and `Nat` form a crucial part of the implementation: ```tut:book:invisible @@ -64,7 +64,7 @@ implicit val booleanRandom: Random[Boolean] = createRandom(() => scala.util.Random.nextBoolean) ``` -We can use these simple generators +We can use these simple generators via the `random` method as follows: ```tut:book @@ -75,7 +75,7 @@ for(i <- 1 to 3) println(random[Char]) ### Random products We can create random values for products -using the `Generic` and `HList` techniques +using the `Generic` and `HList` techniques from Chapter [@sec:generic]: ```tut:book:silent @@ -95,7 +95,7 @@ implicit def hlistRandom[H, T <: HList]( implicit hRandom: Random[H], tRandom: Lazy[Random[T]] -): Random[H :: T] = +): Random[H :: T] = createRandom(() => hRandom.get :: tRandom.value.get) ``` @@ -131,7 +131,7 @@ implicit def coproductRandom[H, T <: Coproduct]( } ``` -There problems with this implementation +There problems with this implementation lie in the 50/50 choice in calculating `chooseH`. This creates an uneven probability distribution. For example, consider the following type: @@ -144,10 +144,10 @@ case object Green extends Light ``` The `Repr` for `Light` is `Red :+: Amber :+: Green :+: CNil`. -An instance of `Random` for this type -will choose `Red` 50% of the time +An instance of `Random` for this type +will choose `Red` 50% of the time and `Amber :+: Green :+: CNil` 50% of the time. -A correct distribution would be +A correct distribution would be 33% `Red` and 67% `Amber :+: Green :+: CNil`. And that's not all. @@ -167,10 +167,10 @@ for(i <- 1 to 100) random[Light] // ... ``` -To fix this problem we have to alter +To fix this problem we have to alter the probability of choosing `H` over `T`. The correct behaviour should be to choose -`H` `1/n`^th^ of the time, +`H` `1/n`^th^ of the time, where `n` is the length of the coproduct. This ensures an even probability distribution across the subtypes of the coproduct @@ -197,7 +197,7 @@ implicit def coproductRandom[H, T <: Coproduct, L <: Nat]( ``` -With these modifications +With these modifications we can generate random values of any product or coproduct: ```tut:book diff --git a/src/pages/nat/summary.md b/src/pages/nat/summary.md index 23c24c1..6abebd8 100644 --- a/src/pages/nat/summary.md +++ b/src/pages/nat/summary.md @@ -1,12 +1,18 @@ ## Summary -In this chapter we discussed +In this chapter we discussed how shapeless represents natural numbers -and how to calculate the length -of an `HList` or `Coproduct`. +and how we can use them in type classes. +We saw some predefined ops type classes +that let us do things like calculate lengths +and access elements by index, +and created our own type classes +that use `Nat` in other ways. -Such calculations involve two parts: -one at the type level involving -the `Length` type classes and the `Nat` type, -and one at the value level involving -the `ToInt` type class and regular `Ints`. +Between `Nat`, `Poly`, and the variety of +type classes and examples we have seen in Part II, +we have seen just a small fraction of +the toolbox provided in `shapeless.ops`. +There are many other ops type classes +that provide a comprehensive foundation +on which to build our own code. diff --git a/src/pages/notes/todos.md b/src/pages/notes/todos.md index 2270d24..81dbabd 100644 --- a/src/pages/notes/todos.md +++ b/src/pages/notes/todos.md @@ -1,4 +1,4 @@ -# TODOs +# TODOs #{-} Still to do: @@ -15,15 +15,15 @@ Still to do: - **DONE** Counting with `Nat` - **DONE** Generating `Arbitrary` instances as an example - Function interop - - Callout box on quirkiness of type inference with poly: - - `val len1: Int = lengthPoly("foo")` fails, but... - - `val len2 = lengthPoly("foo")` compiles, but... - - `val len3: Int = lengthPoly[String]("foo")` fails + - **DONE** Callout box on quirkiness of type inference with poly: + - **DONE** `val len1: Int = lengthPoly("foo")` fails, but... + - **DONE** `val len2 = lengthPoly("foo")` compiles, but... + - **DONE** `val len3: Int = lengthPoly[String]("foo")` fails - Built-in record operations - Performance - `cachedImplicit` - Maybe `Cached` - Maybe - Check cross references - - Final summary + - **DONE** Final summary - **SHIP IT!** diff --git a/src/pages/poly/index.md b/src/pages/poly/index.md index e6f4c32..7f62407 100644 --- a/src/pages/poly/index.md +++ b/src/pages/poly/index.md @@ -1,23 +1,23 @@ # Functional operations on HLists {#sec:poly} -Regular Scala programs make heavy use +"Regular" Scala programs make heavy use of functional operations like `map` and `flatMap`. -A question arises: can we perform similar operations -on `HLists` and `Coproducts`? -The answer is "yes", but the code looks -a little different to regular Scala code. -Unsurprisingly, the mechanisms are type class based -and there are a suite of ops type classes -to help us out. +A question arises: can we perform similar operations on `HLists`? +The answer is "yes", +but the heterogeneous element types require us +to do things a little differently than in regular Scala. +Unsurprisingly the mechanisms are type class based +and there are a suite of ops type classes to help us out. Before we delve in to the type classes themselves, -however, we need to discuss some theory to understand -what operations involving functions look like -when applied to heterogeneously typed data structures. +we need to discuss how shapeless represents +*polymorphic* functions suitable for +mapping over heterogeneous data structures. ## Motivation: mapping over an HList -Let's take the `map` method as an example. +We'll motivate the discussion of polymorphic functions +with a look at the `map` method. Figure [@fig:poly:monomorphic-map] shows a type chart for mapping over a regular list. We start with a `List[A]`, supply a function `A => B`, @@ -25,18 +25,21 @@ and end up with a `List[B]`. ![Mapping over a regular list ("monomorphic" map)](src/pages/poly/monomorphic-map.pdf+svg){#fig:poly:monomorphic-map} -This model breaks down for `HLists` and `Coproducts` -because of the heterogeneous nature of the element types. -Ideally we'd like a mapping -like the one shown in Figure [@fig:poly:polymorphic-map], -which inspects the type of each input element -and uses it to determine the type of each output element. -This gives us a closed, composable transformation. +The heterogeneous element types in an `HList` +cause this model to break down. +Scala functions have fixed input and output types, +so the result of our map will have to have +the same element type in every position. + +Ideally we'd like a `map` operation like +the one shown in Figure [@fig:poly:polymorphic-map], +where the function inspects the type of each input +and uses it to determine the type of each output. +This gives us a closed, composable transformation +that retains the heterogeneous nature of the `HList`. ![Mapping over a heterogeneous list ("polymorphic" map)](src/pages/poly/polymorphic-map.pdf+svg){#fig:poly:polymorphic-map} -Unfortunately, Scala functions are subject to a few restrictions: -they are not polymorphic and they can't have implicit parameters. -In other words, we can't choose an output type -based on input type of a regular function. -We need some new infrastructure to solve this problem. +Unfortunately we can't use Scala functions +to implement this kind of operation. +We need some new infrastructure. diff --git a/src/pages/poly/map-flatmap-fold.md b/src/pages/poly/map-flatmap-fold.md index 2194d91..2afbe02 100644 --- a/src/pages/poly/map-flatmap-fold.md +++ b/src/pages/poly/map-flatmap-fold.md @@ -1,8 +1,10 @@ ## Mapping and flatMapping using Poly -`HList` has a `map` method that -accepts a `Poly` as the mapping function. -Here's an example: +Shapeless provides a suite of +functional operations based on `Poly`, +each implemented as an ops type class. +Let's look at `map` and `flatMap` as examples. +Here's `map`: ```tut:book:silent import shapeless._ @@ -23,8 +25,10 @@ object sizeOf extends Poly1 { (10 :: "hello" :: true :: HNil).map(sizeOf) ``` -We can use `map` provided -the `Poly` provides `Cases` for every member of the `HList`. +Note that the elements in the resulting `HList` +have types matching the `Cases` in `sizeOf`. +We can use `map` with any `Poly` that +provides `Cases` for every member of our starting `HList`. If the compiler can't find a `Case` for a particular member, we get a compile error: @@ -32,8 +36,9 @@ we get a compile error: (1.5 :: HNil).map(sizeOf) ``` -We can also `flatMap` over an `HList` provided -every corresponding case in our `Poly` returns another `HList`: +We can also `flatMap` over an `HList` +provided every corresponding case in our `Poly` +returns another `HList`: ```tut:book:silent object valueAndSizeOf extends Poly1 { @@ -52,19 +57,23 @@ object valueAndSizeOf extends Poly1 { (10 :: "hello" :: true :: HNil).flatMap(valueAndSizeOf) ``` -If there is a missing case or one of the cases doesn't return an `HList`, -we get a compilation error: +Again, we get a compilation error if there is a missing case +or one of the cases doesn't return an `HList`: ```tut:book:fail // Using the wrong Poly with flatMap: (10 :: "hello" :: true :: HNil).flatMap(sizeOf) ``` +`map` and `flatMap` are based on type classes +called `Mapper` and `FlatMapper` respectively. +We'll see an example that makes direct use of `Mapper` in a moment. + ## Folding using Poly In addition to `map` and `flatMap`, shapeless also provides -`foldLeft` and `foldRight` operations on `HLists`: +`foldLeft` and `foldRight` operations based on `Poly2`: ```tut:book:silent import shapeless._ diff --git a/src/pages/poly/poly.md b/src/pages/poly/poly.md index a50872b..bcef996 100644 --- a/src/pages/poly/poly.md +++ b/src/pages/poly/poly.md @@ -1,11 +1,8 @@ ## Polymorphic functions -Shapeless provides the `Poly` datatype -for representing polymorphic functions. -At its core, a `Poly` is an object with an `apply` method -that accepts an implicit `Case` parameter -to map input types to output types. -Here is a simplified explanation of how it all works. +Shapeless represents polymorphic functions +using a type called `Poly`. +Here is a simplified explanation of how it works. Note that this isn't real shapeless code---we're eliding a lot of extra stuff that makes real shapeless `Polys` @@ -13,9 +10,9 @@ much more flexible and easier to use. ### How Polys work -The implementation of `Poly` boils down to the following. -The `apply` method delegates all concrete functionality -to a type class, `Case`: +At its core, a `Poly` is an object with a generic `apply` method. +In addition to its regular parameter of type `A`, +`Poly` accepts an implicit parameter of type `Case[A]`: ```tut:book:silent // This is not real shapeless code. @@ -32,66 +29,54 @@ trait Poly { } ``` -We'll define some extra helpers to simplify the examples below: - -```tut:book:silent -type CaseAux[P, A, R] = Case[P, A] { type Result = R } - -def createCase[P, A, R](func: A => R): CaseAux[P, A, R] = - new Case[P, A] { - type Result = R - def apply(a: A): R = func(a) - } -``` - -`Case` maps an input type `A` to an output type `Result`. -It also has a second type parameter `P` -referencing the singleton type of the `Poly` it is supporting -(we'll come to this in a moment). -When we create a `Poly`, we define the `Cases` -as `implicit vals` within its body: +When we define an actual `Poly`, +we provide instances of `Case` +for each parameter type we care about. +These implement the actual function body: ```tut:book:silent // This is not real shapeless code. // It is purely for illustration. object myPoly extends Poly { - implicit def intCase: CaseAux[this.type, Int, Double] = - createCase(num => num / 2.0) - - implicit def stringCase: CaseAux[this.type, String, Int] = - createCase(str => str.length) + implicit def intCase = + new Case[this.type, Int] { + type Result = Double + def apply(num: Int): Double = num / 2.0 + } + + implicit def stringCase = + new Case[this.type, String] { + type Result = Int + def apply(str: String): Int = str.length + } } ``` -The `Cases` define the behaviour for each input type. When we call `myPoly.apply`, the compiler searches for the relevant implicit `Case` and fills it in as usual: -```scala -myPoly.apply(123) // search for a `Case[myPoly.type, Int]` +```tut:book +myPoly.apply(123) ``` -But how do the `Cases` end up in implicit scope? -There is some subtle behaviour that makes this work. +There is some subtle scoping behaviour here +that allows the compiler to locate instances of `Case` +without any additional imports. +`Case` has an extra type parameter `P` +referincing the singleton type of the `Poly`. The implicit scope for `Case[P, A]` includes the companion objects for `Case`, `P`, and `A`. -We defined `P` as the singleton type `myPoly.type` -and it turns out that -the companion object for `myPoly.type` is `myPoly` itself, -so the `Cases` defined in the body of the `Poly` -are always in implicit scope: - -```tut:book -myPoly.apply(123) // search for a `Case[myPoly.type, Int]` -myPoly.apply("hello") // search for a `Case[myPoly.type, String]` -``` +The companion object for `myPoly.type` is `myPoly` itself, +so `Cases` defined in the body of the `Poly` +are always in scope no matter where the call site is. ### Poly syntax -The code so far this chapter hasn't been real shapeless code. -Here's our demo function from above rewritten in proper syntax: +The code above isn't real shapeless code. +Fortunately, shapeless makes `Polys` much simpler to define. +Here's our `myPoly` function rewritten in proper syntax: ```tut:book:silent import shapeless._ @@ -109,18 +94,20 @@ There are a few key differences with our earlier toy syntax: 1. We're extending a trait called `Poly1` instead of `Poly`. Shapeless has a `Poly` type and a set of subtypes, - `Poly1` through `Poly2`, supporting different arities + `Poly1` through `Poly22`, supporting different arities of polymorphic function. - 2. The `Case` and `Case.Aux` types don't include + 2. The `Case.Aux` types doesn't seem to reference the singleton type of the `Poly`. - In this context `Case` actually refers to - a type alias defined within the body of `Poly1`. + `Case.Aux` is actually actually a type alias + defined within the body of `Poly1`. The singleton type is there---we just don't see it. 3. We're using a helper method, `at`, to define cases. + This lets us eliminate a lot of boilerplate + such as writing out full definitions of `Result` and `apply`. -These syntactic differences aside, +Syntactic differences aside, the shapeless version of `myPoly` is functionally identical to our toy version. We can call it with an `Int` or `String` parameter @@ -176,3 +163,38 @@ total(10) total(Option(20.0)) total(List(1L, 2L, 3L)) ``` + +
+*Idiosyncrasies of type inference* + +`Poly` pushes Scala's type inference out of its comfort zone. +We can easily confuse the compiler by +asking it to do too much inference at once. +For example, the following code compiles ok: + +```tut:book:silent +val a = myPoly.apply(123) +val b: Double = a +``` + +However, if we try to combine the two lines in to one +we get a compilation error: + +```tut:book:fail +val a: Double = myPoly.apply(123) +``` + +If we give the compiler a hint by +telling it what the parameter type is, +the code compiles again: + +```tut:book +val a: Double = myPoly.apply[Int](123) +``` + +This behaviour is confusing and annoying. +Unfortunately there are no concrete rules to follow to avoid problems. +The only general guideline is +to avoid over-constraining the compiler---solve one constraint at a time +and give it a hint when it gets stuck. +
diff --git a/src/pages/poly/type-class.md b/src/pages/poly/product-mapper.md similarity index 72% rename from src/pages/poly/type-class.md rename to src/pages/poly/product-mapper.md index 12feb1e..b173043 100644 --- a/src/pages/poly/type-class.md +++ b/src/pages/poly/product-mapper.md @@ -1,8 +1,8 @@ ## Defining type classes using Poly We can use `Poly` and type classes -such as `Mapper` and `FlatMapper` -in the definitions of our own type classes. +like `Mapper` and `FlatMapper` +as building blocks for our own type classes. As an example let's build a type class for mapping from one case class to another: @@ -12,8 +12,8 @@ trait ProductMapper[A, B, P] { } ``` -We can create a type class instance -using a `Mapper` and a pair of `Generics`: +We can create an instance of `ProductMapper` +using `Mapper` and a pair of `Generics`: ```tut:book:silent import shapeless._ @@ -36,14 +36,13 @@ implicit def genericProductMapper[ } ``` -Interestingly, -the value of the `Poly` does not appear in this code. +Interestingly, although we define a type `P` for our `Poly`, +we don't reference any values of type `P` anywhere in our code. The `Mapper` type class uses implicit resolution to find `Cases`, -so we only need to know the singleton type of the `Poly` -to do the mapping. +so we only need to know the type to do the mapping. -We can create an extension method -to make the type class easy to use. +Let's create an extension method +to make `ProductMapper` easier to use. We only want the user to specify the type of `B` at the call site, so we use some indirection to allow the compiler to infer the type of the `Poly` @@ -62,10 +61,7 @@ implicit class ProductMapperOps[A](a: A) { } ``` -The resulting `mapTo` syntax looks like a single method call, -but is actually two calls: -one call to `mapTo` to fix the `B` type parameter, -and one call to `Builder.apply` to specify the `Poly`: +Here's an example of the method's use: ```tut:book:silent object conversions extends Poly1 { @@ -81,3 +77,9 @@ case class IceCream2(name: String, hasCherries: Boolean, numCones: Int) ```tut:book IceCream1("Sundae", 1, false).mapTo[IceCream2](conversions) ``` + +The `mapTo` syntax looks like a single method call, +but is actually two calls: +one call to `mapTo` to fix the `B` type parameter, +and one call to `Builder.apply` to specify the `Poly`. +Some of shapeless' built-in ops extension methods use similar tricks. diff --git a/src/pages/poly/summary.md b/src/pages/poly/summary.md index 70bf31d..d5e39ad 100644 --- a/src/pages/poly/summary.md +++ b/src/pages/poly/summary.md @@ -2,10 +2,8 @@ In this chapter we discussed *polymorphic functions* whose return types vary based on the types of their parameters. -We discussed shapeless' `Poly` type -that specifies cases as implicit values, -and saw how it is used to implement -functional operations such as +We saw how shapeless' `Poly` type is defined, +and how it is used to implement functional operations such as `map`, `flatMap`, `foldLeft`, and `foldRight`. Each operation is implemented as an extension method on `HList`,