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]`.
{#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`.
{#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`,