├── .gitignore
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── Gruntfile.coffee
├── README.md
├── book_deploy.sh
├── book_deploy_rsa.enc
├── build.sbt
├── docker-compose.yml
├── go.sh
├── index.md
├── package.json
├── project
├── build.properties
└── plugins.sbt
├── sbt.sh
├── src
├── covers
│ ├── epub-cover.png
│ ├── html-cover.pdf
│ ├── html-cover.png
│ └── pdf-cover.pdf
├── css
│ └── book.less
├── meta
│ ├── epub.yaml
│ ├── html.yaml
│ ├── metadata.yaml
│ └── pdf.yaml
├── pages
│ ├── conclusion
│ │ └── index.md
│ ├── foreword.md
│ ├── generic
│ │ ├── coproducts.md
│ │ ├── debugging.md
│ │ ├── exercises.md.disabled
│ │ ├── index.md
│ │ ├── lazy.md
│ │ ├── products.md
│ │ ├── summary.md
│ │ └── type-classes.md
│ ├── intro
│ │ ├── contributors.md
│ │ ├── generic.md
│ │ ├── index.md
│ │ ├── source-code.md
│ │ └── thisbook.md
│ ├── labelled-generic
│ │ ├── coproducts.md
│ │ ├── index.md
│ │ ├── literal-types.md
│ │ ├── products.md
│ │ └── summary.md
│ ├── links.md
│ ├── nat
│ │ ├── index.md
│ │ ├── length.md
│ │ ├── ops.md
│ │ ├── random.md
│ │ └── summary.md
│ ├── notes
│ │ ├── libraries.md
│ │ ├── outline.md
│ │ └── todos.md
│ ├── ops
│ │ ├── index.md
│ │ ├── init-last.md
│ │ ├── migration.md
│ │ ├── penultimate.md
│ │ ├── record.md
│ │ └── summary.md
│ ├── parts
│ │ ├── part1.md
│ │ ├── part2.md
│ │ └── part3.md
│ ├── poly
│ │ ├── index.md
│ │ ├── map-flatmap-fold.md
│ │ ├── monomorphic-map.pdf
│ │ ├── monomorphic-map.svg
│ │ ├── poly.md
│ │ ├── polymorphic-map.pdf
│ │ ├── polymorphic-map.svg
│ │ ├── product-mapper.md
│ │ ├── summary.md
│ │ └── type-charts.sketch
│ ├── representations
│ │ ├── adts.md
│ │ ├── coproduct.png
│ │ ├── coproducts.md
│ │ ├── hlist.png
│ │ ├── index.md
│ │ ├── products.md
│ │ ├── representations.sketch
│ │ └── summary.md
│ ├── solutions.md
│ └── type-level-programming
│ │ ├── dependent-functions.md
│ │ ├── dependent-types.md
│ │ ├── index.md
│ │ └── summary.md
└── template
│ ├── cover-notes.html
│ ├── cover-notes.tex
│ ├── images
│ ├── brand.pdf
│ ├── brand.svg
│ ├── hero-arrow-overlay-white.svg
│ ├── hero-left-overlay-white.pdf
│ ├── hero-left-overlay-white.svg
│ ├── hero-right-overlay-white.pdf
│ ├── hero-right-overlay-white.svg
│ ├── spaaace.pdf
│ └── spaaace.png
│ ├── template.epub.html
│ ├── template.html
│ └── template.tex
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | _site/*
2 | _theme_packages/*
3 |
4 | Thumbs.db
5 | .DS_Store
6 |
7 | !.gitkeep
8 |
9 | .rbenv-version
10 | .rvmrc
11 | /bower_components/
12 | /dist/
13 | /node_modules/
14 |
15 | *.sublime-workspace
16 |
17 | target/
18 | project/target
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | services:
3 | - docker
4 | language: scala
5 | scala:
6 | - 2.12.2
7 | cache:
8 | directories:
9 | - "$HOME/.ivy2/cache"
10 | - "$HOME/.sbt/boot"
11 | - "$HOME/.cache/.coursier"
12 | script:
13 | - sudo mkdir .sbt
14 | - sudo mkdir dist
15 | - docker-compose run book npm install
16 | - docker-compose run book ./sbt.sh pdf html epub ; export SBT_RESULT=$?
17 | - test "$SBT_RESULT" == "0"
18 | after_success:
19 | - openssl aes-256-cbc -K $encrypted_232d7e8fc499_key -iv $encrypted_232d7e8fc499_iv
20 | -in book_deploy_rsa.enc -out book_deploy_rsa -d
21 | - "./book_deploy.sh"
22 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | gem "jekyll"
3 | gem "kramdown"
4 | gem "typogruby"
5 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | blankslate (2.1.2.4)
5 | celluloid (0.16.0)
6 | timers (~> 4.0.0)
7 | classifier-reborn (2.0.1)
8 | fast-stemmer (~> 1.0)
9 | coffee-script (2.3.0)
10 | coffee-script-source
11 | execjs
12 | coffee-script-source (1.8.0)
13 | colorator (0.1)
14 | execjs (2.2.2)
15 | fast-stemmer (1.0.2)
16 | ffi (1.9.6)
17 | hitimes (1.2.2)
18 | jekyll (2.4.0)
19 | classifier-reborn (~> 2.0)
20 | colorator (~> 0.1)
21 | jekyll-coffeescript (~> 1.0)
22 | jekyll-gist (~> 1.0)
23 | jekyll-paginate (~> 1.0)
24 | jekyll-sass-converter (~> 1.0)
25 | jekyll-watch (~> 1.1)
26 | kramdown (~> 1.3)
27 | liquid (~> 2.6.1)
28 | mercenary (~> 0.3.3)
29 | pygments.rb (~> 0.6.0)
30 | redcarpet (~> 3.1)
31 | safe_yaml (~> 1.0)
32 | toml (~> 0.1.0)
33 | jekyll-coffeescript (1.0.1)
34 | coffee-script (~> 2.2)
35 | jekyll-gist (1.1.0)
36 | jekyll-paginate (1.1.0)
37 | jekyll-sass-converter (1.2.1)
38 | sass (~> 3.2)
39 | jekyll-watch (1.1.1)
40 | listen (~> 2.7)
41 | kramdown (1.4.2)
42 | liquid (2.6.1)
43 | listen (2.7.11)
44 | celluloid (>= 0.15.2)
45 | rb-fsevent (>= 0.9.3)
46 | rb-inotify (>= 0.9)
47 | mercenary (0.3.4)
48 | parslet (1.5.0)
49 | blankslate (~> 2.0)
50 | posix-spawn (0.3.9)
51 | pygments.rb (0.6.0)
52 | posix-spawn (~> 0.3.6)
53 | yajl-ruby (~> 1.1.0)
54 | rb-fsevent (0.9.4)
55 | rb-inotify (0.9.5)
56 | ffi (>= 0.5.0)
57 | redcarpet (3.2.0)
58 | rubypants (0.2.0)
59 | safe_yaml (1.0.4)
60 | sass (3.4.6)
61 | timers (4.0.1)
62 | hitimes
63 | toml (0.1.2)
64 | parslet (~> 1.5.0)
65 | typogruby (1.0.17)
66 | rubypants
67 | yajl-ruby (1.1.0)
68 |
69 | PLATFORMS
70 | ruby
71 |
72 | DEPENDENCIES
73 | jekyll
74 | kramdown
75 | typogruby
76 |
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | #global module:false
2 |
3 | "use strict"
4 |
5 | ebook = require 'underscore-ebook-template'
6 |
7 | module.exports = (grunt) ->
8 | ebook(grunt, {
9 | dir: {
10 | # lib : "src/build"
11 | page : "target/pages"
12 | template : "src/template"
13 | }
14 | })
15 | return
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Type Astronaut's Guide to Shapeless
2 |
3 | Copyright 2016 Dave Gurnell.
4 | Text and diagrams licensed [CC-BY-SA 3.0][text-license].
5 | Code samples licensed [Apache 2.0][code-license]
6 |
7 | ## Reading the Book
8 |
9 | You have three options for grabbing the book:
10 |
11 | - read/download the book at the [Underscore][underscore] web site;
12 | - order a print copy from [Underscore][underscore];
13 | - a french translation is [also available][fr].
14 |
15 | ## Related Material
16 |
17 | Accompanying code samples can be found here:
18 | https://github.com/underscoreio/shapeless-guide-code
19 |
20 | Check the `solutions` branch for complete versions of each example.
21 |
22 | ## Building the eBook
23 |
24 | Install Docker and use `go.sh` to boot an instance
25 | with most of the right dependencies:
26 |
27 | ~~~
28 | bash$ ./go.sh
29 | ~~~
30 |
31 | Then run `npm install` to install the remaining dependencies:
32 |
33 | ~~~
34 | npm install
35 | ~~~
36 |
37 | And finally use `sbt` to build the book:
38 |
39 | ~~~
40 | sbt pdf
41 | ~~~
42 |
43 | ## Building a printable book
44 |
45 | To build a black and white,
46 | print-ready version of the book,
47 | edit `src/meta/pdf.yaml` and set
48 | `blackandwhiteprintable` to `true`.
49 | Then run `sbt pdf` as above.
50 |
51 | ## Contributing
52 |
53 | Please raise an issue or submit a PR.
54 | If you submit a PR, make sure to add yourself to
55 | `src/pages/intro/constributors.md`!
56 |
57 | ## Acknowledgements
58 |
59 | Thanks to Miles Sabin, Richard Dallaway, Noel Welsh, Travis Brown,
60 | and our [fellow space-farers on Github][contributors]
61 | for their invaluable help and feedback.
62 |
63 | [text-license]: https://creativecommons.org/licenses/by-sa/3.0/
64 | [code-license]: http://www.apache.org/licenses/LICENSE-2.0
65 | [shapeless]: https://github.com/milessabin/shapeless
66 | [pdf]: https://github.com/underscoreio/shapeless-guide/blob/develop/dist/shapeless-guide.pdf
67 | [slides]: https://github.com/davegurnell/shapeless-guide-slides
68 | [code]: https://github.com/underscoreio/shapeless-guide-code
69 | [contributors]: https://github.com/underscoreio/shapeless-guide/graphs/contributors
70 | [underscore]: https://underscore.io/books/shapeless-guide
71 | [lulu]: http://www.lulu.com/shop/dave-gurnell/the-type-astronauts-guide-to-shapeless/paperback/product-22992219.html
72 | [fr]: https://github.com/crakjie/shapeless-guide
73 |
--------------------------------------------------------------------------------
/book_deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Deploy the PDF, HTML, EPUB files of an Underscore book (source)
4 | # into another Git repository (target)
5 | #
6 | set -e
7 |
8 | # Configuration
9 | # 1. The key for writing into the other repository.
10 | # This is committed to the repo, encrypted by Travis,
11 | # with filename extension '.enc'.
12 | export KEY_FILENAME="book_deploy_rsa"
13 | # 2. The branch we deploy from.
14 | # Only deploy for non-PR commits to this branch.
15 | export DEPLOY_BRANCH="develop"
16 | # 3. Folder inside target of where to place the artifacts:
17 | export TARGET_DIR=books/shapeless-guide/
18 | # 4. Commit message prefix (for the "books" repository)
19 | export COMMIT_PREFIX="deploy shapeless guide via travis"
20 | # End of configuration
21 |
22 | if [[ "${TRAVIS_PULL_REQUEST}" == "false" && "${TRAVIS_BRANCH}" == "${DEPLOY_BRANCH}" ]]; then
23 | echo "Starting deploy to Github Pages"
24 | mkdir -p "~/.ssh"
25 | echo -e "Host github.com\n\tStrictHostKeyChecking no\nIdentityFile ~/.ssh/${KEY_FILENAME}\n" >> ~/.ssh/config
26 | cp ${KEY_FILENAME} ~/.ssh/${KEY_FILENAME}
27 | chmod 600 ~/.ssh/${KEY_FILENAME}
28 |
29 | git config --global user.email "hello@underscore.io"
30 | git config --global user.name "Travis Build"
31 |
32 | export SRC_DIR=`pwd` # e.g., /home/travis/build/underscoreio/insert-book-name-here
33 |
34 | export TEMP_DIR=/tmp/dist
35 | mkdir -p $TEMP_DIR
36 | cd $TEMP_DIR
37 | git clone git@github.com:underscoreio/books.git
38 |
39 | mkdir -p $TARGET_DIR
40 | cd $TARGET_DIR
41 |
42 | cp $SRC_DIR/dist/*.pdf .
43 | cp $SRC_DIR/dist/*.html .
44 | cp $SRC_DIR/dist/*.epub .
45 |
46 | git add .
47 | git commit -m "$COMMIT_PREFIX $TRAVIS_JOB_NUMBER $TRAVIS_COMMIT [ci skip]"
48 | git push git@github.com:underscoreio/books.git master:master
49 |
50 | rm -rf $TEMP_DIR
51 | fi
52 |
--------------------------------------------------------------------------------
/book_deploy_rsa.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/book_deploy_rsa.enc
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val root = project.in(file("."))
2 | .settings(tutSettings)
3 |
4 | tutSourceDirectory := sourceDirectory.value / "pages"
5 |
6 | tutTargetDirectory := target.value / "pages"
7 |
8 | scalaOrganization in ThisBuild := "org.typelevel"
9 | scalaVersion in ThisBuild := "2.12.1"
10 |
11 | scalacOptions ++= Seq(
12 | "-deprecation",
13 | "-encoding", "UTF-8",
14 | "-unchecked",
15 | "-feature",
16 | "-Xlint",
17 | //"-Xfatal-warnings",
18 | "-Ywarn-dead-code",
19 | "-Yliteral-types"
20 | )
21 |
22 | resolvers ++= Seq(Resolver.sonatypeRepo("snapshots"))
23 |
24 | libraryDependencies ++= Seq(
25 | "com.chuusai" %% "shapeless" % "2.3.2",
26 | "org.scalacheck" %% "scalacheck" % "1.13.5",
27 | "org.typelevel" %% "cats" % "0.9.0"
28 | )
29 |
30 | lazy val pdf = taskKey[Unit]("Build the PDF version of the book")
31 | lazy val html = taskKey[Unit]("Build the HTML version of the book")
32 | lazy val epub = taskKey[Unit]("Build the ePub version of the book")
33 | lazy val all = taskKey[Unit]("Build all versions of the book")
34 |
35 | pdf := { tutQuick.value ; "grunt pdf".! }
36 | html := { tutQuick.value ; "grunt html".! }
37 | epub := { tutQuick.value ; "grunt epub".! }
38 |
39 | all := {
40 | tutQuick.value
41 | "grunt pdf".!
42 | "grunt html".!
43 | "grunt epub".!
44 | }
45 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | book:
4 | image: underscoreio/book:latest
5 | volumes:
6 | - .:/source
7 |
--------------------------------------------------------------------------------
/go.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | docker-compose run book bash
3 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/index.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shapeless-guide",
3 | "description": "",
4 | "version": "0.0.1",
5 | "dependencies": {
6 | "grunt": "0.4.5",
7 | "grunt-browserify": "3.3.0",
8 | "grunt-contrib-clean": "0.6.0",
9 | "grunt-contrib-connect": "0.9.0",
10 | "grunt-contrib-copy": "0.7.0",
11 | "grunt-contrib-less": "1.0.0",
12 | "grunt-contrib-watch": "0.6.1",
13 | "grunt-css-url-embed": "1.5.0",
14 | "js-yaml": "3.2.6",
15 | "pandoc-filter": "^0.1.4"
16 | },
17 | "devDependencies": {
18 | "bootstrap": "3.3.1",
19 | "coffeeify": "1.0.0",
20 | "jquery": "2.1.1",
21 | "uglifyify": "2.6.0",
22 | "underscore": "1.7.0",
23 | "underscore-ebook-template": "git+https://github.com/underscoreio/underscore-ebook-template.git#0.6.2"
24 | },
25 | "scripts": {
26 | "test": "echo \"Error: no test specificed\" && exit 1"
27 | },
28 | "author": "Dave Gurnell"
29 | }
30 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.16
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.7")
2 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0")
3 |
--------------------------------------------------------------------------------
/sbt.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | export JAVA_OPTS="-Xmx3g -XX:+TieredCompilation -XX:ReservedCodeCacheSize=256m -XX:+UseNUMA -XX:+UseParallelGC -XX:+CMSClassUnloadingEnabled"
4 |
5 | sbt "$@"
6 |
--------------------------------------------------------------------------------
/src/covers/epub-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/covers/epub-cover.png
--------------------------------------------------------------------------------
/src/covers/html-cover.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/covers/html-cover.pdf
--------------------------------------------------------------------------------
/src/covers/html-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/covers/html-cover.png
--------------------------------------------------------------------------------
/src/covers/pdf-cover.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/covers/pdf-cover.pdf
--------------------------------------------------------------------------------
/src/css/book.less:
--------------------------------------------------------------------------------
1 | @book-color: #042742;
2 |
3 | .hero {
4 | background: @book-color url(src/covers/html-cover.png) center top no-repeat;
5 |
6 | .container {
7 | height: 600px;
8 | }
9 |
10 | .hero-text {
11 | top: auto;
12 | height: auto;
13 | bottom: 0;
14 | padding-bottom: 60px;
15 |
16 | h1.title {
17 | margin-bottom: 0;
18 | font-size: 36px;
19 | }
20 |
21 | h3.author {
22 | margin-bottom: 10px;
23 | font-size: 30px;
24 | }
25 |
26 | p.date {
27 | margin-bottom: 30px;
28 | }
29 |
30 | img.brand-logo {
31 | height: 80px;
32 | }
33 | }
34 | }
35 |
36 | footer {
37 | background: @book-color;
38 | }
--------------------------------------------------------------------------------
/src/meta/epub.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | solutions:
3 | headingText: "Solutions to Exercises"
4 | ...
--------------------------------------------------------------------------------
/src/meta/html.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | solutions:
3 | headingText: "Solutions to Exercises"
4 | ...
--------------------------------------------------------------------------------
/src/meta/metadata.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Used by Pandoc to print covers:
3 | title: "The Type Astronaut's Guide to Shapeless"
4 | author: "Dave Gurnell"
5 | date: "April 2017"
6 | # Used by Grunt to name output files:
7 | filenameStem: "shapeless-guide"
8 | # Used by Grunt to create a ZIP of source code:
9 | exercisesRepo: null
10 | tocDepth: 3
11 | copyright: "2016-17"
12 | # Enable pandoc-crossref (must be installed on user's computer)
13 | # There's no distinction here between section and chapter prefixes,
14 | # we disable all prefixes for consistency and write them by hand:
15 | usePandocCrossref: true
16 | figPrefixTemplate: "$$i$$"
17 | eqnPrefixTemplate: "$$i$$"
18 | tblPrefixTemplate: "$$i$$"
19 | lstPrefixTemplate: "$$i$$"
20 | secPrefixTemplate: "$$i$$"
21 | # Used by Grunt to assemble pandoc command line:
22 | pages:
23 | - foreword.md
24 |
25 | - intro/index.md
26 | - intro/generic.md
27 | - intro/thisbook.md
28 | - intro/source-code.md
29 | - intro/contributors.md
30 |
31 | - parts/part1.md
32 |
33 | - representations/index.md
34 | - representations/adts.md
35 | - representations/products.md
36 | - representations/coproducts.md
37 | - representations/summary.md
38 |
39 | - generic/index.md
40 | - generic/type-classes.md
41 | - generic/products.md
42 | - generic/coproducts.md
43 | - generic/lazy.md
44 | - generic/debugging.md
45 | - generic/summary.md
46 |
47 | - type-level-programming/index.md
48 | - type-level-programming/dependent-types.md
49 | - type-level-programming/dependent-functions.md
50 | - type-level-programming/summary.md
51 |
52 | - labelled-generic/index.md
53 | - labelled-generic/literal-types.md
54 | - labelled-generic/products.md
55 | - labelled-generic/coproducts.md
56 | - labelled-generic/summary.md
57 |
58 | - parts/part2.md
59 |
60 | - ops/index.md
61 | - ops/init-last.md
62 | - ops/penultimate.md
63 | - ops/migration.md
64 | - ops/record.md
65 | - ops/summary.md
66 |
67 | - poly/index.md
68 | - poly/poly.md
69 | - poly/map-flatmap-fold.md
70 | - poly/product-mapper.md
71 | - poly/summary.md
72 |
73 | - nat/index.md
74 | - nat/length.md
75 | - nat/random.md
76 | - nat/ops.md
77 | - nat/summary.md
78 |
79 | - conclusion/index.md
80 |
81 | # - notes/todos.md
82 | - links.md
83 | ...
84 |
--------------------------------------------------------------------------------
/src/meta/pdf.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | papersize: "a5paper"
3 | fontsize: "11pt"
4 | geometry: "margin=.75in"
5 | solutions:
6 | headingText: "Solutions to Exercises"
7 | # blackandwhiteprintable: true
8 | # "links-as-notes": true
9 | ...
10 |
--------------------------------------------------------------------------------
/src/pages/conclusion/index.md:
--------------------------------------------------------------------------------
1 | # Prepare for launch! #{-}
2 |
3 | With Part II's look at `shapeless.ops`
4 | we have arrived at the end of this guide.
5 | We hope you found it useful for understanding
6 | this fascinating and powerful library,
7 | and wish you all the best
8 | on your future journeys as a type astronaut.
9 |
10 | As functional programmers
11 | we value abstraction above all else.
12 | Concepts like functors and monads
13 | arise from years of programming research:
14 | writing code, spotting patterns,
15 | and making abstractions to remove redundancy.
16 | Shapeless raises the bar for abstraction in Scala.
17 | Tools like `Generic` and `LabelledGeneric`
18 | provide an interface for abstracting over data types
19 | that were previously frustratingly unique and distinct.
20 |
21 | There have traditionally been two barriers to entry
22 | for aspiring new shapeless users.
23 | The first is the wealth of theoretical knowledge
24 | and implementation detail
25 | required to understand the patterns we need.
26 | Hopefully this guide has helped in this regard.
27 |
28 | The second barrier is the fear and uncertainty
29 | surrounding a library that is seen
30 | as "academic" or "advanced".
31 | We can overcome this by sharing knowledge---use cases,
32 | pros and cons, implementation strategies, and so on---to
33 | widen the understanding of this valuable tool.
34 | So please share this book with a friend...
35 | and let's scrap some boilerplate together!
36 |
37 | \clearpage
38 | \pagestyle{empty}
39 |
--------------------------------------------------------------------------------
/src/pages/foreword.md:
--------------------------------------------------------------------------------
1 | # Foreword {-}
2 |
3 | Back at the beginning of 2011, when I first started doing the
4 | experiments in generic programming that would eventually turn into
5 | shapeless, I had no idea that five years later it would have evolved
6 | into such a widely used library. I am profoundly grateful to the
7 | people who have trusted me and added shapeless as a dependency to
8 | their own projects: the vote of confidence that this represents is a
9 | huge motivator for any open source project. I am also hugely grateful
10 | to the many people who have contributed to shapeless over the years:
11 | eighty one at the time of writing. Without their help shapeless would
12 | be a far less interesting and useful library.
13 |
14 | These positives notwithstanding, shapeless has suffered from one of
15 | the common failings of open source projects: a lack of comprehensive,
16 | accurate and accessible documentation. The responsibility for this
17 | lies squarely at my door: despite acknowledging the lack I have never
18 | been able to find the time to do anything about it. To some extent
19 | shapeless has been saved from this by Travis Brown's heroic Stack
20 | Overflow performance and also by the many people who have given talks
21 | about and run workshops on shapeless (in particular I'd like to
22 | highlight Sam Halliday's "Shapeless for Mortals" workshop).
23 |
24 | But Dave Gurnell has changed all that: we now have this wonderful book
25 | length treatment of shapeless's most important application: type class
26 | derivation via generic programming. In doing this he has pulled
27 | together fragments of folklore and documentation, has picked my brain,
28 | and turned the impenetrable tangle into something which is clear,
29 | concise and very practical. With any luck he will be able to make good
30 | on my regular claims that at its core shapeless is a very simple
31 | library embodying a set of very simple concepts.
32 |
33 | Thanks Dave, you've done us all a great service.
34 |
35 | Miles Sabin\
36 | Creator of shapeless
37 |
--------------------------------------------------------------------------------
/src/pages/generic/coproducts.md:
--------------------------------------------------------------------------------
1 | ## Deriving instances for coproducts {#sec:generic:coproducts}
2 |
3 | ```tut:book:invisible
4 | // ----------------------------------------------
5 | // Forward definitions
6 |
7 | trait CsvEncoder[A] {
8 | def encode(value: A): List[String]
9 | }
10 |
11 | object CsvEncoder {
12 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
13 | enc
14 | }
15 |
16 | def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
17 | values.map(encoder.encode).map(_.mkString(",")).mkString("\n")
18 |
19 | def createEncoder[A](func: A => List[String]): CsvEncoder[A] =
20 | new CsvEncoder[A] {
21 | def encode(value: A): List[String] =
22 | func(value)
23 | }
24 |
25 | implicit val stringEncoder: CsvEncoder[String] =
26 | createEncoder(str => List(str))
27 |
28 | implicit val intEncoder: CsvEncoder[Int] =
29 | createEncoder(num => List(num.toString))
30 |
31 | implicit val booleanEncoder: CsvEncoder[Boolean] =
32 | createEncoder(bool => List(if(bool) "cone" else "glass"))
33 |
34 | import shapeless.{HList, HNil, ::}
35 |
36 | implicit val hnilEncoder: CsvEncoder[HNil] =
37 | createEncoder(hnil => Nil)
38 |
39 | implicit def hlistEncoder[H, T <: HList](
40 | implicit
41 | hEncoder: CsvEncoder[H],
42 | tEncoder: CsvEncoder[T]
43 | ): CsvEncoder[H :: T] = createEncoder {
44 | case h :: t =>
45 | hEncoder.encode(h) ++ tEncoder.encode(t)
46 | }
47 |
48 | import shapeless.Generic
49 |
50 | implicit def genericEncoder[A, R](
51 | implicit
52 | gen: Generic.Aux[A, R],
53 | rEncoder: CsvEncoder[R]
54 | ): CsvEncoder[A] =
55 | createEncoder(value => rEncoder.encode(gen.to(value)))
56 |
57 | // ----------------------------------------------
58 | ```
59 |
60 | In the last section we created a set of rules
61 | to automatically derive a `CsvEncoder` for any product type.
62 | In this section we will apply the same patterns to coproducts.
63 | Let's return to our shape ADT as an example:
64 |
65 | ```tut:book:silent
66 | sealed trait Shape
67 | final case class Rectangle(width: Double, height: Double) extends Shape
68 | final case class Circle(radius: Double) extends Shape
69 | ```
70 |
71 | The generic representation for `Shape`
72 | is `Rectangle :+: Circle :+: CNil`.
73 | In Section [@sec:generic:product-generic]
74 | we defined product encoders for `Rectangle` and `Circle`.
75 | Now, to write generic `CsvEncoders` for `:+:` and `CNil`,
76 | we can use the same principles we used for `HLists`:
77 |
78 | ```tut:book:silent
79 | import shapeless.{Coproduct, :+:, CNil, Inl, Inr}
80 |
81 | implicit val cnilEncoder: CsvEncoder[CNil] =
82 | createEncoder(cnil => throw new Exception("Inconceivable!"))
83 |
84 | implicit def coproductEncoder[H, T <: Coproduct](
85 | implicit
86 | hEncoder: CsvEncoder[H],
87 | tEncoder: CsvEncoder[T]
88 | ): CsvEncoder[H :+: T] = createEncoder {
89 | case Inl(h) => hEncoder.encode(h)
90 | case Inr(t) => tEncoder.encode(t)
91 | }
92 | ```
93 |
94 | There are two key points of note:
95 |
96 | 1. Because `Coproducts` are *disjunctions* of types,
97 | the encoder for `:+:` has to *choose*
98 | whether to encode a left or right value.
99 | We pattern match on the two subtypes of `:+:`,
100 | which are `Inl` for left and `Inr` for right.
101 |
102 | 2. Alarmingly, the encoder for `CNil` throws an exception!
103 | Don't panic, though.
104 | Remember that we can't
105 | create values of type `CNil`,
106 | so the `throw` expression is dead code.
107 | It's ok to fail abruptly here because
108 | we will never reach this point.
109 |
110 | If we place these definitions
111 | alongside our product encoders from Section [@sec:generic:products],
112 | we should be able to serialize a list of shapes.
113 | Let's give it a try:
114 |
115 | ```tut:book:silent
116 | val shapes: List[Shape] = List(
117 | Rectangle(3.0, 4.0),
118 | Circle(1.0)
119 | )
120 | ```
121 |
122 | ```tut:book:fail
123 | writeCsv(shapes)
124 | ```
125 |
126 | Oh no, it failed!
127 | The error message is unhelpful as we discussed earlier.
128 | The reason for the failure is
129 | we don't have a `CsvEncoder` instance for `Double`:
130 |
131 | ```tut:book:silent
132 | implicit val doubleEncoder: CsvEncoder[Double] =
133 | createEncoder(d => List(d.toString))
134 | ```
135 |
136 | With this definition in place, everything works as expected:
137 |
138 | ```tut:book
139 | writeCsv(shapes)
140 | ```
141 |
142 |
143 | *SI-7046 and you*
144 |
145 | There is a Scala compiler bug called [SI-7046][link-si7046]
146 | that can cause coproduct generic resolution to fail.
147 | The bug causes certain parts of the macro API,
148 | on which shapeless depends, to be sensitive
149 | to the order of the definitions in our source code.
150 | Problems can often be worked around
151 | by reordering code and renaming files,
152 | but such workarounds tend to be volatile and unreliable.
153 |
154 | If you are using Lightbend Scala 2.11.8 or earlier
155 | and coproduct resolution fails for you,
156 | consider upgrading to Lightbend Scala 2.11.9
157 | or Typelevel Scala 2.11.8.
158 | SI-7046 is fixed in each of these releases.
159 |
160 |
161 | ### Aligning CSV output
162 |
163 | Our CSV encoder isn't very practical in its current form.
164 | It allows fields from `Rectangle` and `Circle` to
165 | occupy the same columns in the output.
166 | To fix this problem we need to modify
167 | the definition of `CsvEncoder`
168 | to incorporate the width of the data type
169 | and space the output accordingly.
170 | The examples repo linked
171 | in Section [@sec:intro:source-code]
172 | contains a complete implementation of `CsvEncoder`
173 | that addresses this problem.
174 |
--------------------------------------------------------------------------------
/src/pages/generic/debugging.md:
--------------------------------------------------------------------------------
1 | ## Debugging implicit resolution {#sec:generic:debugging}
2 |
3 | ```tut:book:invisible
4 | import shapeless._
5 |
6 | trait CsvEncoder[A] {
7 | val width: Int
8 | def encode(value: A): List[String]
9 | }
10 |
11 | object CsvEncoder {
12 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
13 | enc
14 | }
15 |
16 | def createEncoder[A](w: Int)(func: A => List[String]): CsvEncoder[A] =
17 | new CsvEncoder[A] {
18 | val width = w
19 | def encode(value: A): List[String] =
20 | func(value)
21 | }
22 |
23 | implicit val stringEncoder: CsvEncoder[String] =
24 | createEncoder(1)(str => List(str))
25 |
26 | implicit val intEncoder: CsvEncoder[Int] =
27 | createEncoder(1)(num => List(num.toString))
28 |
29 | implicit val doubleEncoder: CsvEncoder[Double] =
30 | createEncoder(1)(num => List(num.toString))
31 |
32 | implicit val booleanEncoder: CsvEncoder[Boolean] =
33 | createEncoder(1)(bool => List(if(bool) "yes" else "no"))
34 |
35 | implicit def optionEncoder[A](implicit encoder: CsvEncoder[A]): CsvEncoder[Option[A]] =
36 | createEncoder(encoder.width)(opt => opt.map(encoder.encode).getOrElse(List.fill(encoder.width)("")))
37 |
38 | implicit val hnilEncoder: CsvEncoder[HNil] =
39 | createEncoder(0)(hnil => Nil)
40 |
41 | implicit def hlistEncoder[H, T <: HList](
42 | implicit
43 | hEncoder: Lazy[CsvEncoder[H]],
44 | tEncoder: CsvEncoder[T]
45 | ): CsvEncoder[H :: T] =
46 | createEncoder(hEncoder.value.width + tEncoder.width) {
47 | case h :: t =>
48 | hEncoder.value.encode(h) ++ tEncoder.encode(t)
49 | }
50 |
51 | implicit val cnilEncoder: CsvEncoder[CNil] =
52 | createEncoder(0)(cnil => ???)
53 |
54 | implicit def coproductEncoder[H, T <: Coproduct](
55 | implicit
56 | hEncoder: Lazy[CsvEncoder[H]],
57 | tEncoder: CsvEncoder[T]
58 | ): CsvEncoder[H :+: T] =
59 | createEncoder(hEncoder.value.width + tEncoder.width) {
60 | case Inl(h) => hEncoder.value.encode(h) ++ List.fill(tEncoder.width)("")
61 | case Inr(t) => List.fill(hEncoder.value.width)("") ++ tEncoder.encode(t)
62 | }
63 |
64 | implicit def genericEncoder[A, R](
65 | implicit
66 | gen: Generic.Aux[A, R],
67 | enc: Lazy[CsvEncoder[R]]
68 | ): CsvEncoder[A] =
69 | createEncoder(enc.value.width)(a => enc.value.encode(gen.to(a)))
70 | ```
71 |
72 | Failures in implicit resolution
73 | can be confusing and frustrating.
74 | Here are a couple of techniques to use
75 | when implicits go bad.
76 |
77 | ### Debugging using *implicitly*
78 |
79 | What can we do when the compiler
80 | simply fails to find an implicit value?
81 | The failure could be caused by
82 | the resolution of any one of the implicits in use.
83 | For example:
84 |
85 | ```tut:book:silent
86 | case class Foo(bar: Int, baz: Float)
87 | ```
88 |
89 | ```tut:book:fail
90 | CsvEncoder[Foo]
91 | ```
92 |
93 | The reason for the failure is that
94 | we haven't defined a `CsvEncoder` for `Float`.
95 | However, this may not be obvious in application code.
96 | We can work through the expected expansion sequence
97 | to find the source of the error,
98 | inserting calls to `CsvEncoder.apply` or `implicitly`
99 | above the error to see if they compile.
100 | We start with the generic representation of `Foo`:
101 |
102 | ```tut:book:fail
103 | CsvEncoder[Int :: Float :: HNil]
104 | ```
105 |
106 | This fails so we know we have to search deeper in the expansion.
107 | The next step is to try the components of the `HList`:
108 |
109 | ```tut:book:silent
110 | CsvEncoder[Int]
111 | ```
112 |
113 | ```tut:book:fail
114 | CsvEncoder[Float]
115 | ```
116 |
117 | `Int` passes but `Float` fails.
118 | `CsvEncoder[Float]` is a leaf in our tree of expansions,
119 | so we know to start by implementing this missing instance.
120 | If adding the instance doesn't fix the problem
121 | we repeat the process to find the next point of failure.
122 |
123 | ### Debugging using *reify*
124 |
125 | The `reify` method from `scala.reflect`
126 | takes a Scala expression as a parameter and returns
127 | an AST object representing the expression tree,
128 | complete with type annotations:
129 |
130 | ```tut:book:silent
131 | import scala.reflect.runtime.universe._
132 | ```
133 |
134 | ```tut:book
135 | println(reify(CsvEncoder[Int]))
136 | ```
137 |
138 | The types inferred during implicit resolution
139 | can give us hints about problems.
140 | After implicit resolution,
141 | any remaining existential types such as `A` or `T`
142 | provide a sign that something has gone wrong.
143 | Similarly, "top" and "bottom" types such as `Any` and `Nothing`
144 | are evidence of failure.
145 |
--------------------------------------------------------------------------------
/src/pages/generic/exercises.md.disabled:
--------------------------------------------------------------------------------
1 |
2 | ### Exercise: Aligning columns in CSV output
3 |
4 | It would perhaps be better if we separated
5 | the data for rectangles and circles
6 | into two separate sets of columns.
7 | We can do this by adding a `width` field to `CsvEncoder`:
8 |
9 | ```disabledtut:book:silent
10 | trait CsvEncoder[A] {
11 | def width: Int
12 | def encode(value: A): List[String]
13 | }
14 | ```
15 |
16 | If we follow through with all of our definitions,
17 | we can produce instances
18 | that place each field in the ADT in a different column.
19 | We will leave this as an exercise to the reader.
20 |
21 |
22 | We start by modifying the definition of `createEncoder`
23 | to accept a `width` parameter:
24 |
25 | ```disabledtut:book:silent
26 | def createEncoder[A](cols: Int)(func: A => List[String]): CsvEncoder[A] =
27 | new CsvEncoder[A] {
28 | val width = cols
29 | def encode(value: A): List[String] =
30 | func(value)
31 | }
32 | ```
33 |
34 | Then we modify our base encoders to each record a width of `1`:
35 |
36 | ```disabledtut:book:silent
37 | implicit val stringEncoder: CsvEncoder[String] =
38 | createEncoder(1)(str => List(str))
39 |
40 | implicit val intEncoder: CsvEncoder[Int] =
41 | createEncoder(1)(num => List(num.toString))
42 |
43 | implicit val booleanEncoder: CsvEncoder[Boolean] =
44 | createEncoder(1)(bool => List(if(bool) "cone" else "glass"))
45 |
46 | implicit val doubleEncoder: CsvEncoder[Double] =
47 | createEncoder(1)(d => List(d.toString))
48 | ```
49 |
50 | Our encoders for `HNil` and `CNil` have width `0` and our
51 | encoders for `::` and `:+:` have a width determined by
52 | the encoders for their heads and tails:
53 |
54 | ```disabledtut:book:silent
55 | import shapeless.{HList, HNil, ::}
56 |
57 | implicit val hnilEncoder: CsvEncoder[HNil] =
58 | createEncoder(0)(hnil => Nil)
59 |
60 | implicit def hlistEncoder[H, T <: HList](
61 | implicit
62 | hEncoder: CsvEncoder[H],
63 | tEncoder: CsvEncoder[T]
64 | ): CsvEncoder[H :: T] =
65 | createEncoder(hEncoder.width + tEncoder.width) {
66 | case h :: t =>
67 | hEncoder.encode(h) ++ tEncoder.encode(t)
68 | }
69 | ```
70 |
71 | Our `:+:` encoder pads its output with a number of columns
72 | equal to the width of the encoder it isn't using for serialization:
73 |
74 | ```disabledtut:book:silent
75 | import shapeless.{Coproduct, CNil, :+:, Inl, Inr}
76 |
77 | implicit val cnilEncoder: CsvEncoder[CNil] =
78 | createEncoder(0) { cnil =>
79 | throw new Exception("The impossible has happened!")
80 | }
81 |
82 | implicit def coproductEncoder[H, T <: Coproduct](
83 | implicit
84 | hEncoder: CsvEncoder[H],
85 | tEncoder: CsvEncoder[T]
86 | ): CsvEncoder[H :+: T] =
87 | createEncoder(hEncoder.width + tEncoder.width) {
88 | case Inl(h) => hEncoder.encode(h) ++ List.fill(tEncoder.width)("")
89 | case Inr(t) => List.fill(hEncoder.width)("") ++ tEncoder.encode(t)
90 | }
91 | ```
92 |
93 | Finally, our ADT encoder mirrors the width of
94 | the encoder for its generic representation:
95 |
96 | ```disabledtut:book:silent
97 | import shapeless.Generic
98 |
99 | implicit def genericEncoder[A, R](
100 | implicit
101 | gen: Generic.Aux[A, R],
102 | lEncoder: CsvEncoder[R]
103 | ): CsvEncoder[A] =
104 | createEncoder(lEncoder.width) { value =>
105 | lEncoder.encode(gen.to(value))
106 | }
107 | ```
108 |
109 | With all these definitions in place,
110 | our `writeCsv` method gains the ability to align its output correctly:
111 |
112 | ```disabledtut:book:invisible
113 | def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
114 | values.map(encoder.encode).map(_.mkString(",")).mkString("\n")
115 | ```
116 |
117 | ```disabledtut:book
118 | writeCsv(shapes)
119 | ```
120 |
121 |
--------------------------------------------------------------------------------
/src/pages/generic/index.md:
--------------------------------------------------------------------------------
1 | # Automatically deriving type class instances {#sec:generic}
2 |
3 | In the last chapter we saw how the `Generic` type class
4 | allowed us to convert any instance of an ADT to
5 | a generic encoding made of `HLists` and `Coproducts`.
6 | In this chapter we will look at our first serious use case:
7 | automatic derivation of type class instances.
8 |
--------------------------------------------------------------------------------
/src/pages/generic/lazy.md:
--------------------------------------------------------------------------------
1 | ## Deriving instances for recursive types
2 |
3 | ```tut:book:invisible
4 | // ----------------------------------------------
5 | // Forward definitions
6 |
7 | trait CsvEncoder[A] {
8 | def encode(value: A): List[String]
9 | }
10 |
11 | object CsvEncoder {
12 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
13 | enc
14 | }
15 |
16 | def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
17 | values.map(encoder.encode).map(_.mkString(",")).mkString("\n")
18 |
19 | def createEncoder[A](func: A => List[String]): CsvEncoder[A] =
20 | new CsvEncoder[A] {
21 | def encode(value: A): List[String] =
22 | func(value)
23 | }
24 |
25 | implicit val stringEncoder: CsvEncoder[String] =
26 | createEncoder(str => List(str))
27 |
28 | implicit val intEncoder: CsvEncoder[Int] =
29 | createEncoder(num => List(num.toString))
30 |
31 | implicit val booleanEncoder: CsvEncoder[Boolean] =
32 | createEncoder(bool => List(if(bool) "cone" else "glass"))
33 |
34 | import shapeless.{HList, HNil, ::}
35 |
36 | implicit val hnilEncoder: CsvEncoder[HNil] =
37 | createEncoder(hnil => Nil)
38 |
39 | implicit def hlistEncoder[H, T <: HList](
40 | implicit
41 | hEncoder: CsvEncoder[H],
42 | tEncoder: CsvEncoder[T]
43 | ): CsvEncoder[H :: T] = createEncoder {
44 | case h :: t =>
45 | hEncoder.encode(h) ++ tEncoder.encode(t)
46 | }
47 |
48 | import shapeless.Generic
49 |
50 | implicit def genericEncoder[A, R](
51 | implicit
52 | gen: Generic.Aux[A, R],
53 | rEncoder: CsvEncoder[R]
54 | ): CsvEncoder[A] =
55 | createEncoder(value => rEncoder.encode(gen.to(value)))
56 |
57 | // ----------------------------------------------
58 | ```
59 |
60 | Let's try something more ambitious---a binary tree:
61 |
62 | ```tut:book:silent
63 | sealed trait Tree[A]
64 | case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
65 | case class Leaf[A](value: A) extends Tree[A]
66 | ```
67 |
68 | Theoretically we should already have
69 | all of the definitions in place
70 | to summon a CSV writer for this definition.
71 | However, calls to `writeCsv` fail to compile:
72 |
73 | ```tut:book:fail
74 | CsvEncoder[Tree[Int]]
75 | ````
76 |
77 | The problem is that our type is recursive.
78 | The compiler senses an infinite loop
79 | applying our implicits and gives up.
80 |
81 | ### Implicit divergence
82 |
83 | Implicit resolution is a search process.
84 | The compiler uses heuristics to determine
85 | whether it is "converging" on a solution.
86 | If the heuristics don't yield favorable results
87 | for a particular branch of search,
88 | the compiler assumes the branch is not converging
89 | and moves onto another.
90 |
91 | One heuristic is specifically designed
92 | to avoid infinite loops.
93 | If the compiler sees the same target type twice
94 | in a particular branch of search,
95 | it gives up and moves on.
96 | We can see this happening if
97 | we look at the expansion for `CsvEncoder[Tree[Int]]`
98 | The implicit resolution process
99 | goes through the following types:
100 |
101 | ```scala
102 | CsvEncoder[Tree[Int]] // 1
103 | CsvEncoder[Branch[Int] :+: Leaf[Int] :+: CNil] // 2
104 | CsvEncoder[Branch[Int]] // 3
105 | CsvEncoder[Tree[Int] :: Tree[Int] :: HNil] // 4
106 | CsvEncoder[Tree[Int]] // 5 uh oh
107 | ```
108 |
109 | We see `Tree[A]` twice in lines 1 and 5,
110 | so the compiler moves onto another branch of search.
111 | The eventual consequence is that
112 | it fails to find a suitable implicit.
113 |
114 | In fact, the situation is worse than this.
115 | If the compiler sees the same type constructor twice
116 | and the complexity of the type parameters is *increasing*,
117 | it assumes that branch of search is "diverging".
118 | This is a problem for shapeless
119 | because types like `::[H, T]` and `:+:[H, T]`
120 | can appear several times as the compiler expands
121 | different generic representations.
122 | This causes the compiler to give up prematurely
123 | even though it would eventually find a solution
124 | if it persisted with the same expansion.
125 | Consider the following types:
126 |
127 | ```tut:book:silent
128 | case class Bar(baz: Int, qux: String)
129 | case class Foo(bar: Bar)
130 | ```
131 |
132 | The expansion for `Foo` looks like this:
133 |
134 | ```scala
135 | CsvEncoder[Foo] // 1
136 | CsvEncoder[Bar :: HNil] // 2
137 | CsvEncoder[Bar] // 3
138 | CsvEncoder[Int :: String :: HNil] // 4 uh oh
139 | ```
140 |
141 | The compiler attempts to resolve a `CsvEncoder[::[H, T]]`
142 | twice in this branch of search, on lines 2 and 4.
143 | The type parameter for `T` is more complex on line 4 than on line 2,
144 | so the compiler assumes (incorrectly in this case)
145 | that the branch of search is diverging.
146 | It moves onto another branch and, again,
147 | the result is failure to generate a suitable instance.
148 |
149 | ### *Lazy*
150 |
151 | Implicit divergence would be a show-stopper
152 | for libraries like shapeless.
153 | Fortunately, shapeless provides
154 | a type called `Lazy` as a workaround.
155 | `Lazy` does two things:
156 |
157 | 1. it suppresses implicit divergence at compile time
158 | by guarding against the aforementioned
159 | over-defensive convergence heuristics;
160 |
161 | 2. it defers evaluation of the implicit parameter at runtime,
162 | permitting the derivation of self-referential implicits.
163 |
164 | We use `Lazy` by wrapping it around specific implicit parameters.
165 | As a rule of thumb, it is always a good idea to wrap
166 | the "head" parameter of any `HList` or `Coproduct` rule
167 | and the `Repr` parameter of any `Generic` rule in `Lazy`:
168 |
169 | ```tut:book:invisible:reset
170 | // Forward definitions -------------------------
171 | import shapeless._
172 |
173 | trait CsvEncoder[A] {
174 | def encode(value: A): List[String]
175 | }
176 |
177 | object CsvEncoder {
178 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
179 | enc
180 | }
181 |
182 | def createEncoder[A](func: A => List[String]): CsvEncoder[A] =
183 | new CsvEncoder[A] {
184 | def encode(value: A): List[String] =
185 | func(value)
186 | }
187 |
188 | implicit val intEncoder: CsvEncoder[Int] =
189 | createEncoder(num => List(num.toString))
190 |
191 | implicit val hnilEncoder: CsvEncoder[HNil] =
192 | createEncoder(hnil => Nil)
193 |
194 | implicit val cnilEncoder: CsvEncoder[CNil] =
195 | createEncoder(cnil => throw new Exception("Inconceivable!"))
196 |
197 | // ----------------------------------------------
198 | ```
199 |
200 | ```tut:book:silent
201 | implicit def hlistEncoder[H, T <: HList](
202 | implicit
203 | hEncoder: Lazy[CsvEncoder[H]], // wrap in Lazy
204 | tEncoder: CsvEncoder[T]
205 | ): CsvEncoder[H :: T] = createEncoder {
206 | case h :: t =>
207 | hEncoder.value.encode(h) ++ tEncoder.encode(t)
208 | }
209 | ```
210 |
211 | ```tut:book:silent
212 | implicit def coproductEncoder[H, T <: Coproduct](
213 | implicit
214 | hEncoder: Lazy[CsvEncoder[H]], // wrap in Lazy
215 | tEncoder: CsvEncoder[T]
216 | ): CsvEncoder[H :+: T] = createEncoder {
217 | case Inl(h) => hEncoder.value.encode(h)
218 | case Inr(t) => tEncoder.encode(t)
219 | }
220 | ```
221 |
222 | ```tut:book:silent
223 | implicit def genericEncoder[A, R](
224 | implicit
225 | gen: Generic.Aux[A, R],
226 | rEncoder: Lazy[CsvEncoder[R]] // wrap in Lazy
227 | ): CsvEncoder[A] = createEncoder { value =>
228 | rEncoder.value.encode(gen.to(value))
229 | }
230 | ```
231 |
232 | ```tut:book:invisible
233 | sealed trait Tree[A]
234 | final case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
235 | final case class Leaf[A](value: A) extends Tree[A]
236 | ```
237 |
238 | This prevents the compiler giving up prematurely,
239 | and enables the solution to work
240 | on complex/recursive types like `Tree`:
241 |
242 | ```tut:book
243 | CsvEncoder[Tree[Int]]
244 | ```
245 |
--------------------------------------------------------------------------------
/src/pages/generic/products.md:
--------------------------------------------------------------------------------
1 | ## Deriving instances for products {#sec:generic:products}
2 |
3 | In this section we're going to use shapeless
4 | to derive type class instances for product types
5 | (i.e. case classes).
6 | We'll use two intuitions:
7 |
8 | 1. If we have type class instances
9 | for the head and tail of an `HList`,
10 | we can derive an instance for the whole `HList`.
11 |
12 | 2. If we have a case class `A`, a `Generic[A]`,
13 | and a type class instance for the generic's `Repr`,
14 | we can combine them to create an instance for `A`.
15 |
16 | Take `CsvEncoder` and `IceCream` as examples:
17 |
18 | - `IceCream` has a generic `Repr` of type
19 | `String :: Int :: Boolean :: HNil`.
20 |
21 | - The `Repr` is made up of
22 | a `String`, an `Int`, a `Boolean`, and an `HNil`.
23 | If we have `CsvEncoders` for these types,
24 | we can create an encoder for the whole thing.
25 |
26 | - If we can derive a `CsvEncoder` for the `Repr`,
27 | we can create one for `IceCream`.
28 |
29 | ```tut:book:invisible
30 | // ----------------------------------------------
31 | // Forward definitions
32 |
33 | trait CsvEncoder[A] {
34 | def encode(value: A): List[String]
35 | }
36 |
37 | object CsvEncoder {
38 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
39 | enc
40 | }
41 |
42 | def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
43 | values.map(encoder.encode).map(_.mkString(",")).mkString("\n")
44 |
45 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
46 |
47 | val iceCreams: List[IceCream] = List(
48 | IceCream("Sundae", 1, false),
49 | IceCream("Cornetto", 0, true),
50 | IceCream("Banana Split", 0, false)
51 | )
52 |
53 | case class Employee(name: String, number: Int, manager: Boolean)
54 |
55 | val employees: List[Employee] = List(
56 | Employee("Bill", 1, true),
57 | Employee("Peter", 2, false),
58 | Employee("Milton", 3, false)
59 | )
60 | // ----------------------------------------------
61 | ```
62 |
63 | ### Instances for *HLists*
64 |
65 | Let's start by defining an instance constructor
66 | and `CsvEncoders` for `String`, `Int`, and `Boolean`:
67 |
68 | ```tut:book:silent
69 | def createEncoder[A](func: A => List[String]): CsvEncoder[A] =
70 | new CsvEncoder[A] {
71 | def encode(value: A): List[String] = func(value)
72 | }
73 |
74 | implicit val stringEncoder: CsvEncoder[String] =
75 | createEncoder(str => List(str))
76 |
77 | implicit val intEncoder: CsvEncoder[Int] =
78 | createEncoder(num => List(num.toString))
79 |
80 | implicit val booleanEncoder: CsvEncoder[Boolean] =
81 | createEncoder(bool => List(if(bool) "yes" else "no"))
82 | ```
83 |
84 | We can combine these building blocks
85 | to create an encoder for our `HList`.
86 | We'll use two rules:
87 | one for `HNil` and one for `::` as shown below:
88 |
89 | ```tut:book:silent
90 | import shapeless.{HList, ::, HNil}
91 |
92 | implicit val hnilEncoder: CsvEncoder[HNil] =
93 | createEncoder(hnil => Nil)
94 |
95 | implicit def hlistEncoder[H, T <: HList](
96 | implicit
97 | hEncoder: CsvEncoder[H],
98 | tEncoder: CsvEncoder[T]
99 | ): CsvEncoder[H :: T] =
100 | createEncoder {
101 | case h :: t =>
102 | hEncoder.encode(h) ++ tEncoder.encode(t)
103 | }
104 | ```
105 |
106 | Taken together, these five instances
107 | allow us to summon `CsvEncoders` for any `HList`
108 | involving `Strings`, `Ints`, and `Booleans`:
109 |
110 | ```tut:book:silent
111 | val reprEncoder: CsvEncoder[String :: Int :: Boolean :: HNil] =
112 | implicitly
113 | ```
114 |
115 | ```tut:book
116 | reprEncoder.encode("abc" :: 123 :: true :: HNil)
117 | ```
118 |
119 | ### Instances for concrete products {#sec:generic:product-generic}
120 |
121 | We can combine our derivation rules for `HLists`
122 | with an instance of `Generic`
123 | to produce a `CsvEncoder` for `IceCream`:
124 |
125 | ```tut:book:silent
126 | import shapeless.Generic
127 |
128 | implicit val iceCreamEncoder: CsvEncoder[IceCream] = {
129 | val gen = Generic[IceCream]
130 | val enc = CsvEncoder[gen.Repr]
131 | createEncoder(iceCream => enc.encode(gen.to(iceCream)))
132 | }
133 | ```
134 |
135 | and use it as follows:
136 |
137 | ```tut:book
138 | writeCsv(iceCreams)
139 | ```
140 |
141 | This solution is specific to `IceCream`.
142 | Ideally we'd like to have a single rule
143 | that handles all case classes
144 | that have a `Generic` and a matching `CsvEncoder`.
145 | Let's work through the derivation step by step.
146 | Here's a first cut:
147 |
148 | ```scala
149 | implicit def genericEncoder[A](
150 | implicit
151 | gen: Generic[A],
152 | enc: CsvEncoder[???]
153 | ): CsvEncoder[A] = createEncoder(a => enc.encode(gen.to(a)))
154 | ```
155 |
156 | The first problem we have is
157 | selecting a type to put in place of the `???`.
158 | We want to write the `Repr` type associated with `gen`,
159 | but we can't do this:
160 |
161 | ```tut:book:fail
162 | implicit def genericEncoder[A](
163 | implicit
164 | gen: Generic[A],
165 | enc: CsvEncoder[gen.Repr]
166 | ): CsvEncoder[A] =
167 | createEncoder(a => enc.encode(gen.to(a)))
168 | ```
169 |
170 | The problem here is a scoping issue:
171 | we can't refer to a type member of one parameter
172 | from another parameter in the same block.
173 | The trick to solving this is
174 | to introduce a new type parameter to our method
175 | and refer to it in each of the associated value parameters:
176 |
177 | ```tut:book:silent
178 | implicit def genericEncoder[A, R](
179 | implicit
180 | gen: Generic[A] { type Repr = R },
181 | enc: CsvEncoder[R]
182 | ): CsvEncoder[A] =
183 | createEncoder(a => enc.encode(gen.to(a)))
184 | ```
185 |
186 | We'll cover this coding style in more detail in the next chapter.
187 | Suffice to say, this definition now compiles and works as expected
188 | and we can use it with any case class as expected.
189 | Intuitively, this definition says:
190 |
191 | > *Given a type `A` and an `HList` type `R`,
192 | > an implicit `Generic` to map `A` to `R`,
193 | > and a `CsvEncoder` for `R`,
194 | > create a `CsvEncoder` for `A`.*
195 |
196 | We now have a complete system that handles any case class.
197 | The compiler expands a call like:
198 |
199 | ```tut:book:silent
200 | writeCsv(iceCreams)
201 |
202 | ```
203 |
204 | to use our family of derivation rules:
205 |
206 | ```tut:book:silent
207 | writeCsv(iceCreams)(
208 | genericEncoder(
209 | Generic[IceCream],
210 | hlistEncoder(stringEncoder,
211 | hlistEncoder(intEncoder,
212 | hlistEncoder(booleanEncoder, hnilEncoder)))))
213 | ```
214 |
215 | and can infer the correct expansions
216 | for many different product types.
217 | I'm sure you'll agree,
218 | it's nice not to have to write this code by hand!
219 |
220 |
221 | *Aux type aliases*
222 |
223 | Type refinements like `Generic[A] { type Repr = L }`
224 | are verbose and difficult to read,
225 | so shapeless provides a type alias `Generic.Aux`
226 | to rephrase the type member as a type parameter:
227 |
228 | ```scala
229 | package shapeless
230 |
231 | object Generic {
232 | type Aux[A, R] = Generic[A] { type Repr = R }
233 | }
234 | ```
235 |
236 | Using this alias we get a much more readable definition:
237 |
238 | ```tut:book:silent
239 | implicit def genericEncoder[A, R](
240 | implicit
241 | gen: Generic.Aux[A, R],
242 | env: CsvEncoder[R]
243 | ): CsvEncoder[A] =
244 | createEncoder(a => env.encode(gen.to(a)))
245 | ```
246 |
247 | Note that the `Aux` type isn't changing any semantics---it's
248 | just making things easier to read.
249 | This "`Aux` pattern" is used frequently
250 | in the shapeless codebase.
251 |
252 |
253 | ### So what are the downsides?
254 |
255 | If all of the above seems pretty magical,
256 | allow us to provide one significant dose of reality.
257 | If things go wrong, the compiler isn't great at telling us why.
258 |
259 | There are two main reasons the code above might fail to compile.
260 | The first is when the compiler can't find
261 | an instance of `Generic`.
262 | For example, here we try to call `writeCsv`
263 | with a non-case class:
264 |
265 | ```tut:book:silent
266 | class Foo(bar: String, baz: Int)
267 | ```
268 |
269 | ```tut:book:fail
270 | writeCsv(List(new Foo("abc", 123)))
271 | ```
272 |
273 | In this case the error message is relatively easy to understand.
274 | If shapeless can't calculate a `Generic`
275 | it means that the type in question isn't an ADT---somewhere
276 | in the algebra there is a type that isn't a case class
277 | or a sealed abstract type.
278 |
279 | The other potential source of failure
280 | is when the compiler can't calculate
281 | a `CsvEncoder` for our `HList`.
282 | This normally happens because we don't have
283 | an encoder for one of the fields in our ADT.
284 | For example, we haven't yet defined
285 | a `CsvEncoder` for `java.util.Date`,
286 | so the following code fails:
287 |
288 | ```tut:book:silent
289 | import java.util.Date
290 |
291 | case class Booking(room: String, date: Date)
292 | ```
293 |
294 | ```tut:book:fail
295 | writeCsv(List(Booking("Lecture hall", new Date())))
296 | ```
297 |
298 | The message we get here isn't very helpful.
299 | All the compiler knows is
300 | it tried a lot of combinations of implicits
301 | and couldn't make them work.
302 | It has no idea which combination came closest to the desired result,
303 | so it can't tell us where the source(s) of failure lie.
304 |
305 | There's not much good news here.
306 | We have to find the source of the error ourselves
307 | by a process of elimination.
308 | We'll discuss debugging techniques
309 | in Section [@sec:generic:debugging].
310 | For now, the main redeeming feature
311 | is that implicit resolution always fails at compile time.
312 | There's little chance that we will end up
313 | with code that fails during execution.
314 |
--------------------------------------------------------------------------------
/src/pages/generic/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we discussed how to use
4 | `Generic`, `HLists`, and `Coproducts`
5 | to automatically derive type class instances.
6 | We also covered the `Lazy` type
7 | as a means of handling complex/recursive types.
8 | Taking all of this into account,
9 | we can write a common skeleton
10 | for deriving type class instances as follows.
11 |
12 | First, define the type class:
13 |
14 | ```tut:book:silent
15 | trait MyTC[A]
16 | ```
17 |
18 | Define primitive instances:
19 |
20 | ```tut:book:silent
21 | implicit def intInstance: MyTC[Int] = ???
22 | implicit def stringInstance: MyTC[String] = ???
23 | implicit def booleanInstance: MyTC[Boolean] = ???
24 | ```
25 |
26 | Define instances for `HList`:
27 |
28 | ```tut:book:silent
29 | import shapeless._
30 |
31 | implicit def hnilInstance: MyTC[HNil] = ???
32 |
33 | implicit def hlistInstance[H, T <: HList](
34 | implicit
35 | hInstance: Lazy[MyTC[H]], // wrap in Lazy
36 | tInstance: MyTC[T]
37 | ): MyTC[H :: T] = ???
38 | ```
39 |
40 | If required, define instances for `Coproduct`:
41 |
42 | ```tut:book:silent
43 | implicit def cnilInstance: MyTC[CNil] = ???
44 |
45 | implicit def coproductInstance[H, T <: Coproduct](
46 | implicit
47 | hInstance: Lazy[MyTC[H]], // wrap in Lazy
48 | tInstance: MyTC[T]
49 | ): MyTC[H :+: T] = ???
50 | ```
51 |
52 | Finally, define an instance for `Generic`:
53 |
54 | ```tut:book:silent
55 | implicit def genericInstance[A, R](
56 | implicit
57 | generic: Generic.Aux[A, R],
58 | rInstance: Lazy[MyTC[R]] // wrap in Lazy
59 | ): MyTC[A] = ???
60 | ```
61 |
62 | In the next chapter we'll cover some useful theory
63 | and programming patterns
64 | to help write code in this style.
65 | In Chapter [@sec:labelled-generic]
66 | we will revisit type class derivation
67 | using a variant of `Generic` that
68 | allows us to inspect field and type names
69 | in our ADTs.
70 |
--------------------------------------------------------------------------------
/src/pages/generic/type-classes.md:
--------------------------------------------------------------------------------
1 | ## Recap: type classes {#sec:generic:type-classes}
2 |
3 | Before we get into the depths of instance derivation,
4 | let's quickly recap on the important aspects of type classes.
5 |
6 | Type classes are a programming pattern borrowed from Haskell
7 | (the word "class" has nothing to do with
8 | classes in object oriented programming).
9 | We encode them in Scala using traits and implicits.
10 | A *type class* is a parameterised trait
11 | representing some sort of general functionality
12 | that we would like to apply to a wide range of types:
13 |
14 | ```tut:book:silent
15 | // Turn a value of type A into a row of cells in a CSV file:
16 | trait CsvEncoder[A] {
17 | def encode(value: A): List[String]
18 | }
19 | ```
20 |
21 | We implement our type class with *instances*
22 | for each type we care about.
23 | If we want the instances to automatically be in scope
24 | we can place them in the type class' companion object.
25 | Otherwise we can place them in a separate library object
26 | for the user to import manually:
27 |
28 | ```tut:book:silent
29 | // Custom data type:
30 | case class Employee(name: String, number: Int, manager: Boolean)
31 |
32 | // CsvEncoder instance for the custom data type:
33 | implicit val employeeEncoder: CsvEncoder[Employee] =
34 | new CsvEncoder[Employee] {
35 | def encode(e: Employee): List[String] =
36 | List(
37 | e.name,
38 | e.number.toString,
39 | if(e.manager) "yes" else "no"
40 | )
41 | }
42 | ```
43 |
44 | We mark each instance with the keyword `implicit`,
45 | and define one or more entry point methods
46 | that accept an implicit parameter of the corresponding type:
47 |
48 | ```tut:book:silent
49 | def writeCsv[A](values: List[A])(implicit enc: CsvEncoder[A]): String =
50 | values.map(value => enc.encode(value).mkString(",")).mkString("\n")
51 | ```
52 |
53 | We'll test `writeCsv` with some test data:
54 |
55 | ```tut:book:silent
56 | val employees: List[Employee] = List(
57 | Employee("Bill", 1, true),
58 | Employee("Peter", 2, false),
59 | Employee("Milton", 3, false)
60 | )
61 | ```
62 |
63 | When we call `writeCsv`,
64 | the compiler calculates the value of the type parameter
65 | and searches for an implicit `CsvEncoder`
66 | of the corresponding type:
67 |
68 | ```tut:book
69 | writeCsv(employees)
70 | ```
71 |
72 | We can use `writeCsv` with any data type we like,
73 | provided we have a corresponding implicit `CsvEncoder` in scope:
74 |
75 | ```tut:book:silent
76 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
77 |
78 | implicit val iceCreamEncoder: CsvEncoder[IceCream] =
79 | new CsvEncoder[IceCream] {
80 | def encode(i: IceCream): List[String] =
81 | List(
82 | i.name,
83 | i.numCherries.toString,
84 | if(i.inCone) "yes" else "no"
85 | )
86 | }
87 |
88 | val iceCreams: List[IceCream] = List(
89 | IceCream("Sundae", 1, false),
90 | IceCream("Cornetto", 0, true),
91 | IceCream("Banana Split", 0, false)
92 | )
93 | ```
94 |
95 | ```tut:book
96 | writeCsv(iceCreams)
97 | ```
98 |
99 | ### Resolving instances
100 |
101 | Type classes are very flexible
102 | but they require us to define instances
103 | for every type we care about.
104 | Fortunately, the Scala compiler has a few tricks up its sleeve
105 | to resolve instances for us given sets of user-defined rules.
106 | For example, we can write a rule
107 | that creates a `CsvEncoder` for `(A, B)`
108 | given `CsvEncoders` for `A` and `B`:
109 |
110 | ```tut:book:silent
111 | implicit def pairEncoder[A, B](
112 | implicit
113 | aEncoder: CsvEncoder[A],
114 | bEncoder: CsvEncoder[B]
115 | ): CsvEncoder[(A, B)] =
116 | new CsvEncoder[(A, B)] {
117 | def encode(pair: (A, B)): List[String] = {
118 | val (a, b) = pair
119 | aEncoder.encode(a) ++ bEncoder.encode(b)
120 | }
121 | }
122 | ```
123 |
124 | When all the parameters to an `implicit def`
125 | are themselves marked as `implicit`,
126 | the compiler can use it as a resolution rule
127 | to create instances from other instances.
128 | For example, if we call `writeCsv`
129 | and pass in a `List[(Employee, IceCream)]`,
130 | the compiler is able to combine
131 | `pairEncoder`, `employeeEncoder`, and `iceCreamEncoder`
132 | to produce the required `CsvEncoder[(Employee, IceCream)]`:
133 |
134 | ```tut:book
135 | writeCsv(employees zip iceCreams)
136 | ```
137 |
138 | Given a set of rules
139 | encoded as `implicit vals` and `implicit defs`,
140 | the compiler is capable of *searching* for
141 | combinations to give it the required instances.
142 | This behaviour, known as "implicit resolution",
143 | is what makes the type class pattern so powerful in Scala.
144 |
145 | Even with this power,
146 | the compiler can't pull apart
147 | our case classes and sealed traits.
148 | We are required to define instances for ADTs by hand.
149 | Shapeless' generic representations change all of this,
150 | allowing us to derive instances for any ADT for free.
151 |
152 | ### Idiomatic type class definitions {#sec:generic:idiomatic-style}
153 |
154 | The commonly accepted idiomatic style for type class definitions
155 | includes a companion object containing some standard methods:
156 |
157 | ```tut:book:silent
158 | object CsvEncoder {
159 | // "Summoner" method
160 | def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
161 | enc
162 |
163 | // "Constructor" method
164 | def instance[A](func: A => List[String]): CsvEncoder[A] =
165 | new CsvEncoder[A] {
166 | def encode(value: A): List[String] =
167 | func(value)
168 | }
169 |
170 | // Globally visible type class instances
171 | }
172 | ```
173 |
174 | The `apply` method, known as a "summoner" or "materializer",
175 | allows us to summon a type class instance given a target type:
176 |
177 | ```tut:book
178 | CsvEncoder[IceCream]
179 | ```
180 |
181 | In simple cases the summoner does the same job as
182 | the `implicitly` method defined in `scala.Predef`:
183 |
184 | ```tut:book
185 | implicitly[CsvEncoder[IceCream]]
186 | ```
187 |
188 | However,
189 | as we will see in Section [@sec:type-level-programming:depfun],
190 | when working with shapeless we encounter situations
191 | where `implicitly` doesn't infer types correctly.
192 | We can always define the summoner method to do the right thing,
193 | so it's worth writing one for every type class we create.
194 | We can also use a special method from shapeless called "`the`"
195 | (more on this later):
196 |
197 | ```tut:book:silent
198 | import shapeless._
199 | ```
200 |
201 | ```tut:book
202 | the[CsvEncoder[IceCream]]
203 | ```
204 |
205 | The `instance` method, sometimes named `pure`,
206 | provides a terse syntax for creating new type class instances,
207 | reducing the boilerplate of anonymous class syntax:
208 |
209 | ```tut:book:silent
210 | implicit val booleanEncoder: CsvEncoder[Boolean] =
211 | new CsvEncoder[Boolean] {
212 | def encode(b: Boolean): List[String] =
213 | if(b) List("yes") else List("no")
214 | }
215 | ```
216 |
217 | down to something much shorter:
218 |
219 | ```tut:book:invisible
220 | import CsvEncoder.instance
221 | ```
222 |
223 | ```tut:book:silent
224 | implicit val booleanEncoder: CsvEncoder[Boolean] =
225 | instance(b => if(b) List("yes") else List("no"))
226 | ```
227 |
228 | Unfortunately,
229 | several limitations of typesetting code in a book
230 | prevent us writing long singletons
231 | containing lots of methods and instances.
232 | We therefore tend to describe definitions
233 | outside of their context in the companion object.
234 | Bear this in mind as you read
235 | and check the accompanying repo
236 | linked in Section [@sec:intro:source-code]
237 | for complete worked examples.
238 |
--------------------------------------------------------------------------------
/src/pages/intro/contributors.md:
--------------------------------------------------------------------------------
1 | ## Acknowledgements
2 |
3 | Thanks to Miles Sabin, Richard Dallaway, Noel Welsh, and Travis Brown,
4 | for their invaluable contributions to this guide.
5 |
6 | Special thanks to Sam Halliday for this excellent workshop
7 | [Shapeless for Mortals][link-fommil-scalax-2015],
8 | which provided the initial inspiration and skeleton,
9 | and to Rob Norris and his fellow contributors
10 | for the awesome [Tut][link-tut],
11 | which keeps our examples compiling correctly.
12 |
13 | Finally, thanks to everyone who has contributed on Github.
14 | Here is an alphabetical list of contributors:
15 |
16 | Aaron S. Hawley,
17 | Aleksandr Vinokurov,
18 | Aristotelis Dossas,
19 | Chris Birchall,
20 | Dani Rey,
21 | Dennis Hunziker,
22 | ErunamoJAZZ,
23 | Evgeny Veretennikov,
24 | Jasper Moeys,
25 | Kevin Wright,
26 | ltbs,
27 | Matt Kohl,
28 | Maximilien Riehl,
29 | Mike Limansky,
30 | Philippus Baalman,
31 | Piotr Gołębiewski,
32 | Richard Dallaway,
33 | ronanM,
34 | s3ni0r,
35 | Tony Lottsm
36 | and Yoshimura Yuu
37 |
38 | A separately maintained French translation
39 | of this book is also available [on Github][link-fr].
40 | Thanks to etienne and fellow contributors
41 | for producing and maintaining it!
42 |
43 | If you spot an error or potential improvement,
44 | please raise an issue or submit a PR
45 | on the [Github page][link-book-repo].
46 |
--------------------------------------------------------------------------------
/src/pages/intro/generic.md:
--------------------------------------------------------------------------------
1 | ## What is generic programming?
2 |
3 | Types are helpful because they are specific:
4 | they show us how different pieces of code fit together,
5 | help us prevent bugs,
6 | and guide us toward solutions when we code.
7 |
8 | Sometimes, however, types are *too* specific.
9 | There are situations where we want
10 | to exploit similarities between types to avoid repetition.
11 | For example, consider the following definitions:
12 |
13 | ```tut:book:silent
14 | case class Employee(name: String, number: Int, manager: Boolean)
15 |
16 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
17 | ```
18 |
19 | These two case classes represent different kinds of data
20 | but they have clear similarities:
21 | they both contain three fields of the same types.
22 | Suppose we want to implement a generic operation
23 | such as serializing to a CSV file.
24 | Despite the similarity between the two types,
25 | we have to write two separate serialization methods:
26 |
27 | ```tut:book:silent
28 | def employeeCsv(e: Employee): List[String] =
29 | List(e.name, e.number.toString, e.manager.toString)
30 |
31 | def iceCreamCsv(c: IceCream): List[String] =
32 | List(c.name, c.numCherries.toString, c.inCone.toString)
33 | ```
34 |
35 | Generic programming is about overcoming differences like these.
36 | Shapeless makes it convenient to convert specific types
37 | into generic ones that we can manipulate with common code.
38 |
39 | For example, we can use the code below to
40 | convert employees and ice creams to values of the same type.
41 | Don't worry if you don't follow this example yet---we'll
42 | get to grips with the various concepts later on:
43 |
44 | ```tut:book:silent
45 | import shapeless._
46 | ```
47 |
48 | ```tut:book
49 | val genericEmployee = Generic[Employee].to(Employee("Dave", 123, false))
50 | val genericIceCream = Generic[IceCream].to(IceCream("Sundae", 1, false))
51 | ```
52 |
53 | Both values are now of the same type.
54 | They are both heterogeneous lists (`HLists` for short)
55 | containing a `String`, an `Int`, and a `Boolean`.
56 | We'll look at `HLists` and the important role they play soon.
57 | For now the point is that we can serialize each value
58 | with the same function:
59 |
60 | ```tut:book:silent
61 | def genericCsv(gen: String :: Int :: Boolean :: HNil): List[String] =
62 | List(gen(0), gen(1).toString, gen(2).toString)
63 | ```
64 |
65 | ```tut:book
66 | genericCsv(genericEmployee)
67 | genericCsv(genericIceCream)
68 | ```
69 |
70 | This example is basic
71 | but it hints at the essence of generic programming.
72 | We reformulate problems so we can solve them using generic building blocks,
73 | and write small kernels of code that work with a wide variety of types.
74 | Generic programming with shapeless
75 | allows us to eliminate huge amounts of boilerplate,
76 | making Scala applications easier to read, write, and maintain.
77 |
78 | Does that sound compelling? Thought so. Let's jump in!
79 |
--------------------------------------------------------------------------------
/src/pages/intro/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This book is a guide to using [shapeless][link-shapeless],
4 | a library for *generic programming* in Scala.
5 | Shapeless is a large library,
6 | so rather than cover everything it has to offer
7 | we will concentrate on a few compelling use cases
8 | and use them to build a picture
9 | of the tools and patterns available.
10 |
11 | Before we start, let's talk about what generic programming is
12 | and why shapeless is so exciting to Scala developers.
13 |
--------------------------------------------------------------------------------
/src/pages/intro/source-code.md:
--------------------------------------------------------------------------------
1 | ## Source code and examples {#sec:intro:source-code}
2 |
3 | This book is open source.
4 | You can find the [Markdown source on Github][link-book-repo].
5 | The book receives constant updates from the community
6 | so be sure to check the Github repo
7 | for the most up-to-date version.
8 |
9 | We also maintain a copy of the book
10 | on the [Underscore web site][link-underscore-book].
11 | If you grab a copy of the book from there
12 | we will notify you whenever we release an update.
13 |
14 | There are complete implementations of
15 | the major examples in an [accompanying repo][link-code-repo].
16 | See the README for installation details.
17 | We assume shapeless 2.3.2 and either
18 | Typelevel Scala 2.11.8+ or
19 | Lightbend Scala 2.11.9+ / 2.12.1+.
20 |
21 | Most of the examples in this book
22 | are compiled and executed using
23 | version 2.12.1 of the Typelevel Scala compiler.
24 | Among other niceties
25 | this version of Scala introduces *infix type printing*,
26 | which cleans up the console output on the REPL:
27 |
28 | ```tut:book:invisible
29 | import shapeless._
30 | ```
31 |
32 | ```tut:book
33 | val repr = "Hello" :: 123 :: true :: HNil
34 | ```
35 |
36 | If you are using an older version of Scala
37 | you might end up with prefix type printing like this:
38 |
39 | ```scala
40 | val repr = "Hello" :: 123 :: true :: HNil
41 | // repr: shapeless.::[String,shapeless.::[Int,shapeless.::[Boolean,shapeless.HNil]]] = "Hello" :: 123 :: true :: HNil
42 | ```
43 |
44 | Don't panic!
45 | Aside from the printed form of the result
46 | (infix versus prefix syntax),
47 | these types are the same.
48 | If you find the prefix types difficult to read,
49 | we recommend upgrading to a newer version of Scala.
50 | Simply add the following to your `build.sbt`,
51 | substituting in contemporary version numbers as appropriate:
52 |
53 | ```scala
54 | scalaOrganization := "org.typelevel"
55 | scalaVersion := "2.12.1"
56 | ```
57 |
58 | The `scalaOrganization` setting
59 | is only supported in SBT 0.13.13 or later.
60 | You can specify an SBT version
61 | by writing the following in `project/build.properties`
62 | (create the file if it isn't there in your project):
63 |
64 | ```
65 | sbt.version=0.13.13
66 | ```
67 |
--------------------------------------------------------------------------------
/src/pages/intro/thisbook.md:
--------------------------------------------------------------------------------
1 | ## About this book {#sec:intro:about-this-book}
2 |
3 | This book is divided into two parts.
4 |
5 | In Part I we introduce *type class derivation*,
6 | which allows us to create type class instances
7 | for any algebraic data type
8 | using only a handful of generic rules.
9 | Part I consists of four chapters:
10 |
11 | - In Chapter [@sec:representations]
12 | we introduce *generic representations*.
13 | We also introduce shapeless' `Generic` type class,
14 | which can produce a generic encoding
15 | for any case class or sealed trait.
16 |
17 | - In Chapter [@sec:generic] we use `Generic`
18 | to derive instances of a custom type class.
19 | We create an example type class
20 | to encode Scala data as
21 | Comma Separated Values (CSV),
22 | but the techniques we cover
23 | can be extended to many situations.
24 | We also introduce shapeless' `Lazy` type,
25 | which lets us handle recursive data like lists and trees.
26 |
27 | - In Chapter [@sec:type-level-programming]
28 | we introduce the theory and programming patterns we need
29 | to generalise the techniques from earlier chapters.
30 | Specifically we look at dependent types,
31 | dependently typed functions,
32 | and type level programming.
33 | This allows us to access
34 | more advanced applications of shapeless.
35 |
36 | - In Chapter [@sec:labelled-generic] we introduce `LabelledGeneric`,
37 | a variant of `Generic` that exposes field and type names
38 | as part of its generic representations.
39 | We also introduce additional theory:
40 | literal types, singleton types, phantom types, and type tagging.
41 | We demonstrate `LabelledGeneric` by creating
42 | a JSON encoder that preserves field and type names in its output.
43 |
44 | In Part II we introduce the "ops type classes"
45 | provided in the `shapeless.ops` package.
46 | Ops type classes form an extensive library of tools
47 | for manipulating generic representations.
48 | Rather than discuss every op in detail,
49 | we provide a theoretical primer in three chapters:
50 |
51 | - In Chapter [@sec:ops] we discuss
52 | the general layout of the ops type classes
53 | and provide an example
54 | that strings several simple ops together
55 | to form a powerful "case class migration" tool.
56 |
57 | - In Chapter [@sec:poly] we introduce
58 | *polymorphic functions*,
59 | also known as `Polys`,
60 | and show how they are used in
61 | ops type classes for mapping,
62 | flat mapping, and folding
63 | over generic representations.
64 |
65 | - Finally, in Chapter [@sec:nat] we introduce
66 | the `Nat` type that shapeless uses
67 | to represent natural numbers at the type level.
68 | We introduce several related ops type classes,
69 | and use `Nat` to develop
70 | our own version of Scalacheck's `Arbitrary`.
71 |
--------------------------------------------------------------------------------
/src/pages/labelled-generic/coproducts.md:
--------------------------------------------------------------------------------
1 | ## Deriving coproduct instances with *LabelledGeneric*
2 |
3 | ```tut:book:invisible:reset
4 | // ----------------------------------------------
5 | // Forward definitions:
6 |
7 | sealed trait JsonValue
8 | final case class JsonObject(fields: List[(String, JsonValue)]) extends JsonValue
9 | final case class JsonArray(items: List[JsonValue]) extends JsonValue
10 | final case class JsonString(value: String) extends JsonValue
11 | final case class JsonNumber(value: Double) extends JsonValue
12 | final case class JsonBoolean(value: Boolean) extends JsonValue
13 | case object JsonNull extends JsonValue
14 |
15 | trait JsonEncoder[A] {
16 | def encode(value: A): JsonValue
17 | }
18 |
19 | object JsonEncoder {
20 | def apply[A](implicit enc: JsonEncoder[A]): JsonEncoder[A] =
21 | enc
22 | }
23 |
24 | def createEncoder[A](func: A => JsonValue): JsonEncoder[A] =
25 | new JsonEncoder[A] {
26 | def encode(value: A): JsonValue =
27 | func(value)
28 | }
29 |
30 | implicit val stringEncoder: JsonEncoder[String] =
31 | createEncoder(str => JsonString(str))
32 |
33 | implicit val doubleEncoder: JsonEncoder[Double] =
34 | createEncoder(num => JsonNumber(num))
35 |
36 | implicit val intEncoder: JsonEncoder[Int] =
37 | createEncoder(num => JsonNumber(num))
38 |
39 | implicit val booleanEncoder: JsonEncoder[Boolean] =
40 | createEncoder(bool => JsonBoolean(bool))
41 |
42 | implicit def listEncoder[A](implicit encoder: JsonEncoder[A]): JsonEncoder[List[A]] =
43 | createEncoder(list => JsonArray(list.map(encoder.encode)))
44 |
45 | implicit def optionEncoder[A](implicit encoder: JsonEncoder[A]): JsonEncoder[Option[A]] =
46 | createEncoder(opt => opt.map(encoder.encode).getOrElse(JsonNull))
47 |
48 | trait JsonObjectEncoder[A] extends JsonEncoder[A] {
49 | def encode(value: A): JsonObject
50 | }
51 |
52 | def createObjectEncoder[A](func: A => JsonObject): JsonObjectEncoder[A] =
53 | new JsonObjectEncoder[A] {
54 | def encode(value: A): JsonObject =
55 | func(value)
56 | }
57 |
58 | import shapeless.{HList, ::, HNil, Witness, Lazy}
59 | import shapeless.labelled.FieldType
60 |
61 | implicit val hnilObjectEncoder: JsonObjectEncoder[HNil] =
62 | createObjectEncoder(hnil => JsonObject(Nil))
63 |
64 | implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList](
65 | implicit
66 | witness: Witness.Aux[K],
67 | hEncoder: Lazy[JsonEncoder[H]],
68 | tEncoder: JsonObjectEncoder[T]
69 | ): JsonObjectEncoder[FieldType[K, H] :: T] = {
70 | val fieldName: String = witness.value.name
71 | createObjectEncoder { hlist =>
72 | val head = hEncoder.value.encode(hlist.head)
73 | val tail = tEncoder.encode(hlist.tail)
74 | JsonObject((fieldName, head) :: tail.fields)
75 | }
76 | }
77 |
78 | import shapeless.LabelledGeneric
79 |
80 | implicit def genericObjectEncoder[A, H](
81 | implicit
82 | generic: LabelledGeneric.Aux[A, H],
83 | hEncoder: Lazy[JsonObjectEncoder[H]]
84 | ): JsonObjectEncoder[A] =
85 | createObjectEncoder(value => hEncoder.value.encode(generic.to(value)))
86 |
87 | sealed trait Shape
88 | final case class Rectangle(width: Double, height: Double) extends Shape
89 | final case class Circle(radius: Double) extends Shape
90 |
91 | // ----------------------------------------------
92 | ```
93 |
94 | Applying `LabelledGeneric` with `Coproducts`
95 | involves a mixture of the concepts we've covered already.
96 | Let's start by examining
97 | a `Coproduct` type derived by `LabelledGeneric`.
98 | We'll re-visit our `Shape` ADT from Chapter [@sec:generic]:
99 |
100 | ```tut:book:silent
101 | import shapeless.LabelledGeneric
102 |
103 | sealed trait Shape
104 | final case class Rectangle(width: Double, height: Double) extends Shape
105 | final case class Circle(radius: Double) extends Shape
106 | ```
107 |
108 | ```tut:book
109 | LabelledGeneric[Shape].to(Circle(1.0))
110 | ```
111 |
112 | Here is that `Coproduct` type in a more readable format:
113 |
114 | ```scala
115 | // Rectangle with KeyTag[Symbol with Tagged["Rectangle"], Rectangle] :+:
116 | // Circle with KeyTag[Symbol with Tagged["Circle"], Circle] :+:
117 | // CNil
118 | ```
119 |
120 | As you can see, the result is a `Coproduct` of the subtypes of `Shape`,
121 | each tagged with the type name.
122 | We can use this information to write `JsonEncoders` for `:+:` and `CNil`:
123 |
124 | ```tut:book:silent
125 | import shapeless.{Coproduct, :+:, CNil, Inl, Inr, Witness, Lazy}
126 | import shapeless.labelled.FieldType
127 |
128 | implicit val cnilObjectEncoder: JsonObjectEncoder[CNil] =
129 | createObjectEncoder(cnil => throw new Exception("Inconceivable!"))
130 |
131 | implicit def coproductObjectEncoder[K <: Symbol, H, T <: Coproduct](
132 | implicit
133 | witness: Witness.Aux[K],
134 | hEncoder: Lazy[JsonEncoder[H]],
135 | tEncoder: JsonObjectEncoder[T]
136 | ): JsonObjectEncoder[FieldType[K, H] :+: T] = {
137 | val typeName = witness.value.name
138 | createObjectEncoder {
139 | case Inl(h) =>
140 | JsonObject(List(typeName -> hEncoder.value.encode(h)))
141 |
142 | case Inr(t) =>
143 | tEncoder.encode(t)
144 | }
145 | }
146 | ```
147 |
148 | `coproductEncoder` follows the same pattern as `hlistEncoder`.
149 | We have three type parameters:
150 | `K` for the type name,
151 | `H` for the value at the head of the `HList`,
152 | and `T` for the value at the tail.
153 | We use `FieldType` and `:+:` in the result type
154 | to declare the relationships between the three,
155 | and we use a `Witness` to access the runtime value of the type name.
156 | The result is an object containing a single key/value pair,
157 | the key being the type name and the value the result:
158 |
159 | ```tut:book:silent
160 | val shape: Shape = Circle(1.0)
161 | ```
162 |
163 | ```tut:book
164 | JsonEncoder[Shape].encode(shape)
165 | ```
166 |
167 | Other encodings are possible with a little more work.
168 | We can add a `"type"` field to the output, for example,
169 | or even allow the user to configure the format.
170 | Sam Halliday's [spray-json-shapeless][link-spray-json-shapeless]
171 | is an excellent example of a codebase
172 | that is approachable while providing a great deal of flexibility.
173 |
--------------------------------------------------------------------------------
/src/pages/labelled-generic/index.md:
--------------------------------------------------------------------------------
1 | # Accessing names during implicit derivation {#sec:labelled-generic}
2 |
3 | Often, the type class instances we define
4 | need access to more than just types.
5 | In this chapter we will look at
6 | a variant of `Generic` called `LabelledGeneric`
7 | that gives us access to field names and type names.
8 |
9 | To begin with we have some theory to cover.
10 | `LabelledGeneric` uses some clever techniques
11 | to expose name information at the type level.
12 | To understand these techniques
13 | we must discuss *literal types*,
14 | *singleton types*,
15 | *phantom types*,
16 | and *type tagging*.
17 |
--------------------------------------------------------------------------------
/src/pages/labelled-generic/literal-types.md:
--------------------------------------------------------------------------------
1 | ## Literal types
2 |
3 | A Scala value may have multiple types.
4 | For example, the string `"hello"`
5 | has at least three types:
6 | `String`, `AnyRef`,
7 | and `Any`[^multiple-inheritance]:
8 |
9 | ```tut:book
10 | "hello" : String
11 | "hello" : AnyRef
12 | "hello" : Any
13 | ```
14 |
15 | [^multiple-inheritance]:
16 | `String` also has a bunch of other types
17 | like `Serializable` and `Comparable`
18 | but let's ignore those for now.
19 |
20 | Interestingly, `"hello"` also has another type:
21 | a "singleton type"
22 | that belongs exclusively to that one value.
23 | This is similar to the singleton type we get
24 | when we define a companion object:
25 |
26 | ```tut:book:silent
27 | object Foo
28 | ```
29 |
30 | ```tut:book
31 | Foo
32 | ```
33 |
34 | The type `Foo.type` is the type of `Foo`,
35 | and `Foo` is the only value with that type.
36 |
37 | Singleton types applied to literal values are called *literal types*.
38 | These have existed in Scala for a long time,
39 | but we don't normally interact with them
40 | because the default behaviour of the compiler is
41 | to "widen" literals to their nearest non-singleton type.
42 | For example, these two expressions are essentially equivalent:
43 |
44 | ```tut:book
45 | "hello"
46 |
47 | ("hello" : String)
48 | ```
49 |
50 | Shapeless provides a few tools for working with literal types.
51 | First, there is a `narrow` macro that converts a
52 | literal expression to a singleton-typed literal expression:
53 |
54 | ```tut:book:silent
55 | import shapeless.syntax.singleton._
56 | ```
57 |
58 | ```scala
59 | var x = 42.narrow
60 | // x: Int(42) = 42
61 | ```
62 |
63 | Note the type of `x` here: `Int(42)` is a literal type.
64 | It is a subtype of `Int` that only contains the value `42`.
65 | If we attempt to assign a different number to `x`,
66 | we get a compile error:
67 |
68 | ```scala
69 | x = 43
70 | // :16: error: type mismatch:
71 | // found : Int(43)
72 | // required: Int(42)
73 | // x = 43
74 | // ^
75 | ```
76 |
77 | However, `x` is still an `Int` according to normal subtyping rules.
78 | If we operate on `x` we get a regular type of result:
79 |
80 | ```tut:book:invisible
81 | var x: 42 = 42
82 | ```
83 |
84 | ```tut:book
85 | x + 1
86 | ```
87 |
88 | We can use `narrow` on any literal in Scala:
89 |
90 | ```scala
91 | 1.narrow
92 | // res7: Int(1) = 1
93 |
94 | true.narrow
95 | // res8: Boolean(true) = true
96 |
97 | "hello".narrow
98 | // res9: String("hello") = hello
99 |
100 | // and so on...
101 | ```
102 |
103 | ```tut:book:invisible
104 | 1 : 1
105 | true : true
106 | "hello" : "hello"
107 | ```
108 |
109 | However, we can't use it on compound expressions:
110 |
111 | ```scala
112 | math.sqrt(4).narrow
113 | // :17: error: Expression scala.math.`package`.sqrt(4.0) does not evaluate to a constant or a stable reference value
114 | // math.sqrt(4.0).narrow
115 | // ^
116 | // :17: error: value narrow is not a member of Double
117 | // math.sqrt(4.0).narrow
118 | // ^
119 | ```
120 |
121 |
122 | *Literal types in Scala*
123 |
124 | Until recently, Scala had no syntax for writing literal types.
125 | The types were there in the compiler
126 | but we couldn't express them directly in code.
127 | However, as of Typelevel Scala 2.11.8 we have
128 | direct syntax support for literal types which can be enabled via the `-Yliteral-types` compiler option.
129 |
130 | Lightbend Scala 2.13.0 will also gain literal types by default (with no compiler option needed or available) and allows you to write declarations like the following:
131 |
132 | ```tut:book
133 | val theAnswer: 42 = 42
134 | ```
135 |
136 | The type `42` is the same as the type `Int(42)`
137 | we saw in printed output earlier.
138 | You'll still see `Int(42)` in output for legacy reasons,
139 | but the canonical syntax going forward is `42`.
140 |
141 |
142 | ## Type tagging and phantom types {#sec:labelled-generic:type-tagging}
143 |
144 | Shapeless uses literal types
145 | to model the names of fields in case classes.
146 | It does this by "tagging" the types of the fields
147 | with the literal types of their names.
148 | Before we see how shapeless does this,
149 | we'll do it ourselves to show that there's no magic
150 | (well... minimal magic, at any rate).
151 | Suppose we have a number:
152 |
153 | ```tut:book:silent
154 | val number = 42
155 | ```
156 |
157 | This number is an `Int` in two worlds:
158 | at runtime, where it has an actual value
159 | and methods that we can call,
160 | and at compile-time,
161 | where the compiler uses the type
162 | to calculate which pieces of code work together
163 | and to search for implicits.
164 |
165 | We can modify the type of `number` at compile time
166 | without modifying its run-time behaviour
167 | by "tagging" it with a "phantom type".
168 | Phantom types are types with no run-time semantics,
169 | like this:
170 |
171 | ```tut:book:silent
172 | trait Cherries
173 | ```
174 |
175 | We can tag `number` using `asInstanceOf`.
176 | We end up with a value that is both
177 | an `Int` and a `Cherries` at compile-time,
178 | and an `Int` at run-time:
179 |
180 | ```tut:book
181 | val numCherries = number.asInstanceOf[Int with Cherries]
182 | ```
183 |
184 | Shapeless uses this trick to tag
185 | fields and subtypes in an ADT
186 | with the singleton types of their names.
187 | If you find using `asInstanceOf` uncomfortable then don't worry:
188 | shapeless provides two tagging syntaxes
189 | to avoid such unsavoriness.
190 |
191 | The first syntax, `->>`,
192 | tags the expression on the right of the arrow
193 | with the singleton type of the literal expression on the left:
194 |
195 | ```tut:book:silent
196 | import shapeless.labelled.{KeyTag, FieldType}
197 | import shapeless.syntax.singleton._
198 |
199 | val someNumber = 123
200 | ```
201 |
202 | ```tut:book
203 | val numCherries = "numCherries" ->> someNumber
204 | ```
205 |
206 | Here we are tagging `someNumber` with
207 | the following phantom type:
208 |
209 | ```scala
210 | KeyTag["numCherries", Int]
211 | ```
212 |
213 | The tag encodes both the name and type of the field,
214 | the combination of which is useful
215 | when searching for entries in a `Repr` using implicit resolution.
216 |
217 | The second syntax takes the tag as a type
218 | rather than a literal value.
219 | This is useful when we know what tag to use
220 | but don't have the ability
221 | to write specific literals in our code:
222 |
223 | ```tut:book:silent
224 | import shapeless.labelled.field
225 | ```
226 |
227 | ```tut:book
228 | field[Cherries](123)
229 | ```
230 |
231 | `FieldType` is a type alias that simplifies
232 | extracting the tag and base types from a tagged type:
233 |
234 | ```scala
235 | type FieldType[K, V] = V with KeyTag[K, V]
236 | ```
237 |
238 | As we'll see in a moment,
239 | shapeless uses this mechanism to tag
240 | fields and subtypes with
241 | their names in our source code.
242 |
243 | Tags exist purely at compile time
244 | and have no runtime representation.
245 | How do we convert them to values we can use at runtime?
246 | Shapeless provides a type class called `Witness` for this purpose[^witness].
247 | If we combine `Witness` and `FieldType`,
248 | we get something very compelling---the
249 | ability to extract the field name
250 | from a tagged field:
251 |
252 | [^witness]: The term "witness" is borrowed from
253 | [mathematical proofs][link-witness].
254 |
255 | ```tut:book:silent
256 | import shapeless.Witness
257 | ```
258 |
259 | ```tut:book
260 | val numCherries = "numCherries" ->> 123
261 | ```
262 |
263 | ```tut:book:silent
264 | // Get the tag from a tagged value:
265 | def getFieldName[K, V](value: FieldType[K, V])
266 | (implicit witness: Witness.Aux[K]): K =
267 | witness.value
268 | ```
269 |
270 | ```tut:book
271 | getFieldName(numCherries)
272 | ```
273 |
274 | ```tut:book:silent
275 | // Get the untagged type of a tagged value:
276 | def getFieldValue[K, V](value: FieldType[K, V]): V =
277 | value
278 | ```
279 |
280 | ```tut:book
281 | getFieldValue(numCherries)
282 | ```
283 |
284 | If we build an `HList` of tagged elements,
285 | we get a data structure that has some of the properties of a `Map`.
286 | We can reference fields by tag,
287 | manipulate and replace them,
288 | and maintain all of the type and naming information along the way.
289 | Shapeless calls these structures "records".
290 |
291 | ### Records and *LabelledGeneric*
292 |
293 | Records are `HLists` of tagged elements:
294 |
295 | ```tut:book:silent
296 | import shapeless.{HList, ::, HNil}
297 | ```
298 |
299 | ```tut:book
300 | val garfield = ("cat" ->> "Garfield") :: ("orange" ->> true) :: HNil
301 | ```
302 |
303 | For clarity, the type of `garfield` is as follows:
304 |
305 | ```scala
306 | // FieldType["cat", String] ::
307 | // FieldType["orange", Boolean] ::
308 | // HNil
309 | ```
310 |
311 | We don't need to go into depth regarding records here;
312 | suffice to say that records are the generic representation
313 | used by `LabelledGeneric`.
314 | `LabelledGeneric` tags each item in a product or coproduct
315 | with the corresponding field or type name from the concrete ADT
316 | (although the names are represented as `Symbols`, not `Strings`).
317 | Shapeless provides a suite of `Map`-like operations on records,
318 | some of which we'll cover in Section [@sec:ops:record].
319 | For now, though, let's derive some type classes using `LabelledGeneric`.
320 |
--------------------------------------------------------------------------------
/src/pages/labelled-generic/products.md:
--------------------------------------------------------------------------------
1 | ## Deriving product instances with *LabelledGeneric*
2 |
3 | We'll use a running example of JSON encoding
4 | to illustrate `LabelledGeneric`.
5 | We'll define a `JsonEncoder` type class
6 | that converts values to a JSON AST.
7 | This is the approach taken by
8 | Argonaut, Circe, Play JSON, Spray JSON,
9 | and many other Scala JSON libraries.
10 |
11 | First we'll define our JSON data type:
12 |
13 | ```tut:book:silent
14 | sealed trait JsonValue
15 | case class JsonObject(fields: List[(String, JsonValue)]) extends JsonValue
16 | case class JsonArray(items: List[JsonValue]) extends JsonValue
17 | case class JsonString(value: String) extends JsonValue
18 | case class JsonNumber(value: Double) extends JsonValue
19 | case class JsonBoolean(value: Boolean) extends JsonValue
20 | case object JsonNull extends JsonValue
21 | ```
22 |
23 | then the type class for encoding values as JSON:
24 |
25 | ```tut:book:silent
26 | trait JsonEncoder[A] {
27 | def encode(value: A): JsonValue
28 | }
29 |
30 | object JsonEncoder {
31 | def apply[A](implicit enc: JsonEncoder[A]): JsonEncoder[A] = enc
32 | }
33 | ```
34 |
35 | then a few primitive instances:
36 |
37 | ```tut:book:silent
38 | def createEncoder[A](func: A => JsonValue): JsonEncoder[A] =
39 | new JsonEncoder[A] {
40 | def encode(value: A): JsonValue = func(value)
41 | }
42 |
43 | implicit val stringEncoder: JsonEncoder[String] =
44 | createEncoder(str => JsonString(str))
45 |
46 | implicit val doubleEncoder: JsonEncoder[Double] =
47 | createEncoder(num => JsonNumber(num))
48 |
49 | implicit val intEncoder: JsonEncoder[Int] =
50 | createEncoder(num => JsonNumber(num))
51 |
52 | implicit val booleanEncoder: JsonEncoder[Boolean] =
53 | createEncoder(bool => JsonBoolean(bool))
54 | ```
55 |
56 | and a few instance combinators:
57 |
58 | ```tut:book:silent
59 | implicit def listEncoder[A]
60 | (implicit enc: JsonEncoder[A]): JsonEncoder[List[A]] =
61 | createEncoder(list => JsonArray(list.map(enc.encode)))
62 |
63 | implicit def optionEncoder[A]
64 | (implicit enc: JsonEncoder[A]): JsonEncoder[Option[A]] =
65 | createEncoder(opt => opt.map(enc.encode).getOrElse(JsonNull))
66 | ```
67 |
68 | Ideally, when we encode ADTs as JSON,
69 | we would like to use the correct field names in the output JSON:
70 |
71 | ```tut:book:silent
72 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
73 |
74 | val iceCream = IceCream("Sundae", 1, false)
75 |
76 | // Ideally we'd like to produce something like this:
77 | val iceCreamJson: JsonValue =
78 | JsonObject(List(
79 | "name" -> JsonString("Sundae"),
80 | "numCherries" -> JsonNumber(1),
81 | "inCone" -> JsonBoolean(false)
82 | ))
83 | ```
84 |
85 | This is where `LabelledGeneric` comes in.
86 | Let's summon an instance for `IceCream`
87 | and see what kind of representation it produces:
88 |
89 | ```tut:book:silent
90 | import shapeless.LabelledGeneric
91 | ```
92 |
93 | ```tut:book
94 | val gen = LabelledGeneric[IceCream].to(iceCream)
95 | ```
96 |
97 | For clarity, the full type of the `HList` is:
98 |
99 | ```scala
100 | // String with KeyTag[Symbol with Tagged["name"], String] ::
101 | // Int with KeyTag[Symbol with Tagged["numCherries"], Int] ::
102 | // Boolean with KeyTag[Symbol with Tagged["inCone"], Boolean] ::
103 | // HNil
104 | ```
105 |
106 | The type here is slightly more complex than we have seen.
107 | Instead of representing the field names with literal string types,
108 | shapeless is representing them with symbols tagged with literal string types.
109 | The details of the implementation aren't particularly important:
110 | we can still use `Witness` and `FieldType` to extract the tags,
111 | but they come out as `Symbols` instead of `Strings`[^future-tags].
112 |
113 | [^future-tags]: Future versions of shapeless may switch
114 | to using `Strings` as tags.
115 |
116 | ### Instances for *HLists*
117 |
118 | Let's define `JsonEncoder` instances for `HNil` and `::`.
119 | Our encoders are going to generate and manipulate `JsonObjects`,
120 | so we'll introduce a new type of encoder to make that easier:
121 |
122 | ```tut:book:silent
123 | trait JsonObjectEncoder[A] extends JsonEncoder[A] {
124 | def encode(value: A): JsonObject
125 | }
126 |
127 | def createObjectEncoder[A](fn: A => JsonObject): JsonObjectEncoder[A] =
128 | new JsonObjectEncoder[A] {
129 | def encode(value: A): JsonObject =
130 | fn(value)
131 | }
132 | ```
133 |
134 | The definition for `HNil` is then straightforward:
135 |
136 | ```tut:book:silent
137 | import shapeless.{HList, ::, HNil, Lazy}
138 |
139 | implicit val hnilEncoder: JsonObjectEncoder[HNil] =
140 | createObjectEncoder(hnil => JsonObject(Nil))
141 | ```
142 |
143 | The definition of `hlistEncoder` involves a few moving parts
144 | so we'll go through it piece by piece.
145 | We'll start with the definition we might expect
146 | if we were using regular `Generic`:
147 |
148 | ```tut:book:silent
149 | implicit def hlistObjectEncoder[H, T <: HList](
150 | implicit
151 | hEncoder: Lazy[JsonEncoder[H]],
152 | tEncoder: JsonObjectEncoder[T]
153 | ): JsonEncoder[H :: T] = ???
154 | ```
155 |
156 | `LabelledGeneric` will give us an `HList` of tagged types,
157 | so let's start by introducing a new type variable for the key type:
158 |
159 | ```tut:book:silent
160 | import shapeless.Witness
161 | import shapeless.labelled.FieldType
162 |
163 | implicit def hlistObjectEncoder[K, H, T <: HList](
164 | implicit
165 | hEncoder: Lazy[JsonEncoder[H]],
166 | tEncoder: JsonObjectEncoder[T]
167 | ): JsonObjectEncoder[FieldType[K, H] :: T] = ???
168 | ```
169 |
170 | In the body of our method
171 | we're going to need the value associated with `K`.
172 | We'll add an implicit `Witness` to do this for us:
173 |
174 | ```tut:book:silent
175 | implicit def hlistObjectEncoder[K, H, T <: HList](
176 | implicit
177 | witness: Witness.Aux[K],
178 | hEncoder: Lazy[JsonEncoder[H]],
179 | tEncoder: JsonObjectEncoder[T]
180 | ): JsonObjectEncoder[FieldType[K, H] :: T] = {
181 | val fieldName = witness.value
182 | ???
183 | }
184 | ```
185 |
186 | We can access the value of `K` using `witness.value`,
187 | but the compiler has no way of knowing
188 | what type of tag we're going to get.
189 | `LabelledGeneric` uses `Symbols` for tags,
190 | so we'll put a type bound on `K`
191 | and use `symbol.name` to convert it to a `String`:
192 |
193 | ```tut:book:silent
194 | implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList](
195 | implicit
196 | witness: Witness.Aux[K],
197 | hEncoder: Lazy[JsonEncoder[H]],
198 | tEncoder: JsonObjectEncoder[T]
199 | ): JsonObjectEncoder[FieldType[K, H] :: T] = {
200 | val fieldName: String = witness.value.name
201 | ???
202 | }
203 | ```
204 |
205 | The rest of the definition uses
206 | the principles we covered in Chapter [@sec:generic]:
207 |
208 | ```tut:book:silent
209 | implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList](
210 | implicit
211 | witness: Witness.Aux[K],
212 | hEncoder: Lazy[JsonEncoder[H]],
213 | tEncoder: JsonObjectEncoder[T]
214 | ): JsonObjectEncoder[FieldType[K, H] :: T] = {
215 | val fieldName: String = witness.value.name
216 | createObjectEncoder { hlist =>
217 | val head = hEncoder.value.encode(hlist.head)
218 | val tail = tEncoder.encode(hlist.tail)
219 | JsonObject((fieldName, head) :: tail.fields)
220 | }
221 | }
222 |
223 | ```
224 |
225 | ### Instances for concrete products
226 |
227 | Finally let's turn to our generic instance.
228 | This is identical to the definitions we've seen before,
229 | except that we're using `LabelledGeneric` instead of `Generic`:
230 |
231 | ```tut:book:silent
232 | import shapeless.LabelledGeneric
233 |
234 | implicit def genericObjectEncoder[A, H](
235 | implicit
236 | generic: LabelledGeneric.Aux[A, H],
237 | hEncoder: Lazy[JsonObjectEncoder[H]]
238 | ): JsonEncoder[A] =
239 | createObjectEncoder { value =>
240 | hEncoder.value.encode(generic.to(value))
241 | }
242 | ```
243 |
244 | And that's all we need!
245 | With these definitions in place
246 | we can serialize instances of any case class
247 | and retain the field names in the resulting JSON:
248 |
249 | ```tut:book
250 | JsonEncoder[IceCream].encode(iceCream)
251 | ```
252 |
--------------------------------------------------------------------------------
/src/pages/labelled-generic/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we discussed `LabelledGeneric`,
4 | a variant of `Generic` that exposes type and field names
5 | in its generic representations.
6 |
7 | The names exposed by `LabelledGeneric`
8 | are encoded as type-level tags
9 | so we can target them during implicit resolution.
10 | We started the chapter discussing *literal types*
11 | and the way shapeless uses them in its tags.
12 | We also discussed the `Witness` type class,
13 | which is used to reify literal types as values.
14 |
15 | Finally, we combined `LabelledGeneric`,
16 | literal types, and `Witness` to build a `JsonEcoder` library
17 | that includes sensible names in its output.
18 |
19 | The key take home point from this chapter
20 | is that none of this code uses runtime reflection.
21 | Everything is implemented with types, implicits,
22 | and a small set of macros that are internal to shapeless.
23 | The code we're generating is consequently
24 | very fast and reliable at runtime.
25 |
--------------------------------------------------------------------------------
/src/pages/links.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [link-book-repo]: https://github.com/underscoreio/shapeless-guide
4 | [link-code-repo]: https://github.com/underscoreio/shapeless-guide-code
5 | [link-underscore-book]: https://underscore.io/books/shapeless-guide
6 |
7 |
8 |
9 | [code-ops-hlist]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala
10 | [code-ops-coproduct]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/coproducts.scala
11 | [code-ops-record]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/records.scala
12 | [code-ops-hlist-align]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala#L1973-L1997
13 | [code-ops-hlist-init]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala#L814-L840
14 | [code-ops-hlist-intersection]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala#L1297-L1352
15 | [code-ops-hlist-last]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala#L786-L812
16 | [code-ops-hlist-length]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/hlists.scala#L271-L292
17 | [code-ops-coproduct-length]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/ops/coproduct.scala#L516-L541
18 | [code-syntax-hlist-init-last]: https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/main/scala/shapeless/syntax/hlists.scala#L103-L112
19 |
20 |
21 |
22 | [link-argonaut]: https://argonaut.io
23 | [link-circe]: https://github.com/travisbrown/circe
24 | [link-contributors]: https://github.com/underscoreio/shapeless-guide/graphs/contributors
25 | [link-doobie]: https://github.com/tpolecat/doobie
26 | [link-ensime]: https://ensime.org
27 | [link-finch]: https://github.com/finagle/finch
28 | [link-frameless]: https://github.com/adelbertc/frameless
29 | [link-parboiled2]: https://github.com/sirthias/parboiled2
30 | [link-play-json]: https://www.playframework.com/documentation/2.5.x/ScalaJson
31 | [link-scodec]: https://github.com/scodec/scodec
32 | [link-scalacheck]: https://scalacheck.org
33 | [link-scalacheck-shapeless]: https://github.com/alexarchambault/scalacheck-shapeless
34 | [link-shapeless]: https://github.com/milessabin/shapeless
35 | [link-spray-json]: https://github.com/spray/spray-json
36 | [link-spray-json-shapeless]: https://github.com/milessabin/spray-json-shapeless
37 | [link-tut]: https://github.com/tpolecat/tut
38 | [link-witness]: https://en.wikipedia.org/wiki/Witness_(mathematics)
39 |
40 |
41 |
42 | [link-folone-scalax-2014]: https://skillsmatter.com/skillscasts/5946-42-rise-of-the-dependent-types
43 | [link-fommil-scalax-2015]: http://fommil.com/scalax15/
44 | [link-kevinwright-type-classes]: http://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for
45 | [link-propensive-nescala-2016]: https://www.youtube.com/watch?v=R8GksuRw3VI
46 | [link-tpolecat-adts]: http://tpolecat.github.io/presentations/algebraic_types.html
47 | [link-travisbrown-generic-type-class-derivation]: https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation
48 | [link-travisbrown-incomplete-generic-type-class-derivation]: https://meta.plasm.us/posts/2015/06/21/deriving-incomplete-type-class-instances
49 | [link-travisbrown-type-level-checksum]: https://meta.plasm.us/posts/2013/06/09/learning-shapeless
50 | [link-dallaway-twenty-two]: http://underscore.io/blog/posts/2016/10/11/twenty-two.html
51 |
52 | [link-typelevel-scala-singleton-type-literals]: https://github.com/typelevel/scala#typelevel-scala-2118
53 | [link-lightbend-scala-singleton-type-literals]: https://github.com/scala/scala/pull/5310
54 | [link-si7046]: https://issues.scala-lang.org/browse/SI-7046
55 |
56 |
57 |
58 | [link-underscore]: https://underscore.io
59 | [link-fr]: https://github.com/crakjie/shapeless-guide
--------------------------------------------------------------------------------
/src/pages/nat/index.md:
--------------------------------------------------------------------------------
1 | # Counting with types {#sec:nat}
2 |
3 | From time to time we need to count things at the type level.
4 | For example, we may need to know the length of an `HList`
5 | or the number of terms we have expanded so far in a computation.
6 | We can represent numbers as values easily enough,
7 | but if we want to influence implicit resolution
8 | we need to represent them at the type level.
9 | This chapter covers the theory behind counting with types,
10 | and provides some compelling use cases for type class derivation.
11 |
12 | ## Representing numbers as types
13 |
14 | Shapeless uses "Church encoding"
15 | to represent natural numbers at the type level.
16 | It provides a type `Nat` with two subtypes:
17 | `_0` representing zero,
18 | and `Succ[N]` representing `N+1`:
19 |
20 | ```tut:book:silent
21 | import shapeless.{Nat, Succ}
22 |
23 | type Zero = Nat._0
24 | type One = Succ[Zero]
25 | type Two = Succ[One]
26 | // etc...
27 | ```
28 |
29 | Shapeless provides aliases for the first 22 `Nats` as `Nat._N`:
30 |
31 | ```tut:book:silent
32 | Nat._1
33 | Nat._2
34 | Nat._3
35 | // etc...
36 | ```
37 |
38 | `Nat` has no runtime semantics.
39 | We have to use the `ToInt` type class
40 | to convert a `Nat` to a runtime `Int`:
41 |
42 | ```tut:book:silent
43 | import shapeless.ops.nat.ToInt
44 |
45 | val toInt = ToInt[Two]
46 | ```
47 |
48 | ```tut:book
49 | toInt.apply()
50 | ```
51 |
52 | The `Nat.toInt` method provides
53 | a convenient shorthand for calling `toInt.apply()`.
54 | It accepts the instance of `ToInt` as an implicit parameter:
55 |
56 | ```tut:book
57 | Nat.toInt[Nat._3]
58 | ```
59 |
--------------------------------------------------------------------------------
/src/pages/nat/length.md:
--------------------------------------------------------------------------------
1 | ## Length of generic representations
2 |
3 | One use case for `Nat` is
4 | determining the lengths of `HLists` and `Coproducts`.
5 | Shapeless provides the
6 | `shapeless.ops.hlist.Length` and
7 | `shapeless.ops.coproduct.Length` type classes for this:
8 |
9 | ```tut:book:silent
10 | import shapeless._
11 | import shapeless.ops.{hlist, coproduct, nat}
12 | ```
13 |
14 | ```tut:book
15 | val hlistLength = hlist.Length[String :: Int :: Boolean :: HNil]
16 | val coproductLength = coproduct.Length[Double :+: Char :+: CNil]
17 | ```
18 |
19 | Instances of `Length` have a type member `Out`
20 | that represents the length as a `Nat`:
21 |
22 | ```tut:book
23 | Nat.toInt[hlistLength.Out]
24 | Nat.toInt[coproductLength.Out]
25 | ```
26 |
27 | Let's use this in a concrete example.
28 | We'll create a `SizeOf` type class that
29 | counts the number of fields in a case class
30 | and exposes it as a simple `Int`:
31 |
32 | ```tut:book:silent
33 | trait SizeOf[A] {
34 | def value: Int
35 | }
36 |
37 | def sizeOf[A](implicit size: SizeOf[A]): Int = size.value
38 | ```
39 |
40 | To create an instance of `SizeOf` we need three things:
41 |
42 | 1. a `Generic` to calculate the corresponding `HList` type;
43 | 2. a `Length` to calculate the length of the `HList` as a `Nat`;
44 | 3. a `ToInt` to convert the `Nat` to an `Int`.
45 |
46 | Here's a working implementation
47 | written in the style described in Chapter [@sec:type-level-programming]:
48 |
49 | ```tut:book:silent
50 | implicit def genericSizeOf[A, L <: HList, N <: Nat](
51 | implicit
52 | generic: Generic.Aux[A, L],
53 | size: hlist.Length.Aux[L, N],
54 | sizeToInt: nat.ToInt[N]
55 | ): SizeOf[A] =
56 | new SizeOf[A] {
57 | val value = sizeToInt.apply()
58 | }
59 | ```
60 |
61 | We can test our code as follows:
62 |
63 | ```tut:book:silent
64 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
65 | ```
66 |
67 | ```tut:book
68 | sizeOf[IceCream]
69 | ```
70 |
--------------------------------------------------------------------------------
/src/pages/nat/ops.md:
--------------------------------------------------------------------------------
1 | ## Other operations involving *Nat*
2 |
3 | Shapeless provides a suite of other operations based on `Nat`.
4 | The `apply` methods on `HList` and `Coproduct`
5 | can accept `Nats` as value or type parameters:
6 |
7 | ```tut:book:silent
8 | import shapeless._
9 |
10 | val hlist = 123 :: "foo" :: true :: 'x' :: HNil
11 | ```
12 |
13 | ```tut:book
14 | hlist.apply[Nat._1]
15 | hlist.apply(Nat._3)
16 | ```
17 |
18 | There are also operations such as
19 | `take`, `drop`, `slice`, and `updatedAt`:
20 |
21 | ```tut:book
22 | hlist.take(Nat._3).drop(Nat._1)
23 | hlist.updatedAt(Nat._1, "bar").updatedAt(Nat._2, "baz")
24 | ```
25 |
26 | These operations and their associated type classes
27 | are useful for manipulating
28 | individual elements within a product or coproduct.
29 |
--------------------------------------------------------------------------------
/src/pages/nat/random.md:
--------------------------------------------------------------------------------
1 | ## Case study: random value generator
2 |
3 | Property-based testing libraries like [ScalaCheck][link-scalacheck]
4 | use type classes to generate random data for unit tests.
5 | For example, ScalaCheck provides the `Arbitrary` type class
6 | that we can use as follows:
7 |
8 | ```tut:book:silent
9 | import org.scalacheck._
10 | ```
11 |
12 | ```tut:book
13 | for(i <- 1 to 3) println(Arbitrary.arbitrary[Int].sample)
14 | for(i <- 1 to 3) println(Arbitrary.arbitrary[(Boolean, Byte)].sample)
15 | ```
16 |
17 | ScalaCheck provides built-in instances of `Arbitrary`
18 | for a wide range of standard Scala types.
19 | However, creating instances of `Arbitrary` for user ADTs
20 | is still a time-consuming manual process.
21 | This makes shapeless integration via libraries like
22 | [scalacheck-shapeless][link-scalacheck-shapeless] very attractive.
23 |
24 | In this section we will create a simple `Random` type class
25 | to generate random values of user-defined ADTs.
26 | We will show how `Length` and `Nat` form
27 | a crucial part of the implementation.
28 | As usual we start with
29 | the definition of the type class itself:
30 |
31 | ```tut:book:invisible
32 | // Ensure we always get the same output:
33 | scala.util.Random.setSeed(0)
34 | ```
35 |
36 | ```tut:book:silent
37 | trait Random[A] {
38 | def get: A
39 | }
40 |
41 | def random[A](implicit r: Random[A]): A = r.get
42 | ```
43 |
44 | ### Simple random values
45 |
46 | Let's start with some basic instances of `Random`:
47 |
48 | ```tut:book:silent
49 | // Instance constructor:
50 | def createRandom[A](func: () => A): Random[A] =
51 | new Random[A] {
52 | def get = func()
53 | }
54 |
55 | // Random numbers from 0 to 9:
56 | implicit val intRandom: Random[Int] =
57 | createRandom(() => scala.util.Random.nextInt(10))
58 |
59 | // Random characters from A to Z:
60 | implicit val charRandom: Random[Char] =
61 | createRandom(() => ('A'.toInt + scala.util.Random.nextInt(26)).toChar)
62 |
63 | // Random booleans:
64 | implicit val booleanRandom: Random[Boolean] =
65 | createRandom(() => scala.util.Random.nextBoolean)
66 | ```
67 |
68 | We can use these simple generators
69 | via the `random` method as follows:
70 |
71 | ```tut:book
72 | for(i <- 1 to 3) println(random[Int])
73 | for(i <- 1 to 3) println(random[Char])
74 | ```
75 |
76 | ### Random products
77 |
78 | We can create random values for products
79 | using the `Generic` and `HList` techniques
80 | from Chapter [@sec:generic]:
81 |
82 | ```tut:book:silent
83 | import shapeless._
84 |
85 | implicit def genericRandom[A, R](
86 | implicit
87 | gen: Generic.Aux[A, R],
88 | random: Lazy[Random[R]]
89 | ): Random[A] =
90 | createRandom(() => gen.from(random.value.get))
91 |
92 | implicit val hnilRandom: Random[HNil] =
93 | createRandom(() => HNil)
94 |
95 | implicit def hlistRandom[H, T <: HList](
96 | implicit
97 | hRandom: Lazy[Random[H]],
98 | tRandom: Random[T]
99 | ): Random[H :: T] =
100 | createRandom(() => hRandom.value.get :: tRandom.get)
101 | ```
102 |
103 | This gets us as far as summoning random instances for case classes:
104 |
105 | ```tut:book:silent
106 | case class Cell(col: Char, row: Int)
107 | ```
108 |
109 | ```tut:book
110 | for(i <- 1 to 5) println(random[Cell])
111 | ```
112 |
113 | ### Random coproducts
114 |
115 | This is where we start hitting problems.
116 | Generating a random instance of a coproduct
117 | involves choosing a random subtype.
118 | Let's start with a naïve implementation:
119 |
120 | ```tut:book:silent
121 | implicit val cnilRandom: Random[CNil] =
122 | createRandom(() => throw new Exception("Inconceivable!"))
123 |
124 | implicit def coproductRandom[H, T <: Coproduct](
125 | implicit
126 | hRandom: Lazy[Random[H]],
127 | tRandom: Random[T]
128 | ): Random[H :+: T] =
129 | createRandom { () =>
130 | val chooseH = scala.util.Random.nextDouble < 0.5
131 | if(chooseH) Inl(hRandom.value.get) else Inr(tRandom.get)
132 | }
133 | ```
134 |
135 | There problems with this implementation
136 | lie in the 50/50 choice in calculating `chooseH`.
137 | This creates an uneven probability distribution.
138 | For example, consider the following type:
139 |
140 | ```tut:book:silent
141 | sealed trait Light
142 | case object Red extends Light
143 | case object Amber extends Light
144 | case object Green extends Light
145 | ```
146 |
147 | The `Repr` for `Light` is `Red :+: Amber :+: Green :+: CNil`.
148 | An instance of `Random` for this type
149 | will choose `Red` 50% of the time
150 | and `Amber :+: Green :+: CNil` 50% of the time.
151 | A correct distribution would be
152 | 33% `Red` and 67% `Amber :+: Green :+: CNil`.
153 |
154 | And that's not all.
155 | If we look at the overall probability distribution
156 | we see something even more alarming:
157 |
158 | - `Red` is chosen 1/2 of the time
159 | - `Amber` is chosen 1/4 of the time
160 | - `Green` is chosen 1/8 of the time
161 | - *`CNil` is chosen 1/16 of the time*
162 |
163 | Our coproduct instances will throw exceptions 6.75% of the time!
164 |
165 | ```scala
166 | for(i <- 1 to 100) random[Light]
167 | // java.lang.Exception: Inconceivable!
168 | // ...
169 | ```
170 |
171 | To fix this problem we have to alter
172 | the probability of choosing `H` over `T`.
173 | The correct behaviour should be to choose
174 | `H` `1/n` of the time,
175 | where `n` is the length of the coproduct.
176 | This ensures an even probability distribution
177 | across the subtypes of the coproduct.
178 | It also ensures we choose the head
179 | of a single-subtype `Coproduct` 100% of the time,
180 | which means we never call `cnilProduct.get`.
181 | Here's an updated implementation:
182 |
183 | ```tut:book:silent
184 | import shapeless.ops.coproduct
185 | import shapeless.ops.nat.ToInt
186 |
187 | implicit def coproductRandom[H, T <: Coproduct, L <: Nat](
188 | implicit
189 | hRandom: Lazy[Random[H]],
190 | tRandom: Random[T],
191 | tLength: coproduct.Length.Aux[T, L],
192 | tLengthAsInt: ToInt[L]
193 | ): Random[H :+: T] = {
194 | createRandom { () =>
195 | val length = 1 + tLengthAsInt()
196 | val chooseH = scala.util.Random.nextDouble < (1.0 / length)
197 | if(chooseH) Inl(hRandom.value.get) else Inr(tRandom.get)
198 | }
199 | }
200 |
201 | ```
202 |
203 | With these modifications
204 | we can generate random values of any product or coproduct:
205 |
206 | ```tut:book
207 | for(i <- 1 to 5) println(random[Light])
208 | ```
209 |
210 | Generating test data for ScalaCheck
211 | normally requires a great deal of boilerplate.
212 | Random value generation is a compelling use case for shapeless
213 | of which `Nat` forms an essential component.
214 |
--------------------------------------------------------------------------------
/src/pages/nat/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we discussed
4 | how shapeless represents natural numbers
5 | and how we can use them in type classes.
6 | We saw some predefined ops type classes
7 | that let us do things like calculate lengths
8 | and access elements by index,
9 | and created our own type classes
10 | that use `Nat` in other ways.
11 |
12 | Between `Nat`, `Poly`, and the variety of
13 | types we have seen in the last few chapters,
14 | we have seen just a small fraction of
15 | the toolbox provided in `shapeless.ops`.
16 | There are many other ops type classes
17 | that provide a comprehensive foundation
18 | on which to build our own code.
19 | However, the theory laid out here
20 | is enough to understand the majority of ops
21 | needed to derive our own type classes.
22 | The source code in the `shapeless.ops`
23 | packages should now be approachable enough
24 | to pick up other useful ops.
25 |
--------------------------------------------------------------------------------
/src/pages/notes/outline.md:
--------------------------------------------------------------------------------
1 | # Notes
2 |
3 | This stuff won't be here when the book is done.
4 |
5 | ## Outline
6 |
7 | TODO: Introduce `illTyped` wherever makes sense.
8 |
9 | - DONE Introduction
10 |
11 | - What is generic programming?
12 | - Abstracting over types
13 | - Abstracting over arity
14 | - Patterns in our code
15 | - Algebraic data types
16 | - Type classes
17 | - The rest of this book
18 |
19 | - DONE Generic representations
20 |
21 | - Products and coproducts
22 | - `HList`
23 | - `Coproduct`
24 | - `Generic`
25 | - Converting to/from generic representations
26 | - Converting between different specific representations
27 |
28 | - Deriving type class instances
29 |
30 | - Example using `Generic` and products
31 | - `Lazy` (probably)
32 | - `Strict` (maybe)
33 | - Example using `Generic`, products, and coproducts
34 |
35 | - Making use of field and type names
36 |
37 | - `Witness` and singleton types
38 | - `Widen` and `Narrow`, `SingletonOps`
39 | - `narrow` from `shapeless.syntax.singleton`
40 | - `LabelledGeneric`
41 | - `FieldType`, `KeyTag`, `field`
42 | - `->>` from `shapeless.syntax.singleton`
43 |
44 | - Other useful tools
45 | - `Typeable`
46 | - `Annotation` and `Annotations`
47 | - `Default`, `Default.AsRecord`, and `Default.AsOptions`
48 |
49 | - Counting with types
50 | - `Nat`
51 | - `ToInt`
52 |
53 | - Working with tuples
54 | - `Tupler`
55 | - `shapeless.Tuple.fill` (depends on `Nat`)
56 |
57 | - Working with functions
58 |
59 | - `FnFromProduct` and `FnToProduct`
60 | - `shapeless.syntax.std.function.{fromProduct, toProduct}`
61 | - `ProductArgs` and `FromProductArgs` ??
62 |
63 | - Performance concerns
64 | - `Cached`
65 | - `Lazy` and `Strict`
66 | - `cachedImplicit`
67 |
68 | - Working with HLists
69 | - go through the most common ops from the use cases
70 |
71 | - Working with tuples
72 | - this would be a short section
73 | - relate a lot of this back to HLists
74 |
75 | - Working with coproducts
76 | - go through the most common ops from the use cases
77 |
78 | - Working with records (include this? not used much)
79 | - What is a record?
80 | - these in no particular order
81 | - `Keys`
82 | - `RemoveAll`
83 | - `Remover`
84 | - `Selector`
85 | - `Values`
86 |
87 | - Polymorphic functions (include this? not used much)
88 | - `Poly` and `Case`
89 |
90 | - Dealing with higher kinds (include this? not used much)
91 | - `Generic1`
92 | - `IsHCons1`
93 | - `IsCCons1`
94 |
--------------------------------------------------------------------------------
/src/pages/notes/todos.md:
--------------------------------------------------------------------------------
1 | # TODOs #{-}
2 |
3 | Still to do:
4 |
5 | - Mention type class instance selection in Chapter 4
6 | - Low and high priority
7 | - `LowPriorityImplicit`
8 | - Function interop
9 | - Performance
10 | - `cachedImplicit`
11 | - Maybe `Cached`
12 | - Maybe
13 | - Check cross references
14 | - **DONE** Generic applied to tuples
15 | - **DONE** Mention SI-7046 in Chapter 3
16 | - **DONE** Complete the "debugging" section in Chapter 4
17 | - **DONE** Built-in `HList` and `Coproduct` operations
18 | - **DONE** Migrating case class as a basic example
19 | - **DONE** Polymorphic functions
20 | - **DONE** Mapping over `HLists` as an example
21 | - **DONE** Counting with `Nat`
22 | - **DONE** Generating `Arbitrary` instances as an example
23 | - **DONE** Callout box on quirkiness of type inference with poly:
24 | - **DONE** `val len1: Int = lengthPoly("foo")` fails, but...
25 | - **DONE** `val len2 = lengthPoly("foo")` compiles, but...
26 | - **DONE** `val len3: Int = lengthPoly[String]("foo")` fails
27 | - **DONE** Built-in record operations
28 | - **DONE** Final summary
29 | - **SHIP IT!**
30 |
--------------------------------------------------------------------------------
/src/pages/ops/index.md:
--------------------------------------------------------------------------------
1 | # Working with *HLists* and *Coproducts* {#sec:ops}
2 |
3 | In Part I we discussed methods for
4 | deriving type class instances
5 | for algebraic data types.
6 | We can use type class derivation
7 | to augment almost any type class,
8 | although in more complex cases
9 | we may have to write a lot of supporting code
10 | for manipulating `HLists` and `Coproducts`.
11 |
12 | In Part II we'll look at the `shapeless.ops` package,
13 | which provides a set of helpful tools
14 | that we can use as building blocks.
15 | Each op comes in two parts:
16 | a *type class* that we can use during implicit resolution,
17 | and *extension methods* that we can call on `HList` and `Coproduct`.
18 |
19 | There are three general sets of ops,
20 | available from three packages:
21 |
22 | - `shapeless.ops.hlist` defines type classes for `HLists`.
23 | These can be used directly via extension methods on `HList`,
24 | defined in `shapeless.syntax.hlist`.
25 |
26 | - `shapeless.ops.coproduct` defines type classes for `Coproducts`.
27 | These can be used directly via extension methods on `Coproduct`,
28 | defined in `shapeless.syntax.coproduct`.
29 |
30 | - `shapeless.ops.record` defines type classes for shapeless records
31 | (`HLists` containing
32 | tagged elements---Section [@sec:labelled-generic:type-tagging]).
33 | These can be used via extension methods on `HList`,
34 | imported from `shapeless.record`,
35 | and defined in `shapeless.syntax.record`.
36 |
37 | We don't have room in this book
38 | to cover all of the available ops.
39 | Fortunately, in most cases the code
40 | is understandable and well documented.
41 | Rather than provide an exhaustive guide,
42 | we will touch on
43 | the major theoretical and structural points
44 | and show you how to extract further information
45 | from the shapeless codebase.
46 |
--------------------------------------------------------------------------------
/src/pages/ops/init-last.md:
--------------------------------------------------------------------------------
1 | ## Simple ops examples
2 |
3 | `HList` has `init` and `last`
4 | extension methods based on two type classes:
5 | `shapeless.ops.hlist.Init` and
6 | `shapeless.ops.hlist.Last`.
7 | While `init` drops the last element of an `HList`,
8 | `last` drops all except the last one.
9 | `Coproduct` has similar methods and type classes.
10 | These serve as perfect examples of the ops pattern.
11 | Here are simplified definitions of the extension methods:
12 |
13 | ```scala
14 | package shapeless
15 | package syntax
16 |
17 | implicit class HListOps[L <: HList](l : L) {
18 | def last(implicit last: Last[L]): last.Out = last.apply(l)
19 | def init(implicit init: Init[L]): init.Out = init.apply(l)
20 | }
21 | ```
22 |
23 | The return type of each method is determined
24 | by a dependent type on the implicit parameter.
25 | The instances for each type class provide the actual mapping.
26 | Here's the skeleton definition of `Last` as an example:
27 |
28 | ```scala
29 | trait Last[L <: HList] {
30 | type Out
31 | def apply(in: L): Out
32 | }
33 |
34 | object Last {
35 | type Aux[L <: HList, O] = Last[L] { type Out = O }
36 | implicit def pair[H]: Aux[H :: HNil, H] = ???
37 | implicit def list[H, T <: HList]
38 | (implicit last: Last[T]): Aux[H :: T, last.Out] = ???
39 | }
40 | ```
41 |
42 | We can make a couple of interesting observations
43 | about this implementation.
44 | First, we can typically implement ops type classes
45 | with a small number of instances (just two in this case).
46 | We can therefore package *all* of the required instances
47 | in the companion object of the type class,
48 | allowing us to call the corresponding extension methods
49 | without any imports from `shapeless.ops`:
50 |
51 | ```tut:book:silent
52 | import shapeless._
53 | ```
54 |
55 | ```tut:book
56 | ("Hello" :: 123 :: true :: HNil).last
57 | ("Hello" :: 123 :: true :: HNil).init
58 | ```
59 |
60 | Second, the type class is only defined for `HLists`
61 | with at least one element.
62 | This gives us a degree of static checking.
63 | If we try to call `last` on an empty `HList`,
64 | we get a compile error:
65 |
66 | ```tut:book:fail
67 | HNil.last
68 | ```
69 |
--------------------------------------------------------------------------------
/src/pages/ops/migration.md:
--------------------------------------------------------------------------------
1 | ## Case study: case class migrations {#sec:ops:migration}
2 |
3 | The power of ops type classes fully crystallizes
4 | when we chain them together
5 | as building blocks for our own code.
6 | We'll finish this chapter with a compelling example:
7 | a type class for performing "migrations"
8 | (aka "evolutions") on case classes[^database-migrations].
9 | For example, if version 1 of our app contains the following case class:
10 |
11 | [^database-migrations]: The term is stolen from
12 | "database migrations"---SQL scripts that
13 | automate upgrades to a database schema.
14 |
15 | ```tut:book:silent
16 | case class IceCreamV1(name: String, numCherries: Int, inCone: Boolean)
17 | ```
18 |
19 | our migration library should enable certain
20 | mechanical "upgrades" for free:
21 |
22 | ```tut:book:silent
23 | // Remove fields:
24 | case class IceCreamV2a(name: String, inCone: Boolean)
25 |
26 | // Reorder fields:
27 | case class IceCreamV2b(name: String, inCone: Boolean, numCherries: Int)
28 |
29 | // Insert fields (provided we can determine a default value):
30 | case class IceCreamV2c(
31 | name: String, inCone: Boolean, numCherries: Int, numWaffles: Int)
32 | ```
33 |
34 | Ideally we'd like to be able to write code like this:
35 |
36 | ```scala
37 | IceCreamV1("Sundae", 1, false).migrateTo[IceCreamV2a]
38 | ```
39 |
40 | The type class should take care of the migration
41 | without additional boilerplate.
42 |
43 | ### The type class
44 |
45 | The `Migration` type class represents
46 | a transformation from a source to a destination type.
47 | Both of these are going to be "input" types in our derivation,
48 | so we model both as type parameters.
49 | We don't need an `Aux` type alias
50 | because there are no type members to expose:
51 |
52 | ```tut:book:silent
53 | trait Migration[A, B] {
54 | def apply(a: A): B
55 | }
56 | ```
57 |
58 | We'll also introduce an extension method
59 | to make examples easier to read:
60 |
61 | ```tut:book:silent
62 | implicit class MigrationOps[A](a: A) {
63 | def migrateTo[B](implicit migration: Migration[A, B]): B =
64 | migration.apply(a)
65 | }
66 | ```
67 |
68 | ### Step 1. Removing fields
69 |
70 | Let's build up the solution piece by piece,
71 | starting with field removal.
72 | We can do this in several steps:
73 |
74 | 1. convert `A` to its generic representation;
75 | 2. filter the `HList` from step 1---only retain
76 | fields that are also in `B`;
77 | 3. convert the output of step 2 to `B`.
78 |
79 | We can implement steps 1 and 3 with `Generic` or `LabelledGeneric`,
80 | and step 2 with an op called `Intersection`.
81 | `LabelledGeneric` seems a sensible choice
82 | because we need to identify fields by name:
83 |
84 | ```tut:book:silent
85 | import shapeless._
86 | import shapeless.ops.hlist
87 |
88 | implicit def genericMigration[A, B, ARepr <: HList, BRepr <: HList](
89 | implicit
90 | aGen : LabelledGeneric.Aux[A, ARepr],
91 | bGen : LabelledGeneric.Aux[B, BRepr],
92 | inter : hlist.Intersection.Aux[ARepr, BRepr, BRepr]
93 | ): Migration[A, B] = new Migration[A, B] {
94 | def apply(a: A): B =
95 | bGen.from(inter.apply(aGen.to(a)))
96 | }
97 | ```
98 |
99 | Take a moment to locate [`Intersection`][code-ops-hlist-intersection]
100 | in the shapeless codebase.
101 | Its `Aux` type alias takes three parameters:
102 | two input `HLists` and one output for the intersection type.
103 | In the example above we are specifying
104 | `ARepr` and `BRepr` as the input types
105 | and `BRepr` as the output type.
106 | This means implicit resolution will only succeed
107 | if `B` has an exact subset of the fields of `A`,
108 | specified with the exact same names in the same order:
109 |
110 | ```tut:book
111 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
112 | ```
113 |
114 | We get a compile error if
115 | we try to use `Migration` with non-conforming types:
116 |
117 | ```tut:book:fail
118 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2b]
119 | ```
120 |
121 | ### Step 2. Reordering fields
122 |
123 | We need to lean on another ops type class
124 | to add support for reordering.
125 | The [`Align`][code-ops-hlist-align] op
126 | lets us reorder the fields in one `HList`
127 | to match the order they appear in another `HList`.
128 | We can redefine our instance using `Align` as follows:
129 |
130 | ```tut:book:silent
131 | implicit def genericMigration[
132 | A, B,
133 | ARepr <: HList, BRepr <: HList,
134 | Unaligned <: HList
135 | ](
136 | implicit
137 | aGen : LabelledGeneric.Aux[A, ARepr],
138 | bGen : LabelledGeneric.Aux[B, BRepr],
139 | inter : hlist.Intersection.Aux[ARepr, BRepr, Unaligned],
140 | align : hlist.Align[Unaligned, BRepr]
141 | ): Migration[A, B] = new Migration[A, B] {
142 | def apply(a: A): B =
143 | bGen.from(align.apply(inter.apply(aGen.to(a))))
144 | }
145 | ```
146 |
147 | We introduce a new type parameter called `Unaligned`
148 | to represent the intersection of `ARepr` and `BRepr`
149 | before alignment,
150 | and use `Align` to convert `Unaligned` to `BRepr`.
151 | With this modified definition of `Migration`
152 | we can both remove and reorder fields:
153 |
154 | ```tut:book
155 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
156 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2b]
157 |
158 | ```
159 |
160 | However, if we try to add fields we still get a failure:
161 |
162 | ```tut:book:fail
163 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2c]
164 | ```
165 |
166 | ### Step 3. Adding new fields
167 |
168 | We need a mechanism for calculating default values
169 | to support the addition of new fields.
170 | Shapeless doesn't provide a type class for this,
171 | but Cats does in the form of a `Monoid`.
172 | Here's a simplified definition:
173 |
174 | ```scala
175 | package cats
176 |
177 | trait Monoid[A] {
178 | def empty: A
179 | def combine(x: A, y: A): A
180 | }
181 | ```
182 |
183 | `Monoid` defines two operations:
184 | `empty` for creating a "zero" value
185 | and `combine` for "adding" two values.
186 | We only need `empty` in our code,
187 | but it will be trivial to define `combine` as well.
188 |
189 | Cats provides instances of `Monoid`
190 | for all the primitive types we care about
191 | (`Int`, `Double`, `Boolean`, and `String`).
192 | We can define instances for `HNil` and `::`
193 | using the techniques from Chapter [@sec:labelled-generic]:
194 |
195 | ```tut:book:silent
196 | import cats.Monoid
197 | import cats.instances.all._
198 | import shapeless.labelled.{field, FieldType}
199 |
200 | def createMonoid[A](zero: A)(add: (A, A) => A): Monoid[A] =
201 | new Monoid[A] {
202 | def empty = zero
203 | def combine(x: A, y: A): A = add(x, y)
204 | }
205 |
206 | implicit val hnilMonoid: Monoid[HNil] =
207 | createMonoid[HNil](HNil)((x, y) => HNil)
208 |
209 | implicit def emptyHList[K <: Symbol, H, T <: HList](
210 | implicit
211 | hMonoid: Lazy[Monoid[H]],
212 | tMonoid: Monoid[T]
213 | ): Monoid[FieldType[K, H] :: T] =
214 | createMonoid(field[K](hMonoid.value.empty) :: tMonoid.empty) {
215 | (x, y) =>
216 | field[K](hMonoid.value.combine(x.head, y.head)) ::
217 | tMonoid.combine(x.tail, y.tail)
218 | }
219 | ```
220 |
221 | We need to combine `Monoid`[^monoid-pun] with a couple of other ops
222 | to complete our final implementation of `Migration`.
223 | Here's the full list of steps:
224 |
225 | 1. use `LabelledGeneric` to convert `A` to its generic representation;
226 | 2. use `Intersection` to calculate an `HList` of fields common to `A` and `B`;
227 | 3. calculate the types of fields that appear in `B` but not in `A`;
228 | 4. use `Monoid` to calculate a default value of the type from step 3;
229 | 5. append the common fields from step 2 to the new field from step 4;
230 | 6. use `Align` to reorder the fields from step 5 in the same order as `B`;
231 | 7. use `LabelledGeneric` to convert the output of step 6 to `B`.
232 |
233 | [^monoid-pun]: Pun intended.
234 |
235 | We've already seen how to implement steps 1, 2, 4, 6, and 7.
236 | We can implement step 3 using an op called `Diff`
237 | that is very similar to `Intersection`,
238 | and step 5 using another op called `Prepend`.
239 | Here's the complete solution:
240 |
241 | ```tut:book:silent
242 | implicit def genericMigration[
243 | A, B, ARepr <: HList, BRepr <: HList,
244 | Common <: HList, Added <: HList, Unaligned <: HList
245 | ](
246 | implicit
247 | aGen : LabelledGeneric.Aux[A, ARepr],
248 | bGen : LabelledGeneric.Aux[B, BRepr],
249 | inter : hlist.Intersection.Aux[ARepr, BRepr, Common],
250 | diff : hlist.Diff.Aux[BRepr, Common, Added],
251 | monoid : Monoid[Added],
252 | prepend : hlist.Prepend.Aux[Added, Common, Unaligned],
253 | align : hlist.Align[Unaligned, BRepr]
254 | ): Migration[A, B] =
255 | new Migration[A, B] {
256 | def apply(a: A): B =
257 | bGen.from(align(prepend(monoid.empty, inter(aGen.to(a)))))
258 | }
259 | ```
260 |
261 | Note that this code doesn't use
262 | every type class at the value level.
263 | We use `Diff` to calculate the `Added` data type,
264 | but we don't actually need `diff.apply` at run time.
265 | Instead we use our `Monoid` to summon an instance of `Added`.
266 |
267 | With this final version of the type class instance in place
268 | we can use `Migration` for all the use cases we set out
269 | at the beginning of the case study:
270 |
271 | ```tut:book
272 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
273 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2b]
274 | IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2c]
275 | ```
276 |
277 | It's amazing what we can create with ops type classes.
278 | `Migration` has a single `implicit def`
279 | with a single line of value-level implementation.
280 | It allows us to automate migrations between *any* pair of case classes,
281 | in roughly the same amount of code we'd write
282 | to handle a *single* pair of types using the standard library.
283 | Such is the power of shapeless!
284 |
--------------------------------------------------------------------------------
/src/pages/ops/penultimate.md:
--------------------------------------------------------------------------------
1 | ## Creating a custom op (the "lemma" pattern) {#sec:ops:penultimate}
2 |
3 | If we find a particular sequence of ops useful,
4 | we can package them up and re-provide them as another ops type class.
5 | This is an example of the "lemma" pattern,
6 | a term we introduced in Section [@sec:type-level-programming:summary].
7 |
8 | Let's work through the creation of our own op as an exercise.
9 | We'll combine the power of `Last` and `Init`
10 | to create a `Penultimate` type class
11 | that retrieves the second-to-last element in an `HList`.
12 | Here's the type class definition,
13 | complete with `Aux` type alias and `apply` method:
14 |
15 | ```tut:book:silent
16 | import shapeless._
17 |
18 | trait Penultimate[L] {
19 | type Out
20 | def apply(l: L): Out
21 | }
22 |
23 | object Penultimate {
24 | type Aux[L, O] = Penultimate[L] { type Out = O }
25 |
26 | def apply[L](implicit p: Penultimate[L]): Aux[L, p.Out] = p
27 | }
28 | ```
29 |
30 | Again, notice that the `apply` method
31 | has a return type of `Aux[L, O]` instead of `Penultimate[L]`.
32 | This ensures type members are visible on summoned instances
33 | as discussed in the callout
34 | in Section [@sec:type-level-programming:depfun].
35 |
36 | We only need to define one instance of `Penultimate`,
37 | combining `Init` and `Last` using the techniques
38 | covered in Section [@sec:type-level-programming:chaining]:
39 |
40 | ```tut:book:silent
41 | import shapeless.ops.hlist
42 |
43 | implicit def hlistPenultimate[L <: HList, M <: HList, O](
44 | implicit
45 | init: hlist.Init.Aux[L, M],
46 | last: hlist.Last.Aux[M, O]
47 | ): Penultimate.Aux[L, O] =
48 | new Penultimate[L] {
49 | type Out = O
50 | def apply(l: L): O =
51 | last.apply(init.apply(l))
52 | }
53 | ```
54 |
55 | We can use `Penultimate` as follows:
56 |
57 | ```tut:book:silent
58 | type BigList = String :: Int :: Boolean :: Double :: HNil
59 |
60 | val bigList: BigList = "foo" :: 123 :: true :: 456.0 :: HNil
61 | ```
62 |
63 | ```tut:book
64 | Penultimate[BigList].apply(bigList)
65 | ```
66 |
67 | Summoning an instance of `Penultimate`
68 | requires the compiler to summon instances for `Last` and `Init`,
69 | so we inherit the same level of type checking on short `HLists`:
70 |
71 | ```tut:book:silent
72 | type TinyList = String :: HNil
73 |
74 | val tinyList = "bar" :: HNil
75 | ```
76 |
77 | ```tut:book:fail
78 | Penultimate[TinyList].apply(tinyList)
79 | ```
80 |
81 | We can make things more convenient for end users
82 | by defining an extension method on `HList`:
83 |
84 | ```tut:book:silent
85 | implicit class PenultimateOps[A](a: A) {
86 | def penultimate(implicit inst: Penultimate[A]): inst.Out =
87 | inst.apply(a)
88 | }
89 | ```
90 |
91 | ```tut:book
92 | bigList.penultimate
93 | ```
94 |
95 | We can also provide `Penultimate` for all product types
96 | by providing an instance based on `Generic`:
97 |
98 | ```tut:book:silent
99 | implicit def genericPenultimate[A, R, O](
100 | implicit
101 | generic: Generic.Aux[A, R],
102 | penultimate: Penultimate.Aux[R, O]
103 | ): Penultimate.Aux[A, O] =
104 | new Penultimate[A] {
105 | type Out = O
106 | def apply(a: A): O =
107 | penultimate.apply(generic.to(a))
108 | }
109 |
110 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
111 | ```
112 |
113 | ```tut:book
114 | IceCream("Sundae", 1, false).penultimate
115 | ```
116 |
117 | The important point here is that,
118 | by defining `Penultimate` as another type class,
119 | we have created a reusable tool that we can apply elsewhere.
120 | Shapeless provides many ops for many purposes,
121 | but it's easy to add our own to the toolbox.
122 |
--------------------------------------------------------------------------------
/src/pages/ops/record.md:
--------------------------------------------------------------------------------
1 | ## Record ops {#sec:ops:record}
2 |
3 | We've spent some time in this chapter
4 | looking at type classes from the
5 | `shapeless.ops.hlist` and `shapeless.ops.coproduct` packages.
6 | We mustn't leave without mentioning a third important package:
7 | `shapeless.ops.record`.
8 |
9 | Shapeless' "record ops" provide `Map`-like
10 | operations on `HLists` of tagged elements.
11 | Here are a handful of examples involving ice creams:
12 |
13 | ```tut:book:silent
14 | import shapeless._
15 |
16 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
17 | ```
18 |
19 | ```tut:book
20 | val sundae = LabelledGeneric[IceCream].
21 | to(IceCream("Sundae", 1, false))
22 | ```
23 |
24 | Unlike the `HList` and `Coproduct` ops we have seen already,
25 | record ops syntax requires an explicit import from `shapeless.record`:
26 |
27 | ```tut:book:silent
28 | import shapeless.record._
29 | ```
30 |
31 | ### Selecting fields
32 |
33 | The `get` extension method
34 | and its corresponding `Selector` type class
35 | allow us to fetch a field by tag:
36 |
37 | ```tut:book
38 | sundae.get('name)
39 | ```
40 |
41 | ```tut:book
42 | sundae.get('numCherries)
43 | ```
44 |
45 | Attempting to access an undefined field
46 | causes a compile error as we might expect:
47 |
48 | ```tut:book:fail
49 | sundae.get('nomCherries)
50 | ```
51 |
52 | ### Updating and removing fields
53 |
54 | The `updated` method and `Updater` type class allow us to modify fields by key.
55 | The `remove` method and `Remover` type class allow us to delete fields by key:
56 |
57 | ```tut:book
58 | sundae.updated('numCherries, 3)
59 | ```
60 |
61 | ```tut:book
62 | sundae.remove('inCone)
63 | ```
64 |
65 | The `updateWith` method and `Modifier` type class allow us
66 | to modify a field with an update function:
67 |
68 | ```tut:book
69 | sundae.updateWith('name)("MASSIVE " + _)
70 | ```
71 |
72 | ### Converting to a regular *Map*
73 |
74 | The `toMap` method and `ToMap` type class
75 | allow us to convert a record to a `Map`:
76 |
77 | ```tut:book
78 | sundae.toMap
79 | ```
80 |
81 | ### Other operations
82 |
83 | There are other record ops that we don't have room to cover here.
84 | We can rename fields, merge records, map over their values, and much more.
85 | See the source code of `shapeless.ops.record` and `shapeless.syntax.record`
86 | for more information.
87 |
--------------------------------------------------------------------------------
/src/pages/ops/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we explored a few of the
4 | type classes that are provided in the `shapeless.ops` package.
5 | We looked at `Last` and `Init`
6 | as two simple examples of the ops pattern,
7 | and built our own `Penultimate` and `Migration` type classes
8 | by chaining together existing building blocks.
9 |
10 | Many of the ops type classes share a similar pattern
11 | to the ops we've seen here.
12 | The easiest way to learn them is to
13 | look at the source code
14 | in `shapeless.ops` and `shapeless.syntax`.
15 |
16 | In the next chapters we will look at two suites
17 | of ops type classes that require further theoretical discussion.
18 | Chapter [@sec:poly] discusses functional operations
19 | such as `map` and `flatMap` on `HLists`,
20 | and Chapter [@sec:nat] discusses
21 | how to implement type classes that require
22 | type level representations of numbers.
23 | This knowledge will help us gain
24 | a more complete understanding of
25 | the variety of type classes from `shapeless.ops`.
26 |
--------------------------------------------------------------------------------
/src/pages/parts/part1.md:
--------------------------------------------------------------------------------
1 | \part{Type class derivation}
2 |
--------------------------------------------------------------------------------
/src/pages/parts/part2.md:
--------------------------------------------------------------------------------
1 | \part{Shapeless ops}
2 |
--------------------------------------------------------------------------------
/src/pages/parts/part3.md:
--------------------------------------------------------------------------------
1 | \part{Summary}
2 |
--------------------------------------------------------------------------------
/src/pages/poly/index.md:
--------------------------------------------------------------------------------
1 | # Functional operations on *HLists* {#sec:poly}
2 |
3 | "Regular" Scala programs make heavy use
4 | of functional operations like `map` and `flatMap`.
5 | A question arises: can we perform similar operations on `HLists`?
6 | The answer is "yes", although
7 | we have to do things a little differently than in regular Scala.
8 | Unsurprisingly the mechanisms we use are type class based
9 | and there are a suite of ops type classes to help us out.
10 |
11 | Before we delve in to the type classes themselves,
12 | we need to discuss how shapeless represents
13 | *polymorphic functions* suitable for
14 | mapping over heterogeneous data structures.
15 |
16 | ## Motivation: mapping over an *HList*
17 |
18 | We'll motivate the discussion of polymorphic functions
19 | by looking at the `map` method.
20 | Figure [@fig:poly:monomorphic-map] shows
21 | a type chart for mapping over a regular list.
22 | We start with a `List[A]`, supply a function `A => B`,
23 | and end up with a `List[B]`.
24 |
25 | {#fig:poly:monomorphic-map}
26 |
27 | The heterogeneous element types in an `HList`
28 | cause this model to break down.
29 | Scala functions have fixed input and output types,
30 | so the result of our map will have to have
31 | the same element type in every position.
32 |
33 | Ideally we'd like a `map` operation like
34 | the one shown in Figure [@fig:poly:polymorphic-map],
35 | where the function inspects the type of each input
36 | and uses it to determine the type of each output.
37 | This gives us a closed, composable transformation
38 | that retains the heterogeneous nature of the `HList`.
39 |
40 | {#fig:poly:polymorphic-map}
41 |
42 | Unfortunately we can't use Scala functions
43 | to implement this kind of operation.
44 | We need some new infrastructure.
45 |
--------------------------------------------------------------------------------
/src/pages/poly/map-flatmap-fold.md:
--------------------------------------------------------------------------------
1 | ## Mapping and flatMapping using *Poly*
2 |
3 | Shapeless provides a suite of
4 | functional operations based on `Poly`,
5 | each implemented as an ops type class.
6 | Let's look at `map` and `flatMap` as examples.
7 | Here's `map`:
8 |
9 | ```tut:book:silent
10 | import shapeless._
11 |
12 | object sizeOf extends Poly1 {
13 | implicit val intCase: Case.Aux[Int, Int] =
14 | at(identity)
15 |
16 | implicit val stringCase: Case.Aux[String, Int] =
17 | at(_.length)
18 |
19 | implicit val booleanCase: Case.Aux[Boolean, Int] =
20 | at(bool => if(bool) 1 else 0)
21 | }
22 | ```
23 |
24 | ```tut:book
25 | (10 :: "hello" :: true :: HNil).map(sizeOf)
26 | ```
27 |
28 | Note that the elements in the resulting `HList`
29 | have types matching the `Cases` in `sizeOf`.
30 | We can use `map` with any `Poly` that
31 | provides `Cases` for every member of our starting `HList`.
32 | If the compiler can't find a `Case` for a particular member,
33 | we get a compile error:
34 |
35 | ```tut:book:fail
36 | (1.5 :: HNil).map(sizeOf)
37 | ```
38 |
39 | We can also `flatMap` over an `HList`,
40 | as long as every corresponding case in our `Poly`
41 | returns another `HList`:
42 |
43 | ```tut:book:silent
44 | object valueAndSizeOf extends Poly1 {
45 | implicit val intCase: Case.Aux[Int, Int :: Int :: HNil] =
46 | at(num => num :: num :: HNil)
47 |
48 | implicit val stringCase: Case.Aux[String, String :: Int :: HNil] =
49 | at(str => str :: str.length :: HNil)
50 |
51 | implicit val booleanCase: Case.Aux[Boolean, Boolean :: Int :: HNil] =
52 | at(bool => bool :: (if(bool) 1 else 0) :: HNil)
53 | }
54 | ```
55 |
56 | ```tut:book
57 | (10 :: "hello" :: true :: HNil).flatMap(valueAndSizeOf)
58 | ```
59 |
60 | Again, we get a compilation error if there is a missing case
61 | or one of the cases doesn't return an `HList`:
62 |
63 | ```tut:book:fail
64 | // Using the wrong Poly with flatMap:
65 | (10 :: "hello" :: true :: HNil).flatMap(sizeOf)
66 | ```
67 |
68 | `map` and `flatMap` are based on type classes
69 | called `Mapper` and `FlatMapper` respectively.
70 | We'll see an example that makes direct use of `Mapper`
71 | in Section [@sec:poly:product-mapper].
72 |
73 | ## Folding using *Poly*
74 |
75 | In addition to `map` and `flatMap`,
76 | shapeless also provides
77 | `foldLeft` and `foldRight` operations based on `Poly2`:
78 |
79 | ```tut:book:silent
80 | import shapeless._
81 |
82 | object sum extends Poly2 {
83 | implicit val intIntCase: Case.Aux[Int, Int, Int] =
84 | at((a, b) => a + b)
85 |
86 | implicit val intStringCase: Case.Aux[Int, String, Int] =
87 | at((a, b) => a + b.length)
88 | }
89 | ```
90 |
91 | ```tut:book
92 | (10 :: "hello" :: 100 :: HNil).foldLeft(0)(sum)
93 | ```
94 |
95 | We can also `reduceLeft`, `reduceRight`, `foldMap`, and so on.
96 | Each operation has its own associated type class.
97 | We'll leave it as an exercise to the reader
98 | to investigate the available operations.
99 |
--------------------------------------------------------------------------------
/src/pages/poly/monomorphic-map.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/poly/monomorphic-map.pdf
--------------------------------------------------------------------------------
/src/pages/poly/monomorphic-map.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/pages/poly/poly.md:
--------------------------------------------------------------------------------
1 | ## Polymorphic functions
2 |
3 | Shapeless provides a type called `Poly`
4 | for representing *polymorphic functions*,
5 | where the result type depends on the parameter types.
6 | Here is a simplified explanation of how it works.
7 | Note that the next section doesn't contain real shapeless code---we're
8 | eliding much of the flexibility and ease of use
9 | that comes with real shapeless `Polys`
10 | to create a simplified API for illustrative purposes.
11 |
12 | ### How *Poly* works
13 |
14 | At its core, a `Poly` is an object with a generic `apply` method.
15 | In addition to its regular parameter of type `A`,
16 | `Poly` accepts an implicit parameter of type `Case[A]`:
17 |
18 | ```tut:book:silent
19 | // This is not real shapeless code.
20 | // It's just for demonstration.
21 |
22 | trait Case[P, A] {
23 | type Result
24 | def apply(a: A): Result
25 | }
26 |
27 | trait Poly {
28 | def apply[A](arg: A)(implicit cse: Case[this.type, A]): cse.Result =
29 | cse.apply(arg)
30 | }
31 | ```
32 |
33 | When we define an actual `Poly`,
34 | we provide instances of `Case`
35 | for each parameter type we care about.
36 | These implement the actual function body:
37 |
38 | ```tut:book:silent
39 | // This is not real shapeless code.
40 | // It's just for demonstration.
41 |
42 | object myPoly extends Poly {
43 | implicit def intCase =
44 | new Case[this.type, Int] {
45 | type Result = Double
46 | def apply(num: Int): Double = num / 2.0
47 | }
48 |
49 | implicit def stringCase =
50 | new Case[this.type, String] {
51 | type Result = Int
52 | def apply(str: String): Int = str.length
53 | }
54 | }
55 |
56 | ```
57 |
58 | When we call `myPoly.apply`,
59 | the compiler searches for the relevant implicit `Case`
60 | and inserts it as usual:
61 |
62 | ```tut:book
63 | myPoly.apply(123)
64 | ```
65 |
66 | There is some subtle scoping behaviour here
67 | that allows the compiler to locate instances of `Case`
68 | without any additional imports.
69 | `Case` has an extra type parameter `P`
70 | referencing the singleton type of the `Poly`.
71 | The implicit scope for `Case[P, A]` includes
72 | the companion objects for `Case`, `P`, and `A`.
73 | We've assigned `P` to be `myPoly.type`
74 | and the companion object for `myPoly.type` is `myPoly` itself.
75 | In other words, `Cases` defined in the body of the `Poly`
76 | are always in scope no matter where the call site is.
77 |
78 | ### *Poly* syntax
79 |
80 | The code above isn't real shapeless code.
81 | Fortunately, shapeless makes `Polys` much simpler to define.
82 | Here's our `myPoly` function rewritten in proper syntax:
83 |
84 | ```tut:book:silent
85 | import shapeless._
86 |
87 | object myPoly extends Poly1 {
88 | implicit val intCase: Case.Aux[Int, Double] =
89 | at(num => num / 2.0)
90 |
91 | implicit val stringCase: Case.Aux[String, Int] =
92 | at(str => str.length)
93 | }
94 | ```
95 |
96 | There are a few key differences with our earlier toy syntax:
97 |
98 | 1. We're extending a trait called `Poly1` instead of `Poly`.
99 | Shapeless has a `Poly` type and a set of subtypes,
100 | `Poly1` through `Poly22`, supporting different arities
101 | of polymorphic function.
102 |
103 | 2. The `Case.Aux` types doesn't seem to reference
104 | the singleton type of the `Poly`.
105 | `Case.Aux` is actually a type alias
106 | defined within the body of `Poly1`.
107 | The singleton type is there---we just don't see it.
108 |
109 | 3. We're using a helper method, `at`, to define cases.
110 | This acts as an instance constructor method
111 | as discussed in Section [@sec:generic:idiomatic-style]),
112 | which eliminates a lot of boilerplate.
113 |
114 | Syntactic differences aside,
115 | the shapeless version of `myPoly` is functionally
116 | identical to our toy version.
117 | We can call it with an `Int` or `String` parameter
118 | and get back a result of the corresponding return type:
119 |
120 | ```tut:book
121 | myPoly.apply(123)
122 | myPoly.apply("hello")
123 | ```
124 |
125 | Shapeless also supports `Polys` with more than one parameter.
126 | Here is a binary example:
127 |
128 | ```tut:book:silent
129 | object multiply extends Poly2 {
130 | implicit val intIntCase: Case.Aux[Int, Int, Int] =
131 | at((a, b) => a * b)
132 |
133 | implicit val intStrCase: Case.Aux[Int, String, String] =
134 | at((a, b) => b * a)
135 | }
136 | ```
137 |
138 | ```tut:book
139 | multiply(3, 4)
140 | multiply(3, "4")
141 | ```
142 |
143 | Because `Cases` are just implicit values,
144 | we can define cases based on type classes
145 | and do all of the advanced implicit resolution
146 | covered in previous chapters.
147 | Here's a simple example that
148 | totals numbers in different contexts:
149 |
150 | ```tut:book:silent
151 | import scala.math.Numeric
152 |
153 | object total extends Poly1 {
154 | implicit def base[A](implicit num: Numeric[A]):
155 | Case.Aux[A, Double] =
156 | at(num.toDouble)
157 |
158 | implicit def option[A](implicit num: Numeric[A]):
159 | Case.Aux[Option[A], Double] =
160 | at(opt => opt.map(num.toDouble).getOrElse(0.0))
161 |
162 | implicit def list[A](implicit num: Numeric[A]):
163 | Case.Aux[List[A], Double] =
164 | at(list => num.toDouble(list.sum))
165 | }
166 | ```
167 |
168 | ```tut:book
169 | total(10)
170 | total(Option(20.0))
171 | total(List(1L, 2L, 3L))
172 | ```
173 |
174 |
175 | *Idiosyncrasies of type inference*
176 |
177 | `Poly` pushes Scala's type inference out of its comfort zone.
178 | We can easily confuse the compiler by
179 | asking it to do too much inference at once.
180 | For example, the following code compiles ok:
181 |
182 | ```tut:book:silent
183 | val a = myPoly.apply(123)
184 | val b: Double = a
185 | ```
186 |
187 | However, combining the two lines causes a compilation error:
188 |
189 | ```tut:book:fail
190 | val a: Double = myPoly.apply(123)
191 | ```
192 |
193 | If we add a type annotation, the code compiles again:
194 |
195 | ```tut:book
196 | val a: Double = myPoly.apply[Int](123)
197 | ```
198 |
199 | This behaviour is confusing and annoying.
200 | Unfortunately there are no concrete rules to follow to avoid problems.
201 | The only general guideline is to
202 | try not to over-constrain the compiler,
203 | solve one constraint at a time,
204 | and give it a hint when it gets stuck.
205 |
206 |
--------------------------------------------------------------------------------
/src/pages/poly/polymorphic-map.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/poly/polymorphic-map.pdf
--------------------------------------------------------------------------------
/src/pages/poly/polymorphic-map.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/pages/poly/product-mapper.md:
--------------------------------------------------------------------------------
1 | ## Defining type classes using *Poly* {#sec:poly:product-mapper}
2 |
3 | We can use `Poly` and type classes
4 | like `Mapper` and `FlatMapper`
5 | as building blocks for our own type classes.
6 | As an example let's build a type class
7 | for mapping from one case class to another:
8 |
9 | ```tut:book:silent
10 | trait ProductMapper[A, B, P] {
11 | def apply(a: A): B
12 | }
13 | ```
14 |
15 | We can create an instance of `ProductMapper`
16 | using `Mapper` and a pair of `Generics`:
17 |
18 | ```tut:book:silent
19 | import shapeless._
20 | import shapeless.ops.hlist
21 |
22 | implicit def genericProductMapper[
23 | A, B,
24 | P <: Poly,
25 | ARepr <: HList,
26 | BRepr <: HList
27 | ](
28 | implicit
29 | aGen: Generic.Aux[A, ARepr],
30 | bGen: Generic.Aux[B, BRepr],
31 | mapper: hlist.Mapper.Aux[P, ARepr, BRepr]
32 | ): ProductMapper[A, B, P] =
33 | new ProductMapper[A, B, P] {
34 | def apply(a: A): B =
35 | bGen.from(mapper.apply(aGen.to(a)))
36 | }
37 | ```
38 |
39 | Interestingly, although we define a type `P` for our `Poly`,
40 | we don't reference any values of type `P` anywhere in our code.
41 | The `Mapper` type class uses implicit resolution to find `Cases`,
42 | so the compiler only needs to know the singleton type of `P`
43 | to locate the relevant instances.
44 |
45 | Let's create an extension method
46 | to make `ProductMapper` easier to use.
47 | We only want the user to specify the type of `B` at the call site,
48 | so we use some indirection
49 | to allow the compiler to infer the type of the `Poly`
50 | from a value parameter:
51 |
52 | ```tut:book:silent
53 | implicit class ProductMapperOps[A](a: A) {
54 | class Builder[B] {
55 | def apply[P <: Poly](poly: P)
56 | (implicit pm: ProductMapper[A, B, P]): B =
57 | pm.apply(a)
58 | }
59 |
60 | def mapTo[B]: Builder[B] = new Builder[B]
61 | }
62 | ```
63 |
64 | Here's an example of the method's use:
65 |
66 | ```tut:book:silent
67 | object conversions extends Poly1 {
68 | implicit val intCase: Case.Aux[Int, Boolean] = at(_ > 0)
69 | implicit val boolCase: Case.Aux[Boolean, Int] = at(if(_) 1 else 0)
70 | implicit val strCase: Case.Aux[String, String] = at(identity)
71 | }
72 |
73 | case class IceCream1(name: String, numCherries: Int, inCone: Boolean)
74 | case class IceCream2(name: String, hasCherries: Boolean, numCones: Int)
75 | ```
76 |
77 | ```tut:book
78 | IceCream1("Sundae", 1, false).mapTo[IceCream2](conversions)
79 | ```
80 |
81 | The `mapTo` syntax looks like a single method call,
82 | but is actually two calls:
83 | one call to `mapTo` to fix the `B` type parameter,
84 | and one call to `Builder.apply` to specify the `Poly`.
85 | Some of shapeless' built-in ops extension methods use similar tricks
86 | to provide the user with convenient syntax.
87 |
--------------------------------------------------------------------------------
/src/pages/poly/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we discussed *polymorphic functions*
4 | whose return types vary based on the types of their parameters.
5 | We saw how shapeless' `Poly` type is defined,
6 | and how it is used to implement functional operations such as
7 | `map`, `flatMap`, `foldLeft`, and `foldRight`.
8 |
9 | Each operation is implemented as an extension method on `HList`,
10 | based on a corresponding type class:
11 | `Mapper`, `FlatMapper`, `LeftFolder`, and so on.
12 | We can use these type classes, `Poly`,
13 | and the techniques from Section [@sec:type-level-programming:chaining]
14 | to create our own type classes involving
15 | sequences of sophisticated transformations.
16 |
--------------------------------------------------------------------------------
/src/pages/poly/type-charts.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/poly/type-charts.sketch
--------------------------------------------------------------------------------
/src/pages/representations/adts.md:
--------------------------------------------------------------------------------
1 | ## Recap: algebraic data types
2 |
3 | *Algebraic data types (ADTs)*[^adts]
4 | are a functional programming concept
5 | with a fancy name but a very simple meaning.
6 | They are an idiomatic way of representing data
7 | using "ands" and "ors". For example:
8 |
9 | - a shape is a rectangle **or** a circle
10 | - a rectangle has a width **and** a height
11 | - a circle has a radius
12 |
13 | [^adts]: Not to be confused with "abstract data types",
14 | which are a different tool from computer science
15 | that has little bearing on the discussion here.
16 |
17 | In ADT terminology,
18 | "and" types such as rectangle and circle are called *products*
19 | and "or" types such as shape are called *coproducts*.
20 | In Scala we typically represent products using
21 | case classes and coproducts using sealed traits:
22 |
23 | ```tut:book:silent
24 | sealed trait Shape
25 | final case class Rectangle(width: Double, height: Double) extends Shape
26 | final case class Circle(radius: Double) extends Shape
27 |
28 | val rect: Shape = Rectangle(3.0, 4.0)
29 | val circ: Shape = Circle(1.0)
30 | ```
31 |
32 | The beauty of ADTs is that they are completely type safe.
33 | The compiler has complete knowledge of the algebras[^algebra] we define,
34 | so it can help us write complete,
35 | correctly typed methods involving our types:
36 |
37 | [^algebra]: The word "algebra" meaning: the symbols we define, such as rectangle and circle; and the rules for manipulating those symbols, encoded as methods.
38 |
39 | ```tut:book:silent
40 | def area(shape: Shape): Double =
41 | shape match {
42 | case Rectangle(w, h) => w * h
43 | case Circle(r) => math.Pi * r * r
44 | }
45 | ```
46 |
47 | ```tut:book
48 | area(rect)
49 | area(circ)
50 | ```
51 |
52 | ### Alternative encodings
53 |
54 | Sealed traits and case classes are undoubtedly
55 | the most convenient encoding of ADTs in Scala.
56 | However, they aren't the *only* encoding.
57 | For example, the Scala standard library provides
58 | generic products in the form of `Tuples`
59 | and a generic coproduct in the form of `Either`.
60 | We could have chosen these to encode our `Shape`:
61 |
62 | ```tut:book:silent
63 | type Rectangle2 = (Double, Double)
64 | type Circle2 = Double
65 | type Shape2 = Either[Rectangle2, Circle2]
66 |
67 | val rect2: Shape2 = Left((3.0, 4.0))
68 | val circ2: Shape2 = Right(1.0)
69 | ```
70 |
71 | While this encoding is less readable than the case class encoding above,
72 | it does have some of the same desirable properties.
73 | We can still write completely type safe operations involving `Shape2`:
74 |
75 | ```tut:book:silent
76 | def area2(shape: Shape2): Double =
77 | shape match {
78 | case Left((w, h)) => w * h
79 | case Right(r) => math.Pi * r * r
80 | }
81 | ```
82 |
83 | ```tut:book
84 | area2(rect2)
85 | area2(circ2)
86 | ```
87 |
88 | Importantly, `Shape2` is a more *generic* encoding than `Shape`[^generic].
89 | Any code that operates on a pair of `Doubles`
90 | will be able to operate on a `Rectangle2` and vice versa.
91 | As Scala developers we tend to prefer
92 | semantic types like `Rectangle` and `Circle`
93 | to generic ones like `Rectangle2` and `Circle2`
94 | precisely because of their specialised nature.
95 | However, in some cases generality is desirable.
96 | For example, if we're serializing data to disk,
97 | we don't care about the difference
98 | between a pair of `Doubles` and a `Rectangle2`.
99 | We just write two numbers and we're done.
100 |
101 | Shapeless gives us the best of both worlds:
102 | we can use friendly semantic types by default
103 | and switch to generic representations
104 | when we want interoperability (more on this later).
105 | However, instead of using `Tuples` and `Either`,
106 | shapeless uses its own data types to represent
107 | generic products and coproducts.
108 | We'll introduce these types in the next sections.
109 |
110 | [^generic]: We're using "generic" in an informal way here,
111 | rather than the conventional meaning of
112 | "a type with a type parameter".
113 |
--------------------------------------------------------------------------------
/src/pages/representations/coproduct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/representations/coproduct.png
--------------------------------------------------------------------------------
/src/pages/representations/coproducts.md:
--------------------------------------------------------------------------------
1 | ## Generic coproducts
2 |
3 | Now we know how shapeless encodes product types.
4 | What about coproducts?
5 | We looked at `Either` earlier
6 | but that suffers from similar drawbacks to tuples.
7 | Again, shapeless provides its own encoding that is similar to `HList`:
8 |
9 | ```tut:book:silent
10 | import shapeless.{Coproduct, :+:, CNil, Inl, Inr}
11 |
12 | case class Red()
13 | case class Amber()
14 | case class Green()
15 |
16 | type Light = Red :+: Amber :+: Green :+: CNil
17 | ```
18 |
19 | In general coproducts take the form
20 | `A :+: B :+: C :+: CNil` meaning "A or B or C",
21 | where `:+:` can be loosely interpreted as `Either`.
22 | The overall type of a coproduct
23 | encodes all the possible types in the disjunction,
24 | but each concrete instance
25 | contains a value for just one of the possibilities.
26 | `:+:` has two subtypes, `Inl` and `Inr`,
27 | that correspond loosely to `Left` and `Right`.
28 | We create instances of a coproduct by
29 | nesting `Inl` and `Inr` constructors:
30 |
31 | ```tut:book
32 | val red: Light = Inl(Red())
33 | val green: Light = Inr(Inr(Inl(Green())))
34 | ```
35 |
36 | Every coproduct type is terminated with `CNil`,
37 | which is an empty type with no values, similar to `Nothing`.
38 | We can't instantiate `CNil`
39 | or build a `Coproduct` purely from instances of `Inr`.
40 | We always have exactly one `Inl` in a value.
41 |
42 | Again, it's worth stating that `Coproducts` aren't particularly special.
43 | The functionality above can be achieved using `Either` and `Nothing`
44 | in place of `:+:` and `CNil`.
45 | There are technical difficulties with using `Nothing`,
46 | but we could have used
47 | any other uninhabited or arbitrary singleton type in place of `CNil`.
48 |
49 | ### Switching encodings using *Generic*
50 |
51 | `Coproduct` types are difficult to parse on first glance.
52 | However, we can see how they fit
53 | into the larger picture of generic encodings.
54 | In addition to understanding case classes and case objects,
55 | shapeless' `Generic` type class also understands
56 | sealed traits and abstract classes:
57 |
58 | ```tut:book:silent
59 | import shapeless.Generic
60 |
61 | sealed trait Shape
62 | final case class Rectangle(width: Double, height: Double) extends Shape
63 | final case class Circle(radius: Double) extends Shape
64 | ```
65 |
66 | ```tut:book
67 | val gen = Generic[Shape]
68 | ```
69 |
70 | The `Repr` of the `Generic` for `Shape` is
71 | a `Coproduct` of the subtypes of the sealed trait:
72 | `Rectangle :+: Circle :+: CNil`.
73 | We can use the `to` and `from` methods of the generic
74 | to map back and forth between `Shape` and `gen.Repr`:
75 |
76 | ```tut:book
77 | gen.to(Rectangle(3.0, 4.0))
78 |
79 | gen.to(Circle(1.0))
80 | ```
81 |
--------------------------------------------------------------------------------
/src/pages/representations/hlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/representations/hlist.png
--------------------------------------------------------------------------------
/src/pages/representations/index.md:
--------------------------------------------------------------------------------
1 | # Algebraic data types and generic representations {#sec:representations}
2 |
3 | The main idea behind generic programming
4 | is to solve problems for a wide variety of types
5 | by writing a small amount of generic code.
6 | Shapeless provides two sets of tools to this end:
7 |
8 | 1. a set of generic data types
9 | that can be inspected, traversed, and manipulated
10 | at the type level;
11 |
12 | 2. automatic mapping between *algebraic data types (ADTs)*
13 | (encoded in Scala as case classes and sealed traits)
14 | and these generic representations.
15 |
16 | In this chapter we will start with
17 | a recap of the theory of algebraic data types
18 | and why they might be familiar to Scala developers.
19 | Then we will look at
20 | generic representations used by shapeless
21 | and discuss how they map on to concrete ADTs.
22 | Finally, we will introduce a type class called `Generic`
23 | that provides automatic mapping
24 | back and forth between ADTs and generic representations.
25 | We will finish with some simple examples
26 | using `Generic` to convert values from one type to another.
27 |
--------------------------------------------------------------------------------
/src/pages/representations/products.md:
--------------------------------------------------------------------------------
1 | ## Generic product encodings
2 |
3 | In the previous section we introduced tuples
4 | as a generic representation of products.
5 | Unfortunately, Scala's built-in tuples have a couple of disadvantages
6 | that make them unsuitable for shapeless' purposes:
7 |
8 | 1. Each size of tuple has a different, unrelated type,
9 | making it difficult to write code that abstracts over sizes.
10 |
11 | 2. There is no type for zero-length tuples,
12 | which are important for representing products with zero fields.
13 | We could arguably use `Unit`,
14 | but we ideally want all generic representations
15 | to have a sensible common supertype.
16 | The least upper bound of `Unit` and `Tuple2` is `Any`
17 | so a combination of the two is impractical.
18 |
19 | For these reasons, shapeless uses a different generic encoding
20 | for product types called *heterogeneous lists* or `HLists`[^hlist-name].
21 |
22 | [^hlist-name]: `Product` is perhaps a better name for `HList`,
23 | but the standard library unfortunately already has a type `scala.Product`.
24 |
25 | An `HList` is either the empty list `HNil`,
26 | or a pair `::[H, T]` where `H` is an arbitrary type
27 | and `T` is another `HList`.
28 | Because every `::` has its own `H` and `T`,
29 | the type of each element is encoded separately
30 | in the type of the overall list:
31 |
32 | ```tut:book:silent
33 | import shapeless.{HList, ::, HNil}
34 |
35 | val product: String :: Int :: Boolean :: HNil =
36 | "Sunday" :: 1 :: false :: HNil
37 | ```
38 |
39 | The type and value of the `HList` above mirror one another.
40 | Both represent three members: a `String`, an `Int`, and a `Boolean`.
41 | We can retrieve the `head` and `tail`
42 | and the types of the elements are preserved:
43 |
44 | ```tut:book
45 | val first = product.head
46 | val second = product.tail.head
47 | val rest = product.tail.tail
48 | ```
49 |
50 | The compiler knows the exact length of each `HList`,
51 | so it becomes a compilation error
52 | to take the `head` or `tail` of an empty list:
53 |
54 | ```tut:book:fail
55 | product.tail.tail.tail.head
56 | ```
57 |
58 | We can manipulate and transform `HLists`
59 | in addition to being able to inspect and traverse them.
60 | For example, we can prepend an element with the `::` method.
61 | Again, notice how the type of the result reflects
62 | the number and types of its elements:
63 |
64 | ```tut:book:silent
65 | val newProduct = 42L :: product
66 | ```
67 |
68 | Shapeless also provides tools for performing more complex operations
69 | such as mapping, filtering, and concatenating lists.
70 | We'll discuss these in more detail in Part II.
71 |
72 | The behaviour we get from `HLists` isn't magic.
73 | We could have achieved all of this functionality
74 | using `(A, B)` and `Unit` as alternatives to `::` and `HNil`.
75 | However, there is an advantage in
76 | keeping our representation types separate
77 | from the semantic types used in our applications.
78 | `HList` provides this separation.
79 |
80 | ### Switching representations using *Generic*
81 |
82 | Shapeless provides a type class called `Generic`
83 | that allows us to switch back and forth between
84 | a concrete ADT and its generic representation.
85 | Some behind-the-scenes macro magic
86 | allows us to summon instances of `Generic` without boilerplate:
87 |
88 | ```tut:book:silent
89 | import shapeless.Generic
90 |
91 | case class IceCream(name: String, numCherries: Int, inCone: Boolean)
92 | ```
93 |
94 | ```tut:book
95 | val iceCreamGen = Generic[IceCream]
96 | ```
97 |
98 | Note that the instance of `Generic` has a type member `Repr`
99 | containing the type of its generic representation.
100 | In this case `iceCreamGen.Repr` is `String :: Int :: Boolean :: HNil`.
101 | Instances of `Generic` have two methods:
102 | one for converting `to` the `Repr` type
103 | and one for converting `from` it:
104 |
105 | ```tut:book
106 | val iceCream = IceCream("Sundae", 1, false)
107 |
108 | val repr = iceCreamGen.to(iceCream)
109 |
110 | val iceCream2 = iceCreamGen.from(repr)
111 | ```
112 |
113 | If two ADTs have the same `Repr`,
114 | we can convert back and forth between them using their `Generics`:
115 |
116 | ```tut:book:silent
117 | case class Employee(name: String, number: Int, manager: Boolean)
118 | ```
119 |
120 | ```tut:book
121 | // Create an employee from an ice cream:
122 | val employee = Generic[Employee].from(Generic[IceCream].to(iceCream))
123 | ```
124 |
125 |
126 | *Other product types*
127 |
128 | It's worth noting that
129 | Scala tuples are actually case classes,
130 | so `Generic` works with them just fine:
131 |
132 | ```tut:book:silent
133 | val tupleGen = Generic[(String, Int, Boolean)]
134 | ```
135 |
136 | ```tut:book
137 | tupleGen.to(("Hello", 123, true))
138 | tupleGen.from("Hello" :: 123 :: true :: HNil)
139 | ```
140 |
141 | It also works with case classes of more than 22 fields:
142 |
143 | ```tut:book:silent
144 | case class BigData(
145 | a:Int,b:Int,c:Int,d:Int,e:Int,f:Int,g:Int,h:Int,i:Int,j:Int,
146 | k:Int,l:Int,m:Int,n:Int,o:Int,p:Int,q:Int,r:Int,s:Int,t:Int,
147 | u:Int,v:Int,w:Int)
148 | ```
149 |
150 | ```tut:book
151 | Generic[BigData].from(Generic[BigData].to(BigData(
152 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)))
153 | ```
154 |
155 | In versions 2.10 and earlier, Scala had a limit of 22 fields for case
156 | classes. This limit was nominally fixed in 2.11, but using `HLists`
157 | will help avoid the remaining [limitations of 22 fields in
158 | Scala][link-dallaway-twenty-two].
159 |
160 |
161 |
--------------------------------------------------------------------------------
/src/pages/representations/representations.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/pages/representations/representations.sketch
--------------------------------------------------------------------------------
/src/pages/representations/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | In this chapter we discussed the generic representations
4 | shapeless provides for algebraic data types in Scala:
5 | `HLists` for product types and `Coproducts` for coproduct types.
6 | We also introduced the `Generic` type class
7 | to map back and forth between concrete ADTs and their generic representations.
8 | We haven't yet discussed why generic encodings are so attractive.
9 | The one use case we did cover---converting between ADTs---is
10 | fun but not tremendously useful.
11 |
12 | The real power of `HLists` and `Coproducts` comes from their recursive structure.
13 | We can write code to traverse representations
14 | and calculate values from their constituent elements.
15 | In the next chapter we will look at our first real use case:
16 | automatically deriving type class instances.
17 |
--------------------------------------------------------------------------------
/src/pages/solutions.md:
--------------------------------------------------------------------------------
1 | \appendix
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/pages/type-level-programming/dependent-functions.md:
--------------------------------------------------------------------------------
1 | ## Dependently typed functions {#sec:type-level-programming:depfun}
2 |
3 | Shapeless uses dependent types all over the place:
4 | in `Generic`, in `Witness` (which we will see in the next chapter),
5 | and in a host of other "ops" type classes
6 | that we will survey in Part II of this guide.
7 |
8 | For example, shapeless provides a type class called `Last`
9 | that returns the last element in an `HList`.
10 | Here's a simplified version of its definition:
11 |
12 | ```scala
13 | package shapeless.ops.hlist
14 |
15 | trait Last[L <: HList] {
16 | type Out
17 | def apply(in: L): Out
18 | }
19 | ```
20 |
21 | We can summon instances of `Last`
22 | to inspect `HLists` in our code.
23 | In the two examples below note that
24 | the `Out` types are dependent on
25 | the `HList` types we started with:
26 |
27 | ```tut:book:silent
28 | import shapeless.{HList, ::, HNil}
29 |
30 | import shapeless.ops.hlist.Last
31 | ```
32 |
33 | ```tut:book
34 | val last1 = Last[String :: Int :: HNil]
35 | val last2 = Last[Int :: String :: HNil]
36 | ```
37 |
38 | Once we have summoned instances of `Last`,
39 | we can use them at the value level
40 | via their `apply` methods:
41 |
42 | ```tut:book
43 | last1("foo" :: 123 :: HNil)
44 | last2(321 :: "bar" :: HNil)
45 | ```
46 |
47 | We get two forms of protection against errors.
48 | The implicits defined for `Last` ensure
49 | we can only summon instances if
50 | the input `HList` has at least one element:
51 |
52 | ```tut:book:fail
53 | Last[HNil]
54 | ```
55 |
56 | In addition, the type parameters
57 | on the instances of `Last` check
58 | whether we pass in the expected type of `HList`:
59 |
60 | ```tut:book:fail
61 | last1(321 :: "bar" :: HNil)
62 | ```
63 |
64 | As a further example, let's implement
65 | our own type class, called `Second`,
66 | that returns the second element in an `HList`:
67 |
68 | ```tut:book:silent
69 | trait Second[L <: HList] {
70 | type Out
71 | def apply(value: L): Out
72 | }
73 |
74 | object Second {
75 | type Aux[L <: HList, O] = Second[L] { type Out = O }
76 |
77 | def apply[L <: HList](implicit inst: Second[L]): Aux[L, inst.Out] =
78 | inst
79 | }
80 | ```
81 |
82 | This code uses the idiomatic layout
83 | described in Section [@sec:generic:idiomatic-style].
84 | We define the `Aux` type in the companion object beside
85 | the standard `apply` method for summoning instances.
86 |
87 |
88 | *Summoner methods versus "implicitly" versus "the"*
89 |
90 | Note that the return type on `apply` is `Aux[L, O]`, not `Second[L]`.
91 | This is important.
92 | Using `Aux` ensures the `apply` method
93 | does not erase the type members on summoned instances.
94 | If we define the return type as `Second[L]`,
95 | the `Out` type member will be erased from the return type
96 | and the type class will not work correctly.
97 |
98 | The `implicitly` method from `scala.Predef` has this behaviour.
99 | Compare the type of an instance of `Last` summoned with `implicitly`:
100 |
101 | ```tut:book
102 | implicitly[Last[String :: Int :: HNil]]
103 | ```
104 |
105 | to the type of an instance summoned with `Last.apply`:
106 |
107 | ```tut:book
108 | Last[String :: Int :: HNil]
109 | ```
110 |
111 | The type summoned by `implicitly` has no `Out` type member.
112 | For this reason, we should avoid `implicitly`
113 | when working with dependently typed functions.
114 | We can either use custom summoner methods,
115 | or we can use shapeless' replacement method, `the`:
116 |
117 | ```tut:book:silent
118 | import shapeless._
119 | ```
120 |
121 | ```tut:book
122 | the[Last[String :: Int :: HNil]]
123 | ```
124 |
125 |
126 | We only need a single instance,
127 | defined for `HLists` of at least two elements:
128 |
129 | ```tut:book:silent
130 | import Second._
131 |
132 | implicit def hlistSecond[A, B, Rest <: HList]: Aux[A :: B :: Rest, B] =
133 | new Second[A :: B :: Rest] {
134 | type Out = B
135 | def apply(value: A :: B :: Rest): B =
136 | value.tail.head
137 | }
138 | ```
139 |
140 | We can summon instances using `Second.apply`:
141 |
142 | ```tut:book:invisible
143 | import Second._
144 | ```
145 |
146 | ```tut:book
147 | val second1 = Second[String :: Boolean :: Int :: HNil]
148 | val second2 = Second[String :: Int :: Boolean :: HNil]
149 | ```
150 |
151 | Summoning is subject to similar constraints as `Last`.
152 | If we try to summon an instance for an incompatible `HList`,
153 | resolution fails and we get a compile error:
154 |
155 | ```tut:book:fail
156 | Second[String :: HNil]
157 | ```
158 |
159 | Summoned instances come with an `apply` method
160 | that operates on the relevant type of `HList` at the value level:
161 |
162 | ```tut:book
163 | second1("foo" :: true :: 123 :: HNil)
164 | second2("bar" :: 321 :: false :: HNil)
165 | ```
166 |
167 | ```tut:book:fail
168 | second1("baz" :: HNil)
169 | ```
170 |
171 | ## Chaining dependent functions {#sec:type-level-programming:chaining}
172 |
173 | Dependently typed functions provide
174 | a means of calculating one type from another.
175 | We can *chain* dependently typed functions
176 | to perform calculations involving multiple steps.
177 | For example, we should be able to use a `Generic`
178 | to calculate a `Repr` for a case class,
179 | and use a `Last` to calculate
180 | the type of the last element.
181 | Let's try coding this:
182 |
183 | ```tut:book:invisible
184 | import shapeless.Generic
185 | ```
186 |
187 | ```tut:book:fail
188 | def lastField[A](input: A)(
189 | implicit
190 | gen: Generic[A],
191 | last: Last[gen.Repr]
192 | ): last.Out = last.apply(gen.to(input))
193 | ```
194 |
195 | Unfortunately our code doesn't compile.
196 | This is the same problem we had
197 | in Section [@sec:generic:product-generic]
198 | with our definition of `genericEncoder`.
199 | We worked around the problem by lifting
200 | the free type variable out as a type parameter:
201 |
202 | ```tut:book:silent
203 | def lastField[A, Repr <: HList](input: A)(
204 | implicit
205 | gen: Generic.Aux[A, Repr],
206 | last: Last[Repr]
207 | ): last.Out = last.apply(gen.to(input))
208 | ```
209 |
210 | ```tut:book:invisible
211 | case class Vec(x: Int, y: Int)
212 | case class Rect(origin: Vec, extent: Vec)
213 | ```
214 |
215 | ```tut:book
216 | lastField(Rect(Vec(1, 2), Vec(3, 4)))
217 | ```
218 |
219 | As a general rule,
220 | we always write code in this style.
221 | By encoding all the free variables as type parameters,
222 | we enable the compiler to
223 | unify them with appropriate types.
224 | This goes for more subtle constraints as well.
225 | For example, suppose we wanted
226 | to summon a `Generic` for
227 | a case class of exactly one field.
228 | We might be tempted to write this:
229 |
230 | ```tut:book:silent
231 | def getWrappedValue[A, H](input: A)(
232 | implicit
233 | gen: Generic.Aux[A, H :: HNil]
234 | ): H = gen.to(input).head
235 | ```
236 |
237 | The result here is more insidious.
238 | The method definition compiles but
239 | the compiler can never
240 | find implicits at the call site:
241 |
242 | ```tut:book:silent
243 | case class Wrapper(value: Int)
244 | ```
245 |
246 | ```tut:book:fail
247 | getWrappedValue(Wrapper(42))
248 | ```
249 |
250 | The error message hints at the problem.
251 | The clue is in the appearance of the type `H`.
252 | This is the name of a type parameter in the method:
253 | it shouldn't be appearing
254 | in the type the compiler is trying to unify.
255 | The problem is that the `gen` parameter is over-constrained:
256 | the compiler can't find a `Repr`
257 | *and* ensure its length at the same time.
258 | The type `Nothing` also often provides a clue,
259 | appearing when the compiler
260 | fails to unify covariant type parameters.
261 |
262 | The solution to our problem above
263 | is to separate implicit resolution into steps:
264 |
265 | 1. find a `Generic` with a suitable `Repr` for `A`;
266 | 2. provide the `Repr` that has a head type `H`.
267 |
268 | Here's a revised version of the method
269 | using `=:=` to constrain `Repr`:
270 |
271 | ```tut:book:fail
272 | def getWrappedValue[A, Repr <: HList, Head, Tail <: HList](input: A)(
273 | implicit
274 | gen: Generic.Aux[A, Repr],
275 | ev: (Head :: Tail) =:= Repr
276 | ): Head = gen.to(input).head
277 | ```
278 |
279 | This doesn't compile
280 | because the `head` method in the method body
281 | requires an implicit parameter of type `IsHCons`.
282 | This is a much simpler error message to fix---we
283 | just need to learn a tool from shapeless' toolbox.
284 | `IsHCons` is a shapeless type class
285 | that splits an `HList` into a `Head` and `Tail`.
286 | We can use `IsHCons` instead of `=:=`:
287 |
288 | ```tut:book:silent
289 | import shapeless.ops.hlist.IsHCons
290 |
291 | def getWrappedValue[A, Repr <: HList, Head](in: A)(
292 | implicit
293 | gen: Generic.Aux[A, Repr],
294 | isHCons: IsHCons.Aux[Repr, Head, HNil]
295 | ): Head = gen.to(in).head
296 | ```
297 |
298 | This fixes the bug.
299 | Both the method definition
300 | and the call site now compile as expected:
301 |
302 | ```tut:book
303 | getWrappedValue(Wrapper(42))
304 | ```
305 |
306 | The take home point here isn't
307 | that we solved the problem using `IsHCons`.
308 | Shapeless provides a lot of tools like this
309 | (see Chapters [@sec:ops] to [@sec:nat]),
310 | and we can supplement them where necessary
311 | with our own type classes.
312 | The important point is
313 | to understand the process we use
314 | to write code that compiles
315 | and is capable of finding solutions.
316 | We'll finish off this section
317 | with a step-by-step guide
318 | summarising our findings so far.
319 |
--------------------------------------------------------------------------------
/src/pages/type-level-programming/dependent-types.md:
--------------------------------------------------------------------------------
1 | ## Dependent types
2 |
3 | Last chapter we spent a lot of time using `Generic`,
4 | the type class for mapping ADT types to generic representations.
5 | However, we haven't yet discussed an important bit of theory
6 | that underpins `Generic` and much of shapeless:
7 | *dependent types*.
8 |
9 | To illustrate this, let's take a closer look at `Generic`.
10 | Here's a simplified version of the definition:
11 |
12 | ```scala
13 | trait Generic[A] {
14 | type Repr
15 | def to(value: A): Repr
16 | def from(value: Repr): A
17 | }
18 | ```
19 |
20 | Instances of `Generic` reference two other types:
21 | a type parameter `A` and a type member `Repr`.
22 | Suppose we implement a method `getRepr` as follows.
23 | What type will we get back?
24 |
25 | ```tut:book:silent
26 | import shapeless.Generic
27 |
28 | def getRepr[A](value: A)(implicit gen: Generic[A]) =
29 | gen.to(value)
30 | ```
31 |
32 | The answer is it depends on the instance we get for `gen`.
33 | In expanding the call to `getRepr`,
34 | the compiler will search for a `Generic[A]`
35 | and the result type will be whatever `Repr`
36 | is defined in that instance:
37 |
38 | ```tut:book:silent
39 | case class Vec(x: Int, y: Int)
40 | case class Rect(origin: Vec, size: Vec)
41 | ```
42 |
43 | ```tut:book
44 | getRepr(Vec(1, 2))
45 | getRepr(Rect(Vec(0, 0), Vec(5, 5)))
46 | ```
47 |
48 | What we're seeing here is called *dependent typing*:
49 | the result type of `getRepr` depends on its value parameters
50 | via their type members.
51 | Suppose we had specified `Repr`
52 | as type parameter on `Generic`
53 | instead of a type member:
54 |
55 | ```tut:book:silent
56 | trait Generic2[A, Repr]
57 |
58 | def getRepr2[A, R](value: A)(implicit generic: Generic2[A, R]): R =
59 | ???
60 | ```
61 |
62 | We would have had to pass the desired value of `Repr`
63 | to `getRepr` as a type parameter,
64 | effectively making `getRepr` useless.
65 | The intuitive take-away from this is
66 | that type parameters are useful as "inputs"
67 | and type members are useful as "outputs".
68 |
--------------------------------------------------------------------------------
/src/pages/type-level-programming/index.md:
--------------------------------------------------------------------------------
1 | # Working with types and implicits {#sec:type-level-programming}
2 |
3 | In the last chapter we saw
4 | one of the most compelling use cases for shapeless:
5 | automatically deriving type class instances.
6 | There are plenty of even more powerful examples coming later.
7 | However, before we move on, we should take time
8 | to discuss some theory we've skipped over
9 | and establish a set of patterns for writing and debugging
10 | type- and implicit-heavy code.
11 |
--------------------------------------------------------------------------------
/src/pages/type-level-programming/summary.md:
--------------------------------------------------------------------------------
1 | ## Summary {#sec:type-level-programming:summary}
2 |
3 | When coding with shapeless,
4 | we are often trying to find a target type
5 | that depends on values in our code.
6 | This relationship is called *dependent typing*.
7 |
8 | Problems involving dependent types
9 | can be conveniently expressed using implicit search,
10 | allowing the compiler to resolve
11 | intermediate and target types
12 | given a starting point at the call site.
13 |
14 | We often have to use multiple steps
15 | to calculate a result
16 | (e.g. using a `Generic` to get a `Repr`,
17 | then using another type class to get to another type).
18 | When we do this,
19 | there are a few rules we can follow
20 | to ensure our code compiles and works as expected:
21 |
22 | 1. We should extract every intermediate type
23 | out to a type parameter.
24 | Many type parameters won't be used in the result,
25 | but the compiler needs them to know which types it has to unify.
26 |
27 | 2. The compiler resolves implicits from left to right,
28 | backtracking if it can't find a working combination.
29 | We should write implicits in the order we need them,
30 | using one or more type variables
31 | to connect them to previous implicits.
32 |
33 | 3. The compiler can only solve for one constraint at a time,
34 | so we mustn't over-constrain any single implicit.
35 |
36 | 4. We should state the return type explicitly,
37 | specifying any type parameters and type members
38 | that may be needed elsewhere.
39 | Type members are often important,
40 | so we should use `Aux` types
41 | to preserve them where appropriate.
42 | If we don't state them in the return type,
43 | they won't be available to the compiler
44 | for further implicit resolution.
45 |
46 | 5. The `Aux` type alias pattern is useful
47 | for keeping code readable.
48 | We should look out for `Aux` aliases
49 | when using tools from the shapeless toolbox,
50 | and implement `Aux` aliases
51 | on our own dependently typed functions.
52 |
53 | When we find a useful chain of dependently typed operations
54 | we can capture them as a single type class.
55 | This is sometimes called the "lemma" pattern
56 | (a term borrowed from mathematical proofs).
57 | We'll see an example of this pattern
58 | in Section [@sec:ops:penultimate].
59 |
--------------------------------------------------------------------------------
/src/template/cover-notes.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/cover-notes.html
--------------------------------------------------------------------------------
/src/template/cover-notes.tex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/cover-notes.tex
--------------------------------------------------------------------------------
/src/template/images/brand.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/images/brand.pdf
--------------------------------------------------------------------------------
/src/template/images/brand.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
--------------------------------------------------------------------------------
/src/template/images/hero-arrow-overlay-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/src/template/images/hero-left-overlay-white.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/images/hero-left-overlay-white.pdf
--------------------------------------------------------------------------------
/src/template/images/hero-left-overlay-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/src/template/images/hero-right-overlay-white.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/images/hero-right-overlay-white.pdf
--------------------------------------------------------------------------------
/src/template/images/hero-right-overlay-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/src/template/images/spaaace.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/images/spaaace.pdf
--------------------------------------------------------------------------------
/src/template/images/spaaace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underscoreio/shapeless-guide/110d34792457e1edccb9f9361a3074a1a6640df9/src/template/images/spaaace.png
--------------------------------------------------------------------------------
/src/template/template.epub.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | $pagetitle$
9 | $if(highlighting-css)$
10 |
13 | $endif$
14 | $for(css)$
15 |
16 | $endfor$
17 |
18 |
19 | $if(titlepage)$
20 |