diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9e6ccd3f..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Continuous Integration - -on: - pull_request: - branches: ['**'] - push: - branches: ['main'] - -jobs: - build: - name: Build and Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 25 - - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@main - with: - scala-cli-version: 1.12.2 - - run: scala-cli fmt --check . - - run: scala-cli --server=false build.scala - - if: github.event_name != 'pull_request' - uses: peaceiris/actions-gh-pages@v4.0.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: target - cname: typelevel.org diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1ef06c8e..00000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.bsp -.metals -.scala-build -.vscode - -target/ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/.scalafmt.conf b/.scalafmt.conf deleted file mode 100644 index d14e837a..00000000 --- a/.scalafmt.conf +++ /dev/null @@ -1,2 +0,0 @@ -version = "3.11.1" -runner.dialect = scala3 \ No newline at end of file diff --git a/src/.well-known/openpgpkey/hu/nwnwrk3rczw4ou5x56ibcrdatrgf1xag b/.well-known/openpgpkey/hu/nwnwrk3rczw4ou5x56ibcrdatrgf1xag similarity index 100% rename from src/.well-known/openpgpkey/hu/nwnwrk3rczw4ou5x56ibcrdatrgf1xag rename to .well-known/openpgpkey/hu/nwnwrk3rczw4ou5x56ibcrdatrgf1xag diff --git a/src/.well-known/openpgpkey/policy b/.well-known/openpgpkey/policy similarity index 100% rename from src/.well-known/openpgpkey/policy rename to .well-known/openpgpkey/policy diff --git a/src/.well-known/security.txt b/.well-known/security.txt similarity index 100% rename from src/.well-known/security.txt rename to .well-known/security.txt diff --git a/404.html b/404.html new file mode 100644 index 00000000..0db96d37 --- /dev/null +++ b/404.html @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + Page Not Found (404) + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Page Not Found (404)

+

Sorry, this page is missing. If you think this is a mistake, please open an issue or email us.

+
+ +
+ + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..4aee97c7 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +typelevel.org diff --git a/README.md b/README.md deleted file mode 100644 index f5a67f68..00000000 --- a/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# typelevel.org - -This is the source of typelevel.org. It is built with [Laika] and deployed to GitHub Pages. - -## Get Started - -To work on the website, you will need: -* Scala 3.5 or later -* Java 21 or later - -### Preview Server - -For the best experience, serve the website to immediately see your changes in a live preview. - -```bash -scala build.scala -- serve -``` - -Within a few seconds, a preview server will be available at http://localhost:8000/. Press `Ctrl+C` to stop the server. In case you need to use a different port, you may pass it as an option. -```bash -scala run build.scala -- serve --port 8080 -``` - -### Write a blog post - -Blog posts (including event announcements) are added to the `src/blog/` directory. Content is written using [GitHub-flavored Markdown][gfm]. Code blocks support syntax highlighting in Scala and [several other languages][syntax]. Rendering of mathematical expressions is enabled for any document by setting `katex: true` in the configuration header and using the `@:math` directive. - -``` -@:math -\forall a,b,c \in S : (a \cdot b) \cdot c = a \cdot (b \cdot c) -@:@ -``` - -If this is your first blog post, be sure to add your author info to `src/directory.conf`. - -```hocon -toolkitty { - name: Toolkitty - pronouns: "they/them" - avatar: "https://github.com/toolkitty.png" - github: toolkitty - bluesky: toolkitty.bsky.social - bio: "I am the mascot of the Scala Toolkit!" -} -``` - -Note that event announcements use a custom template with additional fields specified in the configuration header. - -``` -{% - laika.html.template: event.template.html - date: "2025-08-15" # the date the post is published - event-date: "August 22, 2025" # the actual date of the event - event-location: "École Polytechnique Fédérale de Lausanne" - tags: [events] -%} -``` - -## Development - -The build machinery is defined in `build.scala`. It implements several customizations, including an RSS feed generator and integrations with Protosearch, KaTeX, and Font Awesome. - -To learn more about how you can develop and customize the website please reference the extensive [Laika] documentation. - -## Support - -We are happy to help you contribute to our website! Please [create a discussion][discussion] or message the [#website][discord] channel on the Typelevel Discord. - -[Laika]: https://typelevel.org/Laika -[syntax]: https://typelevel.org/Laika/latest/03-preparing-content/05-syntax-highlighting.html#supported-languages -[gfm]: https://github.github.com/gfm/ -[discussion]: https://github.com/typelevel/typelevel.github.com/discussions/new/choose -[discord]: https://discord.gg/krrdNdSDFf diff --git a/about/contributing/index.html b/about/contributing/index.html new file mode 100644 index 00000000..20a3145d --- /dev/null +++ b/about/contributing/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..eb9903aa --- /dev/null +++ b/about/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/04/04/inauguration.html b/blog/2013/04/04/inauguration.html new file mode 100644 index 00000000..0aa466cc --- /dev/null +++ b/blog/2013/04/04/inauguration.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/06/24/deriving-instances-1.html b/blog/2013/06/24/deriving-instances-1.html new file mode 100644 index 00000000..fa8bc901 --- /dev/null +++ b/blog/2013/06/24/deriving-instances-1.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/07/07/generic-numeric-programming.html b/blog/2013/07/07/generic-numeric-programming.html new file mode 100644 index 00000000..3899729e --- /dev/null +++ b/blog/2013/07/07/generic-numeric-programming.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/09/11/using-scalaz-Unapply.html b/blog/2013/09/11/using-scalaz-Unapply.html new file mode 100644 index 00000000..7b5adafd --- /dev/null +++ b/blog/2013/09/11/using-scalaz-Unapply.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/10/13/spires-ops-macros.html b/blog/2013/10/13/spires-ops-macros.html new file mode 100644 index 00000000..7cb40bb6 --- /dev/null +++ b/blog/2013/10/13/spires-ops-macros.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/10/13/towards-scalaz-1.html b/blog/2013/10/13/towards-scalaz-1.html new file mode 100644 index 00000000..ff4689d0 --- /dev/null +++ b/blog/2013/10/13/towards-scalaz-1.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/10/18/treelog.html b/blog/2013/10/18/treelog.html new file mode 100644 index 00000000..d15dd200 --- /dev/null +++ b/blog/2013/10/18/treelog.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/11/17/discipline.html b/blog/2013/11/17/discipline.html new file mode 100644 index 00000000..c7bf7c9a --- /dev/null +++ b/blog/2013/11/17/discipline.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2013/12/15/towards-scalaz-2.html b/blog/2013/12/15/towards-scalaz-2.html new file mode 100644 index 00000000..14b6fa5c --- /dev/null +++ b/blog/2013/12/15/towards-scalaz-2.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/01/18/implicitly_existential.html b/blog/2014/01/18/implicitly_existential.html new file mode 100644 index 00000000..0e9508a8 --- /dev/null +++ b/blog/2014/01/18/implicitly_existential.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/02/21/error-handling.html b/blog/2014/02/21/error-handling.html new file mode 100644 index 00000000..40e7bad1 --- /dev/null +++ b/blog/2014/02/21/error-handling.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/03/09/liskov_lifting.html b/blog/2014/03/09/liskov_lifting.html new file mode 100644 index 00000000..4238983e --- /dev/null +++ b/blog/2014/03/09/liskov_lifting.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/04/14/fix.html b/blog/2014/04/14/fix.html new file mode 100644 index 00000000..71c4176f --- /dev/null +++ b/blog/2014/04/14/fix.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/06/22/mapping-sets.html b/blog/2014/06/22/mapping-sets.html new file mode 100644 index 00000000..d5ce42bf --- /dev/null +++ b/blog/2014/06/22/mapping-sets.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/07/02/type_equality_to_leibniz.html b/blog/2014/07/02/type_equality_to_leibniz.html new file mode 100644 index 00000000..529cc241 --- /dev/null +++ b/blog/2014/07/02/type_equality_to_leibniz.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/07/06/singleton_instance_trick_unsafe.html b/blog/2014/07/06/singleton_instance_trick_unsafe.html new file mode 100644 index 00000000..33977811 --- /dev/null +++ b/blog/2014/07/06/singleton_instance_trick_unsafe.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/09/02/typelevel-scala.html b/blog/2014/09/02/typelevel-scala.html new file mode 100644 index 00000000..3e01abac --- /dev/null +++ b/blog/2014/09/02/typelevel-scala.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/09/20/higher_leibniz.html b/blog/2014/09/20/higher_leibniz.html new file mode 100644 index 00000000..0e682f29 --- /dev/null +++ b/blog/2014/09/20/higher_leibniz.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2014/11/10/why_is_adt_pattern_matching_allowed.html b/blog/2014/11/10/why_is_adt_pattern_matching_allowed.html new file mode 100644 index 00000000..0ae85beb --- /dev/null +++ b/blog/2014/11/10/why_is_adt_pattern_matching_allowed.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/02/26/rawtypes.html b/blog/2015/02/26/rawtypes.html new file mode 100644 index 00000000..b75e4f9a --- /dev/null +++ b/blog/2015/02/26/rawtypes.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/13/type-members-parameters.html b/blog/2015/07/13/type-members-parameters.html new file mode 100644 index 00000000..0a912a40 --- /dev/null +++ b/blog/2015/07/13/type-members-parameters.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/16/method-equiv.html b/blog/2015/07/16/method-equiv.html new file mode 100644 index 00000000..5d340275 --- /dev/null +++ b/blog/2015/07/16/method-equiv.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/19/forget-refinement-aux.html b/blog/2015/07/19/forget-refinement-aux.html new file mode 100644 index 00000000..63bc1294 --- /dev/null +++ b/blog/2015/07/19/forget-refinement-aux.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/23/type-projection.html b/blog/2015/07/23/type-projection.html new file mode 100644 index 00000000..093577fa --- /dev/null +++ b/blog/2015/07/23/type-projection.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/27/nested-existentials.html b/blog/2015/07/27/nested-existentials.html new file mode 100644 index 00000000..3273a0db --- /dev/null +++ b/blog/2015/07/27/nested-existentials.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/07/30/values-never-change-types.html b/blog/2015/07/30/values-never-change-types.html new file mode 100644 index 00000000..46ec0042 --- /dev/null +++ b/blog/2015/07/30/values-never-change-types.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/08/06/machinist.html b/blog/2015/08/06/machinist.html new file mode 100644 index 00000000..6a222f3d --- /dev/null +++ b/blog/2015/08/06/machinist.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/08/07/symbolic-operators.html b/blog/2015/08/07/symbolic-operators.html new file mode 100644 index 00000000..16242b31 --- /dev/null +++ b/blog/2015/08/07/symbolic-operators.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/09/21/change-values.html b/blog/2015/09/21/change-values.html new file mode 100644 index 00000000..814f8e8c --- /dev/null +++ b/blog/2015/09/21/change-values.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2015/12/11/announcement_summit.html b/blog/2015/12/11/announcement_summit.html new file mode 100644 index 00000000..0023487f --- /dev/null +++ b/blog/2015/12/11/announcement_summit.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/01/14/summit_assistance.html b/blog/2016/01/14/summit_assistance.html new file mode 100644 index 00000000..4b31e10c --- /dev/null +++ b/blog/2016/01/14/summit_assistance.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/01/20/summit_keynote.html b/blog/2016/01/20/summit_keynote.html new file mode 100644 index 00000000..72fbb7c7 --- /dev/null +++ b/blog/2016/01/20/summit_keynote.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/01/28/existential-inside.html b/blog/2016/01/28/existential-inside.html new file mode 100644 index 00000000..bdc563d5 --- /dev/null +++ b/blog/2016/01/28/existential-inside.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/01/28/summit_programme.html b/blog/2016/01/28/summit_programme.html new file mode 100644 index 00000000..eced4706 --- /dev/null +++ b/blog/2016/01/28/summit_programme.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/02/04/variance-and-functors.html b/blog/2016/02/04/variance-and-functors.html new file mode 100644 index 00000000..e49fe27a --- /dev/null +++ b/blog/2016/02/04/variance-and-functors.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/03/13/information-hiding.html b/blog/2016/03/13/information-hiding.html new file mode 100644 index 00000000..3093cbd5 --- /dev/null +++ b/blog/2016/03/13/information-hiding.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/03/24/typelevel-boulder.html b/blog/2016/03/24/typelevel-boulder.html new file mode 100644 index 00000000..eb6a2e3d --- /dev/null +++ b/blog/2016/03/24/typelevel-boulder.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/05/10/internal-state.html b/blog/2016/05/10/internal-state.html new file mode 100644 index 00000000..decf02eb --- /dev/null +++ b/blog/2016/05/10/internal-state.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/08/21/hkts-moving-forward.html b/blog/2016/08/21/hkts-moving-forward.html new file mode 100644 index 00000000..32508b98 --- /dev/null +++ b/blog/2016/08/21/hkts-moving-forward.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/09/19/variance-phantom.html b/blog/2016/09/19/variance-phantom.html new file mode 100644 index 00000000..d3d3c7dd --- /dev/null +++ b/blog/2016/09/19/variance-phantom.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/09/21/edsls-part-1.html b/blog/2016/09/21/edsls-part-1.html new file mode 100644 index 00000000..949e8ff4 --- /dev/null +++ b/blog/2016/09/21/edsls-part-1.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/09/30/subtype-typeclasses.html b/blog/2016/09/30/subtype-typeclasses.html new file mode 100644 index 00000000..9d8cb085 --- /dev/null +++ b/blog/2016/09/30/subtype-typeclasses.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/10/17/minicheck.html b/blog/2016/10/17/minicheck.html new file mode 100644 index 00000000..93ee1488 --- /dev/null +++ b/blog/2016/10/17/minicheck.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/10/18/scala-center.html b/blog/2016/10/18/scala-center.html new file mode 100644 index 00000000..130f8ca3 --- /dev/null +++ b/blog/2016/10/18/scala-center.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/10/26/edsls-part-2.html b/blog/2016/10/26/edsls-part-2.html new file mode 100644 index 00000000..64d4ca25 --- /dev/null +++ b/blog/2016/10/26/edsls-part-2.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/11/17/heaps.html b/blog/2016/11/17/heaps.html new file mode 100644 index 00000000..b516ef0c --- /dev/null +++ b/blog/2016/11/17/heaps.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2016/12/17/scala-coc.html b/blog/2016/12/17/scala-coc.html new file mode 100644 index 00000000..64fd1093 --- /dev/null +++ b/blog/2016/12/17/scala-coc.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/02/13/more-types-than-classes.html b/blog/2017/02/13/more-types-than-classes.html new file mode 100644 index 00000000..c826b4ed --- /dev/null +++ b/blog/2017/02/13/more-types-than-classes.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/03/01/four-ways-to-escape-a-cake.html b/blog/2017/03/01/four-ways-to-escape-a-cake.html new file mode 100644 index 00000000..12e10df7 --- /dev/null +++ b/blog/2017/03/01/four-ways-to-escape-a-cake.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/04/02/equivalence-vs-equality.html b/blog/2017/04/02/equivalence-vs-equality.html new file mode 100644 index 00000000..63bf0dd4 --- /dev/null +++ b/blog/2017/04/02/equivalence-vs-equality.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/05/02/io-monad-for-cats.html b/blog/2017/05/02/io-monad-for-cats.html new file mode 100644 index 00000000..75565faf --- /dev/null +++ b/blog/2017/05/02/io-monad-for-cats.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/06/13/libra.html b/blog/2017/06/13/libra.html new file mode 100644 index 00000000..52602f7a --- /dev/null +++ b/blog/2017/06/13/libra.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/06/21/ciris.html b/blog/2017/06/21/ciris.html new file mode 100644 index 00000000..7e224c70 --- /dev/null +++ b/blog/2017/06/21/ciris.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/08/04/cats-1.0-mf.html b/blog/2017/08/04/cats-1.0-mf.html new file mode 100644 index 00000000..b024cd7b --- /dev/null +++ b/blog/2017/08/04/cats-1.0-mf.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/09/05/three-types-of-strings.html b/blog/2017/09/05/three-types-of-strings.html new file mode 100644 index 00000000..5828c090 --- /dev/null +++ b/blog/2017/09/05/three-types-of-strings.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/12/20/who-implements-typeclass.html b/blog/2017/12/20/who-implements-typeclass.html new file mode 100644 index 00000000..0e80290c --- /dev/null +++ b/blog/2017/12/20/who-implements-typeclass.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/12/25/cats-1.0.0.html b/blog/2017/12/25/cats-1.0.0.html new file mode 100644 index 00000000..03faad88 --- /dev/null +++ b/blog/2017/12/25/cats-1.0.0.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2017/12/27/optimizing-final-tagless.html b/blog/2017/12/27/optimizing-final-tagless.html new file mode 100644 index 00000000..8682f407 --- /dev/null +++ b/blog/2017/12/27/optimizing-final-tagless.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/04/13/rethinking-monaderror.html b/blog/2018/04/13/rethinking-monaderror.html new file mode 100644 index 00000000..25702895 --- /dev/null +++ b/blog/2018/04/13/rethinking-monaderror.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/05/09/product-with-serializable.html b/blog/2018/05/09/product-with-serializable.html new file mode 100644 index 00000000..cc476e42 --- /dev/null +++ b/blog/2018/05/09/product-with-serializable.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/05/09/tagless-final-streaming.html b/blog/2018/05/09/tagless-final-streaming.html new file mode 100644 index 00000000..0d3acf64 --- /dev/null +++ b/blog/2018/05/09/tagless-final-streaming.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/06/07/shared-state-in-fp.html b/blog/2018/06/07/shared-state-in-fp.html new file mode 100644 index 00000000..a6527126 --- /dev/null +++ b/blog/2018/06/07/shared-state-in-fp.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/06/15/typedapi.html b/blog/2018/06/15/typedapi.html new file mode 100644 index 00000000..0d1c40e9 --- /dev/null +++ b/blog/2018/06/15/typedapi.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/06/27/optimizing-tagless-final-2.html b/blog/2018/06/27/optimizing-tagless-final-2.html new file mode 100644 index 00000000..c52a4b24 --- /dev/null +++ b/blog/2018/06/27/optimizing-tagless-final-2.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/07/12/testing-in-the-wild.html b/blog/2018/07/12/testing-in-the-wild.html new file mode 100644 index 00000000..258a760a --- /dev/null +++ b/blog/2018/07/12/testing-in-the-wild.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/08/07/refactoring-monads.html b/blog/2018/08/07/refactoring-monads.html new file mode 100644 index 00000000..fdedf159 --- /dev/null +++ b/blog/2018/08/07/refactoring-monads.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/08/25/http4s-error-handling-mtl.html b/blog/2018/08/25/http4s-error-handling-mtl.html new file mode 100644 index 00000000..b9407750 --- /dev/null +++ b/blog/2018/08/25/http4s-error-handling-mtl.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/09/04/chain-replacing-the-list-monoid.html b/blog/2018/09/04/chain-replacing-the-list-monoid.html new file mode 100644 index 00000000..6a5cbc3a --- /dev/null +++ b/blog/2018/09/04/chain-replacing-the-list-monoid.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/09/29/monad-transformer-variance.html b/blog/2018/09/29/monad-transformer-variance.html new file mode 100644 index 00000000..3a4cb97f --- /dev/null +++ b/blog/2018/09/29/monad-transformer-variance.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/10/06/intro-to-mtl.html b/blog/2018/10/06/intro-to-mtl.html new file mode 100644 index 00000000..35110389 --- /dev/null +++ b/blog/2018/10/06/intro-to-mtl.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/11/02/semirings.html b/blog/2018/11/02/semirings.html new file mode 100644 index 00000000..125dc85b --- /dev/null +++ b/blog/2018/11/02/semirings.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2018/11/28/http4s-error-handling-mtl-2.html b/blog/2018/11/28/http4s-error-handling-mtl-2.html new file mode 100644 index 00000000..2756a92c --- /dev/null +++ b/blog/2018/11/28/http4s-error-handling-mtl-2.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/01/30/cats-ecosystem-community-survey-results.html b/blog/2019/01/30/cats-ecosystem-community-survey-results.html new file mode 100644 index 00000000..7458d808 --- /dev/null +++ b/blog/2019/01/30/cats-ecosystem-community-survey-results.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/02/06/algebraic-api-design.html b/blog/2019/02/06/algebraic-api-design.html new file mode 100644 index 00000000..dd2bf96d --- /dev/null +++ b/blog/2019/02/06/algebraic-api-design.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/04/24/typelevel-sustainability-program-announcement.html b/blog/2019/04/24/typelevel-sustainability-program-announcement.html new file mode 100644 index 00000000..37ca716e --- /dev/null +++ b/blog/2019/04/24/typelevel-sustainability-program-announcement.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/05/01/typelevel-switches-to-scala-code-of-conduct.html b/blog/2019/05/01/typelevel-switches-to-scala-code-of-conduct.html new file mode 100644 index 00000000..e9684ada --- /dev/null +++ b/blog/2019/05/01/typelevel-switches-to-scala-code-of-conduct.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/05/29/support-typelevel-thanks-to-triplequote-hydra.html b/blog/2019/05/29/support-typelevel-thanks-to-triplequote-hydra.html new file mode 100644 index 00000000..29ad36b6 --- /dev/null +++ b/blog/2019/05/29/support-typelevel-thanks-to-triplequote-hydra.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/09/05/jdg.html b/blog/2019/09/05/jdg.html new file mode 100644 index 00000000..1db88fe1 --- /dev/null +++ b/blog/2019/09/05/jdg.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2019/11/13/Update-about-sustainability-program.html b/blog/2019/11/13/Update-about-sustainability-program.html new file mode 100644 index 00000000..99c8ae22 --- /dev/null +++ b/blog/2019/11/13/Update-about-sustainability-program.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2020/06/17/confronting-racism.html b/blog/2020/06/17/confronting-racism.html new file mode 100644 index 00000000..38661071 --- /dev/null +++ b/blog/2020/06/17/confronting-racism.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2020/10/30/concurrency-in-ce3.html b/blog/2020/10/30/concurrency-in-ce3.html new file mode 100644 index 00000000..37a07d41 --- /dev/null +++ b/blog/2020/10/30/concurrency-in-ce3.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2021/02/21/fibers-fast-mkay.html b/blog/2021/02/21/fibers-fast-mkay.html new file mode 100644 index 00000000..0333021b --- /dev/null +++ b/blog/2021/02/21/fibers-fast-mkay.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2021/04/27/community-safety.html b/blog/2021/04/27/community-safety.html new file mode 100644 index 00000000..6ac68437 --- /dev/null +++ b/blog/2021/04/27/community-safety.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2021/05/05/discord-migration.html b/blog/2021/05/05/discord-migration.html new file mode 100644 index 00000000..4b566f4c --- /dev/null +++ b/blog/2021/05/05/discord-migration.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2021/11/15/on-recent-events.html b/blog/2021/11/15/on-recent-events.html new file mode 100644 index 00000000..4e7d86e5 --- /dev/null +++ b/blog/2021/11/15/on-recent-events.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/01/19/governing-documents.html b/blog/2022/01/19/governing-documents.html new file mode 100644 index 00000000..2e3fff46 --- /dev/null +++ b/blog/2022/01/19/governing-documents.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/04/01/call-for-steering-committee-members.html b/blog/2022/04/01/call-for-steering-committee-members.html new file mode 100644 index 00000000..4ff48214 --- /dev/null +++ b/blog/2022/04/01/call-for-steering-committee-members.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/07/25/welcoming-new-steering-committee-members.html b/blog/2022/07/25/welcoming-new-steering-committee-members.html new file mode 100644 index 00000000..be683498 --- /dev/null +++ b/blog/2022/07/25/welcoming-new-steering-committee-members.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/09/06/new-website-layout.html b/blog/2022/09/06/new-website-layout.html new file mode 100644 index 00000000..a3724348 --- /dev/null +++ b/blog/2022/09/06/new-website-layout.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/09/12/tuple-announcement.html b/blog/2022/09/12/tuple-announcement.html new file mode 100644 index 00000000..11a1f987 --- /dev/null +++ b/blog/2022/09/12/tuple-announcement.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/09/19/typelevel-native.html b/blog/2022/09/19/typelevel-native.html new file mode 100644 index 00000000..a15334bb --- /dev/null +++ b/blog/2022/09/19/typelevel-native.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2022/11/10/fabric.html b/blog/2022/11/10/fabric.html new file mode 100644 index 00000000..4025c958 --- /dev/null +++ b/blog/2022/11/10/fabric.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2023/02/23/gsoc.html b/blog/2023/02/23/gsoc.html new file mode 100644 index 00000000..e82283a5 --- /dev/null +++ b/blog/2023/02/23/gsoc.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2023/04/03/typelevel_toolkit.html b/blog/2023/04/03/typelevel_toolkit.html new file mode 100644 index 00000000..8a398688 --- /dev/null +++ b/blog/2023/04/03/typelevel_toolkit.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2023/11/03/charter-changes.html b/blog/2023/11/03/charter-changes.html new file mode 100644 index 00000000..00087a79 --- /dev/null +++ b/blog/2023/11/03/charter-changes.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/03/02/gsoc.html b/blog/2024/03/02/gsoc.html new file mode 100644 index 00000000..fe377d2f --- /dev/null +++ b/blog/2024/03/02/gsoc.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/03/10/github-seats.html b/blog/2024/03/10/github-seats.html new file mode 100644 index 00000000..1bb55a02 --- /dev/null +++ b/blog/2024/03/10/github-seats.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/03/11/code-of-conduct.html b/blog/2024/03/11/code-of-conduct.html new file mode 100644 index 00000000..8fa80d72 --- /dev/null +++ b/blog/2024/03/11/code-of-conduct.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/08/24/call-for-code-of-conduct-committee-members.html b/blog/2024/08/24/call-for-code-of-conduct-committee-members.html new file mode 100644 index 00000000..8cab3a83 --- /dev/null +++ b/blog/2024/08/24/call-for-code-of-conduct-committee-members.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/11/21/new-code-of-conduct-committee-members.html b/blog/2024/11/21/new-code-of-conduct-committee-members.html new file mode 100644 index 00000000..6579c369 --- /dev/null +++ b/blog/2024/11/21/new-code-of-conduct-committee-members.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2024/12/22/gsoc24-going-feral-on-the-cloud.html b/blog/2024/12/22/gsoc24-going-feral-on-the-cloud.html new file mode 100644 index 00000000..82979f98 --- /dev/null +++ b/blog/2024/12/22/gsoc24-going-feral-on-the-cloud.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2025/02/21/spotify-foss-fund.html b/blog/2025/02/21/spotify-foss-fund.html new file mode 100644 index 00000000..2cd06930 --- /dev/null +++ b/blog/2025/02/21/spotify-foss-fund.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2025/02/27/gsoc.html b/blog/2025/02/27/gsoc.html new file mode 100644 index 00000000..996ebf1f --- /dev/null +++ b/blog/2025/02/27/gsoc.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2025/06/10/weaver-test-release.html b/blog/2025/06/10/weaver-test-release.html new file mode 100644 index 00000000..6083056e --- /dev/null +++ b/blog/2025/06/10/weaver-test-release.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2025/08/19/evolving-typelevel.html b/blog/2025/08/19/evolving-typelevel.html new file mode 100644 index 00000000..b24930e4 --- /dev/null +++ b/blog/2025/08/19/evolving-typelevel.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/2025/09/02/custom-error-types.html b/blog/2025/09/02/custom-error-types.html new file mode 100644 index 00000000..853b4ed3 --- /dev/null +++ b/blog/2025/09/02/custom-error-types.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/Update-about-sustainability-program.html b/blog/Update-about-sustainability-program.html new file mode 100644 index 00000000..53d19f36 --- /dev/null +++ b/blog/Update-about-sustainability-program.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/algebraic-api-design.html b/blog/algebraic-api-design.html new file mode 100644 index 00000000..badb01c3 --- /dev/null +++ b/blog/algebraic-api-design.html @@ -0,0 +1,694 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Algebraic API Design - Types, Functions, Properties + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Algebraic API Design - Types, Functions, Properties

+ + + technical + +
+
+
+
+

Algebraic API Design - Types, Functions, Properties

+

In this post we are going to explore the concept of algebraic API design which is based on types, pure functions, and the relationships between them known as domain rules or properties. We will do this based on a complete, self-contained example using Cats and Cats Effect and walk through the process of designing and implementing the domain of solving complex, deterministic single player games.

+

An API in this context describes the types and operations that a module exposes to the user.

+

SameGame is a deterministic single player game with perfect information. It has a game-tree complexity of 1082 10^{82} . In other words it is extremely hard to solve. Exhaustive search strategies and traditional path finding algorithms do not perform well. Monte Carlo tree search which is based on random sampling on the other hand is a promising approach. We will go into more details on these concepts below.

+

But how can we implement this with functional programming? How can we express algorithms that are based on randomness, mutable state, and side effects in a purely functional way?

+

All the code in this post is interpreted with tut to make sure everything type-checks so that the reader gets a complete picture rather than simplistic code samples that don't actually work. As a downside some of the code is quite lengthy. Please note that it is not required to read and understand every single line.

+

Let's have a quick recap on the definition of algebraic structures and how they relate to programming and domain modeling.

+ +

Algebraic Structures

+

An algebraic structure consists of:

+
    +
  • One or more sets
  • +
  • A set of operators
  • +
  • A collection of axioms (which the operators are required to satisfy)
  • +
+

A prototypical example of an algebraic structure from mathematics is a group. A concrete example of a group is the set Z \mathbb{Z} of integers together with the addition operator denoted as (Z,+) (\mathbb{Z}, +) that satisfy the group axioms.

+

A group can be defined in an abstract way like this:

+
    +
  • Set and operator: (G,) (G, \circ)
  • +
  • + Axioms: +
      +
    • Closure: a,bG:abG \forall a, b \in G:a \circ b \in G
    • +
    • Associativity: a,b,cG:a(bc)=(ab)c \forall a, b, c \in G: a \circ (b \circ c) = (a \circ b) \circ c
    • +
    • Identity: eG:aG:ea=a=ae \exists e \in G: \forall a \in G:e \circ a = a = a \circ e
    • +
    • Inverse: aG:bG:ab=e=ba \forall a \in G: \exists b \in G:a \circ b = e = b \circ a
    • +
    +
  • +
+ +

Programming

+

There is an analogy in programming where:

+
    +
  • Sets are types (specifically algebraic data types)
  • +
  • Operators are functions
  • +
  • Axioms are properties (predicates expressed in terms of the algebra backed up by property-based tests)
  • +
+

The type classes provided by Cats follow exactly this pattern. Cats provides representations of many algebraic structures, one of which is Group[A]. It is defined by a type A and an operator combine:

+
trait Group[A] {
+  def empty: A
+  def combine(x: A, y: A): A
+  def inverse(a: A): A
+}
+

Group additionally has an empty and an inverse element. Closure is already enforced by the types of the operators. The axioms are expressed as properties.

+

E.g. for all objects x, y, z of type A, the (associative property) must hold:

+
def semigroupAssociative(x: A, y: A, z: A): IsEq[A] =
+  S.combine(S.combine(x, y), z) <-> S.combine(x, S.combine(y, z))
+

We can create a concrete instance of Group[A], e.g. according to (Z,+) (\mathbb{Z}, +) :

+
import cats.Group
+
+implicit val group: Group[Int] = new Group[Int] {
+  def inverse(a: Int): Int         = -a
+  def empty: Int                   = 0
+  def combine(x: Int, y: Int): Int = x + y
+}
+

The verification of the Group properties for the instance we created can be done with the help of discipline.

+

Please note that, while property-based testing is a very powerful tool, it is not the only way to verify properties. There are other methods like dependant types, formal verification, or example-based testing. However, property-based testing works really well and has a huge return on investment.

+ +

Domain Modeling

+

Algebraic design is not exclusively applicable to modeling algebraic structures from mathematics such as groups from the example above. We can use exactly the same technique to model the API of any domain.

+

The elements of algebraic structures can be related to domain driven design as follows:

+
    +
  • Sets are types of entities or value objects
  • +
  • Operators are business behavior
  • +
  • Axioms are business rules
  • +
+

Here is an overview of how algebraic structures, programming, and domain modeling relate to each other:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Algebraic StructureProgrammingDomain Modeling
SetsAlgebraic data typesTypes of entities/value objects
OperatorsFunctionsBusiness behavior
AxiomsPropertiesBusiness rules
+ +

Programs

+

Once we've defined the algebras that model the API of our domain, we can describe programs in terms of one or more of these algebras. Algebras in programming are also sometimes referred to as embedded domain specific languages (EDSLs). Programs that are composed of algebras or EDSLs are parametrically polymorphic and they know nothing about the algebras' concrete implementations other than that they satisfy certain properties.

+

Programs are therefore flexible and constrained at the same time. Flexible in the sense that they can be used with any lawful implementation of the given algebra. And constrained because they can only use the operators provided by the algebra to manipulate values of the types that the algebra is expressed with.

+

To give a crude, concrete example, a program p p is expressed in terms of the algebra of Group[A]. p p can only produce a result by using the operators combine and inverse on given input parameters of type A. Those parameters cannot be manipulated in any other way. Which leaves less room for mistakes and leads to correct programs. The caller of p p decides which concrete implementation of Group[A] they want to provide. Which makes p p reusable in multiple different contexts.

+

We could define a program as follows:

+
A program is a pure polymorphic function that uses algebras by combining their operators with other input parameters to produce a pure value.
+ +

Interpreters

+

The concrete implementation of an algebra is also know as the interpreter of the algebra. In the example from above the value group of type Group[Int] is an interpreter of the algebra of Group[A].

+

This might all sound a bit abstract and theoretical at first. In fact, talking about the concept of algebraic design is one thing, but applying this concept to a real business domain is another.

+

Let's look at a concrete and self-contained example.

+ +

Solving single player games

+

+

We will write a program that finds solutions for deterministic single player games with a high game-tree complexity like SameGame.

+

SameGame is played on a 15×15 15 \times 15 board initially filled with blocks of 5 colors. The goal of the game is to remove as many blocks from the board as possible while maximising the score. See https://en.wikipedia.org/wiki/SameGame for detailed rules. You can play the game at js-games.de or https://samegame.surge.sh.

+

SameGame is a game with perfect information that is very difficult to solve. Given an initial starting position, we can construct a complete game-tree for SameGame as follows:

+
    +
  • The nodes of the tree are board positions
  • +
  • The edges are moves
  • +
  • Each position (node) contains all possible moves (edges)
  • +
  • The root node represents the starting position
  • +
  • The leafs are terminal game states
  • +
+

The total number of leafs is the game-tree complexity. The game-tree complexity of Tic-Tac-Toe e.g. is about 105 10^5 . Tic-Tac-Toe is easy to solve by doing an exhaustive search. Whereas SameGame has a complexity of approximately 1082 10^{82} . This makes it impossible to solve with a brute-force approach or other traditional algorithms in a reasonable amount of time. Smaller SameGame boards are relatively easy to solve. As the size of the board increases we observe a combinatorial explosion. The time required to find the best solution increases so rapidly that we hit a solvability limit.

+ + +

When we encounter a combinatorial explosion, stochastic optimization algorithms come to the rescue. Instead of exploring the complete search tree these algorithms sample the search space and can find very good solutions. It is very unlikely, however, that they reach a global maximum and find the best solution in a reasonable amount of time.

+

A stochastic optimization algorithm that has successfully been employed to game play is known as Monte Carlo tree search. The basic idea of Monte Carlo tree search is to determine the most promising move based on random simulations at each node in the game-tree. In a random simulation the game is played out to the very end by selecting uniformly distributed random moves.

+

Here is a very simple version of a Monte Carlo tree search:

+
    +
  1. Choose the root node as the current node n n of the game-tree
  2. +
  3. + For the current node n n , determine all legal moves ms ms +
      +
    • If no legal moves exist, the algorithm terminates
    • +
    +
  4. +
  5. Determine all child nodes cs cs of n n by applying each of the moves ms ms to the current state n n
  6. +
  7. Perform a random simulation for each of the child nodes cs cs
  8. +
  9. From the child nodes cs cs choose the node with the best simulation result, and continue with step 2.
  10. +
+

A way to improve on this basic algorithm is to add a nested (lower level) search at step 4. such that a random simulation is performed if the current level equals 1, otherwise a level - 1 search is performed.

+

That's all we need to know so let's implement this in a purely functional way using algebraic API design.

+ +

Game algebra

+

First we define a type that represents the game state:

+
final case class GameState[Move, BoardPosition, Score](
+    playedMoves: List[Move],
+    score: Score,
+    position: BoardPosition,
+)
+// defined class GameState
+

GameState consists of playedMoves (the list of moves that have been played), score (the current score), and position (the current board position).

+

With GameState we can now express a game algebra like this:

+
trait Game[F[_], Move, BoardPosition, Score] {
+  type GS = GameState[Move, BoardPosition, Score]
+
+  def applyMove(gameState: GS, move: Move): GS
+  def legalMoves(gameState: GS): List[Move]
+  def simulation(gameState: GS): F[GS]
+}
+// defined trait Game
+

The type alias GS only serves better readability.

+

Note that by parameterizing Game with Move, BoardPosition, and Score there are no dependencies. It is completely decoupled from any specific domain models. We can fully focus on the contract of the operations rather than having to deal with the implementation or cumbersome domain models.

+

Furthermore, Game is defined for any type constructor F[_] which we use to model effects. By making this type abstract we can defer the decision of which effects we need to a later point in time, respectively at the entry point of the application. Common effect types that could be used are IO, Task, Option, State, List etc. or combinations of them. Having an abstract effect type additionally serves better reusability in different contexts like in tests.

+

While applyMove and legalMoves have no effects, simulation returns an effect F. Even though one could think of implementations without effects, the implementation of simulation will use a generator for uniformly distributed random numbers. This can be done by describing a side effect or by using the State Monad. With an abstract effect we are able to choose a specific effect later and make simulation referentially transparent.

+ +

Game properties

+

Just as an algebraic structure has certain axioms, we can also define properties for the Game algebra that all interpreters have to satisfy. These properties can be generic or they can be driven by the business rules of the domain.

+

It is often challenging to come up with meaningful properties for an algebra. However, more and better constraints allow significantly fewer possible implementations of the algebra. Which leads to correct programs because there is less room for errors.

+

For the sake of brevity let's consider only two properties:

+
    +
  • For all game states, a simulation will lead to a terminal board position
  • +
  • For all game states, given a move m, m is either illegal or when applied leads to a new game state and it increments the number of played moves
  • +
+

These rules are expressed as predicates inside the companion object of Game like this:

+
object Game {
+  // let's follow the naming convention used by Cats and call this 'laws'
+  object laws {
+    import cats.Functor
+    import cats.implicits._
+
+    def simulationIsTerminal[F[_]: Functor, Move, BoardPosition, Score](
+        gameState: GameState[Move, BoardPosition, Score])(
+        implicit ev: Game[F, Move, BoardPosition, Score]): F[Boolean] =
+      ev.simulation(gameState).map(ev.legalMoves).map(_.isEmpty)
+
+    def legalMoveModifiesGameState[F[_], Move, BoardPosition, Score](
+        gameState: GameState[Move, BoardPosition, Score],
+        move: Move)(implicit ev: Game[F, Move, BoardPosition, Score]): Boolean = {
+      val legalMoves    = ev.legalMoves(gameState)
+      val nextGameState = ev.applyMove(gameState, move)
+      !legalMoves.contains(move) ||
+        (nextGameState.position != gameState.position && nextGameState.playedMoves.length == gameState.playedMoves.length + 1)
+    }
+  }
+}
+

There is an additional constraint that we discover while implementing the predicates that define the properties of Game. F must have a Functor instance. Functor is the least powerful abstraction that allows us to access the value inside F and express the property related to simulation. In the next section we will see that the program we are going to write will imply additional constraints for F.

+

The properties of Game are tightly coupled to the algebra as any implementation must satisfy them. This is why they are defined inside the companion object of Game as part of the library code.

+

Later we will see how to implement property-based tests to verify the properties.

+ +

Programs

+

The Game properties are expressed solely in terms of the Game algebra. We have no idea how Game is implemented or what the types Move, BoardPosition, or Score look like. In a similar fashion we will only use the algebra to implement programs such as the search algorithm described above.

+

Before we do this, we will define another algebra that describes logging, simply for convenience. Especially during long running searches it is useful to be able to output intermediate results and search states:

+
import cats.Show
+
+trait Logger[F[_]] {
+  def log[T: Show](t: T): F[Unit]
+}
+
+object Logger {
+  def apply[F[_]]()(implicit ev: Logger[F]): Logger[F] = ev
+}
+

With the two algebras Game and Logger we can now implement the nested Monte Carlo tree search.

+
import cats.Monad
+// import cats.Monad
+
+import cats.implicits._
+// import cats.implicits._
+
+def nestedSearch[F[_]: Monad: Logger, Move, Position, Score](
+    numLevels: Int,
+    level: Int,
+    gameState: GameState[Move, Position, Score])(
+    implicit g: Game[F, Move, Position, Score],
+    ord: Ordering[Score],
+    show: Show[GameState[Move, Position, Score]]): F[GameState[Move, Position, Score]] = {
+  val legalMoves = g.legalMoves(gameState)
+  for {
+    _ <- if (level == numLevels) Logger[F].log(gameState) else ().pure[F]
+    result <- if (legalMoves.isEmpty)
+      Monad[F].pure(gameState)
+    else
+      legalMoves
+        .traverse { move =>
+          if (level == 1) {
+            val nextGameState = g.applyMove(gameState, move)
+            g.simulation(nextGameState).map((move, _))
+          } else {
+            val nextState = g.applyMove(gameState, move)
+            nestedSearch[F, Move, Position, Score](numLevels, level - 1, nextState).map((move, _))
+          }
+        }
+        .map(_.maxBy(_._2.score))
+        .flatMap {
+          case (move, _) =>
+            nestedSearch[F, Move, Position, Score](numLevels, level, g.applyMove(gameState, move))
+        }
+  } yield result
+}
+// nestedSearch: [F[_], Move, Position, Score](numLevels: Int, level: Int, gameState: GameState[Move,Position,Score])(implicit evidence$1: cats.Monad[F], implicit evidence$2: Logger[F], implicit g: Game[F,Move,Position,Score], implicit ord: Ordering[Score], implicit show: cats.Show[GameState[Move,Position,Score]])F[GameState[Move,Position,Score]]
+

This program describes the nested Monte Carlo tree search algorithm from above. The biggest difference is that this description is statically type checked by the Scala compiler. The effect of mutating the game state is modelled in a purely functional way with recursion. In fact, the state modifications could be modelled with the State Monad as well, but this makes things a bit more complicated especially when we try to parallelize the search.

+

Note that to implement the algorithm we need a Monad instance for F. Other than that we don't care what F exactly is.

+

Moreover, the nestedSearch function implies additional constraints for Score and GameState. We need to pass instances of Ordering[Score] (because we want to compare scores) and Show[GameState] (which we need for logging) as implicit parameters.

+ +

The Game interpreter

+

The game logic itself is defined in the object SameGame which is not shown here for the sake of brevity. You can find the implementation in this Gist.

+

With SameGame we are able write to an interpreter for Game that implements the SameGame rules. For the type constructor F[_] we will choose IO, so that we can model the side effect of a random number generator in a referentially transparent way. There are other options, like State e.g. that we will not discuss here. The point is that we are free to use whatever effect type serves our needs, as long as the implementation is pure and the properties of the Game algebra hold.

+
import cats.effect.IO
+// import cats.effect.IO
+
+import SameGame._
+// import SameGame._
+
+implicit val game: Game[IO, Position, SameGameState, Int] =
+  new Game[IO, Position, SameGameState, Int] {
+    def applyMove(gameState: GameState[Position, SameGameState, Int],
+                  move: Position): GameState[Position, SameGameState, Int] = {
+      val gs = SameGame.applyMove(move, gameState.position)
+      GameState(move :: gameState.playedMoves, SameGame.score(gs), gs)
+    }
+
+    def legalMoves(gameState: GameState[Position, SameGameState, Int]): List[Position] =
+      SameGame.legalMoves(gameState.position)
+
+    def simulation(gameState: GameState[Position, SameGameState, Int])
+      : IO[GameState[Position, SameGameState, Int]] = {
+      val moves = legalMoves(gameState)
+      if (moves.isEmpty)
+        IO.pure(gameState)
+      else
+        IO(scala.util.Random.nextInt(moves.length))
+          .map(moves)
+          .map(applyMove(gameState, _))
+          .flatMap(simulation)
+    }
+  }
+// game: Game[cats.effect.IO,SameGame.Position,SameGame.SameGameState,Int] = $anon$1@2fd961b3
+

We must not forget to also implement an interpreter for Logger:

+
implicit val logger: Logger[IO] = new Logger[IO] {
+  def log[T: Show](t: T): IO[Unit] = IO(println(t.show))
+}
+// logger: Logger[cats.effect.IO] = $anon$1@32e5f76d
+

And some Show instances to create nicely formatted outputs in a type-safe way:

+
implicit val showCell: Show[CellState] = Show.show {
+  case Empty         => "-"
+  case Filled(Green) => "0"
+  case Filled(Blue)  => "1"
+  case Filled(Red)   => "2"
+  case Filled(Brown) => "3"
+  case Filled(Gray)  => "4"
+}
+
+implicit val showMove: Show[Position] =
+  Show.show(p => show"(${p.col},  ${p.row})")
+
+implicit val showList: Show[List[Position]] =
+  Show.show(_.map(_.show).mkString("[", ", ", "]"))
+
+implicit val showBoard: Show[Board] =
+  Show.show(
+    _.columns
+      .map(col => col.cells.map(_.show).reverse)
+      .transpose
+      .map(_.mkString("[", ",", "]"))
+      .mkString("\n"))
+
+implicit val showGame: Show[SameGameState] = Show.show {
+  case InProgress(board, score) => show"$board\n\nScore:  $score (game in progress)"
+  case Finished(board, score)   => show"$board\n\nScore:  $score (game finished)"
+}
+
+implicit val showGameState: Show[GameState[Position, SameGameState, Int]] =
+  Show.show(t => show"""
+                        |${t.position}
+                        |Moves: ${t.playedMoves.reverse}
+                        |""".stripMargin)
+ +

Verifying the Game properties

+

Now that we have defined the interpreter for Game, it is time to ensure that Game properties are satisfied. We will do this with property-based testing and the library ScalaCheck. ScalaCheck uses a large number of randomly generated test cases to verify that the given properties hold.

+
import org.scalacheck.{Arbitrary, Gen}
+import org.scalacheck.Gen._
+import org.scalatest._
+import org.scalatest.prop.PropertyChecks
+

To generate test cases, we have to define how to construct the test inputs. ScalaCheck provides numerous combinators that can be composed to create generators for our domain objects. Here are basic implementations of generators for GameState and Postion:

+
def colEmpty(size: Int): List[CellState] = List.fill(size)(Empty)
+
+def colNonEmpty(size: Int): Gen[List[CellState]] =
+  for {
+    numFilled <- choose(1, size)
+    filled    <- listOfN(numFilled, choose(0, 5).map(c => Filled(Color(c))))
+  } yield filled ++ colEmpty(size - filled.length)
+
+def gameState(min: Int, max: Int): Gen[GameState[Position, SameGameState, Int]] =
+  for {
+    size      <- choose(min, max)
+    numFilled <- choose(0, size)
+    nonEmpty  <- listOfN(numFilled, colNonEmpty(size)).map(_.map(Column(_)))
+    empty     <- listOfN(size - numFilled, const(colEmpty(size))).map(_.map(Column(_)))
+    score     <- Arbitrary.arbitrary[Int]
+  } yield
+    GameState(
+      playedMoves = List.empty[Position],
+      position = SameGame.evaluateGameState(Board(nonEmpty ++ empty), score),
+      score = score
+    )
+
+def move(boardSize: Int): Gen[Position] =
+  for {
+    col <- choose(0, boardSize)
+    row <- choose(0, boardSize)
+  } yield Position(col, row)
+

Implementing the property checks is now straight forward:

+
class GameTests extends PropSpec with PropertyChecks with Matchers {
+  property("simulation is terminal") {
+    forAll(gameState(4, 8)) { gs =>
+      Game.laws
+        .simulationIsTerminal[IO, Position, SameGameState, Int](gs)
+        .unsafeRunSync()
+    }
+  }
+
+  property("legal move modifies game state") {
+    forAll(gameState(4, 8), move(8)) {
+      case (gs, m) =>
+        Game.laws
+          .legalMoveModifiesGameState[IO, Position, SameGameState, Int](gs, m)
+    }
+  }
+}
+// defined class GameTests
+

Finally we run our tests. In the Scala REPL this can be done like this:

+
run(new GameTests)
+// GameTests:
+// - simulation is terminal
+// - legal move modifies game state
+

Of course, there are additional test strategies that can be employed. In particular, it is useful to test not only the interpreters, but to test the program, too. However, this goes beyond the scope of this post. For more information on testing in the world of functional programming please, refer to the links in the resource section below.

+ +

Application

+

With cats.effect.IOApp we describe a purely functional program that performs a Monte Carlo tree search for a given initial board position. For demonstration purposes we use a smaller board of size 6×6 6 \times 6 to shorten the search time.

+
import cats.effect._
+// import cats.effect._
+
+object Main extends IOApp {
+  def run(args: List[String]): IO[ExitCode] = {
+    val board6x6 = List(
+      List(0, 2, 3, 1, 2, 1),
+      List(3, 0, 0, 3, 3, 2),
+      List(1, 2, 2, 2, 4, 2),
+      List(1, 1, 2, 4, 2, 2),
+      List(0, 0, 1, 3, 1, 4),
+      List(1, 4, 4, 0, 0, 3)
+    )
+
+    val initial =
+      GameState(playedMoves = List.empty[Position], score = 0, position = SameGame(board6x6))
+
+    val level = 4
+
+    nestedSearch[IO, Position, SameGameState, Int](level, level, initial)
+      .as(ExitCode.Success)
+  }
+}
+// defined object Main
+

When we look at the code we cannot explicitly see that the instances of Game, Logger, Ordering, and Show are passed to the nestedSearch function as implicit parameters. They have been defined above and can be successfully resolved by the compiler because of the REPL style code, presented here. In a normal Scala application those instances are imported into the scope in the main method - at the entry point of the application.

+

nestedSearch returns a value of type IO[GameState[Position, SameGameState, Int]]. The return value can be ignored in this case because all the interesting information is logged by the program. IOApp takes care of and hides the execution of the IO.

+

The truncated sample output from the application:

+
[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,4,4,-,-,-]
+[1,0,4,-,-,-]
+[0,0,1,-,-,-]
+
+Score: 16 (game in progress)
+Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1)]
+
+
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[1,0,-,-,-,-]
+[0,0,1,-,-,-]
+
+Score: 17 (game in progress)
+Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2)]
+
+
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[1,1,-,-,-,-]
+
+Score: 18 (game in progress)
+Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2), (0, 0)]
+
+
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+[-,-,-,-,-,-]
+
+Score: 1018 (game finished)
+Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2), (0, 0), (0, 0)]
+ +

Improving on the results

+

+

The Monte Carlo tree search algorithm presented in this post has been intentionally kept simple. There are numerous different strategies of how to guide the tree search based on heuristics to influence the choice of moves which require multiple parameters that have to be fine tuned to maximize the outcomes.

+

A way to greatly improve on results of the algorithm presented here is to store best paths that were found in lower level searches which might otherwise be lost. We can apply domain-specific knowledge as well, as opposed to completely relying on random sampling. For SameGame e.g. there is a strategy called Tabu-Color which determines the most frequent color occurring at the start of each simulation. This predominat color is not allowed to be played because it is known to be advantageous to create large groups of blocks. Furthermore, parallelization can significantly shorten the search time.

+

Please refer to this GitHub repository where the techniques mentioned above are applied. While still simple this implementation leads to very promising results. The algorithm was able to discover a solution with a top ten score at js-games.de.

+

Another very promising strategy that can be combined with a Monte Carlo tree search is simulated annealing.

+ +

Conclusion

+

We have covered a lot of things. Some of the details might have been challenging depending on your prior knowledge of the topics introduced in this post. The reason for presenting such extensive examples is to provide complete and compiling code and to make a point that we do not only get great benefits from functional programming techniques, but that they are also feasible for real world problems.

+

These are the key takeaways:

+
    +
  • An algebra is defined in terms of sets, operators and axioms
  • +
  • In functional programming algebras are expressed with types, functions and properties
  • +
  • This relates nicely to types of entities and value objects, business behavior and business rules in the context of domain driven design
  • +
  • Algebras are abstract and decoupled from any actual implementation which makes them more flexible and constrained at the same time
  • +
  • Property-based tests and parametric polymorphism constrain the possible implementations and lead to correct programs
  • +
  • Functional programming techniques are feasible and beneficial for real world programming
  • +
+

The code has been interpreted with tut, using Scala 2.12.7, scalatest 3.0.5, scalacheck 1.14.0, and cats-effect 1.1.0.

+ +

Resources

+ +

Thanks to Jarrod Urban who did a very thorough review of this post.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Leif Battermann + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/announcement-summit.html b/blog/announcement-summit.html new file mode 100644 index 00000000..851140f9 --- /dev/null +++ b/blog/announcement-summit.html @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + Announcement: Typelevel Summits coming up in 2016 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Announcement: Typelevel Summits coming up in 2016

+ + + summits + +
+
+
+
+

Announcement: Typelevel Summits coming up in 2016

+

We have a big announcement to make. In 2016, there will be not just one, but + two Typelevel Summits. Also, we’ve updated our website to include an up-to-date + list of Typelevel projects. There’s been much work behind the scenes which we + will talk about in a later post, so stay tuned! But first, here are some + details about the Summits.

+ +

Typelevel Summit US

+

The first Typelevel Summit will be co-located with the Northeast Scala + Symposium in Philadelphia. As Brian Clapper already announced on Twitter, NE + Scala is going to happen on 4th and 5th of March with one day of recorded talks + and one day of unconference. Just today, we finalized the booking of the venue + and are happy to report that the Typelevel Summit will have the same format and + take place on 2nd and 3rd of March at the same venue (The Hub’s Cira Centre, + next to 30th Street Station).

+ +

Typelevel Summit Europe

+

The second Typelevel Summit will be co-located with + flatMap(Oslo). We will meet on the 4th of May after + the conference at the same venue (Teknologihuset). More details are to be + announced!

+ +

Call for Speakers, Attendance, & FAQs

+

The planning phase is in full swing and we’ll announce more details soon. + Attendance will probably be limited to about 100. We’re also looking for + sponsors to help pay for the venue and cover other expenses. And we’re also + starting a diversity fund to support people from underrepresented groups, and + to mentor new speakers. If you want to contribute or have any other questions, + please contact us via info@typelevel.org.

+

The Summits are open to all, not just current contributors to and users of the + Typelevel projects, and we are especially keen to encourage participation from + people who are new to them. Whilst many of the Typelevel projects use somewhat + "advanced" Scala, they are a lot more approachable than many people think, and + a major part of Typelevel's mission is to make the ideas they embody much more + widely accessible. So, if you're interested in types and pure functional + programming, want to make those ideas commonplace and are willing to abide by + the Typelevel code of conduct, then the Summits are for you and we'd love to + see you there.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/call-for-code-of-conduct-committee-members.html b/blog/call-for-code-of-conduct-committee-members.html new file mode 100644 index 00000000..38bdb217 --- /dev/null +++ b/blog/call-for-code-of-conduct-committee-members.html @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + Call For Code of Conduct Committee Members + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Call For Code of Conduct Committee Members

+ + + governance + +
+
+
+
+

Call For Code of Conduct Committee Members

+

Are you passionate about fostering a positive and inclusive community? + Do you want to help shape how Typelevel works with the community to build a respectful environment for all? + We’re excited to announce this call for new members to the Typelevel Code of Conduct Committee!

+

Earlier this year Typelevel adopted a new Code of Conduct that encompasses both organization and affiliate projects. + The Typelevel Code of Conduct Committee's mission is to enforce that Code of Conduct to help maintain community trust and safety. + We'd like your help in that mission!

+ +

Responsibilities

+
    +
  • Upholding the Code of Conduct and following through with the procedures outlined in the Enforcement Policy
  • +
  • Completing Code of Conduct enforcement training such as that offered by Otter Technology (paid for by Typelevel)
  • +
  • Willingness to contribute time and effort to committee activities, such as responding to reports in a timely manner
  • +
  • Upholding community trust by ensuring transparency and accountability in their actions.
  • +
+ +

Applying

+

Send an email to volunteer@typelevel.org to put yourself forward for consideration, please include why you wish to join. + The email is visible only to Typelevel Steering Committee membership. + We will thoughtfully consider all serious applications and be discreet in our deliberation. + Accepted candidacies will be public, but we will not disclose the identity of anyone else that applies.

+

The call will be open for three weeks, closing on September 14th 2024.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/call-for-steering-committee-members.html b/blog/call-for-steering-committee-members.html new file mode 100644 index 00000000..acb02fd8 --- /dev/null +++ b/blog/call-for-steering-committee-members.html @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + Call for Steering Committee Members + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Call for Steering Committee Members

+ + + governance + +
+
+
+
+

Call for Steering Committee Members

+

The Typelevel Steering Committee is opening a call for + new members as we continue to build a more transparent and + sustainable community.

+

In 2021, co-founders Lars Hupel and Miles Sabin stepped down from + leadership. We are all grateful for what they created and their + several years of service. What remains is a generation of leadership + that has not expanded in a few years. Retirements and additions alike + are healthy, balancing institutional memory with fresh energy. It is + again a time for renewal.

+

Historically, leadership has invited prominent technical contributors + to join them. This results in homogenous talent, experience, and + identity. Our hope is that this open call for leaders will be heard + by a more well-rounded and diverse set of candidates to realize + Typelevel's mission.

+ +

Mission

+

Typelevel is an association of projects and individuals united to + foster an inclusive, welcoming, and safe environment around functional + programming in Scala.

+ +

Responsibilities

+
    +
  • +

    Together, we are coders, organizers, writers, educators, publicists, + moderators, recruiters, academics, and strategists. You aren't all + of these, but you think you can add something that's scarce.

    +
  • +
  • +

    Membership is not a heavy burden, but neither is it an honorific. + You will participate in governance discussions, and are excited to reimagine the way we run.

    +
  • +
  • +

    The current charter defines no fixed term. You'll try to leave it + better than you found it and when you're ready, hand the baton to + someone who will do the same.

    +
  • +
+ +

Applying

+

Send an e-mail to volunteer@typelevel.org + to put yourself forward for consideration, optionally with a brief + case why you wish to serve. The e-mail is visible only to the + committee membership. We will thoughtfully consider all + serious applications and be discreet in our deliberation. Accepted + candidacies will be public, but we will not disclose the identity of + anyone else. We hope to hear from some who surprise us, or even yet + had the pleasure of meeting. We are not looking for third-party + nominations, but if there's someone you'd like to see, encourage them + to apply!

+

The charter does not prescribe a committee size, but we hope to + welcome two to six new members this cycle, and consider more as + ambitions and capacity warrant.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/cats-1.0-mf.html b/blog/cats-1.0-mf.html new file mode 100644 index 00000000..d213bcba --- /dev/null +++ b/blog/cats-1.0-mf.html @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + Announcement: cats 1.0.0-MF + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Announcement: cats 1.0.0-MF

+ + + technical + +
+
+
+
+

Announcement: cats 1.0.0-MF

+

The cats maintainer team is proud to announce the cats 1.0.0-MF release.

+

MF stands for milestone final, + this will be the last release before cats 1.0.0-RC1 which will be followed by cats 1.0 shortly.

+

The main purpose/focus of this release is to offer a relatively stable API to work with prior to the official 1.0. + It can be deemed as a proposal for the final cats 1.0 API. Please help test it and report any improvements/fixes + needed either in the cats-dev gitter channel or as github issues. + Post cats 1.0, we will keep API stable and maintain strong binary compatibility.

+

Highlights of the major new features include but not limited to:

+
    +
  • #1117: Stack safe foldLeftM without Free, by @TomasMikula
  • +
  • #1598: A ReaderWriterStateT data type, by @iravid
  • +
  • #1526 and #1596: InjectK for free programs, by @tpolecat and @andyscott
  • +
  • #1602: Stack-safe Coyoneda, by @edmundnoble
  • +
  • #1728: As class which represents subtyping relationships (Liskov), by @stew
  • +
  • #1178: Is constructor for Leibniz equality, by @tel
  • +
  • #1748: Stack-safe FreeApplicative, by @edmundnoble
  • +
  • #1611: NonEmptyTraverse. by @LukaJCB
  • +
+

Overall 1.0.0-MF has over 120 merged pull requests of API additions, bug fixes, documentation and misc + improvements from 44 contributors. For the complete change list please go to the release notes.

+ +

Migration

+

There are more breaking changes in this release - we want to include as many necessary breaking changes as possible in this release + to reach stability. Please follow the migration guide from 0.9.0 in the release notes.

+ +

What's next

+

Although we made many improvements to the documentation in this release, it's still by and large a WIP. + The next release 1.0.0-RC1 will focus documentation and API refinement based on community feedback. + RC1 is scheduled to be released in September. Unless the amount of bug fixes warrants a RC2, it's likely that + we'll release cats 1.0.0 within a couple weeks after RC1.

+ +

Credits

+

Last but not least, many thanks to the contributors that make this release possible:

+
    +
  • @alexandru
  • +
  • @andyscott
  • +
  • @BenFradet
  • +
  • @Blaisorblade
  • +
  • @cb372
  • +
  • @ceedubs
  • +
  • @cranst0n
  • +
  • @DavidGregory084
  • +
  • @denisftw
  • +
  • @DieBauer
  • +
  • @diesalbla
  • +
  • @djspiewak
  • +
  • @durban
  • +
  • @edmundnoble
  • +
  • @iravid
  • +
  • @jtjeferreira
  • +
  • @julien-truffaut
  • +
  • @jyane
  • +
  • @kailuowang
  • +
  • @larsrh
  • +
  • @Leammas
  • +
  • @leandrob13
  • +
  • @LukaJCB
  • +
  • @markus1189
  • +
  • @milessabin
  • +
  • @n4to4
  • +
  • @oskoi
  • +
  • @peterneyens
  • +
  • @PeterPerhac
  • +
  • @raulraja
  • +
  • @RawToast
  • +
  • @sellout
  • +
  • @stew
  • +
  • @sullivan-
  • +
  • @SystemFw
  • +
  • @takayuky
  • +
  • @tel
  • +
  • @TomasMikula
  • +
  • @tpolecat
  • +
  • @wedens
  • +
  • @xavier-fernandez
  • +
  • @xuwei-k
  • +
  • @yilinwei
  • +
  • @zainab-ali
  • +
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Kailuo Wang + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/cats-1.0.0.html b/blog/cats-1.0.0.html new file mode 100644 index 00000000..67e255ed --- /dev/null +++ b/blog/cats-1.0.0.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + Announcement: cats 1.0.0 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Announcement: cats 1.0.0

+ + + technical + +
+
+
+
+

Announcement: cats 1.0.0

+

The cats maintainer team is proud to announce the cats 1.0.0 release. + Cats has been striving to provide functional programming abstractions that are core, modular, approachable and efficient. + Cats 1.0.0 marks the point where we believe that our API is robust and stable enough to start guarantee backward binary compatibility going forward until Cats 2.0. We expect the Cats 1.x series to be fully backwards compatible for at least one year. This is a major milestone towards + our goal of providing a solid foundation for an ecosystem of pure, typeful functional libraries.

+ +

Migration

+

The vast majority of changes since 1.0.0-RC1 are API compatible, with scalafix scripts ready for those that do not. + Here is the change list and migration guide.

+ +

Binary compatibility

+

After 1.0.0 release, we'll use the MAJOR.MINOR.PATCH Semantic Versioning 2.0.0 going forward, which is different from the EPOCH.MAJOR.MINOR scheme common among Java and Scala libraries (including the Scala lang). In this semantic versioning, backward breaking change is ONLY allowed between MAJOR versions. We will maintain backward binary compatibility between PATCH and MINOR versions. For example, when we release cats 1.1.0, it will be backward binary compatible with the previous 1.0.x versions. I.E. the new JAR will be a drop-in replacement for the old one. This is critical when your application has a diamond dependency on Cats - depending on two or more libraries that all depend on Cats. If one library upgrades to the new 1.1.0 Cats before the other one does, your application still runs thanks to this backward binary compatibility.

+

We will also consider using organization and package name for MAJOR versioning with binary breaking changes in the future. But that decision is yet to be made.

+ +

Community

+

Cats is built for the FP Scala community by the FP Scala community. We can't thank enough to our 190 (and growing) contributors and our users who provided feedbacks and suggestions.
+ Congratulations to all of us. Let's celebrate this exciting milestone together.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Kailuo Wang + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/cats-ecosystem-community-survey-results.html b/blog/cats-ecosystem-community-survey-results.html new file mode 100644 index 00000000..a48f3c1f --- /dev/null +++ b/blog/cats-ecosystem-community-survey-results.html @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + Cats Ecosystem Community Survey 2018 Results + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Cats Ecosystem Community Survey 2018 Results

+ + + technical + +
+
+
+
+

Cats Ecosystem Community Survey 2018 Results

+

Overall we received 588 responses over the course of 30 days. This feedback is essential for us to make informed decisions on our 2019 plan. Thank you, everyone, who participated.

+

As promised, here are the results, as well as some quick reads from us.

+ +

Q: How long have you been using the Cats ecosystem (including Cats and Cats ecosystem libraries)?

+ +

Results:

+

1

+ +

Our read:

+

46% of the respondents are relatively new (less than 1 year) users. Welcome!

+ +

Q: Do you feel welcome in the Cats ecosystem community?

+ +

Results:

+

2

+ +

Our read:

+

83% of users gave the 4+ ratings on the welcomeness of the community. This is a promising sign. We should aim for having more 5 ratings (currently at 42.5%) and fewer 3- ones.

+ +

Q: How can we be more welcoming?

+ +

Responses summary:

+

We received 92 responses on this free-form question. The vast majority, 90+%, of them suggested that more documentation, tutorials, and real-world examples would help them feel more welcome. Some of them suggested that the learning curve could be less intimidating to them if there were more introductory resources.

+ +

Our read:

+

The learning curve for Cats ecosystem libraries could be steep for people new to pure functional programming. This is by far the most impactful area for us to work on to be more inclusive.

+ +

Q: In what types of projects do you primarily use the Cats ecosystem?

+ +

Results:

+

3

+ +

Our read:

+

83% of respondents are using Cats ecosystem libraries in production applications. We are honored to have that trust. In the meantime, it's a great responsibility for us to maintain stability and robustness.

+ +

Q: In which application domain do you primarily use the Cats ecosystem?

+ +

Results:

+

4

+

The free form other responses are omitted for the sake of conciseness.

+ +

Q: If you use Cats directly, how is your overall experience using Cats?

+ +

Results:

+

5

+ +

Q: What would significantly improve your experience using Cats?

+ +

Results:

+

5

+

The free form other responses can be summarized as + 1. better IDE support 10 (1.8%) + 2. better imports 7 (1.3%) + 3. better integration with other libs 3 (0.6%)

+ +

Our read:

+

Overall the experience is mostly positive for our users. 73% of them believe there is space for improvements - they gave a rating less than 5. Again, more documentation and training will help most users. 25% of users are looking for more features.

+ +

Q: If you are contributing to Cats, thank you! How is your overall experience contributing?

+ +

Results:

+

6

+ +

Q: What would significantly improve your contributing experience?

+ +

Results:

+

6

+ +

Our read:

+

The contributing experience is good/okay but not really great. There is still a lot of work to be done here.

+ +

Q: For your applications, what would be a good time line for the Cats ecosystem to drop Scala 2.11 support?

+ +

Results:

+

6

+ +

Our read:

+

26% of our users still can't migrate to Scala 2.12 before second half of 2019.

+ +

Q: If you are blocked from upgrading to Scala 2.12, by what?

+ +

Results:

+

6

+ +

Q: What would be a good cadence for Cats to release a new major version (backward incompatible with previous ones)?

+ +

Results:

+

6

+ +

Our read:

+

The community is split on this one. There are slightly more users that prefer a longer cadence (18-36 or in sync with Scala minor version) than those who prefer a shorter one (12-18 months).

+ +

Q: How are you using cats-laws?

+ +

Results:

+

6

+ +

Q: How would a breaking change in cats-laws affect you?

+ +

Results:

+

6

+ +

Our Read:

+

Breaking Cats-laws's backward compatibility might be blockers for roughly 6% of the users.

+ +

Q: Would you benefit or suffer from cats-laws and cats-testkit being updated to Scalacheck 1.14 which is binary breaking with Scalacheck 1.13?

+ +

Results:

+

6

+ +

Our Read:

+

While most people are neutral on this one, there are significantly more users (14.2%) who would benefit from a Scalacheck 1.14 upgrade than those who would suffer (1%).

+ +

Q: Cats and most of its ecosystem libraries are maintained by hobbyists on their spare time. How would you feel about the future of the Cats ecosystem if there were financial backing to allow full-time or part-time maintainers?

+ +

Results:

+

6

+ +

Our Read:

+

In regards to financially backed full-time or part-time maintainers, it would give more confidence to 69.9% users while reducing it for 3% of the users.

+ +

Q: If you are against having compensated maintainers, what is your concern?

+ +

The free form answers can be summarized as

+
    +
  • Vendor favoritism and influence
  • +
  • Conflicts of interest
  • +
  • Discouraging non compensated maintainers
  • +
  • Paid maintainer over contributing unnecessary features
  • +
+ +

Our Read:

+

These concerns will be taken into consideration when we, if we decide to, design an institution to support paid maintainership.

+ +

Q: If you are in favor of compensated maintainers, which financial source(s) would help boost your confidence in the ecosystem the most?

+ +

Results:

+

6

+ +

Q: If you are in favor of corporate contribution, would your employer be interested?

+ +

Results:

+

6

+ +

Q: Would you like to participate in Cats ecosystem community surveys going forward?

+ +

Results:

+

6

+ +

Q: Please leave any additional feedback/suggestions below.

+ +

We received many kind words here, we can't list all of them but here are a few examples

+
    +
  • Gitter support for the cats ecosystem is the best I've had in any language or toolset
  • +
  • Great work, the progress in cats in the last year has had significant positive impact in my work. Thank you.
  • +
  • Thanks for providing amazing libraries that are not only very solid but also a joy to use!
  • +
+

Overall we are encouraged by the survey responses from the community, in the meantime, they also showed us many areas to improve. Our 2019 planning will be based on these remarkably valuable feedbacks. We hope to present it to the community soon.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Kailuo Wang + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/chain-replacing-the-list-monoid.html b/blog/chain-replacing-the-list-monoid.html new file mode 100644 index 00000000..a1e9bfe7 --- /dev/null +++ b/blog/chain-replacing-the-list-monoid.html @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + Chain – Replacing the List Monoid + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Chain – Replacing the List Monoid

+ + + technical + +
+
+
+
+

Chain – Replacing the List Monoid

+

List is a great data type, it is very simple and easy to understand. + It has very low overhead for the most important functions such as fold and map and also supports prepending a single element in constant time.

+

Traversing a data structure with something like Writer[List[Log], A] or ValidatedNel[Error, A] is powerful and allows us to precisely specify what kind of iteration we want to do while remaining succint. + However, in terms of efficiency it's a whole different story unfortunately. + That is because both of these traversals make use of the List monoid (or the NonEmptyList semigroup), which by the nature of List is very inefficient. + If you use traverse with a data structure with n elements and Writer or Validated as the Applicative type, you will end up with a runtime of O(n^2). + This is because, with List, appending a single element requires iterating over the entire data structure and therefore takes linear time.

+

So List isn't all that great for this use case, so let's use Vector or NonEmptyVector instead, right?

+

Well, Vector has its own problems and in this case it's unfortunately not that much faster than List at all. You can check this blog post by Li Haoyi for some deeper insight into Vector's issues.

+

Because of this, it's now time to welcome a new data structure to Cats. + Meet Chain and its non-empty counterpart, NonEmptyChain.

+

Available in the newest Cats 1.3.1 release, Chain evolved from what used to be fs2.Catenable and Erik Osheim's Chain library. + Similar to List, it is also a very simple data structure, but unlike List it supports both constant O(1) time append and prepend. + This makes its Monoid instance super performant and a much better fit for usage with Validated,Writer, Ior or Const.

+

To utilize this, we've added a bunch of NonEmptyChain shorthands in Cats 1.3 that mirror those that used NonEmptyList in earlier versions. These include type aliases like ValidatedNec or IorNec as well as helper functions like groupByNec or Validated.invalidNec. + We hope that these make it easy for you to upgrade to the more efficient data structure and enjoy those benefits as soon as possible.

+

To get a good idea of the performance improvements, here are some benchmarks that test monoidal append (higher score is better):

+
[info] Benchmark                                  Mode  Cnt   Score   Error  Units
+[info] CollectionMonoidBench.accumulateChain     thrpt   20  51.911 ± 7.453  ops/s
+[info] CollectionMonoidBench.accumulateList      thrpt   20   6.973 ± 0.781  ops/s
+[info] CollectionMonoidBench.accumulateVector    thrpt   20   6.304 ± 0.129  ops/s
+

As you can see accumulating things with Chain is more than 7 times faster than List and over 8 times faster than Vector. + So appending is a lot more performant than the standard library collections, but what about operations like map or fold? + Fortunately we've also benchmarked these (again, higher score is better):

+
[info] Benchmark                           Mode  Cnt          Score         Error  Units
+[info] ChainBench.foldLeftLargeChain      thrpt   20        117.267 ±       1.815  ops/s
+[info] ChainBench.foldLeftLargeList       thrpt   20        135.954 ±       3.340  ops/s
+[info] ChainBench.foldLeftLargeVector     thrpt   20         61.613 ±       1.326  ops/s
+[info]
+[info] ChainBench.mapLargeChain           thrpt   20         59.379 ±       0.866  ops/s
+[info] ChainBench.mapLargeList            thrpt   20         66.729 ±       7.165  ops/s
+[info] ChainBench.mapLargeVector          thrpt   20         61.374 ±       2.004  ops/s
+

While not as dominant, Chain holds its ground fairly well. + It won't have the random access performance of something like Vector, but in a lot of other cases, Chain seems to outperform it quite handily. + So if you don't perform a lot of random access on your data structure, then you should be fine using Chain extensively instead.

+

So next time you write any code that uses List or Vector as a Monoid, be sure to use Chain instead!

+

The whole code for Chain and NonEmptyChain can be found here and here. + You can also check out the benchmarks here.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/change-values.html b/blog/change-values.html new file mode 100644 index 00000000..c8ed138d --- /dev/null +++ b/blog/change-values.html @@ -0,0 +1,636 @@ + + + + + + + + + + + + + + + + + + + + + + + To change types, change values + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

To change types, change values

+ + + technical + +
+
+
+
+

To change types, change values

+

This is the seventh of a series of articles on “Type Parameters and + Type Members”. You may wish to + start at the beginning; + more specifically, this post is meant as a followup to + the previous entry. + However, in a first for this series, it stands on its own, as + introductory matter.

+

A program is a system for converting data from one format to another, + which we have endowed with the color of magic. In typed programming, + we use a constellation of types to mediate this transformation; a + function’s result can only be passed as another function’s argument to + the extent to which those parts of the functions’ types unify.

+

We rely on the richness of our types in these descriptions. So it is + natural to want the types to change as you move to different parts of + the process; each change reflects the reality of what has just + happened. For example, when you parse a string into an AST, your + program’s state has changed types, from String to MyAST.

+

But, as we have just seen, due to decisions we have made to simplify + our lives, + values cannot change types, + no matter how important it is to the sanity of our code. At the same + time, we don’t want to give up the richness of using more than one + type to describe our data.

+

Fortunately, there is a solution that satisfies these competing + concerns: to change types, change values. You can’t do anything about + the values you have, but you can create new ones of the right type, + and use those instead.

+ +

Type-changing is program organization

+

In values with complex construction semantics, it is common to write + imperative programs that leave “holes” in the data structures using + the terrible null misfeature of Java, Scala, and many other + languages. This looks something like this.

+
class Document(filename: Path) {
+  // this structure has three parts:
+  var text: String = null // ← a body of text,
+  var wordIndex: Map[String, List[Int]] = null
+    // ↑ an index of words to every
+    // occurrence in the text,
+  var mostPopular: List[(String, Int)] = null
+    // ↑ the most frequently used words
+    // in the text, and their number of
+    // occurrences
+...
+

Now, we must fill in these variables, by computing and assigning to + each in turn. First, we compute the corpus text.

+
  initText()
+

Then, we compute and fill in the word index. If we didn’t fill in + text first, this compiles, but crashes at runtime.

+
  initWordIndex()
+

Finally, we figure out which words are most popular. If we didn’t + fill in wordIndex first, this compiles, but crashes.

+
  initMostPopular()
+

How do I know that? Well, I have to inspect the definitions of these + three methods.

+
  def initText(): Unit =
+    text = Source.fromFile(filename.toFile).mkString
+
+  def initWordIndex(): Unit = {
+    val words = """\w+""".r findAllMatchIn text
+    wordIndex = words.foldLeft(Map[String, List[Int]]()){
+      (m, mtch) =>
+        val word = mtch.matched
+        val idx = mtch.start
+        m + (word -> (idx :: m.getOrElse(word, Nil)))
+      }
+  }
+
+  def initMostPopular(): Unit =
+    mostPopular = wordIndex.mapValues(_.size).toList
+      .sortBy(p => 0 - p._2).take(10)
+

This method of organizing object initialization is popular because, + among other properties:

+
    +
  1. it seems self-documenting,
  2. +
  3. you don’t have to pass data around, and
  4. +
  5. steps can be customized by subclassing and overriding.
  6. +
+

However! It has the tremendous drawback of preventing the compiler + from helping you get the order of initialization correct. Go, look; + see if you can spot why I said the latter two calls would crash if you + don’t get the order exactly right. Now, I have four questions for + you.

+
    +
  1. Would you trust yourself to notice these implicit dependencies + every time you look at this code?
  2. +
  3. Suppose you commented on the dependencies. Would you trust these + comments to be updated when the initialization details change?
  4. +
  5. Would you trust subclasses that customize the initialization to + respect the order in which we call these three init functions?
  6. +
  7. Could you keep track of this if the initialization was + significantly more complex? (This is a toy example for a blog + post, after all.)
  8. +
+

Ironically, as your initialization becomes more complex, the compiler + becomes less able to help you with uninitialized-variable warnings and + the like. But, this is not the natural order of things; it is a + consequence of using imperative variable initialization but not + representing this + variable refinement + in the type system. By initializing in a different way, we can + recover type safety.

+

The implications of refinement, linked above, are much less severe + than those of unrestricted type-changing of a variable. So Flow did + not solve, nor did it aim to solve, those difficulties by + introducing the refinement feature.

+ +

The four types of Document

+

If we consider Document as the simple product of its three state + variables, with some special functions associated with them as + whatever Document methods we intend to support, we have a simple + 3-tuple.

+
(String, Map[String, List[Int]],
+ List[(String, Int)])
+

Let us no longer pretend that it is any more complicated than that.

+

But this cannot be mutated to fill these in as they are initialized, + you say! Yes, that’s right, we want a type-changing transformation. + By changing values, this is easy. There are three phases of + initialization, so four states, including uninitialized.

+
Path
+String
+(String, Map[String, List[Int]])
+(String, Map[String, List[Int]], List[(String, Int)])
+

For interesting phases, such as the final one, we might create a case +class to hold its contents, instead. Let us call that class, for + this example, Doc.

+
final case class Doc
+  (text: String, wordIndex: Map[String, List[Int]],
+   mostPopular: List[(String, Int)])
+

Finally, we can build 3 functions to take us through these steps. + Each begins by taking one as an argument, and produces the next state + as a return type.

+
  def initText(filename: Path): String =
+    Source.fromFile(filename.toFile).mkString
+
+  def initWordIndex(text: String): (String, Map[String, List[Int]]) = {
+    val words = """\w+""".r findAllMatchIn text
+    (text, words.foldLeft(Map[String, List[Int]]()){
+       (m, mtch) =>
+       val word = mtch.matched
+       val idx = mtch.start
+       m + (word -> (idx :: m.getOrElse(word, Nil)))
+     })
+  }
+
+  def initMostPopular(twi: (String, Map[String, List[Int]])): Doc = {
+    val (text, wordIndex) = twi
+    Doc(text, wordIndex,
+        wordIndex.mapValues(_.size).toList
+          .sortBy(p => 0 - p._2).take(10))
+  }
+

If we have a Path, we can get a Doc by (initText _) andThen +initWordIndex andThen initMostPopular: Path => Doc. But that hardly + replicates the rich runtime behavior of our imperative version, does + it? That is, we can do reordering of operations in a larger context + with Document, but not Doc. Let us see what that means.

+ +

Many docs

+

Dealing with one document in isolation is one thing, but suppose we + have a structure of Documents.

+
sealed abstract class DocumentTree
+final case class SingleDocument(id: Int, doc: Document)
+  extends DocumentTree
+final case class DocumentCategory
+  (name: String, members: List[DocumentTree])
+  extends DocumentTree
+

In the imperative mode, we can batch and reorder initialization. Say, + for example, we don’t initialize Document when we create it. This + tree then contains Documents that contain only Paths. We can walk + the tree, doing step 1 for every Document.

+
  // add this to DocumentTree
+  def foreach(f: Document => Unit): Unit =
+    this match {
+      case SingleDocument(_, d) => f(d)
+      case DocumentCategory(_, dts) => dts foreach (_ foreach f)
+    }
+
+// now we can initialize the text everywhere,
+// given some dtree: DocumentTree
+dtree foreach (_.initText())
+

The way software does, it got more complex. And we can be ever less + sure that we’re doing things right, under this arrangement.

+ +

The four phases problem, stuck in a tree

+

Our tree only supports one type of document. We could choose the + final one, Doc, but there is no way to replicate more exotic + document tree initializations like the one above.

+

Instead, we want the type of the tree to adapt along with the document + changes. If we have four states, Foo, Bar, Baz, and Quux, we + want four different kinds of DocumentTree to go along with them. In + a language with type parameters, this is easy: we can model those four + as DocTree[Foo], DocTree[Bar], DocTree[Baz], and + DocTree[Quux], respectively, by adding a type parameter.

+
sealed abstract class DocTree[D]
+final case class SingleDoc[D](id: Int, doc: D)
+  extends DocTree[D]
+final case class DocCategory[D]
+  (name: String, members: List[DocTree[D]])
+  extends DocTree[D]
+

Now we need a replacement for the foreach that we used with the + unparameterized DocumentTree to perform each initialization step on + every Document therein. Now that DocTree is agnostic with respect + to the specific document type, this is a little more abstract, but + quite idiomatic.

+
  // add this to DocTree
+  def map[D2](f: D => D2): DocTree[D2] =
+    this match {
+      case SingleDoc(id, d) => SingleDoc(id, f(d))
+      case DocCategory(c, dts) =>
+        DocCategory(c, dts map (_ map f))
+    }
+

It’s worth comparing these side by side. Now we should be able to + step through initialization of DocTree with map, just as with + DocumentTree and foreach.

+
scala> val dtp: DocTree[Path] = DocCategory("rt", List(SingleDoc(42, Paths.get("hello.md"))))
+dtp: tmtp7.DocTree[java.nio.file.Path] = DocCategory(rt,List(SingleDoc(42,hello.md)))
+
+scala> dtp map Doc.initText
+res3: tmtp7.DocTree[String] =
+DocCategory(rt,List(SingleDoc(42,contents of the hello.md file!)))
+ +

You wouldn’t avoid writing functions, would you?

+

There is nothing magical about DocTree that makes it especially + amenable to the introduction of a type parameter. This is not a + feature whose proper use is limited to highly abstract or + general-purpose data structures; with its Strings and Ints strewn + about, it is utterly domain-specific, “business” code.

+

In fact, if we were likely to annotate Docs with more data, Doc + would be a perfect place to add a type parameter!

+
// suppose we add some "extra" data
+final case class Doc[A]
+  (text: String, wordIndex: Map[String, List[Int]],
+   mostPopular: List[(String, Int)],
+   extra: A)
+

You can use a type parameter to represent one simple slot in an + otherwise concretely specified structure, as above. You can + use one to represent 10 slots.

+

Parameterized types are the type system’s version of functions. They + aren’t just for collections, abstract code, or highly general-purpose + libraries: they’re for your code!

+

Unless you are going to suggest that functions are “too academic”. + Or that functions have no place in “business logic”. Or perhaps that, + while it would be nice to define functions to solve this, that, and + sundry, you’ll just do the quick no-defining-functions hack for now + and maybe come back to add some functions later when “paying off + technical debt”. Then, I’m not sure what to say.

+ +

The virtuous circle of FP and types

+

Now we are doing something very close to functional programming. + Moreover, we were led here not by a desire for referential + transparency, nor for purity, but merely for a way to represent the + states of our program in a more well-typed way.

+

In this series of posts, I have deliberately avoided discussion of + functional programming until this section; my chosen subject is types, + not functional programming. But the features we have been considering + unavoidably coalesce here into an empirical argument for functional + programming. Type parameters let us elegantly lift transformations + from one part of our program to another; the intractable complexities + of imperative type-changing direct us to program more functionally, by + computing new values instead of changing old ones, if we want access + to these features. This, in turn, encourages ever more of our program + to be written in a functional style, just as the switch to different + Doc representations induced a switch to different document tree + representations, map instead of foreach.

+ +

Paying it Back

+

Likewise, the use of functional programming style feeds back, in the + aforementioned virtuous circle, to encourage the use of stronger + types.

+

When we wanted stronger guarantees about the initialization of our + documents, and thereby also of the larger structures incorporating + them, we turned to the most powerful tool we have at our disposal for + describing and ensuring such guarantees: the type system. In so + doing, we induced an explosion of explicit data representations; where + we had two, we now have eight, whose connections to each other are + mediated by the types of functions involved.

+

With the increase in the number of explicit concepts in the code comes + a greater need for an automatic method of keeping track of all these + connections. The type system is ideally suited to this role.

+

We induced more explicit data representation, not more + representations overall. The imperative Document has + four stages of initialization, at each of which it exhibits + different behavior. All we have done is expose this fact to the + type system level, at which our usage can be checked.

+ +

Don’t miss one!

+

As it is declared, the type-changing DocTree#map has another + wonderful advantage over DocumentTree#foreach.

+

Let us say that each category should also have a document of its own, + not just a list of subtrees. In refactoring, we adjust the + definitions of DocumentCategory or DocCategory.

+
// imperative version
+final case class DocumentCategory
+  (name: String, doc: Document,
+   members: List[DocumentTree])
+   extends DocumentTree
+
+// functional version
+final case class DocCategory[D]
+  (name: String, doc: D,
+   members: DocTree[D])
+  extends DocTree[D]
+

So far, so good. Next, neither foreach nor map compile anymore.

+
TmTp7.scala:70: wrong number of arguments for pattern
+⤹ tmtp7.DocumentCategory(name: String,doc: tmtp7.Document,
+⤹                        members: List[tmtp7.DocumentTree])
+      case DocumentCategory(_, dts) =>
+                           ^
+TmTp7.scala:71: not found: value d
+        f(d)
+          ^
+TmTp7.scala:91: wrong number of arguments for pattern
+⤹ tmtp7.DocCategory[D](name: String,doc: D,members: List[tmtp7.DocTree[D]])
+      case DocCategory(c, dts) =>
+                      ^
+TmTp7.scala:92: not enough arguments for method
+⤹ apply: (name: String, doc: D, members: List[tmtp7.DocTree[D]]
+⤹        )tmtp7.DocCategory[D] in object DocCategory.
+Unspecified value parameter members.
+        DocCategory(c, dts map (_ map f))
+                   ^
+

So let us fix foreach in the simplest way possible.

+
    //                 added ↓
+    case DocumentCategory(_, _, dts) => ...
+

This compiles. It is wrong, and we can figure out exactly why by + trying the same shortcut with map.

+
      case DocCategory(c, d, dts) =>
+        DocCategory(c, d, dts map (_ map f))
+

We are treating the d: D like the name: String, just passing it + through. It is “ignored” in precisely the same way as the foreach + ignores the new data. But this version does not compile!

+
TmTp7.scala:90: type mismatch;
+ found   : d.type (with underlying type D)
+ required: D2
+        DocCategory(c, d, dts map (_ map f))
+                       ^
+

More broadly, map must return a DocTree[D2]. By implication, the + second argument must be a D2, not a D. We can fix it by using + f.

+
      DocCategory(c, f(d), dts map (_ map f))
+

Likewise, we should make a similar fix to DocumentTree#foreach.

+
    case DocumentCategory(_, d, dts) =>
+      f(d)
+      dts foreach (_ foreach f)
+

But only in the case of map did we get help from the compiler. + That’s because DocumentTree is not the only thing to gain a type + parameter in this new design. When we made DocTree take one, it was + only natural to define map with one, too.

+

We can see how this works out by looking at both foreach and map + as the agents of our practical goal: transformation of the tree by + transforming the documents therein. foreach works like this.

+
              document transformer
+               (Document => Unit)
+ DocumentTree ~~~~~~~~~~~~~~~~~~~> DocumentTree
+ ------------                      -----------------
+initial state                      final state
+  (old tree)                       (same type, “new”
+                                    but same tree)
+

The way map looks at DocTree is very similar, and we give it the + responsibilities that foreach had, so it is unsurprising that the + “shape” we imagine for transformation is similar.

+
               document transformer
+                    (D => D2)
+    DocTree[D] ~~~~~~~~~~~~~~~~~~~~> DocTree[D2]
+ -------------                       ----------------
+ initial state                       final state
+  (old tree)                         (changed type,
+                                      changed value!)
+

The replacement of D with D2 also means that values of type D + cannot occur anywhere in the result, as D is abstract, so only + appears as doc by virtue of being the type parameter passed to + DocTree and its data constructors (er, “subclasses”).

+

As our result type is DocTree[D2], we have two options, considering + only the result type:

+
    +
  1. return a DocTree with no D2s in its representation, one role of + None and Nil in Option and List respectively, or
  2. +
  3. make D2s from the Ds in the DocTree[D] we have in hand, by + passing them to the ‘document transformer’ D => D2.
  4. +
+

Similarly, no DocTree[D] values can appear anywhere in the result. + As with the Ds, they must all be transformed or dropped, with a + different ‘empty’ DocTree chosen.

+ +

The dangers of “simplifying”

+

Suppose we instead defined map as follows.

+
  def map(f: D => D): DocTree[D]
+

If you subscribe to the idea of type parameters being for wonky + academics, this is “simpler”. And it’s fine, I suppose, if you only + have one D in mind, one document type in mind. Setting aside that + we have four, there is another problem. Let’s take a look at the + “shape” of this transformation.

+
               document transformer
+                     (D => D)
+    DocTree[D] ~~~~~~~~~~~~~~~~~~~~> DocTree[D]
+ -------------                       ----------------
+ initial state                       final state
+  (old tree)                         (but no promise,
+                                      same type!)
+

The problem with a D => D transformer is that we can’t make promises + that all our data passed through it. After all, a source d has the + same type as f(d). We could even get away with

+
  def map(f: D => D): DocTree[D] = this
+

map[D2] is strictly more general. Even if we have only one D in + mind for DocTree, it still pays to type-parameterize it and to add + ‘type-changing’ extra type parameters like D2.

+ +

The dangers of missing values

+

Have you ever started getting a new bill, then missed a payment + because you thought you were covered for the month?

+

Have you ever gone on vacation and, in your relief at having not left + anything important at home, left something behind when packing for + your return trip?

+

This kind of thing cannot be characterized in the manner of “well, I + would just get a runtime error if I didn’t have a type checker, so + it’s fine.” Yet it simply falls out of the system we have chosen; + moreover, we have barely begun to consider the possibilities.

+

In this series, I have focused on existential types, which we can in + one sense consider merely abstract types that the compiler checks that + we treat as independent, like D and D2. Existential types are + only one natural outcome of the system of type abstraction brought to + us by type parameters; there are many more interesting conclusions, + like the ones described above.

+

Next, in + “It’s existential on the inside”, + we will see how deeply intertwined universal and existential types + really are.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/charity.html b/blog/charity.html new file mode 100644 index 00000000..8e3f0421 --- /dev/null +++ b/blog/charity.html @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Foundation is a 501(c)(3) public charity + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Foundation is a 501(c)(3) public charity

+ + + governance + +
+
+
+
+

Typelevel Foundation is a 501(c)(3) public charity

+

Last August, we announced the Typelevel Foundation, a nonprofit organization incorporated in California. Today, we are proud to share that the Internal Revenue Service has determined that the Typelevel Foundation is a 501(c)(3) tax-exempt public charity.

+

The process of applying for charitable status is challenging, and especially so for open source organizations, which frequently receive denials. Working together with our attorneys, we prepared an application that explained what Typelevel does and why this work is charitable, citing the unique innovations of our projects, our participation in conferences and mentoring programs, and our commitment to open collaboration. We want to recognize and thank our community for their impressive record of impact that we showcased in our application.

+
The determination that the Typelevel Foundation is a public charity is a testament to the intellectual merit and educational value of the Typelevel community's contributions to functional programming and open source for over a decade.
+ +

Benefits and responsibilities

+

Our charitable status expands fundraising opportunities while also reducing our operating costs.

+
    +
  • +

    We are exempt from paying tax on the money that we raise. Furthermore, individuals and companies that pay tax in the US may deduct donations to the Foundation on their annual returns.

    +
  • +
  • +

    We are eligible for free and discounted services from several companies. For example, GitHub now provides us a Team account without seat limits at no cost, saving us thousands of dollars.

    +
  • +
  • +

    We have internationally recognized institutional credibility. By partnering with the Swiss Philanthropy Foundation and the Maecenata Foundation, we can now receive tax-efficient charitable donations from across Europe. We are also eligible for grants from other 501(c)(3) organizations, including private foundations such as the Alfred P. Sloan Foundation.

    +
  • +
+

As a charity, we have legal and fiduciary responsibilities that reinforce our own commitments to transparency and community-driven open source development. Above all, we are accountable to our mission of advancing research and education in functional programming, prioritizing public benefit over private interests. To remain compliant, we must file Form 990 annually with the IRS which details our finances and describes how funds were spent towards our charitable purpose. These documents are made available for public inspection.

+ +

Migrating our financial infrastructure

+

For several years, Open Source Collective served as the fiscal host for our funds (not to be confused with Open Collective, which is the web platform). Earlier this week, they transferred our balance to the Foundation's bank account and closed our account with them. If you had setup a recurring donation on our old Open Collective page, it has been canceled.

+

We now accept donations via several platforms, including GitHub Sponsors and Every.org (which can handle stock and crypto donations). We have also set up a new Open Collective page for the Foundation, connected to our own bank account, which we will use to provide transparency into our finances and spending.

+ +

What's next

+

The first phase of our work focused on the legal restructuring of Typelevel as a nonprofit Foundation. In this next phase, we will focus on resourcing the Foundation to support its mission, by fundraising, grant writing, and recruiting new members to our committees.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Foundation + + +
+ The Typelevel Foundation is a nonprofit 501(c)(3) public charity. Our mission is to maintain Typelevel projects, advance research and education in functional programming, and grow our community. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/charter-changes.html b/blog/charter-changes.html new file mode 100644 index 00000000..4483b055 --- /dev/null +++ b/blog/charter-changes.html @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Governance Update + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Governance Update

+ + + governance + +
+
+
+
+

Typelevel Governance Update

+

This week the Steering Committee updated the Typelevel Charter. The updates are minimal and intended to clarify the role of Chair. First, the role has been renamed to Secretary. We believe that this renaming is more aligned with the function of the role, as it is not intended to be a leadership position, but is instead responsible for the administrative running of the committee (notably, calling votes). We have also opted to convert the role to a rotating position, with a term of 12 months so that this administrative work is shared across the committee. For those who are particularly interested, here is the pull request and associated discussion of the changes.

+

We would like to extend our sincere thanks to Ross Baker for his work as Chair of the Typelevel Steering Committee these last years. For the first rotation, Andrew Valencik has volunteered to take on the mantle of calling votes and determining quorum. Thank you Andrew, we look forward to voting when you ask us to!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/ciris.html b/blog/ciris.html new file mode 100644 index 00000000..941092b3 --- /dev/null +++ b/blog/ciris.html @@ -0,0 +1,607 @@ + + + + + + + + + + + + + + + + + + + + + + + Validated Configurations with Ciris + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Validated Configurations with Ciris

+ + + technical + +
+
+
+
+

Validated Configurations with Ciris

+

The need for configuration arises in almost every application, as we want to be able to run in different environments -- for example, local, testing, and production environments. Configurations are also used as a way to keep secrets, like passwords and keys, out of source code and version control. By having configurations as untyped structured data in files, we can change and override settings without having to recompile our software.

+

In this blog post, we'll take a look at configurations with configuration files, to see how we can make the loading process less error-prone, while overcoming obstacles with boilerplate, testing, and validation. We'll also identify when it's suitable to use Scala as a configuration language for improved compile-time safety, convenience, and flexibility; and more specifically, how Ciris helps out.

+ +

Configuration Files

+

Traditionally, configuration files, and libraries like Typesafe Config, have been used to load configurations. This involves writing your configuration file, declaring values and how they're loaded, and then writing very similar Scala code for loading that configuration. That kind of boilerplate code typically looks something along the lines of the following example.

+
import com.typesafe.config.{Config, ConfigFactory}
+
+// The settings class, wrapping Typesafe Config
+final case class Settings(config: Config) {
+  object http {
+    def apiKey = config.getString("http.api-key")
+    def timeoutSeconds = config.getInt("http.timeout-seconds")
+    def port = config.getInt("http.port")
+  }
+}
+
+// The configuration file, here represented in code
+val config =
+  ConfigFactory.parseString(
+    """
+      |http {
+      |  api-key = ${?API_KEY}
+      |  timeout-seconds = 10
+      |  port = 989
+      |}
+    """.stripMargin
+  ).resolve()
+
+val settings = Settings(config)
+
show(settings)
+// Settings(Config(SimpleConfigObject({"http":{"port":989,"timeout-seconds":10}})))
+

This is a tedious, error-prone process that rarely sees any testing efforts. PureConfig (and other libraries, like Case Classy) were created to remove that boilerplate. Using macros and conventions, they inspect your configuration model (nested case classes) and generate the necessary configuration loading code. This eliminates a lot of errors typically associated with configuration loading. Following is an example of how you can load that very same configuration with PureConfig.

+
final case class HttpSettings(
+  apiKey: String,
+  timeoutSeconds: Int,
+  port: Int
+)
+
+final case class Settings(http: HttpSettings)
+
+val settings = pureconfig.loadConfig[Settings](config)
+
show(settings)
+// Left(ConfigReaderFailures(KeyNotFound("http.api-key", None, Set()), List()))
+ +

Encoding Validation

+

In both previous examples, we do not check whether our configurations are valid to use with our application. In the case of Typesafe Config, we hit a runtime exception if the key is missing or if the type conversion fails, and in PureConfig's case, we will instead get a ConfigReaderFailures. But in neither case do we care what values are being loaded, as long as they can be converted to the appropriate types. For example, we might require a key of certain length and that it only contains certain characters, the timeout needs to be positive, and the port must be a non-system port number (value in the inclusive range between 1024 and 65535).

+

You could write an additional validation step to ensure the configuration is valid after it has been loaded -- which can be tedious to write and requires testing. One could also argue that the types of the configuration values are too permissive: why use String for the key if you do not accept all String values? And why use an Int for timeout and port, if you only allow a limited subset of values?

+

We could write these custom types ourselves, including the validation logic, and tell PureConfig how to load them -- which would be tedious to write for many types and would require testing. Another alternative is to use refined, which allows you to do type-level refinements (apply predicates) to types. I found this approach so useful that I wrote a small integration between PureConfig and refined at the end of last year (see blog post), so that PureConfig can now load refined's types.

+
import eu.timepit.refined.api.Refined
+import eu.timepit.refined.numeric.Interval
+import eu.timepit.refined.pureconfig._
+import eu.timepit.refined.string.MatchesRegex
+import eu.timepit.refined.types.numeric.PosInt
+import eu.timepit.refined.W
+
+type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]
+
+type NonSystemPort = Int Refined Interval.Closed[W.`1024`.T, W.`65535`.T]
+
+final case class HttpSettings(
+  apiKey: ApiKey,
+  timeoutSeconds: PosInt,
+  port: NonSystemPort
+)
+
+final case class Settings(http: HttpSettings)
+
+val settings = pureconfig.loadConfig[Settings](config)
+
show(settings)
+// Left(
+//   ConfigReaderFailures(
+//     KeyNotFound("http.api-key", None, Set()),
+//     List(
+//       CannotConvert(
+//         "989",
+//         "eu.timepit.refined.api.Refined[Int,eu.timepit.refined.boolean.And[eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Less[Int(1024)]],eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Greater[Int(65535)]]]]",
+//         "Left predicate of (!(989 < 1024) && !(989 > 65535)) failed: Predicate (989 < 1024) did not fail.",
+//         None,
+//         "http.port"
+//       )
+//     )
+//   )
+// )
+

As you can see in the example above, refined already contains type aliases for many common refinement types, like PosInt (for Int values greater than zero). You can also easily define your own predicates, like the one for the key and port. The W here is a shorthand for shapeless' Witness: a way to encode literal-based singleton types (essentially, values on the type-level). If you're using Typelevel Scala with the -Yliteral-types flag, you can write values directly in the type declaration, without having to use Witness.

+

If you're not convinced configurations need to be validated, I can recommend reading the paper Early Detection of Configuration Errors to Reduce Failure Damage, and to read through the slides of Leif Wickland's (one of the authors behind PureConfig) recent presentation Defusing the Configuration Time Bomb on the subject.

+

In many ways, think of configurations as user input -- would you happily accept any values provided to your application from its users? Probably not: you would validate the input, and sanitize it if possible. Think about configurations in the same way, except that the user here might happen to be a developer of the application. The key here, as discussed in the paper linked above, is to check that your configuration is valid as soon as possible, ideally at compile-time, or as soon as the application starts. We want to avoid situations where we're running the application and suddenly discover that configuration values are invalid or cannot be loaded -- or worse, continue running with an invalid configuration, not to discover issues until much later on.

+ +

Improving Compile-time Safety

+

We've now got a way to encode validation in the types of our configurations, and a boilerplate-free way of loading values of those types from configuration files -- is there still room for improvement? To answer that question, we first need to ask why we are using configuration files in the first place.

+

Whether you thought about it or not, the main reason for using configuration files is so that we can change settings without having to recompile the software. In my experience, most developers default to using configuration files, and almost always change values by pushing commits to a version control repository. This is followed by a new release of the software, either manually or via a continuous integration system. In scenarios like this, and in general when it's easy to change and release software (particularly when employing continuous deployment practices), configuration files are not used for the benefit of being able to change values without recompile.

+

In such cases, why are we not writing the configurations directly in source code? Christopher Vogt has written an excellent blog post (and given a presentation) on the subject. The tricky part here is managing values which need to be dynamic in the environment (like the port to bind) and are secret (like passwords and keys). Depending on your requirements and preferences, you more or less have two alternatives.

+
    +
  • +

    If you know which environments your application will run in, and what the configuration values will be in those environments, you can just include the configurations in your application code (if it has no secrets), or store, compile, and bundle the configuration separately. If you have a requirement that secrets shouldn't touch persistent storage, this might not be a feasible alternative. You might also appreciate the fact that all code relating to your application is in the same version control repository and gets compiled together, in which case this approach might not be suitable.

    +
  • +
  • +

    Alternatively, you can include the configuration in your application, but load secrets and values which need to be dynamic from the environment during runtime. This is necessary when configuration values cannot be determined beforehand -- because you do not know what environment your application will run in, or if you use a vault (like credstash, for example) or a configuration service (like ZooKeeper, for example) -- or if you prefer having your configuration together with your application code and in the same version control repository.

    +
  • +
+

In this post, we'll only focus on the latter case. While it’s possible to not use any libraries in the latter case, loading values from the environment typically means dealing with: different environments and configuration sources, type conversions, error handling, and validation. This is where Ciris comes in: a small library, dependency-free at its core, helping you to deal with all of that more easily.

+ +

Introducing Ciris

+

Imagine for the moment that no part of your configuration is secret and that your application only ever runs in one environment. You can then just write your configuration in code.

+
import eu.timepit.refined.auto._
+
+final case class Config(
+  apiKey: ApiKey,
+  timeoutSeconds: PosInt,
+  port: NonSystemPort
+)
+
+val config =
+  Config(
+    apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
+    timeoutSeconds = 10,
+    port = 4000
+  )
+

You then realize that it's a bad idea to put the key in the source code, because source code can easily get into the wrong hands. You decide that you'll instead read an environment variable for the key. Since you want to make sure that your configuration is valid, you have used refinement types, so you'll have to make sure to check that the key conforms to the predicate. You would also welcome a helpful error message if the key is missing or invalid. This sounds like more work than it should be, so let's see how Ciris can help us.

+

Ciris method for loading configurations is loadConfig and it works in two steps: first define what to load, and then how to load the configuration. For reading a key from an environment variable, you can use env[ApiKey]("API_KEY") which reads the environment variable API_KEY as an ApiKey. Ciris has a refined integration in a separate module, so you just need to add an appropriate import. Loading the configuration is then just a function accepting the loaded values as arguments.

+
import ciris._
+import ciris.refined._
+
+val config =
+  loadConfig(
+    env[ApiKey]("API_KEY")
+  ) { apiKey =>
+    Config(
+      apiKey = apiKey,
+      timeoutSeconds = 10,
+      port = 4000
+    )
+  }
+
show(config)
+// Left(ConfigErrors(MissingKey(API_KEY, Environment)))
+

Ciris deals with type conversions, error handling, and error accumulation, so you can focus on your configuration. The loadConfig method returns an Either[ConfigErrors, T] instance back, where T is the result of your configuration loading function. You can retrieve the accumulated error messages by using messages on ConfigErrors.

+
show { config.left.map(_.messages) }
+// Left(Vector("Missing environment variable [API_KEY]"))
+

If we decided that the port needs to be dynamic as well, we can simply make that change. In the example below, we are using prop to read the http.port system property for the port to use. As you can see, you are free to mix configuration sources as you please. While we are reading environment variables and system properties in these examples, you could just as well use sources for some configuration services or vaults.

+
val config =
+  loadConfig(
+    env[ApiKey]("API_KEY"),
+    prop[NonSystemPort]("http.port")
+  ) { (apiKey, port) =>
+    Config(
+      apiKey = apiKey,
+      timeoutSeconds = 10,
+      port = port
+    )
+  }
+
show { config.left.map(_.messages) }
+// Left(
+//   Vector(
+//     "Missing environment variable [API_KEY]",
+//     "Missing system property [http.port]"
+//   )
+// )
+

You might recognize the similarities between loadConfig and ValidatedNel with an Apply instance from Cats. That's because it's more or less how loadConfig works behind the scenes, except Ciris has its own custom implementation in order to be dependency-free in the core module.

+ +

Multiple Environments

+

We still have to deal with multiple environments in our configuration, assuming there are differences between configurations, or how they are loaded, in the different environments. There are several ways you can do this with Ciris -- one way is to define an enumeration with enumeratum and load values of that enumeration. Let's say we want to use a default configuration when running the application locally, but want to keep the key and port dynamic in the other environments (testing and production). We start by defining an enumeration of the different environments.

+
import _root_.enumeratum._
+
+object environments {
+  sealed abstract class AppEnvironment extends EnumEntry
+  object AppEnvironment extends Enum[AppEnvironment] {
+    case object Local extends AppEnvironment
+    case object Testing extends AppEnvironment
+    case object Production extends AppEnvironment
+
+    val values = findValues
+  }
+}
+

We can use the withValue method to define a requirement on a configuration value in order to be able to load our configuration. It works just like loadConfig, except it wraps your loadConfig statements (think of it as flatMap, while loadConfig is map). If no environment was specified in the environment variable APP_ENV or if it was set to Local, we will use a default configuration. We'll load the configuration just like before for any other valid environments (testing and production).

+
import environments._
+import ciris.enumeratum._
+
+val config =
+  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
+    case Some(AppEnvironment.Local) | None =>
+      loadConfig {
+        Config(
+          apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
+          timeoutSeconds = 10,
+          port = 4000
+        )
+      }
+
+    case _ =>
+      loadConfig(
+        env[ApiKey]("API_KEY"),
+        prop[NonSystemPort]("http.port")
+      ) { (apiKey, port) =>
+        Config(
+          apiKey = apiKey,
+          timeoutSeconds = 10,
+          port = port
+        )
+      }
+  }
+
show(config)
+// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))
+

An alternative to the above is to have multiple entrypoints (main methods) in your application, each running the application with different configuration loading code (or using a default configuration) for the respective environment. Depending on how packaging and running of your application looks like across different environments, this may or may not be a suitable solution. Note that it's very much possible to mix these approaches, and you should strive to find what works best in your case.

+
// Runs the application with the provided configuration
+def runApplication(config: Config): Unit = { /* omitted */ }
+
+object Local {
+  def main(args: Array[String]): Unit =
+    runApplication {
+      Config(
+        apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
+        timeoutSeconds = 10,
+        port = 4000
+      )
+    }
+}
+
+object TestingOrProduction {
+  def main(args: Array[String]): Unit =
+    runApplication {
+      val config =
+        loadConfig(
+          env[ApiKey]("API_KEY"),
+          prop[NonSystemPort]("http.port")
+        ) { (apiKey, port) =>
+          Config(
+            apiKey = apiKey,
+            timeoutSeconds = 10,
+            port = port
+          )
+        }
+
+      config.fold(
+        errors => throw new IllegalArgumentException(s"Unable to load configuration: ${errors.messages}"),
+        identity
+      )
+    }
+}
+ +

Testing Configurations

+

Writing your configurations in Scala means you have the flexibility to work with them as you want. You're no longer limited to what can be done with configuration files. Sharing configurations between your application and tests is also very straightforward -- simply make the configuration loading function (and the default configuration) available for the tests.

+
// This can now be accessed from the tests
+val defaultConfig =
+  Config(
+    apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
+    timeoutSeconds = 10,
+    port = 4000
+  )
+
+// This can now be accessed from the tests
+val configWith =
+  (apiKey: ApiKey, port: NonSystemPort) =>
+    Config(
+      apiKey = apiKey,
+      timeoutSeconds = 10,
+      port = port
+    )
+
+val config =
+  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
+    case Some(AppEnvironment.Local) | None =>
+      loadConfig(defaultConfig)
+    case _ =>
+      loadConfig(
+        env[ApiKey]("API_KEY"),
+        prop[NonSystemPort]("http.port")
+      )(configWith)
+  }
+

If you really want to unit test the configuration loading as well, you can do so with minor rewrites. Currently, we depend on some fixed configuration sources for environment variables and system properties (technically, system properties are mutable), but if we instead pass sources (ConfigSources) as arguments, we can read values from those sources using the read method.

+

The read method normally looks for an implicit ConfigSource to read from, which would have been perfect if we only used a single source. But since we have multiple sources here, we instead use read to redefine env and prop to read from the provided sources. ConfigReader[T] captures the ability to convert from String to T, where the String value has been read from a ConfigSource.

+
def config(
+  envs: ConfigSource[String],
+  props: ConfigSource[String]
+): Either[ConfigErrors, Config] = {
+  // Custom env which reads from envs
+  def env[T: ConfigReader](key: String) =
+    read[T](key)(envs, ConfigReader[T])
+
+  // Custom prop which reads from props
+  def prop[T: ConfigReader](key: String) =
+    read[T](key)(props, ConfigReader[T])
+
+  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
+    case Some(AppEnvironment.Local) | None =>
+      loadConfig(defaultConfig)
+    case _ =>
+      loadConfig(
+        env[ApiKey]("API_KEY"),
+        prop[NonSystemPort]("http.port")
+      )(configWith)
+  }
+}
+

We'll then define a couple of helper methods for creating ConfigSources from key-value pairs. The ConfigSource type parameter is the type of keys the source can read, which is String for both environment variables and system properties. The ConfigKeyType is basically the name of the key that can be read, for example environment variable. Below we're using predefined instances in the ConfigKeyType companion object.

+
def envs(entries: (String, String)*): ConfigSource[String] =
+  ConfigSource.fromMap(ConfigKeyType.Environment)(entries.toMap)
+
+def props(entries: (String, String)*): ConfigSource[String] =
+  ConfigSource.fromMap(ConfigKeyType.Property)(entries.toMap)
+

We can test our config method using different combinations of environment variables and system properties. Note that envs and props have the same type, so if you want to avoid using them interchangeably, you can define custom wrapper types for them. We'll leave that out here for sake of simplicity. I've found that it's not very common to read values from more than one ConfigSource, but as it's definitely possible, it can be worth making sure you do not mix them up.

+
show { config(envs(), props()) }
+// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))
+
+show {
+  config(
+    envs("APP_ENV" -> "Local"),
+    props()
+  )
+}
+// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))
+
+show {
+  config(
+    envs("APP_ENV" -> "QA"),
+    props()
+  ).left.map(_.messages)
+}
+// Left(
+//   Vector(
+//     "Environment variable [APP_ENV] with value [QA] cannot be converted to type [$line34.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$environments$AppEnvironment]"
+//   )
+// )
+
+show {
+  config(
+    envs("APP_ENV" -> "Production"),
+    props()
+  ).left.map(_.messages)
+}
+// Left(
+//   Vector(
+//     "Missing environment variable [API_KEY]",
+//     "Missing system property [http.port]"
+//   )
+// )
+
+show {
+  config(
+    envs(
+      "APP_ENV" -> "Production",
+      "API_KEY" -> "changeme"
+    ),
+    props()
+  ).left.map(_.messages)
+}
+// Left(
+//   Vector(
+//     "Environment variable [API_KEY] with value [changeme] cannot be converted to type [eu.timepit.refined.api.Refined[String,eu.timepit.refined.string.MatchesRegex[java.lang.String(\"[a-zA-Z0-9]{25,40}\")]]]: Predicate failed: \"changeme\".matches(\"[a-zA-Z0-9]{25,40}\").",
+//     "Missing system property [http.port]"
+//   )
+// )
+
+show {
+  config(
+    envs(
+      "APP_ENV" -> "Production",
+      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
+    ),
+    props()
+  ).left.map(_.messages)
+}
+// Left(Vector("Missing system property [http.port]"))
+
+show {
+  config(
+    envs(
+      "APP_ENV" -> "Production",
+      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
+    ),
+    props("http.port" -> "900")
+  ).left.map(_.messages)
+}
+// Left(
+//   Vector(
+//     "System property [http.port] with value [900] cannot be converted to type [eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Interval.Closed[Int(1024),Int(65535)]]]: Left predicate of (!(900 < 1024) && !(900 > 65535)) failed: Predicate (900 < 1024) did not fail."
+//   )
+// )
+
+show {
+  config(
+    envs(
+      "APP_ENV" -> "Production",
+      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
+    ),
+    props("http.port" -> "4000")
+  )
+}
+// Right(Config(X9aKACPtircCrrFKYhwPr7fXx8srow, 10, 4000))
+

Finally, when running the application, simply provide the actual ConfigSources for environment variables and system properties.

+
show { config(ConfigSource.Environment, ConfigSource.Properties) }
+// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))
+ +

Conclusion

+

In this blog post, we've seen how we can make the configuration loading process, with configuration files, less error-prone, by eliminating the boilerplate code with PureConfig, and encoding validation with refined -- seeing how the two libraries can work together seamlessly.

+

We've also identified cases where we can use Scala as a configuration language, seeing that it's particularly suitable in cases where it's easy to change and deploy software. We've introduced the challenge of loading configuration values from the environment and seen how Ciris can help you with that, letting you focus on the configuration. We've seen that Scala configurations can provide more compile-time safety and flexibility than traditional configurations with configuration files.

+

If you're looking for more information on Ciris, the project's website (https://cir.is) is a good start.
+ There's also a usage guide and API documentation which expands on what's been discussed here.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Viktor Lövgren + + +
+ Viktor is a Software engineer at Ovo Energy in London, working on the platform powering energy meter readings and consumption data. He’s an advocate of strongly typed functional programming, and Scala in particular, which has been his professional focus the past three years. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/code-of-conduct.html b/blog/code-of-conduct.html new file mode 100644 index 00000000..19481477 --- /dev/null +++ b/blog/code-of-conduct.html @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + New Code of Conduct + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

New Code of Conduct

+ + + governance + +
+
+
+
+

New Code of Conduct

+

We have recently adopted a new Code of Conduct and Enforcement Policy. This change was approved by the Steering committee after a 1 month discussion and voting period. Thank you to everyone who engaged!

+

The scope of the Typelevel Code of Conduct and Enforcement Policy encompasses both organization and affiliate projects (as described in the Typelevel Charter). + While the Typelevel Charter has always specified that affiliate projects must adhere to the Typelevel organization policies, including the Code of Conduct, this has not been enforced in practice.

+

Prior to this change the Typelevel Code of Conduct was a fork of the Scala Code of Conduct. We, the Typelevel Steering Committee, are choosing the Python Software Foundation Code of Conduct to fork because it has an accompanying enforcement policy, and there is associated training available. Some Typelevel Steering Committee members engaged in this training through Otter Technology in late 2023. All Code of Conduct Committee members will be encouraged to take this (or equivalent) training going forward.

+

We believe our community is already a kind and welcoming place. + However, a Code of Conduct must be enforced to maintain community trust and safety. + Additionally, an enforcement policy is useful to provide transparency and accountability on how the Code of Conduct Committee will work.

+ +

What happens next?

+

Now that the Code of Conduct and Enforcement Policy have been voted in by the Typelevel Steering Committee, we will begin updating the CODE_OF_CONDUCT files in organization project repositories.

+ +

As an affiliate project maintainer, what should I expect?

+

All affiliate projects are expected to adopt the Typelevel Code of Conduct. + We will open pull requests to update each affiliate project's CODE_OF_CONDUCT file. + If there are any concerns, we are available for discussion and we encourage affiliate maintainers to please reach out. + Ultimately, if a project chooses not to adopt the Typelevel Code of Conduct, maintainers can close the PR, and we'll handle removing the project from the affiliate project list. + This is totally fine and we support your choices! + We believe open source developers are free to choose the projects they contribute to and the communities they support ♥

+ +

Can an affiliate project maintainer participate in Code of Conduct enforcement?

+

Affiliate project's can list additional moderators in their CODE_OF_CONDUCT file, that the Typelevel Code of Conduct Committee will work with as described in the Enforcement Policy "Affiliate project processes" section. + Additionally, if this work interests you, keep an eye out for future calls for Typelevel Code of Conduct Committee members!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/community-safety.html b/blog/community-safety.html new file mode 100644 index 00000000..0dca4939 --- /dev/null +++ b/blog/community-safety.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + Community Safety + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Community Safety

+ + + social + +
+
+
+
+

Community Safety

+

Effective today, Jon Pretty is barred from participating in Typelevel projects and events. + We make this decision based on well-substantiated reports of predatory behavior at Scala conferences, including conferences at which Typelevel Summits were co-located.

+

All conference organizers aspire to create safe spaces for attendees, and succeed to varying degrees. + Our efforts have been insufficient. + We pledge to review our process and practices for event safety, and will provide details on concrete actions in this space before the next Typelevel Summit is held.

+

The Typelevel Steering Committee:

+
    +
  • Alexandru Nedelcu
  • +
  • Christopher Davenport
  • +
  • Daniel Spiewak
  • +
  • Kailuo Wang
  • +
  • Lars Hupel
  • +
  • Luka Jacobowitz
  • +
  • Michael Pilquist
  • +
  • Rob Norris
  • +
  • Ross A. Baker
  • +
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/concurrency-in-ce3.html b/blog/concurrency-in-ce3.html new file mode 100644 index 00000000..1a4c8d44 --- /dev/null +++ b/blog/concurrency-in-ce3.html @@ -0,0 +1,728 @@ + + + + + + + + + + + + + + + + + + + + + + + Concurrency in Cats Effect 3 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Concurrency in Cats Effect 3

+ + + technical + +
+
+
+
+

Concurrency in Cats Effect 3

+

Cats Effect 3 is just around the corner! The library has seen several major + changes between 2.0 and 3.0, so in an effort to highlight those changes, we + will be releasing a series of blog posts covering a range of topics. If you + would like to see a blog post about a particular subject, don't hesitate to + reach out! You can try out early Cats Effect 3 releases + here.

+ +

Introduction

+

In this post, we offer a broad overview of the concurrency model that serves + as the foundation for Cats Effect and its abstractions. We discuss what fibers + are and how to interact with them. We also talk about how to leverage the + concurrency model to build powerful concurrent state machines. Example programs + are written in terms of cats.effect.IO.

+

Before going any further, let's briefly review what concurrency is, how it's + useful, and why it's tedious to work with.

+ +

Concurrency

+

Concurrent programming is about designing programs in which multiple logical + threads of control are executing at the same time. There is a bit to unpack + in this definition: what are logical threads and what does it mean for them to + execute "at the same time?"

+

A logical thread is merely a description of a sequence of discrete actions. + An action is one of the most primitive operations that can be expressed in + the host language.

+

In programs that are written in traditional high-level languages like Java or + C++, these logical threads are typically represented by native threads that + are managed by the operating system. The actions that comprise these threads + are native processor instructions or VM bytecode instructions. In programs that + are written with Cats Effect, these logical threads are represented by fibers, + and the actions that comprise them are IO values.

+

What does it mean for logical threads to execute at the same time? Because the + actions of a logical thread are discrete, the actions of many logical threads + can be interleaved into one or more streams of actions. This interleaving is + largely influenced by external factors such as scheduler preemption and I/O + operations, so it is usually nondeterministic. More specifically, in the + absence of synchronization, there is no guarantee that the actions of distinct + logical threads occur in some particular order.

+

Another common perspective is that concurrency generates a partial order among + all the actions of all the logical threads in a program. Actions within a + logical thread are ordered consistently with program order. Actions between + multiple logical threads are not ordered in the absence of synchronization.

+ +

Concurrency is useful

+

Concurrency is a tool for designing high-performance applications that are + responsive and resilient. These applications typically involve multiple + interactions or tasks that must happen at the same time. For example, a + computer game needs to listen for keyboard input, play sound effects, and run + game loop logic, all while rendering graphics to the screen. A multiplayer game + must also communicate with a network server to exchange game state information.

+

Traditional sequential programming models quickly become inadequate when + applied to building these kinds of responsive programs. The computer game + program must perform all of the tasks described above at the same time; it + wouldn't be a very good game if we couldn't respond to player input while + playing a sound! Concurrency enables us to structure our program so that + each interaction is confined to its own logical thread(s), all of which + execute at the same time.

+

Concurrency complements modular program design. Interactions like graphics + rendering and network communication are largely unrelated; it would be tedious + and messy to design a program that constantly switches between graphics and + audio I/O operations. Instead of having one complex thread that performs + every interaction, we can confine each interaction to its own logical thread. + This allows us to think about and code each interaction independently of each + other while having the assurance that they occur at the same time. The + modularity that concurrency affords makes for programs that are much easier to + understand, maintain, and evolve.

+ +

Concurrency is hard

+

Concurrency is notoriously cumbersome to work with. This is no surprise to any + developer that has worked with threads and locks in any mainstream programming + language. Once we start dealing with concurrency, we have to deal with a slew + concerns: deadlocks, starvation, race conditions, thread leaks, thread blocking + and so on. A bug in any one of these concerns can have devastating consequences + in terms of correctness and performance.

+

The most popular method for achieving concurrency in Scala is with Future, + which enables the evaluation of asynchronous operations. However, it is plainly + insufficient for those of us who practice strict functional programming. + Furthermore, Future doesn't expose first-class features like cancellation, + finalization, or synchronization that are crucial for building safe concurrent + applications.

+

Akka actors are another common way to achieve concurrency in Scala. For similar + reasons as Future, they are also insufficient in strict functional + programming, however, one could certainly build a pure actor library on top of + the Cats Effect's concurrency model.

+ +

Cats Effect Concurrency

+

Cats Effect takes the perspective that concurrency is a necessary technique + for building useful applications, but existing tools for achieving concurrency + in Scala are tedious to work with. A goal of Cats Effect is then to provide + library authors and application developers a concurrency model that is safe and + simple to work with.

+

Most users should never directly interact with the concurrency model of Cats + Effect because it is a low-level API. This is intentional: low-level + concurrency constructs are inherently unsafe and effectful, so forcing users + to touch it would only be a burden for them. Cats Effect and other libraries + provide higher-level and safer abstractions that users can exploit to achieve + concurrency without having to understand what's happening under the hood. + For example, http4s achieves concurrency on requests by spawning a fiber for + every request it receives; users need only specify the request handling code + that is eventually run inside the fiber.

+

That being said, it can be tremendously helpful to learn about Cats Effect's + internals and concurrency model to understand how our applications behave and + how to tune them, so let's jump into it.

+ +

Fibers

+

Cats Effect chooses fibers as the foundation for its concurrency model. The + main benefit of fibers is that they are conceptually similar to native threads: + both are types of logical threads that describe a sequence of computations. + Unlike native threads, fibers are not associated with any system resources. + They coexist and are scheduled completely within the userspace process, + independently of the operating system. This mode of concurrency reaps major + benefits for users and performance:

+
    +
  1. Fibers are incredibly cheap in terms of memory overhead so we can create + hundreds of thousands of them without thrashing the process. This means that we + also don't need to pool fibers; we just create a new one whenever we need it.
  2. +
  3. Context switching between fibers is orders of magnitude faster than context + switching between threads.
  4. +
  5. Blocking a fiber doesn't necessarily block a native thread; this is called + semantic blocking. This is particularly true for nonblocking I/O and other + asynchronous operations.
  6. +
+

Concretely, a fiber is a logical thread that encapsulates the execution of an + IO[A] program, which is a sequence of IO effects (actions) that are bound + together via flatMap. Fibers are logical threads, so they can run + concurrently.

+

The active execution of some fiber of an effect IO[A] is represented by the + type FiberIO[A]. The execution of a FiberIO[A] terminates with one of three + possible outcomes, which are encoded by the datatype OutcomeIO[A]:

+
    +
  1. Succeeded: indicates success with a value of type A
  2. +
  3. Errored: indicates failure with a value of type Throwable
  4. +
  5. Canceled: indicates abnormal termination via cancellation
  6. +
+

Additionally, a fiber may never produce an outcome, in which case it is said to + be nonterminating.

+

Cats Effect exposes several functions for interacting with fibers directly. + We'll explore parts of this API in the following sections. Again, fibers are + considered to be an unsafe and low-level feature and must be given more caution + than we offer in the examples. Users are encouraged to use the higher-level + concurrency constructs that Cats Effect provides.

+ +

Starting and joining fibers

+

The most basic action of concurrency in Cats Effect is to start or spawn a new + fiber. This requests to the scheduler to begin the concurrent execution of a + program IO[A] inside a new logical thread. The actions of the current fiber + and the spawned fiber are interleaved in a nondeterministic fashion.

+

After a fiber is started, it can be joined which semantically blocks the + joiner until the joinee has terminated, after which join will return the + outcome of the joinee. Let's take a look at an example.

+

Let's take a look at an example that demonstrates spawning and joining of + fibers, as well as the nondeterministic interleaving of their executions.

+
import cats.effect.{IO, IOApp}
+import cats.syntax.all._
+
+object ExampleOne extends IOApp.Simple {
+  def repeat(letter: String): IO[Unit] =
+    IO.print(letter).replicateA(100).void
+
+  override def run: IO[Unit] =
+    for {
+      fa <- (repeat("A") *> repeat("B")).as("foo!").start
+      fb <- (repeat("C") *> repeat("D")).as("bar!").start
+      // joinWithNever is a variant of join that asserts
+      // the fiber has an outcome of Succeeded and returns the
+      // associated value.
+      ra <- fa.joinWithNever
+      rb <- fb.joinWithNever
+      _ <- IO.println(s"\ndone: a says: $ra, b says: $rb")
+    } yield ()
+}
+

In this program, the main fiber spawns two fibers, one which prints A 100 + times and B 100 times, and another which prints C 100 times and D 100 + times. It then joins on both fibers, awaiting their termination. Here is one + possible output from an execution of the program:

+
AAAAAAAAAAAAAAAAAAAAAAACACACACACACACACACACACACACACACACACACACACACACAACACACACACACACACACACACAACACAAACACACACACACACACACACACACACACACACACACACACACACACACACACACCACACACACACACACACACACACACCCCCBBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBBBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDDDDDDDDDDDDDDDDDDDDDD
+done: a says: foo!, b says: bar!
+

We can observe that there is no consistent ordering between the effects of + separate fibers; it is completely nondeterministic! However, the effects + within a given fiber are always sequentially consistent, as dictated by + program order; A is never printed after B, and C is never printed + after D. This is how join imposes an ordering on the execution of the + fibers: the main fiber will only ever print the done: message after the + two spawned fibers have completed.

+

It is recommended to use the safer IO.background instead of IO.start for + spawning fibers.

+ +

Canceling fibers

+

A fiber can be canceled after its execution begins with the FiberIO#cancel + function. This semantically blocks the current fiber until the target fiber + has finalized and terminated, and then returns. Let's take a look at an + example.

+
import cats.effect.{IO, IOApp}
+import cats.syntax.all._
+import scala.concurrent.duration._
+
+object ExampleTwo extends IOApp.Simple {
+  override def run: IO[Unit] =
+    for {
+      fiber <- IO.println("hello!").foreverM.start
+      _ <- IO.sleep(5.seconds)
+      _ <- fiber.cancel
+    } yield ()
+}
+

In this program, the main fiber spawns a second fiber that continuously prints + hello!. After 5 seconds, the main fiber cancels the second fiber and then the + program exits.

+

Cats Effect's concurrency model and cancellation model interact very closely + with each other, however, the latter is out of scope for this post. It will be + discussed in detail in a future post, but in the meantime, visit the Scaladoc + pages for MonadCancel and GenSpawn.

+ +

Racing fibers

+

Cats Effect exposes several utility functions for racing IO actions (or their + fibers) against eachother. Let's take a look at some of them:

+
object IO {
+  def racePair[A, B](left: IO[A], right: IO[B]): IO[Either[(OutcomeIO[A], FiberIO[B]), (FiberIO[A], OutcomeIO[B])]]
+  // higher-level functions
+  def race[A, B](left: IO[A], right: IO[B]): IO[Either[A, B]]
+  def both[A, B](left: IO[A], right: IO[B]): IO[(A, B)]
+}
+

racePair races two fiber and returns the outcome of the winner along with a + FiberIO handle to the loser. race races two fibers and returns the + successful outcome of the winner after canceling the loser. both races two + fibers and returns the successful outcome of both (in other words, it runs both + fibers concurrently and waits for both of them to complete).

+

racePair seems a bit hairy to work with, so let's try an example out with + race:

+
import cats.effect.{IO, IOApp}
+import cats.syntax.all._
+
+object ExampleThree extends IOApp.Simple {
+  def factorial(n: Long): Long =
+    if (n == 0) 1 else n * factorial(n - 1)
+
+  override def run: IO[Unit] =
+    for {
+      res <- IO.race(IO(factorial(20)), IO(factorial(20)))
+      _ <- res.fold(
+        a => IO.println(s"Left hand side won: $a"), 
+        b => IO.println(s"Right hand side won: $b")
+      )
+    } yield ()
+}
+

In this program, we're racing two fibers, both of which calculate the 20th + factorial. Running this program with many iterations demonstrates that either + fiber can win.

+ +

Communication

+

We have seen how fibers can directly communicate with each other via start, + join, and cancel. These mechanisms enable bidirectional communication, + but only at the beginning and end of a fiber's lifetime. It's natural to ask + if there are other ways in which fibers can communicate, particularly during + their lifetime.

+

Shared memory is an alternative means by which fibers can indirectly + communicate and synchronize with each other. Cats Effect exposes two primitive + concurrent data structures that leverage shared memory: Ref and Deferred.

+ +

Ref

+

Ref is a concurrent data structure that represents a mutable variable. It + is used to hold state that can be safely accessed and modified by many + contending fibers. Let's take a look at its basic API:

+
trait Ref[A] {
+  def get: IO[A]
+  def set(a: A): IO[Unit]
+  def update(f: A => A): IO[Unit]
+}
+

get reads and returns the current value of the Ref. set sets the current + value of the Ref. update atomically reads and sets the current value of the + Ref. Let's take a look at an example.

+
import cats.effect.{IO, IOApp}
+import cats.syntax.all._
+
+object ExampleFour extends IOApp.Simple {
+  override def run: IO[Unit] =
+    for {
+      state <- IO.ref(0)
+      fibers <- state.update(_ + 1).start.replicateA(100)
+      _ <- fibers.traverse(_.join).void
+      value <- state.get
+      _ <- IO.println(s"The final value is: $value")
+    } yield ()
+}
+

In this program, the main fiber starts 100 fibers, each of which attempts to + concurrently update the state by atomically incrementing its value. Next, the + main fiber joins on each spawned fiber one after the other, waiting for + their collective completion. Finally, after the spawned fibers are complete, + the main fiber retrieves the final value of the state. The program should + produce the following output:

+
The final value is: 100
+ +

Deferred

+

Deferred is a concurrent data structure that represents a condition variable. + It is used to semantically block fibers until some arbitrary condition has been + fulfilled. Let's take a look at its basic API:

+
trait Deferred[A] {
+  def complete(a: A): IO[Unit]
+  def get: IO[A]
+}
+

get blocks all calling fibers until the Deferred has been completed with a + value, after which it will return that value. complete completes the + Deferred, unblocking all waiters. A Deferred can not be completed more than + once. Let's take a look at an example.

+
import cats.effect.{IO, IOApp}
+import cats.effect.kernel.Deferred
+import cats.syntax.all._
+import scala.concurrent.duration._
+
+object ExampleFive extends IOApp.Simple {
+  def countdown(n: Int, pause: Int, waiter: Deferred[IO, Unit]): IO[Unit] = 
+    IO.println(n) *> IO.defer {
+      if (n == 0) IO.unit
+      else if (n == pause) IO.println("paused...") *> waiter.get *> countdown(n - 1, pause, waiter)
+      else countdown(n - 1, pause, waiter)
+    } 
+
+  override def run: IO[Unit] =
+    for {
+      waiter <- IO.deferred[Unit]
+      f <- countdown(10, 5, waiter).start
+      _ <- IO.sleep(5.seconds)
+      _ <- waiter.complete(())
+      _ <- f.join
+      _ <- IO.println("blast off!")
+    } yield ()
+}
+

In this program, the main fiber spawns a fiber that initiates a countdown. When + the countdown reaches 5, it waits on a Deferred which is completed 5 seconds + later by the main fiber. The main fiber then waits for the countdown to + complete before exiting. The program should produce the following output:

+
10
+9
+8
+7
+6
+5
+paused...
+4
+3
+2
+1
+0
+blast off!
+ +

Building a concurrent state machine

+

Ref and Deferred are often composed together to build more powerful and + more complex concurrent data structures. Most of the concurrent data types in + the std module in Cats Effect are implemented in terms of Ref and/or + Deferred. Example include Semaphore, Queue, and Hotswap.

+

In our next example, we create simple concurrent data structure called Latch + that is blocks a waiter until a certain number of internal latches have been + released. Here is the interface for Latch:

+
trait Latch {
+  def release: IO[Unit]
+  def await: IO[Unit]
+}
+

Next, we need to define the state machine for Latch. We can be in two + possible states. The first state reflects that the Latch is still active + and is waiting for more releases. We need to track how many latches are still + remaining, as well as a Deferred that is used to block new waiters. The + second state reflects that the Latch has been completely released and will no + longer block waiters.

+
sealed trait State
+final case class Awaiting(latches: Int, waiter: Deferred[IO, Unit]) extends State
+case object Done extends State
+

We can implement the Latch interface now. We create a Ref that holds our + state machine, and then a Deferred that block waiters. The initial state of + a Latch is the Awaiting state with a user-specified number of latches + remaining.

+

The release method atomically modifies the state based on its current value: + if the current state is Awaiting and there is more than one latch remaining, + then subtract by one, but if there is only one latch left, then transition to + the Done state and unblock all the waiters. If the current state is Done, + then do nothing.

+

The await method inspects the current state; if it is Done, then allow the + current fiber to pass through, otherwise, block the current fiber with the + waiter.

+
object Latch {
+  def apply(latches: Int): IO[Latch] =
+    for {
+      waiter <- IO.deferred[Unit]
+      state <- IO.ref[State](Awaiting(latches, waiter))
+    } yield new Latch {
+      override def release: IO[Unit] = 
+        state.modify {
+          case Awaiting(n, waiter) => 
+            if (n > 1)
+              (Awaiting(n - 1, waiter), IO.unit)
+            else
+              (Done, waiter.complete(()))
+          case Done => (Done, IO.unit)
+        }.flatten.void
+      override def await: IO[Unit] = 
+        state.get.flatMap {
+          case Done => IO.unit
+          case Awaiting(_, waiter) => waiter.get
+        }
+    }
+}
+

Finally, we can use our new concurrent data type in a runnable example:

+
object ExampleSix extends IOApp.Simple {
+  override def run: IO[Unit] =
+    for {
+      latch <- Latch(10)
+      _ <- (1 to 10).toList.traverse { idx => 
+        (IO.println(s"$idx counting down") *> latch.release).start
+      }
+      _ <- latch.await
+      _ <- IO.println("Got past the latch")
+    } yield ()
+}
+

This program creates a Latch with 10 internal latches and spawns 10 fibers, + each of which releases one internal latch. The main fiber awaits against the + Latch. Once all 10 fibers have released a latch, the main fiber is unblocked + and can proceed. The output of the program should resemble the following:

+
1 counting down
+3 counting down
+6 counting down
+2 counting down
+8 counting down
+9 counting down
+10 counting down
+4 counting down
+5 counting down
+7 counting down
+Got past the latch
+

Notice how the latch serves as a form of synchronization that influences the + ordering of effects among the fibers; the main fiber will never proceed until + after the Latch is completely released.

+ +

Scheduling

+

We've talked about fibers as a conceptual model with which Cats Effect + implements concurrency. One aspect of this that we haven't addressed is: how do + fibers actually execute? Our Scala applications ultimately run on the JVM (or a + JavaScript runtime if we're using Scala.js), so fibers must ultimately be + mapped to native threads in order for them to actually run. The scheduler is + responsible for determining how this mapping takes place.

+

On the JVM, Cats Effect uses an M:N scheduling model to map fibers to threads. + In practice, this means that a large number of fibers is multiplexed onto + much smaller pools of native threads. A typical application will reserve a + fixed-size pool for CPU-bound tasks, an unbounded pool for blocking tasks, + and several event handler pools for asynchronous tasks. Throughout its + lifetime, a fiber will migrate between these pools to accomplish its various + tasks. In JavaScript runtimes, a M:1 scheduling model is employed, where all + active fibers are scheduled onto a single native thread for execution.

+

Another aspect of scheduling is how context switching of fibers takes place, + which has many implications around the fairness and throughput properties of a + concurrent application. Like many other lightweight thread runtimes, Cats + Effect's default scheduler exhibits cooperative multitasking in which fibers + can explicitly "yield" back to the scheduler, allowing a waiting fiber to run. + The yielded fiber will be scheduled to resume execution at some later time. + This is achieved with the IO.cede function.

+

Cats Effect's default scheduler also supports autoyielding which is a form of + preemptive multitasking. As we mentioned earlier, logical threads are composed + of a possibly infinite sequence of actions. Autoyielding "preempts" or forcibly + yields a fiber after it runs a certain number of actions, enabling waiting + fibers to proceed. This is particularly important on runtimes that run with 1 + or 2 native threads; a fiber that runs forever but never cedes will starve + other fibers of compute time.

+ +

Parallelism

+

One aspect of multithreaded programming that we have neglected to mention so + far is parallelism. In theory, parallelism is completely independent of + concurrency; parallelism is about simultaneous execution whereas concurrency + is about interleaved execution.

+

Parallelism is typically achieved by exploiting multiple CPU cores or even + multiple, independent machines to run a set of tasks much faster than they + would run on a single CPU or machine. Parallelism is also not necessarily + nondeterministic; we can run a set of deterministic tasks in parallel multiple + times and expect to get back the same result every time. For our purposes, + parallelism can be paired with concurrency to speed up the execution of many + logical threads. This is exactly what JVMs already do: multiple native threads + run simultaneously, resulting in higher throughput of tasks.

+

An obscure but crucial point here is that concurrency can be achieved without + parallelism; this is called single-threaded concurrency. We've already seen an + example of this: JavaScript runtimes run on a single compute thread, so the + execution of all fibers must take place on that thread as well!

+ +

Exercises

+
    +
  1. Why is the low-level fiber API designated as unsafe? Hint: consider how the + fiber API interacts with cancellation.
  2. +
  3. + Implement timeout in terms of IO.race. timeout runs some action for up + to a specified duration, after which it throws an errors. +
    def timeout[A](io: IO[A], duration: FiniteDuration): IO[A]
    +
  4. +
  5. + Implement parTraverse in terms of IO.both. parTraverse is the same as + traverse except all IO[B] are run in parallel. +
    def parTraverse[A](as: List[A])(f: A => IO[B]): IO[List[B]]
    +
  6. +
  7. + Implement Semaphore in terms of Ref and Deferred. +
    trait Semaphore {
    +def acquire: IO[Unit]
    +def release: IO[Unit]
    +}
    +object Semaphore {
    +def apply(permits: Int): IO[Semaphore]
    +}
    +
  8. +
  9. + Implement Queue in terms of Ref and Deferred. +
    trait Queue[A] {
    +def put(a: A): IO[Unit]
    +def tryPut(a: A): IO[Boolean]
    +def take: IO[A]
    +def tryTake: IO[Option[A]]
    +def peek: IO[Option[A]]
    +}
    +object Queue {
    +def apply[A](length: Int): IO[Queue[A]]
    +}
    +
  10. +
  11. Stateful is a typeclass in Cats MTL that characterizes a monad's ability + to access and manipulate state. This is typically used in monad transformer + stacks in conjunction with the StateT transformer. Is it possible to create + a Stateful instance given a Ref?
  12. +
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Raas Ahsan + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/conf-cadiz-2016-09-30.html b/blog/conf-cadiz-2016-09-30.html new file mode 100644 index 00000000..472ac4d1 --- /dev/null +++ b/blog/conf-cadiz-2016-09-30.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Community Conference Cádiz + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Community Conference Cádiz

+ + + events + +
+
+
+
+

Typelevel Community Conference Cádiz

+
+

"Cádiz" by Anna & Michal is licensed under CC BY 2.0.

+ +

About the Conference

+

A day of Typelevel sessions, co-located with Lambda World.

+

On Friday, September 30th, Lambda World hosted a Typelevel Unconference during the morning and a few workshops in the afternoon. The day concluded with a community dinner where attendees can discuss functional concepts while enjoying Flamenco music.

+ +

Venue

+

This event took place at the Palacio de Congresos de Cádiz.

+ +

Sponsors

+

We'd like to thank all our sponsors who helped to make the conference happen:

+ +

Platinum

+

47 Degrees

+ +

Gold

+

The Workshop

+ +

Silver

+

Ciklum + Workday

+
+
+ +
+ + + + + + diff --git a/blog/conf-cadiz-2017-10-26.html b/blog/conf-cadiz-2017-10-26.html new file mode 100644 index 00000000..51bffc68 --- /dev/null +++ b/blog/conf-cadiz-2017-10-26.html @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel UnConference - Lambda World Cadiz 2017 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel UnConference - Lambda World Cadiz 2017

+ + + events + +
+
+
+
+

Typelevel UnConference - Lambda World Cadiz 2017

+
+

"Cádiz" by Anna & Michal is licensed under CC BY 2.0.

+ +

About the Conference

+

A day of Typelevel sessions, co-located with Lambda World.

+

At Lambda.World, we planned the first day to be an introduction to kick things off, with different workshops and events. Included in your Lambda World ticket price is the ability enjoy round table discussions and workshops to see, in practice, how the functional programming paradigm works.

+

We’ll have a Typelevel Unconference during the morning and introductory workshops and a Scala Spree in the afternoon where we’re going to talk and to work on different Open Source projects from the Scala community.

+ +

Venue

+

This event will take place at the Palacio de Congresos de Cádiz.

+ +

Sponsors

+

We'd like to thank all our sponsors who are helping to make the conference happen:

+ +

Platinum

+

47 Degrees

+
+
+ +
+ + + + + + diff --git a/blog/confronting-racism.html b/blog/confronting-racism.html new file mode 100644 index 00000000..15019851 --- /dev/null +++ b/blog/confronting-racism.html @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + Confronting Racism + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Confronting Racism

+ + + social + +
+
+
+
+

Confronting Racism

+

In the wake of unrest in the United States and elsewhere following the + deaths of George Floyd, Ahmaud Arbery, Breonna Taylor, and countless + others, it is important for us to consider the impact of racism in + every sector of our lives. This includes taking a long, hard look at + race and racism in our community

+

In this blog post, I want to discuss the problem briefly and then + begin to look at ways that individuals and organizations can learn + about racism and to make changes to improve diversity, equity, and + inclusion.

+ +

The Problem

+

The United States Equal Employment Opportunity Commission (EEOC) + produced a + report + showing some sobering statistics about the employment of African + Americans in the technology sector. In the private industry overall, + African Americans represented 14.4 percent of the workforce, but in + the technology sector that number was just 7.4 percent. For + comparison, according to recent census + data, + black or African Americans make up approximately 13.4 percent of the + U.S. population. The problem becomes even worse at the executive + level: African Americans fill between 2 and 5.3 percent of executive + roles in technology firms.

+

A 2016 commissioned + paper produced by the + U.S. National Academies of Science found that African American and + Hispanic people were not underrepresented in terms of computer science + degrees (about 9.2%), but that they were underrepresented in the labor + force (only 5.9%). That study did not address the cause of the + disparity - we are left to speculate if they encountered bias in + hiring and interviewing, hostile work environments, or something else.

+

Also in 2016, the Harvard Business Review reported on a + study + on bias in hiring. Their findings showed that when choosing between + three candidates for a position, people tended to choose a black + candidate only if the candidate pool had at least 2 black candidates. + It is worth noting that the effect was the same for gender - a + candidate pool of two men and one women virtually always resulted in + committees recommending a man.

+

If we are honest with ourselves, these statistics, while sobering, + should not really surprise us. Looking around at our fellow employees, + open source collaborators, and conference attendees, we know that most + are white men. With that, the question becomes, how do we begin to + confront the racism that appears endemic in our industry?

+ +

What Can We Do?

+ +

Workplace culture

+

In May, 2020, the Harvard Business Review issued a + report + titled "What Works? Evidence-Based Ideas to Increase Diversity, + Equity, and Inclusion in the Workplace". One of the authors, David + Pedulla, also contributed a + summary + which included five strategies for employers to help increase + diversity. These recommendations can help companies attract, and + retain diverse talent.

+ +

Codes of Conduct

+

Typelevel has a code of + conduct that emphasizes + the goal of making the community "friendly, safe and welcoming" for + everyone. It prohibits harassment based on (among other things) race + or ethnicity. If you are a member of an underrepresented minority and + feel that anyone in the community is making you feel unwelcome, report + it.

+

Familiarize yourself with the codes of conduct in whatever + organizations, meetings, or events you participate in. Taking time to + digest these in advance will help guide your behavior but also to + recognize when others may be out of line, and help you understand what + steps you can take. If your professional or avocational circles don't + have a code of conduct, you can begin discussions to find one that + will suit the community.

+ +

Intervention

+

However, it must not fall to people of color to recognize and report + harassment or unwelcome behavior. As members of a positive and + welcoming community, we must not be passive bystanders. Learning to + intervene constructively may take some practice.

+

Robin DiAngelo has a list of Silence Breakers for Whites in + Cross-racial + Discussions + containing 18 different phrases you can use to intervene directly in + an ongoing situation. These phrases can help diffuse a difficult + situation calmly and allow people to save face and, ideally, replace a + harmful discussion with a constructive one.

+

The Southern Poverty Law Center has also produced some useful + suggestions + for bystander intervention. Although their document was intended for + use on college campuses, the suggestions may be more broadly + applicable. For instance, the SPLC points out that even if you do not + intervene in the moment, you can document harassing behavior and + provide it to moderators or authorities. In addition, they suggest + that you can provide support to victims even after harassment has + taken place.

+

When you think about intervening, consider the feelings of the people + you are hoping to protect. You may wish to discretely ask if they + would like help in a situation, if there is time or a private channel + available, especially if you intend to notify a moderator or + supervisor.

+ +

Education

+

Those of us who are in the (white) majority owe it to ourselves to + learn about race, racism, and the dark history behind them. We should + also take time to reflect on our role in perpetuating racist power + structures, and how we benefit from the repression of others. Today + there is no shortage of books and resources to help us. Here are a few + commonly-cited resources:

+ +

In addition, we need to look around our own communities for + opportunities to volunteer or contribute. If you cannot march, + consider donating to organizations supporting the Black Lives Matter + movement or to bail funds.

+ +

Black Lives Matter.

+ +

Acknowledgments

+

This blog post incorporates suggestions and resources provided by Lina + Dahlberg and Maria Dahlberg.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Mark Tomko + + +
+ Mark is a senior software engineer at the Broad Institute of MIT and Harvard. He lives in Bellingham, Washington. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/custom-error-types.html b/blog/custom-error-types.html new file mode 100644 index 00000000..5243f795 --- /dev/null +++ b/blog/custom-error-types.html @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + Custom Error Types Using Cats Effect and MTL + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Custom Error Types Using Cats Effect and MTL

+ + + technical + +
+
+
+
+

Custom Error Types Using Cats Effect and MTL

+

tl;dr Cats MTL 1.6.0 introduces a brand new lightweight syntax for managing user-defined error types in the Cats ecosystem without requiring complex monad transformers.

+

One of the most famous and longstanding limitations of the Cats Effect IO type (and the Cats generic typeclasses) is the fact that the only available error channel is Throwable. This stands in contrast to bifunctor or polyfunctor techniques, which add a typed error channel within the monad itself. You can see this easily in type signatures: IO[String] indicates an IO which returns a String or may produce a Throwable error (Future[String] is directly analogous). Something like BIO[ParseError, String] would represent a BIO that produces a String or raises a ParseError. The latter type signature is more general than Throwable, since it allows for user-specified error types, and it's somewhat more explicit about where errors can and cannot occur.

+

In a meaningful sense, this type of bifunctor error encoding is analogous to checked exceptions in Java, whereas monofunctor error encoding (like Cats Effect's IO) is analogous to unchecked exceptions. Both are valid design decisions for an effect type, but they come with different benefits and tradeoffs.

+

Cats has long been quite prescriptive about monofunctor effects, in part because this considerably simplifies the compositional integration space. Libraries like Fs2, Http4s, Calico, and so many more are able to build on top of parametric effects (the famous F[_]) with a consistent understanding of what error channels are available and how they're going to behave. This has very subtle interactions with concurrent logic and resource handling, and by insisting on a monofunctor calculus, the Cats ecosystem is able to maintain very strong properties with relatively simple implementations in these areas.

+

However, the core problem of custom error types doesn't really go away. Parsing is a great example of this. For example, Circe has a ParsingFailure type which carries a specific JSON parse error message as well as some associated traceback context. While this type does happen to extend Exception, and thus can be raised within an IO, it's not necessarily right for it to do so. This is common, but arguably it's only common because of the prevalence of monofunctors.

+

A standard solution to this problem, if you don't want to extend Exception with your error types, is to simply return Either everywhere. Unfortunately, that results in a lot of type signatures which look like this:

+
def parse(input: String): IO[Either[Failure, Result]] = ???
+

And then of course, everything you do with that result must be explicitly flatMapped into the Either, and higher-order control flow libraries like Fs2 will often need some extra coaxing in order to make everything work the way you want it to. This gets old in a hurry, which often results in reaching for alternatives like EitherT. That way lies frustration and woe.

+ +

Capabilities

+

The good news is that we now have a better answer here, and one which composes very nicely with the existing (and future) ecosystem, maintains all relevant concurrency properties, and which type-infers extremely well, particularly in Scala 3. The answer has been to double down on the relatively little-used implicit capabilities library for Cats, known under the very misleading name of Cats MTL.

+

The name "Cats MTL" comes from Haskell's MTL package, which in turn was pretty aptly named: "Monad Transformer Library". Haskell's MTL is entirely oriented around making it easier and more ergonomic to manipulate monad transformer stacks, which is to say, multiple layers of datatypes like EitherT, Kleisli, and so on. Monad transformer stacks are extremely difficult to work with, both in Scala and in Haskell, and so over time people progressively evolved techniques involving typeclasses in Haskell and implicits in Scala to more ergonomically manipulate composable effect types. Cats MTL was rooted in an adaptation of some of these ideas.

+

Over time though, we've learned that monad transformer datatypes themselves are often too clunky and even unnecessary. They work well in a few contexts, most notably local scopes (i.e. within the body of a single method), but they're generally the wrong solution for the problem. Quite notably, while the Cats Effect concurrent typeclasses do work on monad transformer stacks and derive lawful results, the practical outcomes can be very unintuitive. For that reason, it's generally not advisable to use types like EitherT or IorT composed together with libraries like Fs2 or similar.

+

However, the basic idea of MTL itself, divorced from the datatypes (like EitherT), is actually a very good one. At its core, MTL is just about expressing capabilities available within a given scope using implicit evidence. Capabilities can be things like parallelism, resource safety, error handling, dependency injection, sequential composition, or similar. When done correctly, this can be a very powerful and lightweight way of expressing compositional effects with a high degree of granularity and type safety. It's not a coincidence that this is exactly the route being explored by many of the researchers working on Scala academically!

+ +

Scoped Error Capabilities

+

The problem has been to find a way to blend all of these constructs together in a way that practically works with the ecosystem, is syntactically lightweight, has pleasant type inference and errors, and doesn't confuse the heck out of anyone who touches it. That is a problem we feel we have now solved, at least with errors.

+
import cats.Monad
+import cats.effect.IO
+import cats.mtl.syntax.all.*
+import cats.mtl.{Handle, Raise}
+import cats.syntax.all.*
+
+// define a domain error type
+enum ParseError:
+  case UnclosedBracket
+  case MissingSemicolon
+  case Other(msg: String)
+
+// use that error type in some function
+def parse[F[_]](input: String)(using Raise[F, ParseError], Monad[F]): F[Result] =
+  // do some hardcore parsing
+  if missingBracket then
+    UnclosedBracket.raise[F, Result]
+  else if missingSemicolon then
+    MissingSemicolon.raise // we can rely on type inference and omit extra typings
+  else
+    result.pure[F]
+
+// use allow/rescue like try/catch to create scoped error handling
+val program: IO[Unit] = Handle.allow[ParseError]:
+  for
+    x <- parse[IO](inputX)
+    y <- parse(inputY)
+    _ <- IO.println(s"successfully parsed $x and $y")
+  yield ()
+.rescue:
+  case ParseError.UnclosedBracket =>
+    IO.println("you didn't close your brackets")
+  case ParseError.MissingSemicolon =>
+    IO.println("you missed your semicolons very much")
+  case ParseError.Other(msg) =>
+    IO.println(s"error: $msg")
+

There's a lot to unpack here! At the very beginning we define a custom error type, ParseError. This is just a domain error like any other, and you'll note that it doesn't extend Exception or Throwable or similar. Without Cats MTL, we would generally have to wrap this error up in Either in all our function's result types, if we wanted to use it (similar to what Circe does). In this case though, instead of adding the error to the result type, we added a using parameter to our parse function!

+

Specifically, what we're doing here when we say using Raise[F, ParseError] is that the parse method requires the ability to raise (but not handle!) errors of type ParseError. This is a bit like saying throws ParseError in Java, except it isn't an exception!

+

Later on, in the body of parse, we use this Raise capability to call the raise method, producing errors in failure cases. This is a bit like the throw keyword, but again with our own custom domain error type. Btw, if we had expanded our Monad[F] using into something like MonadError[F, Throwable] or, more aggressively, Async[F], we would have also had the ability to raise any error of type Throwable using the same syntax! In this case though, parse is only able to raise domain errors.

+

As an aside, the F[_] here could be instantiated with many different monadic types. While we're using IO in production, perhaps we would want to test this function using Either[ParseError, A] as our type. This is very much supported! And in fact, if you did this, the Raise would have been implicitly materialized by Cats MTL, since Either has an obvious implementation of that function.

+

Finally, at the end of the snippet above, we define program using the brand new syntax: allow/rescue. This is where things get very fancy. What we're doing here is we're introducing a new lexical scope (indented after the allow[ParseError]:) in which it is valid to raise an error of type ParseError. You should think of this as being very similar to try/catch, except it works with effect types like IO and any error type you define (not just Throwable). Within this scope, we write code as usual, and we're allowed to call the parse function. Note that if we had tried to call parse outside of this scope, it would have been a compile error informing us that we're missing the Raise capability.

+

At the end of the allow scope, we call .rescue, and this requires us to pass a function which handles any errors which could have been raised by the body of the allow. This works exactly like catch, except with your own domain error types. In this case, we are apparently just logging the existence of the errors and moving on with our life, because we do some printing and away we go, but you could imagine perhaps returning a custom HTTP error code, or triggering some fallback behavior, or really any other error handling logic.

+ +

Scala 2

+

Oh, and just in case you were wondering, this syntax does work on Scala 2 as well, it's just a bit less fancy! Here's the same snippet from above, but with 100% more braces and a lot more explicit types:

+
import cats.Monad
+import cats.effect.IO
+import cats.mtl.syntax.all._
+import cats.mtl.{Handle, Raise}
+import cats.syntax.all._
+
+// define a domain error type
+sealed trait ParseError extends Product with Serializable
+
+object ParseError {
+  case object UnclosedBracket extends ParseError
+  case object MissingSemicolon extends ParseError
+  case class Other(msg: String) extends ParseError
+}
+
+// use that error type in some function
+def parse[F[_]](input: String)(implicit r: Raise[F, ParseError], m: Monad[F]): F[Result] = {
+  // do some hardcore parsing
+  if (missingBracket)
+    UnclosedBracket.raise[F]
+  else if (missingSemicolon)
+    MissingSemicolon.raise[F]
+  else
+    result.pure[F]
+}
+
+// use allow/rescue like try/catch to create scoped error handling
+val program: IO[Unit] = Handle.allowF[IO, ParseError] { implicit h =>
+  for {
+    x <- parse[IO](inputX)
+    y <- parse[IO](inputY)
+    _ <- IO.println(s"successfully parsed $x and $y")
+  } yield ()
+} rescue {
+  case ParseError.UnclosedBracket =>
+    IO.println("you didn't close your brackets")
+  case ParseError.MissingSemicolon =>
+    IO.println("you missed your semicolons very much")
+  case ParseError.Other(msg) =>
+    IO.println(s"error: $msg")
+}
+

We need to do a lot more hand-holding for the compiler by using the allowF function instead of allow, but in general this is very much the same idea!

+ +

Under the Hood

+

Behind the scenes, this functionality is doing two very creative things. First, as the Scala 2 snippet hints, we're introducing a new implicit within the local scope of the function passed to allow/allowF. This is one of Scala's more unique features and we're leveraging it quite heavily. In Scala 3, we're able to hide this syntax entirely by using context functions (the A ?=> B syntax), but in Scala 2 we need to use the implicit x => lambda syntax in order to make this work.

+

That implicit is introduced targeting the effect type we passed to allowF, or in Scala 3's case, the type which was inferred from the return. In this case, that type is IO! In other words, you don't need to be using parametric effects (F[_]) in order to make all this work! Raise[IO, ParseError] is a totally valid Raise instance, and it's exactly what we have in scope here. Or rather, we actually have Handle[IO, ParseError] (which extends Raise), which gives us the ability to both raise and handle errors.

+

Once the scope is closed, syntactically, we force the user to supply an error handler to ensure that any errors which were raised and unhandled within the body are correctly managed. This is a pretty logical way of setting up your error handling, and precisely mirrors the way that you would do this same thing with a more imperative direct syntax like try/catch/throw/throws.

+

In the way way deep underdark of the implementation, this whole thing works at runtime by creating what we call a "submarine error". Specifically, we have a local traceless exception type called Submarine inside of the allow implementation which extends RuntimeException. When you raise a custom domain error (ParseError in this case), we use Submarine to "submerge" your error within the Throwable error channel of the enclosing effect – in this case, IO. Since we catch this error at the boundary, this whole process is entirely invisible to you unless you write something like handleErrorWith and catch all Throwable-typed errors within the scope, in which case you might see something of type Submarine. The correct thing to do with this error type, should you see it, depends considerably on exactly why you're writing handleErrorWith, and as it turns out this is exactly the whole point!

+

By implementing this functionality without extending the number of actual error channels within the effect type (either with a bifunctor or something like EitherT), we ensure that everything continues to compose correctly around all resource handling, structured and unstructured concurrency, and otherwise-oblivious generic library code which has no idea what your domain errors are or how they might behave. Even in the case of an explicit handleErrorWith, you might be adding that type of error handler because you're writing some logic which must make certain that there is no possible way to short-circuit without passing through your handler (e.g. perhaps you're trying to make sure that some critical resource is cleaned up), or alternatively you may just be trying to observe Throwable errors to log and re-raise them, or any number of other things you might be doing with the error channel that we don't have any insight into.

+

Rather than trying to impose a particular multi-channel composition semantic on your code, we simply stick with a single error channel with known and well-understood supremacy semantics, and everything else follows from there.

+ +

Conclusion

+

Hopefully you find this technique helpful! This has been in the works for a surprisingly long time (I think it was first suggested in the Typelevel Discord about two or three years ago), and it was Thanh Le (@lenguyenthanh) who ultimately pushed it over the line. Huge shoutout! He has already begun leveraging this functionality in Lichess, one of the larger production Scala projects: lichess-org/lila#17944

+

Even more excitingly, this is a bit of a taste of the next phase of the effect type ecosystem. Scala is continuing to move heavily in the direction of implicit capabilities for these types of behaviors, and while efforts such as Caprese are still a long way from bearing real-world fruit, much of the work that is being done in that direction also creates the primitives needed to encode a compositional capabilities ecosystem for our existing production effect types, such as Cats Effect IO!

+

Cats MTL will continue to evolve in this area, with an eye towards advancing the capabilities and improving syntax and ergonomics of this type of functionality both now and in the future.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + +
+ I write code, read papers, and think thoughts. Broadly, I'm interested in: type theory, parser theory, functional abstractions, data structures, performance. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/deriving-instances-1.html b/blog/deriving-instances-1.html new file mode 100644 index 00000000..93214b50 --- /dev/null +++ b/blog/deriving-instances-1.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Deriving Type Class Instances + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Deriving Type Class Instances

+ + + technical + +
+
+
+
+

Deriving Type Class Instances

+ +

Motivating example

+

Assume that you have a case class representing vectors in three-dimensional space:

+
case class Vector3D(x: Int, y: Int, z: Int)
+

Now you want to implement addition on this class. + Currently, you have to do that manually:

+
def +(that: Vector3D): Vector3D =
+  Vector3D(this.x + that.x, this.y + that.y, this.z + that.z)
+

If you are writing some code involving three-dimensional vectors, chances are that you also have to deal with two-dimensional ones:

+
case class Vector2D(x: Int, y: Int) {
+  def +(that: Vector2D): Vector2D =
+    Vector2D(this.x + that.x, this.y + that.y)
+}
+

Observe that the hand-written implementation of + is quite repetitive. + We want to avoid that sort of boilerplate code as much as possible.

+

In this post, we will introduce an abstraction over the addition operation, namely semigroups, + and introduce a macro-based facility which allows you to get the implementation of + for free. + In the end, the only thing you will have to write is this:

+
implicit val vector2DSemigroup = TypeClass[Semigroup, Vector2D]
+implicit val vector3DSemigroup = TypeClass[Semigroup, Vector3D]
+

That is still a little bit of boilerplate, right? How about:

+
import Semigroup.auto._
+

This will give you Semigroup instances for all of your data types – with zero boilerplate!

+

But first, let us introduce all the related concepts properly.

+ +

Abstracting all the things

+

If you are already familiar with type classes in general and algebraic structures in particular, you can safely skip this and the next section. + Keep in mind though that we are dealing with classes for types of kind * only. Type classes for * \rightarrow * are different and not supported.

+

Type classes are an incredibly useful abstraction mechanism, originally introduced in Haskell. + If you have been using some of the typelevel.scala libraries already, you probably know how type classes and their instances are represented in Scala: as traits and implicits. + In the following section, we will get started with an example type class from abstract algebra, which is implemented in spire.

+ +

Group theory

+

Group theory is a very important field of research in mathematics and has a very broad range of applications, especially in computer science. + One of the most fundamental structures is a semigroup, which consists of a set of elements equipped with one operation (often called append, mplus, or similarly; in textbooks you will often find \circ or \oplus ). + Additionally, the operation has to obey the law of associativity, meaning that for any three values s1,s2, s_1, s_2, and s3 s_3 , it does not matter if you append s1 s_1 and s2 s_2 first and then append s3 s_3 , or append s2 s_2 and s3 s_3 first and then append s1 s_1 and the result of that. + In other words, the precise order in which the steps of a larger operation are executed does not matter. + A good analogy here is when flattening a list: + On the surface, you just do not care if it proceeds by splitting the list recursively or if the concatenation is done sequentially by folding.

+
+

In fact, some list operations actually require associativity. From the Scaladoc of the fold method on Seq:

+
Folds the elements of this collection or iterator using the specified associative binary operator. + The order in which operations are performed on elements is unspecified and may be nondeterministic.
+

This allows a particular collection implementation to use whichever order is most efficient.

+
+

Lists are already a good example for a semigroup: Any List[T] is a semigroup, with the semigroup operation being list concatenation! + A Map[K, V] is a semigroup too, given that V is a semigroup. + The operation is just "merging" two maps, and if you have two duplicate keys, you can use the semigroup operation for V.

+

Enough examples. We can represent the concept of a semigroup in Scala using a trait:

+
trait Semigroup[S] {
+  def append(s1: S, s2: S): S
+}
+

Obviously, we can also implement a semigroup for base types like Int. An instance could look like this:

+
implicit val intInstance = new Semigroup[Int] {
+  def append(s1: Int, s2: Int) = s1 + s2
+}
+

In other words, we just use the built-in addition function.

+

If you want to know more about applications of abstract algebra in programming, especially in spire, head over to YouTube and watch an introduction by Tom Switzer.

+ +

Composing instances

+

Now suppose you are working with three-dimensional images. + Most likely, you will encounter a data structure for vectors (or points), which we recall from above:

+
case class Vector3D(x: Int, y: Int, z: Int)
+

And since you know your maths, you also know that vectors can be added, and that vector addition forms a semigroup! + Hence, a semigroup instance for Vector3D is the next logical step.

+
implicit val vectorInstance = new Semigroup[Vector3D] {
+  def append(u: Vector3D, v: Vector3D) =
+    Vector3D(u.x + v.x, u.y + v.y, u.z + v.z)
+}
+

Now, that was a bit tedious, right? We would love to have a way the compiler could write that instance for us. + (I mean, it already generates reasonable defaults for equals, hashCode and toString, so why not for that?)

+

In any case, you can see a pattern here: Each element of the case class is added separately. + Here, we could have even delegated the addition to our intInstance from above.

+

In essence, what we need is a way to combine smaller instances (e.g. for Int) into larger instances (e.g. for Vector3D consisting of three Ints). + Luckily, this is completely mechanic. As an exercise, try writing the following instance:

+
implicit def tupleInstance[A, B](implicit A: Semigroup[A], B: Semigroup[B]) =
+  new Semigroup[(A, B)] {
+    def append(t1: (A, B), t2: (A, B)): (A, B) = ???
+  }
+ +

Representing data types

+

Once we know how to produce an instance for a pair, we can apply that two times and obtain an instance for a triple. + However, there are still two problems here:

+
    +
  1. We would like an instance for Vector3D, but we have an instance for (Int, Int, Int).
  2. +
  3. This is still a lie. We actually have an instance for (Int, (Int, Int)).
  4. +
+

Let us address these problems now. The following sections assume familiarity with HLists, as implemented in shapeless.

+

If you are not familiar with HLists yet, + watch Miles Sabin's talk about shapeless at the Northeast Scala Symposium 2012. + There's also a blog series exploring type-level programming in general by Mark Harrah.

+

Now, we want to generate an instance for Vector3D and countless other data types. + That means that we cannot just special-case for every possible data type, but we have to abstract over them. + The trick is actually quite simple: + For the purposes of automatic instance derivation, we temporarily convert data types into a canonical representation using HLists, where each case class parameter corresponds to an element in the HList.

+

In our example, that representation is Int :: Int :: Int :: HNil. + Yes, that type is completely equivalent to Vector3D, and you can implement the conversion functions straightforwardly:

+
def to(vec: Vector3D): Int :: Int :: Int :: HNil =
+  vec.x :: vec.y :: vec.z :: HNil
+
+def from(hlist: Int :: Int :: Int :: HNil) =
+  ??? // fun exercise!
+

Because we are lazy, we let a macro automatically generate the to and from methods. + We will see in the second part of the series how that works. + For now, just assume that you can invoke some method, magic happens, and you get the conversions out.

+ +

Using the representation

+

At this point, we have a canonical representation for arbitrary case classes. + We will also assume that there are Semigroup instances for each of its elements. + Now we would like to combine those base instances into an instance for the representation. + We need two implicits for that:

+
implicit val nilInstance =
+  new Semigroup[HNil] {
+    def append(x: HNil, y: HNil) = HNil
+  }
+
+implicit def consInstance[H, T <: HList](implicit val H: Semigroup[H], T: Semigroup[T]) =
+  new Semigroup[H :: T] {
+    // actual implementation doesn't matter that much
+    def append(x: H :: T, y: H :: T) = ???
+  }
+

The key insight is that the compiler can come up with an instance for Int :: Int :: Int :: HNil, just because these two implicits are in scope.

+

Now we just need a way to get an instance for Vector3D.

+
def subst[A, B](to: A => B, from: B => A, instance: Semigroup[B]) =
+  new Semigroup[A] {
+    def append(a1: A, a2: A) =
+      from(instance.append(to(a1), to(a2))
+  }
+

Easy enough, right? + To get our much-wanted Semigroup[Vector3D], we ask the compiler to make an instance its HList representation, conjure the conversion functions and plug all that stuff into the subst machine. Voilà, done! + Add some teaspoons of macros, and we are able to write

+
Semigroup.derive[Vector3D]
+

Are we done yet? No. We can go even further.

+ +

Abstracting over type classes

+

Semigroup is not the only type class around. For example, there is a whole tower of classes from group theory for varying use cases. Then there are some type classes from scalaz:

+
    +
  • Show provides a way to convert a value to a String
  • +
  • Equal for type-safe equality
  • +
  • Order provides total ordering on values
  • +
+

... and many more!

+

Another key insight is that all of those classes are able to deal with HLists and also support the subst operation. + Hence, one could be tempted to write:

+
Show.derive[Vector3D]
+Equal.derive[Vector3D]
+// and more
+

I hate duplication, though. I do not want to implement the derive macro over and over again. + Now, if only there was a way to abstract over common functionality of types ...

+ +

A type class called "TypeClass"

+

"What," I hear you saying, "the TypeClass type class? You can't be serious!"

+

I am serious.

+

We use type classes to abstract over types. + Semigroup abstracts over types which offer some sort of addition functionality.

+

However, type classes are themselves just types in Scala. + Thus, we can use type classes to abstract over type classes. + We are defining a type class which abstracts over type classes whose instances can be combined to form larger instances.

+

So, without further ado:

+
trait TypeClass[C[_]] {
+  def nil: C[HNil]
+  def cons[H, T <: HList](H: C[H], T: C[T]): C[H :: T]
+  def subst[A, B](to: A => B, from: B => A, instance: C[B]): C[A]
+}
+

This should actually be not too surprising. We already know exactly how to implement TypeClass[Semigroup]. + If we put this implementation into the companion object of Semigroup, it will be available for the macro to use.

+ +

Wrapping it up

+

How can this actually be used? + The work can be roughly divided between three roles:

+
    +
  1. +

    The macro author, who has to implement all the nitty-gritty details of the derivation process.

    +

    That is already done and implemented in shapeless. + The upcoming 2.0.0 release will contain all the necessary bits and pieces, but requires at least Scala 2.10.2 (it will not work for 2.10.1 or earlier). + If you are brave, try the latest snapshot version which is available on Sonatype.

    +
  2. +
  3. +

    The library author, who defines type classes, fundamental instances thereof, and of course the necessary TypeClass instances.

    +

    These are usually contained in the libraries you use, but the last part will additionally require a bridge library. + But fear not, those bridge libraries already exist, at least for the typelevel.scala libraries, and can be readily added as dependency for your build. + Head over to the GitHub project, we will keep you posted for when a new version comes out. + We also plan to put a compatibility chart on this site.

    +
  4. +
  5. +

    The library user, who defines data types and wants to get instances without all the boilerplate.

    +

    This is the simplest task of all: All you have to do is to put

    +
    implicit val myInstance = TypeClass[Semigroup, Vector3D]
    +// or
    +import Semigroup.auto._
    +

    somewhere into your scope, and you are done!

    +

    Providing "explicit" implicit declarations for each type class instance provides the tightest control over your implicit scope and ensures you only have the instances that you want. + Importing auto reduces the boilerplate to the absolute minimum, which is often desirable, but might result in more instances being materialized than you expect. + Which to choose is partly a matter of taste and partly a function of the size and complexity of the scopes you are importing in to: + large or complex scopes might favour explicit declarations; tighter, simpler scopes might favour auto.

    +
  6. +
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/discipline.html b/blog/discipline.html new file mode 100644 index 00000000..99b9285a --- /dev/null +++ b/blog/discipline.html @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + Law Enforcement using Discipline + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Law Enforcement using Discipline

+ + + technical + +
+
+
+
+

Law Enforcement using Discipline

+

Some nine or ten months ago, Spire's project structure underwent a major reorganization. + Simultaneously, the Scalacheck bindings were refactored, completely overhauling the law-checking infrastructure.

+ +

Requirements

+

The main goal was to make it easy to check that instances of Spire's type classes adhere to the set of algebraic laws of the respective type classes. + Scalaz also has such an infrastructure, so why not take that one? + The problem is that in Spire, the hierarchy of type classes is a little bit more complex:

+

On the one hand, there is a "generic" tower of type classes including Semigroup, Monoid and the like, where each successive type extends its predecessor. + On the other hand, this tower is replicated twice for their "additive" and "multiplicative" counterparts. + These classes are isomorphic, up to the semantics, and hence naming of their operations.

+

This distinction is quite useful, because now one can write:

+
trait Semiring[A] extends AdditiveMonoid[A] with MultiplicativeSemigroup[A] {
+  // ...
+}
+

without clashes between the additive and multiplicative binary operations. + Also, a semiring can now be quite naturally treated as an additive monoid and a multiplicative semigroup (but not as a generic semigroup, which would be ambiguous). + (One could consider this the third hierarchy of algebraic type classes in spire.)

+

When checking laws, we do not want to repeat the same laws over and over again. + Hence, we need some way to express that certain type classes share laws with others which are not necessarily in the same type hierarchy.

+ +

Interface

+

The implementation fundamentally depends on Scalacheck. + To be more specific, it uses Prop as the elementary unit of testing.

+

Now, a set of named Props do not quite suffice as the "law" of a type class. + First, to avoid ambiguous naming, let us call the complete law of a type class (including dependencies), a "rule set".

+

To satisfy our requirement of having dependencies from (potentially) different hierarchies, we will distinguish parents and bases. + A parent is a rule set of a type class in the same hierachy, whereas a base can come from everywhere. + This distinction is expressed with the use of path-dependent types:

+
trait Laws {
+
+  trait RuleSet {
+    def name: String
+    def bases: Seq[(String, Laws#RuleSet)] = Seq()
+    def parents: Seq[RuleSet] = Seq()
+    def props: Seq[(String, Prop)] = Seq()
+
+    // ...
+  }
+
+}
+

As we can see, parents uses type RuleSet, which constrains parents to the same outer Laws instance. + In contrast, bases uses the type Laws#RuleSet which means that bases can come from other instances of Laws.

+

When you define type classes, the general idea is to define one instance of Laws for each hierarchy of type classes. + Coming back to the Spire example, that could look like this:

+
trait GroupLaws[A] {
+  def semigroup(implicit A: Semigroup[A]): RuleSet = new RuleSet {
+    def name = "semigroup"
+    def props = // ...
+  }
+
+  def monoid(implicit A: Monoid[A]): RuleSet = new RuleSet {
+    def name = "monoid"
+    def parents = Seq(semigroup)
+    def props = // ...
+  }
+}
+
+trait AdditiveLaws[A] {
+  def groupLaws: GroupLaws[A]
+
+  def semigroup(implicit A: AdditiveSemigroup[A]): RuleSet = new RuleSet {
+    def name = "additive semigroup"
+
+    // `.additive` converts an additive X to a generic X
+    def bases = Seq("additive"groupLaws.semigroup(A.additive))
+  }
+
+  def monoid(implicit A: AdditiveMonoid[A]): RuleSet = new RuleSet {
+    def name = "additive monoid"
+
+    def bases = Seq("additive"groupLaws.monoid(A.additive))
+    def parent = Seq(semigroup)
+  }
+}
+

This now clearly expresses the intention:

+
    +
  • A monoid is a semigroup.
  • +
  • An additive semigroup should satisfy the laws of a semigroup.
  • +
  • An additive monoid is an additive semigroup and should satisfy the laws of a monoid.
  • +
+

Note that in the definitions inside AdditiveLaws, no properties have been restated. + The system will automatically take care that all the properties of the parents and the bases are being checked.

+

Obviously, this is not very interesting yet, because so far it could have been achieved by other means. + If you are interested in more complex examples, check the sources of Spire: + There are a couple of examples where the additive and multiplicative versions have extra checks which are not covered by the generic version.

+ +

Implementation

+

Now, the question is how to compute the set of all properties which need to be checked. + A naïve algorithm would just recursively traverse all bases and parents, and check the union of all the property sets.

+

However, this leads to unnecessary work. + Consider the rule set of an additive monoid. + There, the properties of semigroup would be included twice: + once via the semigroup base of the additive semigroup parent, and once via the semigroup parent of the monoid base.

+

While checking properties twice certainly does no damage, we still do not want to pay for that overhead. + Hence, a slightly smarter algorithm is used. + We compute the set of all properties of a certain class by taking the union of these sets:

+
    +
  • the properties of the class itself
  • +
  • recursively, the properties of all its parents (ignoring their bases)
  • +
  • recursively, the set of all properties of its bases
  • +
+

In order to present the user a more transparent output, the names of the properties are hierarchical. + When a base is pulled in as dependency, their properties are additionally prefixed with the name of the base. + This should make it very easy to see where exactly a property came from.

+

There is a slight complication, though. + Recall the definition of a semiring in spire, which is given above. + A semiring actually consists of two different semigroups of which we must check the laws separately. + At this point, it is not immediately clear what would happen with the presented algorithm. + With just a minor clarification it turns out that this is not actually a problem: + The rule set of a semiring specifies two bases (one for the additive component and one for the multiplicative component), and we only need to make sure that they have different names. + Laws pulled in via different bases are considered different, and are hence not conflated.

+ +

Usage

+

Previously, this new law checking infrastructure was tailored to be used just in Spire. + Since it is useful outside of Spire, too, it has recently been generalized and pulled out into a separate project: Discipline.

+

In there, you can find a stripped-down example of the Spire use case.

+

Furthermore, there is integration with Specs2 and ScalaTest. + You just have to extend the specs2.Discipline (or scalatest.Discipline, respectively) trait, and write

+
checkAll("Int", RingLaws[Int].ring /* put your own `RuleSet` here */)
+

and rule sets are expanded and turned into individual tests automatically. + For a Specs2-based tests, this will result in the following output (similar for ScalaTest):

+
[info] ring laws must hold for Int
+[info]
+[info]  + ring.additive:group.base:group.associative
+[info]  + ring.additive:group.base:group.identity
+[info]  + ring.additive:group.base:group.inverse
+[info]  + ring.multiplicative:monoid.base:monoid.associative
+[info]  + ring.multiplicative:monoid.base:monoid.identity
+[info]  + ring.distributive
+

Observe that the associativity law for semigroups shows up twice (additive and multiplicative), but not four times (as would have happened with the naïve algorithm).

+

In the future, we will investigate whether Scalaz can also be migrated towards Discipline, for a more unified approach to law checking.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/discord-migration.html b/blog/discord-migration.html new file mode 100644 index 00000000..39e77cbe --- /dev/null +++ b/blog/discord-migration.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + Discord Migration + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Discord Migration

+ + + social + +
+
+
+
+

Discord Migration

+

Hello Community!

+

We have a new Typelevel discord server.

+

There is a large and growing community of Scala developers on Discord.

+

Gitter has struggled as a platform for Typelevel for many reasons. We have received feedback that the platform is hard to approach, + and that the sense of community is fractured by being in separate rooms, and difficult for beginners since you have to know where to get started. + Additionally, mobile support has completely deteriorated throughout the years. + The only users who remain satisfied unconditionally are perhaps those who are using bridges.

+

Discord is a modern platform with solid support across all devices, has + best-in-class moderation tools both personally and administratively, and builds the Typelevel community as a whole rather than per repo silos. + It additionally can support new types of engagement like pairing, voice chat, presentations, and casting. + We hope Discord will help allow us to build even better spaces.

+

Discord has some disadvantages compared to Gitter: it's not indexed by Google, and you must have an account to read. + We hope the advantages outweigh the disadvantages, + and we also hope that other tools like Github Discussions and FAQ's/project docs can be a home for permanent documentation.

+

We encourage Typelevel projects to create channels there and to give it a try for a few months and see how it goes.

+ +

We hope you'll join us over on the new Discord - https://discord.gg/dXWPjcKv2A

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/edsls-part-1.html b/blog/edsls-part-1.html new file mode 100644 index 00000000..d9a106ff --- /dev/null +++ b/blog/edsls-part-1.html @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + It's programs all the way down + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

It's programs all the way down

+ + + technical + +
+
+
+
+

It's programs all the way down

+

This is the first of a series of articles on "Monadic EDSLs in Scala."

+

Embedded domain specific languages (EDSLs) are a powerful tool for + abstracting complexities such as effects and business logic from our + programs. Instead of mixing ad-hoc error handling, database access, and web + calls through our code, we isolate each domain into a little language. These + little languages can then be used to write "mini-programs" describing, for + example, how to create a web page for a user.

+

Our program then becomes a composition of mini-programs, and running our + program becomes interpreting these mini-programs into actions. This is + analogous to running an interpreter, itself a program, which turns code + into actions.

+

The following illustrates what an EDSL might look like in Scala.

+
// An embedded program for fetching data for a user
+def process(id: UserId): Program[Page] = for {
+  bio  <- getBio(id)
+  feed <- getFeed(id)
+  page <- createPage(bio, feed)
+} yield page
+
+def interpretProgram[A](page: Program[A]): IO[A] = page.interpret {
+  case GetBio(id)            => ...
+  case GetFeed(id)           => ...
+  case CreatePage(bio, feed) => ...
+}
+

Here process defines a program in our embedded language. + No action has actually been performed yet, that happens when it gets + interpreted by interpretProgram and run at runtime.

+

In many situations a program in one EDSL is translated into another EDSL, + much like a compiler (again another program).

+
// Translate each term of the program into a database call
+def compile[A](program: Program[A]): Database[A] = program.interpret {
+  case GetBio(id)            => ...
+  case GetFeed(id)           => ...
+  case CreatePage(bio, feed) => ...
+}
+
+def interpretDatabase(db: Database[A]): IO[A] = db.interpret { ... }
+

Sometimes you can even optimize programs in an EDSL, much like an optimizing + compiler. In the above example, interpretDatabase could deduplicate identical + requests and batch requests to the same table.

+

In this series of articles we will explore a couple approaches to embedding + such DSLs in Scala. These techniques will be evaluated against the following + axes:

+
    +
  • +

    Abstraction: Separation of structure from interpretation. Programs + describe only the structure of a computation, to be interpreted later on. + A common use case is to have a live interpreter that queries databases and + API endpoints and a test interpreter that works with in-memory stores.

    +
  • +
  • +

    Composition: Given two or more EDSLs, how simple is it to compose them? + Given EDSLs for database access and RPC, can we query for data and send + it over the wire while maintaining the abstraction requirement?

    +
  • +
  • +

    Performance: At the end of the day we must run our programs and therefore + interpret our mini-programs. How EDSLs are encoded will affect + how they perform and therefore affect any downstream consumers of our + programs, be it other programs or end users.

    +
  • +
+

In the next post we'll take a look + at the first of these approaches.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/edsls-part-2.html b/blog/edsls-part-2.html new file mode 100644 index 00000000..b721e630 --- /dev/null +++ b/blog/edsls-part-2.html @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + EDSLs as functions + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

EDSLs as functions

+ + + technical + +
+
+
+
+

EDSLs as functions

+

This is the second of a series of articles on "Monadic EDSLs in Scala."

+

Perhaps the most direct way to start writing an EDSL is to start writing + functions. Let's say we want a language for talking about sets of integers.

+
trait SetLang {
+  def add(i: Int, set: Set[Int]): Set[Int]
+  def remove(i: Int, set: Set[Int]): Set[Int]
+  def exists(i: Int, set: Set[Int]): Boolean
+}
+

This works... to the extent that we want only to work with + scala.collection.Sets. As it stands we cannot talk about + other sets such as bloom filters or sets controlled by other threads. + Our language isn't abstract enough, so let's remove + all traces of Set.

+
trait SetLang[F[_]] {
+  def add(i: Int, set: F[Int]): F[Int]
+  def remove(i: Int, set: F[Int]): F[Int]
+  def exists(i: Int, set: F[Int]): Boolean
+
+  // Given unknown F we no longer know how to create an empty set
+  // so we add the capability to our language
+  def empty: F[Int]
+}
+

We've parameterized our language with a higher-kinded type which + represents the context of our set. A similar parameterization could be + done with a *-kinded type (e.g. SetLang[A]) but since this series + focuses on monadic EDSLs, the choice is made for us.

+

Now we can write mini-programs which talk about some abstract set + yet to be determined.

+
def program[F[_]](lang: SetLang[F]): Boolean = {
+  import lang._
+  exists(10, remove(5, add(10, add(5, empty))))
+}
+

Interpretation of our program is done by implementing SetLang and + passing an instance into program.

+

However, our language is still not abstract enough. Replacing Set + with F allows us to swap in implementations of sets, but doesn't + allow us to talk about the context. Consider the behavior of exists if F + represents some remote set. Since exists returns a Boolean, + checking membership must be a synchronous operation despite the set living + on another node.

+

It's also tedious to thread the set through each method manually.

+

We can solve both problems by generalizing the use of F to some + context that is able to read and write to some set + (think Set[Int] => (Set[Int], A)).

+
trait SetLang[F[_]] {
+  def add(i: Int): F[Unit]
+  def remove(i: Int): F[Unit]
+  def exists(i: Int): F[Boolean]
+
+  // No longer need `empty` since the "context" has it already
+}
+

SetLang can now talk about the effects around interpretation, such as + asynchronity.

+
import scala.concurrent.Future
+
+type AsyncSet[A] = Set[Int] => Future[(Set[Int], A)]
+
+object AsyncSet extends SetLang[AsyncSet] {
+  def add(i: Int): Set[Int] => Future[(Set[Int], Unit)] = ???
+
+  def remove(i: Int): Set[Int] => Future[(Set[Int], Unit)] = ???
+
+  def exists(i: Int): Set[Int] => Future[(Set[Int], Boolean)] = ???
+}
+

This new encoding introduces a new but important problem: how do we + combine the results of multiple calls to SetLang methods? In the previous + encoding we could add and remove by threading the set from one call to + the next. With this change to represent a context, it's not clear how to do + that.

+

Fortunately we are now in a position to leverage a powerful tool: + monads. By extending our set language to be monadic + we recover composition in an elegant way. The Cats library is used + for demonstration purposes, but the discussion applies equally to + Scalaz.

+
import cats.Monad
+import cats.implicits._
+
+trait SetLang[F[_]] {
+  // See: subtype-typeclasses.md
+  // for why the `Monad` instance is defined as a member as opposed to inherited
+  def monad: Monad[F]
+
+  def add(i: Int): F[Unit]
+  def remove(i: Int): F[Unit]
+  def exists(i: Int): F[Boolean]
+}
+
+def program[F[_]](lang: SetLang[F]): F[Boolean] = {
+  import lang._
+  implicit val monadInstance = monad
+  for {
+    _ <- add(5)
+    _ <- add(10)
+    _ <- remove(5)
+    b <- exists(10)
+  } yield b
+}
+

Defining an interpreter starts by identifying a target context. Since the context + computes values while updating state, this suggests the state monad.

+
import cats.data.State
+
+object ScalaSet extends SetLang[State[Set[Int], ?]] {
+  val monad = Monad[State[Set[Int], ?]]
+
+  def add(i: Int): State[Set[Int], Unit] =
+    State.modify(_ + i)
+
+  def remove(i: Int): State[Set[Int], Unit] =
+    State.modify(_ - i)
+
+  def exists(i: Int): State[Set[Int], Boolean] =
+    State.inspect(_(i))
+}
+
val state = program[State[Set[Int], ?]](ScalaSet)
+// state: cats.data.StateT[cats.Eval,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@ce9f626
+
+state.run(Set.empty).value
+// res5: (scala.collection.immutable.Set[Int], Boolean) = (Set(10),true)
+

Note that calling program did not require any context-specific knowledge - + we could define another interpreter, perhaps one that talks to a set + concurrently.

+
import cats.data.StateT
+import scala.concurrent.{ExecutionContext, Future}
+
+// Asynchronous state
+def AsyncSet(implicit ec: ExecutionContext): SetLang[StateT[Future, Set[Int], ?]] =
+  new SetLang[StateT[Future, Set[Int], ?]] {
+    val monad = Monad[StateT[Future, Set[Int], ?]]
+
+    def add(i: Int): StateT[Future, Set[Int], Unit] =
+      StateT.modify(_ + i)
+
+    def remove(i: Int): StateT[Future, Set[Int], Unit] =
+      StateT.modify(_ - i)
+
+    def exists(i: Int): StateT[Future, Set[Int], Boolean] =
+      StateT.inspect(_(i))
+  }
+
// No changes to `program` required
+val result = program(AsyncSet(ExecutionContext.global))
+// result: cats.data.StateT[scala.concurrent.Future,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@1c029382
+

SetLang captures the structure of a computation, but leaves open + its interpretation.

+ +

Monad transformers and classes

+

As it turns out, SetLang is an example of an encoding often referred to as + MTL-style.

+ +

Monads in monads

+

Among the motivations for monad classes is to remove the need to specify + monad transformer stacks. The following example is adapted from + Functional Programming with Overloading and Higher-Order Polymorphism + by Professor Mark P. Jones.

+

Consider a program that is open to failure and computes with some state. This + suggests a combinator of Either and State, both of which have + monad transformers. All that is left is to decide which transformer to use.

+
type App1[A] = EitherT[State[S, ?], Error, A]
+            // State[S, Either[Error, A]]
+            // S => (S, Either[Error, A])
+
+type App2[A] = StateT[Either[Error, ?], S, A]
+            // S => Either[Error, (S, A)]
+

While App1 and App2 are both valid compositions, the + semantics of the compositions differ. App1 describes a program where + the computation of a value at each transition may fail - but any changes + are preserved - whereas App2 describes a program where the entire + transition may fail.

+

We can abstract away the difference by creating a type class which provides + the relevant operations we need.

+
trait MonadError[F[_], E] {
+  def monad: Monad[F]
+
+  def raiseError[A](e: E): F[A]
+  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]
+}
+
+trait MonadState[F[_], S] {
+  def monad: Monad[F]
+
+  def get: F[S]
+  def set(s: S): F[Unit]
+}
+

Similar type classes exist for the Reader and Writer data types. + These type classes are provided in both Cats and Scalaz, + with some caveats.

+

With these type classes in place we can write functions against these as + opposed to specific transformer stacks. Furthermore our functions can specify + exactly what operations they need which helps correctness and + parametricity.

+
import cats.{MonadError, MonadState}
+import cats.data.{EitherT, State, StateT}
+
+def program[F[_]](implicit F0: MonadError[F, String],
+                           F1: MonadState[F, Int]): F[Int] =
+  F0.flatMap(F1.get) { i =>
+    F0.raiseError[Int]("fail")
+  }
+

Our program can then be instantiated with either transformer stack.

+
import cats.implicits._
+
+// At the time of this writing Cats does not have these instances
+// so they are defined here.
+//
+// Additionally, both Cats and Scalaz 7 have encoding issues
+// with these MTL type classes which requires us to redefine Monad when
+// defining MonadState instances, despite there already being one.
+implicit def eitherTMonadState[F[_], E, S](implicit F: MonadState[F, S]): MonadState[EitherT[F, E, ?], S] =
+  new MonadState[EitherT[F, E, ?], S] {
+    def get: EitherT[F, E, S] =
+      EitherT(F.get.map(Right(_)))
+
+    def set(s: S): EitherT[F, E, Unit] =
+      EitherT(F.set(s).map(Right(_)))
+
+    def flatMap[A, B](fa: EitherT[F, E, A])
+                     (f: A => EitherT[F, E, B]): EitherT[F, E, B] =
+      fa.flatMap(f)
+
+    def pure[A](x: A): EitherT[F, E, A] =
+      EitherT.pure(x)
+
+    def tailRecM[A, B](a: A)(f: A => EitherT[F, E, Either[A, B]]): EitherT[F, E, B] =
+      EitherT.catsDataMonadErrorForEitherT[F, E].tailRecM(a)(f)
+  }
+
+implicit def stateTMonadError[F[_], E, S](implicit F: MonadError[F, E]): MonadError[StateT[F, S, ?], E] =
+  new MonadError[StateT[F, S, ?], E] {
+    def handleErrorWith[A](fa: StateT[F, S, A])(f: E => StateT[F, S, A]): StateT[F, S, A] =
+      StateT[F, S, A] { (s: S) =>
+        val state: F[(S, A)] = fa.run(s)
+        F.handleErrorWith(state)(e => f(e).run(s))
+      }
+
+    def raiseError[A](e: E): StateT[F, S, A] =
+      StateT.lift(F.raiseError(e))
+
+    def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] =
+      fa.flatMap(f)
+
+    def pure[A](x: A): StateT[F, S, A] = StateT.pure(x)
+
+    def tailRecM[A, B](a: A)(f: A => StateT[F, S, Either[A, B]]): StateT[F, S, B] =
+      StateT.catsDataMonadStateForStateT[F, S].tailRecM(a)(f)
+  }
+
+type App1[A] = EitherT[State[Int, ?], String, A]
+
+type App2[A] = StateT[Either[String, ?], Int, A]
+
val app1 = program[App1]
+// app1: App1[Int] = EitherT(cats.data.StateT@5fdc056d)
+
+val app2 = program[App2]
+// app2: App2[Int] = cats.data.StateT@72493a33
+ +

Composing languages

+

From one angle we can view our set language, or more generally any EDSL + in MTL-style, as an effect like MonadError and MonadState. From another + angle we can view MonadError and MonadState as EDSLs that talk about errors + and stateful computations. We can eliminate the distinctions by renaming + SetLang to MonadSet and treating it as a type class.

+
import cats.Monad
+import cats.implicits._
+
+trait MonadSet[F[_]] {
+  def monad: Monad[F]
+
+  def add(i: Int): F[Unit]
+  def remove(i: Int): F[Unit]
+  def exists(i: Int): F[Boolean]
+}
+

Composing multiple languages then becomes adding constraints to functions, and + interpretation becomes instantiating type parameters that satisfy the + constraints.

+
trait MonadCalc[F[_]] {
+  def monad: Monad[F]
+
+  def lit(i: Int): F[Int]
+  def plus(l: F[Int], r: F[Int]): F[Int]
+}
+
+def setProgram[F[_]: MonadSet](i: Int): F[Boolean] =
+  implicitly[MonadSet[F]].exists(i)
+
+def calcProgram[F[_]: MonadCalc]: F[Int] = {
+  val calc = implicitly[MonadCalc[F]]
+  calc.plus(calc.lit(1), calc.lit(2))
+}
+
+def composedProgram[F[_]: MonadCalc: MonadSet]: F[Boolean] = {
+  implicit val monad: Monad[F] = implicitly[MonadCalc[F]].monad
+  for {
+    i <- calcProgram[F]
+    b <- setProgram(i)
+  } yield b
+}
+
+// Instance
+
+// Instances are defined together but nothing is stopping us from defining
+// these separately, perhaps one in the MonadSet object and another in the
+// SetState object.
+implicit val stateInstance: MonadSet[State[Set[Int], ?]] with MonadCalc[State[Set[Int], ?]] =
+  new MonadSet[State[Set[Int], ?]] with MonadCalc[State[Set[Int], ?]] {
+    val monad = Monad[State[Set[Int], ?]]
+
+    def add(i: Int): State[Set[Int], Unit] = State.modify(_ + i)
+
+    def remove(i: Int): State[Set[Int], Unit] = State.modify(_ - i)
+
+    def exists(i: Int): State[Set[Int], Boolean] = State.inspect(_(i))
+
+    def lit(i: Int): State[Set[Int], Int] = State.pure(i)
+    def plus(l: State[Set[Int], Int], r: State[Set[Int], Int]): State[Set[Int], Int] =
+      (l |@| r).map(_ + _)
+  }
+
val result = composedProgram[State[Set[Int], ?]].run(Set.empty[Int]).value
+// result: (scala.collection.immutable.Set[Int], Boolean) = (Set(),false)
+

As before, composedProgram, calcProgram, and setProgram are defined + independent of interpretation, so alternative interpretations simply require + defining appropriate instances.

+ +

A note about laws

+

Type classes should come with laws - this lets us give meaning to their use. + The Monoid type class requires data types to have an associative binary + operation and a corresponding identity element. These laws allow us to + parallelize batch operations, such as partitioning a List[A] into + multiple chunks to be scattered across threads or machines and gathered + back.

+

Since our EDSLs are type classes, we should think about what laws we expect + to hold. Below are some possible candidates for laws:

+
// MonadSet
+set *> add(i)    *> remove(i) = set
+set *> remove(i) *> exists(i) = false
+set *> add(i)    *> exists(i) = true
+
+// MonadCalc - these are just the Monoid laws
+plus(lit(0), x) = plus(x, lit(0)) = x
+plus(x, plus(y, z)) = plus(plus(x, y), z)
+

Next up we'll take a look at some pitfalls of this approach, and a modified + encoding that solves some of them.

+

This article was tested with Scala 2.11.8, Cats 0.7.2, kind-projector 0.9.0, + and si2712fix-plugin 1.2.0 using tut.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/equivalence-vs-equality.html b/blog/equivalence-vs-equality.html new file mode 100644 index 00000000..0f29f79a --- /dev/null +++ b/blog/equivalence-vs-equality.html @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Equivalence versus Equality + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Equivalence versus Equality

+ + + technical + +
+
+
+
+

Equivalence versus Equality

+

This is a guest post by Tomas Mikula. It was initially published as a document in the hasheq. It has been slightly edited and is being republished here with the permission of the original author.

+

This article describes what we mean when we say that the data structures in this library are equivalence-aware in a type-safe fashion.

+ +

Equivalence

+

Set is a data structure that doesn't contain duplicate elements. An implementation of Set must therefore have a way to compare elements for "sameness". + A useful notion of sameness is equivalence, i.e. a binary relation that is reflexive, symmetric and transitive. + Any reasonable implementation of Set is equipped with some equivalence relation on its element type.

+

Here's the catch: For any type with more than one inhabitant there are multiple valid equivalence relations. + We cannot (in general) pick one that is suitable in all contexts. + For example, are these two binary trees same?

+
  +            +
+ / \          / \
+1   +        +   3
+   / \      / \
+  2   3    1   2
+

It depends on the context. They clearly have different structure, but they are both binary search trees containing the same elements. + For a balancing algorithm, they are different trees, but as an implementation of Set, they represent the same set of integers.

+ +

Equality

+

Despite the non-uniqueness, there is one equivalence relation that stands out: equality. + Two objects are considered equal when they are indistinguishable to an observer. + Formally, equality is required to have the substitution property:

+ a,bA,f(AB):a=Ab    f(a)=Bf(b)\forall a,b \in A, \forall f \in (A \to B): a=_A b \implies f(a)=_B f(b) + +

(Here, =A =_A denotes equality on A A , =B =_B denotes equality on B B .)

+

Equality is the finest equivalence: whenever two elements are equal, they are necessarily equivalent with respect to every equivalence.

+ +

Choices in libraries

+

Popular Scala libraries take one of these two approaches when dealing with comparing elements for "sameness".

+

The current approach of cats is equality. + Instances of the cats.Eq[A] typeclass are required to have all the properties of equality, including the substitution property above. + The problem with this approach is that for some types, such as Set[Int], equality is too strict to be useful: + Are values Set(1, 2) and Set(2, 1) equal? + For that to be true, they have to be indistinguishable by any function. + Let's try (_.toList):

+
scala> Set(1, 2).toList == Set(2, 1).toList
+res0: Boolean = false
+

So, Set(1, 2) and Set(2, 1) are clearly not equal. + As a result, we cannot use Set[Int] in a context where equality is required (without cheating).

+

On the other hand, scalaz uses unspecified equivalence. + Although the name scalaz.Equal[A] might suggest equality, instances of this typeclass are only tested for properties of equivalence. + As mentioned above, there are multiple valid equivalence relations for virtually any type. + When there are also multiple useful equivalences for a type, we are at risk of mixing them up (and the fact that they are usually resolved as implicit arguments only makes things worse).

+ +

Equivalence-aware sets (a.k.a. setoids)

+

Let's look at how we deal with this issue. We define typeclass Equiv with an extra type parameter that serves as a "tag" identifying the meaning of the equivalence.

+
trait Equiv[A, Eq] {
+  def equiv(a: A, b: A): Boolean
+}
+// defined trait Equiv
+

For the compiler, the "tag" is an opaque type. It only has specific meaning for humans. The only meaning it has for the compiler is that different tags represent (intensionally) different equivalence relations.

+

An equivalence-aware data structure then carries in its type the tag of the equivalence it uses.

+
import hasheq._
+// import hasheq._
+
+import hasheq.immutable._
+// import hasheq.immutable._
+
+import hasheq.std.int._
+// import hasheq.std.int._
+
scala> HashSet(1, 2, 3, 4, 5)
+res0: hasheq.immutable.HashSet[Int] = HashSetoid(5, 1, 2, 3, 4)
+

What on earth is HashSetoid? + A setoid is an equivalence-aware set. + HashSetoid is then just a setoid implementated using hash-table. + Let's look at the definition of HashSet:

+
type HashSet[A] = HashSetoid[A, Equality.type]
+

So HashSet is just a HashSetoid whose equivalence is equality. + To create an instance of HashSet[Int] above, we needed to have an implicit instance of Equiv[Int, Equality.type] in scope.

+
implicitly[Equiv[Int, Equality.type]]
+

For the compiler, Equality is just a rather arbitrary singleton object. + It only has the meaning of mathematical equality for us, humans.

+

There is a convenient type alias provided for equality relation:

+
type Equal[A] = Equiv[A, Equality.type]
+
implicitly[Equal[Int]]
+

So how do we deal with the problem of set equality mentioned above, i.e. that HashSet(1, 2) and HashSet(2, 1) are not truly equal? + We just don't provide a definition of equality for HashSet[Int].

+
scala> implicitly[Equal[HashSet[Int]]]
+<console>:22: error: could not find implicit value for parameter e: hasheq.Equal[hasheq.immutable.HashSet[Int]]
+       implicitly[Equal[HashSet[Int]]]
+                 ^
+

But that means we cannot have a HashSet[HashSet[Int]]! + (Remember, for a HashSet[A], we need an instance of Equal[A], and we just showed we don't have an instance of Equal[HashSet[Int]].)

+
scala> HashSet(HashSet(1, 2, 3, 4, 5))
+<console>:22: error: could not find implicit value for parameter A: hasheq.Hash[hasheq.immutable.HashSet[Int]]
+       HashSet(HashSet(1, 2, 3, 4, 5))
+              ^
+

But we can have a HashSetoid[HashSet[Int], E], where E is some equivalence on HashSet[Int].

+
scala> HashSet.of(HashSet(1, 2, 3, 4, 5))
+res5: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))
+

HashSet.of(elems) is like HashSet(elems), except it tries to infer the equivalence on the element type, instead of requiring it to be equality.

+

Notice the equivalence tag: Setoid.ContentEquiv[Int, Equality.type]. + Its meaning is (again, for humans only) that two setoids are equivalent when they contain the same elements (here, of type Int), as compared by the given equivalence of elements (here, Equality).

+

The remaining question is: How does this work in the presence of multiple useful equivalences?

+

Let's define another equivalence on Int (in addition to the provided equality).

+
// Our "tag" for equivalence modulo 10.
+// This trait will never be instantiated.
+sealed trait Mod10
+
+// Provide equivalence tagged by Mod10.
+implicit object EqMod10 extends Equiv[Int, Mod10] {
+  def mod10(i: Int): Int = {
+    val r = i % 10
+    if (r < 0) r + 10
+    else r
+  }
+  def equiv(a: Int, b: Int): Boolean = mod10(a) == mod10(b)
+}
+
+// Provide hash function compatible with equivalence modulo 10.
+// Note that the HashEq typeclass is also tagged by Mod10.
+implicit object HashMod10 extends HashEq[Int, Mod10] {
+  def hash(a: Int): Int = EqMod10.mod10(a)
+}
+

Now let's create a "setoid of sets of integers", as before.

+
scala> HashSet.of(HashSet(1, 2, 3, 4, 5))
+res13: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))
+

This still works, because HashSet requires an equality on Int, and there is only one in the implicit scope (the newly defined equivalence EqMod10 is not equality). + Let's try to create a "setoid of setoids of integers":

+
scala> HashSet.of(HashSet.of(1, 2, 3, 4, 5))
+<console>:24: error: ambiguous implicit values:
+ both method hashInstance in object int of type => hasheq.Hash[Int]
+ and object HashMod10 of type HashMod10.type
+ match expected type hasheq.HashEq[Int,Eq]
+       HashSet.of(HashSet.of(1, 2, 3, 4, 5))
+                            ^
+

This fails, because there are now more equivalences on Int in scope. + (There are now also multiple hash functions, which is what the error message actually says.) + We need to be more specific:

+
scala> HashSet.of(HashSet.of[Int, Mod10](1, 2, 3, 4, 5))
+res15: hasheq.immutable.HashSetoid[hasheq.immutable.HashSetoid[Int,Mod10],hasheq.immutable.Setoid.ContentEquiv[Int,Mod10]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))
+

Finally, does it prevent mixing up equivalences? Let's see:

+
scala> val s1 = HashSet(1,  2,  3,         11, 12, 13    )
+s1: hasheq.immutable.HashSet[Int] = HashSetoid(1, 13, 2, 12, 3, 11)
+
+scala> val s2 = HashSet(    2,  3,  4,  5,         13, 14)
+s2: hasheq.immutable.HashSet[Int] = HashSetoid(5, 14, 13, 2, 3, 4)
+
+scala> val t1 = HashSet.of[Int, Mod10](1,  2,  3,         11, 12, 13    )
+t1: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(1, 2, 3)
+
+scala> val t2 = HashSet.of[Int, Mod10](    2,  3,  4,  5,         13, 14)
+t2: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 2, 3, 4)
+

Combining compatible setoids:

+
scala> s1 union s2
+res16: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type] = HashSetoid(5, 14, 1, 13, 2, 12, 3, 11, 4)
+
+scala> t1 union t2
+res17: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 1, 2, 3, 4)
+

Combining incompatible setoids:

+
scala> s1 union t2
+<console>:26: error: type mismatch;
+ found   : hasheq.immutable.HashSetoid[Int,Mod10]
+ required: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type]
+       s1 union t2
+                ^
+
+scala> t1 union s2
+<console>:26: error: type mismatch;
+ found   : hasheq.immutable.HashSet[Int]
+    (which expands to)  hasheq.immutable.HashSetoid[Int,hasheq.Equality.type]
+ required: hasheq.immutable.HashSetoid[Int,Mod10]
+       t1 union s2
+                ^
+ +

Conclusion

+

We went one step further in the direction of type-safe equivalence in Scala compared to what is typically seen out in the wild today. + There is nothing very sophisticated about this encoding. + I think the major win is that we can design APIs so that the extra type parameter (the "equivalence tag") stays unnoticed by the user of the API as long as they only deal with equalities. + As soon as the equivalence tag starts requesting our attention (via an ambiguous implicit or a type error), it is likely that the attention is justified.

+

This article was tested with Scala 2.11.8 and hasheq version 0.3.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Tomas Mikula + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/error-handling.html b/blog/error-handling.html new file mode 100644 index 00000000..6df5b817 --- /dev/null +++ b/blog/error-handling.html @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + How do I error handle thee? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

How do I error handle thee?

+ + + technical + +
+
+
+
+

How do I error handle thee?

+

Scala has several ways to deal with error handling, and often times people + get confused as to when to use what. This post hopes to address that.

+

Let me count the ways.

+ +

Option

+

People coming to Scala from Java-like languages are often told Option is + a replacement for null or exception throwing. Say we have a function that + creates some sort of interval, but only allows intervals where the lower bound + comes first.

+
class Interval(val low: Int, val high: Int) {
+  if (low > high)
+    throw new Exception("Lower bound must be smaller than upper bound!")
+}
+

Here we want to create an Interval, but we want to ensure that the lower bound + is smaller than the upper bound. If it isn't, we throw an exception. The idea here + is to have some sort of "guarantee" that if at any point I'm given an Interval, + the lower bound is smaller than the upper bound (otherwise an exception would have + been thrown).

+

However, throwing exceptions breaks our ability to reason about a function/program. + Control is handed off to the call site, and we hope the call site catches it – if not, + it propagates further up until at some point something catches it, or our program + crashes. We'd like something a bit cleaner than that.

+

Enter Option – given our Interval constructor, construction may or may not succeed. + Put another way, after we enter the constructor, we may or may not have a valid + Interval. Option is a type that represents a value that may or may not be there; + it can either be Some or None. Let's use what's called a smart constructor.

+
final class Interval private(val low: Int, val high: Int)
+
+object Interval {
+  def apply(low: Int, high: Int): Option[Interval] =
+    if (low <= high) Some(new Interval(low, high))
+    else None
+}
+

We make our class final so nothing can inherit from it, and we make our constructor + private so nobody can create an instance of Interval without going through our own + smart constructor function, Interval.apply. Our apply function takes some relevant + parameters, and returns an Option[Interval] that may or may not contain our constructed + Interval. Our function does not arbitrarily kick control back to the call site due + to an exception and we can reason about it much more easily.

+ +

Either and scalaz.\/

+

So, Option gives us Some or None, which is all we need if there is only one thing + that could go wrong. For instance, the standard library's Map[K, V] has a function get + that given a key of type K, returns Option[V] – clearly if the key exists, the associated + value is returned (wrapped in a Some). If the key does not exist, it returns a None.

+

But sometimes one of several things can go wrong. Let's say we have some wonky type that + wants a string that is exactly of length 5 and another string that is a palindrome.

+
final class Wonky private(five: String, palindrome: String)
+
+object Wonky {
+  def validate(five: String, palindrome: String): Option[Wonky] =
+    if (five.size != 5) None
+    else if (palindrome != palindrome.reverse) None
+    else Some(new Wonky(five, palindrome))
+}
+
+/* Somewhere else.. */
+val w = Wonky.validate(x, y) // say this returns None
+

Clearly something went wrong here, but we don't know what. If the strings were sent over + from some front end via JSON or something, when we send an error back hopefully we have + something more descriptive than "Something went wrong." What we want is instead of None, + we want something more descriptive. We can look into Either for this, where we use + Left to hold some sort of error value (similar to None), and Right to hold a successful + one (similar to Some).

+

To manipulate such values that may or may not exist (presumably obtained from functions that may or may not + fail), we use monadic functions such as flatMap, often in the form of monad comprehensions, or + for comprehensions as Scala calls them.

+
val x = ...
+val y = ...
+
+for {
+  a <- foo(x)
+  b <- bar(a)
+  c <- baz(y)
+  d <- quux(b, c)
+} yield d
+

In the case of Option, if any of foo/bar/baz/quux returns a None, that None simply + gets threaded through the rest of the computation – no try/catch statements marching off + the right side of the screen!

+

For comprehensions in Scala require the type we're working with to have flatMap and + map. flatMap, along with pure and some laws, are the requisite functions needed + to form a monad – map can be defined in terms of flatMap and pure. + With scala.util.Either however, we don't have those – we have + to use an explicit conversion via Either#right or Either#left to get a + RightProjection or LeftProjection (respectively), which specifies in what direction we bias + the map and flatMap calls. The convention however, is that the right side is the "correct" + (or "right", if you will) side and the left represents the failure case, but it is tedious to + continously call Either#right on values of type Either to achieve this.

+

Thankfully, we have an alternative in the Scalaz library via + scalaz.\/ (I just pronounce this "either" – some say disjoint union or just "or"), a right-biased + version of scala.util.Either – that is, calling \/#map maps over the value if it's in + a "right" (scalaz.\/-), otherwise if it's "left" (scalaz.-\/) it just threads it through + without touching it, much like how Option behaves. We can therefore alter the earlier function:

+
sealed abstract class WonkyError
+case class MustHaveLengthFive(s: String) extends WonkyError
+case class MustBePalindromic(s: String) extends WonkyError
+
+final class Wonky private(five: String, palindrome: String)
+
+object Wonky {
+  def validate(five: String, palindrome: String): WonkyError \/ Wonky =
+    if (five.size != 5) -\/(MustHaveLengthFive(five))
+    else if (palindrome != palindrome.reverse) -\/(MustBePalindromic(palindrome))
+    else \/-(new Wonky(five, palindrome))
+}
+
+/* Somewhere else.. */
+val w = Wonky.validate(x, y)
+

scalaz.\/ also has several useful methods not found on Either.

+ +

Try

+

As of Scala 2.10, we have scala.util.Try which is essentially an either, with the left type + fixed as Throwable. There are two problems (that I can think of at this moment) with this:

+
    +
  1. We want to avoid exceptions where we can.
  2. +
  3. It violates the monad laws.
  4. +
+

A big factor in our ability to deal with all these error handling types nicely + is using their monadic properties in for comprehensions.

+

For an explanation of the monad laws, there is a nice post + here describing them (using Scala). Try + violates the left identity.

+
def foo[A, B](a: A): Try[B] = throw new Exception("oops")
+
+foo(1) // exception is thrown
+
+Try(1).flatMap(foo) // scala.util.Failure
+

This can cause unexpected behavior when used, perhaps in a monad/for comprehension. Furthermore, + Try encourages the use of Throwables which breaks control flow and parametricity. + While it certainly may be convenient to be able to wrap an arbitrarily code block with the Try constructor + and let it catch any exception that may be thrown, we still recommend using an algebraic data type + describing the errors and using YourErrorType \/ YourReturnType.

+ +

scalaz.Validation

+

Going back to our previous example with validating wonky strings, we see an improvement that + could be made.

+
sealed abstract class WonkyError
+case class MustHaveLengthFive(s: String) extends WonkyError
+case class MustBePalindromic(s: String) extends WonkyError
+
+final class Wonky private(five: String, palindrome: String)
+
+object Wonky {
+  def validate(five: String, palindrome: String): WonkyError \/ Wonky =
+    if (five.size != 5) -\/(MustHaveLengthFive(five))
+    else if (palindrome != palindrome.reverse) -\/(MustBePalindromic(palindrome))
+    else \/-(new Wonky(five, palindrome))
+}
+
+/* Somewhere else.. */
+val w = Wonky.validate("foo", "bar") // -\/(MustHaveLengthFive("foo"))
+

The fact that one string must have a length of 5 can be checked and reported separately from the other + being palindromic. Note that in the above example "foo" does not satisfy the length requirement, + and "bar" does not satisfy the palindromic requirement, yet only "foo"'s error is reported + due to how \/ works. What if we want to report any and all errors that could be reported + ("foo" does not have a length of 5 and "bar" is not palindromic)?

+

If we want to validate several properties at once, and return any and all validation errors, + we can turn to scalaz.Validation. The modified function would look something like:

+
sealed abstract class WonkyError
+case class MustHaveLengthFive(s: String) extends WonkyError
+case class MustBePalindromic(s: String) extends WonkyError
+
+final class Wonky private(five: String, palindrome: String)
+
+object Wonky {
+  def checkFive(five: String): ValidationNel[WonkyError, String] =
+    if (five.size != 5) MustHaveLengthFive(five).failNel
+    else five.success
+
+  def checkPalindrome(p: String): ValidationNel[WonkyError, String] =
+    if (p != p.reverse) MustBePalindromic(p).failNel
+    else p.success
+
+  def validate(five: String, palindrome: String): ValidationNel[WonkyError, Wonky] =
+    (checkFive(five) |@| checkPalindrome(palindrome)) { (f, p) => new Wonky(f, p) }
+}
+
+/* Somewhere else.. */
+// Failure(NonEmptyList(MustHaveLengthFive("foo"), MustBePalindromic("bar")))
+Wonky.validate("foo", "bar")
+
+// Failure(NonEmptyList(MustBePalindromic("bar")))
+Wonky.validate("monad", "bar")
+
+// Success(Wonky("monad", "radar"))
+Wonky.validate("monad", "radar")
+

Awesome! However, there is one caveat – we cannot in good conscience use + scalaz.Validation in a for comprehension. Why? Because there is no valid + monad for it. Validation's accumulative nature works via its Applicative + instance, but due to how the instance works, there is no consistent monad + (every monad is an applicative functor, where monadic bind is consistent with + applicative apply). However, you can use the Validation#disjunction function to + convert it to a scalaz.\/, which can then be used in a for comprehension.

+

One more thing to note: in the above code snippet I used + ValidationNel, which is just a type alias. + ValidationNel[E, A] stands for for + Validation[NonEmptyList[E], A] – the actual Validation will take + anything on the left side that is a Semigroup, and ValidationNel is + provided as a convenience as often times you may want a non-empty + list of errors describing the various errors that happened in a function. + However, you can do several interesting things with other semigroups.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/evolving-typelevel.html b/blog/evolving-typelevel.html new file mode 100644 index 00000000..cfff884b --- /dev/null +++ b/blog/evolving-typelevel.html @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + Evolving Typelevel + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Evolving Typelevel

+ + + governance + +
+
+
+
+

Evolving Typelevel

+

In recent years, Typelevel has existed in a somewhat grey area legally. We have long managed a good deal of intellectual + property (the Organization Libraries) and raised funds. In 2022, we adopted a Charter establishing our governance. + However, we have not had a well-defined legal status.

+

We have decided it is time to become a legally-recognized organization. To that end, we have formed the Typelevel + Foundation, a nonprofit organization incorporated in the United States, in the state of California. Additionally, we are applying for + 501(c)(3) tax-exempt status from the IRS (the US tax agency), but this process will take several months.

+

The initial Board of Directors for the Foundation will be:

+
    +
  • Arman Bilge
  • +
  • Daniel Spiewak
  • +
  • Andrew Valencik
  • +
  • Mark Waks (known in the community as Justin du Coeur)
  • +
+

Arman will also be acting as our interim, part-time Executive Director. In the coming months, we will refine the + mission of the Foundation and define the roles that will carry out its work.

+

The Foundation Board will assume the responsibilities of governance, particularly legal and financial matters. + Meanwhile, the Steering Committee will become the Technical Steering Committee and focus on the technical work of + overseeing the Typelevel ecosystem. (The Code of Conduct Committee and Security Team will also continue their essential + work.) Our intent is that, by separating the governance and technical responsibilities, everyone can better focus on + what they enjoy and are good at.

+ +

Updates to the Technical Steering Committee

+

Besides the new name and refined scope of the Technical Steering Committee, we are also announcing changes to its + membership. Zach McCoy and Jasna Rodulfa-Blemberg have completed their terms on the Committee; we thank them for their + service!

+ +

Next steps

+

Our core mission remains the same: we will continue to steward the code and culture that are at the heart of Typelevel. + This transition supports our ongoing effort to build a more transparent and sustainable organization that serves our + growing community. Please stay tuned for upcoming announcements of new initiatives, a roadmap for the Foundation, and + opportunities to become involved.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Foundation + + +
+ The Typelevel Foundation is a nonprofit 501(c)(3) public charity. Our mission is to maintain Typelevel projects, advance research and education in functional programming, and grow our community. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/existential-inside.html b/blog/existential-inside.html new file mode 100644 index 00000000..f07aa59a --- /dev/null +++ b/blog/existential-inside.html @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + It’s existential on the inside + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

It’s existential on the inside

+ + + technical + +
+
+
+
+

It’s existential on the inside

+

This is the eighth of a series of articles on “Type Parameters and + Type Members”. You may wish to + check out the beginning, + which introduces the PList type we refer to throughout this article + without further ado.

+

When you start working with type parameters, nothing makes it + immediately apparent that you are working with universal and + existential types at the same time. It is literally a matter of + perspective.

+

I will momentarily set aside a lengthy explanation of what this means, + in favor of some diagrams.

+ +

Universal outside, existential inside

+
def fizzle[A]: PList[A] => PList[A] = {
+  def rec(pl: PList[A], tl: PList[A]): PList[A] = tl match {
+    case PNil() => pl
+    case PCons(x, xs) => rec(PCons(x, pl), xs)
+  }
+  xs => rec(PNil(), xs)
+}
+

Universal outside existential inside

+

The caller can select any A, but the implementation must work with + whatever A the caller chooses. So fizzle is universal in A from + the outside, but existential in A from the inside.

+

So what happens when the caller and callee ‘trade places’?

+ +

Existential outside, universal inside

+
def wazzle: Int => PList[_] =
+  n => if (n <= 0) PCons(42, PNil())
+       else PCons("hi", PNil())
+

Existential outside universal inside

+

Now the implementation gets to choose an A, and the caller must work + with whatever A the implementation chooses. So wazzle is universal + in A from the inside, but existential in A from the outside.

+

A good way to think about these two, fizzle and wazzle, is that + fizzle takes a type argument from the caller, but wazzle + returns a type (alongside the list) to the caller.

+ +

Universal (!) outside, existential inside

+
def duzzle[A]: PList[A] => Int = {
+  case PNil() => 0
+  case PCons(_, xs) => 1 + duzzle(xs)
+}
+
+def duzzle2: PList[_] => Int = {
+  case PNil() => 0
+  case PCons(_, xs) => 1 + duzzle2(xs)
+}
+

Universal outside existential inside

+

wazzle “returns” a type, alongside the list, because the existential + appears as part of the return type. However, duzzle2 places the + existential in argument position. So, as with all type-parameterized + cases, duzzle among them, this is one where the caller determines + the type.

+

We’ve discussed how you can + prove that duzzle m \equiv_{m} duzzle2, in a + previous post. Now, it’s time to see why.

+ +

Type parameters are parameters

+

The caller chooses the value of a type parameter. It also chooses the + value of normal parameters. So, it makes sense to treat them the same.

+

Let’s try to look at fizzle’s type this way.

+
[A] => PList[A] => PList[A]
+ +

Existential types are pairs

+

If wazzle returns a type and a value, it makes sense to treat them + as a returned pair.

+

Let’s look at wazzle’s type this way.

+
Int => ([A], PList[A])
+

This corresponds exactly to the forSome scope + we have explored previously. + So we can interpret PList[PList[_]] as follows.

+
PList[PList[A] forSome {type A}]  // explicitly scoped
+PList[([A], PList[A])]            // “paired”
+ +

The duzzles are currying

+

With these two models, we can finally get to the bottom of + duzzle m \equiv_{m}  duzzle2. Here are their + types, rewritten in the forms we’ve just seen.

+
[A] => List[A] => Int
+([A], List[A]) => Int
+

Recognize that? They’re just the curried and uncurried forms of the + same function type.

+

You can also see why the same type change will not work for wazzle.

+
Int => ([A], List[A])
+[A] => Int => List[A]
+

We’ve moved part of the return type into an argument, which is…not the + same.

+ +

The future of types?

+

This formulation of universal and existential types is due to + dependently-typed systems, in which they are “dependent functions” and + “dependent pairs”, respectively, though with significantly more + expressive power than we’re working with here. They come by way of the + description of the Quest programming language in + “Typeful Programming” by Luca Cardelli, + which shows in a clear, syntactic way that the dependent view of + universal and existential types is perfectly cromulent to + non-dependent type systems like Scala’s.

+

It is also the root of my frustration that Scala doesn’t support a + forAll, like forSome but for universally-quantified types. After + all, you can’t work with one without the other.

+

Now we have enough groundwork for + “Making internal state functional”, + the next part of this series. I suspect it will be a little prosaic at + this point, though.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/fabric.html b/blog/fabric.html new file mode 100644 index 00000000..fe60b17a --- /dev/null +++ b/blog/fabric.html @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + Fabric: A New JSON Library + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Fabric: A New JSON Library

+ + + governance + +
+
+
+
+

Fabric: A New JSON Library

+

I know what you're thinking! "A new JSON library? Why? Don't we have plenty of those?" Well, the short answer is a + resounding yes, but the idea of this library is simplicity and convenience. This library benefited a great deal from the things I liked + about the existing libraries, but hopefully offers some convenience features and simplifications that are entirely new.

+

Once I wrote the library I wanted to do some sanity-checking to make sure the performance wasn't substantially worse + than the existing solutions. Much to my surprise, the initial performance was generally faster than Circe or uPickle. + After some additional tuning, Fabric is outperforming the alternatives: Benchmarks (Source)

+

Check it out and please give feedback if there's anything we can do to improve it.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Matt Hicks + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/feed.rss b/blog/feed.rss new file mode 100644 index 00000000..4aff9984 --- /dev/null +++ b/blog/feed.rss @@ -0,0 +1,345 @@ + + + +Typelevel Blog +https://typelevel.org/blog/ +The Typelevel Blog RSS Feed + + Join the Technical Steering Committee + Thu, 2 Apr 2026 00:00:00 GMT + + Typelevel Foundation + + https://typelevel.org/blog/join-tsc-2026.html + https://typelevel.org/blog/join-tsc-2026.html + Last December, we established the Technical Steering Committee (TSC) as an advisory committee to the Typelevel Foundation. We are passionate about making programming joyful and social, and our aspiration for this new committee is to bring together a diverse group of contributors, users, and enthusiasts to build camaraderie and collaborate to develop a collective vision for Typelevel and our technology.

+

The TSC will meet monthly and is broadly tasked with helping to steward Typelevel's projects. This may look like many things, such as:

+
    +
  • sharing your experiences, to grow our organizational knowledge
  • +
  • providing early feedback on new developments
  • +
  • maintaining and documenting our libraries and infrastructure
  • +
  • curating our portfolio of Organization and Affiliate projects
  • +
  • developing an AI policy for contributions to our repositories
  • +
  • advising the Typelevel Foundation on its technical roadmap
  • +
  • getting nerd-sniped with friends and brainstorming new ideas!
  • +
  • being a cheerleader for our community :)
  • +
+

As a member of the TSC, you certainly do not have to do all of these, but you are excited and eager to play to your strengths and help where you can. Moreover, as part of Typelevel's leadership, you will exemplify the values in our Code of Conduct.

+

We invited the former Steering Committee to serve as the founding members of the TSC. Today, we are welcoming additional members to join the team. This is a special opportunity to help grow an organization that is important to you and so many people in a collaborative and social setting. If you are active on GitHub or Discord, use Typelevel projects for business or pleasure, teach functional programming, and/or build community, please consider applying. And this is by no means an exhaustive list of what a great candidate may look like!

+ +

Applying

+

To apply, please submit your name, optionally with a brief statement about why you wish to serve on the TSC, by Monday, April 20. Terms are two years long, with the opportunity to renew.

+

Applications are visible only to the current committee membership. We will thoughtfully consider all serious applications, deliberate in private, and not disclose the identities of applicants.

+

We look forward to reading applications from familiar names and also hope to hear from new faces with fresh perspectives whom we have not yet had the pleasure of meeting. We are not looking for third-party nominations, but if there is someone you would like to see, encourage them to apply!

]]>
+
+ + Typelevel Foundation is a 501(c)(3) public charity + Thu, 5 Mar 2026 00:00:00 GMT + + Typelevel Foundation + + https://typelevel.org/blog/charity.html + https://typelevel.org/blog/charity.html + Last August, we announced the Typelevel Foundation, a nonprofit organization incorporated in California. Today, we are proud to share that the Internal Revenue Service has determined that the Typelevel Foundation is a 501(c)(3) tax-exempt public charity.

+

The process of applying for charitable status is challenging, and especially so for open source organizations, which frequently receive denials. Working together with our attorneys, we prepared an application that explained what Typelevel does and why this work is charitable, citing the unique innovations of our projects, our participation in conferences and mentoring programs, and our commitment to open collaboration. We want to recognize and thank our community for their impressive record of impact that we showcased in our application.

+
The determination that the Typelevel Foundation is a public charity is a testament to the intellectual merit and educational value of the Typelevel community's contributions to functional programming and open source for over a decade.
+ +

Benefits and responsibilities

+

Our charitable status expands fundraising opportunities while also reducing our operating costs.

+
    +
  • +

    We are exempt from paying tax on the money that we raise. Furthermore, individuals and companies that pay tax in the US may deduct donations to the Foundation on their annual returns.

    +
  • +
  • +

    We are eligible for free and discounted services from several companies. For example, GitHub now provides us a Team account without seat limits at no cost, saving us thousands of dollars.

    +
  • +
  • +

    We have internationally recognized institutional credibility. By partnering with the Swiss Philanthropy Foundation and the Maecenata Foundation, we can now receive tax-efficient charitable donations from across Europe. We are also eligible for grants from other 501(c)(3) organizations, including private foundations such as the Alfred P. Sloan Foundation.

    +
  • +
+

As a charity, we have legal and fiduciary responsibilities that reinforce our own commitments to transparency and community-driven open source development. Above all, we are accountable to our mission of advancing research and education in functional programming, prioritizing public benefit over private interests. To remain compliant, we must file Form 990 annually with the IRS which details our finances and describes how funds were spent towards our charitable purpose. These documents are made available for public inspection.

+ +

Migrating our financial infrastructure

+

For several years, Open Source Collective served as the fiscal host for our funds (not to be confused with Open Collective, which is the web platform). Earlier this week, they transferred our balance to the Foundation's bank account and closed our account with them. If you had setup a recurring donation on our old Open Collective page, it has been canceled.

+

We now accept donations via several platforms, including GitHub Sponsors and Every.org (which can handle stock and crypto donations). We have also set up a new Open Collective page for the Foundation, connected to our own bank account, which we will use to provide transparency into our finances and spending.

+ +

What's next

+

The first phase of our work focused on the legal restructuring of Typelevel as a nonprofit Foundation. In this next phase, we will focus on resourcing the Foundation to support its mission, by fundraising, grant writing, and recruiting new members to our committees.

]]>
+
+ + Typelevel Summer of Code 2026 + Mon, 2 Mar 2026 00:00:00 GMT + + Arman Bilge + + Antonio Jimenez + + Andrew Valencik + + https://typelevel.org/blog/gsoc-2026.html + https://typelevel.org/blog/gsoc-2026.html + Due to overwhelming participation in GSoC 2026, we are only able to consider proposals from applicants who complete our onboarding process by Monday, March 16th. + If you missed this deadline, we appreciate your interest and hope you will apply next year!

+

We are pleased to announce that Typelevel is a Mentoring Organization for Google Summer of Code 2026! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. You can learn more about what the experience is like in this blog post by our 2024 contributor Ching Hian Chew.

+

Please check out our project ideas and mentors spanning serverless, build tooling, frontend/UIs, systems programming, web assembly, and more. Furthermore, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.”

+

We look forward to welcoming you to the Typelevel community and we hope that participating in this program will be the beginning of your open source journey. Applications open on March 16! Until then, get started by watching our intro presentation and making your first contribution.

+ +

2025 recap

+

Congratulations to our GSoC contributors last year:

+
    +
  • Rahul upgraded several FS2 APIs to use non-blocking, polling-based I/O, including datagram sockets and native processes on both the JVM and native platforms. This improves their performance and semantics.
  • +
  • Shrey worked on an ambitious project to implement a machine learning inference runtime with Cats Effect on Scala Native. This makes it possible to serve ML models alongside web services without compromising latency.
  • +
+

They were joined by a contributor outside of the official GSoC program:

+
    +
  • Jay developed a new API for log4cats based on a proposal by Olivier Mélois. It encodes the interface as a single abstract method to enhance usability and extensibility while maintaining compatibility with the original API.
  • +
+

Our summer culminated in a virtual meetup where each of them gave a lightning talk about their project. Thank you to everyone in our community who showed up for our contributors, especially our mentors Arman Bilge, Antonio Jimenez, Morgen Peschke, Michael Pilquist, and Andrew Valencik.

+

Finally, we are making a broad call for any and all new contributors. Even if you are not eligible to participate in GSoC, you are always welcome to join the Typelevel community and contribute to our projects!

]]>
+
+ + typelevel.org built with Typelevel + Wed, 18 Feb 2026 00:00:00 GMT + + Arman Bilge + + Andrew Valencik + + https://typelevel.org/blog/typelevel-org-built-with-typelevel.html + https://typelevel.org/blog/typelevel-org-built-with-typelevel.html + We are proud to share that our website is now built with Laika, a Typelevel Organization project for generating static sites! As cool as it is that we are self-hosting, the intention of this revamp was to make it easier for our community to develop and contribute to the website. We chose technologies that we hope balance familiarity and ease-of-use with functionality and stability. Notably, this new website can be generated in its entirety by running a Scala script: scala build.scala. Stay tuned for a future blog post that dives into the details, but for now you may peruse the PR.

+

Finally, we would like to express gratitude to our friends at 47 Degrees who generously built the previous version of the website for us.

+ +

What’s next and how you can help

+

Truthfully, so far this is a "minimally viable website" and we invite you to help us iterate on it. Broadly, our goals are to explain:

+
    +
  1. Who we are, and how you can join our community.
  2. +
  3. What we build, and how you can use it.
  4. +
+

The next phase of development will largely focus on creating new content to support these goals (and the infrastructure to support that content). Here are a few ideas we have:

+
    +
  • + Educational and tutorial content to facilitate onboarding. +
      +
    • How to Get Started with Typelevel using our Toolkit.
    • +
    • Curated pathways to Learn how to use Typelevel in different scenarios: web services, serverless, CLIs, UIs, etc.
    • +
    • How to Get Started Contributing both to existing projects and also by publishing new libraries with sbt-typelevel.
    • +
    +
  • +
  • A Typelevel Project Index for exploring Organization and Affiliate projects, à la Scaladex. We are imagining a webapp built with Calico, with features for browsing projects, finding version numbers, and scaffolding new applications.
  • +
  • + Content-agnostic enhancements to the website itself. +
      +
    • Upstreaming customizations from our build to Laika.
    • +
    • Integrating mdoc, for typechecking code.
    • +
    • Improvements to layout, styling, and theme.
    • +
    +
  • +
+

We are accepting ideas and help in many forms! Please use our issue tracker and join the discussion on the #website channel in our Discord server.

+ +

In memoriam

+

This project would not have been possible without Jens Halm and his vision for a documentation tool that is native to our ecosystem. Jens raised the bar for open source stewardship: beyond the technical excellence of his work on Laika, he consistently published feature roadmaps, detailed issue and PR descriptions, and thorough documentation. Indeed, by creating a documentation tool that integrated so well with our tech stack, he has empowered all of us to become exemplary maintainers. Moreover, Jens' enthusiasm to support our community (including entertaining our numerous feature requests with in-depth responses full of context and design insights!) was his most generous gift to us.

]]>
+
+ + Custom Error Types Using Cats Effect and MTL + Tue, 2 Sep 2025 00:00:00 GMT + + Daniel Spiewak + + https://typelevel.org/blog/custom-error-types.html + https://typelevel.org/blog/custom-error-types.html + tl;dr Cats MTL 1.6.0 introduces a brand new lightweight syntax for managing user-defined error types in the Cats ecosystem without requiring complex monad transformers.

+

One of the most famous and longstanding limitations of the Cats Effect IO type (and the Cats generic typeclasses) is the fact that the only available error channel is Throwable. This stands in contrast to bifunctor or polyfunctor techniques, which add a typed error channel within the monad itself. You can see this easily in type signatures: IO[String] indicates an IO which returns a String or may produce a Throwable error (Future[String] is directly analogous). Something like BIO[ParseError, String] would represent a BIO that produces a String or raises a ParseError. The latter type signature is more general than Throwable, since it allows for user-specified error types, and it's somewhat more explicit about where errors can and cannot occur.

+

In a meaningful sense, this type of bifunctor error encoding is analogous to checked exceptions in Java, whereas monofunctor error encoding (like Cats Effect's IO) is analogous to unchecked exceptions. Both are valid design decisions for an effect type, but they come with different benefits and tradeoffs.

+

Cats has long been quite prescriptive about monofunctor effects, in part because this considerably simplifies the compositional integration space. Libraries like Fs2, Http4s, Calico, and so many more are able to build on top of parametric effects (the famous F[_]) with a consistent understanding of what error channels are available and how they're going to behave. This has very subtle interactions with concurrent logic and resource handling, and by insisting on a monofunctor calculus, the Cats ecosystem is able to maintain very strong properties with relatively simple implementations in these areas.

+

However, the core problem of custom error types doesn't really go away. Parsing is a great example of this. For example, Circe has a ParsingFailure type which carries a specific JSON parse error message as well as some associated traceback context. While this type does happen to extend Exception, and thus can be raised within an IO, it's not necessarily right for it to do so. This is common, but arguably it's only common because of the prevalence of monofunctors.

+

A standard solution to this problem, if you don't want to extend Exception with your error types, is to simply return Either everywhere. Unfortunately, that results in a lot of type signatures which look like this:

+
def parse(input: String): IO[Either[Failure, Result]] = ???
+

And then of course, everything you do with that result must be explicitly flatMapped into the Either, and higher-order control flow libraries like Fs2 will often need some extra coaxing in order to make everything work the way you want it to. This gets old in a hurry, which often results in reaching for alternatives like EitherT. That way lies frustration and woe.

+ +

Capabilities

+

The good news is that we now have a better answer here, and one which composes very nicely with the existing (and future) ecosystem, maintains all relevant concurrency properties, and which type-infers extremely well, particularly in Scala 3. The answer has been to double down on the relatively little-used implicit capabilities library for Cats, known under the very misleading name of Cats MTL.

+

The name "Cats MTL" comes from Haskell's MTL package, which in turn was pretty aptly named: "Monad Transformer Library". Haskell's MTL is entirely oriented around making it easier and more ergonomic to manipulate monad transformer stacks, which is to say, multiple layers of datatypes like EitherT, Kleisli, and so on. Monad transformer stacks are extremely difficult to work with, both in Scala and in Haskell, and so over time people progressively evolved techniques involving typeclasses in Haskell and implicits in Scala to more ergonomically manipulate composable effect types. Cats MTL was rooted in an adaptation of some of these ideas.

+

Over time though, we've learned that monad transformer datatypes themselves are often too clunky and even unnecessary. They work well in a few contexts, most notably local scopes (i.e. within the body of a single method), but they're generally the wrong solution for the problem. Quite notably, while the Cats Effect concurrent typeclasses do work on monad transformer stacks and derive lawful results, the practical outcomes can be very unintuitive. For that reason, it's generally not advisable to use types like EitherT or IorT composed together with libraries like Fs2 or similar.

+

However, the basic idea of MTL itself, divorced from the datatypes (like EitherT), is actually a very good one. At its core, MTL is just about expressing capabilities available within a given scope using implicit evidence. Capabilities can be things like parallelism, resource safety, error handling, dependency injection, sequential composition, or similar. When done correctly, this can be a very powerful and lightweight way of expressing compositional effects with a high degree of granularity and type safety. It's not a coincidence that this is exactly the route being explored by many of the researchers working on Scala academically!

+ +

Scoped Error Capabilities

+

The problem has been to find a way to blend all of these constructs together in a way that practically works with the ecosystem, is syntactically lightweight, has pleasant type inference and errors, and doesn't confuse the heck out of anyone who touches it. That is a problem we feel we have now solved, at least with errors.

+
import cats.Monad
+import cats.effect.IO
+import cats.mtl.syntax.all.*
+import cats.mtl.{Handle, Raise}
+import cats.syntax.all.*
+
+// define a domain error type
+enum ParseError:
+  case UnclosedBracket
+  case MissingSemicolon
+  case Other(msg: String)
+
+// use that error type in some function
+def parse[F[_]](input: String)(using Raise[F, ParseError], Monad[F]): F[Result] =
+  // do some hardcore parsing
+  if missingBracket then
+    UnclosedBracket.raise[F, Result]
+  else if missingSemicolon then
+    MissingSemicolon.raise // we can rely on type inference and omit extra typings
+  else
+    result.pure[F]
+
+// use allow/rescue like try/catch to create scoped error handling
+val program: IO[Unit] = Handle.allow[ParseError]:
+  for
+    x <- parse[IO](inputX)
+    y <- parse(inputY)
+    _ <- IO.println(s"successfully parsed $x and $y")
+  yield ()
+.rescue:
+  case ParseError.UnclosedBracket =>
+    IO.println("you didn't close your brackets")
+  case ParseError.MissingSemicolon =>
+    IO.println("you missed your semicolons very much")
+  case ParseError.Other(msg) =>
+    IO.println(s"error: $msg")
+

There's a lot to unpack here! At the very beginning we define a custom error type, ParseError. This is just a domain error like any other, and you'll note that it doesn't extend Exception or Throwable or similar. Without Cats MTL, we would generally have to wrap this error up in Either in all our function's result types, if we wanted to use it (similar to what Circe does). In this case though, instead of adding the error to the result type, we added a using parameter to our parse function!

+

Specifically, what we're doing here when we say using Raise[F, ParseError] is that the parse method requires the ability to raise (but not handle!) errors of type ParseError. This is a bit like saying throws ParseError in Java, except it isn't an exception!

+

Later on, in the body of parse, we use this Raise capability to call the raise method, producing errors in failure cases. This is a bit like the throw keyword, but again with our own custom domain error type. Btw, if we had expanded our Monad[F] using into something like MonadError[F, Throwable] or, more aggressively, Async[F], we would have also had the ability to raise any error of type Throwable using the same syntax! In this case though, parse is only able to raise domain errors.

+

As an aside, the F[_] here could be instantiated with many different monadic types. While we're using IO in production, perhaps we would want to test this function using Either[ParseError, A] as our type. This is very much supported! And in fact, if you did this, the Raise would have been implicitly materialized by Cats MTL, since Either has an obvious implementation of that function.

+

Finally, at the end of the snippet above, we define program using the brand new syntax: allow/rescue. This is where things get very fancy. What we're doing here is we're introducing a new lexical scope (indented after the allow[ParseError]:) in which it is valid to raise an error of type ParseError. You should think of this as being very similar to try/catch, except it works with effect types like IO and any error type you define (not just Throwable). Within this scope, we write code as usual, and we're allowed to call the parse function. Note that if we had tried to call parse outside of this scope, it would have been a compile error informing us that we're missing the Raise capability.

+

At the end of the allow scope, we call .rescue, and this requires us to pass a function which handles any errors which could have been raised by the body of the allow. This works exactly like catch, except with your own domain error types. In this case, we are apparently just logging the existence of the errors and moving on with our life, because we do some printing and away we go, but you could imagine perhaps returning a custom HTTP error code, or triggering some fallback behavior, or really any other error handling logic.

+ +

Scala 2

+

Oh, and just in case you were wondering, this syntax does work on Scala 2 as well, it's just a bit less fancy! Here's the same snippet from above, but with 100% more braces and a lot more explicit types:

+
import cats.Monad
+import cats.effect.IO
+import cats.mtl.syntax.all._
+import cats.mtl.{Handle, Raise}
+import cats.syntax.all._
+
+// define a domain error type
+sealed trait ParseError extends Product with Serializable
+
+object ParseError {
+  case object UnclosedBracket extends ParseError
+  case object MissingSemicolon extends ParseError
+  case class Other(msg: String) extends ParseError
+}
+
+// use that error type in some function
+def parse[F[_]](input: String)(implicit r: Raise[F, ParseError], m: Monad[F]): F[Result] = {
+  // do some hardcore parsing
+  if (missingBracket)
+    UnclosedBracket.raise[F]
+  else if (missingSemicolon)
+    MissingSemicolon.raise[F]
+  else
+    result.pure[F]
+}
+
+// use allow/rescue like try/catch to create scoped error handling
+val program: IO[Unit] = Handle.allowF[IO, ParseError] { implicit h =>
+  for {
+    x <- parse[IO](inputX)
+    y <- parse[IO](inputY)
+    _ <- IO.println(s"successfully parsed $x and $y")
+  } yield ()
+} rescue {
+  case ParseError.UnclosedBracket =>
+    IO.println("you didn't close your brackets")
+  case ParseError.MissingSemicolon =>
+    IO.println("you missed your semicolons very much")
+  case ParseError.Other(msg) =>
+    IO.println(s"error: $msg")
+}
+

We need to do a lot more hand-holding for the compiler by using the allowF function instead of allow, but in general this is very much the same idea!

+ +

Under the Hood

+

Behind the scenes, this functionality is doing two very creative things. First, as the Scala 2 snippet hints, we're introducing a new implicit within the local scope of the function passed to allow/allowF. This is one of Scala's more unique features and we're leveraging it quite heavily. In Scala 3, we're able to hide this syntax entirely by using context functions (the A ?=> B syntax), but in Scala 2 we need to use the implicit x => lambda syntax in order to make this work.

+

That implicit is introduced targeting the effect type we passed to allowF, or in Scala 3's case, the type which was inferred from the return. In this case, that type is IO! In other words, you don't need to be using parametric effects (F[_]) in order to make all this work! Raise[IO, ParseError] is a totally valid Raise instance, and it's exactly what we have in scope here. Or rather, we actually have Handle[IO, ParseError] (which extends Raise), which gives us the ability to both raise and handle errors.

+

Once the scope is closed, syntactically, we force the user to supply an error handler to ensure that any errors which were raised and unhandled within the body are correctly managed. This is a pretty logical way of setting up your error handling, and precisely mirrors the way that you would do this same thing with a more imperative direct syntax like try/catch/throw/throws.

+

In the way way deep underdark of the implementation, this whole thing works at runtime by creating what we call a "submarine error". Specifically, we have a local traceless exception type called Submarine inside of the allow implementation which extends RuntimeException. When you raise a custom domain error (ParseError in this case), we use Submarine to "submerge" your error within the Throwable error channel of the enclosing effect – in this case, IO. Since we catch this error at the boundary, this whole process is entirely invisible to you unless you write something like handleErrorWith and catch all Throwable-typed errors within the scope, in which case you might see something of type Submarine. The correct thing to do with this error type, should you see it, depends considerably on exactly why you're writing handleErrorWith, and as it turns out this is exactly the whole point!

+

By implementing this functionality without extending the number of actual error channels within the effect type (either with a bifunctor or something like EitherT), we ensure that everything continues to compose correctly around all resource handling, structured and unstructured concurrency, and otherwise-oblivious generic library code which has no idea what your domain errors are or how they might behave. Even in the case of an explicit handleErrorWith, you might be adding that type of error handler because you're writing some logic which must make certain that there is no possible way to short-circuit without passing through your handler (e.g. perhaps you're trying to make sure that some critical resource is cleaned up), or alternatively you may just be trying to observe Throwable errors to log and re-raise them, or any number of other things you might be doing with the error channel that we don't have any insight into.

+

Rather than trying to impose a particular multi-channel composition semantic on your code, we simply stick with a single error channel with known and well-understood supremacy semantics, and everything else follows from there.

+ +

Conclusion

+

Hopefully you find this technique helpful! This has been in the works for a surprisingly long time (I think it was first suggested in the Typelevel Discord about two or three years ago), and it was Thanh Le (@lenguyenthanh) who ultimately pushed it over the line. Huge shoutout! He has already begun leveraging this functionality in Lichess, one of the larger production Scala projects: lichess-org/lila#17944

+

Even more excitingly, this is a bit of a taste of the next phase of the effect type ecosystem. Scala is continuing to move heavily in the direction of implicit capabilities for these types of behaviors, and while efforts such as Caprese are still a long way from bearing real-world fruit, much of the work that is being done in that direction also creates the primitives needed to encode a compositional capabilities ecosystem for our existing production effect types, such as Cats Effect IO!

+

Cats MTL will continue to evolve in this area, with an eye towards advancing the capabilities and improving syntax and ergonomics of this type of functionality both now and in the future.

]]>
+
+ + Evolving Typelevel + Tue, 19 Aug 2025 00:00:00 GMT + + Typelevel Steering Committee + + Typelevel Foundation + + https://typelevel.org/blog/evolving-typelevel.html + https://typelevel.org/blog/evolving-typelevel.html + In recent years, Typelevel has existed in a somewhat grey area legally. We have long managed a good deal of intellectual +property (the Organization Libraries) and raised funds. In 2022, we adopted a Charter establishing our governance. +However, we have not had a well-defined legal status.

+

We have decided it is time to become a legally-recognized organization. To that end, we have formed the Typelevel +Foundation, a nonprofit organization incorporated in the United States, in the state of California. Additionally, we are applying for +501(c)(3) tax-exempt status from the IRS (the US tax agency), but this process will take several months.

+

The initial Board of Directors for the Foundation will be:

+
    +
  • Arman Bilge
  • +
  • Daniel Spiewak
  • +
  • Andrew Valencik
  • +
  • Mark Waks (known in the community as Justin du Coeur)
  • +
+

Arman will also be acting as our interim, part-time Executive Director. In the coming months, we will refine the +mission of the Foundation and define the roles that will carry out its work.

+

The Foundation Board will assume the responsibilities of governance, particularly legal and financial matters. +Meanwhile, the Steering Committee will become the Technical Steering Committee and focus on the technical work of +overseeing the Typelevel ecosystem. (The Code of Conduct Committee and Security Team will also continue their essential +work.) Our intent is that, by separating the governance and technical responsibilities, everyone can better focus on +what they enjoy and are good at.

+ +

Updates to the Technical Steering Committee

+

Besides the new name and refined scope of the Technical Steering Committee, we are also announcing changes to its +membership. Zach McCoy and Jasna Rodulfa-Blemberg have completed their terms on the Committee; we thank them for their +service!

+ +

Next steps

+

Our core mission remains the same: we will continue to steward the code and culture that are at the heart of Typelevel. +This transition supports our ongoing effort to build a more transparent and sustainable organization that serves our +growing community. Please stay tuned for upcoming announcements of new initiatives, a roadmap for the Foundation, and +opportunities to become involved.

]]>
+
+ + Typelevel Meetup Lausanne + Fri, 15 Aug 2025 00:00:00 GMT + + https://typelevel.org/blog/meetup-lausanne-2025-08-22.html + https://typelevel.org/blog/meetup-lausanne-2025-08-22.html + + +

+
+

"lauvax" by harmishhk is licensed under CC BY-SA 2.0.

+
+ + +

About the Meetup

+

Join the Typelevel community at an in-person meetup on EPFL campus in Lausanne, organized by Arman Bilge and Antonio Jimenez. This meetup is open to (aspiring) Typelevel users and contributors and anyone curious to learn more about functional programming in Scala, no matter their prior experience.

+

During this meetup you can expect:

+
    +
  • chat/q&a about functional programming and Typelevel libraries
  • +
  • a small tutorial on Cats Effect, FS2, and Calico
  • +
  • a group activity building widgets with Calico
  • +
  • lunch
  • +
+

More details and registration are available on the event page. All participants and organizers must abide by the Typelevel Code of Conduct.

]]>
+
+
+
diff --git a/blog/fibers-fast-mkay.html b/blog/fibers-fast-mkay.html new file mode 100644 index 00000000..fd6bbdee --- /dev/null +++ b/blog/fibers-fast-mkay.html @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + Why Are Fibers Fast? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Why Are Fibers Fast?

+ + + technical + +
+
+
+
+

Why Are Fibers Fast?

+

With Cats Effect 3.0 right around the corner, we've been publishing a lot of numbers and scenarios which demonstrate disorientingly high performance in a lot of realistic setups. At first glance, this seems to defy intuition. After all, IO is quite a heavyweight abstraction; how is it that it can be competitive with hand-written and hand-tuned code for the same use-case?

+

There are a lot of ways to answer this question, and they get both complex and depend heavily on exactly what use-case you're talking about, but we can get a general idea of what's going on here by zooming out a bit on the problem space.

+ +

A Small Strawman

+

The main thing to realize is that fibers are not fast compared to a single, hot, straight-line piece of code that runs sequentially. For example:

+
val data: Array[Int] = // ...
+var i = 0
+while (i < data.length) {
+  data(i) *= 2
+  i += 1
+}
+

Consider the above when compared to the tortured purely functional encoding of the same idea:

+
val data: List[Int] = // ...
+data.traverse(i => IO.pure(i * 2))
+

Okay the functional encoding is shorter, and that's even accounting for the fact that I just artificially threw IO in there for no reason. However, it is also several orders of magnitude slower than the imperative version, and more importantly, offers no advantages whatsoever. This code is not run concurrently. It is not asynchronous. It's just doing a bit of stuff with some data.

+

Functional programming is not compelling "in the small". It is never compelling in the small, because the small is pretty easy regardless of how you do it, and in many cases the "small" is the most performance-sensitive part of your application. Where functional programming is truly compelling is in the large, and this is what brings us to exactly why it is that Cats Effect is a shockingly fast runtime system.

+ +

The Large

+

Cats Effect specifically, and fibers as an idea, are meant to solve a very particular problem: how do you architect the control flow of a highly-asynchronous, highly-concurrent application without losing your mind or sacrificing performance? That's the primary thing they are designed to do. This is why their benefits in terms of code quality and maintainability are usually only felt within realistic examples, and not in scenarios which fit into slides or blog posts.

+

More relevantly to our original point though, this is also why the performance question is less obvious than it initially seems. In "the large" of system design, micro-optimized performance questions such as how fast you can loop over a List are just not relevant. Instead, what's really relevant is how efficiently you optimize the utilization of system resources such as CPU pipelining and page faults, what your inefficiencies are around contention and scheduling, whether or not you have pathological outcomes under pressure, and so on.

+

This is where Cats Effect truly shines. By providing a prescriptive (yet pleasant!) framework for defining your application's control flow, Cats Effect is able to deeply understand what it means to schedule and optimize most programs written in IO, and thus take advantage of this knowledge using algorithms and techniques which would be impossible (or at the very least, highly impractical) in a hand-written application. This is very much like how modern concurrent garbage collectors are usually much faster than hand-tuned malloc/free in practical applications, simply because the GC is able to perform some very complicated and non-local logic which would be essentially impossible if managing memory by hand.

+ +

The Alternative

+

To understand this more concretely, we need to think about what we could be doing instead of using Cats Effect. Imagine a typical HTTP service at high scale. We need to be able to accept requests as they come in, perform some basic data translation, make network calls to downstream services, and then formulate a response and serialize it to bytes. All of this must be done very very quickly and highly concurrently.

+

The most direct and naive way to approach this is to allocate one thread per connection. When a client connection comes in, we allocate a thread and hand off the connection for handling, and that thread will live until the response is fully generated. When we make network calls to downstream services, this thread will block on the responses, giving us a nice linear application control flow.

+ +

Unbounded Threads

+

loads of threads diagram

+

Implementation-wise, this is very easy to reason about. Your code will all take on a highly imperative structure, with A followed by B followed by C, etc, and it will behave entirely reasonably at small scales! Unfortunately, the problem here is that threads are not particularly cheap. The reasons for this are relatively complex, but they manifest in two places: the OS kernel scheduler, and the JVM itself.

+

The JVM is a bit easier to understand here. Whenever you create a new thread, the garbage collector must establish an awareness of that thread and its current call stack so that it can accurately determine what parts of the shared memory space are reachable and what parts are not (literally, figuring out what memory needs to be freed). For this reason, threads are considered GC roots, which basically means that when the garbage collector scans the heap for active/inactive memory, it will do so by starting from each thread individually and will traverse its referenced object graph starting from the call stack and working upwards. Modern garbage collectors are extremely clever, but this bit of juggling between the GC and the actively-running threads is still a source of meaningful overhead, even today. The more threads you have, and the larger their respective object graphs, the more time the GC has to take to perform this scan, and the slower your whole system becomes.

+

In practice, on most practical hardware, you really can't have more than a few hundred threads before the JVM starts to behave poorly. Keeping things bounded to around a few dozen threads is generally considered best practice.

+

This is a huge problem, and we run face-first into it in architectures like the above where every request is given its own thread. How can we possibly serve tens of thousands of requests simultaneously when each request implies the allocation and maintenance of such an expensive resource? We need some way of handling multiple requests on the same thread, or at the very least bounding the number of threads we have floating around. Enter thread pools and the famous "disruptor pattern".

+ +

Bounded Threads

+

thread pool diagram

+

In this kind of architecture, incoming requests are handed off to a scheduler (usually a shared lock-free work queue) which then hands them off to a fixed set of worker threads for processing. This is the kind of thing you'll see in almost every JVM application written in the past decade or so.

+

The problem is that it still doesn't quite resolve the issue. Notice how we're still making that network call to downstream, which presumably doesn't return instantaneously. We have to block our carrier thread (the worker responsible for handling our specific request) while waiting for that downstream to respond. This creates a situation generally known as "starvation", where every thread in the pool is wasted on blocking operations, waiting for the downstream to respond, while new requests are continuing to flood in at the top waiting for worker to pick them up.

+

This is extremely wasteful, because we have a scarce resource (threads) which could be utilized to make some progress on our ever-filling request queue, but instead is just sitting there in BLOCKED state waiting for downstream to respond and just generally being a nuisance. The classic solution to this problem is to evolve to an asynchronous model for the network connections, allowing the downstream to take as long as it needs to get back to us, and only using the thread when we actually have real work to do.

+ +

Improved Thread Utilization

+

async pool diagram

+

This is much more efficient! It's also incredibly confusing, and it gets exponentially worse the more complexity you have in your control flow. In practice most systems like this one have multiple downstreams that they need to talk to, often in parallel, which makes this whole thing get crazy in a hurry. It also doesn't get any easier when you add in the fact that just talking to a downstream (like a database) often involves some form of resource management which has to be correctly threaded across these asynchronous boundaries and carried between threads, not to mention problems like timeouts and fallback races and such. It's a mess.

+

However, this is essentially what modern systems look like without Cats Effect. It's incredibly complicated to reason about it, you pretty much always get it wrong in some way, and it's actually still very wasteful and naive in terms of resource efficiency! For example, imagine if any one of these pipelines takes a particularly long time to execute. While it's executing on a thread, everything else that might be waiting for that thread has to sit there in the queue, not receiving a response. This is another form of starvation and it leads to the problem space of fairness and related questions. When this happens in your system, you could see extremely high CPU utilization (since all the worker threads are trying very hard to answer requests as quickly as possible) and very very good p50 response times, but your p99 and maybe even p90 response times climb into the stratosphere since a subset of requests under load end up sitting in the work queue for a long time, just waiting for a thread.

+

All in all, this is very bad, and it starts to hint at why it is that Cats Effect is able to do so much better. The above architecture is already insane and effectively impossible to get right if you're doing it by hand. Cats Effect raises your program up to a higher abstraction layer and allows you to think about things in terms of a simpler model: fibers.

+ +

Many Fibers, Fewer Threads, One Scheduler

+

fiber diagram

+

This diagram looks a lot like the first one! In here, we're just allocating a new fiber for each request that comes in, much like how we tried to allocate a new thread per request. Each fiber is a self-contained, sequential unit which semantically runs from start to finish and we don't really need to think about what's going on under the surface. Once the response has been produced to the client, the fiber goes away and we never have to think about it again.

+

Of course, you can't map fibers 1-to-1 with threads, otherwise we end up recreating the first architecture but with more extra overhead in all directions. The solution here is to implement a Scheduler which understands the nature of fibers and figures out how to optimally map them onto some tightly controlled set of underlying worker threads. In Cats Effect 2, as in almost all asynchronous runtimes on the JVM, this was done using an Executor which simply maintained an internal queue of fibers. Each time a worker thread finished with a previous fiber, the queue would dictate which fiber it needs to work on next.

+

Critically, fibers are free to suspend at any time. In our example, fibers suspend whenever they talk to the downstream, since they're semantically waiting for a response and cannot make any forward progress within their sequential flow. Fiber suspension is safe and efficient though, because the scheduler responds to this by simply removing the fiber from the work queue until such time as the downstream responds, at which point the scheduler seamlessly re-enqueues the fiber and a new thread is able to pick it up. This ensures that the threads are always kept busy and any suspended fibers are truly free: the only resource they consume is memory.

+

Raising the level of abstraction here isn't just good for users who write programs (you!) in this model, it's also good for the runtime itself. The scheduler is able to solve problems such as fairness in a more general way – for example, by preventing a fiber from hogging its worker thread for too long and artificially suspending that fiber and sending it to the back of the work queue. The system is also able to contemplate other, vastly more complex problems such as resource management and interruption (for timeouts), simply because the scheduler is taking care of all of these low-level details related to keeping the workers busy.

+

This is already a significant improvement over what we can do by hand, not only cognitively but also in terms of runtime performance. The scheduler is able to solve a very complicated problem space in a nice, general fashion given global knowledge of the fiber space, and this results in dramatically improved efficiency in resource utilization throughout the system. We're not done though.

+

The above diagram is basically how Cats Effect 2, Vert.x, Project Loom, and almost every other asynchronous framework on the JVM behaves. There are some refinements that you can apply by trying to make the scheduler a bit smarter and such, but the state of the art on the JVM is basically what you see above.

+

That is, until Cats Effect 3.

+ +

Many Fibers, Fewer Threads, Many Schedulers

+

work-stealing diagram

+

Cats Effect 3 has a much smarter and more efficient scheduler than any other asynchronous framework on the JVM. It was heavily inspired by the Tokio Rust framework, which is fairly close to Cats Effect's problem space. As you might infer from the diagram, the scheduler is no longer a central clearing house for work, and instead is dispersed among the worker threads. This immediately results in some massive efficiency wins, but the real magic is still to come.

+

Work-stealing schedulers are not a new idea, and in fact, they are the default when you use Monix, Scala's Future, or even the upcoming Project Loom. The whole idea behind a work-stealing scheduler is that the overhead of the scheduler itself can be dispersed and amortized across the worker pool. This has a number of nice benefits, but the most substantial is that it removes the single point of contention for all of the worker threads.

+

In a conventional implementation of the disruptor pattern (which is what a fixed thread pool is), all workers must poll from a single work queue each time they iterate, fetching their next task. The queue must solve the relatively complex synchronization problem of ensuring that every task is handed to exactly one worker, and as it turns out, this synchronization is extremely expensive. To make matters worse, the cost of this synchronization increases quadratically with the amount of contention! As you increase the number of working threads, the number of synchronization points between threads increases with the square of the threads. This is actually conceptually obvious, since each thread will create contention faults with every other thread, and only one thread will actually "win" and get the task. This overhead is somewhat imperceptible if you have a small number of workers, but with a larger number of workers it begins to dominate very quickly. And note, the ideal number of workers is exactly equal to the number of CPUs backing your JVM, meaning that scaling up is almost impossible with this kind of design.

+

Work-stealing, for contrast, allows the individual worker threads to manage their own local queue, and when that queue runs out, they simply take work from each other on a one-to-one basis. Thus, the only contention that exists is between the stealer and the "stealee", entirely avoiding the quadratic growth problem. In fact, contention becomes less frequent as the number of workers and the load on the pool increases. You can conceptualize this with the following extremely silly plot (higher numbers are bad because they represent overhead):

+

plot of work stealing overhead vs standard disruptor pattern

+

Work-stealing is simply very, very, very good. But we can do even better.

+

Most work-stealing implementations on the JVM delegate to ForkJoinThreadPool. There are a number of problems with this thread pool implementation, not the least of which is that it tries to be a little too clever around thread blocking. In essence, the work-stealing implementation in the JDK has to render itself through a highly generic interface – literally just execute(Runnable): Unit! – and cannot take advantage of the deep system knowledge that we have within the Cats Effect framework. Specifically, we know some very important things:

+
    +
  • Hard-blocking worker threads are very rare, if they exist at all. Users of Cats Effect tend to be quite good about leveraging blocking and other tools to avoid hard-blocking the main compute pool, meaning that we can optimize assuming that this case is exceptionally rare and we don't need to try to detect blocked threads and speculatively grow the pool. This keeps our worker thread count exactly where it should be: pinned to the number of physical CPUs.
  • +
  • Fibers represent sequential workflows that maintain working memory sets across suspension boundaries. This is a more complicated thing to conceptualize, but it makes a lot of intuitive sense. When our request/response fiber in our running example hears back from downstream, it's going to pick up exactly where it left off with the same data that it was touching prior to its suspension. This is a huge thing to optimize for, because memory working sets are very expensive, and we can leverage this knowledge to reduce page faults and improve CPU cache utilization.
  • +
+

The way in which we optimize for fibers specifically is by ensuring that fiber suspension and wake-up is kept pinned to a single worker thread per fiber as much as possible. Obviously, if one thread is completely overloaded by work and the other threads are idle, then the fiber is going to move over to those other threads and the result is going to be a page fault, but in the common case this simply doesn't happen. Each thread is responsible for some number of fibers, and those fibers remain on that thread as long as possible. This allows the operating system thread scheduler in turn to live within its happy-path assumptions about memory utilization, keeping fiber memory state resident within the L3 and L2 caches for longer, even across asynchronous boundaries! This has a massive impact on application performance, because it means that fibers are able to avoid round-trips to system main memory in the common case.

+

This is in contrast to the single shared queue implementation that is all-too common in this space. With a single shared work queue, once a fiber suspends, there is no guarantee that it will be reallocated back to the thread it was on prior to suspension. In fact, the odds of this drop to almost nothing as you increase the number of workers, and so again the general pattern gets much worse as you scale up the number of CPUs. Remember that any time semantically-sequential work is moved from one thread to another, that work is usually also going to be moving from one CPU to another, meaning that all of the nice caching benefits that we normally get from the memory cache hierarchy are blown away as we have to laboriously re-build our working set from main memory (a page fault). A fiber-aware work-stealing scheduler avoids this problem almost all of the time.

+

This also means that the fairness problem can be solved effectively for free, since a fiber is able to yield control back to its worker thread without incurring the penalty of a page fault or even a memory barrier! In the event that the worker thread has other fibers waiting for time, those fibers will take over at the yield and the starvation is averted (keeping your p99s low!). If the worker thread does not have other fibers waiting for time, then the yielding fiber picks up right where it left off with almost exactly zero cost, meaning that we're able to solve fairness with no cost to throughput.

+ +

Conclusion

+

There's a lot more going on in this layer than just what I've described, but hopefully this gives a rough intuition as to why Cats Effect 3's scheduler is able to achieve the crazy performance numbers we see in practice, and also why it is that fibers are not only an easier model for writing highly scalable modern applications, they are also a faster and more efficient model for running those applications at scale.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + +
+ I write code, read papers, and think thoughts. Broadly, I'm interested in: type theory, parser theory, functional abstractions, data structures, performance. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/fix.html b/blog/fix.html new file mode 100644 index 00000000..20376c22 --- /dev/null +++ b/blog/fix.html @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + Primitive recursion with fix and Mu + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Primitive recursion with fix and Mu

+ + + technical + +
+
+
+
+

Primitive recursion with fix and Mu

+

Consider the simple cons-list datatype.

+
import scalaz.Equal, scalaz.std.option._, scalaz.syntax.std.option._,
+       scalaz.std.anyVal._, scalaz.std.function._,
+       scala.reflect.runtime.universe.reify
+
+sealed abstract class XList[A]
+final case class XNil[A]() extends XList[A]
+final case class XCons[A](head: A, tail: XList[A]) extends XList[A]
+

And a simple function over this structure. Say, a simple summing + function.

+
def sum(xs: XList[Int]): Int = xs match {
+  case XNil() => 0
+  case XCons(x, xs) => x + sum(xs)
+}
+

And that seems to work out alright.

+
scala> val nums = XCons(2, XCons(3, XCons(4, XCons(42, XNil()))))
+nums: XCons[Int] = XCons(2,XCons(3,XCons(4,XCons(42,XNil()))))
+
+scala> sum(nums)
+res0: Int = 51
+

Has it ever struck you as curious that, though its own value was + required to construct a value like sum, the system has no problem + with that?

+

Oh, well, that's just a recursive function, you say. Well, what's so + special about recursive functions? Why do they get special treatment + so that they can define themselves with themselves?

+ +

Induction and termination

+

First, let's be clear: there's a limit to how much of sum can be + used in its own definition.

+

Let us consider the moral equivalent of the statement “this function + gives the sum of a list of integers because it is the function that + gives the sum of a list of integers.”

+
def sum2(xs: XList[Int]): Int = sum2(xs)
+

scalac will compile this definition; it is well-typed. However, it + will be nonsensical at runtime, because it is nonsense; it will either + throw some exception or loop forever.

+

Let us consider a similar case: the infinite list of 42s.

+
scala> val fortyTwos: Stream[Int] = 42 #:: fortyTwos
+fortyTwos: Stream[Int] = Stream(42, ?)
+
+scala> fortyTwos take 5 toList
+res0: List[Int] = List(42, 42, 42, 42, 42)
+

The definition of fortyTwos is like that of sum; it uses its own + value while constructing said value. A similar definition to sum2 + is, likewise, nonsense, though scalac can catch this particular case:

+
scala> val fortyTwos2: Stream[Int] = fortyTwos2
+<console>:7: error: value fortyTwos2 does nothing other than call itself recursively
+       val fortyTwos2: Stream[Int] = fortyTwos2
+                                     ^
+

Obviously, functions aren't special; non-function values, like + functions, can be defined using their own values. But how can we + characterize the difference between the good, terminating definitions, + and the bad, nonterminating definitions?

+

Proof systems like Coq and Agda perform a strong check on recursive + definitions; for definitions like sum, they require the recursion + match the structure of the data type, just as ours does, so that each + recursive call is known to operate over smaller data. For definitions + like fortyTwos, they apply other strategies. In Scala, we have to + make do with informality.

+

I like to think of it this way: a recursive definition must always + perform at least one inductive step. sum does so because, in the + recursive case, it gives “supposing I have the sum of tail, the sum + is the head plus that.” fortyTwos does because it says “the value + fortyTwos is 42 consed onto the value fortyTwos.” It is, at + least, the start of a systematic way of thinking about terminating + recursive definitions.

+ +

Abstracting the recursion

+

Now that we have a framework for thinking about what is required in a + recursive definition, we can start abstracting over it.

+

The above recursive definitions were accomplished with special + language support: the right-hand side of any term definition, val or + def, can refer to the thing being so defined. Scalaz provides + the fix function, + which, if it were provided intrinsically, would eliminate the need for + this language support.

+
def fix[A](f: (=> A) => A): A = {
+  lazy val a: A = f(a)
+  a
+}
+

In this definition, the value returned by f is the value given to + it as an argument. It's a by-name argument because that's how we + enforce the requirement: f must perform at least one inductive step + in the definition of its result, though it can refer to that result by + its argument, which we enforce by requiring it to return a value + before evaluating that argument.

+

Let's redefine sum with fix, after importing it from + scalaz.std.function.

+
val sum3: XList[Int] => Int = fix[XList[Int] => Int](rec => {
+  case XNil() => 0
+  case XCons(x, xs) => x + rec.apply(xs)
+})
+

And Scala thinks that's alright.

+
scala> sum3(nums)
+res1: Int = 51
+

The interesting thing here is that sum3's definition doesn't refer + to the name sum3; the recursion is entirely inside the fix + argument. So one advantage of fix is that it's easy to write + recursive values as expressions without giving them a name.

+

For example, there's the definition of fortyTwos:

+
scala> fix[Stream[Int]](42 #:: _)
+res2: Stream[Int] = Stream(42, ?)
+
+scala> res2 take 5 toList
+res3: List[Int] = List(42, 42, 42, 42, 42)
+ +

For special data structures

+

It can be inconvenient to avoid evaluating the argument when providing + an induction step. Fortunately, the requirement that f be nonstrict + in its argument is too strong to characterize the space of values that + can be defined with fix-style recursion.

+

For a given data type, there's often a way to abstract out the + nonstrictness. For example, here's an Equal instance combinator + that is fully evaluated, but doesn't force the argument until after + the (equivalent) result has been produced.

+
def lazyEqual[A](A: => Equal[A]): Equal[A] = new Equal[A] {
+  def equal(l: A, r: A): Boolean = A equal (l, r)
+  override def equalIsNatural = A.equalIsNatural
+}
+

Given that, we can produce a fix variant for Equal that passes the + Equal argument strictly. You're simply not allowed to invoke any of + the typeclass's methods.

+
def fixEq[A](f: Equal[A] => Equal[A]): Equal[A] =
+  fix[Equal[A]](A => f(lazyEqual(A)))
+

And now, we have the machinery to build a fully derived Equal + instance for XList, without function recursion, by defining the base + case and inductive step!

+
def `list equal`[A: Equal]: Equal[XList[A]] =
+  fixEq[XList[A]](implicit rec =>
+    Equal.equalBy[XList[A], Option[(A, XList[A])]]{
+      case XNil() => None
+      case XCons(x, xs) => Some((x, xs))
+    })
+

That works out to interesting compiled output. Note especially the + last line, and its (strict) use of rec towards the end.

+
scala> reify(fixEq[XList[Int]](implicit rec =>
+     |     Equal.equalBy[XList[Int], Option[(Int, XList[Int])]]{
+     |       case XNil() => None
+     |       case XCons(x, xs) => Some((x, xs))
+     |     }))
+res10: reflect.runtime.universe.Expr[scalaz.Equal[XList[Int]]] = 
+Expr[scalaz.Equal[XList[Int]]]($read.fixEq[$read.XList[Int]](((implicit rec) =>
+ Equal.equalBy[$read.XList[Int], Option[Tuple2[Int, $read.XList[Int]]]]
+ (((x0$1) => x0$1 match {
+  case $read.XNil() => None
+  case $read.XCons((x @ _), (xs @ _)) => Some.apply(Tuple2.apply(x, xs))
+}))(option.optionEqual(tuple.tuple2Equal(anyVal.intInstance, rec))))))
+

f0, a binary serialization library, + uses a similar technique + to help define codecs on recursive data structures.

+ +

What about XList?

+

If we can abstract out the idea of recursive value definitions, what + about recursive type definitions? Well, thanks to higher kinds, sure! + Scalaz doesn't provide it, but it is commonly called Mu.

+
final case class Mu[F[_]](value: F[Mu[F]])
+

We have to put a class in the middle of it so that we don't have an + infinite type; Haskell has a similar restriction. But the principle + is the same as with fix: feed one datatype induction step F to the + higher-order type Mu and it will feed F's result back to itself.

+

For example, here is the equivalent definition of XList with Mu.

+
type XList2Step[A] = {type λ[α] = Option[(A, α)]}
+type XList2[A] = Mu[XList2Step[A]#λ]
+

Note the typelambda's similarity to the second type argument to + #equalBy above. And for demonstration, the isomorphism with + XList.

+
def onetotwo[A](xs: XList[A]): XList2[A] = xs match {
+  case XNil() => Mu[XList2Step[A]#λ](None)
+  case XCons(x, xs) => Mu[XList2Step[A]#λ](Some((x, onetotwo(xs))))
+}
+
+def twotoone[A](xs: XList2[A]): XList[A] =
+  xs.value cata ({case (x, xs) => XCons(x, twotoone(xs))}, XNil())
+

Of course, fix lends itself to both of these definitions; I have + left its use off here. But let's check those functions:

+
scala> onetotwo(nums)
+res11: XList2[Int] = Mu(Some((2,Mu(Some((3,Mu(Some((4,Mu(Some((42,Mu(None)))))))))))))
+
+scala> twotoone(res11)
+res12: XList[Int] = XCons(2,XCons(3,XCons(4,XCons(42,XNil()))))
+ +

fix over Mu

+

And, finally, the associated general Equal definition for Mu. The + contramap step is just noise to deal with the fact that the Mu + structure has to actually exist; you can ignore it for the most part.

+
def equalMu[F[_]](fa: Equal[Mu[F]] => Equal[F[Mu[F]]]): Equal[Mu[F]] =
+  fixEq[Mu[F]](emf => fa(emf) contramap (_.value))
+

The evidence we really want is forall a. Equal[a] => Equal[F[a]], + but that's too hard to express in Scala, so this does it in a pinch. + All we're interested in is that we can derive F's equality given the + equality of any type argument given to it. Let's prove that we have + such an Equal-lifter:

+
// redefined because Tuple2Equal scalaz is strict on equalIsNatural
+class Tuple2Equal[A1, A2](_1: Equal[A1], _2: Equal[A2])
+    extends Equal[(A1, A2)] {
+  def equal(f1: (A1, A2), f2: (A1, A2)) =
+    _1.equal(f1._1, f2._1) && _2.equal(f1._2, f2._2)
+  override def equalIsNatural: Boolean = _1.equalIsNatural && _2.equalIsNatural
+}
+implicit def tup2eq[A1: Equal, A2: Equal] =
+  new Tuple2Equal[A1, A2](implicitly, implicitly)
+
+abstract class Blah // just a placeholder
+
+scala> {implicit X: Equal[Blah] => implicitly[Equal[XList2Step[Int]#λ[Blah]]]}
+res4: scalaz.Equal[Blah] => scalaz.Equal[Option[(Int, Blah)]] = <function1>
+

And now that we have F equality, we're done, because Mu is Fs + all the way down.

+
scala> equalMu[XList2Step[Int]#λ](implicit fa => implicitly)
+res5: scalaz.Equal[Mu[[α]Option[(Int, α)]]] = scalaz.Equal$$anon$2@de52bcf
+
+scala> res5 equal (onetotwo(nums), onetotwo(nums))
+res6: Boolean = true
+
+scala> res5 equal (onetotwo(nums), onetotwo(XCons(3,nums)))
+res7: Boolean = false
+

This article was tested with Scala 2.10.4 & Scalaz 7.0.6.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/forget-refinement-aux.html b/blog/forget-refinement-aux.html new file mode 100644 index 00000000..fb1d0355 --- /dev/null +++ b/blog/forget-refinement-aux.html @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + What happens when I forget a refinement? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

What happens when I forget a refinement?

+ + + technical + +
+
+
+
+

What happens when I forget a refinement?

+

This is the third of a series of articles on “Type Parameters and + Type Members”. If you haven’t yet, you should + start at the beginning, + which introduces code we refer to throughout this article without + further ado.

+

As I mentioned + in the previous article, + the error of the mdropFirstE signature, taking MList and returning + merely MList, was to fail to relate the input element type to the + output element type. This mistake is an easy one to make when failure + is the default behavior.

+

By contrast, when we try this with PList, the compiler helpfully + points out our error.

+
def pdropFirst(xs: PList): PList = ???
+
+TmTp3.scala:6: class PList takes type parameters
+  def pdropFirst(xs: PList): PList = ???
+                             ^
+TmTp3.scala:6: class PList takes type parameters
+  def pdropFirst(xs: PList): PList = ???
+                     ^
+ +

What happens when I misspell a refinement?

+

There is another mistake that type members open you up to. I have been + using the very odd type parameter—and member—name T. + Java developers will find this choice very ordinary, but the name of + choice for the discerning Scala programmer is A. So suppose I + attempted to correct mdropFirstE’s type as follows:

+
def mdropFirstE2[T0](xs: MList {type A = T0}) =
+  xs.uncons match {
+    case None => MNil()
+    case Some(c) => c.tail
+  }
+

This method compiles, but I cannot invoke it!

+
> mdropFirstE2(MNil[Int]())
+<console>:20: error: type mismatch;
+ found   : tmtp.MNil{type T = Int}
+ required: tmtp.MList{type A = ?}
+       mdropFirstE2(MNil[Int]())
+                             ^
+
+> mdropFirstE2(MCons[Int](42, MNil[Int]()))
+<console>:20: error: type mismatch;
+ found   : tmtp.MCons{type T = Int}
+ required: tmtp.MList{type A = ?}
+       mdropFirstE2(MCons[Int](42, MNil[Int]()))
+                              ^
+

That’s because MList {type A = T0} is a perfectly reasonable + intersection type: values of this type have both the type MList in + their supertype tree somewhere, and a type member named A, which + is bound to T0. In terms of subtyping relationships:

+
MList {type A = T0} <: MList
+// and unrelatedly,
+MList {type A = T0} <: {type A = T0}
+

That MList has no such type member A is irrelevant to the + intersection and refinement of types in Scala. This type means “an + instance of the trait MList, with a type member named A set + to T0”. This type member A could come from another trait mixed + with MList or an inline subclass. Whether such a thing is + impossible to instantiate—due to sealed, final, or anything + else—is also irrelevant; types with no values are meaningful and + useful in both Java and Scala.

+ +

Why T0? What’s Aux?

+

A few of the methods on MList we have seen so far take a type + parameter T0 instead of T. This is just a mnemonic trick; I’m + saying “I would write T here if scalac would let me”, which I have + borrowed from + scalaz.Unapply. + Let’s try to implement def MNil taking a T instead.

+
def MNil[T](): MNil {type T = T} =
+  new MNil {
+    type T = T
+  }
+
+// Scala complains, though:
+TmTp3.scala:15: illegal cyclic reference involving type T
+  def MNil[T](): MNil {type T = T} =
+                                ^
+

This is a scoping problem; the refinement type makes the member T + shadow our method type parameter T. We dealt with the problem in + MList#uncons and MCons#tail as well, way back in section “Two + lists, all alike” of + the first part, + in those cases by outer-scoping the T as + self.T instead.

+

When defining a type with members, you should define an Aux type + in your companion that converts the member to a type parameter. The + name Aux is a convention I have borrowed from + Shapeless ops. + This is pretty much boilerplate; in this case, add this to + object MList:

+
type Aux[T0] = MList {type T = T0}
+

Now you can write MList.Aux[Int] instead of MList {type T = Int}. + Here’s mdropFirstT’s signature rewritten in this style.

+
def mdropFirstT2[T](xs: MList.Aux[T]): MList.Aux[T] = ???
+

Furthermore, because the member T is not in scope for Aux’s type + parameter position, you can take method type parameters named T and + sensibly write MList.Aux[T] without the above error. You can see + this in the immediately preceding example. But, stepping back a bit, + this should be considered an advantage for type parameters more + generally; PList doesn’t have this problem in the first place.

+

Using Aux also helps you avoid the errors of forgetting to specify + or misspelling a type member, as described at the beginning of this + article. With Aux, as with ordinary parameterized types, a missing + argument is caught by the compiler, and misspelling the parameter name + is impossible.

+

In + the next part, “Type projection isn’t that specific”, + we’ll see why something that, at first glance, seems + like a workable alternative to either refinement or the Aux trick, + doesn’t work out as well as people wish it would.

+

This article was tested with Scala 2.11.7.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/four-ways-to-escape-a-cake.html b/blog/four-ways-to-escape-a-cake.html new file mode 100644 index 00000000..91004223 --- /dev/null +++ b/blog/four-ways-to-escape-a-cake.html @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + Four ways to escape a cake + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Four ways to escape a cake

+ + + technical + +
+
+
+
+

Four ways to escape a cake

+

The mixin style of importing in which classes and traits are defined + within traits, as seen in scala.reflect.Universe, ScalaTest, and + other Scala styles, seems to be infectious. By that, I mean once you + define something in a trait to be mixed in, to produce another + reusable module that calls that thing, you must define another + trait, and so must anyone using your module, and so on and so forth. + You effectively become “trapped in the cake”.

+

However, we can use type parameters that represent singleton types + to write functions that are polymorphic over these “cakes”, without + being defined as part of them or mixed in themselves. For example, you + can use this to write functions that operate on elements of a + reflection universe, without necessarily passing that universe around + all over the place.

+

Well, for the most part. Let’s see how far this goes.

+ +

Our little universe

+

Let’s set aside the heavyweight real-world examples I mentioned above + in favor of a small example. Then, we should be able to explore the + possibilities in this simpler space.

+
final case class LittleUniverse() {
+  val haystack: Haystack = Haystack()
+
+  final case class Haystack() {
+    def init: Needle = Needle()
+    def iter(n: Needle): Needle = n
+  }
+  
+  final case class Needle()
+}
+

For brevity, I’ve defined member classes, but this article equally + applies if you are using abstract types instead, as any Functional + programmer of pure, virtuous heart ought to!

+

Suppose we have a universe.

+
scala> val lu: LittleUniverse = LittleUniverse()
+lu: LittleUniverse = LittleUniverse()
+

The thing that Scala does for us is not let Haystacks and Needle s + from one universe be confused with those from another.

+
val anotherU = LittleUniverse()
+
+scala> lu.haystack.iter(anotherU.haystack.init)
+<console>:14: error: type mismatch;
+ found   : anotherU.Needle
+ required: lu.Needle
+       lu.haystack.iter(anotherU.haystack.init)
+                                          ^
+

The meaning of this error is “you can’t use one universe’s Haystack + to iter a Needle from another universe”.

+

This doesn’t look very important given the above code, but it’s a + real boon to more complex scenarios. You can set up a lot of + interdependent abstract invariants, verify them all, and have the + whole set represented with the “index” formed by the singleton type, + here lu.type or anotherU.type.

+ +

Working with a universe on hand

+

Refactoring in macro-writing style seems to be based upon passing the + universe around everywhere. We can do that.

+
def twoInits(u: LittleUniverse): (u.Needle, u.Needle) =
+  (u.haystack.init, u.haystack.init)
+  
+def stepTwice(u: LittleUniverse)(n: u.Needle): u.Needle =
+  u.haystack.iter(u.haystack.iter(n))
+

The most important feature we’re reaching for with these fancy + dependent method types, and the one that we have to keep reaching + for if we want to write sane functions outside the cake, is + preserving the singleton type index.

+
scala> twoInits(lu)
+res3: (lu.Needle, lu.Needle) = (Needle(),Needle())
+
+scala> stepTwice(anotherU)(anotherU.haystack.init)
+res4: anotherU.Needle = Needle()
+

These values are ready for continued itering, or whatever else + you’ve come up with, in the confines of their respective + universes. That’s because they’ve “remembered” where they came from.

+

By contrast, consider a simple replacement of the path-dependencies + with a type projection.

+
def brokenTwoInits(u: LittleUniverse)
+    : (LittleUniverse#Needle, LittleUniverse#Needle) =
+  (u.haystack.init, u.haystack.init)
+
+scala> val bti = brokenTwoInits(lu)
+bti: (LittleUniverse#Needle, LittleUniverse#Needle) = (Needle(),Needle())
+

That seems to be okay, until it’s time to actually use the result.

+
scala> lu.haystack.iter(bti._1)
+<console>:14: error: type mismatch;
+ found   : LittleUniverse#Needle
+ required: lu.Needle
+       lu.haystack.iter(bti._1)
+                            ^
+

The return type of brokenTwoInits “forgot” the index, lu.type.

+ +

Getting two needles without a universe

+

When we pass a LittleUniverse to the above functions, we’re also + kind of passing in a constraint on the singleton type created by the + argument variable. That’s how we know that the returned u.Needle is + a perfectly acceptable lu.Needle in the caller scope, when we pass + lu as the universe.

+

However, as the contents of a universe become more complex, there are + many more interactions that need not involve a universe at all, at + least not directly.

+
def twoInitsFromAHaystack[U <: LittleUniverse](
+    h: U#Haystack): (U#Needle, U#Needle) =
+  (h.init, h.init)
+  
+scala> val tifah = twoInitsFromAHaystack[lu.type](lu.haystack)
+tifah: (lu.Needle, lu.Needle) = (Needle(),Needle())
+

Since we didn’t pass in lu, how did it know that the returned + Needles were lu.Needles?

+
    +
  1. The type of lu.haystack is lu.Haystack.
  2. +
  3. That type is shorthand for lu.type#Haystack.
  4. +
  5. We passed in U = lu.type, and our argument meets the resulting + requirement for a lu.type#Haystack (after expanding U).
  6. +
  7. The type of the expression h.init is + u.Needle forSome {val u: U}. We use an existential because the + relevant variable (and its singleton type) is not in scope.
  8. +
  9. This type widens to U#Needle, satisfying the expected return + type.
  10. +
+

This seems like a more complicated way of doing things, but it’s very + freeing: by not being forced to necessarily pass the universe around + everywhere, you’ve managed to escape the cake’s clutches much more + thoroughly. You can also write syntax enrichments on various members + of the universe that don’t need to talk about the universe’s value, + just its singleton type.

+

Unless, you know, the index appears in contravariant position.

+ +

Syntactic stepTwice

+

One test of how well we’ve managed to escape the cake is to be able to + write enrichments that deal with the universe. This is a little + tricky, but quite doable if you have the universe’s value.

+

With the advent of implicit class, this became a little easier to do + wrongly, but it’s a good start.

+
implicit class NonWorkingStepTwice(val u: LittleUniverse) {
+  def stepTwiceOops(n: u.Needle): u.Needle =
+    u.haystack.iter(u.haystack.iter(n))
+}
+

That compiles okay, but seemingly can’t actually be used!

+
scala> lu stepTwiceOops lu.haystack.init
+<console>:15: error: type mismatch;
+ found   : lu.Needle
+ required: _1.u.Needle where val _1: NonWorkingStepTwice
+       lu stepTwiceOops lu.haystack.init
+                                    ^
+

There’s a hint in that we had to write val u, not u, nor private +val u, in order for the implicit class itself to compile. This + signature tells us that there’s an argument of type + LittleUniverse, and a member u: LittleUniverse. However, whereas + with the function examples above, we (and the compiler) could trust + that they’re one and the same, we have no such guarantee here. So we + don’t know that an lu.Needle is a u.Needle. We didn’t get far + enough, but we don’t know that a u.Needle is an lu.Needle, either.

+ +

Relatable variables

+

Instead, we have to expand a little bit, and take advantage of a very + interesting, if obscure, element of the type equivalence rules in the + Scala language.

+
class WorkingStepTwice[U <: LittleUniverse](val u: U) {
+  def stepTwice(n: u.Needle): u.Needle =
+    u.haystack.iter(u.haystack.iter(n))
+}
+
+implicit def WorkingStepTwice[U <: LittleUniverse](u: U)
+    : WorkingStepTwice[u.type] =
+  new WorkingStepTwice(u)
+

Unfortunately, the ritual of expanding the implicit class shorthand + is absolutely necessary; the implicit class won’t generate the + dependent-method-typed implicit conversion we need.

+

Now we can get the proof we need.

+
scala> lu stepTwice lu.haystack.init
+res7: _1.u.Needle forSome { val _1: WorkingStepTwice[lu.type] } = Needle()
+
+// that's a little weird, but reduces to what we need
+scala> res7: lu.Needle
+res8: lu.Needle = Needle()
+

How does this work?

+
    +
  1. Implicitly convert lu, giving us a conv: +WorkingStepTwice[lu.type].
  2. +
  3. This means that conv.u: lu.type, by expansion of U.
  4. +
  5. This in turn means that conv.u.type <: lu.type.
  6. +
+

The next part is worth taking in two parts. It may be worth + having + §3.5.2 “Conformance” of + the language spec open for reference. First, let’s consider the return + type (a covariant position), which is simpler.

+
    +
  1. The return type expands to conv.u.type#Needle.
  2. +
  3. The ninth conformance bullet point tells us that the left side of a + # projection is covariant, so because conv.u.type <: lu.type + (see above), the return type widens to lu.type#Needle.
  4. +
  5. For this, lu.Needle is a shorthand.
  6. +
+

It was far longer until I realized how the argument type works. You’ll + want to scroll up on the SLS a bit, to the “Equivalence” section. Keep + in mind that we are trying to widen lu.Needle to conv.u.Needle, + which is the reverse of what we did for the return type.

+
    +
  1. Our argument’s type expands to lu.type#Needle.
  2. +
  3. The second bullet point under “Equivalence” says that “If a path + p has a singleton type q.type, then p.type ≡ q.type.” + From this, we can derive that conv.u.type = lu.type. This is a + stronger conclusion than we reached above!
  4. +
  5. We substitute the left side of the # using the equivalence, + giving us conv.u.type#Needle.
  6. +
+

I cannot characterize this feature of the type system as anything + other than “really freaky” when you first encounter it. It seems like + an odd corner case. Normally, when you write val x: T, then x.type + is a strict subtype of T, and you can count on that, but this + carves out an exception to that rule. It is sound, though, and an + absolutely essential feature!

+
val sameLu: lu.type = lu
+
+scala> sameLu.haystack.iter(lu.haystack.init)
+res9: sameLu.Needle = Needle()
+

Without this rule, even though we have given it the most specific type + possible, sameLu couldn’t be a true substitute for lu in all + scenarios. That means that in order to make use of singleton type + indices, we would be forever beholden to the variable we initially + stored the value in. I think this would be extremely inconvenient, + structurally, in almost all useful programs.

+

With the rule in place, we can fully relate the lu and conv.u + variables, to let us reorganize how we talk about universes and values + indexed by their singleton types in many ways.

+ +

A pointless argument

+

Let’s try to hide the universe. We don’t need it, after all. We can’t + refer to u in the method signature anymore, so let’s try the same + conversion we used with twoInitsFromAHaystack. We already have the + U type parameter, after all.

+
class CleanerStepTwice[U <: LittleUniverse](private val u: U) {
+  def stepTwiceLively(n: U#Needle): U#Needle =
+    ???
+}
+
+implicit def CleanerStepTwice[U <: LittleUniverse](u: U)
+    : CleanerStepTwice[u.type] =
+  new CleanerStepTwice(u)
+

This has the proper signature, and it’s cleaner, since we don’t expose + the unused-at-runtime u variable anymore. We could refine a little + further, and replace it with a U#Haystack, just as with + twoInitsFromAHaystack.

+

This gives us the same interface, with all the index preservation we + need. Even better, it infers a nicer return type.

+
scala> def trial = lu stepTwiceLively lu.haystack.init
+trial: lu.Needle
+

Now, let’s turn to implementation.

+
class OnceMoreStepTwice[U <: LittleUniverse](u: U) {
+  def stepTwiceFinally(n: U#Needle): U#Needle =
+    u.haystack.iter(u.haystack.iter(n))
+}
+
+<console>:18: error: type mismatch;
+ found   : U#Needle
+ required: OnceMoreStepTwice.this.u.Needle
+           u.haystack.iter(u.haystack.iter(n))
+                                           ^
+

This is the last part of the escape! If this worked, we could fully + erase the LittleUniverse from most code, relying on the pure + type-level index to prove enough of its existence! So it’s a little + frustrating that it doesn’t quite work.

+

Let’s break it down. First, the return type is fine.

+
    +
  1. Since u: U, u.type <: U. (This is true, and useful, in the + scope of u, which is now invisible to the caller.)
  2. +
  3. + iter returns a u.type#Needle. +
      +
    • Note: since u is not in scope for the caller, if we returned + this as is, it would effectively widen to the existentially + bound u.type#Needle forSome {val u: U}. But the same logic in + the next step would apply to that type.
    • +
    +
  4. +
  5. By the # left side covariance, u.type#Needle widens to + U#Needle.
  6. +
+

Pretty simple, by the standards of what we’ve seen so far.

+ +

Contravariance is the root of all…

+

But things break down when we try to call iter(n). Keep in mind that + n: U#Needle and the expected type is u.Needle. Specifically: since + we don’t know in the implementation that U is a singleton type, we + can’t use the “singleton type equivalence” rule on it! But suppose + that we could; that is, suppose that we could constrain U to be + a singleton type.

+
    +
  1. The argument type is U#Needle.
  2. +
  3. By singleton equivalence, since u: U and u is stable, so + u.type = U.
  4. +
  5. By substituting the left-hand side of the #, we get + u.type#Needle.
  6. +
  7. This shortens to u.Needle.
  8. +
+

If we are unable to constrain U in this way, though, we are + restricted to places where U occurs in covariant position when using + cake-extracted APIs. We can invoke functions like init, because + they only have the singleton index occurring in covariant position.

+

Invoking functions like iter, where the index occurs in + contravariant or invariant position, requires being able to add this + constraint, so that we can use singleton equivalence directly on the + type variable U. This is quite a bit trickier.

+ +

Extracting more types

+

We have the same problem with the function version.

+
def stepTwiceHaystack[U <: LittleUniverse](
+    h: U#Haystack, n: U#Needle): U#Needle =
+  h.iter(h.iter(n))
+
+<console>:18: error: type mismatch;
+ found   : U#Needle
+ required: _1.Needle where val _1: U
+         h.iter(h.iter(n))
+                       ^
+

Let’s walk through it one more time.

+
    +
  1. n: U#Needle.
  2. +
  3. h.iter expects a u.type#Needle for all val u: U.
  4. +
  5. + Suppose that we constrain U to be a singleton type: +
      +
    1. (The existential) u.type = U, by singleton equivalence.
    2. +
    3. By # left side equivalence, h.iter expects a U#Needle.
    4. +
    +
  6. +
+

The existential variable complicates things, but the rule is sound.

+

As a workaround, it is commonly suggested to extract the member types + in question into separate type variables. This works in some cases, + but let’s see how it goes in this one.

+
def stepTwiceExUnim[N, U <: LittleUniverse{type Needle = N}](
+    h: U#Haystack, n: N): N = ???
+

This looks a lot weirder, but should be able to return the right type.

+
scala> def trial2 = stepTwiceExUnim[lu.Needle, lu.type](lu.haystack, lu.haystack.init)
+trial2: lu.Needle
+

But this situation is complex enough for the technique to not work.

+
def stepTwiceEx[N, U <: LittleUniverse{type Needle = N}](
+    h: U#Haystack, n: N): N =
+  h.iter(h.iter(n))
+
+<console>:18: error: type mismatch;
+ found   : N
+ required: _1.Needle where val _1: U
+         h.iter(h.iter(n))
+                       ^
+

Instead, we need to index Haystack directly with the Needle + type, that is, add a type parameter to Haystack so that its Needle + arguments can be talked about completely independently of the + LittleUniverse, and then to write h: U#Haystack[N] + above. Essentially, this means that any time a type talks about + another type in a Universe, you need another type parameter to + redeclare a little bit of the relationships between types in the + universe.

+

The problem with this is that we already declared those relationships + by declaring the universe! All of the non-redundant information is + represented in the singleton type index. So even where the above + type-refinement technique works (and it does in many cases), it’s + still redeclaring things that ought to be derivable from the “mere” + fact that U is a singleton type.

+ +

The fact that it’s a singleton type

+

(The following is based on enlightening commentary by Daniel Urban on + an earlier draft.)

+

Let’s examine the underlying error in stepTwiceEx more directly.

+
scala> def fetchIter[U <: LittleUniverse](
+    h: U#Haystack): U#Needle => U#Needle = h.iter
+<console>:14: error: type mismatch;
+ found   : _1.type(in method fetchIter)#Needle
+             where type _1.type(in method fetchIter) <: U with Singleton
+ required: _1.type(in value $anonfun)#Needle
+             where type _1.type(in value $anonfun) <: U with Singleton
+           h: U#Haystack): U#Needle => U#Needle = h.iter
+                                                    ^
+

It’s a good thing that this doesn’t compile. If it did, we could do

+
fetchIter[LittleUniverse](lu.haystack)(anotherU.haystack.init)
+

Which is unsound.

+

§3.2.1 “Singleton Types” of + the specification mentions this Singleton, which is in a way related + to singleton types.

+
A stable type is either a singleton type or a type which is + declared to be a subtype of trait scala.Singleton.
+

Adding with Singleton to the upper bound on U causes fetchIter + to compile! This is sound, because we are protected from the above + problem with the original fetchIter.

+
def fetchIter[U <: LittleUniverse with Singleton](
+    h: U#Haystack): U#Needle => U#Needle = h.iter
+
+scala> fetchIter[lu.type](lu.haystack)
+res3: lu.Needle => lu.Needle = $$Lambda$1397/1159581520@683e7892
+
+scala> fetchIter[LittleUniverse](lu.haystack)
+<console>:16: error: type arguments [LittleUniverse] do not conform
+                     to method fetchIter's type parameter bounds
+                     [U <: LittleUniverse with Singleton]
+       fetchIter[LittleUniverse](lu.haystack)
+                ^
+

Let’s walk through the logic for fetchIter. The expression h.iter + has type u.Needle => u.Needle for some val u: U, and our goal type + is U#Needle => U#Needle. So we have two subgoals: prove + u.Needle <: U#Needle for the covariant position (after =>), and + U#Needle <: u.Needle for the contravariant position (before =>).

+

First, covariant:

+
    +
  1. Since u: U, u.type <: U.
  2. +
  3. Since the left side of # is covariant, #1 implies + u.type#Needle <: U#Needle.
  4. +
  5. This re-sugars to u.Needle <: U#Needle, which is the goal.
  6. +
+

Secondly, contravariant. We’re going to have to make a best guess + here, because it’s not entirely clear to me what’s going on.

+
    +
  1. Since (existential) path u has a singleton type U (if we define + “has a singleton type” as “having a type X such that + X <: Singleton”), so u.type = U by the singleton equivalence.
  2. +
  3. Since equivalence implies conformance, according to the first + bullet under “Conformance”, #1 implies U <: u.type.
  4. +
  5. Since the left side of # is covariant, #2 implies that + U#Needle <: u.type#Needle.
  6. +
  7. This resugars to U#Needle <: u.Needle, which is the goal.
  8. +
+

I don’t quite understand this, because U doesn’t seem to meet the + requirements for “singleton type”, according to the definition of + singleton types. However, I’m fairly sure it’s sound, since type + stability seems to be the property that lets us avoid the + universe-mixing unsoundness. Unfortunately, it only seems to work with + existential vals; we seem to be out of luck with vals that the + compiler can still see.

+
// works fine!
+def stepTwiceSingly[U <: LittleUniverse with Singleton](
+    h: U#Haystack, n: U#Needle): U#Needle = {
+  h.iter(h.iter(n))
+}
+
+// but alas, this form doesn't
+class StepTwiceSingly[U <: LittleUniverse with Singleton](u: U) {
+  def stepTwiceSingly(n: U#Needle): U#Needle =
+    u.haystack.iter(u.haystack.iter(n))
+}
+
+<console>:15: error: type mismatch;
+ found   : U#Needle
+ required: StepTwiceSingly.this.u.Needle
+           u.haystack.iter(u.haystack.iter(n))
+                                            ^
+

We can work around this by having the second form invoke the first + with the Haystack, thus “existentializing” the universe. I imagine + that most, albeit not all, cakes can successfully follow this + strategy.

+

So, finally, we’re almost out of the cake.

+
    +
  1. Escape covariant positions with universe variable: complete.
  2. +
  3. Escape contravariant/invariant positions with universe variable: + complete.
  4. +
  5. Escape covariant positions with universe singleton type: + complete!
  6. +
  7. Escape contravariant/invariant positions with universe singleton + type: 90% there!
  8. +
+

This article was tested with Scala 2.12.1.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/generic-numeric-programming.html b/blog/generic-numeric-programming.html new file mode 100644 index 00000000..36d8ac96 --- /dev/null +++ b/blog/generic-numeric-programming.html @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + + + + + + + + An Intro to Generic Numeric Programming with Spire + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

An Intro to Generic Numeric Programming with Spire

+ + + technical + +
+
+
+
+

An Intro to Generic Numeric Programming with Spire

+

In this post I'd like to introduce you to what I have been calling generic + numeric programming.

+ +

What is Generic Numeric Programming?

+

What do we mean by generic numeric programming? Let's take a simple example; we + want to add 2 numbers together. However, we don't want to restrict ourselves to + a particular type, like Int or Double, instead we just want to work with + some generic type A that can be added. For instance:

+
def add[A](x: A, y: A): A = x + y
+

Of course, this won't compile since A has no method +. What we are really + saying is that we want A to be some type that behaves like a number. The + usual OO way to achieve this is by creating an interface that defines our + desired behaviour. This is less than ideal, but if we were to go this route, + our add function might look like this:

+
trait Addable[A] { self: A =>
+  def +(that: A): A
+}
+
+def add[A <: Addable[A]](x: A, y: A): A = x + y
+

We've created an interface that defines our + method, and then bound our type + parameter A to subsume this interface. The main problem with this is that we + can't directly use types out of our control, like those that come in the + standard library (ie. Int, Long, Double, BigInt, etc). The only option + would be to wrap these types, which means extra allocations and either explicit + or implicit conversions, neither of which are good options.

+

A better approach is to use type classes. A discussion on type classes is out + of the scope of this post, but they let us express that the type A must have + some desired behaviour, without inheritence. Using the type class pattern, we + could write something like this:

+
trait Addable[A] {
+  // Both arguments must be provided. Addable works with the type A, but
+  // does not extend it.
+  def plus(x: A, y: A): A
+}
+
+// This class adds the + operator to any type A that is Addable,
+// by delegating to that Addable's `plus` method.
+implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) {
+  def +(rhs: A): A = ev.plus(lhs, rhs)
+}
+
+// We use a context bound to require that A has an Addable instance.
+def add[A: Addable](x: A, y: A): A = x + y
+

We can then easily add implementations for any numeric type, regardless if we + control it or not, or even if it is a primitive type:

+
implicit object IntIsAddable extends Addable[Int] {
+  def plus(x: Int, y: Int): Int = x + y
+}
+
+add(5, 4)
+

This is, more or less, the approach Spire takes.

+ +

Why be Generic?

+

Why be generic? The flippant answer I could give is: why not? I do hope that + after reading this, that is an acceptable answer to you, but I know that's not + what you came here for.

+

The first reason is the obvious one; sometimes you want to run the same + algorithm, but with different number types. Euclid's GCD algorithm is the same + whether you are using Byte, Short, Int, Long, or BigInt. Why implement + it only for 1, when you could do it for all 5? Worse; why implement it 5 + times, when you need only implement it once?

+

Another reason is that you want to push certain trade-offs, such as speed vs + precision to the user of your library, rather than making the decision for + them. Double is fast, but has a fixed precision. BigDecimal is slow, but + can have much higher precision. Which one do you use? When in doubt, let + someone else figure it out!

+

A last great reason is that it let's you write less tests and can make + testing much less hairy.

+ +

One Algorithm, Many Types

+

So, what does a generic version of Euclid's GCD algorithm look like? Spire + strives to make generic numeric code look more or less like what you'd write + for a direct implementation. So, let's let you compare; first up, the direct + implementation:

+
def euclidGcd(x: Int, y: Int): Int =
+  if (y == 0) x
+  else euclidGcd(y, x % y)
+

With Spire, we can use the spire.math.Integral type class to rewrite this as:

+
import spire.math.Integral
+import spire.implicits._
+
+def euclidGcd[A: Integral](x: A, y: A): A =
+  if (y == 0) x
+  else euclidGcd(y, x % y)
+

The 2 methods are almost identical, save the Integral context bound. + Integral gives us many methods we expect integers to have, like addition, + multiplication, and euclidean division (quotient + remainder).

+

Because Spire provides default implicit instances of Integral for all of the + integral types that come in the Scala standard library, we can immediately use + euclidGcd to find the GCD of many integer types:

+
euclidGcd(42, 96)
+euclidGcd(42L, 96L)
+euclidGcd(BigInt(42), BigInt(96))
+

This is much better than writing 5 different versions of the same algorithm! + With Spire, you can actually do away with euclidGcd altogether, as gcd + comes with Integral anyways:

+
spire.math.gcd(BigInt(1), BigInt(2))
+ +

Performance vs Precision

+

Another benefit of generic numeric programming, is that you can push the choice + of numeric type off to someone else. Rather than hardcode a method or data + structure using Double, you can simple require some Fractional type.

+

I actually first found a need for generic numeric programming after I had + implemented a swath of algorithms with double precision floating point + arithmetic, only to find out that the minor precision errors were causing + serious correctness issues. The obvious fix was to just to use an exact type, + like spire.math.Rational, which would've worked for many of my purposes. + However, many of the algorithms actually worked fine with doubles or even + integers, where as others required exact n-roots (provided by a number type + like spire.math.Real). Being more precise meant everything got slower, even + when it didn't need to be. Being less precise meant some algorithms would + occasionally return wrong answers. Abstracting out the actual number type + used meant I didn't have to worry about these issues. I could make the choice + later, when I knew a bit more about my data, performance and precision + requirements.

+

We can illustrate this using a simple algorithm to compute the mean of some + numbers.

+
import spire.math._
+import spire.implicits._
+
+// Note: It is generally better to use an incremental mean.
+def mean[A: Fractional](xs: A*): A = xs.reduceLeft(_ + _) / xs.size
+

Here, we don't care what type A is, as long as it can be summed and divided. + If we're working with approximate measurements, perhaps finding the mean of a + list of Doubles is good enough:

+
mean(0.5, 1.5, 0.0) 
+// = 0.6666666666666666
+

Or perhaps we'd like an exact answer back instead:

+
import spire.math.Rational
+
+mean(Rational(1, 2), Rational(3, 2), Rational(0))
+// = Rational(2, 3)
+

The main thing here is that as a user of the mean function, I get to choose + whether I'd prefer the speed of Double or the precision or Rational. The + algorithm itself looks no different, so why not give the user the choice?

+ +

Better Testing

+

One of the best things is that if you write test code that abstracts over the + number type, then you can re-use your tests for many different types. Spire + makes great use of this, to ensure instances of our type classes obey the rules + of algebra and that the number types in Spire (Rational, Complex, UInt, etc) + are fundamentally correct.

+

There is another benefit though -- you can ignore the subtleties of floating + point arithmetic in your tests if you want! If your code works with any number + type, then you can test with an exact type such as spire.math.Rational or + spire.math.Real. No more epsilons and NaNs. You shouldn't let this excuse you + from writing numerically stable code, but it may save you many false negatives + in your build system, while also making you more confident that the fundamentals + are correct.

+

This is a big topic, deserving of its own blog post (you know who you are), so + I'll leave this here.

+ +

What Abstractions Exist?

+

We've already seen Integral, which can be used wherever you need something + that acts like an integer. We also saw the modulus operator, x % y, but not + integer division. Spire differentiates between integer division and exact + division. You perform integer division with x /~ y. To see it in action, + let's use an overly complicated function to negate an integer:

+
import spire.math._
+import spire.implicits._
+
+def negate[A: Integral](x: A) = -(42 * (x /~ 42) + x % 42)
+

Instances of Integral exist for Byte, Short, Int, Long and BigInt.

+

Another type class Spire provides is Fractional[A], which is used for things + that have "exact" division. "Exact" is in quotes, since Double or + BigDecimal division isn't really exact, but it's close enough that we give + them a pass. Fractional also provides x.sqrt and x nroot k for taking the + roots of a number.

+
def distance[A: Fractional](x: A, y: A): A = (x ** 2 + y ** 2).sqrt
+

Note that Fractional[A] <: Integral[A], so anything you can do with + Integral, you can do with Fractional[A] too. Here, we can use distance + to calculate the length of the hypotenuse with Doubles, Floats, + BigDecimals, or some of Spire's number types like Real or Rational.

+

Lastly, you often have cases where you just don't care if / means exact or + integer division, or whether you are taking the square root of an Int or a + Double. For this kind of catch-all work Spire provides Numeric[A].

+ +

Why Spire?

+

If you've already hit the types of problems solved by generic numeric + programming, then you may have seen that scala.math also provides Numeric, + Integral, and Fractional, so why use Spire? Well, we originally created + Spire largely due to the problems with the type classes as they exist in Scala.

+

To start, Scala's versions aren't specialized, so they only worked with boxed + versions of primitive types. The operators in Scala also required boxing, which + means you need to trade-off performance for readability. They also aren't very + useful for a lot of numeric programming; what about nroots, trig functions, + unsigned types, etc?

+

Spire also provides many more useful (and specialized) type classes. Some are + ones you'd expect, like Eq and Order, while others define more basic + algebras than Numeric and friends, like Ring, Semigroup, VectorSpace, + etc.

+

There are many useful number types that are missing from Scala in Spire, such + as Rational, Complex, UInt, etc.

+

Spire was written by people who actually use it. I somewhat feel like Scala's + Numeric and friends weren't really used much after they were created, other + than for Scala's NumericRange support (ie. 1.2 to 2.4 by 0.1). They miss + many little creature comforts whose need becomes apparent after using Scala's + type classes for a bit.

+ +

Spire is Fast

+

One of Spire's goals is that the performance of generic code shouldn't suffer. + Ideally, the generic code should be as fast as the direct implementation. Using + the GCD implementation above as an example, we can compare Spire vs. Scala vs. + a direct implementation. I've put the + benchmarking code up as a Gist.

+
gcdDirect:        29.981   1.00 x gcdDirect
+gcdSpire:         30.094   1.00 x gcdDirect
+gcdSpireNonSpec:  36.903   1.23 x gcdDirect
+gcdScala:         38.989   1.30 x gcdDirect
+

For another example, we can look at the code to + find the mean of an array.

+
meanDirect:        10.592   **1.00 x gcdDirect**
+meanSpire:         10.638   **1.00 x gcdDirect**
+meanSpireNonSpec:  13.434   **1.26 x gcdDirect** 
+meanScala:         19.388   **1.83 x gcdDirect**
+

Spire achieves these goals fairly simply. All our type classes are + @specialized, so when using primitives types you can avoid boxing. We then use macros to remove + the boxing normally required for the operators by the implicit conversions.

+

Using @specialized, both gcdSpire and meanSpire aren't noticably slower + than the direct implementation. We can see the slow down caused by dropping + @specialized in gcdSpireNonSpec and meanSpireNonSpec. The difference + between gcdSpireNonSpec and gcdScala is because Spire doesn't allocate an + object for the % operator (using macros to remove the allocation). The + difference is even more pronounced between meanSpireNonSpec and meanScala.

+ +

More than just Numeric, Integral, and Fractional

+

The 3 type classes highlighted in this post are just the tip of the iceberg. + Spire provides a whole slew of type classes in spire.algebra. This package + contains type classes representing a wide variety of algebraic structures, + such as Monoid, Group, Ring, Field, VectorSpace, and more. The 3 type + classes discussed above provide a good starting point, but if you use Spire in + your project, you will probably find yourself using spire.algebra more and + more often. If you'd like to learn more, you can watch my talk on abstract + algebra in Scala.

+

As an example of using the algebra package, spire.math.Integral is simply + defined as:

+
import spire.algebra.{ EuclideanRing, IsReal }
+
+trait Integral[A] extends EuclideanRing[A]
+                     with IsReal[A] // Includes Order[A] with Signed[A].
+                     with ConvertableFrom[A]
+                     with ConvertableTo[A]
+

Whereas spire.math.Fractional is just:

+
import spire.algebra.{ Field, NRoot }
+
+trait Fractional[A] extends Integral[A] with Field[A] with NRoot[A]
+ +

Many New Number Types

+

Spire also adds many new useful number types. Here's an incomplete list:

+
    +
  • spire.math.Rational is a fast, exact number type for working with + rational numbers,
  • +
  • spire.math.Complex[A] is a parametric number type for complex numbers,
  • +
  • spire.math.Number is a boxed number type that strives for flexibility + of use,
  • +
  • spire.math.Interval is a number type for interval arithmetic,
  • +
  • spire.math.Real is a number type for exact geometric computation that + provides exact n-roots, as well as exact division,
  • +
  • spire.math.{UByte,UShort,UInt,ULong} unsigned integer types, and
  • +
  • spire.math.Natural an arbitrary precision unsigned integer type.
  • +
+ +

Better Readability

+

Spire also provides better operator integration with Int and Double. For + instance, 2 * x or x * 2 will just work for any x whose type has an + Ring. On the other hand, Scala requires something like + implicitly[Numeric[A]].fromInt(2) * x which is much less readable. This + also goes for working with fractions; x * 0.5 will just work, if x has a + Field.

+ +

Try It Out!

+

Spire has a basic algebra that let's us work generically with numeric types. It + does this without sacrificing readability or performance. It also provides many + more useful abstractions and concrete number types. This means you can write + less code, write less tests, and worry less about concerns like performance vs. + precision. If this appeals to you, then you should try it out!

+

There is some basic information on getting up-and-running with Spire in SBT on + Spire's project page. If you have any further + questions, comments, suggestions, criticism or witticisms you can say what you + want to say on the Spire mailing list + or on IRC on Freenode in #spire-math.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Tom Switzer + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/github-seats.html b/blog/github-seats.html new file mode 100644 index 00000000..b89793fd --- /dev/null +++ b/blog/github-seats.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + GitHub Seats + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

GitHub Seats

+ + + technical + +
+
+
+
+

GitHub Seats

+

As we continue to grow our community on GitHub, we've encountered a challenge with our seat limit for maintainers. + It's important to note that each maintainer seat incurs a fee. + By managing our maintainers more efficiently, we can allocate resources effectively and sustain the growth of our community.

+

In order to be able to add new members, we've recently conducted a review and removed 25 accounts that have not been active on the organization since January 1st, 2023.

+

We understand that circumstances may vary, and if you believe your account was removed in error, we want to hear from you! + Please don't hesitate to reach out to us, and we'll happily re-add you as a maintainer.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/governance/index.html b/blog/governance/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/blog/governance/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/governing-documents.html b/blog/governing-documents.html new file mode 100644 index 00000000..0ceee251 --- /dev/null +++ b/blog/governing-documents.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + Governing Documents + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Governing Documents

+ + + governance + +
+
+
+
+

Governing Documents

+

As a first step in our effort to increase transparency in the Typelevel organization, the Steering Commitee have approved and released an initial set of Governing Documents.

+

The most important document is the Charter, which specifies the role of the Steering Committee and criteria for member projects. The most notable change is that Typelevel no longer distinguishes "full" and "incubator" projects, but instead recognizes:

+
    +
  • +

    Affiliate Projects. These projects agree to use an approved license and adhere to organization policies, including the Code of Conduct.

    +
  • +
  • +

    Organization Projects. In addition to the requirements for Affiliate membership, these projects agree to be hosted by the Typelevel organization, and to abide by the guidance and direction of the Steering Commitee (which may specify policies for binary compatibility, documentation, contributor base, and so on). It is our hope that these changes will help clarify maintenance expectations for users of foundational libraries like Cats and Cats-Effect.

    +
  • +
+

Most projects that are now hosted by Typelevel will become Organization Projects, and most that are hosted elsewhere will become Affiliate Projects. Final designations will be reviewed with maintainers and cataloged on the Typelevel website.

+ +

Next Steps

+

First, the Governing Documents provide high-level structure, but do not specify fine-grained policies and procedures for Organization Projects, events, moderation, and so on. These will be discussed and added to the Governance repository in the coming months, under the voting procedures specified by the Charter. Notable policy changes will be accompanied by an announcement here.

+

Second, the Typelevel website does not yet reflect the Governing Documents or membership policy changes. The current website will be updated minimally, until the redesigned website (in progress) becomes available sometime in the next few months.

+

Finally, we recognize that the Steering Committee does not currently represent the diversity of the Typelevel community. We expect to appoint many new faces and see the retirement of many longstanding members over the next year.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/gsoc-2023.html b/blog/gsoc-2023.html new file mode 100644 index 00000000..d328841f --- /dev/null +++ b/blog/gsoc-2023.html @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summer of Code + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summer of Code

+ + + technical + +
+
+
+
+

Typelevel Summer of Code

+

We are happy to announce that Typelevel will be participating in Google Summer of Code 2023, under the auspice of the Scala Center! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend.

+

We are proposing the following three projects:

+ +

Each of these projects is an important enhancement for the Typelevel ecosystem. Taken together, they are a major milestone towards offering a full-featured stack for developing services and deploying on high-performance runtimes available for JVM, Node.js, and Native.

+

Moreover, we are excited to welcome you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. Applications open on March 20! Until then, please feel free to reach out or join us on Discord; we look forward to getting to know you :)

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/gsoc-2024.html b/blog/gsoc-2024.html new file mode 100644 index 00000000..0a5a69d4 --- /dev/null +++ b/blog/gsoc-2024.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summer of Code 2024 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summer of Code 2024

+ + + technical + +
+
+
+
+

Typelevel Summer of Code 2024

+

We are excited to share that Typelevel will be participating in Google Summer of Code 2024, thanks to the gracious support of the Scala Center! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend.

+

This year we have several project ideas and mentors lined up spanning AI/ML, serverless, data streaming, observability, systems programming, and more. We will continue adding ideas, so please keep checking back! Also, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.”

+

We look forward to welcoming you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. Applications open on March 18! Until then, please join us on Discord or email us at gsoc@typelevel.org; we look forward to getting to know you :)

+ +

2023 recap

+

We are proud of our GSoC contributors' accomplishments last year:

+ +

To learn more about their projects, check out the lightning talks they gave at the 2023 Northeast Scala Symposium. Their success would not have been possible without the support and mentorship of our community, especially Diego Alonso, Ross Baker, Chris Davenport, Brian Holt, Maksym Ochenashko, and Daniel Spiewak. Thank you for your enthusiasm and generosity; it made an impression on our students, and it made an impression on me :)

+

Lastly, we were surprised by the breadth of the response we got to our announcement last year: there are many people eager to contribute. Even if you are not eligible to participate in GSoC, you are always welcome to join the Typelevel community and contribute to our projects!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/gsoc-2025.html b/blog/gsoc-2025.html new file mode 100644 index 00000000..3a0686f6 --- /dev/null +++ b/blog/gsoc-2025.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summer of Code 2025 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summer of Code 2025

+ + + technical + +
+
+
+
+

Typelevel Summer of Code 2025

+

We are proud to announce that Typelevel is a Mentoring Organization for Google Summer of Code 2025! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. You can learn more about what the experience is like in this blog post by our 2024 contributor Ching Hian Chew.

+

Please check out our project ideas and mentors spanning AI/ML, serverless, build tooling, frontend/UIs, systems programming, web assembly, and more. Furthermore, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.”

+

We look forward to welcoming you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. Applications open on March 24! Until then, please join us on Discord or email us at gsoc@typelevel.org; we are excited to get to know you :)

+ +

2024 recap

+

Congratulations to our GSoC contributors last year:

+
    +
  • Ching expanded Feral’s serverless integrations, in particular adding support for Google Cloud Run HTTP Functions. Her work landed in the v0.3.1 release. Read her blog post!
  • +
  • Gaby designed and implemented Catscript, a new library for beginner-friendly scripting with Cats Effect, complete with tutorials and documentation. It will become an essential part of the Typelevel Toolkit.
  • +
  • Abhi integrated Protosearch with Scaladoc, enabling a unified search experience across written and API documentation. It will roll out to all Typelevel libraries in a forthcoming sbt-typelevel release.
  • +
+

Our program culminated in a virtual meetup where each of them gave a lightning talk about their project. Thank you to everyone in our community who showed up for our contributors, especially our mentors Tonio Gela, Thanh Le, Michael Pilquist, Rishad Sewnarain, and Andrew Valencik!

+

Finally, we are making a broad call for any and all new contributors. Even if you are not eligible to participate in GSoC, you are always welcome to join the Typelevel community and contribute to our projects!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/gsoc-2026.html b/blog/gsoc-2026.html new file mode 100644 index 00000000..0e11689f --- /dev/null +++ b/blog/gsoc-2026.html @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summer of Code 2026 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summer of Code 2026

+ + + technical + +
+
+
+
+

Typelevel Summer of Code 2026

+

Due to overwhelming participation in GSoC 2026, we are only able to consider proposals from applicants who complete our onboarding process by Monday, March 16th. + If you missed this deadline, we appreciate your interest and hope you will apply next year!

+

We are pleased to announce that Typelevel is a Mentoring Organization for Google Summer of Code 2026! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. You can learn more about what the experience is like in this blog post by our 2024 contributor Ching Hian Chew.

+

Please check out our project ideas and mentors spanning serverless, build tooling, frontend/UIs, systems programming, web assembly, and more. Furthermore, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.”

+

We look forward to welcoming you to the Typelevel community and we hope that participating in this program will be the beginning of your open source journey. Applications open on March 16! Until then, get started by watching our intro presentation and making your first contribution.

+ +

2025 recap

+

Congratulations to our GSoC contributors last year:

+
    +
  • Rahul upgraded several FS2 APIs to use non-blocking, polling-based I/O, including datagram sockets and native processes on both the JVM and native platforms. This improves their performance and semantics.
  • +
  • Shrey worked on an ambitious project to implement a machine learning inference runtime with Cats Effect on Scala Native. This makes it possible to serve ML models alongside web services without compromising latency.
  • +
+

They were joined by a contributor outside of the official GSoC program:

+
    +
  • Jay developed a new API for log4cats based on a proposal by Olivier Mélois. It encodes the interface as a single abstract method to enhance usability and extensibility while maintaining compatibility with the original API.
  • +
+

Our summer culminated in a virtual meetup where each of them gave a lightning talk about their project. Thank you to everyone in our community who showed up for our contributors, especially our mentors Arman Bilge, Antonio Jimenez, Morgen Peschke, Michael Pilquist, and Andrew Valencik.

+

Finally, we are making a broad call for any and all new contributors. Even if you are not eligible to participate in GSoC, you are always welcome to join the Typelevel community and contribute to our projects!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Antonio Jimenez he/him + + +
+ Hi, my name is Antonio. I'm originally from Spain and currently work full-time as software engineer using Scala in Switzerland. I've been using Scala for 5 years, ever since discovering the language during a functional programming course at EPFL. I participated in Google Summer of Code as a student, working on Cats Effect's new I/O integrated runtime leveraging io_uring. Today, I remain actively involved in the project as a co-mentor. + + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/gsoc24-going-feral-on-the-cloud.html b/blog/gsoc24-going-feral-on-the-cloud.html new file mode 100644 index 00000000..e74ec5bd --- /dev/null +++ b/blog/gsoc24-going-feral-on-the-cloud.html @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + Google Summer of Code 2024 - Going Feral on The Cloud + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Google Summer of Code 2024 - Going Feral on The Cloud

+ + + technical + +
+
+
+
+

Google Summer of Code 2024 - Going Feral on The Cloud

+

This project was proposed by the Typelevel community in collaboration with the Scala Center, and carried out under Google Summer of Code (GSoC) 2024. Feral is a library in the Typelevel ecosystem that provides a framework for Scala developers to write and deploy serverless functions. As Feral was only supporting AWS Lambda, the goal of the project was to extend Feral to support other serverless providers, specifically Vercel and Google Cloud.

+

The vision for Feral is to enable Scala developers to easily switch between one cloud provider to another that better suits their needs, without the need for major refactoring of their codebases. Such convenience would give developers greater freedom as they do not need to be tied down to one platform. Furthermore, as Feral is part of the Typelevel ecosystem, developers who are currently using the Http4s Typelevel library for non-serverless web services may also effortlessly make the switch to serverless.

+

With these goals in mind, it is imperative to provide robust support for the common serverless providers, which is what the project aimed to work towards.

+ +

What I Did

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Pull Request (PR)StatusComments
Add ApiGatewayV2WebSocketEventMergedThe addition of this AWS Lambda event provided an introduction to serverless functions for me, while enhancing the support that Feral has for AWS Lambda.
Created support for Vercel using Node.js runtimeNot MergedVercel's implementation of routes conflicts with the way Http4s handles routes. Due to this incompatibility between Feral and Vercel, this PR was not merged. Through working on this, there is a better understanding of how Vercel and Http4s work, which could pave the way for future work.
Created support for Google Cloud HTTP functionsMergedThis PR enabled support for both JVM and Node.js runtimes. There is a minor error logged in the JVM runtime implementation that we minimized to a bug unrelated to Feral or any of the Typelevel libraries. The error does not seem to impact the functionality of the resulting web application, but it would be good to further investigate the cause of it.
+ +

Challenges and Lessons Learnt

+

It was challenging to support various serverless platforms, particularly Vercel and Google Cloud, which I created the initial implementation for. While I learnt along the way that the general procedure for supporting each platform was generally the same, where one would have to do things such as converting between types and using a dispatcher, there were still differences in certain details. For example, while referencing the pre-existing Feral implementation to support AWS Lambda event functions in order to support Google Cloud HTTP functions for the JVM runtime, I learnt that HTTP functions are a subset of AWS Lambda event functions, while they were separate in Google Cloud.

+

My lack of familiarity with Scala and functional programming also posed a hindrance. While I had previously done some functional programming with Scala prior to GSoC, it was still something rather new to me. As such, it took me a longer time to write code and debug than the time I would probably have taken if I was more familiar. However, this GSoC project gave me the opportunity to improve myself in these areas. I became more familiar with previously-learnt concepts such as monads, and learnt new things such as for-yield statements and how they can be desugared.

+

Through GSoC, I have learnt many new things while reinforcing what I already know. I am grateful that the Typelevel organization held a session that taught the concept of programs-as-values as it was something that I never knew existed. I also learnt to appreciate what I have learnt in school better, by experiencing first-hand how I can apply such knowledge in real-world situations. For example, this project utilized the concept of resource allocation which was something I had previously learnt about.

+ +

Future Work

+

I plan to continue contributing to enhancing Feral after GSoC 2024 ends. Some ways I could do this is to create support for Google Cloud Event functions and create SBT plug-ins to test Google Cloud functions locally as well as deploy the functions. If possible, investigation could also be done on how, if possible, Vercel can be integrated into Feral without impeding developers from using Http4s with it.

+ +

Acknowledgements

+

I would first like to thank my mentors, Arman and Antonio for their constant guidance during GSoC. In particular, I would like to thank Arman for taking the time to set up weekly pair programming sessions, which has enhanced my learning experience greatly. I would also like to thank the Scala Center and the Typelevel community for proposing and supporting this project. Lastly, I would like to thank Google for hosting GSoC 2024 and providing me with the opportunity to learn about open source projects.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Ching Hian Chew + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/hackday-2016-06-11.html b/blog/hackday-2016-06-11.html new file mode 100644 index 00000000..bea4a44c --- /dev/null +++ b/blog/hackday-2016-06-11.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the second Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

At the May Typelevel hack day we had people working on the Scala compiler (we submitted a pull request against 2.12.x which as been accepted and merged!), people exploring Cats and shapeless, people working on Ensime and FreeSlick and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise.

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

This event will take place at the Salesforce Tower in London.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2016-07-16.html b/blog/hackday-2016-07-16.html new file mode 100644 index 00000000..617fa34b --- /dev/null +++ b/blog/hackday-2016-07-16.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the third Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

At the May Typelevel hack day we had people working on the Scala compiler (we submitted a pull request against 2.12.x which as been accepted and merged!), people exploring Cats and shapeless, people working on Ensime and FreeSlick and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise.

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

Note the changed location: This event will take place at Hydrogen Group, 30 Eastcheap, London.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2016-08-13.html b/blog/hackday-2016-08-13.html new file mode 100644 index 00000000..60183459 --- /dev/null +++ b/blog/hackday-2016-08-13.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the fourth Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

At the July Typelevel hack day we had people working on the Scala compiler, a Monocle workshop, people working on Ensime and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise.

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

Note the changed location: This event will take place at Hydrogen Group, 30 Eastcheap, London.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2016-09-17.html b/blog/hackday-2016-09-17.html new file mode 100644 index 00000000..6aaefd0f --- /dev/null +++ b/blog/hackday-2016-09-17.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the fifth Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

At the August Typelevel hack day we had people working on the Scala compiler, SBT and Ensime, and experimenting with using Scala.Meta to preprocess Scala source code!

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

Note the changed location: This event will again take place at the Salesforce Tower.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2016-10-15.html b/blog/hackday-2016-10-15.html new file mode 100644 index 00000000..ef7ef95f --- /dev/null +++ b/blog/hackday-2016-10-15.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the sixth Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

At the August Typelevel hack day we had people working on the Scala compiler, SBT and Ensime, and experimenting with using Scala.Meta to preprocess Scala source code!

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

This event will take place at the Salesforce Tower.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2016-11-12.html b/blog/hackday-2016-11-12.html new file mode 100644 index 00000000..3af97bc6 --- /dev/null +++ b/blog/hackday-2016-11-12.html @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the seventh Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

This event will take place at the Salesforce Tower.

+
+
+ +
+ + + + + + diff --git a/blog/hackday-2017-01-21.html b/blog/hackday-2017-01-21.html new file mode 100644 index 00000000..b1e031c1 --- /dev/null +++ b/blog/hackday-2017-01-21.html @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + Hack the Tower – Typelevel & Scala hack day + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Hack the Tower – Typelevel & Scala hack day

+ + + events + +
+
+
+
+

Hack the Tower – Typelevel & Scala hack day

+

Hack the Tower – Typelevel & Scala hack day

+ +

About the Hackday

+

This is the ninth Typelevel hack day in conjunction with the London Scala User Group and Hack The Tower.

+

Join users of and contributors to Typelevel projects and Typelevel Scala and learn how to put them to good use in your own projects and how to make them better for the whole Scala community!

+

Please come and join us, and if there's anything you'd like to chat about beforehand head over to the Gitter channel.

+ +

Schedule

+

More details, including the schedule, can be found on the Meetup page. + It is important that you sign up there.

+

Sign up

+ +

Venue

+

This event will take place at the Salesforce Tower.

+
+
+ +
+ + + + + + diff --git a/blog/heaps.html b/blog/heaps.html new file mode 100644 index 00000000..8051d00f --- /dev/null +++ b/blog/heaps.html @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + + + + + + + API Design for Heaps (aka Priority Queues) + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

API Design for Heaps (aka Priority Queues)

+ + + technical + +
+
+
+
+

API Design for Heaps (aka Priority Queues)

+

This is a guest post by Chris Okasaki. It was initially published as the design document behind scads. It is being republished here with the permission of the original author.

+

A heap (or priority queue) is a collection of elements ordered by some Ordering, optimized for retrieving the first element according to that ordering. + Duplicate elements are allowed. + Applications vary in whether they need the first element to be the smallest or the biggest element according to the ordering, so both variations should be easy to use. + (However, any given heap is expected to offer easy access to either the smallest element only or the biggest element only, not both at the same time.) + I will consider immutable heaps in this document, but the core issues discussed below apply to both immutable heaps and mutable heaps.

+

Even if an element type has a natural ordering, that ordering may not be the one we want to use, so we must allow the user to specify the ordering.

+ +

Problem 1: Don't mix two different orderings in the same heap

+

Here is a strawman design for a very simple heap API:

+
trait Heap[Elem] {  // WARNING: THIS IS BROKEN!!!
+  def isEmpty(implicit ord: Ordering[Elem]): Boolean
+  def add(elem: Elem)(implicit ord: Ordering[Elem]): Heap[Elem]
+  def first(implicit ord: Ordering[Elem]): Elem
+  def rest(implicit ord: Ordering[Elem]): Heap[Elem]
+}
+
+// factory method, probably in some companion object
+def empty[Elem](implicit ord: Ordering[Elem]): Heap[Elem] = ???
+

You may think it strange that all of these methods are taking an ord parameter. + From a Scala point of view, that doesn't make much sense. + But you can find variations of this design in many implementations of heaps on GitHub, including in the well-respected Scalaz library. + Why? As far as I can tell, the answer is because Haskell does it that way. + Here's the equivalent design in Haskell:

+
type Heap a
+empty   :: Ord a => Heap a
+isEmpty :: Ord a => Heap a -> Bool
+add     :: Ord a => a -> Heap a -> Heap a
+first   :: Ord a => Heap a -> a
+rest    :: Ord a => Heap a -> Heap a
+

In Haskell, this makes perfect sense. + Behind the scenes, each method takes an Ord dictionary as a hidden parameter. + But there's one critical difference between Haskell and Scala: in Haskell, there can only be a single ordering for an element type, but in Scala, there can be many orderings for the same element type.

+

For example, consider this Scala code:

+
val ord1 = Ordering.Int
+val ord2 = ord1.reverse
+val heap1 = empty[Int](ord1).add(5)(ord1).add(7)(ord1)
+val heap2 = heap1.add(4)(ord2)
+println(heap2.first)
+

What should this print? + Of course, that depends on the details of the implementation, but you would expect it to print either 4 (the smallest element) or 7 (the biggest element). + However, because one ordering was used for two of the adds and the opposite ordering was used for the third add, there's an excellent chance that the actual result will be 5, which is the wrong answer for both orderings.

+

The magic of implicit parameters is that you usually don't need to pass them explicitly. + But (A) there's nothing to stop you from doing so, and (B) there's nothing to prevent you from calling methods in different scopes with different orderings. + No, if you're anything like me, the possibility that this could happen by accident is making your skin crawl. + Surely, the API should prevent this from happening!

+

Fortunately, this problem is very easy to fix. + Only the empty method should take an ordering. + Once that initial heap has been created, all future heaps derived from that heap via any sequence of adds or rests should use the same ordering. + With this change, the API becomes:

+
trait Heap[Elem] {
+  def isEmpty: Boolean
+  def add(elem: Elem): Heap[Elem]
+  def first: Elem
+  def rest: Heap[Elem]
+}
+
+// factory method, probably in some companion object
+def empty[Elem](implicit ord: Ordering[Elem]): Heap[Elem] = ???
+

Yay! That is both simpler and safer.

+ +

Problem 2: merge

+

Another operation supported by many kinds of heaps is merge, which combines two heaps into a single heap. + Examples of heaps supporting merge include leftist heaps, skew heaps, binomial heaps (aka binomial queues), Fibonacci heaps, etc.

+

We can easily add merge to the existing Heap[Elem] trait.

+
trait Heap[Elem] {
+  ...
+  def merge(other: Heap[Elem]): Heap[Elem]
+}
+

However, there are at last two problems with this. + First, traits allow for subclassing, so we might have several different implementations, such as leftist heaps and binomial heaps. + But we only want to merge leftist heaps with leftist heaps and binomial heaps with binomial heaps&mdash;we do not want to merge leftist heaps with binomial heaps.

+

There are several ways to address this problem. + For example, leftist heaps and binomial heaps could just use completely separate trait hierarchies, and each could use a sealed trait to prevent this unwanted mixing of types.

+

But the code duplication this would entail is unsatisfying. + It would also make it more difficult to share code (such as a testing harness) between different implementations.

+

Alternatively, we can control the types more precisely by adding a second type parameter for the specific representation being used, as in:

+
trait MHeap[Elem, Heap] {
+  // MHeap is "Mergeable Heap"
+  // Heap is the specific Heap representation being used
+  def isEmpty: Boolean
+  def add(elem: Elem): Heap
+  def first: Elem
+  def rest: Heap
+  def merge(other: Heap): Heap
+}
+
+sealed trait LeftistHeap[Elem] extends MHeap[Elem, LeftistHeap[Elem]]
+
+sealed trait BinomialHeap[Elem] extends MHeap[Elem, BinomialHeap[Elem]]
+

Because of the extra type parameter, a leftist heap and binomial heap are incompatible and cannot be merged.

+ +

Problem 3: merge (continued)

+

There's a second problem with merge. A particular implementation, such as leftist heaps, would provide a factory method for creating a new heap.

+
object LeftistHeap { // companion object
+  def empty[Elem](implicit ord: Ordering[Elem]): LeftistHeap[Elem] = ???
+}
+

Because of the MHeap definition, we can't merge a leftist heap with a binomial heap. + But now we've re-introduced the problem of incompatible orderings!

+
val ord1 = Ordering.Int
+val ord2 = ord1.reverse
+val heap1 = empty[Int](ord1).add(5).add(7)
+val heap2 = empty[Int](ord2).add(6).add(4)
+var heap3 = heap1.merge(heap2)
+while (!heap3.isEmpty) {
+  println(heap3.first)
+  heap3 = heap3.rest
+}
+

Notice that heap1 and heap2 were created with opposite orderings. + What happens if we merge them? Nothing good! + The exact results depend on details of the implementation, but a likely result is that loop will print the elements in the order 5,6,4,7&mdash;or maybe 5,7,6,4&mdash;when it should print them in sorted order!

+

We would really like to make this sort of situation impossible! + Maybe we could test the orderings for object equality at runtime, and throw an exception if they're different? + That could actually work for simple types like integers with a built-in ordering object. + But for more complicated types, such as tuples, the orderings are generated on demand from the orderings of their constituent parts. + And this generation is not memoized, so if we demand an ordering for, say, (Int,String) twice, we'll get two separate ordering objects, which will cause a false negative for our hypothetical dynamic equality check.

+

No, we would really like to make merging two heaps with different orderings a type error. + We can achieve this by making the notion of a factory explicit. + The idea is that heaps can only be merged with other heaps from the same factory. + Attempting to merge heaps from different factories will cause a type error.

+

In code, we might express this as follows:

+
trait HeapFactory {
+  type Elem
+  type Heap <: MHeap[Elem,Heap]
+
+  def empty: Heap
+  // plus other factory methods
+}
+
+object LeftistHeap { // companion object
+  def factory[E](implicit ord: Ordering[E]): HeapFactory { type Elem = E } = ???
+}
+

Now we can say:

+
val minHeaps = LeftistHeap.factory[Int](Ordering.Int)
+val maxHeaps = LeftistHeap.factory[Int](Ordering.Int.reverse)
+
+val heap1 = minHeaps.empty.add(5).add(7)
+val heap2 = minHeaps.empty.add(6).add(4)
+val heap3 = heap1.merge(heap2) // this typechecks
+
+val heap4 = maxHeaps.empty.add(6).add(4)
+val heap5 = heap1.merge(heap4) // !!!type error!!!
+

Notice that heap1, heap2, and heap3 have type minHeaps.Heap but heap4 has type maxHeaps.Heap. + According to Scala's notion of path-dependent types, these types are incompatible so attempting to merge heap1 and heap4 causes a type error, as desired.

+ +

A question

+

Clearly, if I create two factories with incompatible element types, then a heap from one factory should not be mergeable with a heap from the other factory. + Similarly, if I create two factories with the same element type but incompatible orderings, then again a heap from one factory should not be mergeable with a heap from the other factory.

+

But what if I create two separate factories with the same element type and the same ordering? + Should a heap created from one of these factories be mergeable with a heap created from the other factory? + It's not clear. + If this duplication of factories was deliberate, then the answer is probably “no”. + This often happens with units of measure. + For example, maybe one of the factories is using integers to represent inches and the other is using integers to represent grams. + Even if the factories are using the same ordering, we probably don't want to merge a heap of inches with a heap of grams!

+

On the other hand, the duplication of factories could be accidental, perhaps the result of two chunks of code being written separately and then brought together later. + In that case, we might very well want to be able to merge a heap from one factory with a heap from another accidentally-separate-but-equivalent factory.

+

Regardless of where you come down on what should happen, what will happen in the above design is that attempting to merge heaps from distinct factories will cause a type error, even if the factories were made for the same element type and ordering.

+ +

Problem 4: Usability in the simple case

+

Most applications of priority queues do not need the merge method. + Trying to make merge typesafe has made the API more complicated and harder to use because of the need to instantiate a factory before creating actual heaps. + Can we hide these complications from a user until and unless they actually need to use merge? Yes.

+

I'll re-introduce the interface without merge, but now called SHeap for “Simple Heap”:

+
trait SHeap[Elem] {
+  def isEmpty: Boolean
+  def add(elem: Elem): SHeap[Elem]
+  def first: Elem
+  def rest: SHeap[Elem]
+}
+

Then MHeap should be a subtype of SHeap:

+
trait MHeap[Elem, Heap <: SHeap[Elem]] extends SHeap[Elem] {
+  // inherits isEmpty and first from SHeap[Elem]
+  def add(elem: Elem): Heap // more specific return type
+  def rest: Heap // more specific return type
+  def merge(other: Heap): Heap
+}
+

The HeapFactory definition is unchanged:

+
trait HeapFactory {
+  type Elem
+  type Heap <: MHeap[Elem,Heap]
+
+  def empty: Heap
+  // plus other factory methods
+}
+

The last part is that the companion object should supply simple factory methods in terms of SHeap:

+
object LeftistHeap { // companion object
+  def empty[E](implicit ord: Ordering[E]): SHeap[E] = ???
+  // plus other ordinary factory methods, similar to other Scala collections
+
+  // the big bad
+  def factory[E](implicit ord: Ordering[E]): HeapFactory { type Elem = E } = ???
+}
+

Now the user can proceed in blissful ignorance of factory or MHeap, treating this essentially just like any other Scala collection, until they need merge. + Of course, empty will probably be defined as

+
  def empty[E](implicit ord: Ordering[E]): SHeap[E] = factory[E](ord).empty
+

(and similarly for the other ordinary factory methods), but the user doesn't need to know that.

+ +

Problem 5: min vs max

+

Should a heap favor smaller elements or bigger elements? + There's no obvious answer&mdash;applications abound for both. + Therefore, an interface should easily support both flavors. + Right now, the ordering parameter allows us to say

+
val minHeaps = LeftistHeap.factory[Int](Ordering.Int)
+val maxHeaps = LeftistHeap.factory[Int](Ordering.Int.reverse)
+

But how did I know that Ordering.Int was the right ordering for min-heaps and Ordering.Int.reverse was the right ordering for max-heaps? + The opposite could just as easily have been true. + Sure, this detail would probably be documented in the API, but it was fundamentally a flip-a-coin arbitrary decision. + And arbitrary decisions with no logic favoring one choice over the other are the hardest to remember.

+

In an easier-to-use interface, the user might write:

+
val minHeaps = LeftistHeap.minFactory[Int](Ordering.Int)
+val maxHeaps = LeftistHeap.maxFactory[Int](Ordering.Int)
+

Now, the user doesn't need to worry whether to use Ordering.Int or Ordering.Int.reverse. + Instead, if they want min-oriented heaps, they call minFactory(Ordering.Int) and if they want max-oriented heaps, they call maxFactory(Ordering.Int). + In fact, it's even better than that. + The whole point of implicit parameters is that you usually don't need to write them down explicitly. + In reality, the user would probably only write:

+
val minHeaps = LeftistHeap.minFactory[Int]
+val maxHeaps = LeftistHeap.maxFactory[Int]
+

Actually, in the current version of scads, this is now

+
val minHeaps = LeftistHeap.Min.factory[Int]
+val maxHeaps = LeftistHeap.Max.factory[Int]
+

where LeftistHeap.Min and LeftistHeap.Max both support other simpler methods for creating SHeaps for users who don't need merge. + For example, a user could write:

+
val h1 = LeftistHeap.Min.empty[Int] // an empty min-heap of integers
+val h2 = LeftistHeap.Max(1,2,3) // a max-heap containing 1, 2, and 3
+

Of course, there's lots more needed to flesh the whole design out into an industrial-strength API, and even more to integrate it with the current Scala collections. + I'll continue to work on this, and I welcome discussion on these issues.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Chris Okasaki + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/higher-leibniz.html b/blog/higher-leibniz.html new file mode 100644 index 00000000..e37e587d --- /dev/null +++ b/blog/higher-leibniz.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + Higher Leibniz + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Higher Leibniz

+ + + technical + +
+
+
+
+

Higher Leibniz

+

We’ve previously seen + the basic implementation and motivation for scalaz.Leibniz. + But there’s still quite a bit more to this traditionally esoteric + member of the Scalaz collection of well-typed stuff.

+ +

Strictly necessarily strict

+

The word “witness” implies that Leibniz is a passive bystander in + your function; sitting back and telling you that some type is equal to + another type, otherwise content to let the real code do the real + work. The fact that Leibniz lifts into functions (which are a + member of the everything set, you’ll agree) might reinforce the + notion that Leibniz is spooky action at a distance.

+

But one of the nice things about Leibniz is that there’s really no + cheating: the value with its shiny new type is dependent on the + Leibniz actually existing, and its subst, however much a glorified + identity function it might be, completing successfully.

+

To see this in action, let’s check in with the bastion of not + evaluating stuff, Haskell.

+ +

The Haskell implementation

+
{-# LANGUAGE RankNTypes, PolyKinds #-}
+module Leib
+  ( Leib()
+  , subst
+  , lift
+  , symm
+  , compose
+  ) where
+
+import Data.Functor
+
+data Leib a b = Leib {
+  subst :: forall f. f a -> f b
+}
+
+refl :: Leib a a
+refl = Leib id
+
+lift :: Leib a b -> Leib (f a) (f b)
+lift ab = runOn . subst ab . On $ refl
+
+newtype On c f a b = On {
+  runOn :: c (f a) (f b)
+}
+
+symm :: Leib a b -> Leib b a
+symm ab = runDual . subst ab . Dual $ refl
+
+newtype Dual c a b = Dual {
+  runDual :: c b a
+}
+
+compose :: Leib b c -> Leib a b -> Leib a c
+compose = subst
+

We use newtypes in place + of type lambdas, and a value instead of a method, but the + implementation is otherwise identical.

+ +

It’s really there

+

OK. Let’s try to make a fake Leib.

+
badForce :: Leib a b
+badForce = Leib $ \_ -> error "sorry for fibbing"
+

The following code will signal an error only if forcing the head + cons of the substed list signals such an error. We never give + Haskell the chance to force anything else.

+
λ> subst (badForce :: Leib Int String) [42] `seq` 33
+*** Exception: sorry for fibbing
+

Oh well, let’s try to bury it behind combinators.

+
λ> subst (symm . symm $ badForce :: Leib Int String) [42] `seq` 33
+*** Exception: sorry for fibbing
+λ> subst (compose refl $ badForce :: Leib Int String) [42] `seq` 33
+*** Exception: sorry for fibbing
+

Hmm. We have two properties:

+
    +
  1. The id from refl? The type-substituted data actually goes + through that function. The same goes for the subst method in + Scala.
  2. +
  3. When using Leibniz combinators, the strictness forms a chain to + all underlying Leibniz evidence. If there are any missing + values, the transform will also fail.
  4. +
+ +

Higher kinded Leibniz

+

Let’s try a variant on Leib.

+
sealed abstract class LeibF[G[_], H[_]] {
+  def subst[F[_[_]]](fa: F[G]): F[H]
+}
+

This reads “LeibF[G, H] can replace G with H in any type + function”. But, whereas the + kind + of the types that Leib discusses is *, for LeibF it’s *->*. So, + LeibF[List, List] exhibits that the type constructors List and + List are equal.

+
implicit def refl[G[_]]: LeibF[G, G] = new LeibF[G, G] {
+  override def subst[F[_[_]]](fa: F[G]): F[G] = fa
+}
+

Interestingly, except for the kinds of type parameters, these + definitions are exactly the same as for Leib. Does that hold for + lift?

+
def lift[F[_[_], _], A[_] , B[_]](ab: LeibF[A, B]): LeibF[F[A, ?], F[B, ?]] =
+  ab.subst[Lambda[x[_] => LeibF[F[A, ?], F[x, ?]]]](LeibF.refl[F[A, ?]])
+

Despite that we are positively buried in type lambdas (yet moderated + by Kind Projector) now, + absolutely!

+

As an exercise, adapt your symm and compose methods from the last + part for LeibF, by only changing type parameters and switching any + refl references.

+
def symm[A[_], B[_]](ab: LeibF[A, B]): LeibF[B, A]
+def compose[A[_], B[_], C[_]](ab: LeibF[A, B], bc: LeibF[B, C]): LeibF[A, C]
+

You can write a Leibniz and associated combinators for types of + any kind; the principles and implementation techniques outlined + above for types of kind *->* apply to all kinds.

+ +

Whence PolyKinds?

+

You have to define a new Leib variant and set of combinators for + each kind you wish to support. There is no need to do this in + Haskell, though.

+
λ> :k Leib []
+Leib [] :: (* -> *) -> *
+λ> :t refl :: Leib [] []
+refl :: Leib [] [] :: Leib [] []
+λ> :t lift (refl :: Leib [] [])
+lift (refl :: Leib [] []) :: Leib (f []) (f [])
+λ> :t compose (refl :: Leib [] [])
+compose (refl :: Leib [] []) :: Leib a [] -> Leib a []
+

In Haskell, we can take advantage of the fact that the actual + implementations are kind-agnostic, by having those definitions be + applicable to all kinds via + the PolyKinds language extension, + mentioned at the top of the Haskell code above. No such luck in + Scala.

+ +

Better GADTs

+

In a post from a couple months ago, + Kenji Yoshida outlines an interesting way to simulate the missing + type-evidence features of Scala’s GADT support with Leibniz. This + works in Haskell, too, in case you are comfortable with turning on + RankNTypes + but not + GADTs + somehow.

+

Let’s examine Kenji’s GADT.

+
sealed abstract class Foo[A, B]
+final case class X[A]() extends Foo[A, A]
+final case class Y[A, B](a: A, b: B) extends Foo[A, B]
+

For completeness, let’s also see the Haskell version, including the + function that demands so much hoop-jumping in Scala, but just works in + Haskell.

+
{-# LANGUAGE GADTs #-}
+module FooXY where
+
+data Foo a b where
+  X :: Foo a a
+  Y :: a -> b -> Foo a b
+
+hoge :: Foo a b -> f a c -> f b c
+hoge X bar = bar
+

Note that the Haskell type system understands that when hoge’s first + argument’s data constructor is X, the type variables a and b + must be the same type, and therefore by implication the argument of + type f a c must also be of type f b c. This is what we’re trying + to get Scala to understand.

+
def hoge1[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] =
+  foo match {
+    case X() => bar
+  }
+

This transliteration of the above Haskell hoge function fails to + compile, as Kenji notes, with the following:

+
…/LeibnizArticle.scala:39: type mismatch;
+ found   : bar.type (with underlying type F[A,C])
+ required: F[B,C]
+      case X() => bar
+                  ^
+ +

The overridden cata method

+

Kenji introduces a cata method on Foo to constrain use of the + Leibniz.force hack, while still providing external code with usable + Leibniz evidence that can be lifted to implement hoge. However, + by implementing the method in a slightly different way, we can use + refl instead.

+
sealed abstract class Foo[A, B] {
+  def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z): Z
+}
+
+final case class X[A]() extends Foo[A, A] {
+  def cata[Z](x: (A Leib A) => Z, y: (A, A) => Z) =
+    x(Leib.refl)
+}
+
+final case class Y[A, B](a: A, b: B) extends Foo[A, B] {
+  def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z) =
+    y(a, b)
+}
+

Now we can replace the pattern match (and all other such pattern + matches) with an equivalent cata invocation.

+
def hoge2[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] =
+  foo.cata(x => x.subst[F[?, C]](bar),
+           (_, _) => sys error "nonexhaustive")
+

So why can we get away with Leib.refl, whereas the function version + Kenji presents cannot? Compare the cata signature in Foo versus + X:

+
  def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z): Z
+  def cata[Z](x: (A Leib A) => Z, y: (A, A) => Z): Z
+

We supplied A for both the A and B type parameters in our + extends clause, so that substitution also applies in all methods + from Foo that we’re implementing, including cata. At that point + it’s obvious to the compiler that refl implements the requested + Leib.

+

Incidentally, a similar style of substitution underlies the definition + of refl.

+ +

The Leib member

+

What if we don’t want to write or maintain an overriding-style cata? + After all, that’s an n² commitment. Instead, we can incorporate a + Leib value in the GADT. First, let’s see what the equivalent + Haskell is, without the GADTs extension:

+
data Foo a b = X (Leib a b) | Y a b
+
+hoge :: Foo a b -> f a c -> f b c
+hoge (X leib) bar = runDual . subst leib . Dual $ bar
+

We needed RankNTypes to implement Leib, of course, but perhaps + that’s acceptable. It’s useful in + Ermine, which supports rank-N + types but not GADTs as of this writing.

+

The above is simple enough to port to Scala, though.

+
sealed abstract class Foo[A, B]
+final case class X[A, B](leib: Leib[A, B]) extends Foo[A, B]
+final case class Y[A, B](a: A, b: B) extends Foo[A, B]
+
+def hoge3[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] =
+  foo match {
+    case X(leib) => leib.subst[F[?, C]](bar)
+  }
+

It feels a little weird that X now must retain Foo’s + type-system-level separation of the two type parameters. But this + style may more naturally integrate in your ADTs, and it is much closer + to the original non-working hoge1 implementation.

+

It also feels a little weird that you have to waste a slot carting + around this evidence of type equality. As demonstrated in section + “It’s really there” above, though, it matters that the instance + exists.

+

You can play games with this definition to make it easier to supply + the wholly mechanical leib argument to X, e.g. adding it as an + implicit val in the second parameter list so it can be imported and + implicitly supplied on X construction. The basic technique is + exactly the same as above, though.

+ +

Leibniz mastery

+

This time we talked about

+
    +
  • Why it matters that subst always executes to use a type equality,
  • +
  • the Haskell implementation,
  • +
  • higher-kinded type equalities and their Leibnizes,
  • +
  • simulating GADTs with Leibniz members of data constructors.
  • +
+

This article was tested with Scala 2.11.2, + Kind Projector 0.5.2, and + GHC 7.8.3.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/hkts-moving-forward.html b/blog/hkts-moving-forward.html new file mode 100644 index 00000000..5376a36e --- /dev/null +++ b/blog/hkts-moving-forward.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + + + + + + + + Higher-kinded types: the difference between giving up, and moving forward + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Higher-kinded types: the difference between giving up, and moving forward

+ + + technical + +
+
+
+
+

Higher-kinded types: the difference between giving up, and moving forward

+

As its opening sentence reminds the reader—a point often missed by + many reviewers—the book + Functional Programming in Scala + is not a book about Scala. This (wise) choice occasionally manifests + in peculiar ways.

+

For example, you can go quite far into the book implementing its + exercises in languages with simpler type systems. Chapters 1–8 and 10 + port quite readily to + Java 8 and C#. So + Functional Programming in Scala can be a very fine resource for + learning some typed functional programming, even if such languages are + all you have to work with. Within these chapters, you can remain + blissfully unaware of the limitations imposed on you by these + languages’ type systems.

+

However, there is a point of inflection in the book at chapter 11. You + can pass through with a language such as OCaml, + Scala, Haskell, PureScript, or one of a + few others. However, users of Java, C#, F#, + Elm, and many others may proceed no further, + and must turn back here.

+

Various languages' chapter 11 support

+

Here is where abstracting over type constructors, or “higher-kinded + types”, comes into play. At this point in the book, you can give up, + or proceed with a sufficiently powerful language. Let’s see how this + happens.

+ +

Functional combinators

+

The bread and butter of everyday functional programming, the + “patterns” if you like, is the implementation of standard functional + combinators for your datatypes, and more importantly the comfortable, + confident use of these combinators in your program.

+

For example, confidence with bind, also known as >>= or flatMap, + is very important. The best way to acquire this comfort is to + reimplement it a bunch of times, so Functional Programming in Scala + has you do just that.

+
def flatMap[B](f: A => List[B]): List[B] // in List[A]
+def flatMap[B](f: A => Option[B]): Option[B] // in Option[A]
+def flatMap[B](f: A => Either[E, B]): Either[E, B] // in Either[E, A]
+def flatMap[B](f: A => State[S, B]): State[S, B] // in State[S, A]
+ +

All flatMaps are the same

+

The similarity between these functions’ types is the most obvious + surfacing of their ‘sameness’. (Unless you wish to count their names, + which I do not.) That sameness is congruent: when you write functions + using flatMap, in any of the varieties above, these functions + inherit a sort of sameness from the underlying flatMap combinator.

+

For example, supposing we have map and flatMap for a type, we can + ‘tuple’ the values within.

+
def tuple[A, B](as: List[A], bs: List[B]): List[(A, B)] =
+  as.flatMap{a =>
+    bs.map((a, _))}
+    
+def tuple[A, B](as: Option[A], bs: Option[B]): Option[(A, B)] =
+  as.flatMap{a =>
+    bs.map((a, _))}
+    
+def tuple[E, A, B](as: Either[E, A], bs: Either[E, B]): Either[E, (A, B)] =
+  as.flatMap{a =>
+    bs.map((a, _))}
+    
+def tuple[S, A, B](as: State[S, A], bs: State[S, B]): State[S, (A, B)] =
+  as.flatMap{a =>
+    bs.map((a, _))}
+

Functional Programming in Scala contains several such functions, + such as sequence. These are each implemented for several types, each + time with potentially the same code, if you remember to look back and + try copying and pasting a previous solution.

+ +

To parameterize, or not to parameterize

+

In programming, when we encounter such great sameness—not merely + similar code, but identical code—we would like the opportunity to + parameterize: extract the parts that are different to arguments, and + recycle the common code for all situations.

+

In tuple’s case, what is different are

+
    +
  1. the flatMap and map implementations, and
  2. +
  3. the type constructor: List, Option, State[S, ...], what + have you.
  4. +
+

We have a way to pass in implementations; that’s just higher-order + functions, or ‘functions as arguments’. For the type constructor, we + need ‘type-level functions as arguments’.

+
def tuplef[F[_], A, B](fa: F[A], fb: F[B]): F[(A, B)] = ???
+

We’ve handled ‘type constructor as argument’, and will add the + flatMap and map implementations in a moment. First, let’s learn + how to read this.

+ +

Reading a higher-kinded type

+

Confronted with a type like this, it’s helpful to sit back and muse on + the nature of a function for a moment.

+

Functions are given meaning by substitution of their arguments.

+
def double(x: Int) = x + x
+

double remains “an abstraction” until we substitute for x; in + other words, pass an argument.

+
double(2)    double(5)
+2 + 2        5 + 5
+4            10
+

But this isn’t enough to tell us what double is; all we see from + these tests is that double sometimes returns 4, sometimes 10, + sometimes maybe other things. We must imagine what double does in + common for all possible arguments.

+

Likewise, we give meaning to type-parameterized definitions like + tuplef by substitution. The parameter declaration F[_] means that + F may not be a simple type, like Int or String, but instead a + one-argument type constructor, like List or Option. Performing + these substitutions for tuplef, we get

+
// original, as above
+def tuplef[F[_], A, B](fa: F[A], fb: F[B]): F[(A, B)]
+
+// F = List
+def tupleList[A, B](fa: List[A], fb: List[B]): List[(A, B)]
+
+// F = Option
+def tupleOpt[A, B](fa: Option[A], fb: Option[B]): Option[(A, B)]
+

More complicated and powerful cases are available with other kinds of + type constructors, such as by partially applying. That’s how we can + fit State, Either, and other such types with two or more + parameters into the F parameter.

+
// F = Either[E, ...]
+def tupleEither[E, A, B](fa: Either[E, A], fb: Either[E, B])
+    : Either[E, (A, B)]
+
+// F = State[S, ...]
+def tupleState[S, A, B](fa: State[S, A], fb: State[S, B])
+    : State[S, (A, B)]
+

Just as with double, though this isn’t the whole story of tuplef, + its true meaning arises from the common way in which it treats all + possible F arguments. That is where higher kinds start to get + interesting.

+ +

Implementing functions with higher-kinded type

+

The type of tuplef expresses precisely our intent—the idea of + “multiplying” two Fs, tupling the values within—but cannot be + implemented as written. That’s because we don’t have functions that + operate on F-constructed values, like fa: F[A] and fb: F[B]. As + with any value of an ordinary type parameter, these are opaque.

+

In Scala, there are a few ways to pass in the necessary functions. One + option is to implement a trait or abstract class that itself uses + a higher-kinded type parameter or abstract type constructor. Here are + a couple possibilities.

+
trait Bindable[F[_], +A] {
+  def map[B](f: A => B): F[B]
+  def flatMap[B](f: A => F[B]): F[B]
+}
+
+trait BindableTM[+A] {
+  type F[X]
+  def map[B](f: A => B): F[B]
+  def flatMap[B](f: A => F[B]): F[B]
+}
+

Note that we must use higher-kinded trait type signatures to support + our higher-kinded method types; otherwise, we can’t write the return + types for map and flatMap.

+
trait BindableBad[F] {
+  def map[B](f: A => B): F ???
+            // where is the B supposed to go?
+

Now we make every type we’d like to support either inherit from or + implicitly convert to Bindable, such as List[+A] extends +Bindable[List, A], and write tuplef as follows.

+
def tupleBindable[F[_], A, B](fa: Bindable[F, A], fb: Bindable[F, B])
+    : F[(A, B)] =
+  fa.flatMap{a =>
+    fb.map((a, _))}
+ +

Escaping two bad choices

+

There are two major problems with Bindable’s representation of map + and flatMap, ensuring its wild unpopularity in the Scala functional + community, though it still appears in some places, such as + in Ermine.

+
    +
  1. The choices of inheritance and implicit conversion are both bad in + different ways. Implicit conversion propagates very poorly—it + doesn’t compose, after all, and fails as soon as we do something + innocent like put the value-to-be-converted into a tuple. + Inheritance leaves its own mess: modifying a type to add new, + nonessential operations, and the weird way that F is declared in + the method type parameters above.
  2. +
  3. The knowledge required to work out the new type signature above is + excessively magical. There are rules about when implicit conversion + happens, how much duplication of the reference to Bindable is + required to have the F parameter infer correctly, and even how + many calls to Bindable methods are performed. For example, we’d + have to declare the F parameter as F[X] <: Bindable[F, X] if we + did one more trailing map call. But then we wouldn’t support + implicit conversion cases anymore, so we’d have to do something + else, too.
  4. +
+

As a result of all this magic, generic functions over higher kinds + with OO-style operations tend to be ugly; note how much tuplef + looked like the List-specific type, and how little tupleBindable + looks like either of them.

+

But we still really, really want to be able to write this kind of + generic function. Luckily, we have a Wadler-made alternative.

+ +

Typeclasses constrain higher-kinded types elegantly

+

To constrain F to types with the flatMap and map we need, we use + typeclasses instead. For tuplef, that means we leave F abstract, + and leave the types of fa and fb as well as the return type + unchanged, but add an implicit argument, the “typeclass instance”, + which is a first-class representation of the map and flatMap + operations.

+
trait Bind[F[_]] {
+  // note the new ↓ fa argument
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
+}
+

Then we define instances for the types we’d like to have this on: + Bind[List], Bind[Option], and so on, as seen in chapter 11 of + Functional Programming in Scala.

+

Now we just add the argument to tuplef.

+
def tupleTC[F[_], A, B](fa: F[A], fb: F[B])
+           (implicit F: Bind[F]): F[(A, B)] =
+  F.flatMap(fa){a =>
+    F.map(fb)((a, _))}
+

We typically mirror the typeclass operations back to methods with an + implicit conversion—unlike with Bindable, this has no effect on + exposed APIs, so is benign. Then, we can remove the implicit F + argument, replacing it by writing F[_]: Bind in the type argument + list, and write the method body as it has been written before, with + flatMap and map methods.

+

There’s another major reason to prefer typeclasses, but let’s get back + to Functional Programming in Scala.

+ +

Getting stuck

+

I’ve just described many of the practical mechanics of writing useful + functions that abstract over type constructors, but all this is moot + if you cannot abstract over type constructors. The fact that Java + provides no such capability is not an indicator that they have + sufficient abstractions to replace this missing feature: it is simply + an abstraction that they do not provide you.

+

Oh, you would like to factor this common code? Sorry, you are + stuck. You will have to switch languages if you wish to proceed.

+ +

Don’t get stuck on the second order

+

map functions are obvious candidates for essential parts of a usable + library for functional programming. This is the first-order + abstraction—it eliminates the concrete loops, recursive functions, + or State lambda specifications, you would need to write otherwise.

+

When we note a commonality in patterns and define an abstraction over + that commonality, we move “one order up”. When we stopped simply + defining functions, and started taking functions as arguments, we + moved from the first order to the second order.

+

It is not enough for a modern general-purpose functional library in + Scala to simply have a bunch of map functions. It must also provide + the second-order feature: the ability to abstract over map + functions, as well as many, many other functions numerous type + constructors have in common. Let’s not give up; let’s move forward.

+

This article was tested with Scala 2.11.7 and + fpinscala 5b0115a answers, + with the addition of the method variants of List#map and + List#flatMap.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/http4s-error-handling-mtl-2.html b/blog/http4s-error-handling-mtl-2.html new file mode 100644 index 00000000..fe7a2a5b --- /dev/null +++ b/blog/http4s-error-handling-mtl-2.html @@ -0,0 +1,538 @@ + + + + + + + + + + + + + + + + + + + + + + + Error handling in Http4s with classy optics – Part 2 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Error handling in Http4s with classy optics – Part 2

+ + + technical + +
+
+
+
+

Error handling in Http4s with classy optics – Part 2

+

This is a continuation of my previous blog post. Make sure you have read that one before continuing here.

+

I recently gave a 20 minutes talk on classy optics at the unconference of Scale by the Bay where I also talked about this error handling technique and on my way back home I was still thinking of different ways of doing this. So, after some exploratory work, I came up with a few different alternatives.

+ +

Issues with first approach

+

Something that made me cringe and that a few of my colleagues at work were not happy with was that the algebras had no association with the error type defined in HttpErrorHandler[F, E] so the type-safety was down to the programmer's discipline and in this case the compiler was not able to do much.

+

When working with EitherT[F, E, A] or a bifunctor IO[E, A] we have a clear error type whereas by just relying on a single F[A] with a MonadError[F, Throwable] instance we lose this property. There are a few issues with the first though:

+
    +
  • It has a "double error channel", meaning that can report errors via Left or via its effect type IO.
  • +
  • As any other Monad Transformer in Scala, introduces a performance overhead due to the extra flatMap calls and extra allocations.
  • +
  • Code becomes cumbersome as we need to lift effects and pure Either values into the transformer stack.
  • +
+

The IO[E, A] model is naturally a better approach but I found out polymorphic code is more cumbersome than working with F[A]. Although this might change once Cats Effect 2.0 is out, it'll take a while until we get there.

+ +

Errors vs Failures

+

What I like about the IO[E, A] model is that we can distinguish between "business errors" and "unexpected failures" such as a database connection failure (learn more about zio's error model here). Eg: when working on a REST API, most of the time we only care about mapping a few business errors into the appropriate http responses. The unexpected failures should be handled by someone else. In this case http4s will convert any failure into a response with code 500 (internal server error).

+

And this is exactly what we want to achieve here. Writing polymorphic code using cats-effect while trying to keep it as simple as possible. Here's an encoding I would like to explore further:

+ +

Error Channel

+

In the previous blog post we defined the algebras as a single trait. In this case we are going to try a different encoding but first we need to introduce an ErrorChannel[F, E] typeclass where the error type is a subtype of Throwable to be compatible with the error type of the cats-effect typeclasses:

+
trait ErrorChannel[F[_], E <: Throwable] {
+  def raise[A](e: E): F[A]
+}
+

An instance can be derived for any ApplicativeError[F, Throwable] so we don't need to write it manually for every error type.

+
import cats.ApplicativeError
+
+object ErrorChannel {
+  def apply[F[_], E <: Throwable](implicit ev: ErrorChannel[F, E]) = ev
+
+  implicit def instance[F[_], E <: Throwable](implicit F: ApplicativeError[F, Throwable]): ErrorChannel[F, E] =
+    new ErrorChannel[F, E] {
+      override def raise[A](e: E) = F.raiseError(e)
+    }
+
+  object syntax {
+    implicit class ErrorChannelOps[F[_]: ErrorChannel[?[_], E], E <: Throwable](e: E) {
+      def raise[A]: F[A] = ErrorChannel[F, E].raise[A](e)
+    }
+  }
+}
+ +

User Algebra

+

Our UserAlg will now be defined as an abstract class instead in order to be able to add typeclass constraint.

+
case class User(username: String, age: Int)
+case class UserUpdateAge(age: Int)
+
+abstract class UserAlg[F[_]: ErrorChannel[?[_], E], E <: Throwable] {
+  def find(username: String): F[Option[User]]
+  def save(user: User): F[Unit]
+  def updateAge(username: String, age: Int): F[Unit]
+}
+

And here's the ADT of the possible errors that may arise (notice the extends Exception part):

+
sealed trait UserError extends Exception
+case class UserAlreadyExists(username: String) extends UserError
+case class UserNotFound(username: String) extends UserError
+case class InvalidUserAge(age: Int) extends UserError
+

We want to make sure our ADT is a subtype of Throwable and indeed Exception <: Throwable.

+ +

User Interpreter

+

Here's a similar UserAlg interpreter to the one presented in the previous post. Note that in a real-life project an interpreter will more likely connect to a database instead of using an in-memory representation based on Ref.

+

The interesting part is that in order to construct a UserAlg[F, UserError] we now need an ErrorChannel[F, UserError] instance in scope. This will be the chosen strategy to report errors in the context of F.

+
import cats.effect.{ Concurrent, Sync }
+import cats.effect.concurrent.Ref
+import cats.syntax.all._
+
+object UserInterpreter {
+
+  def mkUserAlg[F[_]: Sync](implicit error: ErrorChannel[F, UserError]): F[UserAlg[F, UserError]] =
+    Ref.of[F, Map[String, User]](Map.empty).map { state =>
+      new UserAlg[F, UserError] {
+        private def validateAge(age: Int): F[Unit] =
+          if (age <= 0) error.raise(InvalidUserAge(age)) else ().pure[F]
+
+        override def find(username: String): F[Option[User]] =
+          state.get.map(_.get(username))
+
+        override def save(user: User): F[Unit] =
+          validateAge(user.age) *>
+            find(user.username).flatMap {
+              case Some(_) =>
+                error.raise(UserAlreadyExists(user.username))
+//                error.raise(new Exception("asd")) // Does not compile
+//                Sync[F].raiseError(new Exception("")) // Should be considered an unrecoverable failure
+              case None =>
+                state.update(_.updated(user.username, user))
+            }
+
+        override def updateAge(username: String, age: Int): F[Unit] =
+          validateAge(age) *>
+            find(username).flatMap {
+              case Some(user) =>
+                state.update(_.updated(username, user.copy(age = age)))
+              case None =>
+                error.raise(UserNotFound(username))
+            }
+      }
+    }
+
+}
+

Notice that we could still call Sync[F].raiseError(new Exception("boom")) and it will still compile. However, if we choose to use ErrorChannel to signal business errors we will have the compiler on our side and it'll warn us when we try to raise an error that is not part of the ADT we have declared. So signaling error in a different way should just be considered unrecoverable. These are the same semantics you get when working with EitherT[IO, Throwable, ?] as shown in the comparison table at the beginning.

+ +

Http Error Handler

+

Here's the same HttpErrorHandler defined in the previous blog post:

+
import cats.{ ApplicativeError, MonadError }
+import cats.data.{ Kleisli, OptionT }
+import org.http4s._
+
+trait HttpErrorHandler[F[_], E <: Throwable] {
+  def handle(routes: HttpRoutes[F]): HttpRoutes[F]
+}
+
+object RoutesHttpErrorHandler {
+  def apply[F[_]: ApplicativeError[?[_], E], E <: Throwable](
+      routes: HttpRoutes[F]
+  )(handler: E => F[Response[F]]): HttpRoutes[F] =
+    Kleisli { req =>
+      OptionT {
+        routes.run(req).value.handleErrorWith(e => handler(e).map(Option(_)))
+      }
+    }
+}
+
+object HttpErrorHandler {
+  def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev
+
+  def mkInstance[F[_]: ApplicativeError[?[_], E], E <: Throwable](
+      handler: E => F[Response[F]]
+  ): HttpErrorHandler[F, E] =
+    (routes: HttpRoutes[F]) => RoutesHttpErrorHandler(routes)(handler)
+}
+ +

Http Routes with error handling

+

Now let's look at the new implementation of UserRoutes using the error-type algebra:

+
import cats.effect.Sync
+import cats.syntax.all._
+import io.circe.generic.auto._
+import io.circe.syntax._
+import org.http4s._
+import org.http4s.circe.CirceEntityDecoder._
+import org.http4s.circe._
+import org.http4s.dsl.Http4sDsl
+
+class PreUserRoutesMTL[F[_]: Sync](users: UserAlg[F, UserError]) extends Http4sDsl[F] {
+
+  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
+
+    case GET -> Root / "users" / username =>
+      users.find(username).flatMap {
+        case Some(user) => Ok(user.asJson)
+        case None       => NotFound(username.asJson)
+      }
+
+    case req @ POST -> Root / "users" =>
+      req.as[User].flatMap { user =>
+        users.save(user) *> Created(user.username.asJson)
+      }
+
+    case req @ PUT -> Root / "users" / username =>
+      req.as[UserUpdateAge].flatMap { userUpdate =>
+        users.updateAge(username, userUpdate.age) *> Created(username.asJson)
+      }
+  }
+
+  def routes(implicit H: HttpErrorHandler[F, UserError]): HttpRoutes[F] =
+    H.handle(httpRoutes)
+
+}
+

Notice that in contrast to the example shown in the previous blog post there is now a relationship between UserAlg and HttpErrorHandler: the error type is the same. However, this is not enforced by the compiler. Can we be more strict about it?

+

We could define a generic Routes[F, E]:

+
abstract class Routes[F[_], E <: Throwable](implicit H: HttpErrorHandler[F, E]) extends Http4sDsl[F] {
+  protected def httpRoutes: HttpRoutes[F]
+  val routes: HttpRoutes[F] = H.handle(httpRoutes)
+}
+

But we'll also need something else to connect the error types of the algebra and the http error handler:

+
abstract class UserRoutes[F[_]: HttpErrorHandler[?[_], E], E <: Throwable](
+    users: UserAlg[F, E]
+) extends Routes[F, E]
+

That's it! We are now enforcing this relationship at compile time. Let's see how the HttpRoutes looks like:

+
class UserRoutesAlt[F[_]: HttpErrorHandler[?[_], UserError]: Sync](
+    users: UserAlg[F, UserError]
+) extends UserRoutes(users) {
+
+  protected val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
+
+    case GET -> Root / "users" / username =>
+      users.find(username).flatMap {
+        case Some(user) => Ok(user.asJson)
+        case None       => NotFound(username.asJson)
+      }
+
+    case req @ POST -> Root / "users" =>
+      req
+        .as[User]
+        .flatMap { user =>
+          users.save(user) *> Created(user.username.asJson)
+        }
+
+    case req @ PUT -> Root / "users" / username =>
+      req
+        .as[UserUpdateAge]
+        .flatMap { userUpdate =>
+          users.updateAge(username, userUpdate.age) *> Ok(username.asJson)
+        }
+  }
+
+}
+

Neat! Right? If we try to change the error type of UserAlg it wouldn't compile!

+ +

More than one algebra per Http Route

+

In most of my programs I tend to specify an HttpRoute per algebra. But what if we wanted to just define a single HttpRoute that uses multiple algebras? There are a couple of options.

+

Let's first define a new ADT of errors and a new algebra to illustrate the problem:

+ +

Catalog Error

+
sealed trait CatalogError extends Exception
+case class ItemAlreadyExists(item: String) extends CatalogError
+case class CatalogNotFound(id: Long) extends CatalogError
+ +

CatalogAlg

+
case class Item(name: String) extends AnyVal
+
+abstract class CatalogAlg[F[_]: ErrorChannel[?[_], E], E <: Throwable] {
+  def find(id: Long): F[List[Item]]
+  def save(id: Long, item: Item): F[Unit]
+}
+ +

HttpRoutes with multiple algebras

+

Here we have an HttpRoutes that makes use of two algebras with different error types:

+
class UserRoutesMTL[F[_]: Sync](
+    users: UserAlg[F, UserError],
+    catalog: CatalogAlg[F, CatalogError]
+) extends Http4sDsl[F] {
+
+  private val httpRoutes: HttpRoutes[F] = ???
+
+  def routes(
+    implicit H1: HttpErrorHandler[F, UserError],
+             H2: HttpErrorHandler[F, CatalogError]
+  ): HttpRoutes[F] =
+    H2.handle(H1.handle(httpRoutes))
+
+}
+

It works! But it's not as elegant as we would like it to be and if we add more algebras this would quicky get out of control.

+

Can we generalize this pattern?

+ +

Shapeless Coproduct

+

We can define our error type as a coproduct of different errors, in our case UserError and CatalogError. For example:

+
import shapeless._
+
+def routes[F[_]](implicit H: HttpErrorHandler[F, UserError :+: CatalogError :+: CNil]) = ???
+

However, this doesn't compile because the error type is no longer a subtype of Throwable. It is now a Coproduct.

+

But we might be able to derive an instance for a coproduct of errors if we have an instance of HttpErrorHandler[F, E] for each error type. Let's give it a try! We need to define a new typeclass CoHttpErrorHandler:

+
import shapeless._
+
+trait CoHttpErrorHandler[F[_], Err <: Coproduct] {
+  def handle(routes: HttpRoutes[F]): HttpRoutes[F]
+}
+
+object CoHttpErrorHandler {
+  def apply[F[_], Err <: Coproduct](implicit ev: CoHttpErrorHandler[F, Err]) = ev
+
+  implicit def cNilInstance[F[_]]: CoHttpErrorHandler[F, CNil] =
+    (routes: HttpRoutes[F]) => routes
+
+  implicit def consInstance[F[_], E <: Throwable, T <: Coproduct](
+      implicit H: HttpErrorHandler[F, E],
+      CH: CoHttpErrorHandler[F, T]
+  ): CoHttpErrorHandler[F, E :+: T] =
+    (routes: HttpRoutes[F]) => CH.handle(H.handle(routes))
+}
+

Voilà! We introduced a CoHttpErrorHandler where the error type is a coproduct and the instance can only be derived if each type is a subtype of Throwable making it impossible to define an invalid coproduct. So it compiles! But how do we use it?

+ +

HttpRoutes for a coproduct of errors

+
class CoUserRoutesMTL[F[_]: Sync](
+    users: UserAlg[F, UserError],
+    catalog: CatalogAlg[F, CatalogError]
+) extends Http4sDsl[F] {
+
+  private val httpRoutes: HttpRoutes[F] = ???
+
+  def routes(implicit CH: CoHttpErrorHandler[F, UserError :+: CatalogError :+: CNil]): HttpRoutes[F] =
+    CH.handle(httpRoutes)
+
+}
+

Yay!!! Now this is more elegant and generic so we can re-use the same pattern in different routes. But now again we have lost the relationship between the error types of the algebras and the error type of CoHttpErrorHandler. So maybe we could do something similar to what we have done previously?

+

It's possible but in the case of coproducts we need to introduce some boilerplate...

+ +

CoRoutes

+
abstract class CoRoutes[F[_], E <: Coproduct](implicit CH: CoHttpErrorHandler[F, E]) extends Http4sDsl[F] {
+  protected def httpRoutes: HttpRoutes[F]
+  val routes: HttpRoutes[F] = CH.handle(httpRoutes)
+}
+

This one is pretty basic and similar to Routes defined before.

+ +

CoUserRoutes

+
abstract class CoUserRoutes[
+    F[_]: CoHttpErrorHandler[?[_], E],
+    A <: Throwable,
+    B <: Throwable,
+    E <: Coproduct: =:=[?, A :+: B :+: CNil]
+](
+    users: UserAlg[F, A],
+    catalog: CatalogAlg[F, B]
+) extends CoRoutes[F, E]
+
+type CustomError = UserError :+: CatalogError :+: CNil
+

Here we have a couple of constraints:

+
    +
  • F[_] needs to have an instance of CoHttpErrorHandler[F, E].
  • +
  • A and B are the error types of the two algebras.
  • +
  • E needs to be a Coproduct of type A :+: B :+: CNil.
  • +
+ +

HttpRoutes with multiple algebras - Strict version

+
class CoUserRoutesMTL[F[_]: CoHttpErrorHandler[?[_], CustomError]: Sync](
+    users: UserAlg[F, UserError],
+    catalog: CatalogAlg[F, CatalogError]
+) extends CoUserRoutes(users, catalog) {
+
+  protected val httpRoutes: HttpRoutes[F] = ???
+
+}
+

Now we are saying that the error type of our CoHttpErrorHandler is a coproduct of each error type of the algebras. And we wouldn't be able to change the error type of any of them without getting a compiler error.

+ +

Source code

+

You can see all the compiling examples here. Make sure you check out all the different branches.

+ +

Conclusion

+

The last approach is probably too much but we have demonstrated that it's possible to push the boundaries to make our application very type-safe. However, we also need to consider the trade-offs of writing more boilerplate.

+

Personally, I settle for the previous approach where the error type of the algebra matches the error type of the HttpErrorHandler even if it requires a bit more of discipline. The choice is yours! Just make sure you understand the trade-offs of every mechanism.

+

I hope you have enjoyed this post and please do let me know if you have other ideas to keep broadening my understanding!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Gabriel Volpe + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/http4s-error-handling-mtl.html b/blog/http4s-error-handling-mtl.html new file mode 100644 index 00000000..77e7bc57 --- /dev/null +++ b/blog/http4s-error-handling-mtl.html @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + + + + + + + Error handling in Http4s with classy optics + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Error handling in Http4s with classy optics

+ + + technical + +
+
+
+
+

Error handling in Http4s with classy optics

+

As a longtime http4s user I keep on learning new things and I'm always trying to come up with the best practices for writing http applications. This time I want to talk about my latest achievements in error handling within the context of an http application where it basically means mapping each business error to the appropiate http response.

+

So let's get started by putting up an example of an http application with three different endpoints that interacts with a UserAlgebra that may or may not fail with some specific errors.

+

If you are one of those who don't like to read and prefer to jump straight into the code please find it here :)

+ +

User Algebra

+

We have a simple UserAlgebra that let us perform some actions such as finding and persisting users.

+
case class User(username: String, age: Int)
+case class UserUpdateAge(age: Int)
+
+trait UserAlgebra[F[_]] {
+  def find(username: String): F[Option[User]]
+  def save(user: User): F[Unit]
+  def updateAge(username: String, age: Int): F[Unit]
+}
+

And also an ADT of the possible errors that may arise. I'll explain later in this post why it extends Exception.

+
sealed trait UserError extends Exception
+case class UserAlreadyExists(username: String) extends UserError
+case class UserNotFound(username: String) extends UserError
+case class InvalidUserAge(age: Int) extends UserError
+ +

User Interpreter

+

And here we have a simple interpreter for our UserAlgebra for demonstration purposes so you can have an idea on how the logic would look like. In a real-life project an interpreter will more likely connect to a database instead of using an in-memory representaion based on Ref.

+
import cats.effect.Sync
+import cats.effect.concurrent.Ref
+import cats.syntax.all._
+
+object UserInterpreter {
+
+  def create[F[_]](implicit F: Sync[F]): F[UserAlgebra[F]] =
+    Ref.of[F, Map[String, User]](Map.empty).map { state =>
+      new UserAlgebra[F] {
+        private def validateAge(age: Int): F[Unit] =
+          if (age <= 0) F.raiseError(InvalidUserAge(age)) else F.unit
+
+        override def find(username: String): F[Option[User]] =
+          state.get.map(_.get(username))
+
+        override def save(user: User): F[Unit] =
+          validateAge(user.age) *>
+            find(user.username).flatMap {
+              case Some(_) =>
+                F.raiseError(UserAlreadyExists(user.username))
+              case None =>
+                state.update(_.updated(user.username, user))
+            }
+
+        override def updateAge(username: String, age: Int): F[Unit] =
+          validateAge(age) *>
+            find(username).flatMap {
+              case Some(user) =>
+                state.update(_.updated(username, user.copy(age = age)))
+              case None =>
+                F.raiseError(UserNotFound(username))
+            }
+      }
+    }
+
+}
+ +

Http Routes

+

The following implementation of UserRoutes applies the tagless final encoding and the concept of "abstracting over the effect type" where we do not commit to a particular effect until the edge of our application.

+
import io.circe.generic.auto._
+import io.circe.syntax._
+import org.http4s._
+import org.http4s.circe._
+import org.http4s.circe.CirceEntityDecoder._
+import org.http4s.dsl.Http4sDsl
+
+class UserRoutes[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] {
+
+  val routes: HttpRoutes[F] = HttpRoutes.of[F] {
+
+    case GET -> Root / "users" / username =>
+      userAlgebra.find(username).flatMap {
+        case Some(user) => Ok(user.asJson)
+        case None => NotFound(username.asJson)
+      }
+
+    case req @ POST -> Root / "users" =>
+      req.as[User].flatMap { user =>
+        userAlgebra.save(user) *> Created(user.username.asJson)
+      }
+
+    case req @ PUT -> Root / "users" / username =>
+      req.as[UserUpdateAge].flatMap { userUpdate =>
+        userAlgebra.updateAge(username, userUpdate.age) *> Ok(username)
+      }
+  }
+
+}
+

Now this particular implementation is missing a very important part: error handling. If we use the UserAlgebra's interpreter previously defined we will clearly miss the three errors defined by the UserError ADT.

+

NOTE: If you are not familiar with these concepts make sure you check out my talk at Scala Matsuri early this year where I also talk about error handling in http applications using the Http4s library.

+ +

Http Error Handling

+

Okay let's just go ahead and add some error handling to our http route by taking advantange of the MonadError instance defined by our constraint Sync[F] and making use of the syntax provided by cats:

+
class UserRoutesAlt[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] {
+
+  val routes: HttpRoutes[F] = HttpRoutes.of[F] {
+
+    case GET -> Root / "users" / username =>
+      userAlgebra.find(username).flatMap {
+        case Some(user) => Ok(user.asJson)
+        case None => NotFound(username.asJson)
+      }
+
+    case req @ POST -> Root / "users" =>
+      req.as[User].flatMap { user =>
+        userAlgebra.save(user) *> Created(user.username.asJson)
+      }.handleErrorWith {
+        case UserAlreadyExists(username) => Conflict(username.asJson)
+      }
+
+    case req @ PUT -> Root / "users" / username =>
+      req.as[UserUpdateAge].flatMap { userUpdate =>
+        userAlgebra.updateAge(username, userUpdate.age) *> Ok(username.asJson)
+      }.handleErrorWith {
+        case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson)
+      }
+  }
+
+}
+

Now we can say this implementation is quite elegant! We are handling and mapping business errors to the according http response and our code compiles without any warning whatsoever. But wait... We are not handling the UserNotFound error and the compiler didn't tell us about it! That's not cool and we as functional programmers believe in types because we can know what a function might do just by looking at the types but here it seems we hit the wall.

+

The problem is that our constraint of type Sync from cats-effect has a MonadError instance with its type error fixed as Throwable. So the compiler can't help us here since this type is too generic. And we can't add a constraint for MonadError[F, UserError] because we would get an "ambigous implicits" error with two instances of MonadError in scope.

+

So, what can we do about it?

+ +

Next level MTL: Optics

+

I heard sometime ago about Classy Optics (Lenses, Prisms, etc) when I was learning Haskell and watched this amazing talk by George Wilson but I never got to use this concept in Scala until now!

+

Well first, let me give you a quick definition of Lenses and Prisms. In a few words we can define:

+
    +
  • Lenses as getters and setters that compose making the accessing of nested data structure's fields quite easy.
  • +
  • Prisms as first-class pattern matching that let us access branches of an ADT and that also compose.
  • +
+

And Classy Optics as the idea of "associate with each type a typeclass full of optics for that type".

+

So what am I talking about and how can these concepts help us solving the http error handling problem?

+

Remember that I defined the UserError ADT by extending Exception?

+
sealed trait UserError extends Exception
+case class UserAlreadyExists(username: String) extends UserError
+case class UserNotFound(username: String) extends UserError
+case class InvalidUserAge(age: Int) extends UserError
+

Well there's a reason! By making UserError a subtype of Exception (and by default of Throwable) we can take advantage of Prisms by going back and forth in the types. See what I'm going yet?

+

UserRoute has a Sync[F] constraint, meaning that we have available a MonadError[F, Throwable] instance, but we would like to have MonadError[F, UserError] instead to leverage the Scala compiler. The caveat is that the error types need to be of the same family so we can derive a Prism that can navigate the errors types in one direction or another. But how do we derive it?

+ +

Cats Meow MTL

+

Fortunately our friend Oleg Pyzhcov has created this great library named meow-mtl that makes heavy use of Shapeless in order to derive Lenses and Prisms and it provides instances for some cats-effect compatible datatypes.

+

And two of the supported typeclasses are ApplicativeError and MonadError as long as the error type is a subtype of Throwable to make it compatible with cats-effect. So we can do something like this:

+
import cats.MonadError
+import cats.effect.IO
+import com.olegpy.meow.hierarchy._ // All you need is this import!
+import scala.util.Random
+
+case class CustomError(msg: String) extends Throwable
+
+def customHandle[F[_], A](f: F[A], fallback: F[A])(implicit ev: MonadError[F, CustomError]): F[A] =
+  f.handleErrorWith(_ => fallback)
+
+val io: IO[Int] = IO(Random.nextInt(2)).flatMap { case 1 => IO.raiseError(new Exception("boom")) }
+customHandle(io, IO.pure(123))
+ +

Generalizing Http Error Handling

+

Now back to our use case. We can't have a MonadError[F, UserError] constraint because there's already a MonadError[F, Throwable] in scope given our Sync[F] constraint. But it turns out we can make this work if we also abstract over the error handling by introducing an HttpErrorHandler algebra where the error type is a subtype of Throwable.

+
trait HttpErrorHandler[F[_], E <: Throwable] {
+  def handle(routes: HttpRoutes[F]): HttpRoutes[F]
+}
+
+object HttpErrorHandler {
+  def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev
+}
+

UserRoutes can now have an additional constraint of type HttpErrorHandler[F, UserError] so we clearly know what kind of errors we are dealing with and can have the Scala compiler on our side.

+
class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F])(implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] {
+
+  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
+
+    case GET -> Root / "users" / username =>
+      userAlgebra.find(username).flatMap {
+        case Some(user) => Ok(user.asJson)
+        case None => NotFound(username.asJson)
+      }
+
+    case req @ POST -> Root / "users" =>
+      req.as[User].flatMap { user =>
+        userAlgebra.save(user) *> Created(user.username.asJson)
+      }
+
+    case req @ PUT -> Root / "users" / username =>
+      req.as[UserUpdateAge].flatMap { userUpdate =>
+        userAlgebra.updateAge(username, userUpdate.age) *> Created(username.asJson)
+      }
+  }
+
+  val routes: HttpRoutes[F] = H.handle(httpRoutes)
+
+}
+

We are basically delegating the error handling (AKA mapping business errors to appropiate http responses) to a specific algebra.

+

We also need an implementation for this algebra in order to handle errors of type UserError but first we can introduce a RoutesHttpErrorHandler object that encapsulates the repetitive task of handling errors given an HttpRoutes[F]:

+
import cats.ApplicativeError
+import cats.data.{Kleisli, OptionT}
+
+object RoutesHttpErrorHandler {
+  def apply[F[_], E <: Throwable](routes: HttpRoutes[F])(handler: E => F[Response[F]])(implicit ev: ApplicativeError[F, E]): HttpRoutes[F] =
+    Kleisli { req: Request[F] =>
+      OptionT {
+        routes.run(req).value.handleErrorWith { e => handler(e).map(Option(_)) }
+      }
+    }
+}
+

And our implementation:

+
class UserHttpErrorHandler[F[_]](implicit M: MonadError[F, UserError]) extends HttpErrorHandler[F, UserError] with Http4sDsl[F] {
+  private val handler: UserError => F[Response[F]] = {
+    case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson)
+    case UserAlreadyExists(username) => Conflict(username.asJson)
+    case UserNotFound(username) => NotFound(username.asJson)
+  }
+
+  override def handle(routes: HttpRoutes[F]): HttpRoutes[F] =
+    RoutesHttpErrorHandler(routes)(handler)
+  }
+

If we forget to handle some errors the compiler will shout at us "match may not be exhaustive!" That's fantastic :)

+ +

Wiring all the components

+

And the last part will be the wiring of all these components where we need to include the meow-mtl import to figure out the derivation of the instances we need in order to make this work. It'll look something like this if using cats.effect.IO:

+
import com.olegpy.meow.hierarchy._
+
+implicit val userHttpErrorHandler: HttpErrorHandler[IO, UserError] = new UserHttpErrorHandler[IO]
+
+UserInterpreter.create[IO].flatMap { UserAlgebra =>
+  val routes = new UserRoutesMTL[IO](UserAlgebra)
+  IO.unit // pretend this is the rest of your program
+}
+ +

Final thoughts

+

This is such an exciting time to be writing pure functional programming in Scala! The Typelevel ecosystem is getting richer and more mature, having an amazing set of libraries to solve business problems in an elegant and purely functional way.

+

I hope you have enjoyed this post and please do let me know if you know of better ways to solve this problem in the comments!

+

And last but not least I would like to thank all the friendly folks I hang out with in the cats-effect, cats, fs2 and http4s Gitter channels for all the time and effort they put (for free) into making this community an amazing space.

+

UPDATE: See the new article Error handling in Http4s with classy optics – Part 2.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Gabriel Volpe + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/implicitly-existential.html b/blog/implicitly-existential.html new file mode 100644 index 00000000..2683046d --- /dev/null +++ b/blog/implicitly-existential.html @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + + + + + + + When implicitly isn't specific enough + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

When implicitly isn't specific enough

+ + + technical + +
+
+
+
+

When implicitly isn't specific enough

+

When working with implicit-encoded dependent function types, such as + scalaz.Unapply and numerous Shapeless operations, you'd frequently + like to acquire instances of those functions to see what types get + calculated for them.

+

For example, ++ on Shapeless HLists is driven by Prepend:

+
def ++[S <: HList](suffix : S)(implicit prepend : Prepend[L, S])
+  : prepend.Out = prepend(l, suffix)
+

So given some HLists, we can expect to be able to combine them in a + couple ways. First, by using the syntax function above, and then by + acquiring a value of prepend's type directly and invoking it, just + as in the body of the above function.

+
import shapeless._, ops.hlist._
+import scalaz._, std.string._, std.tuple._, syntax.applicative._
+
+scala> val ohi = 1 :: "hi" :: HNil
+ohi: shapeless.::[Int,shapeless.::[String,shapeless.HNil]]
+        = 1 :: hi :: HNil
+
+scala> ohi ++ ohi
+res0: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]] = 1 :: hi :: 1 :: hi :: HNil
+
+scala> val ohipohi = implicitly[Prepend[String :: Int :: HNil, String :: Int :: HNil]]
+ohipohi: shapeless.ops.hlist.Prepend[
+           shapeless.::[String,shapeless.::[Int,shapeless.HNil]],
+           shapeless.::[String,shapeless.::[Int,shapeless.HNil]]]
+  = shapeless.ops.hlist$Prepend$$anon$58@13399e98
+
+scala> ohipohi(ohi, ohi)
+res3: ohipohi.Out = 1 :: hi :: 1 :: hi :: HNil
+

Back over in Scalaz, for purposes of an Applicative instance, + (String, Int) selects its second type parameter. Just as the + To*OpsUnapply functions acquire Unapply instances to do their + work:

+
implicit def ToApplicativeOpsUnapply[FA](v: FA)(implicit F0: Unapply[Applicative, FA]) =
+  new ApplicativeOps[F0.M,F0.A](F0(v))(F0.TC)
+

We can acquire an instance and use it.

+
scala> val t2ap = implicitly[Unapply[Applicative, (String, Int)]]
+t2ap: scalaz.Unapply[scalaz.Applicative,(String, Int)] =
+scalaz.Unapply_0$$anon$13@18214797
+
+scala> t2ap.TC.point(42)
+res5: t2ap.M[Int] = ("",42)
+ +

The mysterious result

+

Now let's get that first element out of that tuple we got by calling + point.

+
scala> res5._1
+<console>:31: error: value _1 is not a member of t2ap.M[Int]
+              res5._1
+                   ^
+

Uh, huh? Let's try adding the HLists we got from ohipohi before.

+
cala> res3 ++ res3
+<console>:32: error: could not find implicit value for parameter
+              prepend: shapeless.ops.hlist.Prepend[ohipohi.Out,ohipohi.Out]
+              res3 ++ res3
+                   ^
+

The clue is in the type report in the above: path-dependent type + members of t2ap and ohipohi appear. That wouldn't be a problem, + normally, as we know what they are, but they're existential to + Scala.

+
scala> implicitly[t2ap.M[Int] =:= (String, Int)]
+<console>:30: error: Cannot prove that t2ap.M[Int] =:= (String, Int).
+              implicitly[t2ap.M[Int] =:= (String, Int)]
+                        ^
+ +

implicitly only gives what you ask for

+

The explanation lies with the implicitly calls we made to acquire + the specific dependent functions we wanted to use. Let's look at the + definition of implicitly and see if it can enlighten:

+
def implicitly[T](implicit e: T): T
+

In other words, implicitly returns exactly what you asked for, + type-wise. Recall the inferred type of ohipohi when it was defined:

+
ohipohi: shapeless.ops.hlist.Prepend[
+           shapeless.::[String,shapeless.::[Int,shapeless.HNil]],
+           shapeless.::[String,shapeless.::[Int,shapeless.HNil]]]
+

Not coincidentally, this is the exact type we gave as a type + parameter to implicitly. What's important is that Out, the type + member of Prepend that determines its result type, is existential in + both cases.

+

In other words, the rule of implicitly is “you asked for it, you got + it”.

+ +

A more specific implicitly

+

The answer here is to simulate the weird way in which dependent method + types, like ++ and ToApplicativeOpsUnapply, can pass through extra + type information about their implicit parameters that would otherwise + be lost. We do this by reinventing implicitly.

+

The first try is obvious: follow the comment in the Predef.scala + source and give implicitly a singleton type result.

+
def implicitly2[T <: AnyRef](implicit e: T): T with e.type = e
+
+scala> val ohipohi2 = implicitly2[Prepend[Int :: String :: HNil, Int :: String :: HNil]]
+ohipohi2: shapeless.ops.hlist.Prepend[
+              shapeless.::[Int,shapeless.::[String,shapeless.HNil]],
+              shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]
+     with e.type = shapeless.ops.hlist$Prepend$$anon$58@4abe65da
+
+scala> ohipohi2(ohi, ohi)
+res9: ohipohi2.Out = 1 :: hi :: 1 :: hi :: HNil
+
+scala> res9 ++ res9
+<console>:33: error: could not find implicit value for parameter
+              prepend: shapeless.ops.hlist.Prepend[ohipohi2.Out,ohipohi2.Out]
+              res9 ++ res9
+                   ^
+

Not quite good enough.

+ +

An even more, albeit less, specific implicitly

+

I think it's strange that the above doesn't work, but we can deal with + it by being a little more specific.

+
def implicitlyDepFn[T <: DepFn2[_,_]](implicit e: T)
+    : T {type Out = e.Out} = e
+
+scala> val ohipohi3 = implicitlyDepFn[Prepend[Int :: String :: HNil, Int :: String :: HNil]]
+ohipohi3: shapeless.ops.hlist.Prepend[
+              shapeless.::[Int,shapeless.::[String,shapeless.HNil]],
+              shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]{
+                type Out = shapeless.::[Int,shapeless.::[String,
+                            shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]]
+          } = shapeless.ops.hlist$Prepend$$anon$58@7306572f
+
+scala> ohipohi3(ohi, ohi)
+res11: ohipohi3.Out = 1 :: hi :: 1 :: hi :: HNil
+
+scala> res11 ++ res11
+res12: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String,
+       shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String,
+       shapeless.HNil]]]]]]]]
+   = 1 :: hi :: 1 :: hi :: 1 :: hi :: 1 :: hi :: HNil
+

Now that's more like it. The trick is in the return type of + implicitlyDepFn, which includes the structural refinement {type Out += e.Out}.

+

Again, it's weird that this structural refinement isn't subsumed by + the return type e.type from implicitly2's definition, but I'm not + sure it's wrong, either, given the ephemeral nature of type stability.

+

Thankfully, most of the evidence for dependent function types in + Shapeless extends from the DepFn* traits, so you only need one of + these special implicitly variants for each, rather than one for each + individual dependent function type you wish to acquire instances of in + this way.

+ +

And likewise with Unapply

+

We can similarly acquire instances of scalaz.Unapply conveniently. + I believe this function will be supplied with Scalaz 7.0.6, and it is + already included in the 7.1 development branch, + so you will be able to write Unapply[TC, type] to get instances as + with plain typeclass lookup in Scalaz, but it's easy enough to define + yourself.

+
def unap[TC[_[_]], MA](implicit U: Unapply[TC, MA]): U.type {
+  type M[A] = U.M[A]
+  type A = U.A
+} = U
+
+scala> val t2ap2 = unap[Applicative, (String, Int)]
+t2ap2: U.type{type M[A] = (String, A); type A = Int} 
+  = scalaz.Unapply_0$$anon$13@3adb9933
+
+scala> t2ap2.TC.point(42)
+res13: (String, Int) = ("",42)
+
+scala> res13._1
+res14: String = ""
+

This article was tested with Scala 2.10.3, Scalaz 7.0.5, and + Shapeless 2.0.0-M1.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/inauguration.html b/blog/inauguration.html new file mode 100644 index 00000000..b3af28b0 --- /dev/null +++ b/blog/inauguration.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + Inaugurating the typelevel.scala blog + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Inaugurating the typelevel.scala blog

+ + + governance + +
+
+
+
+

Inaugurating the typelevel.scala blog

+

This Twitter conversation happened today:

+
+

@d6 do you have a blog on numerics, marcos, and performance? + — eugene yokota (@eed3si9n) April 4, 2013

+

@eed3si9n Not currently. Maybe I should start one? + — Eiríkr Åsheim (@d6) April 4, 2013

+

@d6 *cough* typelevel.org/blog/ *cough* + — Tom Switzer (@tixxit) April 4, 2013

+
+

So, here it is, the typelevel.scala blog!

+ +

What is it about?

+

As you might already know, typelevel.scala is a collection of libraries which provide a great amount of abstraction. + Here, we would like to show how to use them in your code, provide examples, collect learning resources, and explore implementation details.

+ +

Who writes here?

+

Everyone who would like to! Contributions are welcome. + If you want to share something about Scalaz, Shapeless, Spire, or Scala topics in general (e.g. type classes), case studies, examples, or other related content, please do not hesitate to contact us. + This blog (and in fact, the whole web site) is built using Jekyll on GitHub pages, so you can just fork the repository, add a post, and create a pull request.

+ +

Stay tuned!

+

We hope that this blog will be filled with content soon. + To make sure that you don't miss anything, follow @typelevel on Twitter or subscribe to the RSS feed.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 00000000..dd7df007 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,2854 @@ + + + + + + + + + + + + + + + + + + + + + + + Blog + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ + +
+
+

+ + Blog + + + + +

+

+ Follow our blog for announcements, events, and community-contributed posts. +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + diff --git a/blog/information-hiding.html b/blog/information-hiding.html new file mode 100644 index 00000000..43ffe303 --- /dev/null +++ b/blog/information-hiding.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + Information hiding, enforced + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Information hiding, enforced

+ + + technical + +
+
+
+
+

Information hiding, enforced

+

Code should be reusable. An expression traversing a data structure + shouldn't be written multiple times, it should be pulled out into a + generic traversal function. At a larger scale, a random number generator + shouldn't be written multiple times, but rather pulled out into a + module that can be used by others.

+

It is important that such abstractions must be done carefully. + Often times a type is visible to the caller, and if the type + is not handled carefully the abstraction can leak.

+

For example, a set with fast random indexing (useful for random + walks on a graph) can be implemented with a sorted Vector. + However, if the Vector type is + leaked, the user can use this knowledge to violate the invariant.

+
import scala.annotation.tailrec
+
+/** (i in repr, position of i in repr) */
+def binarySearch(i: Int, repr: Vector[Int]): (Boolean, Int) = /* elided */
+
+object IntSet {
+  type Repr = Vector[Int]
+
+  def empty: Repr = Vector.empty
+
+  def add(i: Int, repr: Repr): Repr = {
+    val (isMember, indexOf) = binarySearch(i, repr)
+    if (isMember) repr
+    else {
+      val (prefix, suffix) = repr.splitAt(indexOf)
+      prefix ++ Vector(i) ++ suffix
+    }
+  }
+
+  def contains(i: Int, repr: Repr): Boolean =
+    binarySearch(i, repr)._1
+}
+
import IntSet._
+// import IntSet._
+
+val good = add(1, add(10, add(5, empty)))
+// good: IntSet.Repr = Vector(1, 5, 10)
+
+val goodResult = contains(10, good)
+// goodResult: Boolean = true
+
+val bad = good.reverse // We know it's a Vector!
+// bad: scala.collection.immutable.Vector[Int] = Vector(10, 5, 1)
+
+val badResult = contains(10, bad)
+// badResult: Boolean = false
+
+val bad2 = Vector(10, 5, 1) // Alternatively..
+// bad2: scala.collection.immutable.Vector[Int] = Vector(10, 5, 1)
+
+val badResult2 = contains(10, bad2)
+// badResult2: Boolean = false
+

The issue here is the user knows more about the representation than they + should. The function add enforces the sorted invariant on each insert, + and the function contains leverages this to do an efficient look-up. + Because the Vector definition of Repr is exposed, the user is + free to create any Vector they wish which may violate the invariant, + thus breaking contains.

+

In general, the name of the representation type is needed but the + definition is not. If the definition is hidden, the user is only able to + work with the type to the extent the module allows. This is precisely + the notion of information hiding. If this can be enforced by the type + system, modules can be swapped in and out without worrying about breaking + client code.

+ +

Quantification

+

It turns out there is a well understood principle + behind this idea called existential quantification. Contrast with + universal quantification which says "for all", existential quantification + says "there is a."

+

Below is an encoding of universal quantification via parametric polymorphism.

+
trait Universal {
+  def apply[A]: A => A
+}
+

Here Universal#apply says for all choices of A, a function A => A can be + written. In the Curry-Howard Isomorphism, a profound + relationship between logic and computation, this translates to "for all propositions + A, A implies A." It is therefore acceptable to write the following, which picks + A to be Int.

+
def intInstantiatedU(u: Universal): Int => Int =
+  (i: Int) => u.apply(i)
+// intInstantiatedU: (u: Universal)Int => Int
+

Existential quantification can also be written in Scala.

+
trait Existential {
+  type A
+
+  def apply: A => A
+}
+

Note that this is just one way of encoding existentials - for a deeper + discussion, refer to the excellent Type Parameters and Type Members + blog series.

+

The type parameter on apply has been moved up to a type member of the trait. + Practically, this means every instance of Existential must pick one choice of + A, whereas in Universal the A was parameterized and therefore free. In the + language of logic, Existential#apply says "there is a" or "there exists some A such that + A implies A." This "there is a" is the crux of the error when trying + to write a corresponding intExistential function.

+
def intInstantiatedE(e: Existential): Int => Int =
+  (i: Int) => e.apply(i)
+// <console>:19: error: type mismatch;
+//  found   : i.type (with underlying type Int)
+//  required: e.A
+//          (i: Int) => e.apply(i)
+//                              ^
+

In code, the type in Existential is chosen per-instance, so there is no way + of knowing what the actual type chosen is. In logical terms, the only guarantee is + that there exists some proposition that satisfies the implication, but it is not + necessarily the case (and often is not) it holds for all propositions.

+ +

Abstract types

+

In the ML family of languages (e.g. Standard ML, OCaml), existential quantification + and thus information hiding, is achieved through type members. + Programs are organized into modules which are what contain these + types.

+

In Scala, this translates to organizing code with the object system, using the same + type member feature to hide representation. The earlier example of IntSet can then + be written:

+
/** Abstract signature */
+trait IntSet {
+  type Repr
+
+  def empty: Repr
+  def add(i: Int, repr: Repr): Repr
+  def contains(i: Int, repr: Repr): Boolean
+}
+
+/** Concrete implementation */
+object VectorIntSet extends IntSet {
+  type Repr = Vector[Int]
+
+  def empty: Repr = Vector.empty
+
+  def add(i: Int, repr: Repr): Repr = {
+    val (isMember, indexOf) = binarySearch(i, repr)
+    if (isMember) repr
+    else {
+      val (prefix, suffix) = repr.splitAt(indexOf)
+      prefix ++ Vector(i) ++ suffix
+    }
+  }
+
+  def contains(i: Int, repr: Repr): Boolean =
+    binarySearch(i, repr)._1
+}
+

As long as client code is written against the signature, the + representation cannot be leaked.

+
def goodUsage(set: IntSet) = {
+  import set._
+  val s = add(1, add(10, add(5, empty)))
+  contains(5, s)
+}
+// goodUsage: (set: IntSet)Boolean
+

If the user tries to assert the representation type, the type + checker prevents it at compile time.

+
def badUsage(set: IntSet) = {
+  import set._
+  val s = add(10, add(1, empty))
+
+  // Maybe it's a Vector
+  s.reverse
+  contains(10, Vector(10, 5, 1))
+}
+// <console>:23: error: value reverse is not a member of set.Repr
+//          s.reverse
+//            ^
+// <console>:24: error: type mismatch;
+//  found   : scala.collection.immutable.Vector[Int]
+//  required: set.Repr
+//          contains(10, Vector(10, 5, 1))
+//                             ^
+ +

Parametricity

+

Abstract types enforce information hiding at the definition site (the definition + of IntSet is what hides Repr). There is another mechanism that enforces information + hiding, which pushes the constraint to the use site.

+

Consider implementing the following function.

+
def foo[A](a: A): A = ???
+

Given nothing is known about a, the only possible thing foo can do is return a. If + instead of a type parameter the function was given more information..

+
def bar(a: String): String = "not even going to use `a`"
+

..that information can be leveraged to do unexpected things. This is similar to + the first IntSet example when knowledge of the underlying Vector allowed unintended + behavior to occur.

+

From the outside looking in, foo is universally quantified - the caller gets to + pick any A they want. From the inside looking out, it is + existentially quantified - the implementation knows only as much + about A as there are constraints on A (in this case, nothing).

+

Consider another function listReplace.

+
def listReplace[A, B](as: List[A], b: B): List[B] = ???
+

Given the type parameters, listReplace looks fairly constrained. The name and signature + suggests it takes each element of as and replaces it with b, returning a new list. + However, even knowledge of List can lead to type checking implementations with strange behavior.

+
// Completely ignores the input parameters
+def listReplace[A, B](as: List[A], b: B): List[B] = List.empty[B]
+

Here, knowledge of List allows the implementation + to create a list out of thin air and use that in the implementation. If instead listReplace + only knew about some F[_] where F is a Functor, the implementation becomes much more + constrained.

+
trait Functor[F[_]] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+}
+
+implicit val listFunctor: Functor[List] =
+  new Functor[List] {
+    def map[A, B](fa: List[A])(f: A => B): List[B] =
+      fa.map(f)
+  }
+
+def replace[F[_]: Functor, A, B](fa: F[A], b: B): F[B] =
+  implicitly[Functor[F]].map(fa)(_ => b)
+
replace(List(1, 2, 3), "typelevel")
+// res8: List[String] = List(typelevel, typelevel, typelevel)
+

Absent any knowledge of F other than the ability to map over it, replace is + forced to do the correct thing. Put differently, irrelevant information about F is hidden.

+

The fundamental idea behind this is known as parametricity, made popular by Philip Wadler's + seminal Theorems for free! paper. The technique is best summarized by the + following excerpt from the paper:

+
Write down the definition of a polymorphic function on a piece of paper. Tell me its type, + but be careful not to let me see the function's definition. I will tell you a theorem that + the function satisfies.
+ +

Why types matter

+

Information hiding is a core tenet of good program design, and it is important to make + sure it is enforced. Underlying information hiding is existential quantification, + which can manifest itself in computation through abstract types and + parametricity. Few languages support defining abstract type members, and fewer + yet support higher-kinded types used in the replace example. It is therefore + to the extent that a language's type system is expressive that + abstraction can be enforced.

+

This blog post was tested with Scala 2.11.7 using tut.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/internal-state.html b/blog/internal-state.html new file mode 100644 index 00000000..f1a17184 --- /dev/null +++ b/blog/internal-state.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + Making internal state functional + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Making internal state functional

+ + + technical + +
+
+
+
+

Making internal state functional

+

This is the ninth of a series of articles on “Type Parameters and + Type Members”.

+

Scala’s + CanBuildFrom API + is relatively well-founded and flexible; + in combination with GADTs, it can provide that flexibility in a fully type-safe way, + if users choose not to circumvent it with typecasting.

+

However, it is designed in a purely mutable way; you cannot write a + useful CanBuildFrom that does not rely on mutation, and you cannot + use the API externally in a functional way.

+

Let’s design an alternative to CanBuildFrom that makes sense in a + purely functional context, allowing both implementers and users to + avoid unsightly mutation.

+

Spoiler warning! Our first pass will have one glaring inelegance. We + will use concepts from previous articles in Type Parameters and Type + Members to “invert the abstraction”, which will greatly simplify the + design. Once you’re comfortable with the “inversion”, you can skip the + intermediate step and use this technique directly in your own designs.

+ +

Disallowing functional approaches

+

The pattern of use of CanBuildFrom is

+
    +
  1. apply the CBF to produce a + Builder.
  2. +
  3. Call += and ++= methods to “fill up” the Builder.
  4. +
  5. Call result to “finalize” or “commit” to the final structure.
  6. +
+
import collection.generic.CanBuildFrom
+
+val cbf = implicitly[CanBuildFrom[Nothing, Int, List[Int]]]
+val b = cbf()
+b += 3
+b ++= Seq(4, 5)
+b.result()
+
+res0: List[Int] = List(3, 4, 5)
+

Let’s set aside that this is only suited to eager collections, not + lazy ones like + Stream. You + can tell the problem by types: += and ++= have the return type + this.type. Effectively, this means that if their implementations are + purely functional, all they can do is return this:

+
  def +=(elem: Elem) = this
+  def ++=(elems: TraversableOnce[Elem]) = this
+

Aside from the informal contract of Builder, which suggests that + calls to these methods perform a side effect, the types enforce that + they must perform any useful work by means of side effects.

+

Returning this.type permits these methods to be called in a + superficially functional style:

+
b.+=(3)
+ .++=(Seq(4, 5))
+ .result()
+
+res1: List[Int] = List(3, 4, 5)
+

This retouch is only skin-deep, and can’t repair the defect making + CanBuildFrom unsuitable for functional programs, but it implies that + a functional alternative lurks nearby. Let’s go looking for it.

+ +

Step 1: explicit Builder state

+

First, we need to take the essential mutation out of Builder. That + means it needs to provide an initial state, and the other methods must + use it as a parameter and return value.

+
    +
  1. We’ll add a new method to return the initial state.
  2. +
  3. += and ++= will take that state as an argument, returning the + new state instead of this.type.
  4. +
  5. result will take the final state as an argument, still producing + the result collection.
  6. +
+

While the intermediate state might be the same as the final state, + we don’t want to require that. So Builder also gains a type + parameter to represent the type of state, S.

+
trait FunBuilder[S, -Elem, +To] {
+  /** Produce the initial state. */
+  def init: S
+
+  // note everywhere 'S' was added
+  def +=(s: S, elem: Elem): S
+  def ++=(s: S, elems: TraversableOnce[Elem]): S
+  def result(s: S): To
+}
+ +

A sample FunBuilder

+

We can incrementally build a + Vector, + but it may not be the most efficient way. Instead, let’s try to + accumulate a + List, + then construct the Vector once we’re done.

+
class VectorBuilderList[A]
+    extends FunBuilder[List[A], A, Vector[A]] {
+
+  def init = List()
+  
+  def +=(s: List[A], elem: A) = elem :: s
+  
+  def ++=(s: List[A], elems: TraversableOnce[A]) =
+    elems.toList reverse_::: s
+  
+  def result(s: List[A]) =
+    s.toVector.reverse
+}
+
+val vbl = new VectorBuilderList[Int]
+vbl.result(vbl.++=(vbl.+=(vbl.init, 2), Seq(3, 4)))
+
+res0: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4)
+

(There’s a problem with CanBuildFrom now, but we’ll hold off fixing + it.)

+ +

A slightly different Builder

+

Maybe it would be better to optimize for the ++= “bulk add” method, + though.

+
class VectorBuilderListList[A]
+    extends FunBuilder[List[Traversable[A]], A, Vector[A]] {
+  def init = List()
+  
+  def +=(s: List[Traversable[A]], elem: A) =
+    Traversable(elem) :: s
+    
+  def ++=(s: List[Traversable[A]], elems: TraversableOnce[A]) =
+    elems.toTraversable :: s
+    
+  def result(s: List[Traversable[A]]) =
+    s.foldLeft(Vector[A]()){(z, as) => as ++: z}
+}
+
+val vbll = new VectorBuilderListList[Int]
+vbll.result(vbll.++=(vbll.+=(vbll.init, 2), Seq(3, 4)))
+
+res0: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4)
+ +

Hide your state

+

The type of these builders are different, even though their usage is + the same. This design also exposes what was originally internal + state as part of the API. Luckily, CanBuildFrom makes a point of + this when we try to integrate FunBuilder into our own CBF version; + there’s nowhere to put the S type parameter.

+
trait FunCanBuildFrom[-From, -Elem, +To] {
+  def apply(): FunBuilder[S, Elem, To]
+}
+
+…/FCBF.scala:42: not found: type S
+  def apply(): FunBuilder[S, Elem, To]
+                          ^
+

We can hide the state by forcing the caller to deal with the builder + in a state-generic context. One way to do this is with a generic + continuation.

+
trait BuilderCont[+Elem, -To, +Z] {
+  def continue[S](builder: FunBuilder[S, Elem, To]): Z
+}
+
+// in FunCanBuildFrom...
+  def apply[Z](cont: BuilderCont[Elem, To, Z]): Z
+

Now we can implement a FunCanBuildFrom that can use either of the + FunBuilders we’ve defined.

+
class VectorCBF[A](bulkOptimized: Boolean)
+    extends FunCanBuildFrom[Any, A, Vector[A]] {
+  def apply[Z](cont: BuilderCont[A, Vector[A], Z]) =
+    if (bulkOptimized)
+      cont continue (new VectorBuilderListList)
+    else
+      cont continue (new VectorBuilderList)
+}
+

Take a look at the type flow. The caller of apply is the one who + decides the Z type. But the apply implementation chooses the S + to pass to continue, which cannot know any more about what that + state type is. (It can even choose different types based on runtime + decisions.) Information hiding is restored.

+
val cbf = new VectorCBF[Int](true)
+cbf{new BuilderCont[Int, Vector[Int], Vector[Int]] {
+  def continue[S](vbl: FunBuilder[S, Int, Vector[Int]]) =
+    vbl.result(vbl.++=(vbl.+=(vbl.init, 2), Seq(3, 4)))
+}}
+
+res1: Vector[Int] = Vector(2, 3, 4)
+

Now the code using the FunBuilder can’t fiddle with the + FunBuilder’s state values; it can only rewind to previously seen + states, a norm to be expected in functional programming with + persistent state values.

+ +

Existential types are abstraction inversion

+

This is rather a lot of inconvenient ceremony, though. Instead of + passing a continuation that receives the S type as an argument along + with the FunBuilder, let’s just have apply return the type along + with the FunBuilder. We have a tool for returning a pair of type and + value using that type.

+
  def apply(): FunBuilder[_, Elem, To]
+

Remember that existential types are pairs.

+

Having collapsed callee-of-callee back to caller perspective, let’s + apply the rule of thumb from + the first post in this series.

+
A type parameter is usually more convenient and harder to screw up, but if you intend to use it existentially in most cases, changing it to a member is probably better.
+

The usual case will be from the perspective of a CBF user, so the + usual use of the S parameter is existential. So let’s turn it into + the equivalent type member.

+
// rewrite the heading of FunBuilder as
+trait FunBuilder[-Elem, +To] {
+  type S
+  
+// and FunCanBuildFrom#apply as
+  def apply(): FunBuilder[Elem, To]
+
+// and the parameter S moves to a member
+// for all implementations so far;
+// fix until compile or see appendix
+

And we can see the information stays hidden.

+
scala> val cbf = new VectorCBF[Int](true)
+cbf: fcbf.VectorCBF[Int] = fcbf.VectorCBF@4363e2ba
+
+scala> val vb = cbf()
+vb: fcbf.FunBuilder[Int,Vector[Int]] = fcbf.VectorBuilderListList@527c222e
+
+scala> val with1 = vb.+=(vb.init, 2)
+with1: vb.S = List(List(2))
+
+scala> val with2 = vb.++=(with1, Seq(2, 3))
+with2: vb.S = List(List(2, 3), List(2))
+
+scala> vb.result(with2)
+res0: Vector[Int] = Vector(2, 2, 3)
+

As in + “Values never change types”, + vb.S is abstract, existential, irreducible.

+ +

Last minute adjustments

+

Builder had to be separate from CanBuildFrom because the latter + had to be stateless, with Builder needing to be stateful. Now that + both are stateless, the FunBuilder API can probably be collapsed + into FunCanBuildFrom.

+

This leaves the question, what about the mutable-state Builders? + They can mutate the S, returning the input state from += and + ++=. You can’t use S values to rewind such a FunBuilder, but you + couldn’t before, anyway.

+

In the next part, “Avoiding refinement with dependent method types”, + we’ll look at the meaning of Scala’s “dependent method types” feature, + using it to replace some more type parameters with type members in + non-existential use cases.

+

This article was tested with Scala 2.11.8.

+ +

Appendix: final FunBuilder examples

+

The rewrite from S type parameter to member in the FunBuilder + implementations is a boring, mechanical transform, but I’ve included + it here for easy reference.

+
class VectorBuilderList[A]
+    extends FunBuilder[A, Vector[A]] {
+  type S = List[A]
+
+  def init = List()
+  
+  def +=(s: S, elem: A) = elem :: s
+  
+  def ++=(s: S, elems: TraversableOnce[A]) =
+    elems.toList reverse_::: s
+  
+  def result(s: S) =
+    s.toVector.reverse
+}
+
+class VectorBuilderListList[A]
+    extends FunBuilder[A, Vector[A]] {
+  type S = List[Traversable[A]]
+
+  def init = List()
+  
+  def +=(s: S, elem: A) =
+    Traversable(elem) :: s
+    
+  def ++=(s: S, elems: TraversableOnce[A]) =
+    elems.toTraversable :: s
+    
+  def result(s: S) =
+    s.foldLeft(Vector[A]()){(z, as) => as ++: z}
+}
+
+class VectorCBF[A](bulkOptimized: Boolean)
+    extends FunCanBuildFrom[Any, A, Vector[A]] {
+  def apply() =
+    if (bulkOptimized)
+      new VectorBuilderListList
+    else
+      new VectorBuilderList
+}
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/intro-to-mtl.html b/blog/intro-to-mtl.html new file mode 100644 index 00000000..e4797756 --- /dev/null +++ b/blog/intro-to-mtl.html @@ -0,0 +1,529 @@ + + + + + + + + + + + + + + + + + + + + + + + A comprehensive introduction to Cats-mtl + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

A comprehensive introduction to Cats-mtl

+ + + technical + +
+
+
+
+

A comprehensive introduction to Cats-mtl

+

MTL is a library for composing monad transformers and making it easier to work with nested monad transformer stacks. + It originates from the land of Haskell, but has made it into Scala a long time ago. + For the longest time however, it was barely usable, because of a bunch of different Scala quirks coming together. + With all this, I feel many have the impression that mtl is something scary, abstract or too complicated. + In this blog post, I'll try my best to disprove this notion and demonstrate the simplicity and elegance of Cats-mtl. After reading this, I hope you'll agree that one should prefer mtl whenever one needs to compose more than one monad transformer nested inside of each other.

+ +

What is mtl?

+

Mtl is an acronym and stands for Monad Transformer Library. Its main purpose it make it easier to work with nested monad transformers. It achieves this by encoding the effects of most common monad transformers as type classes. + To understand what this means we'll first have to look at some of the common monad transformers.

+

I'll go over some of the lesser known transformers StateT and ReaderT next, so feel free to skip the next section if you already know about StateT and ReaderT.

+ +

ReaderT

+

ReaderT allows us to read from an environment and create other values that depend on the environment. + This can be especially useful for e.g. reading from some external configuration. + Some like to describe this as the functional programming equivalent of dependency injection.

+

As an example, let's imagine we want to make a call to a service, but to make that call we need to pass some configuration.

+

First, some imports and some declarations:

+
import cats._
+import cats.data._
+import cats.implicits._
+import cats.effect._
+
+// These are just String for simplicity
+type Config = String
+type Result = String
+

Now let's say we have these two functions for the service we want to call and the configuration we want to read from.

+

+def getConfig: IO[Config] = ???
+// getConfig: cats.effect.IO[Config]
+
+def serviceCall(c: Config): IO[Result] = ???
+// serviceCall: (c: Config)cats.effect.IO[Result]
+

The easiest thing would be to just pass down the configuration from the very top of your application. + However that can be pretty tedious, so what we do instead is use ReaderT. + ReaderT gives us the ask function, which gives us access to a read-only environment value of type E:

+
def ask[F[_]: Applicative, E]: ReaderT[F, E, E]
+

We can then use flatMap, map or for-comprehensions to actually use that value and do things with it:

+
def readerProgram: ReaderT[IO, Config, Result] = for {
+  config <- ReaderT.ask[IO, Config]
+  result <- ReaderT.liftF(serviceCall(config))
+} yield result
+// readerProgram: cats.data.ReaderT[cats.effect.IO,Config,Result]
+

Now that we have a value of ReaderT that gives us back our result, the next step is to actually "inject" the dependency. + For this purpose, ReaderT[F, E, A] gives us a run function that expects us to give it a value of E and will then return an F[A], so in our case an IO of Result:

+
def run(e: E): F[A]
+

Combined with our getConfig function we can now write the entry point to our program:

+
def main: IO[Result] = getConfig.flatMap(readerProgram.run)
+// main: cats.effect.IO[Result]
+

And that is how we can do functional dependency injection in Scala. + However, I believe this pattern isn't used very often, because it forces you to wrap all of your steps in ReaderT. + If you continue reading on, we'll go through how this problem can be mitigated using MTL.

+ +

StateT

+

Like ReaderT, StateT also allows us to read from an environment. + However, unlike ReaderT, it also allows us to write to that environment, making it capable of holding state, hence the name. + With StateT over IO, we can deliberately create programs that can access the outside world and also maintain mutable state. + This is very powerful and, when used without care, can give rise to similar problems as can be found in imperative programs that abuse global mutable state and unlimited side effects. + Use StateT with care however, and it can be a really great tool for parts of your application that require some notion of mutable state.

+

An example use case that comes up very often is the ability to send some requests to an external services and after each of those requests, use the resulting value to modify an environment with which you'll create the next request. + This environment could be used for something simple like a cache, or something more complex like dynamically changing the parameters of each request, depening on what state the environment currently holds. + Let's look at an abstract example, that showcases this ability.

+

First, we'll define a function that calls our external service which will take the environment into account.

+
// Again we use String here for simplicity, in real code this would be something else
+type Env = String
+type Request = String
+type Response = String
+
+def initialEnv: Env = ???
+
+def request(r: Request, env: Env): IO[Response] = ???
+

Next, we'll also need a function that given a response and an old environment will return a new updated environment.

+
def updateEnv(r: Response, env: Env): Env = ???
+
+// We also need some fake requests
+def req1: Request = ???
+def req2: Request = ???
+def req3: Request = ???
+def req4: Request = ???
+

Now we can get started with StateT. + To do so, we'll create a new request function that will make the request with the current environment and update it after we've received the response:

+
def requestWithState(r: Request): StateT[IO, Env, Response] = for {
+  env <- StateT.get[IO, Env]
+  resp <- StateT.liftF(request(r, env))
+  _ <- StateT.modify[IO, Env](updateEnv(resp, _))
+} yield resp
+

This demonstrates the power of StateT. + We can get the current state by using StateT.get (which returns a StateT[IO, Env, Env] similar to ReaderT.ask) and we can also modify it using StateT.modify (which takes a function Env => Env and returns a StateT[IO, Env, Unit]).

+

Now, if we wanted to make those different requests, we could just reuse that requestWithState function N number of times:

+
def stateProgram: StateT[IO, Env, Response] = for {
+  resp1 <- requestWithState(req1)
+  resp2 <- requestWithState(req2)
+  resp3 <- requestWithState(req3)
+  resp4 <- requestWithState(req4)
+} yield resp4
+

And now we have a fully fledged program exactly as we wanted. + But what can we actually do with the StateT value? + To run the full program, we need an IO. + Of course, just like ReaderT, we can turn StateT into IO by using the run method and supplying an initial value for our environment. + Let's try that out!

+
def main: IO[(Env, Response)] = stateProgram.run(initialEnv)
+// main: cats.effect.IO[(Env, Response)]
+

And that gives us a fully working stateful application. Cool. + Next, we'll look at how we can combine different transformers and what monad transformers actually represent.

+ +

Monad Transformers encode some notion of effect

+

EitherT encodes the effect of short-circuiting errors. + ReaderT encodes the effect of reading a value from the environment. + StateT encodes the effect of pure local mutable state.

+

All of these monad transformers encode their effects as data structures, but there's another way to achieve the same result: Type classes!

+

For example we've looked extensively at the ReaderT.ask function, what would it look like if we used a type class here instead? + Well, Cats-mtl has an answer and it's called ApplicativeAsk. + You can think of it as ReaderT encoded as a type class:

+
trait ApplicativeAsk[F[_], E] {
+  val applicative: Applicative[F]
+
+  def ask: F[E]
+}
+

At it's core ApplicativeAsk just encodes the fact that we can ask for a value from the environment, exactly like ReaderT does. + Exactly like ReaderT, it also includes another type parameter E, that represents that environment.

+

If you're wondering why ApplicativeAsk has an Applicative field instead of just extending from Applicative, that is to avoid implicit ambiguities that arise from having multiple subclasses of a given type (here Applicative) in scope implicitly. + So in this case we favor composition over inheritance as otherwise, we could not e.g. use Monad together with ApplicativeAsk. + You can read more about this issue in this excellent blog post by Adelbert Chang.

+ +

Effect type classes

+

ApplicativeAsk is an example for what is at the core of Cats-mtl. + Cats-mtl provides type classes for most common effects which let you choose what kind of effects you need without committing to a specific monad transformer stack.

+

Ideally, you'd write all your code using only an abstract type constructor F[_] with different type class constraints and then at the end run that code with a specific data type that is able to fulfill those constraints.

+

So without further ado, let's try to convert our Reader program from earlier into mtl-style. + First, I'll include the original program again:

+
def getConfig: IO[Config] = ???
+// getConfig: cats.effect.IO[Config]
+
+def serviceCall(c: Config): IO[Result] = ???
+// serviceCall: (c: Config)cats.effect.IO[Result]
+
+def readerProgram: ReaderT[IO, Config, Result] = for {
+  config <- ReaderT.ask[IO, Config]
+  result <- ReaderT.liftF(serviceCall(config))
+} yield result
+// readerProgram: cats.data.ReaderT[cats.effect.IO,Config,Result]
+
+def main: IO[Result] = getConfig.flatMap(readerProgram.run)
+// main: cats.effect.IO[Result]
+

Now we should just replace that ReaderT with an F and add an ApplicativeAsk[F, Config] constraint, right? + We have one small problem though, how can we lift our serviceCall which is an IO value, into our abstract F context? + Fortunately cats-effect already defines a typeclass designed to help us out here called LiftIO. + It defines a single function liftIO that does exactly what you'd expect:

+
@typeclass trait LiftIO[F[_]] {
+  def liftIO[A](io: IO[A]): F[A]
+}
+

If there's an instance for LiftIO[F] we can lift any IO[A] into an F[A]. + Furthermore IO defines a method to which makes use of this type class to provide some nicer looking syntax.

+

With this in mind, we can now define our readerProgram fully using MTL:

+
import cats.mtl._
+import cats.mtl.instances.all._
+
+def readerProgram[F[_]: Monad: LiftIO](implicit A: ApplicativeAsk[F, Config]): F[Result] = for {
+  config <- A.ask
+  result <- serviceCall(config).to[F]
+} yield result
+

We replaced our call to ReaderT.ask with a call to ask provided by ApplicativeAsk and instead of using ReaderT.liftF to lift an IO into ReaderT, we can simply use the to function on IO, pretty neat if you ask me.

+

Now to run it, all we need to do is specify the target F to run in, in our case ReaderT[IO, Config, Result] fits perfectly:

+
val materializedProgram = readerProgram[ReaderT[IO, Config, ?]]
+
+def main: IO[Result] = getConfig.flatMap(materializedProgram.run)
+

This process of turning a program defined by an abstract type constructor with additional type class constraints into an actual concrete data type is sometimes called interpreting or materializing a program.

+

Another thing we can do is define a type alias for ApplicativeAsk[F, Config] so that we can more easily use it with the context bound syntax:

+
type ApplicativeConfig[F[_]] = ApplicativeAsk[F, Config]
+
+def readerProgram[F[_]: Monad: LiftIO: ApplicativeConfig]: F[Result] = ???
+

So far so good, but this doesn't seem to be any better than what we had before. + I've teased at the beginning that MTL really shines once you use more than one monad transformer. + So let's say our program now also needs to be able to handle errors (which I think is a very reasonable requirement).

+

To do so, we'll use MonadError, which can be found in cats-core instead of mtl, but in its essence, it encodes the short circuting effect that's shared with EitherT.

+

To keep things simple for now, we want to raise an error if the configuration we got was invalid somehow. + For this purpose we'll have this simple function that will simply return if a Config is valid or not:

+
def validConfig(c: Config): Boolean = ???
+

Then we'll also want to define an error ADT for our app:

+
sealed trait AppError
+case object InvalidConfig extends AppError
+

Now we can go and extend our program from earlier. + We'll add a MonadError[F, AppError] type alias, MonadAppError and then add a constraint for it in our program.

+
type MonadAppError[F[_]] = MonadError[F, AppError]
+
+def program[F[_]: MonadAppError: ApplicativeConfig: LiftIO]: F[Result] = ???
+

Now we want so ensure somehow that our config is valid and raise an InvalidConfig error if it's not. + To do so, we'll simply use the ensure function provided by MonadError. + It looks like this:

+
def ensure(error: => E)(predicate: A => Boolean): F[A]
+

And it fills our need exactly. It will raise the passed error, if the predicate function returns false. + Let's go and try it out:

+
def program[F[_]: MonadAppError: ApplicativeConfig: LiftIO]: F[Result] = for {
+  config <- ApplicativeAsk[F, Config].ask
+              .ensure(InvalidConfig)(validConfig)
+  result <- serviceCall(config).to[F]
+} yield result
+// program: [F[_]](implicit evidence$1: MonadAppError[F], implicit evidence$2: ApplicativeConfig[F], implicit evidence$3: cats.effect.LiftIO[F])F[Result]
+

Pretty simple, now let's materialize it! + To do so, we'll use a monad stack of ReaderT, EitherT and IO. + Unwrapped it should look like this IO[Either[AppError, Reader[Config, A]]].

+

We'll create some type aliases to get a better overview:

+
type EitherApp[A] = EitherT[IO, AppError, A]
+type Stack[A] = ReaderT[EitherApp, Config, A]
+
+val materializedProgram: Stack[Result] = program[Stack]
+
+def main: IO[Either[AppError, Result]] =
+  EitherT.liftF(getConfig).flatMap(materializedProgram.run).value
+

This is the magic of mtl, it is able to give you type class instances for every single monad transformer in the stack. + This means that when you stack EitherT, ReaderT and StateT, you'll be able to get instances for MonadError, ApplicativeAsk and MonadState, which is really useful!

+

If you're wondering how this works, well let's just have a quick look at how the MonadError instance for ReaderT

+
def monadErrorForReaderT[F[_], E, R](implicit F: MonadError[F, E]): MonadError[ReaderT[F, R, ?], E] =
+  new MonadError[ReaderT[F, R, ?], E] {
+    def raiseError[A](e: E): ReaderT[F, R, A] =
+      ReaderT.liftF(F.raiseError(e))
+
+    def handleErrorWith[A](fa: ReaderT[F, R, A])(f: E => ReaderT[F, R, A]): ReaderT[F, R, A] =
+      ReaderT.ask[F, R].flatMap { r => 
+        ReaderT.liftF(fa.run(r).handleErrorWith(e => f(e).run(r)))
+      }
+  }
+

To get an instance of MonadError for ReaderT[F, R, ?], we need to have a MonadError for F. + Then we can easily use that underlying instance to handle and raise the errors instead. + Again, this means that if some part of transformer stack is capable of raising and handling errors, now your whole stack is. + So if it includes EitherT somewhere, you can "lift" that capability.

+

There are different strategies for lifting these capabilities throughout your monad stack, but they'd be out of scope for this article.

+

What this means for us, is that we never have to think about lifting individual monads through transformer stacks. + The implicit search used by the type class mechanic takes care of it. + Pretty neat, I think. + Now contrast this lack of lifting, with the same program written without mtl:

+
type EitherApp[A] = EitherT[IO, AppError, A]
+// defined type alias EitherApp
+
+type Stack[A] = ReaderT[EitherApp, Config, A]
+// defined type alias Stack
+
+def program: Stack[Result] = for {
+  config <- ReaderT.ask[EitherApp, Config]
+  _ <- if (validConfig(config)) ().pure[Stack]
+       else ReaderT.liftF[EitherApp, Config, Unit](EitherT.leftT(InvalidConfig))
+  result <- ReaderT.liftF(EitherT.liftF[IO, AppError, Result](serviceCall(config)))
+} yield result
+// program: Stack[Result]
+

It's the same program, but now we have to add type annotations and liftFs everywhere. + If you try to take away one of those type annotations the program will fail to compile, so this is the minimum amount of boilerplate you need.

+ +

Adding State

+

For the next step, let's imagine we want to send multiple requests and after each, use information we retrieved from the response for the next request, similar to how we did earlier in the StateT example.

+

Instead of using StateT, we'll use the MonadState type class:

+
trait MonadState[F[_], S] {
+  val monad: Monad[F]
+
+  def get: F[S]
+
+  def set(s: S): F[Unit]
+
+  def modify(f: S => S): F[Unit] = get.flatMap(s => set(f(s)))
+}
+

Let's imagine we have a list of requests, where we want to update the environment after each request, and we also want to use the environment to create the next request. + At the very end we want to return the list of all the responses we got:

+
type Result = List[Response]
+
+def updateEnv(r: Response, env: Env): Env = ???
+
+def requests: List[Request] = ???
+
+def newServiceCall(c: Config, req: Request, e: Env): IO[Response] = ???
+

So far, so good, next we'll use MonadState to create a new function that will wrap newServiceCall with the addition of modifying the environment using updateEnv. + To do so, we'll create a new type alias for MonadState[F, Env]:

+
type MonadStateEnv[F[_]] = MonadState[F, Env]
+// defined type alias MonadStateEnv
+
+def requestWithState[F[_]: Monad: MonadStateEnv: LiftIO](c: Config, req: Request): F[Response] = for {
+  env <- MonadState[F, Env].get
+  response <- newServiceCall(c, req, env).to[F]
+  _ <- MonadState[F, Env].modify(updateEnv(response, _))
+} yield response
+// requestWithState: [F[_]](c: Config, req: Request)(implicit evidence$1: cats.Monad[F], implicit evidence$2: MonadStateEnv[F], implicit evidence$3: cats.effect.LiftIO[F])F[Response]
+

Here, we use get to retrieve the current state of the environment, then we use newServiceCall and lift it into F and use the response to modify the environment with updateEnv.

+

Now, we can use requestWithState on our list of requests and embed this new part into our program. + The best way to do that, is of course traverse, as we want to go from a List[Request] and a function Request => F[Response] to an F[List[Response]]. + So without further ado, this is our final program, using all three different mtl type classes we learned about in this article:

+
def program[F[_]: MonadAppError: MonadStateEnv: ApplicativeConfig: LiftIO]: F[Result] = for {
+  config <- ApplicativeAsk[F, Config].ask
+    .ensure(InvalidConfig)(validConfig)
+  responses <- requests.traverse(req => requestWithState[F](config, req))
+} yield responses
+// program: [F[_]](implicit evidence$1: MonadAppError[F], implicit evidence$2: MonadStateEnv[F], implicit evidence$3: ApplicativeConfig[F], implicit evidence$4: cats.effect.LiftIO[F])F[Result]
+

And that is it! + Of course, we still have to run it, so let's materialize our F into an appropriate data type. + We'll be using a stack of EitherT, StateT and ReaderT, with IO as our base to satisfy LiftIO:

+
def materializedProgram = program[StateT[EitherT[ReaderT[IO, Config, ?], AppError, ?], Env, ?]]
+

And now we have a fully applied transformer stack.

+

The only thing left is to turn that stack back into an IO by running the individual layers.

+
def main: IO[Either[AppError, (Env, Result)]] = 
+  getConfig.flatMap(conf => 
+    materializedProgram.run(initialEnv) //Run the StateT layer
+      .value //Run the EitherT layer
+      .run(conf) //Run the ReaderT layer
+  ) 
+

If we were to get that same value using just transformers and no mtl, the amount of boilerplate would be excruciating. We would need multiple liftFs for every monad transformer and dozens of type annotations, leaving the actual code hidden under layers and layers of boilerplate.

+

With Cats-mtl, dealing with different effects is simple and free of boilerplate. + We can describe our application as functions dealing with an abstract context F[_] that must be able to provide certain effect constraints. + These constraints are provided by the different MTL type classes in Cats-mtl and their instances can be lifted up to the highest layer with Cats-mtl's underlying machinery.

+

In summary Cats-mtl provides two things: + MTL type classes representing effects and a way to lift instances of these classes through transformer stacks. + If you'd like to learn more about Cats-mtl, check out its new website!

+ +

Other mtl class instances

+

Now I said that ApplicativeAsk is the type class encoding of ReaderT, but it's by no means the only one that can form an ApplicativeAsk instance. + Monad transformer stacks are known to be quite unperformant, especially so on the JVM, so there are some alternate solutions. For example, one could use the Arrows library, which provides effect types with an input type in addition to its output type Arrow[A, B]. If you squint a bit, it's practically equivalent to a function A => IO[B] or ReaderT[IO, A, B]. At the same time, however, it can be substantially more performant.

+

Other examples include using something like cats-effect' Ref for MonadState (a working instance can be found here), or using a bifunctor IO that includes an extra type parameter for the error type, i.e. BIO[E, A] instead of using EitherT[IO, E, A] (a WIP for cats-effect can be found here).

+

In general, we can think up more performant solutions to our effect type class instances by using more specialized data structures. Monad Transformers are extremely general, which makes them very flexible, but that flexibility may come at a price. One of the great things about mtl is that we don't have to choose up front, but only at the very end when our program is run. + For example, we might choose to use only monad transformers at the begining when developing our application. Then, when we want to scale up, we can move to more performant instances simply by changing a few lines when materializing our programs.

+

In the long term, I'd like to provide a submodule of cats-mtl that has very specialized and performant data types for every combination of effect type classes. + For this purpose, I've created the cats-mtl-special library some time ago, but it still remains very much a work in progress. + Shoutout also to Jamie Pullar who has been using cats-mtl extensively in production and has also built some more performant instances along with some benchmarks which you can find as part of his talk here.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/io-monad-for-cats.html b/blog/io-monad-for-cats.html new file mode 100644 index 00000000..f051b8fd --- /dev/null +++ b/blog/io-monad-for-cats.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + An IO monad for cats + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

An IO monad for cats

+ + + technical + +
+
+
+
+

An IO monad for cats

+

Haskell is a pure language. Every Haskell expression is referentially transparent, meaning that you can substitute that expression with its evaluated result without changing the program. Or, put into code:

+
-- this program
+f expr expr -- apply function f to arguments expr, expr
+
+-- is equivalent to this one, which factors out `expr`
+let
+  x = expr -- introduce a new variable `x` with the value of `expr`
+in
+  f x x
+

And this is true for all expressions e, and all functions f. These could be complex expressions which describe ways of manipulating network channels or window buffers, or something trivial like a numeric literal. You can always substitute the expression with its value.

+

This is not true in Scala, simply because Scala allows unrestricted side-effects. Unlike Haskell, Scala puts no limitations on where and when we can use things like mutable state (vars) or evaluated external effects like println or launchTheMissiles. Since there are no restrictions on where and when we can do evil, the Scala equivalent to the above just doesn't work:

+
f(e, e)
+// isn't really equivalent to!
+val x = e
+f(x, x)
+

The reason it isn't equivalent comes from the different sorts of expressions that we could find in e. For example, what if e is println("hi!"). If we make that substitution, our snippet looks like the following:

+
f(println("hi"), println("hi"))
+// isn't really equivalent to!
+val x = println("hi")
+f(x, x)
+

Clearly these are not the same two programs. The first prints "hi" twice, while the second only prints it once. This is a violation of referential transparency, and it's why we sometimes say that Scala is an impure language. Any expression which is not referentially transparent must contain side-effects, by definition.

+

Now of course, we found this problem by using a side-effecting function: namely, println. Haskell clearly has the ability to print to standard output, so how does it avoid this issue? If we build the same program in Haskell, can we violate referential transparency?

+
f (putStrLn "hi") (putStrLn "hi")
+-- is equivalent to
+let x = putStrLn "hi" in f x x
+

As it turns out, this is still referentially transparent! These two programs still have the same meaning. This is possible only because neither program actually prints anything!

+

In Haskell, effects are treated as first-class values. The putStrLn function doesn't print to standard out, it returns a value (of type IO ()) which describes how to print to standard out, but stops short of actually doing it. These sorts of values can be composed using the monadic operators (in Scala, flatMap and pure), allowing Haskell programmers to build up expressions composed of sequences of dependent effects, all of which are merely descriptions of the side-effects which will eventually be performed by the runtime. Ultimately, the description which comprises your whole program is the return result from the main function. The Haskell runtime runs the main function to get this description of all your effects, and then runs the effects per your instructions.

+

This is kind of a clever trick. It allows Haskell to simultaneously be pure and still have excellent support for manipulating effects and interacting with the "real world". But why is it relevant to Scala? After all, Scala is an impure language. We don't need to go through this complex rigmarole of describing our effects and composing those descriptions; the language lets us just do it! So why wouldn't we just, you know, evaluate the effects that we need evaluated?

+

The answer is that we want to reason about where and when our effects are evaluated. And of course, we want to be able to leverage laws and abstractions which assume equational semantics for expressions (i.e. referential transparency). Cats is full of these sorts of abstractions, and cats-laws provides a vast set of laws which describe them. But all of these abstractions and all of these laws break down the moment you introduce some sort of side-effecting expression. Because, much like our referential transparency example from earlier, these abstractions assume that you can substitute expressions with their evaluated results, and that's just not true in the presence of side-effects.

+

What we need is a data type which allows us to encapsulate Scala-style side-effects in the form of a pure value, on which referential transparency holds and which we can compose using other well-defined abstractions, such as Monad. Scalaz defines two such data types which meet these criteria: scalaz.effect.IO and scalaz.concurrent.Task. But in practice, nearly everyone uses Task instead of IO because of its support for asynchronous effects.

+

Cats does not define any such abstraction, and what's worse is the cats ecosystem also doesn't really provide any such abstraction. There are two Task implementations that are relatively commonly used with cats – namely, monix.eval.Task and fs2.Task – but these are not part of cats per se, nor are they deeply integrated into its abstraction hierarchy. Additionally, the proliferation of broadly equivalent options has led to confusion in the ecosystem, with middleware authors often forced to choose a solution for their end-users, and end-users uncertain as to which choice is "right".

+ +

Introducing cats-effect

+

The cats-effect project aims to change all of that. The goal of cats-effect is to provide an "easy default" IO type for the cats ecosystem, deeply integrated with cats-core, with all of the features and performance that are required for real world production use. Additionally, cats-effect defines a set of abstractions in the form of several typeclasses which describe what it means to be a pure effect type. These abstractions are extremely useful both in enabling MTL-style program composition and to ensure that other pre-existing Task implementations remain first-class citizens of the ecosystem. IO does not overshadow monix.eval.Task or fs2.Task; it complements them by providing a set of abstractions and laws which allow users to write safe, parametric code which supports each of them equally.

+

One important sidebar here: cats-effect does not provide any concurrency primitives. scalaz.concurrent.Task and monix.eval.Task are both notable for providing functions such as both, which takes two Tasks and runs them in parallel, returning a Task of a tuple of the results. The cats.effect.IO type does not provide any such function, and while it would be possible to define such a function (and others like it!), we strongly encourage users to instead consider full-on streaming frameworks such as fs2 or Monix for their concurrency needs, as these frameworks are able to provide a much sounder foundation for such functions. See here for a rough outline of why this is. Also note that some Task implementations, such as Monix's, can and do provide parallelism on a sound foundation by enriching their internal algebraic structures. Thus, monix.eval.Task is actually quite different from cats.effect.IO, despite having a similar core set of operations.

+ +

Enough Talk…

+

What does this look like in practice? Well, ideally, as convenient as possible! Let's look at our println example:

+
def putStrLn(line: String): IO[Unit] =
+  IO { println(line) }
+
+f(putStrLn("hi!"), putStrLn("hi!"))
+
+// is equivalent to
+
+val x = putStrLn("hi!")
+f(x, x)
+

Great! We can write Haskell fanfic in Scala. 😛

+

The notable element here is the use of the IO.apply constructor to wrap the println effect in a pure IO value. This pattern can be applied to any side-effect. You can think of this sort of like an FFI that converts impure code (like println) into pure code (like putStrLn). The goal of this API was to be as simple and straightforward as possible. If you have a curly brace block of impure side-effecting code, you can wrap it in a composable and pure abstraction by just adding two characters: IO. You can wrap arbitrarily large or small blocks of code, potentially involving complex allocations, JNI calls, resource semantics, etc; but it is generally considered a best practice to wrap side-effects into the smallest composable units that make sense and do all of your sequentialization using flatMap and for-comprehensions.

+

For example, here's a program that performs some simple user interaction in the shell:

+
import cats.effect.IO
+
+val program = for {
+  _ <- IO { println("Welcome to Scala!  What's your name?") }
+  name <- IO { Console.readLine }
+  _ <- IO { println(s"Well hello, $name!") }
+} yield ()
+

We could have just as easily written this program in the following way:

+
val program = IO {
+  println("Welcome to Scala!  What's your name?")
+  val name = Console.readLine
+  println(s"Well hello, $name!")
+}
+

But this gives us less flexibility for composition. Remember that even though program is a pure and referentially transparent value, its definition is not, which is to say that IO { expr } is not the same as val x = expr; IO { x }. Anything inside the IO {} block is not referentially transparent, and so should be treated with extreme care and suspicion. The less of our program we have inside these blocks, the better!

+

As a sidebar that is actually kinda cool, we can implement a readString IO action that wraps Console.readLine as a val!

+
val readString = IO { Console.readLine }
+

This is totally valid! We don't need to worry about the difference between def and val anymore, because IO is referentially transparent. So you use def when you need parameters, and you use val when you don't, and you don't have to think about evaluation semantics. No more subtle bugs caused by accidentally memoizing your effects!

+

Of course, if program is referentially transparent, then clearly repeated values of program cannot possibly run the effects it represents multiple times. For example:

+
program
+program
+program
+
+// must be the same as!
+
+program
+

If this weren't the case, then we would be in trouble when trying to construct examples like the Haskell one from earlier. But there is an implication here that is quite profound: IO cannot eagerly evaluate its effects, and similarly cannot memoize its results! If IO were to eagerly evaluate or to memoize, then we could no longer replace references to the expression with the expression itself, since that would result in a different IO instance to be evaluated separately.

+

This is precisely why scala.concurrent.Future is not a suitable type for encapsulating effects in this way: constructing a Future that will eventually side-effect is itself a side-effect! Future evaluates eagerly (sort of, see below) and memoizes its results, meaning that a println inside of a given Future will only evaluate once, even if the Future is sequenced multiple times. This in turn means that val x = Future(...); f(x, x) is not the same program as f(Future(...), Future(...)), which is the very definition of a violation of referential transparency.

+

Coming back to IO… If program does not evaluate eagerly, then clearly there must be some mechanism for asking it to evaluate. After all, Scala is not like Haskell: we don't return a value of type IO[Unit] from our main function. IO provides an FFI of sorts for wrapping side-effecting code into pure IO values, so it must also provide an FFI for going in the opposite direction: taking a pure IO value and evaluating its constituent actions as side-effects.

+
program.unsafeRunSync()    // uh oh!
+

This function is called unsafeRunSync(). Given an IO[A], the unsafeRunSync() function will give you a value of type A. You should only call this function once, ideally at the very end of your program! (i.e. in your main function) Just as with IO.apply, any expression involving unsafeRunSync() is not referentially transparent. For example:

+
program.unsafeRunSync()
+program.unsafeRunSync()
+

The above will run program twice. So clearly, referential transparency is out the window whenever we do this, and we cannot expect the normal laws and abstractions to remain sound in the presence of this function.

+ +

A sidebar on Future's eager evaluation

+

As Viktor Klang is fond of pointing out, Future doesn't need to evaluate eagerly. It is possible to define an ExecutionContext in which Future defers its evaluation until some indefinitely later point. However, this is not the default mode of operation for 99% of all Futures ever constructed; most people just use ExecutionContext.global and leave it at that. Additionally, if someone hands me an arbitrary Future, perhaps as a return value from a function, I really have no idea whether or not that Future is secretly running without my consent. In other words, the referential transparency (or lack thereof) of functions that I write using Future is dependent on the runtime configuration of some other function which is hidden from me. That's not referential transparency anymore. Because we cannot be certain that Future is deferring its evaluation, we must defensively assume that it is not.

+

This, in a nutshell, is precisely why Future is not appropriate for functional programming. IO provides a pair of functions (fromFuture and unsafeToFuture) for interacting with Future-using APIs, but in general, you should try to stick with IO as much as possible when manipulating effects.

+ +

Asynchrony and the JVM

+

Scala runs on three platforms: the JVM, JavaScript and LLVM. For the moment, we'll just focus on the first two. The JVM has support for multiple threads, but those threads are native (i.e. kernel) threads, meaning that they are relatively expensive to create and maintain in the runtime. They are a very limited resource, sort of like file handles or heap space, and you can't just write programs which require an unbounded number of them. The exact upper bound on the JVM varies from platform to platform, and varies considerably depending on your GC configuration, but a general rule of thumb is "a few thousand", where "few" is a small number. In practice, you're going to want far less threads than that if you want to avoid thrashing your GC, and most applications will divide themselves into a bounded "main" thread pool (usually bounded to exactly the number of CPUs) on which all CPU-bound tasks are performed and most of the program runs, as well as a set of unbounded "blocking" thread pools on which blocking IO actions (such as anything in java.io) are run. When you add NIO worker pools into the mix, the final number of threads in a practical production service is usually around 30-40 on an 8 CPU machine, growing roughly linearly as you add CPUs. Clearly, this is not a very large number.

+

On JavaScript runtimes (such as node or in the browser), the situation is even worse: you have exactly one thread! JavaScript simply doesn't have multi-threading in any (real) form, and so it's like the JVM situation, but 30-40x more constraining.

+

For this reason, we need to be very careful when writing Scala to treat threads as an extremely scarce resource. Blocking threads (using mechanisms such as wait, join or CountDownLatch) should be considered absolutely anathema, since it selfishly wastes a very finite and very critical resource, leading to thread starvation and deadlocks.

+

This is very different from how things are in Haskell though! The Haskell runtime is implemented around the concept of green threads, which is to say, emulated concurrency by means of a runtime dispatch lock. Haskell basically creates a global bounded thread pool in the runtime with the same number of threads as your machine has CPUs. On top of that pool, it runs dispatch trampolines that schedule and evict expression evaluation, effectively emulating an arbitrarily large number of "fake" threads atop a small fixed set of "real" threads. So when you write code in Haskell, you generally just assume that threads are extremely cheap and you can have as many of them as you want. Under these circumstances, blocking a thread is not really a big deal (as long as you don't do it in FFI native code), so there's no reason to go out of your way to avoid it in abstractions like IO.

+

This presents a bit of a dilemma for cats-effect: we want to provide a practical pure abstraction for encapsulating effects, but we need to run on the JVM and on JavaScript which means we need to provide a way to avoid thread blocking. So, the IO implementation in cats-effect is going to necessarily end up looking very, very different from the one in Haskell, providing a very different set of operations.

+

Specifically, cats.effect.IO provides an additional constructor, async, which allows the construction of IO instances from callback-driven APIs. This is generally referred to as "asynchronous" control flow, as opposed to "synchronous" control flow (represented by the apply constructor). To see how this works, we're going to need a bit of setup.

+

Consider the following somewhat-realistic NIO API (translated to Scala):

+
trait Response[T] {
+  def onError(t: Throwable): Unit
+  def onSuccess(t: T): Unit
+}
+// defined trait Response
+
+trait Channel {
+  def sendBytes(chunk: Array[Byte], handler: Response[Unit]): Unit
+  def receiveBytes(handler: Response[Array[Byte]]): Unit
+}
+// defined trait Channel
+

This is an asynchronous API. Neither of the functions sendBytes or receiveBytes attempt to block on completion. Instead, they schedule their operations via some underlying mechanism. This interface could be implemented on top of java.io (which is a synchronous API) through the use of an internal thread pool, but most NIO implementations are actually going to delegate their scheduling all the way down to the kernel layer, avoiding the consumption of a precious thread while waiting for the underlying IO – which, in the case of network sockets, may be a very long wait indeed!

+

Wrapping this sort of API in a referentially transparent and uniform fashion is a very important feature of IO, precisely because of Scala's underlying platform constraints. Clearly, sendBytes and receiveBytes both represent side-effects, but they're different than println and readLine in that they don't produce their results in a sequentially returned value. Instead, they take a callback, Response, which will eventually be notified (likely on some other thread!) when the result is available. The IO.async constructor is designed for precisely these situations:

+
def send(c: Channel, chunk: Array[Byte]): IO[Unit] = {
+  IO async { cb =>
+    c.sendBytes(chunk, new Response[Unit] {
+      def onError(t: Throwable) = cb(Left(t))
+      def onSuccess(v: Unit) = cb(Right(()))
+    })
+  }
+}
+// send: (c: Channel, chunk: Array[Byte])cats.effect.IO[Unit]
+
+def receive(c: Channel): IO[Array[Byte]] = {
+  IO async { cb =>
+    c.receiveBytes(new Response[Array[Byte]] {
+      def onError(t: Throwable) = cb(Left(t))
+      def onSuccess(chunk: Array[Byte]) = cb(Right(chunk))
+    })
+  }
+}
+// receive: (c: Channel)cats.effect.IO[Array[Byte]]
+

Obviously, this is a little more daunting than the println examples from earlier, but that's mostly the fault of the anonymous inner class syntactic ceremony. The IO interaction is actually quite simple!

+

The async constructor takes a function which is handed a callback (represented above by cb in both cases). This callback is itself a function of type Either[Throwable, A] => Unit, where A is the type produced by the IO. So when our Response comes back as onSuccess in the send example, we invoke the callback with a Right(()) since we're trying to produce an IO[Unit]. When the Response comes back as onSuccess in the receive example, we invoke the callback with Right(chunk), since the IO produces an Array[Byte].

+

Now remember, IO is still a monad, and IO values constructed with async are perfectly capable of all of the things that "normal", synchronous IO values are, which means that you can use these values inside for-comprehensions and other conventional composition! This is incredibly, unbelievably nice in practice, because it takes your complex, nested, callback-driven code and flattens it into simple, easy-to-read sequential composition. For example:

+
val c: Channel = null // pretend this is an actual channel
+
+for {
+  _ <- send(c, "SYN".getBytes)
+  response <- receive(c)
+
+  _ <- if (response == "ACK".getBytes)   // pretend == works on Array[Byte]
+    IO { println("found the guy!") }
+  else
+    IO { println("no idea what happened, but it wasn't good") }
+} yield ()
+

This is kind of amazing. There's no thread blocking at all in the above (other than the println blocking on standard output). The receive could take quite a long time to come back to us, and our thread is free to do other things in the interim. Everything is driven by callbacks under the surface, and asynchronous actions can be manipulated just as easily as synchronous ones.

+

Of course, this is an even bigger win on JavaScript, where nearly everything is callback-based, and gigantic, deeply nested chunks of code are not unusual. IO allows you to flatten those deeply nested chunks of code into a nice, clean, linear and sequential formulation.

+ +

Thread Shifting

+

Now there is a caveat here. When our Response handler is invoked by Channel, it is very likely that the callback will be run on a thread which is part of a different thread pool than our main program. Remember from earlier where I described how most well-designed Java services are organized:

+
    +
  • A bounded thread pool set to num CPUs in size for any non-IO actions
  • +
  • A set of unbounded thread pools for blocking IO
  • +
  • Some bounded internal thread worker pools for NIO polling
  • +
+

We definitely want to run nearly everything on that first pool (which is probably ExecutionContext.global), but we're probably going to receive the Response callback on one of the third pools. So how can we force the rest of our program (including those printlns) back onto the main pool?

+

The answer is the shift function.

+
import scala.concurrent._
+implicit val ec = ExecutionContext.global
+
+for {
+  _ <- send(c, "SYN".getBytes)
+  response <- receive(c).shift    // there's no place like home!
+
+  _ <- if (response == "ACK".getBytes)   // pretend == works on Array[Byte]
+    IO { println("found the guy!") }
+  else
+    IO { println("no idea what happened, but it wasn't good") }
+} yield ()
+

shift's functionality is a little complicated, but generally speaking, you should think of it as a "force this IO onto this other thread pool" function. Of course, when receive executes, most of its work isn't done on any thread at all (since it is simply registering a hook with the kernel), and so that work isn't thread shifted to any pool, main or otherwise. But when receive gets back to us with the network response, the callback will be handled and then immediately thread-shifted back onto the main pool, which is passed implicitly as a parameter to shift (you can also pass this explicitly if you like). This thread-shifting means that all of the subsequent actions within the for-comprehension – which is to say, the continuation of receive(c) – will be run on the ec thread pool, rather than whatever worker pool is used internally by Channel. This is an extremely common use-case in practice, and IO attempts to make it as straightforward as possible.

+

Another possible application of thread shifting is ensuring that a blocking IO action is relocated from the main, CPU-bound thread pool onto one of the pools designated for blocking IO. An example of this would be any interaction with java.io:

+
import java.io.{BufferedReader, FileReader}
+// import java.io.{BufferedReader, FileReader}
+
+def readLines(name: String): IO[Vector[String]] = IO {
+  val reader = new BufferedReader(new FileReader(name))
+  var back: Vector[String] = Vector.empty
+
+  try {
+    var line: String = null
+    do {
+      line = reader.readLine()
+      back :+ line
+    } while (line != null)
+  } finally {
+    reader.close()
+  }
+
+  back
+}
+// readLines: (name: String)cats.effect.IO[Vector[String]]
+
for {
+  _ <- IO { println("Name, pls.") }
+  name <- IO { Console.readLine }
+  lines <- readLines("names.txt")
+
+  _ <- if (lines.contains(name))
+    IO { println("You're on the list, boss.") }
+  else
+    IO { println("Get outa here!") }
+} yield ()
+

Clearly, readLines is blocking the underlying thread while it waits for the disk to return the file contents to us, and for a large file, we might be blocking the thread for quite a long time! Now if we're treating our thread pools with respect (as described above), then we probably have a pair of ExecutionContext(s) sitting around in our code somewhere:

+
import java.util.concurrent.Executors
+
+implicit val Main = ExecutionContext.global
+val BlockingFileIO = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())
+

We want to ensure that readLines runs on the BlockingFileIO pool, while everything else in the for-comprehension runs on Main. How can we achieve this?

+

With shift!

+
for {
+  _ <- IO { println("Name, pls.") }
+  name <- IO { Console.readLine }
+  lines <- readLines("names.txt").shift(BlockingFileIO).shift(Main)
+
+  _ <- if (lines.contains(name))
+    IO { println("You're on the list, boss.") }
+  else
+    IO { println("Get outa here!") }
+} yield ()
+

Now we're definitely in bizarro land. Two calls to shift, one after the other? Let's break this apart:

+
readLines("names.txt").shift(BlockingFileIO)
+

One of the functions of shift is to take the IO action it is given and relocate that action onto the given thread pool. In the case of receive, this component of shift was meaningless since receive didn't use a thread under the surface (it was asynchronous!). However, readLines does use a thread under the surface (hint: it was constructed with IO.apply rather than IO.async), and so that work will be relocated onto the BlockingFileIO pool by the above expression.

+

Additionally, the continuation of this work will also be relocated onto the BlockingFileIO pool, and that's definitely not what we want. The evaluation of the contains function is definitely CPU-bound, and should be run on the Main pool. So we need to shift a second time, but only the continuation of the readLines action, not readLines itself. As it turns out, we can achieve this just by adding the second shift call:

+
readLines("names.txt").shift(BlockingFileIO).shift(Main)
+

Now, readLines will be run on the BlockingFileIO pool, but the continuation of readLines (namely, everything that follows it in the for-comprehension) will be run on Main. This works because shift creates an asynchronous IO that schedules the target action on the given thread pool and invokes its continuation from a callback. The ExecutionContext#execute function should give you an idea of how this works. This means that the result of the first shift is an IO constructed with async, and cannot itself be thread-shifted (unlike an IO constructed with apply), but its continuation can be thread-shifted, which is exactly what happens.

+

This sort of double-shift idiom is very common in production service code that makes use of legacy blocking IO libraries such as java.io.

+ +

Synchronous vs Asynchronous Execution

+

Speaking of asynchrony, readers who have been looking ahead in the class syllabus probably realized that the type signature of unsafeRunSync() is more than a little suspicious. Specifically, it promises to give us an A immediately given an IO[A]; but if that IO[A] is an asynchronous action invoked with a callback, how can it achieve this promise?

+

The answer is that it blocks a thread. (gasp!!!) Under the surface, a CountDownLatch is used to block the calling thread whenever an IO is encountered that was constructed with IO.async. Functionally, this is very similar to the Await.result function in scala.concurrent, and it is just as dangerous. Additionally, it clearly cannot possibly work on JavaScript, since you only have one thread to block! If you try to call unsafeRunSync() on JavaScript with an underlying IO.async, it will just throw an exception rather than deadlock your application.

+

This is not such a great state of affairs. I mean, it works if unsafeRunSync() is being run in test code, or as the last line of your main function, but sometimes we need to interact with legacy code or with Java APIs that weren't designed for purity. Sometimes, we just have to evaluate our IO actions before "the end of the world", and when we do that, we don't want to block any of our precious threads.

+

So IO provides an additional function: unsafeRunAsync. This function takes a callback (of type Either[Throwable, A] => Unit) which it will run when (and if) the IO[A] completes its execution. As the name implies, this function is also not referentially transparent, but unlike unsafeRunSync(), it will not block a thread.

+

As a sidebar that will be important in a few paragraphs, IO also defines a safe function called runAsync which has a very similar signature to unsafeRunAsync, except it returns an IO[Unit]. The IO[Unit] which is returned from this function will not block if you call unsafeRunAsync(). In other words, it is always safe to call unsafeRunSync() on the results of runAsync, even on JavaScript.

+

Another way to look at this is in terms of unsafeRunAsync. You can define unsafeRunAsync in terms of runAsync and unsafeRunSync():

+
def unsafeRunAsync[A](ioa: IO[A])(cb: Either[Throwable, A] => Unit): Unit =
+  ioa.runAsync(e => IO { cb(e) }).unsafeRunSync()
+// unsafeRunAsync: [A](ioa: cats.effect.IO[A])(cb: Either[Throwable,A] => Unit)Unit
+

This isn't the actual definition, but it would be a valid one, and it would run correctly on every platform.

+ +

Abstraction and Lawfulness

+

As mentioned earlier (about 10000 words ago…), the cats-effect project not only provides a concrete IO type with a lot of nice features, it also provides a set of abstractions characterized by typeclasses and associated laws. These abstractions collectively define what it means to be a type which encapsulates side-effects in a pure fashion, and they are implemented by IO as well as several other types (including fs2.Task and monix.eval.Task). The hierarchy looks like this:

+

cats-effect typeclasses

+

Monad and MonadError are of course a part of cats-core, while everything else is in cats-effect. MonadError is functionally equivalent to the familiar scalaz.Catchable typeclass, which was commonly used in conjunction with scalaz.concurrent.Task. It literally means "a monad with error-handling capabilities". IO certainly fits that description, as any exceptions thrown within its apply method (or within async) will be caught and may be handled in pure code by means of the attempt function. Sync, Async, LiftIO and Effect are the new typeclasses.

+

Sync simply describes the IO.apply function (in the typeclasses, this function is called delay). Which is to say, any type constructor F[_] which has a Sync[F] has the capability to suspend synchronous side-effecting code. Async is very similar to this in that it describes the async function. So any type constructor F[_] which has an Async[F] can suspend asynchronous side-effecting code. LiftIO should be familiar to Haskell veterans, and is broadly useful for defining parametric signatures and composing monad transformer stacks.

+

Effect is where everything is brought together. In addition to being able to suspend synchronous and asynchronous side-effecting code, anything that has an Effect instance may also be asynchronously interpreted into an IO. The way this is specified is using the runAsync function:

+
import cats.effect.{Async, LiftIO, Sync}
+
+trait Effect[F[_]] extends Sync[F] with Async[F] with LiftIO[F] {
+  def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): IO[Unit]
+}
+

What this is saying is that any Effect must define the ability to evaluate as a side-effect, but of course, we don't want to have side-effects in our pure and reasonable code. So how are side-effects purely represented? With IO!

+

From a parametric reasoning standpoint, IO means "here be effects", and so any type signature which involves IO thus also involves side-effects (well, effects anyway), and any type signature which requires side-effects must also involve IO. This bit of trickery allows us to reason about Effect in a way that would have been much harder if we had defined unsafeRunAsync as a member, and it ensures that downstream projects which write code abstracting over Effect types can do so without using any unsafe functions if they so choose (especially when taken together with the liftIO function).

+ +

Conclusion

+

The lack of a production-ready Task-like type fully integrated into the cats ecosystem has been a sticking point for a lot of people considering adopting cats. With the introduction of cats-effect, this should no longer be a problem! As of right now, the only releases are snapshots with hash-based versions, the latest of which can be found in the maven badge at the top of the readme. These snapshots are stable versions (in the repeatable-build sense), but they should not be considered stable, production-ready, future-proof software. We are quickly moving towards a final 0.1 release, which will depend on cats-core and will represent the stable, finalized API.

+

Once cats releases a final 1.0 version, cats-effect will also release version 1.0 which will depend on the corresponding version of cats-core. Changes to cats-effect are expected to be extremely rare, and thus the dependency should be considered quite stable for the purposes of upstream compatibility. Nevertheless, the release and versioning cycle is decoupled from cats-core to account for the possibility that breaking changes may need to be made independent of the cats-core release cycle.

+

Check out the sources! Check out the documentation. Play around with the snapshots, and let us know what you think! Now is the time to make your opinion heard. If IO in its current form doesn't meet your needs, we want to hear about it!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + +
+ I write code, read papers, and think thoughts. Broadly, I'm interested in: type theory, parser theory, functional abstractions, data structures, performance. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/jdg.html b/blog/jdg.html new file mode 100644 index 00000000..2ae00c47 --- /dev/null +++ b/blog/jdg.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + Contributors and Community + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Contributors and Community

+ + + governance + +
+
+
+
+

Contributors and Community

+

Effective today, John De Goes has been indefinitely barred from participation in Typelevel projects. This most directly impacts Cats Effect, but applies to our other repositories as well. The cause is John's combative style of interaction in Typelevel channels. His interactions when in agreement are always cordial, but when he disagrees with something or someone, the results are inevitably drawn out, intensely aggressive, and stressful. We have tried for the past three years, via one-on-one discussions and multiple warnings, to arrive at a style of respectful collaboration that we can all live with. These attempts have consistently failed, despite considerable time-consuming effort.

+

Our overriding goal is the well-being of our contributors. Too much of their energy and enthusiasm is being drained away by these conflicts, and we're concerned about the potential chilling effect on new contributors as well. While we appreciate John's technical insight and expertise and the time he devotes to sharing those things, neither outweigh the well-being of our contributors and community.

+

We are very much aware that, particularly with actions such as this, a lot of questions and concerns naturally arise. We want to be as open and as transparent as we can be, and we invite anyone who has concerns to engage with us in the Typelevel Admin channel on Gitter. If you prefer private correspondence for any reason, please feel free to directly contact the Typelevel leadership directly at info@typelevel.org.

+

While we are aware that the timing of this announcement is likely to be conflated with the recent action by Skills Matter, that timing is completely coincidental. This action by Typelevel has been independently in process for a considerable period of time.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/join-tsc-2026.html b/blog/join-tsc-2026.html new file mode 100644 index 00000000..7cae8e86 --- /dev/null +++ b/blog/join-tsc-2026.html @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + Join the Technical Steering Committee + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Join the Technical Steering Committee

+ + + governance + +
+
+
+
+

Join the Technical Steering Committee

+

Last December, we established the Technical Steering Committee (TSC) as an advisory committee to the Typelevel Foundation. We are passionate about making programming joyful and social, and our aspiration for this new committee is to bring together a diverse group of contributors, users, and enthusiasts to build camaraderie and collaborate to develop a collective vision for Typelevel and our technology.

+

The TSC will meet monthly and is broadly tasked with helping to steward Typelevel's projects. This may look like many things, such as:

+
    +
  • sharing your experiences, to grow our organizational knowledge
  • +
  • providing early feedback on new developments
  • +
  • maintaining and documenting our libraries and infrastructure
  • +
  • curating our portfolio of Organization and Affiliate projects
  • +
  • developing an AI policy for contributions to our repositories
  • +
  • advising the Typelevel Foundation on its technical roadmap
  • +
  • getting nerd-sniped with friends and brainstorming new ideas!
  • +
  • being a cheerleader for our community :)
  • +
+

As a member of the TSC, you certainly do not have to do all of these, but you are excited and eager to play to your strengths and help where you can. Moreover, as part of Typelevel's leadership, you will exemplify the values in our Code of Conduct.

+

We invited the former Steering Committee to serve as the founding members of the TSC. Today, we are welcoming additional members to join the team. This is a special opportunity to help grow an organization that is important to you and so many people in a collaborative and social setting. If you are active on GitHub or Discord, use Typelevel projects for business or pleasure, teach functional programming, and/or build community, please consider applying. And this is by no means an exhaustive list of what a great candidate may look like!

+ +

Applying

+

To apply, please submit your name, optionally with a brief statement about why you wish to serve on the TSC, by Monday, April 20. Terms are two years long, with the opportunity to renew.

+

Applications are visible only to the current committee membership. We will thoughtfully consider all serious applications, deliberate in private, and not disclose the identities of applicants.

+

We look forward to reading applications from familiar names and also hope to hear from new faces with fresh perspectives whom we have not yet had the pleasure of meeting. We are not looking for third-party nominations, but if there is someone you would like to see, encourage them to apply!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Foundation + + +
+ The Typelevel Foundation is a nonprofit 501(c)(3) public charity. Our mission is to maintain Typelevel projects, advance research and education in functional programming, and grow our community. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/lake-district-workshop-2016-09-14.html b/blog/lake-district-workshop-2016-09-14.html new file mode 100644 index 00000000..abf88145 --- /dev/null +++ b/blog/lake-district-workshop-2016-09-14.html @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Workshop in the Lake District + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Workshop in the Lake District

+ + + events + +
+
+
+
+

Typelevel Workshop in the Lake District

+
+

Image under commercial license. All rights reserved.

+ +

About the Workshop

+

A day of talks & unconference, co-located with Scala World.

+

The full set of sessions and topics will be decided by the participants on the day of the workshop. To give + us some context, and provide introductions for possible sessions on the day the following attendees have + kindly offered to do short informal presentations at the beginning of the day,

+ +

Dave Gurnell &mdash; State of the Mewnion

+

In this talk Dave will introduce the wonderful State monad: what it is, how it works, and how to use it to publish + books and compose songs using cat noises.

+ +

Rüdiger Klaehn &mdash; abc

+

Rüdiger will introduce abc, an experiment in writing a tiny collections library that + is more friendly to type classes. He will talk about the library itself and also about the projects structure and pain + points when using type classes in scala. We can dig deeper during the unconference.

+ +

Eda Meadows &mdash; Monadic Wack-A-Mole

+

Working with code structured as monads can start out looking beautifully straightforward but as things develop you + find yourself with for/yields that don't compile as you have a random type that doesn't match up. Eda will sketch out + some ideas on how to deal with this which we can explore during the unconference.

+ +

Miles Sabin &mdash; Typelevel Scala rebooted

+

Following on from the fix for SI-2712 the Typelevel Scala project has been reinvigorated. Miles is going to give a + quick overview of what Typelevel Scala adds to Lightbend Scala and set the scene for unconference sessions on using it + in your projects today and contributing to Typelevel and Lightbend Scala development.

+ +

Paweł Szulc &mdash; The Cats toolbox: a quick tour of some basic typeclasses

+

It's happened to all of us: we ran away from some conversation or library because it kept on using those "weird" + phrases. You know, like "type classes", "semigroups", "monoids", "applicatives". Yikes! They all seem so academic, + so pointlessly detached from real-world problems. But then again, given how frequently we run into them in functional + programming, are they REALLY irrelevant, or do they have real-world applications? Paweł will start helping us make + sense of the gobbledygook and we can continue in a later session.

+ +

Pere Villega &mdash; What happens when you use Free Monads

+

There's a lot of talk about Free Monads and how awesome they are. But, what happens when we try to implement a real + application using Free? Pere will show ane example using Freek and + the StockFighter API and highlight some of the choices and limitations + involved. Later, during the unconference, we can experiment with Free Monads to see their impact in everyday concerns + like testing, program design, etc.

+ +

Dick Wall &mdash; Ensime Sublime

+

Ensime isn't just for Emacs! Dick Wall will give us a taste of what's been happening in the Ensime project to support + users of the Sublime Text editor and encourage you all to hack on Ensime later.

+ +

Venue

+

This event took place at the Rheged Centre, situated near Penrith in the Lake District National Park, UK.

+
+
+ +
+ + + + + + diff --git a/blog/libra.html b/blog/libra.html new file mode 100644 index 00000000..d035e9f5 --- /dev/null +++ b/blog/libra.html @@ -0,0 +1,529 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Compile time dimensional analysis with Libra + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Compile time dimensional analysis with Libra

+ + + technical + +
+
+
+
+

Compile time dimensional analysis with Libra

+ +

Dimensional analysis

+

When we code, we code in numbers - doubles, floats and ints. Those numbers always represent real world quantities.

+

For example, the number of people in a room can be represented as an integer, as can the number of chairs. + Adding people and chairs together gives a nonsensical result, but dividing the number of people by the number of chairs gives a useful indicator of how full up the room is.

+
val numberOfPeople = 9
+val numberOfChairs = 10
+numberOfPeople + numberOfChairs // this is a bug
+numberOfPeople.toDouble / numberOfChairs.toDouble // this is useful
+

This is actually a form of dimensional analysis. We're mentally assigning the dimension Person to the quantity of people, and Chair to the quantity of chairs. Dimensional analysis can be summarized in two laws.

+
    +
  1. Quantities can only be added or subtracted to quantities of the same dimension
  2. +
  3. Quantities of different dimensions can be multiplied or divided
  4. +
+ +

Why is it important?

+

Ignoring the laws can result in serious problems. + Take the Mars Climate Orbiter, a $200 million space probe which successfully reached Mars after a year long voyage, but suddenly crashed into the Martian atmosphere on arrival. Most components on the orbiter were using metric units, however a single component was sending instructions in Imperial units. The other components did not detect this, and instead began a sudden descent causing the orbiter to burn up. This was a simple unit conversion error! It was a basic mistake that could have been easily avoided. It should have been picked up during testing, or in the runtime validation layer.

+

In fact, it could even have been caught at compile time.

+ +

Compile time dimensional analysis

+

We're going to use a similar problem to demonstrate compile time dimensional analysis. + To fit with the theme of rocket physics, we will tackle a rocket launch towards the distant constellation of Libra. + We'll begin by working through our calculation in doubles before adding compile time safety with dependent types and finally supporting compile time dimensional analysis with typeclass induction.

+ +

Destination: Alpha Librae

+

The star that we're aiming for is Alpha Librae. This is pretty far, so we can only send one very small person. We have been given the following quantities to work with:

+
    +
  • rocket mass of a small person - 40kg 40 \text{kg}
  • +
  • fuel mass of a lot of fuel - 104kg 10^4 \text{kg}
  • +
  • exhaust speed of a decent fuel - 106ms1 10^6 \text{ms}^{-1}
  • +
  • distance to Alpha Librae - 77ly 77 \text{ly}
  • +
+

We want to calculate when the rocket will arrive.

+

To do so, we're going to make use of a formula known as the Ideal Rocket Equation. + This calculates the speed of a rocket in ideal conditions.

+
val rocketSpeed = exhaustSpeed * log((rocketMass + fuelMass) / rocketMass)
+

Once we have the speed, we can work out the travel time.

+
val time = distance / rocketSpeed
+ +

Plugging the numbers in

+

Let's do what we're used to doing and use doubles:

+
val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0)
+// rocketSpeed: Double = 5525452.939131783
+
+val time = 77.0 / rocketSpeed
+// time: Double = 1.39355091515989E-5
+

Fantastic! We can get to Libra in less than a day!

+

Unfortunately, this time estimate is too far off to be valid. We can't get to Libra that quickly at light speed, let alone rocket speed. We've clearly made a mistake somewhere. Instead of pouring over our code to find out where that is, let's try and use the compiler.

+ +

Using types

+

We can add some type safety to this problem by using a case class to represent each quantity.

+
case class Quantity[A](value: Double)
+

A represents the quantity dimension. So given the following dimensions:

+
type Kilogram
+type Metre
+type Second
+type MetresPerSecond
+type C
+type LightYear
+type Year
+

We can create quantities:

+
val rocketMass = Quantity[Kilogram](40.0)
+val fuelMass = Quantity[Kilogram](10000.0)
+val exhaust = Quantity[MetresPerSecond](1000000.0)
+val distance = Quantity[LightYear](77.0)
+

It's important to note that these are types, not classes. We never instantiate a MetresPerSecond - we're just using it to differentiate between Quantity[MetresPerSecond] and Quantity[Year] at the type level.

+

So how does this change the code?

+
val rocketSpeed = Quantity[MetresPerSecond](exhaust.value * log((rocketMass.value + fuelMass.value) / rocketMass.value))
+// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783)
+
+val time = Quantity[Year](distance.value / rocketSpeed.value)
+// time: Quantity[Types.Year] = Quantity(1.39355091515989E-5)
+

In short, it doesn't. The code might be clearer, but we don't know what the bug is. This is because the compiler isn't doing anything with the types we've added.

+ +

Operating on quantities

+

We can encode our first law of addition at compile time by creating a function to add quantities:

+
def add[A](q0: Quantity[A], q1: Quantity[A]): Quantity[A] = Quantity(q0.value + q1.value)
+

This ensures that quantities can only be added to other quantities of the same type. Trying to add quantities of different types will result in a compilation error.

+

A quantity can also be multiplied by a dimensionless scalar value to give a quantity of the same dimension.

+
def times[A](q: Quantity[A], v: Double): Quantity[A] = Quantity(q.value * v)
+

It would be great if we could divide quantities too. Writing a divide function is more difficult:

+
def divide[A, B, Magic](q0: Quantity[A], q1: Quantity[B]): Quantity[Magic] =
+  Quantity[Magic](q0.value / q1.value)
+

There's a clear problem with trying to do this. When we divide a quantity by another, we don't know what the Magic output type should be. + The output type is dependent on what the input types are (for example, dividing Metre by Second should give MetresPerSecond). The compiler needs a way of working out what the output is, provided that it knows the input types.

+ +

Dependent types

+

What we actually want is a dependent type. A division operation should occur at the type level, taking two input types and supplying a dependent output type. + We can create the trait Divide with a dependent output type:

+
trait Divide[A, B] {
+  type Out
+}
+

We also need to define an Aux type alias. This is known as the Aux pattern and makes it easier to refer to all three types at once.

+
object Divide {
+  type Aux[A, B, Out0] = Divide[A, B] { type Out = Out0 }
+}
+

We can create instances of this divide typeclass with different output types, so the output type is dependent on the value of the divide typeclass instance.

+

When dividing, the compiler looks for this implicit typeclass instance and returns a quantity corresponding to the output type.

+
def divide[A, B](q0: Quantity[A], q1: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] =
+  Quantity[d.Out](q0.value / q1.value)
+

So given that we want to divide A by B, the compiler will look for a value of Divide[A, B] and find the Out type of it. If no instance exists, the code doesn't compile.

+

We'll need some more types to represent the result of a division:

+
type LightYearSecondsPerMetre
+type MetresPerSecondPerC
+type Dimensionless
+

And we'll need to write instances for all combinations of dimensions.

+
implicit val kgDivideKg: Divide.Aux[Kilogram, Kilogram, Dimensionless] =
+  new Divide[Kilogram, Kilogram] { type Out = Dimensionless }
+
+implicit val lyDivideC: Divide.Aux[LightYear, C, Year] =
+  new Divide[LightYear, C] { type Out = Year }
+
+implicit val lyDivideMps: Divide.Aux[LightYear, MetresPerSecond, LightYearSecondsPerMetre] =
+  new Divide[LightYear, MetresPerSecond] { type Out = LightYearSecondsPerMetre }
+
+implicit val mpsDivideC: Divide.Aux[MetresPerSecond, C, MetresPerSecondPerC] =
+  new Divide[MetresPerSecond, C] { type Out = MetresPerSecondPerC }
+
+implicit val mpsDivideMpsPerC: Divide.Aux[MetresPerSecond, MetresPerSecondPerC, C] =
+  new Divide[MetresPerSecond, MetresPerSecondPerC] { type Out = C }
+

And so on.

+

Unfortunately, there are an infinite number of combinations, so there are an infinite number of instances. + Nevertheless, let's plough on with the ones we've written. We can modify our rocket equation to use add, times and divide:

+
val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value))
+// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783)
+
+val time: Quantity[Year] = divide(distance, rocketSpeed)
+// <console>:31: error: type mismatch;
+//  found   : Quantity[lyDivideMps.Out]
+//     (which expands to)  Quantity[MoreTypes.LightYearSecondsPerMetre]
+//  required: Quantity[Types.Year]
+//        val time: Quantity[Year] = divide(distance, rocketSpeed)
+//                                         ^
+

Great! We've caught our bug! The result was in LightYearSecondsPerMetre, not Year. We made a unit conversion error, just like the Mars orbiter.

+

We can now fix this by adding a conversion:

+
val metresPerSecondPerC: Quantity[MetresPerSecondPerC] = divide(Quantity[MetresPerSecond](300000000.0), Quantity[C](1.0))
+// metresPerSecondPerC: Quantity[MoreTypes.MetresPerSecondPerC] = Quantity(3.0E8)
+
+val speedInC = divide(rocketSpeed, metresPerSecondPerC)
+// speedInC: Quantity[mpsDivideMpsPerC.Out] = Quantity(0.018418176463772612)
+
+val time: Quantity[Year] = divide(distance, speedInC)
+// time: Quantity[Types.Year] = Quantity(4180.65274547967)
+

It seems like it's going to take a lot longer than we hoped to get to Libra. Perhaps it's unwise to send a person.

+ +

Automatic derivation

+

We found the bug, but we needed to explicitly write out typeclass instances for every combination of dimensions. + This might have worked for our small problem, but it just doesn't scale in the long run. + We need to figure out a way of deriving the typeclass instances automatically. + To attempt this, we first need to generalize what a combination of dimensions actually is.

+ +

Representing dimensions

+

We can represent a combination of dimensions as a heterogeneous list (HList) of base dimensions. HLists are defined in shapeless, a cornerstone of most functional libraries, and can be thought of as a type level list.

+
type LightYearSeconds = LightYear :: Second :: HNil
+

This is good for multiples of dimensions, such as LightYearSeconds, but doesn't represent combinations created from division, such as MetresPerSecond. + To do this, we need some way of representing integer exponents as types. We can represent integers as types using Singleton types. We actually need these singleton types in type position. This is supported by a new feature present in Typelevel Scala, called literal types:

+
scalaOrganization := "org.typelevel"
+scalacOptions += "-Yliteral-types"
+

We need to represent a key value pair of dimension and integer exponent. We could use a Tuple for this, but will use a shapeless FieldType instead. This is similar to a Tuple, but is more compatible with some of shapeless's typeclasses.

+
type MetresPerSecond = FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil
+

It's important to note that the number 1 above is a type, not a value. Because it's a type, the compiler can work with it.

+ +

Operations on Singleton types

+

When we multiply and divide dimensions, we want to add or subtract from these exponents.

+

We can use a library called singleton ops to do this. This provides us with type level integer operations using the OpInt typeclass:

+
OpInt.Aux[1 + 2, 3]
+OpInt.Aux[3 * 2, 6]
+

It also provides a convenient alias for integer singleton types

+
type XInt = Singleton with Int
+

The type 1, for example, is a subtype of XInt.

+ +

Deriving typeclass instances

+

We now need to automatically derive typeclass instances of Divide. + To do this, we're going to derive instances for Invert and Multiply operations first. + Deriving Divide then becomes much simpler.

+

The technique we're going to use to automatically derive instances is known as typeclass induction.

+ +

Typeclass Induction

+

Aaron Levin gave a great introduction to induction in his talk earlier at the Typelevel Summit. In summary, you can derive an implicit typeclass instance for all cases by:

+
    +
  1. Providing it for the base case
  2. +
  3. Providing it for the n + 1 case, given that the n case is provided
  4. +
+

This is similar to the mathematical method of proof by induction.

+ +

Invert

+

We're first going to derive inductive typeclass instances for the Invert operation. + Inverting a quantity raises it to the exponent of -1. This means that the exponents of all dimensions must be negated.

+

For example, the inverse of FieldType[Metre, 1] :: HNil is FieldType[Metre, -1] :: HNil. + Invert takes one input type and returns one output type:

+
trait Invert[A] {
+	type Out
+}
+object Invert {
+	type Aux[A, Out0] = Invert[A] { type Out = Out0 }
+}
+

To inductively derive typeclass instances for inverting, we need to prove that:

+
    +
  1. We can derive an instance for HNil (the base case)
  2. +
  3. We can derive an instance for a non-empty HList (the n + 1 case), provided there is an existing instance for its tail (the n case)
  4. +
+

The base case operates on HNil

+
implicit def baseCase: Invert.Aux[HNil, HNil] = new Invert[HNil] { type Out = HNil }
+

The inductive case assumes that the tail has an instance, and derives an instance for the head by negating the exponent:

+
implicit def inductiveCase[D, Exp <: XInt, NExp <: XInt, Tail <: HList,
+  OutTail <: HList](
+    implicit negateEv: OpInt.Aux[Negate[Exp], NExp],
+    tailEv: Invert.Aux[Tail, OutTail]
+): Invert.Aux[FieldType[D, Exp] :: Tail, FieldType[D, NExp] :: OutTail] =
+  new Invert[FieldType[D, Exp] :: Tail] {
+    type Out = FieldType[D, NExp] :: OutTail
+}
+

When the compiler looks for the implicit instance for FieldType[Metre, 1] :: HNil:

+
    +
  • It finds that the inductiveCase method has a return type which fits the signature
  • +
  • It can find the required evidence negateEv for negating 1 from singleton ops
  • +
  • It requires evidence of an implicit instance for the tail HNil
  • +
  • It finds that baseCase provides this evidence
  • +
+

So in hunting for implicit typeclass instance for the whole list, the compiler goes and finds instances for the tail (the n case), right up until the base. If we provide an inductive proof with a baseCase and an inductiveCase, we fit the bill for what the compiler needs.

+ +

Multiply

+

Now that we've tested a basic example of induction, we can go on to a more complex one.

+

We want to multiply two HLists of dimensions together. This means that the exponents should be added.

+
trait Multiply[A, B] {
+  type Out
+}
+object Multiply {
+  type Aux[A, B, Out0] = Multiply[A, B] { type Out = Out0 }
+}
+

This is harder to make inductive because there are two input lists involved. Luckily, we only need to recurse over one of them, as we can pick dimensions from the other using shapeless's Selector. We will recurse over the left list and can pick elements from the right list.

+

Our base case can be the same:

+
implicit def baseCase: Multiply.Aux[HNil, HNil, HNil] = new Multiply[HNil, HNil] {
+  type Out = HNil
+}
+

We can define the inductive case using the following logic: + 1. Pick the exponent in the right list corresponding to the head dimension in the left list + 2. Add the left and right exponents together + 3. Filter the term from the right list to get the remaining elements + 4. Look for a typeclass instance for the left list tail and the remaining elements in the right list

+
implicit def inductiveCase[D, R <: HList, LExp <: XInt , RExp <: XInt,
+  OutExp <: XInt, RTail <: HList, LTail <: HList, OutTail <: HList](
+  implicit pickEv: Selector.Aux[R, D, RExp],
+  addEv: OpInt.Aux[LExp + RExp, OutExp],
+  filterEv: FilterNot.Aux[R, FieldType[D, RExp], RTail],
+  tailEv: Multiply.Aux[LTail, RTail, OutTail]
+): Multiply.Aux[FieldType[D, LExp] :: LTail, R, FieldType[D, OutExp] :: OutTail] =
+  new Multiply[FieldType[D, LExp] :: LTail, R] {
+    type Out = FieldType[D, OutExp] :: OutTail
+}
+

When the compiler looks for an implicit instance of multiply for FieldType[Metre, -1] :: HNil and FieldType[Metre, 3] :: HNil:

+
    +
  • It finds that the inductiveCase has a return type which fits the signature
  • +
  • Given that the head of the left list is Metre, it selects the exponent for Metre from the right list
  • +
  • It can find the evidence addEv to add the exponents -1 and 3
  • +
  • It filters Metre from the right list to get HNil
  • +
  • It requires evidence of an instance for HNil and HNil
  • +
  • This is provided by the base case
  • +
+

The compiler can now find instances of Multiply, as long as a dimension appears in both the left and right lists. + This can be extended to when a dimension doesn't appear by writing a few more inductive cases.

+ +

Divide

+

The reason we went to the effort of writing Invert and Multiply was to divide. + Dividing a numerator by a denominator is as simple as inverting the denominator and multiplying it by the numerator. + We can write this in a single non-inductive instance:

+
implicit def divide[L <: HList, R <: HList, RInverted <: HList,
+  Divided <: HList](
+  implicit invertEv: Invert.Aux[R, RInverted],
+  multiplyEv: Multiply.Aux[L, RInverted, Divided]
+): Divide.Aux[L, R, Divided] = new Divide[L, R] {
+  type Out = Divided
+}
+

That's far simpler than the work we've done before - we're just building on the typeclasses we wrote to do this.

+ +

Automatically derived instances

+

We can now have compile time dimensional analysis without writing out divide instances for every combination of dimensions:

+
val rocketMass = Quantity[FieldType[Kilogram, 1] :: HNil](40.0)
+val fuelMass = Quantity[FieldType[Kilogram, 1] :: HNil](10000.0)
+val exhaust = Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil](1000000.0)
+val distance = Quantity[FieldType[LightYear, 1] :: HNil](77.0)
+
+val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value))
+val time: Quantity[FieldType[Year, 1] :: HNil] = divide(distance, rocketSpeed)
+// error: type mismatch; found: FieldType[LightYear, 1] :: FieldType[Metre, -1] :: FieldType[Second, 1] :: HNil; required: FieldType[Year, 1] :: HNil
+

Yay! We've solved the problem! It looks a lot more verbose than what we started with, but we can tidy this up by using extension methods:

+
implicit final class DoubleOps(val d: Double) {
+  def ly: Quantity[FieldType[LightYear,1] :: HNil] = Quantity(d)
+  def kg: Quantity[FieldType[Kilogram, 1] :: HNil] = Quantity(d)
+  def yr: Quantity[FieldType[Year, 1] :: HNil]     = Quantity(d)
+  def mps: Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil] = Quantity(d)
+  def c: Quantity[FieldType[LightYear, 1] :: FieldType[Year, -1] :: HNil] = Quantity(d)
+}
+

We can also add symbolic infix operators for add, times and divide:

+
case class Quantity[A](value: Double) {
+  def +(q: Quantity[A]): Quantity[A] = Quantity(value + q.value)
+  def *(v: Double): Quantity[A] = Quantity(value + v)
+  def /[B](q: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] = Quantity(value / q.value)
+}
+ +

We've reached Libra!

+

We started with doubles:

+
val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0)
+val time = 77.0 / rocketSpeed
+

And we finished with compile time dimensional analysis:

+
val rocketSpeed = 1000000.0.mps * log(((40.0.kg +10000.0.kg) /40.0.kg).value)
+val speedConversion = 300000000.0.mps / 1.c
+val speedInC = rocketSpeed / speedConversion
+val time = 77.0.ly / speedInC
+//time: Quantity[FieldType[Year, 1] :: HNil] = Quantity(4180.65274634)
+

The code isn't more verbose - if anything, it's more explanatory and just as easy to work with.

+ +

Rolling this out to more problems

+

All we need to provide for the business logic of our rocket launch problem are the dimensions and DoubleOps. + We could roll this out to any other problem. Let's say we wanted to do a currency conversion between GBP and DKK:

+
val exchangeRate: Quantity[FieldType[DKK, 1] :: FieldType[GBP, -1] :: HNil] =
+   currentExchangeRate()
+Val krone: Quantity[FieldType[DKK, 1] :: HNil] = 10.gbp * exchangeRate
+

We get dimensional analysis for any problem domain out of the box!

+

Most of the code we've written is library code. In fact, it's Libra code! Libra is a dimensional analysis library based on typelevel induction. It performs compile time dimensional analysis to any problem domain. It also uses spire for its numeric typeclasses, so can be used for far more than just doubles.

+ +

Conclusion

+

It's been a long way from the humble Double. We started with basic types, explored dependent types, took a look at Typelevel Scala along the way, before finally ending up performing typelevel induction. As a result, we've managed to achieve compile time dimensional analysis for any problem. If you're curious about typelevel induction take a look at the Libra codebase for more examples. Enjoy!

+
+
+
+
+ +
+
+ +
+
+

+ Zainab Ali + + +
+ Zainab is a functional programmer who converted from object oriented design. A physicist at heart, she was excited to find an application of dimensional analysis and dependent types to real world problems. She is the author of Libra and a contributor to many typelevel libraries, such as cats and fs2. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/liskov-lifting.html b/blog/liskov-lifting.html new file mode 100644 index 00000000..616c6076 --- /dev/null +++ b/blog/liskov-lifting.html @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + When can Liskov be lifted? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

When can Liskov be lifted?

+ + + technical + +
+
+
+
+

When can Liskov be lifted?

+

Scalaz avoids + variance in the sense of the Scala type parameter annotation, + with its associated higher-kind implications, except where it has + historically featured variance; even here, variance is vanishing as + unsoundness in its released implementations is discovered.

+

There is a deeply related concept in Scalaz's typeclasses, though: + covariant and contravariant + functors. Functor + is traditional shorthand for covariant functor, whereas + Contravariant + represents contravariant functors.

+

These concepts are related, but neither subsumes the other. A + Functor instance does not require its parameter to be + Scala-covariant. A type can be Scala-covariant over a parameter + without having a legal Functor instance.

+ +

Liskov

+

Liskov, + also known as <~< and very close to Scala's own + <:<, + represents a subtyping relationship, and is defined by the ability to + lift it into Scala-covariant and Scala-contravariant parameter + positions, like so:

+
def liftCo[F[+_], A, B](a: A <~< B): F[A] <~< F[B]
+def liftCt[F[-_], A, B](a: A <~< B): F[B] <~< F[A]
+

As Liskov is, soundly, Scala-variant, this can be implemented + without a cast. However, it can only be called with Scala-covariant + F.

+

By definition, applying an A <~< B to a value of type A should + yield a value of type B, but must also do nothing but return the + value; in other words, it is an operational identity. Despite the + limitation of liftCo, for functorial values that are parametrically + sound, even for Scala-invariant F, it is operationally sound to + lift Liskov, though impossible to implement without exploiting Scala + soundness holes:

+
def liftCvf[F[_]: Functor](a: A <~< B): F[A] <~< F[B]
+

For example, + this is sound for scalaz.IList.

+ +

But IList[Int] isn't a subtype of IList[Any]!

+

Sure, as far as Scala is concerned. But Liskov is all about making + claims that can't directly be proven due to the language's + limitations. Haskell allows you to constrain functions with type + equalities, which is very important when working with type families; + Scala doesn't, so we get + Leibniz + instead.

+

A type is a set of values. Where Y is a supertype of X, every + value in X is in Y. Since IList[String]("hi", "there") has the + same representation as IList[Any]("hi", "there"), they are the same + value. This is true for all IList[String]s, but the opposite is + not true; therefore, IList[Any] is an IList[String] supertype, + regardless of what Scala knows.

+

So doing a casting Liskov lift, like that into IList, is + essentially “admitted” in a proof system sense. You are saying, “I + can't prove that this subtype relationship holds, but it does, so + assume it.”

+

To decide whether an admitted A <~< B is sound: suppose that the + compiler admits that subtyping relationship. Can it then draw + incorrect conclusions, about the sets of values, derived from that + assumption? This is the cardinal rule.

+

By extension, to decide whether an F permits Liskov lifting: + does the above rule pass given F[A] <~< F[B] for all A, B + where B is a supertype of A?

+ +

Parametrically sound covariance

+

Because a Liskov must be an operational identity, it is essential + that, given any value of F[A], for all supertypes B of A, the + representation of F[B] must be identical. You can determine this by + analyzing the subclasses of F as an algebraic data type, where the + key test is to ensure that A never appears in the primitive + contravariant position: as the parameter to a function. This test is + not quite enough to prove that Liskov lifting is sound, but it gets + us most of the way.

+

For example, an IList of "hi" and "there" has exactly the same + representation whether you instantiated the IList with String or + with Any. So that is a good first test. If a class changes its + construction behavior based on manifest type information, or its basic + data construction functions violate + the rules of parametricity, + that is a good sign that the data type cannot follow these rules.

+

This data type analysis is recursive: a data type being variant in a + parametrically sound way over a parameter requires that all + appearances of that parameter in elements of your data type are also + parametrically sound in that way. For example, if your F[A] contains + an IList[A] in its representation, you may rely on IList's + parametrically sound covariance when considering F's.

+

Any var, or var-like thing such as an Array, places its + parameter in an invariant position, because it features a getter + (return type) and setter (parameter type). So its presence in the data + model invalidates Liskov lifting if the type parameter appears + within it.

+

Obviously, runtime evidence of a type parameter's value eliminates the + possibility of lifting Liskov over that parameter.

+

You cannot perform this representation analysis without considering + all subclasses of a class under consideration. For example, + considering only + HashSet, + collection.immutable.Set + appears to allow Liskov lifting. However, + TreeSet, + a subclass of Set, contains a function (A, A) => Ordering. If + any representation contains a contradiction like this, Liskov + lifting is unsafe. You cannot constrain Liskov application by a + runtime test.

+

If you permit open subclassing, you must either declare the + requirement to preserve parametric covariance, or accept that it will + be violated, and so forbid Liskov lifting.

+

Data that doesn't use a type parameter doesn't affect its parametric + soundness. For example, here A is invariant, but B is covariant:

+
final case class VA[A, B](xs: Set[A], ys: IList[B])
+ +

GADTs

+

Some features of Scala resist simple ADT analysis, so must be + considered separately from the above. Despite their sound covariance + considering only the representational rules in the previous section, + they still break the cardinal rule by allowing the compiler to make + invalid assumptions about the sets of values. A “recoverable phantom” + implies a type relationship that forbids Liskov-lifting, for + example:

+
sealed trait Gimme[A]
+case object GimmeI extends Gimme[Int]
+

In pattern matching, given a Gimme[A] over unknown A, matching + GimmeI successfully recovers the type equality A ~ Int; therefore, + Liskov-lifting is unsound for Gimme. For example, lifting + Int <~< Any, applying to GimmeI, and matching, gives us + Any ~ Int, which is nonsense.

+

We can reason about this type equality as a value member of GimmeI + of type Leibniz[⊥, ⊤, A, Int], which places A in a + representationally invariant position.

+

Some other GADTs invalidate covariance. For example:

+
sealed trait P[A]
+case class PP[A, B](a: P[A], b: P[B]) extends P[(A, B)]
+

The pattern match of a P[A] to PP[_,_] can theoretically determine + A ~ (x, y) forSome {type x; type y}, so Liskov cannot be lifted + into P.

+

However, not all GADTs invalidate Liskov-lifting:

+
sealed trait AM[A]
+case class FAM[A, B](fa: AM[A], f: A => B) extends AM[B]
+

Matching AM[A] to FAM[_,_] reveals nothing about A; its use of + GADTs only introduces a new existential unrelated to A. Considering + only B, as the A parameter is called in FAM, its covariance is + sound in FAM, so Liskovs can be lifted into AM.

+ +

Contravariance

+

Liskovs can also be lifted into parametrically sound contravariant + positions. This looks a bit like:

+
def liftCtf[F[_]: Contravariant, A, B](a: A <~< B): F[B] <~< F[A]
+

Analysis of parametrically sound contravariance is essentially the + same as that for covariance. The only difference is that, for F[A], + A can only appear in the primitive contravariant position: the + function parameter type.

+

With regard to recursion, the “flipping” behavior of + Scala-contravariance applies. For example, this data type is soundly + contravariant over A:

+
final case class IOf[A](f: IList[A] => Unit)
+

IList is soundly covariant over A, and IList[A] appears in + soundly contravariant position, making A contravariant. Meanwhile, + A is soundly covariant in this data type built upon IOf:

+
final case class IOf2[A](f: IOf[IOf[A]])
+ +

Some surprises

+

Despite the unsoundness of Liskov-lifting into Gimme earlier, it + may seem surprising that Scala allows:

+
sealed trait GimmeC[+A]
+case object GimmeCI extends Gimme[Int]
+

Moreover, this isn't a bug; it's perfectly sound. That is because, + while matching GimmeI causes Scala to infer A ~ Int, it won't do + that for GimmeCI! Scala can soundly determine that A ⊇ Int when + it matches GimmeCI, but I do not think it even goes so far as to do + that as of this writing. We can't blame Scala for this difference; + Scala has declared up front that its type system encodes what it + believes, and is our responsibility to follow the cardinal rule of + not violating its assumptions if we lift Liskov into Gimme.

+

As stated earlier, Liskov cannot be lifted into + collection.immutable.Set; TreeSet exists to trivially demonstrate + the problem, but even if TreeSet was not there, we would not be able + to honestly do it because c.i.Set is open to new subclasses that + could perform similar violations. However, despite lacking a + Functor, scalaz.ISet does allow Liskov-lifting. + Do the ADT analysis yourself, if you like. + Well, so, once you convert your ISet[Int] to ISet[Any], you can't + do many operations on it, but that's neither here nor there.

+ +

Should this function exist?

+

The Scalaz community has settled on a definition of + covariant-functoriality that conforms with the principle of parametric + soundness. The rejection of Functor instances + for the scala.collection.* classes, + which have subclasses with mutable values over their parameters, and + for collection.immutable.Set, + which has the TreeSet case stated above and violates parametricity + in the construction of HashSets, speak to this. As far as I know, + Scalaz contains no Functors that are both Scala-invariant and + violate the rules delineated above.

+

So how do you feel about the provision of a combinator of the type of + liftCvf for Scalaz's Functor?

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/machinist.html b/blog/machinist.html new file mode 100644 index 00000000..fe85ced8 --- /dev/null +++ b/blog/machinist.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + + + + + + + Machinist vs. value classes + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Machinist vs. value classes

+ + + technical + +
+
+
+
+

Machinist vs. value classes

+

This article is about machinist, a stand-alone project which started out as part of the spire project and has been originally published in October 2014. + The original description can be found on this blog. + You should read that linked post first if you are not familiar with how Machinist works.

+ +

Introduction

+

Machinist Issue #2 asks:

+
Is it correct, that this stuff is completely obsolete now due to + value classes or are there still some use cases? An example of using + value class for zero-cost implicit enrichment: (...)
+

The short answer is that value classes existed before the Machinist macros were implemented, and they do not solve the same problem Machinist solves.

+

This article is the long answer.

+ +

The base case

+

The Machinist's goal is to remove any overhead that would distinguish + using a type class "directly" from using it indirectly via an implicit + operator.

+

Imagine we have the following "toy" type class:

+
trait Div[A] {
+  def div(lhs: A, rhs: A): A
+}
+
+object Div {
+  implicit val DivString = new Div[String] {
+    def div(lhs: String, rhs: String): String = lhs + "/" + rhs
+  }
+}
+

This allows us to write generic code such as:

+
class Test1 {
+  def gen[A](x: A, y: A)(implicit ev: Div[A]): A = ev.div(x, y)
+  def test: String = gen("foo", "bar")
+}
+

We have a generic method gen that works with any type we have a Div[A] instance for, and we verify that it works using a test method that operates on some strings. So far, so good. But obviously, calling ev.div is a bit ugly.

+ +

Implicit conversion with a value class

+

We can make the gen method look a bit nicer by using an implicit conversion. Here's the code:

+
object Test3 {
+  implicit class DivOps[A](val lhs: A) extends AnyVal {
+    def /(rhs: A)(implicit ev: Div[A]): A = ev.div(lhs, rhs)
+  }
+}
+
+class Test3 {
+  import Test3.DivOps
+  def gen[A: Div](x: A, y: A): A = x / y
+  def test: String = gen("foo", "bar")
+}
+

Now, we can just say x / y and have that call Div#div automatically. We also don't need a reference to ev: Div[A] so we can use the nicer [A: Div] syntax.

+

With a normal implicit conversion, every call to gen would construct an instance of Test3.DivOps. However, since we have defined Test3.DivOps as a value class (by extending AnyVal), the object instantiation is ellided. Instead, the method call is dispatched to Test3.DivOps.$div$extension which calls ev.div.

+

We often talk about value classes as not having a cost. Since no class is instantiated, we are not required to pay a cost in allocations, but we do still pay a cost in indirection (instead of calling ev.div directly as in Test1 we have an intermediate extension method).

+

You can see the difference in the output from javap.

+

In the case of Test1.gen, the call to ev.div and return are all handled with 5 instructions (8 bytes of bytecode):

+
// cost.Test1.gen(A, A, Div[A]): A
+0: aload_3
+1: aload_1
+2: aload_2
+3: invokeinterface #16,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+8: areturn
+

In the case of Test3.gen, there is extra ceremony setting up the companion objects, and a call to the extension method ($div$extension), which is defined in Test3.DivOps:

+
// cost.Test3.gen(A, A, Div[A]): A
+0: getstatic     #25                 // Field cost/Test3$DivOps$.MODULE$:Lcost/Test3$DivOps$;
+3: getstatic     #16                 // Field cost/Test3$.MODULE$:Lcost/Test3$;
+6: aload_1
+7: invokevirtual #18                 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object;
+10: aload_2
+11: aload_3
+12: invokevirtual #28                 // Method cost/Test3$DivOps$.$div$extension:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object;
+15: areturn
+
+// cost.Test3.DivOps.$div$extension(A, A, Div[A]): A
+0: aload_3
+1: aload_1
+2: aload_2
+3: invokeinterface #20,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+8: areturn
+

In fact the bytecode for the extension method is uncannily similar to that of Test1.gen, but in this case Test3.gen involves 8 more instructions (15 bytes).

+

In some cases these bytecode differences might not be significant (for example if the running time of Div[A].div is expected to dwarf the cost of method dispatch). However, when type classes are used to support primitive operations (such as addition or comparisons) it's likely that this overhead might be significant.

+ +

Enter machinist

+

Machinist is based on a set of macros that were introduced in Spire to remove the performance penalties associated with generic math implementations. These macros were based on an even earlier approach which used a compiler plugin.

+

The basic approach has not changed: at compile-time we can detect situations where we build an object just to assemble a method call with the arguments to its constructor. In these cases we rewrite the tree, removing the object allocation and making the method call directly. Machinist's documentation goes to some trouble to explain it, but basically, we want to be able to write code like Test3.gen but have it interpreted as Test1.gen. That is literally the entire purpose of machinist.

+

Here's a construction that works for this example:

+
object Test2 {
+  implicit class DivOps[A](lhs: A)(implicit ev: Div[A]) {
+    def /(rhs: A): A = macro machinist.DefaultOps.binop[A, A]
+  }
+}
+
+class Test2 {
+  import Test2.DivOps
+  def gen[A: Div](x: A, y: A): A = x / y
+  def test: String = gen("foo", "bar")
+}
+

We use the machinist.DefaultOps object to provide an instance of the binop macros, which will rewrite DivOps(x)(ev).$div(y) into ev.div(x, y).

+

Here's what we end up with in bytecode:

+
// cost.Test2.gen(A, A, Div[A]): A
+0: aload_3
+1: aload_1
+2: aload_2
+3: invokeinterface #26,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+8: areturn
+

As you can see, the sourcecode for Test2.gen is identical to Test3.gen, and the bytecode for Test2.gen is identical to that of Test1.gen. Success!

+ +

Caveats

+

There are a few caveats that are worth mentioning:

+ +

Managing compilation units

+

The issue that sparked this article used the operator +/+. Machinist claims to be able to support any symbolic operator. Why didn't we use that operator here?

+

The answer has to do with how Scala macros work. Scala requires that macros be defined in a separate "compilation unit" from the one they are invoked in. This makes it very awkward to create a code snippet that both defines and uses a macro. In this case, it means that we can't extend machinist.Ops to define new symbolic operators in the same file that demonstrates their use. This is why we used / (which maps to div and is a "default operator").

+

You can arrange your "real" projects so that they are not affected by this limitation.

+ +

Use outside of generic methods

+

Now that we've demonstrated the cost that implicit conversions to value classes impose, you might imagine wanting to perform this transformation on all your implicit conversions.

+

Unfortunately, Machinist is not sufficiently general to support this. Right now its macros support a number of different "shapes" but assume generic method which dispatches to an implicit evidence parameter. It might be possible to write macros which inline the method body of a concrete implicit class, but that's outside the scope of the project.

+ +

Postscript: messy details

+

This article throws around a lot of source code and bytecode. Below are included the files needed to build the demo (cost.scala and build.sbt) as well as the javap output from the three test classes, and the value class.

+ +

cost.scala

+
package cost
+
+import language.implicitConversions
+import scala.language.experimental.macros
+
+trait Div[A] {
+  def div(lhs: A, rhs: A): A
+}
+
+object Div {
+  implicit val DivString = new Div[String] {
+    def div(lhs: String, rhs: String): String = lhs + "/" + rhs
+  }
+}
+
+class Test1 {
+  def gen[A](x: A, y: A)(implicit ev: Div[A]): A = ev.div(x, y)
+  def test: String = gen("foo", "bar")
+}
+
+object Test2 {
+  implicit class DivOps[A](lhs: A)(implicit ev: Div[A]) {
+    def /(rhs: A): A = macro machinist.DefaultOps.binop[A, A]
+  }
+}
+
+class Test2 {
+  import Test2.DivOps
+  def gen[A: Div](x: A, y: A): A = x / y
+  def test: String = gen("foo", "bar")
+}
+
+object Test3 {
+  implicit class DivOps[A](val lhs: A) extends AnyVal {
+    def /(rhs: A)(implicit ev: Div[A]): A = ev.div(lhs, rhs)
+  }
+}
+
+class Test3 {
+  import Test3.DivOps
+  def gen[A: Div](x: A, y: A): A = x / y
+  def test: String = gen("foo", "bar")
+}
+ +

build.sbt

+
name := "cost"
+
+scalaVersion := "2.11.2"
+
+resolvers += "bintray/non" at "http://dl.bintray.com/non/maven"
+
+libraryDependencies += "org.typelevel" %% "machinist" % "0.2.2"
+ +

Test1.out

+
Compiled from "cost.scala"
+public class cost.Test1 {
+  public <A extends java/lang/Object> A gen(A, A, cost.Div<A>);
+    Code:
+       0: aload_3       
+       1: aload_1       
+       2: aload_2       
+       3: invokeinterface #16,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+       8: areturn       
+
+  public java.lang.String test();
+    Code:
+       0: aload_0       
+       1: ldc           #27                 // String foo
+       3: ldc           #29                 // String bar
+       5: getstatic     #35                 // Field cost/Div$.MODULE$:Lcost/Div$;
+       8: invokevirtual #39                 // Method cost/Div$.DivString:()Lcost/Div;
+      11: invokevirtual #41                 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object;
+      14: checkcast     #43                 // class java/lang/String
+      17: areturn       
+
+  public cost.Test1();
+    Code:
+       0: aload_0       
+       1: invokespecial #47                 // Method java/lang/Object."<init>":()V
+       4: return        
+}
+ +

Test2.out

+
Compiled from "cost.scala"
+public class cost.Test2 {
+  public static <A extends java/lang/Object> cost.Test2$DivOps<A> DivOps(A, cost.Div<A>);
+    Code:
+       0: getstatic     #16                 // Field cost/Test2$.MODULE$:Lcost/Test2$;
+       3: aload_0       
+       4: aload_1       
+       5: invokevirtual #18                 // Method cost/Test2$.DivOps:(Ljava/lang/Object;Lcost/Div;)Lcost/Test2$DivOps;
+       8: areturn       
+
+  public <A extends java/lang/Object> A gen(A, A, cost.Div<A>);
+    Code:
+       0: aload_3       
+       1: aload_1       
+       2: aload_2       
+       3: invokeinterface #26,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+       8: areturn       
+
+  public java.lang.String test();
+    Code:
+       0: aload_0       
+       1: ldc           #37                 // String foo
+       3: ldc           #39                 // String bar
+       5: getstatic     #44                 // Field cost/Div$.MODULE$:Lcost/Div$;
+       8: invokevirtual #48                 // Method cost/Div$.DivString:()Lcost/Div;
+      11: invokevirtual #50                 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object;
+      14: checkcast     #52                 // class java/lang/String
+      17: areturn       
+
+  public cost.Test2();
+    Code:
+       0: aload_0       
+       1: invokespecial #56                 // Method java/lang/Object."<init>":()V
+       4: return        
+}
+ +

Test3.out

+
Compiled from "cost.scala"
+public class cost.Test3 {
+  public static java.lang.Object DivOps(java.lang.Object);
+    Code:
+       0: getstatic     #16                 // Field cost/Test3$.MODULE$:Lcost/Test3$;
+       3: aload_0       
+       4: invokevirtual #18                 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object;
+       7: areturn       
+
+  public <A extends java/lang/Object> A gen(A, A, cost.Div<A>);
+    Code:
+       0: getstatic     #25                 // Field cost/Test3$DivOps$.MODULE$:Lcost/Test3$DivOps$;
+       3: getstatic     #16                 // Field cost/Test3$.MODULE$:Lcost/Test3$;
+       6: aload_1       
+       7: invokevirtual #18                 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object;
+      10: aload_2       
+      11: aload_3       
+      12: invokevirtual #28                 // Method cost/Test3$DivOps$.$div$extension:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object;
+      15: areturn       
+
+  public java.lang.String test();
+    Code:
+       0: aload_0       
+       1: ldc           #39                 // String foo
+       3: ldc           #41                 // String bar
+       5: getstatic     #46                 // Field cost/Div$.MODULE$:Lcost/Div$;
+       8: invokevirtual #50                 // Method cost/Div$.DivString:()Lcost/Div;
+      11: invokevirtual #52                 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object;
+      14: checkcast     #54                 // class java/lang/String
+      17: areturn       
+
+  public cost.Test3();
+    Code:
+       0: aload_0       
+       1: invokespecial #58                 // Method java/lang/Object."<init>":()V
+       4: return        
+}
+ +

Test3.DivOps.out

+
Compiled from "cost.scala"
+public class cost.Test3$DivOps$ {
+  public static final cost.Test3$DivOps$ MODULE$;
+
+  public static {};
+    Code:
+       0: new           #2                  // class cost/Test3$DivOps$
+       3: invokespecial #12                 // Method "<init>":()V
+       6: return        
+
+  public final <A extends java/lang/Object> A $div$extension(A, A, cost.Div<A>);
+    Code:
+       0: aload_3       
+       1: aload_1       
+       2: aload_2       
+       3: invokeinterface #20,  3           // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+       8: areturn       
+
+  public final <A extends java/lang/Object> int hashCode$extension(A);
+    Code:
+       0: aload_1       
+       1: invokevirtual #32                 // Method java/lang/Object.hashCode:()I
+       4: ireturn       
+
+  public final <A extends java/lang/Object> boolean equals$extension(A, java.lang.Object);
+    Code:
+       0: aload_2       
+       1: astore_3      
+       2: aload_3       
+       3: instanceof    #36                 // class cost/Test3$DivOps
+       6: ifeq          15
+       9: iconst_1      
+      10: istore        4
+      12: goto          18
+      15: iconst_0      
+      16: istore        4
+      18: iload         4
+      20: ifeq          61
+      23: aload_2       
+      24: ifnonnull     31
+      27: aconst_null   
+      28: goto          38
+      31: aload_2       
+      32: checkcast     #36                 // class cost/Test3$DivOps
+      35: invokevirtual #40                 // Method cost/Test3$DivOps.lhs:()Ljava/lang/Object;
+      38: astore        5
+      40: aload_1       
+      41: aload         5
+      43: invokestatic  #45                 // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
+      46: ifeq          53
+      49: iconst_1      
+      50: goto          54
+      53: iconst_0      
+      54: ifeq          61
+      57: iconst_1      
+      58: goto          62
+      61: iconst_0      
+      62: ireturn       
+
+  public cost.Test3$DivOps$();
+    Code:
+       0: aload_0       
+       1: invokespecial #47                 // Method java/lang/Object."<init>":()V
+       4: aload_0       
+       5: putstatic     #49                 // Field MODULE$:Lcost/Test3$DivOps$;
+       8: return        
+}
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Erik Osheim + + +
+ Erik Osheim is one of the founders of Typelevel, and maintains several Scala libraries including Cats, Spire, and others. He hacks Scala for a living at Stripe, and is committed to having his cake and eating it too when it comes to functional programming. Besides programming he spends time playing music, drinking tea, and cycling around Providence, Rhode Island. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/mapping-sets.html b/blog/mapping-sets.html new file mode 100644 index 00000000..b8b444d9 --- /dev/null +++ b/blog/mapping-sets.html @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + How can we map a Set? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

How can we map a Set?

+ + + technical + +
+
+
+
+

How can we map a Set?

+

Scalaz used to have a scalaz.Functor for scala.collection.Set but + it was eventually removed + because it relied on + Any's == method. You + can read more about why Functor[Set] is a bad idea at + Fake Theorems for Free.

+

If Set had been truly parametric, we wouldn't have been able to + define a Functor in the first place. Luckily, a truly parametric Set + has recently been added to Scalaz as scalaz.ISet, with preliminary + benchmarks also showing some nice performance improvements. I highly + recommend using ISet whenever you can!

+

Now we can see the problem more clearly; the type of map on ISet + is too restrictive to be used inside of a Functor because of the + scalaz.Order constraint:

+
def map[B: Order](f: A => B): ISet[B]
+

And it might seem like we've lost something useful by not having a + Functor available. For example, we can't write the following:

+
val nes = OneAnd("2014-05-01", ISet.fromList("2014-06-01" :: "2014-06-22" :: Nil)) // a non-empty Set
+val OneAnd(h, t) = nes.map(parseDate)
+

Which is because the map function on scalaz.OneAnd requires a + scalaz.Functor for the F[_] type parameter, which is ISet in the + above example.

+

But we have a solution! It's called + Coyoneda + (also known as the Free Functor) and it'll hopefully be able to + demonstrate why not having Functor[ISet] available has no + fundamental, practical consequences.

+

Coyoneda + can be defined in Scala + like so:

+
trait Coyoneda[F[_], A] {
+  type I
+  def k: I => A
+  def fi: F[I]
+}
+

There are just three parts to it:

+
    +
  1. I - an existential type
  2. +
  3. k - a mapping from I to A
  4. +
  5. fi - a value of F[I]
  6. +
+

We can create a couple of functions to help with constructing a + Coyoneda value:

+
def apply[F[_], A, B](fa: F[A])(_k: A => B): Coyoneda[F, B] { type I = A } =
+  new Coyoneda[F, B] {
+    type I = A
+    val k = _k
+    val fi = fa
+  }
+
+def lift[F[_], A](fa: F[A]): Coyoneda[F, A] = Coyoneda(fa)(identity[A])
+

The constructors allow any type constructor to become a Coyoneda value:

+
val s: Coyoneda[ISet, Int] = Coyoneda.lift(ISet.fromList(1 :: 2 :: 3 :: Nil))
+

Now here's the special part; we can define a Functor for all + Coyoneda values:

+
implicit def coyonedaFunctor[F[_]]: Functor[({type λ[α] = Coyoneda[F, α]})#λ] =
+  new Functor[({type λ[α] = Coyoneda[F,α]})#λ] {
+    def map[A, B](ya: Coyoneda[F, A])(f: A => B) = Coyoneda(ya.fi)(f compose ya.k)
+  }
+

What's interesting is that the F[_] type does not have to have a + Functor defined for the Coyoneda to be mapped!

+

Let's use this to try out our original example. We'll define a type + alias to make things a bit cleaner:

+
type ISetF[A] = Coyoneda[ISet, A]
+

And we can use this new type instead of a plain ISet:

+
// Scala has a really hard time with inference here, so we have to help it out.
+val functor = OneAnd.oneAndFunctor[ISetF](Coyoneda.coyonedaFunctor[ISet])
+import functor.functorSyntax._
+
+val nes = OneAnd[ISetF, String]("2014-05-01", Coyoneda.lift(ISet.fromList("2014-06-01" :: "2014-06-22" :: Nil)))
+val OneAnd(h, t) = nes.map(parseDate)
+

So we've been able to map the Coyoneda! But how do we do something + useful with it?

+

We couldn't define a Functor because it needs scalaz.Order on the + output type, but we can use the map method directly on ISet. We + can use that function by running the Coyoneda like so:

+
// Converts ISetF back to an ISet, using ISet#map with the Order constraint
+val s = t.fi.map(t.k).insert(h)
+

And we're done!

+

We've been able to use Coyoneda to treat an ISet as a Functor, + even though its map function is too constrained to have one defined + directly. This same technique applies to scala.collection.Set and + any other type-constructor which would otherwise require a + restricted Functor. I + hope this has demonstrated that Functor[Set] not existing has no + practical consequences, other than scalac not being as good at + type-inference.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Brian McKenna + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/meetup-lausanne-2025-08-22.html b/blog/meetup-lausanne-2025-08-22.html new file mode 100644 index 00000000..53a5945b --- /dev/null +++ b/blog/meetup-lausanne-2025-08-22.html @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Meetup Lausanne + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Meetup Lausanne

+ + + events + +
+
+
+
+

Typelevel Meetup Lausanne

+
+

"lauvax" by harmishhk is licensed under CC BY-SA 2.0.

+ +

About the Meetup

+

Join the Typelevel community at an in-person meetup on EPFL campus in Lausanne, organized by Arman Bilge and Antonio Jimenez. This meetup is open to (aspiring) Typelevel users and contributors and anyone curious to learn more about functional programming in Scala, no matter their prior experience.

+

During this meetup you can expect:

+
    +
  • chat/q&a about functional programming and Typelevel libraries
  • +
  • a small tutorial on Cats Effect, FS2, and Calico
  • +
  • a group activity building widgets with Calico
  • +
  • lunch
  • +
+

More details and registration are available on the event page. All participants and organizers must abide by the Typelevel Code of Conduct.

+
+
+ +
+ + + + + + diff --git a/blog/method-equiv.html b/blog/method-equiv.html new file mode 100644 index 00000000..26a7a684 --- /dev/null +++ b/blog/method-equiv.html @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + When are two methods alike? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

When are two methods alike?

+ + + technical + +
+
+
+
+

When are two methods alike?

+

This is the second of a series of articles on “Type Parameters and + Type Members”. If you haven’t yet, you should + start at the beginning, + which introduces code we refer to throughout this article without + further ado.

+

In the last part, + we just saw two method types that, though different, are effectively + the same: those of plengthT and plengthE. We have rules for + deciding when an existential parameter can be lifted into a method + type parameter—or a method type parameter lowered to an + existential—but there are other pairs of method types I want to + explore that are the same, or very close. So let’s talk about how we + determine this equivalence.

+

A method R is more general than or as general as Q if Q may be + implemented by only making a call to R, passing along the arguments. + By more general, we mean R can be invoked in all the situations that + Q can be invoked in, and more besides. Let us call the result of + this test R<:mQ R <:_m Q (where <:m <:_m is pronounced “party duck”); if + the test of Q making a call to R fails, then ¬(R<:mQ) \neg(R <:_m Q) .

+

If Q<:mR Q <:_m R and R<:mQ R <:_m Q , then the two method types are + equivalent; that is, neither has more expressive power than the + other, since each can be implemented merely by invoking the other and + doing nothing else. We write this as QmR Q \equiv_m R . Likewise, if + R<:mQ R <:_m Q and ¬(Q<:mR) \neg(Q <:_m R) , that is, Q can be written by + calling R, but not vice versa, then R is strictly more general + than Q, or R<mQ R <_m Q .

+

What the concrete method—the one actually doing stuff, not invoking + the other one—does is irrelevant, for the purposes of this test, + because this is about types. That matters because sometimes, in + Scala, as in Java, the body will compile in one of the methods, but + not the other. Let’s see an example that doesn’t compile.

+
import scala.collection.mutable.ArrayBuffer
+
+def copyToZero(xs: ArrayBuffer[_]): Unit =
+  xs += xs(0)
+
+TmTp2.scala:9: type mismatch;
+ found   : (some other)_$1(in value xs)
+ required: _$1(in value xs)
+    xs += xs(0)
+            ^
+

Likewise, the Java version has a similar problem, though the error + message doesn’t give as good a hint as to what’s going on.

+
import java.util.List;
+
+void copyToZero(final List<?> xs) {
+    xs.add(xs.get(0));
+}
+
+TmTp2.java:11:  error: no suitable method found for add(CAP#1)
+        xs.add(xs.get(0));
+          ^
+

Luckily, in both Java and Scala, we have an equivalent method type, + from lifting the existential (misleadingly called wildcard in Java + terminology) to a method type parameter.

+

We can apply this transformation to put the method implementation + somewhere it will compile.

+
def copyToZeroE(xs: ArrayBuffer[_]): Unit =
+  copyToZeroP(xs)
+
+private def copyToZeroP[T](xs: ArrayBuffer[T]): Unit =
+  xs += xs(0)
+

Similarly, in Java,

+
void copyToZeroE(final List<?> xs) {
+    copyToZeroP(xs);
+}
+
+<T> void copyToZeroP(final List<T> xs) {
+    final T zv = xs.get(0);
+    xs.add(zv);
+}
+

The last gives a hint as to what’s going on, both here and in the + compiler errors above: in copyToZeroP’s body, the list element type + has a name, T; we can use the name to create variables, and the + compiler can rely on the name as well. The compiler, ideally, + shouldn’t care about whether the name can be written, but that one of + the above compiles and the other doesn’t is telling.

+

If you were to define a variable to hold the result of getting the + first element in the list in either version of copyToZeroE, how + would you do that? In Java, the reason this doesn’t work is + straightforward: you would have to declare the variable to be of type + Object, but that type isn’t specific enough to allow the variable to + be used as an argument to xs.add.

+

Scala’s type-inferred variables don’t help here; Scala considers the + existential type to be scoped to xs, and makes the definition of + zv independent of xs by breaking the type relationship, and + crushing the inferred type of zv to Any.

+
def copyToZeroE(xs: ArrayBuffer[_]): Unit = {
+  val zv = xs(0)
+  xs += zv
+}
+
+TmTp2.scala:19: type mismatch;
+ found   : zv.type (with underlying type Any)
+ required: _$1
+    xs += zv
+          ^
+

When we call the type-parameterized variant to implement the + existential variant, with the real implementation residing in the + former, we are just helping the compiler along by using the equivalent + method type; in the simpler case of the former, both scalac and + javac manage to infer that the type T should be the (otherwise + unspeakable) existential. Method equivalence and generality make it + possible to write methods, safely, that could not be written + directly.

+ +

Why are existentials harder to think about?

+

I think we, as humans, may have even more difficulty with the lack of + names for existentials than the compilers do. The name “unspeakable”, + which I have borrowed from Jon Skeet’s C# in Depth, is telling: even + in our heads, our thought processes are shaped by language. We tame + the mathematics of programming with symbols, with names. Existentials + and their “unspeakable” names rob us of the tools to talk about them, + to think about them.

+

Java has done its practitioners two great disservices here. One: by + calling its existentials “wildcards”. They are not “wildcards”, in + any commonly or uncommonly understood sense. If you suppose your + preexisting notions of “wildcards” to apply to these much more exotic + creatures, you will confidently stroll into the darkness until you + trip and fall off a cliff. They are only superficially “wildcards”. + The effect of this sorry attempt at avoiding new terminology is + chiefly to cheat Java programmers out of learning what’s really going + on. (We will explore some of this more exotic behavior + in a later post.)

+

Two: by + encouraging use of existential signatures + like mdropFirstE over parameterized versions like mdropFirstT that + do not require the same kind of mental gymnastics.

+

For lifting these type parameters is how we can reclaim the power we + lost in the debacle of the unspeakable names. We name them, and in so + doing can once more talk and think about them without exhausting + ourselves by gesticulating wildly, comforting ourselves with + fairytales of “wildcards”. Because in parameter lifting, we have + found a true analogy.

+ +

When are two methods less alike?

+

Now, let’s examine another pair of methods, and apply our test to + them.

+

Let’s say we want to write the equivalent of this method for MList.

+
def pdropFirst[T](xs: PList[T]): PList[T] =
+  xs match {
+    case PNil() => PNil()
+    case PCons(_, t) => t
+  }
+

According to the PListMList conversion rules given + in the previous article, + section “Why all the {type T = ...}?”, the equivalent for MList + should be

+
def mdropFirstT[T0](xs: MList {type T = T0})
+  : MList {type T = T0} =
+  xs.uncons match {
+    case None => MNil()
+    case Some(c) => c.tail
+  }
+

Let us try to drop the refinements. That seems to compile:

+
def mdropFirstE(xs: MList): MList =
+  xs.uncons match {
+    case None => MNil()
+    case Some(c) => c.tail
+  }
+

It certainly looks nicer. However, while mdropFirstE can be + implemented by calling mdropFirstT, passing the type parameter + xs.T, the opposite is not true; mdropFirstT <m <_m mdropFirstE, + or, mdropFirstT is strictly more general.

+

In this case, the reason is that mdropFirstE fails to relate the + argument’s T to the result’s T; you could implement mdropFirstE + as follows:

+
def mdropFirstE[T0](xs: MList): MList =
+  MCons[Int](42, MNil())
+

The stronger type of mdropFirstT forbids such shenanigans. However, + I can just tell you that largely because I’m already comfortable with + existentials; how could you figure that out if you’re just starting + out with these tools? You don’t have to; the beauty of the + equivalence test is that you can apply it mechanically. Knowing + nothing about the mechanics of the parameterization and existentialism + of the types involved, you can work out with the equivalence test + that mdropFirstT <m <_m mdropFirstE, and therefore, that you can’t + get away with simply dropping the refinements.

+ +

Method likeness and subtyping, all alike

+

If you know what the symbol <: means in Scala, or perhaps you’ve + read + SLS §3.5 “Relations between types”, + you might think, “gosh, method equivalence and generality look awfully + familiar.”

+

Indeed, the thing we’re talking about is very much like subtyping and + type equality! In fact, every type-equal pair of methods M₁ and + M₂ also pass our method equivalence test, and every pair of methods + M₃ and M₄ where M3<:M4 M_3 <: M_4 passes our M₄-calls-M₃ test. + So M1M2 M_1 \equiv M_2 implies M1mM2 M_1 \equiv_m M_2 , and + M3<:M4 M_3 <: M_4 implies M3<:mM4 M_3 <:_m M_4 .

+

We even follow many of the same rules as the type relations. We have + transitivity: if M₁ can call M₂ to implement itself, and M₂ can + call M₃ to implement itself, obviously we can snap the pointer and + have M₁ call M₃ directly. Likewise, every method type is + equivalent to itself: reflexivity. Likewise, if a method M₁ is + strictly more general than M₂, obviously M₂ cannot be strictly + more general than M₁: antisymmetricity. And we even copy the + relationship between ≡ and <: themselves: just as T1T2 T_1 \equiv T_2 + implies T1<:T2 T_1 <: T_2 , so RmQ R \equiv_m Q implies R<:mQ R <:_m Q .

+

Scala doesn’t understand the notion of method equivalence we’ve + defined above, though. So you can’t, say, implement an abstract + method in a subclass using an equivalent or more general form, at + least directly; you have to override the Scala way, and call the + alternative form yourself, if that’s what you want.

+

I do confess to one oddity in my terminology: the method that has + more specific type is the more general method. I hope the example + of mdropFirstT <:m <:_m mdropFirstE justifies my choice. + mdropFirstT has more specific type, and rejects more + implementations, such as the one that returns a list with 42 in it + above. Thus, it has fewer implementations, in the same way that more + specific types have fewer values inhabiting them. But it can be used + in more circumstances, so it is “more general”. The generality in + terms of when a method can be used is directly proportional to the + specificity of its type.

+ +

Java’s edge of insanity

+

Now we have enough power to demonstrate that Scala’s integration with + Java generics is faulty. Or, more fairly, that Java’s generics are + faulty.

+

Consider this method type, in Scala:

+
def goshWhatIsThis[T](t: T): T
+

This is a pretty specific method type; there are not too many + implementations. Of course you can always perform a side effect; we + don’t track that in Scala’s type system. But what can it return? + Just t.

+

Specifically, you can’t return null:

+
TmTp2.scala:36: type mismatch;
+ found   : Null(null)
+ required: T
+  def goshWhatIsThis[T](t: T): T = null
+                                   ^
+

Well now, let’s convert this type to Java:

+
public static <T> T holdOnNow(T t) {
+    return null;
+}
+

We got away with that! And, indeed, we can call holdOnNow to + implement goshWhatIsThis, and vice versa; they’re equivalent. But + the type says we can’t return null!

+

The problem is that Java adds an implicit upper bound, because it + assumes generic type parameters can only have class types chosen for + them; in Scala terms, [T <: AnyRef]. If we encode this constraint + in Scala, Scala gives us the correct error.

+
def holdOnNow[T <: AnyRef](t: T): T = TmTp2.holdOnNow(t)
+
+def goshWhatIsThis[T](t: T): T = holdOnNow(t)
+
+TmTp2.scala:38: inferred type arguments [T] do not conform
+⤹ to method holdOnNow's type parameter bounds [T <: AnyRef]
+  def goshWhatIsThis[T](t: T): T = holdOnNow(t)
+                                   ^
+

This is forgivable on Scala’s part, because it’d be annoying to add + <: AnyRef to your generic methods just because you called some Java + code and it’s probably going to work out fine. I blame null, and + while I’m at it, I blame Object having any methods at all, too. + We’d be better off without these bad features.

+

In + the next part, “What happens when I forget a refinement?”, + we’ll talk about what happens when you forget refinements for things + like MList, and how you can avoid that while simplifying your + type-member-binding code.

+

This article was tested with Scala 2.11.7 and Java 1.8.0_45.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/minicheck.html b/blog/minicheck.html new file mode 100644 index 00000000..022f2065 --- /dev/null +++ b/blog/minicheck.html @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Let's build ourselves a small ScalaCheck + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Let's build ourselves a small ScalaCheck

+ + + technical + +
+
+
+
+

Let's build ourselves a small ScalaCheck

+

ScalaCheck is a well-known property-based testing library, based on ideas from Haskell's QuickCheck. + It is also a Typelevel project. + In this post, I'd like to show some of the underlying mechanisms, stripped down to the bare minimum.

+

Testing with properties is well-understood in academia and widely used in parts of the industry – namely the parts which embrace functional programming. + However, the design space of property-testing libraries is rather large. + I think it is high time to talk about various tradeoffs done in libraries. + Here, I'd like to contribute by implementing a ScalaCheck clone from scratch using a very similar design and explaining the design choices along the way.

+

This is not an introduction to property testing. + However, it can be read as a guide to implementation ideas. + QuickCheck, ScalaCheck and the like are nice examples of functional library design, but their internals are often obscured by technicalities. + I hope that by clearing up some of the concepts it will become easier to read their code and perhaps designing your own property-testing library.

+ +

The first design decision

+

The basic point of a property testing library is providing an interface looking roughly like this:

+
class Prop {
+  def check(): Unit = ???
+}
+
+object Prop {
+  def forAll[A](prop: A => Boolean): Prop = ???
+}
+

Now, you can use that in your test code:

+
Prop.forAll { (x: Int) =>
+  x == x
+}
+

This expresses that you have a property which is parameterized on a single integer number. + Hence, the library must somehow provide these integer numbers. + The original Haskell QuickCheck, ScalaCheck and many other libraries use a random generator for this. + This comes with a number of advantages:

+
    +
  • It is relatively simple and efficient to implement.
  • +
  • Random number generators compose exceedingly well.
  • +
  • The confidence in the tests can be increased by just generating more inputs.
  • +
  • Depending on the random distributions of the generators used, you have chances that both “exotic” and “common” inputs are covered.
  • +
  • In practice, it turns out that random generators are decent at finding edge cases.
  • +
+

But it is also not without problems:

+
    +
  • For more complex inputs, the default generators are basically useless, because they will produce invalid input most of the time.
  • +
  • Filtering random values before feeding them into the property can dramatically slow down the whole process.
  • +
  • By default, it is non-deterministic (but there are remedies available).
  • +
  • Generation of random functions to be used as inputs for higher-order properties is quite round-about.
  • +
+

Of course, there are other possible design choices:

+
    +
  • SmallCheck instead enumerates all values up to a certain size. + For example, you can specify that you want to test some function over integer lists with all lists up to size 5, containing all integers between -5 and +5. + In some situations, namely when your input is finite, you can even exhaustively check all inputs, which is equivalent to a proof that your program is correct. + The disadvantage is that even for small sizes, the input space may explode exponentially or worse (e.g. when generating lists of lists).
  • +
  • Isabelle Quickcheck supports multiple modes, including narrowing, which is a form of symbolically exploring the search space. + This is based on Haskell's Lazy SmallCheck (see also the paper by Runciman et.al.). + The basic idea is that we can try to evaluate properties with partially-defined inputs and refine them on demand.
  • +
+

For this post, we're assuming that random generation is a given.

+ +

The second design decision

+
Do we want to do this purely or poorly?
+

Of course, this motto is tongue-in-cheek. + Just because something isn't pure doesn't mean that it is poor.

+

To understand the design space here, let's focus on the smallest building block: A primitive random generator. + There are two possible ways to model this. + The mutable way is what Java, Scala and many other languages offer in their libraries:

+
trait Random {
+  def nextInt(min: Int, max: Int): Int
+  def nextFloat(): Float
+  def nextItem[A](pool: List[A]): A
+}
+

By looking at the types alone, we can already see that two subsequent calls of nextInt will produce different results; the interface is thus impure.

+

The pure way is to make the internal state (also known as “seed” in the context of random generators) explicit:

+
trait Seed {
+  def nextInt(min: Int, max: Int): (Int, Seed)
+  def nextFloat: (Float, Seed)
+  def nextItem[A](pool: List[A]): (A, Seed)
+}
+
+object Seed {
+  def init(): Seed = ???
+}
+

Because this is difficult to actually use (don't mix up the Seed instances and use them twice!), one would wrap this into a state monad:

+
class Random[A](private val op: Seed => (A, Seed)) { self =>
+  def run(): A = op(Seed.init())._1
+
+  def map[B](f: A => B): Random[B] =
+    new Random[B]({ seed0 =>
+      val (a, seed1) = self.op(seed0)
+      (f(a), seed1)
+    })
+
+  def flatMap[B](f: A => Random[B]): Random[B] =
+    new Random[B]({ seed0 =>
+      val (a, seed1) = self.op(seed0)
+      f(a).op(seed1)
+    })
+
+  override def toString: String = "<random>"
+}
+
+object Random {
+  def int(min: Int, max: Int): Random[Int] = new Random(_.nextInt(min, max))
+  val float: Random[Float] = new Random(_.nextFloat)
+}
+

Now we can use Scala's for comprehensions:

+
for {
+  x <- Random.int(-5, 5)
+  y <- Random.int(-3, 3)
+} yield (x, y)
+// res2: Random[(Int, Int)] = <random>
+

The tradeoffs here are the usual when we're talking about functional programming in Scala: Reasoning ability, convenience, performance, … + In the pure case, there are also multiple other possible encodings, including free monads. + Luckily, this blog covers that topic in another post.

+

How do other libraries fare here?

+
    +
  • ScalaCheck up to 1.12.x uses a mutable random number generator; namely, scala.util.Random.
  • +
  • ScalaCheck 1.13.x+ uses its own, immutable implementation.
  • +
  • Another Scala library for property testing, scalaprops, does not. + I'm not familiar with it, but as far as I can tell from the sources, it's similar to the Seed trait from above, and there is also an additional state-monadic layer on top of it.
  • +
  • In QuickCheck, the encoding seems strange at first. + They use a primitive generator which looks a lot like Seed, but they don't use the updated seed. + Instead, their approach is via an additional primitive split of type Seed => (Seed, Seed), which gets used to “distribute” randomness during composition (see the paper by Claessen & Pałka about the theory behind that). + It is worth noting that Java 8 introduced a SplittableRandom class.
  • +
+

For this post, we're assuming that mutable state is a given. + We'll use scala.util.Random (because it's readily available) in a similar fashion to ScalaCheck 1.12.x.

+ +

The third design decision

+

Asynchronous programming is all the rage these days. + This means that many functions will not return plain values of type A, but rather Future[A], Task[A] or some other similar type. + For our testing framework, this poses a challenge: + If our properties call such asynchronous functions, the framework needs to know how to deal with a lot of Future[Boolean] values. + On the JVM, although not ideal, we could fall back to blocking on the result and proceed as usual. + On Scala.js, this won't fly, because you just can't block in JavaScript.

+

Most general-purpose testing frameworks, like Specs2, have a story about this, enabling asynchronous checking of assertions.

+

In theory, it's not a problem to support this in a property testing library. + But in practice, there are some complications:

+
    +
  • Has the library been designed that way? If not, can we change it to support it? + This is a real problem: It took quite some time and some significant refactorings to support Futures in ScalaTest.
  • +
  • Should random generators also return Future values? + We can easily imagine wanting to draw from a pool of inputs stemming from a database, or possibly to get better randomness from random.org. + (The latter is a joke.)
  • +
  • What async type constructor should we support? + The built-in one? + Monix' Task? + fs2's Task? + All of them?
  • +
+

If in the first design decisions we had chosen exhaustive generators, this problem would be even tougher, because designing a correct effectful stream type (of all possible inputs) is not trivial.

+

For this post, we're assuming that we're only interested in synchronous properties, or can always block. + However, I'd like to add, I'd probably try to incorporate async properties right from the start if I were to implement a testing library from scratch.

+

What about the existing libraries?

+
    +
  • ScalaCheck itself does not support asynchronous properties.
  • +
  • In SmallCheck, both generators and properties may be monadic.
  • +
  • QuickCheck supports arbitrary I/O actions in a property via a function called morallyDubiosIOProperty (nowadays just ioProperty). + But there is also more advanced support for monadic testing.
  • +
+ +

The fourth design decision

+

Let's summarize what we have so far:

+
    +
  1. randomly generated inputs
  2. +
  3. ... using a stateful primitive generator
  4. +
  5. synchronous properties
  6. +
+

Now, I'd like to talk about how to “package” random generators. + Earlier, we've only seen random integer and floating-point numbers, but of course, we want something more complex, including custom data structures. + It is convenient to abstract over this and specify the concept of a generator for type A. + The idea is to make a generator for a type as “general” as possible and then provide combinators to compose them.

+
import scala.util.Random
+
+trait Gen[T] {
+  def generate(rnd: Random): T
+}
+
+object Gen {
+  val int: Gen[Int] = new Gen[Int] {
+    def generate(rnd: Random): Int =
+      rnd.nextInt()
+  }
+}
+

An obvious combinator is a generator for tuples:

+
def zip[T, U](genT: Gen[T], genU: Gen[U]): Gen[(T, U)] = new Gen[(T, U)] {
+  def generate(rnd: Random): (T, U) =
+    (genT.generate(rnd), genU.generate(rnd))
+}
+

But we still have a problem: + There is currently no way to talk about the size of the generated inputs. + Let's say we want to check an expensive algorithm over lists, for example with a complexity of O(n3) \mathcal O(n^3) over lists. + A naive implemenation of a list generator would take a a random size, and then give you some generator for lists. + The problem arises at the use site: Whenver you want to change the size of the generated inputs, you need to change the expression constructing the generator.

+

But we'd like to do better here:

+

For this post, there should be a way to specify a maximum size of generated values, together with a way to influence that size in the tests without having to modify the generators.

+

Here's how we can do that:

+
import scala.util.Random
+
+trait Gen[T] {
+  def generate(size: Int, rnd: Random): T
+
+  override def toString: String = "<gen>"
+}
+
+object Gen {
+
+  val int: Gen[Int] = new Gen[Int] {
+    def generate(size: Int, rnd: Random): Int = {
+      val range = size * 2 + 1
+      rnd.nextInt(range) - size
+    }
+  }
+
+  def list[A](genA: Gen[A]): Gen[List[A]] = new Gen[List[A]] {
+    def generate(size: Int, rnd: Random): List[A] = {
+      val length = rnd.nextInt(size + 1)
+      List.fill(length)(genA.generate(size, rnd))
+    }
+  }
+
+}
+

We can now check this (note that for the purpose of this post we'll be using fixed seeds):

+
def printSample[T](genT: Gen[T], size: Int, count: Int = 10): Unit = {
+  val rnd = new Random(0)
+  for (i <- 0 until size)
+    println(genT.generate(size, rnd))
+}
+
scala> printSample(Gen.int, 10)
+2
+6
+-6
+-8
+1
+4
+-8
+5
+-4
+-8
+
+scala> printSample(Gen.int, 3)
+2
+-1
+1
+
+scala> printSample(Gen.list(Gen.int), 10)
+List()
+List(-6, -8, 1, 4, -8, 5)
+List(-8, -2)
+List(4, 6)
+List(4, 0)
+List(10, 4, -9, -7, 7)
+List(-8, 6, -4, 9, -1, 10, 4, 7, -8)
+List(1, 7, -7, 4, 0, 5, 4, 9, 7, 4)
+List(5, 9, -3, 3, -10)
+List()
+
+scala> printSample(Gen.list(Gen.int), 3)
+List(-1, 1)
+List(1, -3)
+List(-2, 3)
+

That's already pretty cool. + But there's another hidden design decision here: + We're using the same size on all sub-elements in the generated thing. + For example, in Gen.list, we're just passing the size through to the child generator.

+

SmallCheck does that differently: The “size” is defined to be the total number of constructors in the generated value. + For integer numbers, the “number of constructors” is basically the number itself. + For example, the value List(1, 2) has size 2 2 in our framework (length of the list), but size 1+2+2=5 1 + 2 + 2 = 5 in SmallCheck (roughly: size of all elements plus length of list).

+

Of course, our design decision might mean that stuff grows too fast. + The explicit size parameter can be used to alleviate that, especially for writing recursive generators:

+
def recList[T](genT: Gen[T]): Gen[List[T]] = new Gen[List[T]] {
+  // extremely stupid implementation, don't use it
+  def generate(size: Int, rnd: Random): List[T] =
+    if (rnd.nextInt(size + 1) > 0)
+      genT.generate(size, rnd) :: recList(genT).generate(size - 1, rnd)
+    else
+      Nil
+}
+// recList: [T](genT: Gen[T])Gen[List[T]]
+
+printSample(recList(Gen.int), 10)
+// List()
+// List(-6, 1, -6)
+// List(-8, 8, 4, 7, 0, 5, -4)
+// List(-8)
+// List(9, -3, 4)
+// List(1, 3, -7, -2, -3, 0, -3, 3)
+// List()
+// List(10, 5, -8, -4, -5, 4, -1)
+// List(-5, 9, 7)
+// List(-8)
+

We can also provide a combinator for this:

+
def resize[T](genT: Gen[T], newSize: Int): Gen[T] = new Gen[T] {
+  def generate(size: Int, rnd: Random): T =
+    genT.generate(newSize, rnd)
+}
+

That one is useful because in reality ScalaCheck's generate method takes some more parameters than just the size. + Some readers might be reminded that this is just the reader monad and its local combinator in disguise.

+ +

Some sugar

+

In order to make these generators nicely composable, we can leverage for comprehensions. + We just need to implement map, flatMap and withFilter:

+
import scala.util.Random
+
+trait Gen[T] { self =>
+  def generate(size: Int, rnd: Random): T
+
+  // Generate a value and then apply a function to it
+  def map[U](f: T => U): Gen[U] = new Gen[U] {
+    def generate(size: Int, rnd: Random): U =
+      f(self.generate(size, rnd))
+  }
+
+  // Generate a value and then use it to produce a new generator
+  def flatMap[U](f: T => Gen[U]): Gen[U] = new Gen[U] {
+    def generate(size: Int, rnd: Random): U =
+      f(self.generate(size, rnd)).generate(size, rnd)
+  }
+
+  // Repeatedly generate values until one passes the check
+  // (We would usually call this `filter`, but Scala requires us to
+  // call it `withFilter` in order to be used in `for` comprehensions)
+  def withFilter(p: T => Boolean): Gen[T] = new Gen[T] {
+    def generate(size: Int, rnd: Random): T = {
+      val candidate = self.generate(size, rnd)
+      if (p(candidate))
+        candidate
+      else // try again
+        generate(size, rnd)
+    }
+  }
+
+  override def toString: String = "<gen>"
+}
+
+object Gen {
+
+  // unchanged from above
+
+  val int: Gen[Int] = new Gen[Int] {
+    def generate(size: Int, rnd: Random): Int = {
+      val range = size * 2 + 1
+      rnd.nextInt(range) - size
+    }
+  }
+
+  def list[A](genA: Gen[A]): Gen[List[A]] = new Gen[List[A]] {
+    def generate(size: Int, rnd: Random): List[A] = {
+      val length = rnd.nextInt(size + 1)
+      List.fill(length)(genA.generate(size, rnd))
+    }
+  }
+
+}
+

Look how simple composition is now:

+
case class Frac(numerator: Int, denominator: Int)
+// defined class Frac
+
+val fracGen: Gen[Frac] =
+  for {
+    num <- Gen.int
+    den <- Gen.int
+    if den != 0
+  } yield Frac(num, den)
+// fracGen: Gen[Frac] = <gen>
+
+printSample(fracGen, 10)
+// Frac(2,6)
+// Frac(-6,-8)
+// Frac(1,4)
+// Frac(-8,5)
+// Frac(-4,-8)
+// Frac(-2,-8)
+// Frac(4,6)
+// Frac(1,4)
+// Frac(0,-10)
+// Frac(10,4)
+

And we can even read the construction nicely: “First draw a numerator, then draw a denominator, then check that the denominator is not zero, then construct a fraction.” + However, we need to be cautious with the filtering. + If you look closely at the implementation of withFilter, you can see that there is potential for an infinite loop. + For example, when you pass in the filter _ => false. + It will just keep generating values and then discard them. + How do existing frameworks alleviate this?

+
    +
  • QuickCheck has two filter combinators: one that returns Gen[A] as above, and one that return Gen[Option[A]]. + The latter uses a number of tries and if they all fail, terminates and returns None. + The former uses the latter, but keeps increasing the size parameter. + Of course, this might not terminate.
  • +
  • ScalaCheck's filter method returns Gen[A], but the possibility of failure is encoded in the return type of its equivalent of the generate method, which always returns Option[T]. + But there is also a combinator which retries until it finds a valid input, called retryUntil.
  • +
+

As a side note: Gen as it is right now is definitely not a valid monad, because it internally relies on mutable state. + But in my opinion, it is still justified to offer the map and flatMap methods, but don't give a Monad instance. + This prevents you from shoving Gen into functions which expect lawful monads.

+

It's still tedious to having to construct these generators by hand. + Both QuickCheck and ScalaCheck introduce a thin layer atop generators, called Arbitrary. + This is just a type class which contains a generator, nothing more. + Here's how it would look like in Scala:

+
trait Arbitrary[T] {
+  def gen: Gen[T]
+}
+
+// in practice we would put that into the companion object
+//object Arbitrary {
+
+  implicit val arbitraryInt: Arbitrary[Int] = new Arbitrary[Int] {
+    def gen = Gen.int
+  }
+
+//}
+

Based on this definition, ScalaCheck provides a lot of pre-defined instances for all sorts of types. + For your custom types, the idea is that you define a low-level generator and wrap it into an implicit Arbitrary. + Then, in your tests, you just use the implicitly provided generator, and avoid to drop down to constructing them manually.

+

The purpose of the additional layer is explained easily: It is common to have multiple Gen[T] for the same T depending on which context it is needed in. + But there should only be one Arbitrary[T] for each T. + For example, you might have Gen[Int] for positive and negative integers, but you only have a single Arbitrary[Int] which covers all integers. + You use the latter when you actually need to supply an integer to your property, and the former to construct more complex generators, like for Frac above.

+ +

The fifth design decision

+

This is where everything really comes together. + We're now looking at how to use Gen to implement the desired forAll function we've seen early in the introduction of the post, and how that is related to the Prop type I didn't define. + I'll readily admit that the following isn't really a design decision per se, because we'll be guided by the presence of type classes in Scala. + Still, one could reasonably structure this differently, and in fact, the design of the Prop type in e.g. QuickCheck is much more complex than what you'll see.

+

The rest of this post will now depart from the way it's done in ScalaCheck, although the ideas are still similar. + Instead, I'll try to show a simplified version without introducing complications required to make it work nicely.

+

Let's start with the concept of a property. + A property is something that we can run and which returns a result. + The result should ideally be something like a boolean: Either the property holds or it doesn't. + But one of the main features of any property testing library is that it will return a counterexample for the inputs where the property doesn't hold. + Hence, we need to store this counterexample in the failure case. + In practice, the result type would be much richer, with attached labels, reasons, expectations, counters, ... and more diagnostic fields.

+
sealed trait Result
+case object Success extends Result
+final case class Failure(counterexample: List[String]) extends Result
+
+object Result {
+  def fromBoolean(b: Boolean): Result =
+    if (b)
+      Success
+    else
+      // if it's false, it's false; no input has been produced,
+      // so the counterexample is empty
+      Failure(Nil)
+}
+

You'll note that I've used List[String] here, because in the end we only want to print the counterexample on the console. + ScalaCheck has a dedicated Pretty type for that. + We could do even more fancy things here if we wanted to, but let's keep it simple.

+

Now we define the Prop type:

+
trait Prop {
+  def run(size: Int, rnd: Random): Result
+
+  override def toString: String = "<prop>"
+}
+

What's missing is a way to construct properties. + Sure, we could implement the trait manually in our tests, but that would be tedious. + Type classes to the rescue! + We call something testable if it can be converted to a Prop:

+
trait Testable[T] {
+  def asProp(t: T): Prop
+}
+
+// in practice we would put these into the companion object
+//object Testable {
+
+  // Booleans can be trivially converted to a property:
+  // They are already basically a `Result`, so no need
+  // to run anything!
+  implicit val booleanIsTestable: Testable[Boolean] = new Testable[Boolean] {
+    def asProp(t: Boolean): Prop = new Prop {
+      def run(size: Int, rnd: Random): Result =
+        Result.fromBoolean(t)
+    }
+  }
+
+  // Props are already `Prop`s.
+  implicit val propIsTestable: Testable[Prop] = new Testable[Prop] {
+    def asProp(t: Prop): Prop = t
+  }
+
+//}
+

Now we're all set:

+
def forAll[I, O](prop: I => O)(implicit arbI: Arbitrary[I], testO: Testable[O]): Prop =
+  new Prop {
+    def run(size: Int, rnd: Random): Result = {
+      val input = arbI.gen.generate(size, rnd)
+      val subprop = testO.asProp(prop(input))
+      subprop.run(size, rnd) match {
+        case Success =>
+          Success
+        case Failure(counterexample) =>
+          Failure(input.toString :: counterexample)
+      }
+    }
+  }
+

Let's unpack this step by step.

+
    +
  1. We're taking a function from I => O. + This is supposed to be our parameterized property, for example { (x: Int) => x == x }. + Because we abstracted over values that can be generated (Arbitrary) and things that can be tested (Testable), the input and output types are completely generic. + In the implicit block, we're taking the instructions of how to fit everything together.
  2. +
  3. We're constructing a Prop; that is, a thing that we can run and that produces a boolean-ish Result.
  4. +
  5. To run the property, we need to construct a random input. + We can use the Gen[I] which we get from the Arbitrary[I].
  6. +
  7. We pass that I into the parameterized property. + To stick with the example, we evaluate the anonymous function { (x: Int) => x == x } at input 5, and obtain true.
  8. +
  9. We convert the result to a Prop again. + This allows us to recursively nest forAlls, for example when we need two inputs.
  10. +
  11. We run the resulting property and check if it fails. + If it does, we prepend the generated input to the counterexample. + In the nested scenario, this allows us to see all generated inputs and the order in which we sticked them into the property.
  12. +
+

At this point we should look at an example.

+
val propReflexivity =
+  forAll { (x: Int) =>
+    x == x
+  }
+// propReflexivity: Prop = <prop>
+

Cool, but how do we run this?

+

Remember that our tool is supposed to evaluate a property on multiple inputs. + All these evaluations will produce a Result. + Hence, we need to merge those together into a single result. + We'll also define a convenient function that runs a property multiple times on different sizes:

+
def merge(rs: List[Result]): Result =
+  rs.foldLeft(Success: Result) {
+    case (Failure(cs), _) => Failure(cs)
+    case (Success, Success) => Success
+    case (Success, Failure(cs)) => Failure(cs)
+  }
+
+def check[P](prop: P)(implicit testP: Testable[P]): Unit = {
+  val rnd = new Random(0)
+  val rs =
+    for (size <- 0 to 100)
+    yield testP.asProp(prop).run(size, rnd)
+  merge(rs.toList) match {
+    case Success =>
+      println("✓ Property successfully checked")
+    case Failure(counterexample) =>
+      val pretty = counterexample.mkString("(", ", ", ")")
+      println(s"✗ Property failed with counterexample: $pretty")
+  }
+}
+

What is happening here?

+
    +
  1. The merge function takes a list of Results and returns the first Failure, if it exists. + Otherwise it returns Success. + In case there are multiple Failures, it doesn't care and just discards the later ones.
  2. +
  3. The check function initializes a fresh random generator.
  4. +
  5. We have fixed the maximum size to 100 and will run the passed property with each size from 0 to 100. + This ensures that we get a nice coverage of various input sizes. + An obvious optimisation here would be to stop after the first failure, instead of merging the results in a subsequent step.
  6. +
  7. In case there's a failure, we just print the counterexample.
  8. +
+

Let's check our property!

+
scala> check(propReflexivity)
+✓ Property successfully checked
+

... and how about something wrong?

+
scala> check(forAll { (x: Int) =>
+     |   x > x
+     | })
+✗ Property failed with counterexample: (0)
+ +

Some more sugar

+

Okay, we're almost done. + The only tedious thing that remains is that we have to use the forAll combinator, especially in the nested case. + It would be great if we could just use check and pass it a function. + But since we've used type classes for everything, we're in luck!

+
implicit def funTestable[I : Arbitrary, O : Testable]: Testable[I => O] = new Testable[I => O] {
+  def asProp(f: I => O): Prop =
+    // wait for it ...
+    // ...
+    // ...
+    // it's really simple ...
+    forAll(f)
+}
+

Now we can check our functions even easier!

+
scala> check { (x: Int) =>
+     |   x == x
+     | }
+✓ Property successfully checked
+
+scala> check { (x: Int) =>
+     |   x > x
+     | }
+✗ Property failed with counterexample: (0)
+
+scala> check { (x: Int) => (y: Int) =>
+     |   x + y == y + x
+     | }
+✓ Property successfully checked
+
+scala> check { (x: Int) => (y: Int) =>
+     |   x + y == x * y
+     | }
+✗ Property failed with counterexample: (0, 1)
+

Now, if you look closely, you can basically get rid of the Prop class and define it as

+
type Prop = Gen[Result]
+

If you think about this for a moment, it makes sense: A “property” is really just a thing which feeds on randomness and produces a result. + The only thing left is to define a driver which runs a couple of iterations and gathers the results; in our implementation, that's the check function. + I encourage you to spell out the other functions (e.g. forAll), and you will notice that our Prop trait is indeed isomorphic to Gen[Result]. + In practice, QuickCheck uses such a representation (although with some more contraptions).

+ +

Summary

+

It turns out that it's not that hard to write a small property-testing library. + I'm going to stop here with the implementation, although there are still some things to explore:

+ +

Finally, I'd like to note that there are many more libraries out there than I've mentioned here, some of which depart more, some less, from the original Haskell implementation. + They even exist for not-functional languages, e.g. Javaslang for Java or Hypothesis for Python.

+

Correction: In a previous version of this post, I incorrectly stated that ScalaCheck uses a mutable random generator. This is only true up to ScalaCheck 1.12.x. I have updated that section in the post.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/monad-transformer-variance.html b/blog/monad-transformer-variance.html new file mode 100644 index 00000000..b8c5a684 --- /dev/null +++ b/blog/monad-transformer-variance.html @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + Variance of Monad Transformers + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Variance of Monad Transformers

+ + + technical + +
+
+
+
+

Variance of Monad Transformers

+

A question that repeatedly pops up about Cats is why monad transformer types like OptionT and EitherT aren't covariant like their Option and Either counterparts. This blog post aims to answer that question.

+ +

Covariance

+

What does it mean to say that Option is covariant? It means that an Option[B] is allowed to be treated as an Option[A] if B is a subtype of A. For example:

+
abstract class Err(val msg: String)
+final case class NotFound(id: Long) extends Err(s"Not found: $id")
+
val optionNotFound: Option[NotFound] = Some(NotFound(42L))
+// optionNotFound: Option[NotFound] = Some(NotFound(42))
+
+optionNotFound: Option[Err]
+// res0: Option[Err] = Some(NotFound(42))
+

Great. If you want to treat your Option[NotFound] as an Option[Err], you are free to.

+

This is made possible because the Option type is declared as sealed abstract class Option[+A], where the + in front of the A means that it is covariant in the A type parameter.

+

What happens if we try to do the same with OptionT?

+
import cats.Eval
+import cats.data.OptionT
+
val optionTNotFound: OptionT[Eval, NotFound] = OptionT(Eval.now(optionNotFound))
+// optionTNotFound: cats.data.OptionT[cats.Eval,NotFound] = OptionT(Now(Some(NotFound(42))))
+
optionTNotFound: OptionT[Eval, Err]
+// <console>:17: error: type mismatch;
+//  found   : cats.data.OptionT[cats.Eval,NotFound]
+//  required: cats.data.OptionT[cats.Eval,Err]
+// Note: NotFound <: Err, but class OptionT is invariant in type A.
+// You may wish to define A as +A instead. (SLS 4.5)
+//        optionTNotFound: OptionT[Eval, Err]
+//        ^
+

The compiler complains that an OptionT[Eval, NotFound] is not an OptionT[Eval, Err], but it also suggests that we may be able to fix this by using +A like is done with Option.

+

OptionT in Cats is defined as:

+
final case class OptionT[F[_], A](value: F[Option[A]])
+

Let's try to make the suggested change with an experimental MyOptionT structure:

+
final case class MyOptionT[F[_], +A](value: F[Option[A]])
+// <console>:14: error: covariant type A occurs in invariant position in type => F[Option[A]] of value value
+//        final case class MyOptionT[F[_], +A](value: F[Option[A]])
+//                                             ^
+

Now it's complaining that A is a covariant type but shows up in an invariant position. We'll discuss more about what this means later in the post, but for now let's just try declaring F as covariant:

+
final case class CovariantOptionT[F[+_], +A](value: F[Option[A]])
+
val covOptionTNotFound: CovariantOptionT[Eval, NotFound] = CovariantOptionT(Eval.now(optionNotFound))
+// covOptionTNotFound: CovariantOptionT[cats.Eval,NotFound] = CovariantOptionT(Now(Some(NotFound(42))))
+
+covOptionTNotFound: CovariantOptionT[Eval, Err]
+// res2: CovariantOptionT[cats.Eval,Err] = CovariantOptionT(Now(Some(NotFound(42))))
+

Woohoo! Problem solved, right?

+

Well, not exactly. This works great if F is in fact covariant, but what if it's not? For example, the JSON library circe has a Decoder type that could be covariant but isn't (at least as of circe 0.10.0). With the invariant OptionT in Cats we can do something like this:

+
import io.circe.Decoder
+
+def defaultValueDecoder[A](defaultValue: A, optionDecoder: Decoder[Option[A]]): Decoder[A] =
+  OptionT(optionDecoder).getOrElse(defaultValue)
+

However, we can't do the same with our CovariantOptionT, because we can't even create an OptionT where the F type isn't covariant:

+
def wrap[A](optionDecoder: Decoder[Option[A]]): CovariantOptionT[Decoder, A] =
+  CovariantOptionT[Decoder, A](optionDecoder)
+// <console>:18: error: kinds of the type arguments (io.circe.Decoder,A) do not conform to the expected kinds of the type parameters (type F,type A).
+// io.circe.Decoder's type parameters do not match type F's expected parameters:
+// type A is invariant, but type _ is declared covariant
+//          CovariantOptionT[Decoder, A](optionDecoder)
+//                          ^
+

In this particular case, Decoder could be declared as covariant, but it's not. It would be unfortunate to lose the ability to use a monad transformer because a 3rd party library chose not to make a type covariant. And perhaps more importantly, sometimes you might want to use an OptionT with an F type that fundamentally isn't covariant in nature, such as Monoid (which is invariant) or Order (which is contravariant in nature and is declared as invariant in its type definition). Later in this post there will be some examples of using OptionT with a contravariant functor type (Eq) to gain acess to a handy contramap operation.

+ +

Workaround

+

It's completely reasonable to want to be able to take your OptionT[Eval, NotFound] and treat it as an OptionT[Eval, Err], and in some cases it may only be typed as OptionT[Eval, NotFound] because of type inference picking a more specific type than you intended. Luckily Cats has some handy methods to make this easy:

+
import cats.implicits._
+
optionTNotFound.widen[Err]
+// res4: cats.data.OptionT[cats.Eval,Err] = OptionT(Now(Some(NotFound(42))))
+

The .widen method is available for any type that has a Functor. If you are working with a type that represents a Bifunctor, such as EitherT, then you can use widen for the type on the right and leftWiden for the type on the left:

+
import cats.data.EitherT
+
val eitherTNotFound: EitherT[Eval, NotFound, Some[Int]] = EitherT.leftT[Eval, Some[Int]](NotFound(42L))
+// eitherTNotFound: cats.data.EitherT[cats.Eval,NotFound,Some[Int]] = EitherT(Now(Left(NotFound(42))))
+
+eitherTNotFound.widen[Option[Int]]
+// res5: cats.data.EitherT[cats.Eval,NotFound,Option[Int]] = EitherT(Now(Left(NotFound(42))))
+
+eitherTNotFound.leftWiden[Err]
+// res6: cats.data.EitherT[cats.Eval,Err,Some[Int]] = EitherT(Now(Left(NotFound(42))))
+

Similarly there is a narrow method for contravariant functors:

+
import cats.Eq
+
val optionTEqErr: OptionT[Eq, Err] = OptionT(Eq[Option[String]]).contramap((err: Err) => err.msg)
+// optionTEqErr: cats.data.OptionT[cats.Eq,Err] = OptionT(cats.kernel.Eq$$anon$103@65c9a471)
+
+optionTEqErr.narrow[NotFound]
+// res7: cats.data.OptionT[cats.Eq,NotFound] = OptionT(cats.kernel.Eq$$anon$103@65c9a471)
+ +

Back to that compile error...

+

Let's return to that covariant type A occurs in invariant position compile error that was triggered when we tried to declare final case class MyOptionT[F[_], +A](value: F[Option[A]]). Why did this happen?

+

Pretend for a minute that the Scala compiler did allow us to do this. We could then write:

+
val eqOptionNotFound: Eq[Option[NotFound]] = Eq.instance[Option[NotFound]]{
+  case (None, None) => true
+  case (None, Some(_)) => false
+  case (Some(_), None) => false
+  case (Some(x), Some(y)) => x.id === y.id
+}
+
val optionTEqNotFound: MyOptionT[Eq, NotFound] = MyOptionT(eqOptionNotfound) // only allowed in our pretend world
+
+val optionTEqErr: MyOptionT[Eq, Err] = optionTEqNotFound // only allowed in our pretend world
+

Because MyOptionT would be covariant in A, this MyOptionT[Eq, NotFound] could be treated as a MyOptionT[Eq, Err]. That is, it would have a value that is an Eq[Option[Err]]. But if you look at how we implemented our equality check, it's taking two NotFound instances and comparing their id fields. For a general Err, we have no guarantee that it will be a NotFound and that it will have an id field. We can't treat an Eq[Option[NotFound]] as an Eq[Option[Err]], because Eq is contravariant and not covariant in nature. The covariant type A occurs in invariant position message that the scala compiler gave us was the compiler correctly identifying that our code was unsound.

+ +

Conclusion

+

There are some use-cases in which having monad transforers such as OptionT and EitherT be defined as covariant would be convenient, such as helping to nudge type inference in the right direction. However, if Cats were to define these types as covariant, it would eliminate the possibility of using them with non-covariant types. Forcing covariance in Cats would be overly restrictive, since third-party libraries might not declare types as covariant, and monad transformers can be useful for invariant a contravariant types. Luckily, methods such as widen, leftWiden, and narrow provide concise solutions to turning a monad transformer (or other type that forms a functor) into the type that you need.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Cody Allen + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/more-types-than-classes.html b/blog/more-types-than-classes.html new file mode 100644 index 00000000..8f712f98 --- /dev/null +++ b/blog/more-types-than-classes.html @@ -0,0 +1,748 @@ + + + + + + + + + + + + + + + + + + + + + + + There are more types than classes + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

There are more types than classes

+ + + technical + +
+
+
+
+

There are more types than classes

+

As programmers, we are very incautious with our use of the word + “type”. The concept of “type” is sufficiently abstract and specific + that we are tempted to understand it by analogy, so much that we begin + to confuse analogy with sameness.

+

The colloquial “runtime type”, a fair approximation of “class”, makes + it tempting to equate types with “classes, interfaces, traits, that + sort of thing”, which I will name classes for the rest of this + article. But they aren’t the same. The type system is much richer and + more interesting than the class system, even in Java.

+

To appreciate this richness, we must stop thinking of types as classes + and stop drawing conclusions from that weak analogy. Luckily, the + compiler will readily reveal how unlike classes types are, if we ask + it some simple questions.

+ +

One value with class, many variables with type

+
val greeting: String = "hi there!"
+

Here I have constructed a String and assigned it to a variable. (I + have also constructed the char array in the String and various + other details, but immediately handed those off to the String and + forgotten about them.) This value has class String. It has several + classes, really.

+
    +
  1. String
  2. +
  3. java.io.Serializable
  4. +
  5. CharSequence
  6. +
  7. Comparable[String]
  8. +
  9. Object/AnyRef
  10. +
+

That seems like a lot of classes for one value. And they are genuine + classes of greeting, though 2-5 are all implied by #1.

+

greeting also has all five of these types. We can ask the compiler + to verify that this type truth holds, entirely separately from the + class truth.

+
scala> (greeting: String, greeting: java.io.Serializable,
+        greeting: CharSequence, greeting: Comparable[String],
+        greeting: AnyRef)
+res3: (String, java.io.Serializable, CharSequence,
+       Comparable[String], AnyRef) =
+  (hi there!,hi there!,hi there!,hi there!,hi there!)
+

So we have exhausted the classes, but aren’t quite done with types.

+
scala> greeting: greeting.type
+res0: greeting.type = hi there!
+

greeting.type is not like the other five types we just tested. It is + a strict subtype of String, and has no class with the same name.

+
// If and only if call compiles, A is a subtype of B.
+def conformance[A, B >: A]: Unit = ()
+
+scala> conformance[greeting.type, String]
+
+scala> conformance[String, greeting.type]
+<console>:14: error: type arguments [String,greeting.type] do not conform
+              to method conformance's type parameter bounds [A,B >: A]
+

Fine, we can accept that object identity is represented at the type + level without our universe imploding, by inventing the theory that + this is about object identity; hold on, though:

+
scala> val salutation = greeting
+salutation: String = hi there!
+

Fine, salutation is just another name for greeting, right?

+
scala> conformance[salutation.type, String]
+
+scala> implicitly[greeting.type =:= salutation.type]
+<console>:14: error: Cannot prove that greeting.type =:= salutation.type.
+

Now we have seven. I’ll spare you spelling out the induction: each new + variable defined like salutation will yield a new alias with a + distinct type. This is not about objects; this is about variables!

+
// find a type for the literal "hi there!"
+scala> val literalHiThere = shapeless.Witness("hi there!")
+literalHiThere: shapeless.Witness.Aux[String("hi there!")] = shapeless.Witness$$anon$1@1d1537bb
+
+scala> conformance[greeting.type, literalHiThere.T]
+<console>:15: error: type arguments [greeting.type,literalHiThere.T] do not conform
+              to method conformance's type parameter bounds [A,B >: A]
+
+scala> conformance[literalHiThere.T, greeting.type]
+<console>:15: error: type arguments [literalHiThere.T,greeting.type] do not conform
+              to method conformance's type parameter bounds [A,B >: A]
+

As local variables are a strictly compile-time abstraction, and we + have anyway seen that the numbers don’t match up, that should be the + end of the “types are classes” confusion for you. But maybe this is + just some Scala oddity! And anyhow I haven’t even begun to demonstrate + the overwhelming richness of the type model as it blindingly outshines + the paucity of the class model. Let’s go further.

+ +

No values, infinite types: method type parameters

+

To our small program of a greeting, we can add a small method.

+
def pickGreeting[G](grt: G, rand: Int) = grt
+
+scala> pickGreeting(greeting, 42)
+res9: String = hi there!
+

It seems like G must be String, because the argument passed to + pickGreeting is a string, and in that case so must its return value + be, according to the implementation. And from the perspective of this + call, outside + pickGreeting’s implementation, it is String indeed.

+

But that implementation’s perspective matters, too; it is also part of + our program. And it sees things quite differently. We can ask its + thoughts on the matter by adding to its body

+
def pickGreeting[G](grt: G, rand: Int) = {
+  implicitly[G =:= String]
+  grt
+}
+
+<console>:12: error: Cannot prove that G =:= String.
+         implicitly[G =:= String]
+                   ^
+

In fact, G bears no direct relationship to String at all.

+
// replace implicitly with
+conformance[G, String]
+
+<console>:13: error: type arguments [G,String] do not conform
+              to method conformance's type parameter bounds [A,B >: A]
+         conformance[G, String]
+                    ^
+
+// or with
+conformance[String, G]
+
+<console>:13: error: type arguments [String,G] do not conform
+              to method conformance's type parameter bounds [A,B >: A]
+         conformance[String, G]
+                    ^
+

Let’s apply the pigeonhole principle. Imagine that you had a list of + every class that ever was or ever will be. Imagine that, somehow, all + of these classes, from String to + AbstractFactoryMethodProxyBuilder, were on your classpath, available + to your program.

+

Next, imagine that you had the time and inclination to try the =:= + test with every last one of these classes.

+
implicitly[G =:= javax.swing.JFrame]
+implicitly[G =:= AbstractFactoryMethodProxyBuilder]
+// ad [in]finitum
+

Your search will be futile; every class on your list-of-every-class + will give the same compiler error we got with String.

+

So, since G is not equal to anything on this list, it must be + something else that doesn’t appear on the list. Because this list + contains all classes, G must be something other than a class.

+ +

It must not necessarily be anything

+

It seems like it might be convenient to say “well, in this program G + is only ever String by substitution, so therefore it is, even if the + compiler doesn’t see that.” However, thinking like this misses out on + the second key advantage of type parameterization, the one not based + on multiplicity of substitution, or the type-safety of callers: + blindness.

+

The implementation of type-parameterized classes and methods are + required to treat each type parameter uniquely, uniformly, and without + prejudice. The compiler enforces this by making the implementation + blind to what that parameter, like G, could be. It can only use what + the caller, the “outside”, has told it about G—arguments whose + types contain G, like List[G], (G, G) => G, or G itself, like + the argument to pickGreeting. This + is + information-hiding at the type level; + if you find information-hiding a useful tool for implementing correct + programs, you will find the same of the fresh, unique, and mysterious + types induced by each introduction of a type parameter.

+

Each operation a language permits by default, not via an argument, + on values of a type parameter is a leak in this abstraction. This + includes testing the value’s class, converting to string, and + comparing to other values of supposedly utter mystery for + equality. The ability to create a “default” value is also a leak. A + function is always permitted to ask only that of these that it needs + from the caller; make them default, and this design choice is taken + away. That is why Object#equals is little better for + type-safety than reflection-based calls, and why total type erasure + is a desirable feature rather than a design flaw—plugging these + leaks gives the programmer as much freedom to abstract by + information-hiding as she wishes.

+ +

How many calls are there?

+

Put another way, when implementing the code in the scope of a type + parameter, your implementation must be equally valid for all + possible G substitutions, including the ones that haven’t been + invented yet. This is why we call it universal quantification.

+

But it is not merely each declaration of a type parameter that yields + a distinct type—each call does! Consider two consecutive calls to + pickGreeting.

+
pickGreeting(greeting, 42)
+pickGreeting(33, 84)
+

Externally, there are two G types. However, the possibility of + writing this demands another level of uniqueness treatment when + typechecking pickGreeting’s definition: whatever G is now, like + String, it might be something else in the next call, like Int in + the above example. With recursion, it might even be two different + things at the same time. There’s nothing to hold this at two, either: + there may be an unbounded number of substitutions for a given type + parameter within a single program, at a single point in time.

+

While G may be the same between two invocations of pickGreeting, + it might not. So we have no choice but to treat the G types of each + call as separate types. There may be infinitely many calls, so there + are so many types.

+

Incidentally, the same happens for singleton types. Each time val +greeting comes into scope, it induces a separate singleton type. It + is easy enough to arrange for an unbounded number of scope entries in + a particular program. This isn’t so practical as the type parameter + phenomenon, though.

+ +

More types from variable copies

+

Suppose we’d like to wait a while to compute our greeting. We can + define a type-and-class to represent that conveniently.

+
// like Coyoneda Id, if that helps
+sealed abstract class Later[A] {
+  type I
+  val i: I
+  val f: I => A
+}
+
+def later[In, A](now: In)(later: In => A)
+  : Later[A] = new Later[A] {
+    type I = In
+    val i = now
+    val f = later
+}
+
+val greeting3 = later(3){
+  n => List.fill(n)("hi").mkString(" ")
+}
+

How many classes are involved here, in the type of greeting3?

+
    +
  1. Later, obviously;
  2. +
  3. Function1, the greeting3.f overall class;
  4. +
  5. String, the output type of greeting3.f;
  6. +
  7. Int, the I type.
  8. +
+

How many types?

+

The first difference is that greeting3.I is not Int.

+
scala> implicitly[greeting3.I =:= Int]
+<console>:14: error: Cannot prove that greeting3.I =:= Int.
+       implicitly[greeting3.I =:= Int]
+                 ^
+

They are unrelated for much the same reason as G was unrelated to + String in the previous example: the only things code following + val greeting3 may know are those embodied in the greeting3.i and + greeting3.f members. You can almost think of them as “arguments”.

+

But that’s not all.

+
val salut3 = greeting3
+
+scala> greeting3.f(greeting3.i)
+res11: String = hi hi hi
+
+scala> salut3.f(salut3.i)
+res12: String = hi hi hi
+
+scala> greeting3.f(salut3.i)
+<console>:14: error: type mismatch;
+ found   : salut3.i.type (with underlying type salut3.I)
+ required: greeting3.I
+       greeting3.f(salut3.i)
+                          ^
+
+scala> implicitly[greeting3.I =:= salut3.I]
+<console>:14: error: Cannot prove that greeting3.I =:= salut3.I.
+

Just like every call to pickGreeting induces a new G type, each + simple val copy of greeting3 will induce a new, unique I + type. It doesn’t matter that they’re all the same value; this is a + matter of variables, not values, just as with singleton types.

+

But that’s still not all.

+ +

One value with class, many variable references with types

+

The preceding is more delicate than it seems.

+
var allo = greeting3
+
+scala> allo.f(allo.i)
+<console>:13: error: type mismatch;
+ found   : Later[String]#I
+ required: _1.I where val _1: Later[String]
+       allo.f(allo.i)
+                   ^
+

All we have done differently is use a mutable var instead of an + immutable val. Why is this enough to throw a wrench in the works?

+

Suppose you had another value of the Later[String] type.

+
val bhello = later("olleh")(_.reverse)
+

The I substitution here is String. So the f takes a String + argument, and the I is a String.

+

bhello is of a compatible type with the allo var. So this + assignment will work.

+
allo = bhello
+

In a sense, when this mutation occurs, the I type also mutates, + from Int to String. But that isn’t quite right; types cannot + mutate.

+

Suppose that this assignment happened in the middle of that line of + code that could not compile. We could imagine the sequence of events, + were it permitted.

+
    +
  1. allo.f (which is greeting3.f) evaluates. It is the function + (n: Int) => List.fill(n)("hi").mkString(" ").
  2. +
  3. The allo = bhello assignment occurs.
  4. +
  5. allo.i (which is bhello.i) evaluates. It is the string + "olleh".
  6. +
  7. We attempt to pass "olleh" as the (n: Int) argument to complete + the evaluation, and get stuck.
  8. +
+

Just as it makes no difference what concrete substitutions you make + for G, it makes no difference whether such an assignment could ever + happen in your specific program; the compiler takes it as a + possibility because you declared a var. (def allo = greeting3 gets + the same treatment, lest you think non-functional programs get to have + all the fun here.) Each reference to allo gets a new I type + member. That failing line of code had two allo references, so was + working with two incompatible I types.

+

Since the number of references to a variable in a program is also + unbounded...you get the picture.

+

This also occurs with existential type parameters, which are equally + expressive to type members. Accordingly, Java also + generates new types from occurrences of expressions of existential + type.

+ +

How do we tell the two apart?

+

All of this is simply to say that we must be working with two separate + concepts here.

+
    +
  1. The runtime shape and properties of the values that end up + flying around when a program actually runs. This we call + class.
  2. +
  3. The compile-time, statically-discoverable shape and properties of + the expressions that fly around when a program is + written. This we call type.
  4. +
+

The case with var is revealing. Maybe the I type will always be + the same for a given mutable variable. But demonstrating that this + holds true for one run of the program (#1, class) isn’t nearly good + enough to prove that it will be true for all runs of the program + (#2, type).

+

We refuse to apply the term “type” to the #1, ‘class’ concept because + it does not live up to the name. The statement “these two types are + the same” is another level of power entirely; “these two values have + the same class” is extraordinarily weak by comparison.

+

It is tempting to use the term “runtime type” to refer to + classes. However, in the case of Scala, as with all type systems + featuring parametric polymorphism, classes are so dissimilar to types + that the similar-sounding term leads to false intuition, not helpful + analogy. It is a detriment to learning, not an aid.

+

Types are compile-time, and classes are runtime.

+ +

When are types real?

+

The phase separation—compile-time versus runtime—is the key to + the strength of types in Scala and similar type systems. The static + nature of types means that the truths they represent must be + universally quantified—true in all possible cases, not just some + test cases.

+

We need this strength because the phase separation forbids us from + taking into account anything that cannot be known about the program + without running it. We need to think in terms of “could happen”, not + “pretty sure it doesn’t”.

+ +

How do classes give rise to types?

+

There appears to be some overlap between the classes of greeting and + its types. While greeting has the class String, it also has the + type String.

+

We want types to represent static truths about the expressions in a + program. That’s why it makes sense to include a “model of the classes” + in the type system. When we define a class, we also define an + associated type or family of types.

+

When we use a class to construct a value, as in new Blob, we would + like to assign as much specific meaning to that expression as we can + at compile time. So, because we know right now that this expression + will make a value of class Blob, we assign it the type Blob too.

+ +

How do the types disappear?

+

There’s a common way to throw away type information in Scala, + especially popular in object-oriented style.

+
val absGreeting: CharSequence = greeting
+

absGreeting has the same value as greeting, so it has the same + five classes. However, it only has two of those five types, because we + threw away the other three statically. It has lost some other types, + too, namely greeting.type, and acquired some new ones, namely + absGreeting.type.

+

Once a value is constructed, the expression will naturally cast off + the types specifying its precise identity, as it moves into more + abstract contexts. Ironically, the best way to preserve that + information as it passes through abstract contexts is to take + advantage of purely abstract types—type parameters and type + members.

+
scala> pickGreeting[greeting.type](greeting, 100)
+res16: greeting.type = hi there!
+

While the implementation must treat its argument as being of the + abstract type G, the caller knows that the more specific + greeting.type must come out of that process.

+ +

How do the types come back?

+

There is a feature in Scala that lets you use class to get back some + type information via a dynamic, runtime test.

+
absGreeting match {
+  case hiAgain: String =>
+    conformance[hiAgain.type, String] // will compile
+}
+

The name “type test” for this feature is poorly chosen. The + conclusion affects the type level—hiAgain is, indeed, proven + statically to be of type String—but the test occurs only at + the class level.

+

The compiler will tell you about this limitation sometimes.

+
def pickGreeting2[G](grt: G, rand: Int): G =
+  ("magic": Any) match {
+    case ok: G => ok
+    case _ => sys error "failed!"
+  }
+
+<console>:13: warning: abstract type pattern G is unchecked
+              since it is eliminated by erasure
+           case ok: G => ok
+                    ^
+

But reflecting the runtime classes back to compile-time types is a + subtle art, and the compiler often can’t explain exactly what you got + wrong.

+
def pickGreeting3[G](grt: G, rand: Int): G =
+  grt match {
+    case _: String =>
+      "Surely type G is String, right?"
+    case _ => grt
+  }
+
+<console>:14: error: type mismatch;
+ found   : String("Surely type G is String, right?")
+ required: G
+             "Surely type G is String, right?"
+             ^
+

I’ve touched upon this + mistake + in previous articles, + but it’s worth taking at least one more look. Let’s examine how + tempting this mistake is.

+

String is a final class. So it is true that G can contain no + more specific class than String, if the first case matches. For + example, given trait MyAwesomeMixin, G cannot be + String with MyAwesomeMixin if this case succeeds, because that + can’t be instantiated; you would need to create a subclass of String + that implemented MyAwesomeMixin.

+

This pattern match isn’t enough evidence to say that G is exactly + String. There are still other class-based types it could be, like + Serializable.

+
pickGreeting3[java.io.Serializable](greeting, 4055)
+

Instead, it feels like this pattern match confirms Serializable as a + possibility, instead of denying it.

+

But we don’t need G = String for this code to compile; we only need + G >: String. If that was true, then "Surely type G is String, +right?", a String, could simply upcast to G.

+

However, even G >: String is unproven. There are no subclasses of + String, but there are infinitely many subtypes of + String. Including the G created by each entry into + pickGreeting3, every abstract and existential type bounded by + String, and every singleton type of String variable definitions.

+

This mistake is, once again, confusing a demonstration of one case + with a proof. Pattern matching tells us a great deal about one value, + the grt argument, but very little about the type G. All we know + for sure is that “grt is of type G, and also of type String, so + these types overlap by at least one value.” In the type system, if you + don’t know something for sure, you don’t know it at all.

+ +

Classes are a concrete source of values

+

In the parlance of functional Scala, concrete classes are often called + “data constructors”.

+

When you are creating a value, you must ultimately be concrete about + its class, at the bottom of all the abstractions and indirections used + to hide this potentially messy detail.

+
scala> def pickGreeting4[G]: G = new G
+<console>:12: error: class type required but G found
+       def pickGreeting4[G]: G = new G
+                                     ^
+

You’ll have to do something else here, like take an argument + () => G, to let pickGreeting4 construct Gs.

+

The truly essential role that classes play is that they encapsulate + instructions for constructing concrete values of various types. In a + safe program, this is the only feature of classes you’ll use.

+

In Scala, classes leave fingerprints on the values that they + construct, without fail. This is merely an auxiliary part of their + primary function as value factories, like a “Made in class Blah” + sticker on the back. Pattern matching’s “type tests” work by checking + this fingerprint of construction.

+ +

Most runtime “type test” mechanisms do not work for types

+

These fingerprints only come from classes, not types. So “type tests” + only work for “classy” types, like String and MyAwesomeMixin. They + also work for specific singleton types because construction also + leaves an “object identity” fingerprint that the test can use.

+

The + ClassTag typeclass does + not change this restriction. When you add a ClassTag or TypeTag + context bound, you also prevent that type parameter from working with + most types.

+
scala> implicitly[reflect.ClassTag[greeting3.I]]
+<console>:14: error: No ClassTag available for greeting3.I
+       implicitly[reflect.ClassTag[greeting3.I]]
+                 ^
+

As such, judicious use of ClassTag is not a great solution to + excessive use of type tests in abstract contexts. There are so many + more types than classes that this is to confine the expressivity of + your types to a very small, class-reflective box. Set them free!

+ +

“But doesn’t Python/JavaScript/&c have both types and classes at runtime?”

+

In JavaScript, there’s a very general runtime classification of values + called “type”, meant to classify built-in categories like string, + number, and the like.

+
>> typeof "hi"
+"string"
+>> typeof 42
+"number"
+>>> typeof [1, 'a']
+"object"
+

Defining a class with the new class keyword doesn’t extend this + partition with new “types”; instead, it further subdivides one of + those with a separate classification.

+
>> class Foo() {}
+>> class Bar() {}
+>> typeof (new Foo)
+"object"
+>> typeof (new Bar)
+"object"
+>> new Foo().constructor
+function Foo()
+>> new Bar().constructor
+function Bar()
+

So, if you treat JavaScript’s definition of the word “type” as + analogous to the usage in this article, then yes, JavaScript has + “runtime types”.

+

But JavaScript can only conveniently get away with this because its + static types are uninteresting. It has one type—the type of all + values—and no opportunities to do interesting type-level modeling, + at least not as part of the standard language.

+

Hence, JavaScript is free to repurpose the word “type” for a flavor of + its classes, because our “types” aren’t a tool you make much use of in + JavaScript. But when you come back to Scala, Haskell, the ML family, + et al, you need a word for the static concept once again.

+ +

Thinking about types as just classes leads to incorrect conclusions

+

Setting aside the goal of principled definition of terms, this + separation is the one that makes the most sense for a practitioner of + Scala. Consider the practicalities:

+

Types and classes have different behavior, are equal and unequal + according to different rules, and there are a lot more types than + classes. So we need different words to distinguish them.

+

Saying “compile-time type” or “runtime type” is not a practical + solution—no one wants to speak such an unwieldy qualifier every + time they refer to such a commonly-used concept.

+

While I’ve given a sampling of the richness of the type system in this + article, it’s not necessary to know that full richness to appreciate + or remember the difference between the two: types are static and + compile-time; classes are dynamic and runtime.

+

This article was tested with Scala 2.12.1, Shapeless 2.3.2, and + Firefox 53.0a2.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/nested-existentials.html b/blog/nested-existentials.html new file mode 100644 index 00000000..9f176730 --- /dev/null +++ b/blog/nested-existentials.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Nested existentials + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Nested existentials

+ + + technical + +
+
+
+
+

Nested existentials

+

This is the fifth of a series of articles on “Type Parameters and + Type Members”. If you haven’t yet, you should + start at the beginning, + which introduces code we refer to throughout this article without + further ado.

+

Let’s consider a few values of type MList:

+
val estrs: MList = MCons("hi", MCons("bye", MNil())): MList.Aux[String]
+
+val eints: MList = MCons(21, MCons(42, MNil())): MList.Aux[Int]
+
+val ebools: MList = MCons(true, MCons(false, MNil())): MList.Aux[Boolean]
+

Recall + from the first part + that the equivalent type in PList style is PList[_]. Now, these + variables all have the “same” type, by virtue of forgetting what their + specific element type is, though you know that every value of, for + example, estrs has the same type.

+ +

What if we list different existentials?

+

Lists hold values of the same type, and as you might expect, you can + put these three lists in another list:

+
val elists: PList[MList] =
+  PCons(estrs, PCons(eints, PCons(ebools, PNil())))
+

Again, the equivalent is PList[PList[_]]. We can see what this + means merely by doing substitution in the PList type.

+
sealed abstract class PList
+final case class PNil() extends PList
+final case class PCons(head: MList, tail: PList)
+// don't compile this, it's a thought process
+

Equivalently, head would have type PList[_], a homogeneous list of + unknown element type, just like MList.

+ +

Method equivalence … broken?

+

But we come to a problem. Suppose we wish to count the elements of + doubly-nested lists.

+
def plenLength(xss: PList[PList[_]]): Int =
+  plenLengthTP(xss)
+
+def plenLengthTP[T](xss: PList[PList[T]]): Int =
+  xss match {
+    case PNil() => 0
+    case PCons(h, t) => plengthT(h) + plenLengthTP(t)
+  }
+
+TmTp5.scala:16: no type parameters for method plenLengthTP:
+⤹ (xss: tmtp.PList[tmtp.PList[T]])Int exist so that it
+⤹ can be applied to arguments (tmtp.PList[tmtp.PList[_]])
+ --- because ---
+argument expression's type is not compatible with formal parameter type;
+ found   : tmtp.PList[tmtp.PList[_]]
+ required: tmtp.PList[tmtp.PList[?T]]
+

According to our equivalence test, neither of these methods works to + implement the other! This despite + the “simple rule” we have already discussed. + Here’s the error the other way.

+
TmTp5.scala:20: type mismatch;
+ found   : tmtp.PList[tmtp.PList[T]]
+ required: tmtp.PList[tmtp.PList[_]]
+

The problem with calling plenLengthTP from plenLength is there is + no one T we can choose, even an unspeakable one, to call + plenLengthTP. That’s what the ?T and the “no type parameters” + phrasing in the first error above means.

+

This is an accurate compiler error because PList[PList[_]] means + PList[PList[E] forSome {type E}]. Let’s see the substitution again.

+
sealed abstract class PList
+final case class PNil() extends PList
+final case class PCons(head: PList[E] forSome {type E}, tail: PList)
+// don't compile this, it's a thought process
+

Java has the same problem. See?

+
int llLength(final List<List<?>> xss) {
+    return llLengthTP(xss);
+}
+
+<T> int llLengthTP(final List<List<T>> xss) {
+    return 0;  // we only care about types in this example
+}
+
+TmTp5.java:7:  error: method llLengthTP in class TmTp5
+⤹ cannot be applied to given types;
+    return llLengthTP(xss);
+           ^
+
+// or, with llLengthTP calling llLength
+TmTp5.java:11:  error: incompatible types: List<List<T>>
+⤹ cannot be converted to List<List<?>>
+    return llLength(xss);
+                    ^
+

This discovery, which I made for myself + in the depths of the Ermine Java code + (though it was certainly already well-known to others), was my first + clue, personally, that the term + “wildcard” was a lie, as discussed in a previous part.

+ +

Scoping existential quantifiers

+

The difference is, in Scala, we can write an equivalent for + plenLengthTP, using the Scala-only + forSome existential quantifier.

+
def plenLengthE(xss: PList[PList[E]] forSome {type E}): Int =
+  plenLengthTP(xss)
+

Of course, this type doesn’t mean the same thing as plenLength’s + type; for both plenLengthE and plenLengthTP, we demand proof that + each sublist in the argument has the same element type, which is not a + condition satisfied by either PList[PList[_]] or its equivalent + PList[MList].

+
+

The reason you can’t invoke plenLength from + plenLengthTP is complicated, even for this article. In + short, plenLength demands evidence that, + supposing PList had a method taking an + argument of the element type, + e.g. def lookAt(x: T): Unit, it could do things like + xss.lookAt(PList("hi", PNil())). In + plenLengthTP, this hypothetical method could only be + invoked with empty lists, or lists gotten by inspecting + xss itself.

+

That no such method exists is irrelevant for the purposes of this + reasoning; we have written the definition of PList in a + way that scalac assumes that such a method may exist. You can + determine the consequences yourself by adding the + lookAt method to PList, repeating the + above substitution for PList, and thinking about the + meaning of the resulting def lookAt(x: + PList[E] forSome {type E}): Unit.

+
+

Let’s examine the meaning of the type + PList[PList[E]] forSome {type E}. It requires a little bit more + mental suspension.

+
// Let there be some unknown (abstract)
+type E
+// then the structure of the value is
+sealed abstract class PList
+final case class PNil() extends PList
+final case class PCons(head: PList[E], tail: PList)
+// don't compile this, it's a thought process
+

By moving the forSome existential scope outside the outer PList, + we also move the existential type variable outside of the whole + structure, substituting the same variable for each place we’re + expanding the type under consideration. Once the forSome scope + extends over the whole type, Scala can pick that type as the parameter + to plenLengthTP.

+

This isn’t possible in Java at all; PList<PList<?>> is your only + choice, as ? in Java, like _ in Scala, is always scoped to + exactly one level outside. So in Java, you simply can’t write + plenLengthE’s type. Luckily, the type-parameter equivalent is + perfectly expressible.

+ +

What happens when I move the existential scope?

+

Of course, moving the scope makes the type mean something different, + which you can tell by counting how many Es there will be in a value. + A PList[PList[_]] is a list of lists where each list may have a + different, unknown element type, like elists. A + PList[PList[E]] forSome {type E} is a list of lists where you still + don’t know the inner element type, but you know it’s the same for each + sublist. We can tell that because, in the expansion, there’s only one + E, whereas the expansion for the former has an E introduced in + each head value.

+

So for the latter it is type-correct to, say, move elements from one + sublist to another; you know that, whichever pair of sublists you + choose to make this trade, they have the same element type. But you + don’t know that for PList[PList[_]].

+

Similarly, also by substitution, PList[_] => Int is a function that + takes PLists of any element type and returns Int, like plengthE. + You can figure this out by substituting for + Function1#apply:

+
def apply(v1: T1): R
+def apply(v1: PList[_]): Int
+

But (PList[E] => Int) forSome {type E} is a function that takes + PLists of one specific element type that we don’t know.

+
// Let there be some unknown (abstract)
+type E
+// then the method is
+def apply(v1: List[E]): Int
+

It’s easy to use existential scoping to create functions that are + impossible to call and other values that are impossible to use besides + functions. This is almost one of those:

+
def badlength: (PList[E] => Int) forSome {type E} = plengthE
+badlength(??? : PList[Int])
+
+TmTp5.scala:29: type mismatch;
+ found   : tmtp.PList[Int]
+ required: tmtp.PList[E] where type E
+badlength(??? : PList[Int])
+              ^
+

But in this case, there is one way we can call this function: with an + empty list. Whatever the E is, it will be inferred when we call + PNil(). So badlength(PNil()) works.

+

There is a broader theme here hinted at by the interaction between + PNil and badlength: the most efficient, most easily understood + way to work with values of existential type is with type-parameterized + methods. But we’ll get to that later.

+ +

Back to type members

+

Let us translate the working existential variant we discovered above + to the PList[MList] form of the function, though. What is the + existential equivalent to mlenLengthTP?

+
def mlenLengthTP[T](xss: PList[MList.Aux[T]]): Int =
+  xss match {
+    case PNil() => 0
+    case PCons(h, t) => mlength(h) + mlenLengthTP(t)
+  }
+
+def mlenLength(xss: PList[MList]): Int =
+  mlenLengthTP(xss)
+
+TmTp5.scala:38: type mismatch;
+ found   : tmtp.PList[tmtp.MList]
+ required: tmtp.PList[tmtp.MList.Aux[this.T]]
+  mlenLengthTP(xss)
+               ^
+

MList is equivalent to MList {type T = E} forSome {type E}. We + can prove that directly in Scala.

+
scala> implicitly[MList =:= (MList {type T = E} forSome {type E})]
+res0: =:=[tmtp.MList,tmtp.MList{type T = E} forSome { type E }] = <function1>
+

That’s why we could use runStSource to infer a type parameter for + the existential S in + the last post: + the scope is on the outside, so there’s exactly one type parameter to + infer. So the scoping problem now looks very similar to the + PList-in-PList problem, and we can write:

+
def mlenLengthE(xss: PList[MList.Aux[E]] forSome {type E})
+  : Int = mlenLengthTP(xss)
+ +

A triangular generalization

+

Once again, mlenLengthE demands proof that each sublist of xss has + the same element type, by virtue of the position of its forSome + scope. We can’t satisfy that with elists.

+
mlenLengthE(elists)
+

Or, we shouldn’t be able to, anyway. + Sometimes, the wrong thing happens. + We get the right error when we try to invoke mlenLengthTP.

+
mlenLengthTP(elists)
+
+TmTp5.scala:43: type mismatch;
+ found   : tmtp.PList[tmtp.MList]
+ required: tmtp.PList[tmtp.MList.Aux[this.T]]
+    (which expands to)  tmtp.PList[tmtp.MList{type T = this.T}]
+mlenLengthTP(elists)
+             ^
+

So we have mlenLengthE _m \equiv\_m  mlenLengthTP. mlenLength, + however, is incompatible with both; neither is more general than the + other! What we really want is a function that is more general than + all three, and subsumes all their definitions. Here it is, in two + variants: one half-type-parameterized, the other wholly existential.

+
def mlenLengthTP2[T <: MList](xss: PList[T]): Int =
+  xss match {
+    case PNil() => 0
+    case PCons(h, t) => mlength(h) + mlenLengthTP2(t)
+  }
+
+def mlenLengthE2(xss: PList[_ <: MList]): Int =
+  xss match {
+    case PNil() => 0
+    case PCons(h, t) => mlength(h) + mlenLengthTP2(t)
+  }
+

We’ve woven a tangled web, so here are, restated, the full + relationships for the MList-in-PList functions above.

+
    +
  1. mlenLengthTP2 m \equiv_m mlenLengthE2
  2. +
  3. mlenLengthTP m \equiv_m mlenLengthE
  4. +
  5. ¬( \neg( mlenLength <:m <:_m mlenLengthE) )
  6. +
  7. ¬( \neg( mlenLengthE <:m <:_m mlenLength) )
  8. +
  9. ¬( \neg( mlenLength <:m <:_m mlenLengthTP) )
  10. +
  11. ¬( \neg( mlenLengthTP <:m <:_m mlenLength) )
  12. +
  13. mlenLengthTP2 <m <_m mlenLengthTP
  14. +
  15. mlenLengthTP2 <m <_m mlenLength
  16. +
  17. mlenLengthTP2 <m <_m mlenLengthE
  18. +
  19. mlenLengthE2 <m <_m mlenLengthTP
  20. +
  21. mlenLengthE2 <m <_m mlenLength
  22. +
  23. mlenLengthE2 <m <_m mlenLengthE
  24. +
+

Moreover, the full existential in mlenLengthE2 is shorthand for:

+
PList[E] forSome {
+  type E <: MList {
+    type T = E2
+  } forSome {type E2}
+}
+

…a nested existential, though not in the meaning I intend in the title + of this article. You can prove it with =:=, as above.

+

And I say all this simply as a means of saying that this is what + you’re signing up for when you decide to “simplify” your code by using + type members instead of parameters and leaving off the refinements + that make them concrete.

+

In + the next part, “Values never change types”, + we’ll get some idea of why working with existential types can be so + full of compiler errors, especially when allowing for mutation and + impure functions.

+

This article was tested with Scala 2.11.7 and Java 1.8.0_45.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/new-code-of-conduct-committee-members.html b/blog/new-code-of-conduct-committee-members.html new file mode 100644 index 00000000..6bca6ea7 --- /dev/null +++ b/blog/new-code-of-conduct-committee-members.html @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + Code of Conduct Committee + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Code of Conduct Committee

+ + + governance + +
+
+
+
+

Code of Conduct Committee

+

A few months back the Typelevel Steering Committee put out a call for new members to join the Typelevel Code of Conduct Committee. Thank you very much to all who applied, it's lovely to see folks interested in keeping our community safe.

+

Since that time, the Typelevel Steering Committee voted in three new members to the Code of Conduct Committee! The committee now has five members total.

+

Here are some brief introductions to our current committee members.

+ +

Lucas Satabin (he/him)

+
+

I've been using Scala since 2009 both professionally and personally, and the Typelevel ecosystem since the scalac fork. Since then I have been trying to contribute libraries in the ecosystem on a regular basis. I currently work at commercetools.

+

I am based in the Paris area in France, and in my free time I like to play board games, TTRPGs, CRPGs, and I read a lot of comic books (mostly European and American). Baking bread, making mustard, and brewing slow coffees are my favorite kitchen based activities. I also tend to talk too much about trains.

+
+ +

Kateu Herbert (he/him)

+
I’ve been programming with Scala for about 5 years now. I am a statistician by profession, currently working as a data analyst for Enveritas based in Kampala, Uganda. I use Scala for all my personal projects and in my free time, I share my knowledge through blog posts. Other hobbies include playing around with Linux distros, training my dog, and watching and reading about tech related content.
+ +

Arman Bilge (he/him)

+
I'm Arman, based in Seattle, and I have been involved with Typelevel for almost four years now. I was first drawn to contribute by the welcoming and friendly people in our Discord! This community is near and dear to me and I am committed to fostering a safe and inclusive environment where we can work, learn and play together :)
+ +

Sam Pillsworth (she/her)

+
Hi, I'm Sam! I'm part of the Typelevel Steering Committee and now also the code of conduct committee! I care deeply about the Typelevel community and creating a welcoming and supportive environment for everyone. I live in Canada, and when I'm not doing computer related activities I like to read books and play with my dog.
+ +

Andrew Valencik (he/him)

+
Hi, I'm Andrew, I live in Ottawa, Canada. I hang out around Typelevel because the people are kind and welcoming. I want to help keep our community a safe and fun space. I think the libraries are pretty cool too and enjoy contributing to open source when I can. When I'm not thinking about computers I'm probably thinking about food or my two cats.
+ +

What does this mean for our community?

+

Code of Conduct members will work in accordance with the Code of Conduct and Enforcement Policy. + The team is expected to evaluate reported incidents about Code of Conduct violations and propose behavior adjustments or consequences for the reported behavior which will be done with the help of community moderators. + This ensures Code of Conduct violations are dealt with promptly and all community members feel welcome and safe as part of the Typelevel community.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/new-website-layout.html b/blog/new-website-layout.html new file mode 100644 index 00000000..86523f1b --- /dev/null +++ b/blog/new-website-layout.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + New Website Layout Launched + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

New Website Layout Launched

+ + + governance + +
+
+
+
+

New Website Layout Launched

+

In August, you may have noticed that Typelevel.org has a new layout! We are grateful to our old friends at 47 Degrees for their generously donated time and effort in planning, designing, and implementing this long-needed revamp. Our deepest thanks to the following individuals from 47 Degrees for working with our Steering Committee on a proposal: Israel "Isra" Pérez, Jetro Cabau Quirós, Maureen Elsberry, Benjy Montoya, and Raúl Raja Martínez.

+

Special thanks again to Isra for the months-long development work in Jekyll, our own Ross Baker for safely resolving countless merge conflicts, our own Jasna Rodulfa-Blemberg for bravely pushing the final merge to our main development branch, and all Steering Committee members for working with Isra and Raul in identifying follow-up improvements.

+

Some issues have already been filed against this new layout, and we thank our community for taking the time to let us know. If you find any other issues or have ideas to improve the layout, please do not hesitate to open up an issue in our typelevel.org github issues section. You can also always start up a conversation in our Discord server.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/on-recent-events.html b/blog/on-recent-events.html new file mode 100644 index 00000000..685c46c2 --- /dev/null +++ b/blog/on-recent-events.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + On Recent Events + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

On Recent Events

+ + + governance + +
+
+
+
+

On Recent Events

+

Open source developers are free to choose the projects they contribute to and the communities they support. Martin Odersky’s recent objection to an exercise of this freedom threw the community into turmoil. We believe that those who questioned his intervention and spoke out were justified in doing so. Regardless, we encourage everyone to consider the tone and weight of their words before hitting “send”.

+

We stand with those who feel excluded by the notion of politics being somehow optional, a notion we disagree with. Typelevel was founded with a mission to provide an inclusive, welcoming and safe environment, and we remain committed to that goal. We believe that human rights cannot be dismissed as mere “politics”.

+

The Scala Center, which has a long and celebrated history of success in education and technical improvement to the tooling we use daily, has repeatedly been expected to take on a community management role beyond its chartered goals, for which it is ill-equipped and underfunded. We acknowledge the challenges they face and we support the Scala Community Management and Governance strategy under consideration as a promising way forward.

+

Typelevel has functioned with a not-very-formal governance structure of volunteers for some time. This group is known as the Typelevel Steering Committee. In the coming weeks we will formalize how leadership is selected and what the roles and responsibilities are.

+

The Typelevel Steering Committee:

+
    +
  • Ross A. Baker
  • +
  • Oscar Boykin
  • +
  • Christopher Davenport
  • +
  • Luka Jacobowitz
  • +
  • Alexandru Nedelcu
  • +
  • Rob Norris
  • +
  • Michael Pilquist
  • +
  • Miles Sabin
  • +
  • Daniel Spiewak
  • +
  • Kailuo Wang
  • +
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/optimizing-final-tagless.html b/blog/optimizing-final-tagless.html new file mode 100644 index 00000000..8a9c5bc7 --- /dev/null +++ b/blog/optimizing-final-tagless.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + + + + + + + Optimizing Tagless Final – Saying farewell to Free + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Optimizing Tagless Final – Saying farewell to Free

+ + + technical + +
+
+
+
+

Optimizing Tagless Final – Saying farewell to Free

+

The Tagless Final encoding has gained some steam recently, with some people hailing 2017 as the year of Tagless Final. + Being conceptually similar to the Free Monad, different comparisons have been brought up and the one trade-off that always comes up is the lack or the difficulty of inspection of tagless final programs and in fact, I couldn't find a single example on the web. + This seems to make sense, as programs in the tagless final encoding aren't values, like programs expressed in terms of free structures. + However, in this blog post, I'd like to dispell the myth that inspecting and optimizing tagless final programs is more difficult than using Free.

+

Without further ado, let's get into it, starting with our example algebra, a very simple key-value store:

+
trait KVStore[F[_]] {
+  def get(key: String): F[Option[String]]
+  def put(key: String, a: String): F[Unit]
+}
+

To get the easiest example out of the way, here's how to achieve parallelism in a tagless final program:

+
import cats._
+import cats.implicits._
+
+def program[M[_]: FlatMap, F[_]](a: String)(K: KVStore[M])(implicit P: Parallel[M, F]) =
+  for {
+    _ <- K.put("A", a)
+    x <- (K.get("B"), K.get("C")).parMapN(_ |+| _)
+    _ <- K.put("X", x.getOrElse("-"))
+  } yield x
+

This programs makes use of the cats.Parallel type class, that allows us to make use of the parMapN combinator to use independent computations with a related Applicative type. This is already much simpler than doing the same thing with Free and FreeApplicative. For more info on Parallel check out the cats docs here.

+

However this is kind of like cheating, we're not really inspecting the structure of our program at all, so let's look at an example where we actually have access to the structure to do optimizations with.

+

Let's say we have the following program:

+
def program[F[_]: Apply](F: KVStore[F]): F[List[String]] =
+    (F.get("Cats"), F.get("Dogs"), F.put("Mice", "42"), F.get("Cats"))
+      .mapN((f, s, _, t) => List(f, s, t).flatten)
+

Not a very exciting program, but it has some definite optimization potential. + Right now, if our KVStore implementation is an asynchronous one with a network boundary, our program will make 4 network requests sequentially if interpreted with the standard Apply instance of something like cats.effect.IO. + We also have a duplicate request with the "Cats"-key.

+

So let's look at what we could potentially do about this. + The first thing we should do, is extract the static information. + The easiest way to do so, is to interpret it into something we can use using a Monoid. + This is essentially equivalent to the analyze function commonly found on FreeApplicative.

+

Getting this done, is actually quite simple, as we can use cats.Const as our Applicative data type, whenever the lefthand side of Const is a Monoid. + I.e. if M has a Monoid instance, Const[M, A] has an Applicative instance. + You can read more about Const here.

+
val analysisInterpreter: KVStore[Const[(Set[String], Map[String, String]), ?]] =
+  new KVStore[Const[(Set[String], Map[String, String]), ?]] {
+    def get(key: String) = Const((Set(key), Map.empty))
+    def put(key: String, a: String) = Const((Set.empty, Map(key -> a)))
+  }
+
+program(analysisInterpreter).getConst
+// res0: (Set[String], Map[String,String]) = (Set(Cats, Dogs),Map(Mice -> 42))
+

By using a Tuple of Set and Map as our Monoid, we now get all the unique keys for our get and put operations. + Next, we can use this information to recreate our program in an optimized way.

+
def optimizedProgram[F[_]: Applicative](F: KVStore[F]): F[List[String]] = {
+  val (gets, puts) = program(analysisInterpreter).getConst
+
+  puts.toList.traverse { case (k, v) => F.put(k, v) } 
+    *> gets.toList.traverse(F.get).map(_.flatten)
+}
+

And we got our first very simple optimization. + It's not much, but we can imagine the power of this technique. + For example, if we were using something like GraphQL, we could sum all of our get requests into one large request, so only one network roundtrip is made. + We could imagine similar things for other use cases, e.g. if we're querying a bunch of team members that all belong to the same team, it might make sense to just make one request to all the team's members instead of requesting them all individually.

+

Other more complex optimizations could involve writing a new interpreter with the information we gained from our static analysis. + One could also precompute some of the computations and then create a new interpreter with those computations in mind.

+

Embedding our Applicative program inside a larger monadic program is also trivial:

+
def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] =
+  (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats"))
+    .mapN((f, s, _, t) => List(f, s, t).flatten)
+
+def optimizedProgram[F[_]: Applicative](mouse: String)(F: KVStore[F]): F[List[String]] = {
+  val (gets, puts) = program(mouse)(analysisInterpreter).getConst
+
+  puts.toList.traverse { case (k, v) => F.put(k, v) } 
+    *> gets.toList.traverse(F.get).map(_.flatten)
+}
+
+def monadicProgram[F[_]: Monad](F: KVStore[F]): F[Unit] = for {
+  mouse <- F.get("Mice")
+  list <- optimizedProgram(mouse.getOrElse("64"))(F)
+  _ <- F.put("Birds", list.headOption.getOrElse("128"))
+} yield ()
+

Here we refactor our optimizedProgram to take an extra parameter mouse. Then in our larger monadicProgram, we perform a get operation and then apply its result to optimizedProgram.

+

So now we have a way to optimize our one specific program, next we should see if we can introduce some abstraction. + Sadly Scala lacks Rank-N types, which makes this a bit difficult as we'll see.

+

First we'll have to look at the shape of a generic program, they usually are functions from an interpreter Algebra[F] to an expression inside the type constructor F, such as F[A].

+
type Program[Alg[_[_]], F[_], A] = Alg[F] => F[A]
+

The problem of Rank-N types becomes apparent when we want to write a function where we interpret our program with two different interpreters, as we did before when interpreting into Const:

+
def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid]
+  (program: Alg[F] => F[A])
+  (extract: Alg[Const[M, ?]])
+  (restructure: M => F[A]): Alg[F] => F[A] = { interp =>
+
+    val m = program(extract).getConst // error: type mismatch;
+    // found   : extract.type (with underlying type Alg[[β$0$]cats.data.Const[M,β$0$]])
+    // required: Alg[F]
+
+    restructure(m)
+  }
+

So, because of the lack of Rank-N types, this simple definition for our program is not enough to say that our program works for ALL type constructors F[_]: Applicative.

+

Fortunately there is a workaround, albeit requiring a bit more boilerplate:

+
trait Program[Alg[_[_]], A] {
+  def apply[F[_]: Applicative](interpreter: Alg[F]) : F[A]
+}
+
+def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid]
+  (program: Program[Alg, A])
+  (extract: Alg[Const[M, ?]])
+  (restructure: M => F[A]): Alg[F] => F[A] = { interp =>
+    val m = program(extract).getConst
+
+    restructure(m)
+  }
+

And now it should compile without a problem. + Now we should be able to express our original optimization with this new generic approach:

+
def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] =
+  (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats"))
+    .mapN((f, s, _, t) => List(f, s, t).flatten)
+
+def wrappedProgram(mouse: String) = new Program[KVStore, List[String]] {
+  def apply[F[_]: Applicative](alg: KVStore[F]): F[List[String]] = program(mouse)(alg)
+}
+
+def optimizedProgram[F[_]: Applicative](mouse: String)(F: KVStore[F]): KVStore[F] => F[List[String]] = 
+  optimize(wrappedProgram(mouse))(analysisInterpreter) { case (gets, puts) =>
+    puts.toList.traverse { case (k, v) => F.put(k, v) } *> gets.toList.traverseFilter(F.get)
+  }
+

So far so good, we've managed to write a function to generically optimize tagless final programs. + However, one of the main advantages of tagless final is that implementation and logic should be separate concerns. + With what we have right now, we're violating the separation, by mixing the optimization part with the program logic part. + Our optimization should be handled by the interpreter, just as the sequencing of individual steps of a monadic program is the job of the target Monad instance.

+

One way to go forward, is to create a typeclass that requires certain algebras to be optimizable. + This typeclass could be written using the generic function we wrote before, so let's see what we can come up with:

+
trait Optimizer[Alg[_[_]], F[_]] {
+  type M
+
+  def monoidM: Monoid[M]
+  def monadF: Monad[F]
+
+  def extract: Alg[Const[M, ?]]
+  def rebuild(m: M, interpreter: Alg[F]): F[Alg[F]]
+
+  def optimize[A](p: Program[Alg, A]): Alg[F] => F[A] = { interpreter =>
+    implicit val M: Monoid[M] = monoidM
+    implicit val F: Monad[F] = monadF
+
+    val m: M = p(extract).getConst
+
+    rebuild(m, interpreter).flatMap(interp => p(interp))
+  }
+}
+

This might look a bit daunting at first, but we'll go through it bit by bit. + First we define our type class Optimizer parameterized by an algebra Alg[_[_]] and a type constructor F[_]. + This means we can define different optimizations for different algebras and different target types. + For example, we might want a different optimization for a production Optimizer[KVStore, EitherT[Task, E, ?]] and a testing Optimizer[KVStore, Id]. + Next, for our interpreter we need a Monoid M for our static analysis, however we don't to parameterize our Optimizer with an extra type parameter, since the actual type of M isn't necessary for the API, so we use an abstract type member instead.

+

Next we need actual Monoid and Monad instances for F[_] and M respectively. + The other two functions should seem familiar, the extract function defines an interpreter to get an M out of our program. + The rebuild function takes that value of M and the interpreter and produces an F[Alg[F]], which can be understood as an F of an interpreter. + This means that we can statically analyze a program and then use the result of that to create a new optimized interpreter and this is exactly what the optimize function does. + This is also why we needed the Monad constraint on F, we could also get away with returning just a new interpreter Alg[F] from the rebuild method and get away with an Applicative constraint, but we can do more different things this way.

+

We'll also define some quick syntax sugar for this type class to make using it a tiny bit more ergonomic.

+
implicit class OptimizerOps[Alg[_[_]], A](val value: Program[Alg, A]) extends AnyVal {
+  def optimize[F[_]: Monad](interp: Alg[F])(implicit O: Optimizer[Alg, F]): F[A] =
+    O.optimize(value)(interp)
+}
+

Let's see what our program would look like with this new functionality:

+
def monadicProgram[F[_]: Monad](F: KVStore[F])(implicit O: Optimizer[KVStore, F]): F[Unit] = for {
+  mouse <- F.get("Mice")
+  list <- wrappedProgram(mouse.getOrElse("64")).optimize(F)
+  _ <- F.put("Birds", list.headOption.getOrElse("128"))
+} yield ()
+

Looking good so far, now all we need to run this is an actual instance of Optimizer. + We'll use a Monix Task for this and for simplicity our new optimization will only look at the get operations:

+
implicit val kvStoreTaskOptimizer: Optimizer[KVStore, Task] = new Optimizer[KVStore, Task] {
+  type M = Set[String]
+
+  def monoidM = implicitly
+
+  def monadF = implicitly
+
+  def extract = new KVStore[Const[Set[String], ?]] {
+    def get(key: String) = Const(Set(key))
+    def put(key: String, a: String): Const[Set[String], Unit] = Const(Set.empty)
+  }
+
+  def rebuild(gs: Set[String], interp: KVStore[Task]): Task[KVStore[Task]] =
+    gs.toList
+      .parTraverse(key => interp.get(key).map(_.map(s => (key, s))))
+      .map(_.flattenOption.toMap)
+      .map { m =>
+        new KVStore[Task] {
+          override def get(key: String) = m.get(key) match {
+            case v @ Some(_) => v.pure[Task]
+            case None => interp.get(key)
+          }
+
+          def put(key: String, a: String): Task[Unit] = interp.put(key, a)
+        }
+      }
+
+}
+

Our Monoid type is just a simple Set[String] here, as the extract function will only extract the get operations inside the Set. + Then with the rebuild we build up our new interpreter. + First we want to precompute all the values of the program. + To do so, we just run all the operations in parallel and put them into a Map, while discarding values where the get operation returned None. + Now when we have that precomputed Map, we'll create a new interpreter with it, that will check if the key given to get operation is in the precomputed Map instead of performing an actual request. + We can then lift the value into a Task[Option[String]]. + For all the put operations, we'll simply run the interpreter.

+

Now we should have a great optimizer for KVStore programs interpreted into a Task. + Let's see how we did by interpreting into a silly implementation that only prints whenever you use one of the operations:

+
object TestInterpreter extends KVStore[Task] {
+  def get(key: String): Task[Option[String]] = Task {
+
+    println("Hit network for " + key)
+
+    Option(key + "!")
+  }
+
+  def put(key: String, a: String): Task[Unit] = Task {
+    println("Put something: " + a)
+
+    ()
+  }
+}
+

Now let's run our program with this interpreter and the optimizations!

+
monadicProgram(TestInterpreter).runAsync
+// Hit network for Mice
+// Hit network for Cats
+// Hit network for Dogs
+// Put something: Mice!
+// Put something: Cats!
+

And it works, we've now got a principled way to write programs that can then be potentially optimized.

+ +

Conclusion

+

Designing a way to completely separate the problem description from the actual problem solution is fairly difficult. The tagless final encoding allows us one such fairly simple way. + Using the technique described in this blog post, we should be able to have even more control over the problem solution by inspecting the structure of our program statically. + We've seen a few roadblocks along the way, such as the lack of Rank-N types in Scala, but we might be able to come up with a macro for that in the future, making it even more ergonomic. + Another thing we haven't covered here, are programs with multiple algebras, which is quite a bit more complex as you can surely imagine, maybe that will be the topic of a follow up blog post.

+

The code is published right here, but might still change after getting a feeling for which API feels best.

+

What kind of problems and techniques would you like to see with regards to tagless final? + Would love to hear from you in the comments!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/optimizing-tagless-final-2.html b/blog/optimizing-tagless-final-2.html new file mode 100644 index 00000000..ed1b64ea --- /dev/null +++ b/blog/optimizing-tagless-final-2.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + Optimizing Tagless Final – Part 2 – Monadic programs + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Optimizing Tagless Final – Part 2 – Monadic programs

+ + + technical + +
+
+
+
+

Optimizing Tagless Final – Part 2 – Monadic programs

+

In our previous post on optimizing tagless final programs we learned how we could use the sphynx library to derive some optimization schemes for your tagless final code. In case you missed it and want to read up on it, you can find it right here or you can watch my presentation on the topic here, but you should be able to follow this blog post without going through it all in detail.

+ +

Optimizing monadic programs

+

One of the questions I've been getting a lot, is if we can also do something like that for the monadic parts of our program. + The answer is yes, we can, however it will have to be quite a bit different.

+

I don't think the differences are quite obvious, so we'll go through them step by step. + With applicative programs, we're optimizing a bunch of independent instructions. + That means, we can look at all of them and extract information out of them statically (i.e. without running the interpreter). + They can be seen as a sequence of instructions that we can fold down to a single monoid M, that holds the information that we need to optimize. + We then used that monoid to recreate a new interpreter that can take this extra information into account.

+

With monadic programs, we do not have such luxury. + We can only step through each of our instructions one at a time, because every instruction depends on the results of the prior one. + This means that we cannot extract any information beyond the very first instruction we have. + That might seem like a deal breaker, but there's still a few things we can do. + We could, for example, build up our monoid M dynamically, after each monadic instruction. + Then, before invoking the next computation in the monadic sequence, we could take that monoid and recreate that next computation with that extra information.

+

Now, that might sound super abstract to you, and I wouldn't disagree, so let's look at a quick example. + Say, we're using the KVStore algebra again from last time:

+
trait KVStore[F[_]] {
+  def get(key: String): F[Option[String]]
+  def put(key: String, value: String): F[Unit]
+}
+

We could optimize programs with this algebra by caching the results of get and we could use that same cache to also cache key-value pairs we inserted using put.

+

So given this example program:

+
def program[F[_]: Monad](key: String)(F: KVStore[F]): F[List[String]] = for {
+  _ <- F.put(key, "cat")
+  dog <- F.get(key)
+  cat <- F.get(dog.getOrElse("dog"))
+  cat2 <- F.get("cat")
+} yield List(dog, cat, cat2).flatten
+

The naively interpreted program would be doing the following things: + 1. put the value "cat" into the store with the key passed by the user + 2. get the value "cat" back out of the store + 3. access the key-value store and maybe return a value associated with the key "cat" + 4. access the store again with the same "cat" key.

+

Now if accessing the key-value store means going through a network layer this is of course highly inefficient. + Ideally our fully optimized program should do the following things: + 1. put the value "cat" into the store with the key parameter passed by the user and cache it. + 2. access the cache to get the value "cat" associated with key + 3. access the key-value-store and maybe return a value associated with the key "cat" + 4. access the cache to return the previous result for the "cat" key.

+

Cool, next, let's look at how we might get there. + First the type of our cache, which for our case can just be a Map[String, String], but generically could just be any monoid.

+

Now what we want to do is transform any interpreter for KVStore programs into interpreters that + 1. Look in the cache before performing a get action with the actual interpreter + 2. Write to the cache after performing either a get or put action.

+

So how can we get there? It seems like we want to thread a bunch of state through our program, that we want to both read and write to. + If you're familiar with FP folklore you might recognize that that description fits almost exactly to the State monad. + Furthermore, because we know that our F[_] is a monad, that means the StateT monad transformer over F will also be a monad.

+

Okay with that said, let's try to develop function that turns any interpreter KVStore[F] into an interpreter into StateT[F, M, A], so an KVStore[StateT[F, M, ?]], where M is the monoid we use to accumulate our extracted information. + We'll start with the put operation. + For put, we'll want to call the interpreter to perform the action and then modify the state by adding the retrieved value into our cache. + To make the code a bit more legible we'll also define a few type aliases.

+
type Cache = Map[String, String]
+type CachedAction[A] = StateT[F, Cache, A]
+
+def transform(interp: KVStore[F]): KVStore[CachedAction] = new KVStore[CachedAction] {
+  def put(key: String, v: String): CachedAction[Unit] =
+    StateT.liftF[F, Cache, Unit](interp.put(key, v)) *> StateT.modify(_.updated(key, v))
+
+  def get(key: String): CachedAction[Option[String]] = ???
+}
+

So far, so good, now let's have a look at what to do with the get function. + It's a bit more complex, because we want to read from the cache, as well as write to it if the cache didn't include our key. + What we have to do is, get our current state, then check if the key is included, if so, just return it, otherwise call the interpreter to perform the get action and then write that into the cache.

+
def get(key: String): CachedAction[Option[String]] = for {
+  cache <- StateT.get[F, Cache]
+  result <- cache.get(key) match {
+              case s @ Some(_) => s.pure[CachedAction]
+              case None => StateT.liftF[F, Cache, Option[String]](interp.get(key))
+                             .flatTap(updateCache(key))
+            }
+} yield result
+
+def updateCache(key: String)(ov: Option[String]): CachedAction[Unit] = ov match {
+  case Some(v) => StateT.modify(_.updated(key, v))
+  case None => ().pure[CachedAction]
+}
+

This is quite something, so let's try to walk through it step by step. + First we get the cache using StateT.get, so far so good. + Now, we check if the key is in the cache using cache.get(key). + The result of that is an Option[String], which we can pattern match to see if it did include the key. + If it did, then we can just return that Option[String] by lifting it into CachedAction using pure. + If it wasn't in the cache, things are a bit more tricky. + First, we lift the interpreter action into CachedAction using StateT.liftF, that gives us a CachedAction[Option[String]], which is already the return type we need and we could return it right there, but we still need to update the cache. + Because we already have the return type we need, we can use the flatTap combinator. + Then inside the updateCache function, we take the result of our interpreter, which is again an Option[String], and update the cache if the value is present. + If it's empty, we don't want to do anything at all, so we just lift unit into CachedAction.

+

In case you're wondering flatTap works just like flatMap, but will then map the result type back to the original one, making it a bit similar to a monadic version of the left shark (<*) operator, making it very useful for these "fire-and-forget" operations. + It's defined like this:

+
def flatTap[F[_]: Monad, A, B](fa: F[A])(f: A => F[B]): F[A] =
+  fa.flatMap(a => f(a).map(b => a))
+

And with that we now have a working function to turn any interpreter into an optimized interpreter. + We can also generalize this fairly easily into a function that will do all of the wiring for us. + To do so, we'll generalize away from KVStore and Cache and instead use generic Alg[_[_]] and M parameters:

+
def optimize[Alg[_[_]], F[_]: Monad, M: Monoid, A]
+  (program: MonadProgram[Alg, A])
+  (withState: Alg[F] => Alg[StateT[F, M, ?]]): Alg[F] => F[A] = interpreter =>
+    program(withState(interpreter)).runEmptyA
+

Just like last time, we have to use a MonadProgram wrapper around Alg[F] => F[A], because Scala lacks rank-N types which would allow us to define values that work over ALL type constructors F[_]: Monad (Fortunately however, this will very probably soon be fixed in dotty, PR here).

+

Now let's see if we can actually use it, by checking it with a test interpreter that will print whenever we retrieve or insert values into the KVStore.

+
optimize[KVStore, IO, Cache, List[String]](program("mouse"))(transform)
+  .apply(printInterpreter)
+  .unsafeRunSync()
+
+// Put key: mouse, value: cat
+// Get key: cat
+

It works and does exactly what we want! + Nice! We could end this blog post right here, but there's still a couple of things I'd like to slightly alter.

+ +

Refining the API

+

As you were able to tell the implementation of our transformation from the standard interpreter to the optimized interpreter is already quite complex and that is for a very very simple algebra that doesn't do a lot. + Even then, I initially wrote an implementation that packs everything in a single StateT constructor to avoid the overhead of multiple calls to flatMap, but considered the version I showed here more easily understandable. + For more involved algebras and more complex programs, all of this will become a lot more difficult to manage. + In our last blog post we were able to clearly separate the extraction of our information from the rebuilding of our interpreter with that information. + Let's have a look at if we can do the same thing here.

+

First we'll want to define an extraction method. + For applicative programs we used Const[M, ?], however that cannot work here, as Const doesn't have a Monad instance and also, because for extraction with monadic programs, we need to actually take the result of the computation into account. + That means, that for every operation in our algebra, we want a way to turn it into our monoid M. + With that said, it seems we want a function A => M, where A is the result type of the operations in our algebra. + So what we can do here is define an algebra for ? => M, in types an Alg[? => M].

+

Let's try to do define such an interpreter for our KVStore along with Cache/Map[String, String:

+
def extract: KVStore[? => Cache] = new KVStore[? => Cache] {
+  def get(key: String): Option[String] => Cache = {
+    case Some(s) => Map(key -> s)
+    case None => Map.empty
+  }
+
+  def put(key: String, a: String): Unit => Cache =
+    _ => Map(key -> a)
+}
+

Just as before we want to extract the cache piece by piece with every monadic step. + Whenever we get an Option[String] after using get, we can then turn that into a Cache if it's non-empty. + The same goes for put, where we'll create a Map using the key-value pair. + We now have a way to turn the results of our algebra operations into our information M, so far so good!

+

Next, we'll need a way to rebuild our operations using that extracted information. + For that, let's consider what that actually means. + For applicative programs this meant a function that given a state M and an interpreter Alg[F], gave a reconstructed interpreter inside the F context F[Alg[F]]. + So a function (M, Alg[F]) => F[Alg[F]].

+

For monadic programs, there's no need to precompute any values, as we're dealing with fully sequential computations that can potentially update the state after every evaluation. + So we're left with a function (M, Alg[F]) => Alg[F]. + Let's try building that for KVStore:

+
def rebuild(m: M, interp: KVStore[F]): KVStore[F] = new KVStore[F] {
+  def get(key: String): F[Option[String]] = m.get(key) match {
+    case o @ Some(_) => Monad[F].pure(o)
+    case None => interp.get(key)
+  }
+
+  def put(key: String, a: String): F[Unit] =
+    m => interp.put(key, a)
+}
+

Easy enough! + For get we look inside our cache and use the value if it's there, otherwise we call the original interpreter to do its job. + For put, there's nothing to gain from having access to our extracted information and the only thing we can do is call the interpreter and let it do what needs to be done.

+

Now we have a way to extract information and then also use that information, next up is finding a way to wire these two things together to get back to the behaviour we got using StateT.

+

And as a matter of fact, we'll wire them back together using exactly StateT, as it's monad instance does do exactly what we want.

+

Using our two functions extract and rebuild it's fairly easy to get back to KVStore[StateT[F, Cache, ?]]:

+
def transform(interp: KVStore[F]): KVStore[StateT[F, Cache, ?]] = new KVStore[StateT[F, Cache, ?]] {
+  def put(key: String, v: String): StateT[F, Cache, Unit] =
+    StateT(cache => rebuild(cache, interp).put(key, v).map(a => 
+      (cache |+| extract.put(key, v)) -> a))
+
+  def get(key: String): StateT[F, Cache, Option[String]] =
+    StateT(cache => rebuild(cache, interp).get(key).map(a => 
+      (cache |+| extract.get(key)) -> a))
+}
+

This is fairly straightforward, we use rebuild with our cache and the interpreter to get a new interpreter that will run the operation. + Then, we use the result, which is just an F[Unit]/F[Option[String]] respectively, and map it + using the extractor to get the newest Cache and using its Monoid instance to update the state and then we tuple it with the result, giving us an F[(Cache, Unit)] or F[(Cache, Option[String])], which is exactly what the StateT constructor needs.

+

This is great, but can we generalize this to any algebra and any monoid?

+

The answer is yes, but it's not exactly easy. + First let's look at the actual problem. + We have two interpreters extract and rebuild, but we have no way to combine them, because Alg, is completely unconstrained and that means we can't call any functions on a generic Alg[F] at all. + So, okay, we need to constrain our Alg parameter to be able to combine values of Alg[F] with values of Alg[G] in some way, but what kind of type class could that be? + Are there even type classes that operate on the kind of Alg?

+ +

Higher kinded things

+

There are, they're just hidden away in a small library called Mainecoon. + That library gives us higher kinded versions of things like functors and contravariant functors, called FunctorK and ContravariantK respectively.

+

Let's have a quick look at FunctorK:

+
@typeclass
+trait FunctorK[A[_[_]]] {
+  def mapK[F[_], G[_]](af: A[F])(f: F ~> G): A[G]
+}
+

Instead of mapping over type constructors F[_], we map over algebras A[_[_]] and insteading of using functions A => B, we use natural transformations F ~> G. + This is nice, but doesn't really get us that far.

+

What we really need is the equivalent of the Applicative/Apply map2 operation. + map2 looks like this:

+
def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+

And a higher kinded version would look like this:

+
def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H]
+

If you haven't guessed yet Tuple2K is just a higher kinded version of Tuple2:

+
type Tuple2K[F[_], G[_], A] = (F[A], G[A])
+

Unfortunately Mainecoon doesn't have an ApplyK type class that gives us this map2K operation, but it gives the next best thing! + A higher-kinded Semigroupal, which when combined with the higher kinded Functor gives us that higher kinded Apply type class. + It's called CartesianK (because cats Semigroupal used to be called Cartesian, but is renamed to SemigroupalK in the next version) and looks like this:

+
@typeclass 
+trait CartesianK[A[_[_]]] {
+  def productK[F[_], G[_]](af: A[F], ag: A[G]): A[Tuple2K[F, G, ?]]
+}
+

Now just like you can define map2 using map and product we can do the same for map2K:

+
def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H] =
+  productK(af, ag).mapK(f)
+ +

Putting it all together

+

Okay, after that quick detour, let's have a look at how can make use of these type classes.

+

If we look at what we have and how we'd like to use the map2K function, we can infer the rest that we need quite easily.

+

We have an Alg[F] and a Alg[? => M], and we want an Alg[StateT[F, M, ?]], so given those two as the inputs to map2K, all that seems to be missing is the natural transformation Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]. + Nice! As so often, the types guide us and show us the way.

+

Well let's try to define just that:

+
new (Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]) {
+  def apply[A](fa: Tuple2K[F, ? => M, ?]): StateT[F, M, A] =
+    StateT(m => F.map(fa.first)(a => M.combine(fa.second(a), m) -> a))
+}
+

This looks good, but actually has a problem, to get an Alg[F] from rebuild we give it an M and an interpreter Alg[F]. + The interpreter isn't really a problem, but the M can prove problematic as we need to give it to the rebuild function after each monadic step to always receive the latest state. + If we look at our natural transformation above, that function will never receive the newest state. + So what can we do about this? + Well, we could be a bit more honest about our types:

+
type FunctionM[A] = M => F[A]
+def rebuild(interp: Alg[F]): Alg[FunctionM]
+

Hey, now we're getting there. This works, but if we look into some of the data types provided by Cats we can acutally see that this is just Kleisli or ReaderT, so our rebuild should actually look like this:

+
def rebuild(interp: Alg[F]): Alg[Kleisli[F, M, ?]]
+

And now, we can easily implement a correct version of that natural transformation from earlier:

+
new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) {
+  def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, ?]): StateT[F, M, A] =
+    StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a))
+}
+

Cool, then let us also adjust the rebuild function we created for KVStore:

+
def rebuild(interp: KVStore[F]): KVStore[Kleisli[F, M, ?]] = new KVStore[Kleisli[F, M, ?]] {
+  def get(key: String): Kleisli[F, Cache, Option[String]] = Kleisli(m => m.get(key) match {
+    case o @ Some(_) => Monad[F].pure(o)
+    case None => interp.get(key)
+  })
+
+  def put(key: String, a: String): Kleisli[F, Cache, Unit] =
+    Kleisli(m => interp.put(key, a))
+}
+

It's stayed pretty much the same, we just needed to wrap the whole thing in a Kleisli and we're good!

+

Now we can go ahead and define the full function signature:

+
def optimize[Alg[_[_]]: FunctorK: CartesianK, F[_]: Monad, M: Monoid, A]
+  (program: MonadProgram[Alg, A])
+  (extract: Alg[? => M])
+  (rebuild: Alg[F] => Alg[Kleisli[F, M, ?]]): Alg[F] => F[A] = { interpreter =>
+  
+    val tupleToState = new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) {
+      def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, A]): StateT[F, M, A] =
+        StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a))
+    }
+
+    val withState: Alg[StateT[F, M, ?]] =
+      map2K(extract(interpreter), rebuild))(tupleToState)
+
+    program(withState).runEmptyA
+  
+  }
+

That is all, we've got a fully polymorphic function that can optimize monadic programs.

+

Let's use it!

+
optimize(program)(extract)(rebuild)
+  .apply(printInterpreter)
+  .unsafeRunSync()
+

Now, when we run this, it should be exactly the same result as when we ran it earlier using the direct StateT interpreter, but the resulting code is much cleaner. + However, it does have the drawback that you'll now need additional constraints for every algebra to use this function. + That said though, one of the cool features of Mainecoon is that it comes with auto-derivation. + Meaning we can just add an annotation to any of our algebras and it will automatically derive the FunctorK and CartesianK instances.

+

In fact, that is exactly how I defined those two instances for the KVStore algebra:

+
@autoFunctorK
+@autoCartesianK
+trait KVStore[F[_]] { ... }
+

This makes it fairly easy to use these extra type classes and helpts mitigate the drawbacks I mentioned.

+ +

Conclusions

+

Today we've seen a way to make optimizing monadic tagless final programs easier and intuitive, all the code is taken from the sphynx library and can be found right here, but might still be subject to change, because designing a good API is hard.

+

What do you think about this optimization scheme? Maybe you just prefer using StateT and being done with it, or maybe you like to use a typeclass based approach like the one we used last time?

+

Would love to hear from you all in the comments!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/product-with-serializable.html b/blog/product-with-serializable.html new file mode 100644 index 00000000..bc8c6a54 --- /dev/null +++ b/blog/product-with-serializable.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + Product with Serializable + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Product with Serializable

+ + + technical + +
+
+
+
+

Product with Serializable

+

A somewhat common Scala idiom is to make an abstract type extend Product with Serializable. There isn't an obvious reason to do this, and people have asked me a number of times why I've done this. While I don't think that Product or Serializable are particularly good abstractions, there's a reason that I extend them.

+

Let's say that I'm writing a simple enum-like Status type:

+
object EnumExample1 {
+  sealed abstract class Status
+  case object Pending extends Status
+  case object InProgress extends Status
+  case object Finished extends Status
+}
+

Now let's create a Set of statuses that represent incomplete items:

+
import EnumExample1._
+
val incomplete = Set(Pending, InProgress)
+// incomplete: scala.collection.immutable.Set[Product with Serializable with EnumExample1.Status] = Set(Pending, InProgress)
+

Here, I didn't give in explicit return type to incomplete and you may have noticed that the compiler inferred a somewhat bizarre one: Set[Product with Serializable with Status]. Why is that?

+

The compiler generally tries to infer the most specific type possible. Usually this makes sense. If you write val x = 3 you probably don't want it to infer val x: Any = 3. And in the example above, I didn't want the return type for incomplete to be inferred as Any or even Set[Any]. However, the compiler was a bit too clever and realized that not only is every item in the set an instance of Status, they are also instances of Product and Serializable since every case object (and case class) automatically extends Product and Serializable. Therefore, when it calculates the least upper bound (LUB) of the types in the set, it comes up with Product with Serializable with Status.

+

While there's nothing inherently wrong with the return type of Product with Serializable with Status, it is verbose, it wasn't what I intended, and in certain situations it might cause inference issues. Luckily there's a simple workaround to get the inferred type that I want:

+
object EnumExample2 {
+  // note the `extends` addition here
+  sealed abstract class Status extends Product with Serializable
+  case object Pending extends Status
+  case object InProgress extends Status
+  case object Finished extends Status
+}
+
import EnumExample2._
+
val incomplete = Set(Pending, InProgress)
+// incomplete: scala.collection.immutable.Set[EnumExample2.Status] = Set(Pending, InProgress)
+

Now since Status itself already includes Product and Serializable, Status is the LUB type of Pending, InProgress, and Finished.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Cody Allen + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/rawtypes.html b/blog/rawtypes.html new file mode 100644 index 00000000..41ae3d56 --- /dev/null +++ b/blog/rawtypes.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + Existential types are not raw types + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Existential types are not raw types

+ + + technical + +
+
+
+
+

Existential types are not raw types

+

While this blog is typically strictly for Scala developers interested + in strongly-typed programming, this particular article is of interest + to Java developers as well. You don’t need to know Scala to follow + along.

+

Scala makes a welcome simplification in its type system: + type arguments + are always required. That is, in Java, you may (unsafely) leave off + the type arguments for compatibility with pre-1.5 code, + e.g. java.util.List, forming a + raw type. + Scala does not permit this, and requires you to pass a type argument.

+

The most frequent trouble people have with this rule is being unable + to implement some Java method with missing type arguments in its + signature, e.g. one that takes a raw List as an argument. Let us + see why they have trouble, and why this is a good thing.

+ +

Existentials are safe, raw types are not

+

Stripping the type argument list, e.g. going from + java.util.List<String> to java.util.List is an unsafe cast. + Wildcarding + the same type argument, e.g. going from java.util.List<String> to + java.util.List<?>, is safe. The latter type is written + java.util.List[_], or java.util.List[T] forSome {type T}, in + Scala. In both Java and Scala, this is an + existential type. + As compiled with -Xlint:rawtypes -Xlint:unchecked:

+
import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class TestEx {
+    public static List<String> words() {
+        return new ArrayList<>(Arrays.asList("hi", "there"));
+    }
+
+    // TestEx.java:17: warning: [rawtypes] found raw type: List
+    //  missing type arguments for generic class List<E>
+    //  where E is a type-variable:
+    //    E extends Object declared in interface List
+    //                  ↓
+    public static final List wordsRaw = words();
+
+    // there is no warning for this
+    public static final List<?> wordsET = words();
+}
+

Also note that there is no warning for the equivalent to wordsET in + Scala. Because it, like javac, knows that it’s safe.

+
scala> TestEx.words
+res0: java.util.List[String] = [hi, there]
+
+scala> val wordsET = TestEx.words : java.util.List[_]
+wordsET: java.util.List[_] = [hi, there]
+ +

Raw Types are bad. Stop using them

+

The reason that existentials are safe is that the rules in place for + values of existential type are consistent with the rest of the generic + system, whereas raw types contradict those rules, resulting in code + that should not typecheck, and only does for legacy code support. We + can see this in action with two Java methods.

+
public static void addThing(final List xs) {
+    xs.add(42);
+}
+
+public static void swapAround(final List<?> xs) {
+    xs.add(84);
+}
+

These methods are the same, except for the use of raw types versus + existentials. However, the second does not compile:

+
TestEx.java:26: error: no suitable method found for add(int)
+        xs.add(84);
+          ^
+    method Collection.add(CAP#1) is not applicable
+      (argument mismatch; int cannot be converted to CAP#1)
+    method List.add(CAP#1) is not applicable
+      (argument mismatch; int cannot be converted to CAP#1)
+  where CAP#1 is a fresh type-variable:
+    CAP#1 extends Object from capture of ?
+

Why forbid adding 42 to the list? The element type of list is + unknown. The answer lies in that statement: its unknownness isn’t a + freedom for the body of the method, it’s a restriction. The rawtype + version treats its lack of knowledge as a freedom, and the caller pays + for it by having its data mangled.

+
public static void testIt() {
+    final List<String> someWords = words();
+    addThing(someWords);
+    System.out.println("Contents of someWords after addThing:");
+    System.out.println(someWords);
+    System.out.println("Well that seems okay, what's the last element?");
+    System.out.println(someWords.get(someWords.size() - 1));
+}
+

And it compiles:

+
TestEx.java:23: warning: [unchecked] unchecked call to add(E) as a
+                         member of the raw type List
+        xs.add(42);
+              ^
+  where E is a type-variable:
+    E extends Object declared in interface List
+

But when we try to run it:

+
scala> TestEx.testIt()
+Contents of someWords after addThing:
+[hi, there, 42]
+Well that seems okay, what's the last element?
+java.lang.ClassCastException: java.lang.Integer cannot be cast to
+                              java.lang.String
+  at rawtypes.TestEx.testIt(TestEx.java:32)
+  ... 43 elided
+

It is a mistake to think that just because some code throws + ClassCastException, it must be to blame for a type error. This line + is blameless. It is the fault of the unchecked cast when we called + addThing, and more specifically, the unsafe assumption about the + List’s element type that was made in its body.

+ +

Existentials are much better

+

When we used the wildcard, we were forbidden from doing the unsafe + thing. But what kinds of things can we do with the safe, existential + form? Here’s one:

+
private static <E> void swapAroundAux(final List<E> xs) {
+    xs.add(xs.get(0));
+}
+
+public static void swapAround(final List<?> xs) {
+    swapAroundAux(xs);
+}
+

In other words: let E be the unknown element type of xs. + xs.get() has type E, and xs.add has argument type E. They + line up, so this is okay, no matter what the element type of xs + turns out to be. Let’s try a test:

+
scala> val w = TestEx.words
+w: java.util.List[String] = [hi, there]
+
+scala> TestEx.swapAround(w)
+
+scala> w.get(w.size - 1)
+res1: String = hi
+

The body of swapAround is guaranteed not to mangle its argument by + the type checker, so we, as a caller, can safely call it, and know + that our argument’s type integrity is protected.

+

Scala has more features to let us get away without swapAroundAux. + This translation uses a lowercase + type variable pattern + to name the existential. To the right of the =>, we can declare + variables of type e and use e to construct more types, while still + referring to the _ in the xs argument’s type. But in this case, + we just do the same as swapAroundAux above.

+
def swapAround(xs: java.util.List[_]): Unit =
+  xs match {
+    case xs2: java.util.List[e] => xs2.add(xs2.get(0))
+  }
+ +

Crushing the existential

+

Let’s consider the xs.get() and xs.add methods, which have return + type and argument type E, respectively. As you can’t write the name + of an existential type in Java, what happens when we “crush” it, + choosing the closest safe type we can write the name of?

+

First, we can simplify by considering every existential to be bounded. + That is, instead of E, we think about E extends Object super +Nothing, or E <: Any >: Nothing in Scala. While Object or Any + is the “top” of the type hierarchy, which every type is a subtype + of, Nothing is the “bottom”, sadly left out of Java’s type system, + which every type is a supertype of.

+

For get, the E appears in the result type, a covariant position. + So we crush it to the upper bound, Any.

+
scala> wordsET.get _
+res2: Int => Any = <function1>
+

However, for add, the E appears in the argument type, a + contravariant position. So if it is to be crushed, it must be + crushed to the lower bound, Nothing, instead.

+
scala> (wordsET: java.util.Collection[_]).add _ : (Any => Boolean)
+<console>:12: error: type mismatch;
+ found   : _$1 => Boolean where type _$1
+ required: Any => Boolean
+              (wordsET: java.util.Collection[_]).add _ : (Any => Boolean)
+                                                 ^
+scala> (wordsET: java.util.Collection[_]).add _ : (Nothing => Boolean)
+res8: Nothing => Boolean = <function1>
+

Each occurrence of an existential in a signature may be crushed + independently. However, a variable that appears once but may be + distributed to either side, such as in a generic type parameter, is + invariant, and may not be crushed at that point. That is why the + existential is preserved in the inferred type of wordsET itself.

+
scala> wordsET
+res9: java.util.List[_] = [hi, there]
+

Herein lies something closer to a formalization of the problem with + raw types: they crush existential occurrences in contravariant and + invariant positions to the upper bound, Object, when the only safe + positions to crush in this way are the covariant positions.

+ +

How do List and List<?> relate?

+

It is well understood that, in Java, List<String> is not a subtype + of List<Object>. In Scala terms, this is because all type + parameters are invariant, which has exactly the meaning it had in + the previous section. However, that doesn’t mean it’s impossible to + draw subtyping relationships between different Lists for different + type arguments; they must merely be mediated by existentials, as is + common in the Java standard library.

+

The basic technique is as follows: we can convert any T in List<T> + to ? extends T super T. Following that, we can raise the argument + to extends and lower the argument to super as we like. A ? by + itself, I have described above, is merely the most extreme course of + this formula you can take. So List<T> for any T is a subtype of + List<?>. (This only applies at one level of depth; + e.g. List<List<T>> is not necessarily a subtype of List<List<?>>.)

+

Does this mean that List is a subtype of List<?>? Well, kind of. + Following the rule for specialization of method signatures in + subclasses, we should be able to override a method that returns + List<?> with one that returns List, and override a method that + takes List as an argument with one that takes List<?> as an + argument. However, this is like building a house on a foam mattress: + the conversion that got us a raw type wasn’t sound in the first place, + so what soundness value does this relationship have?

+ +

The frequent Java library bug

+

Let’s see the specific problem that people usually encounter in Scala. + Suppose addThing, defined above, is an instance member of TestEx:

+
class TestEx2 extends TestEx {
+    @Override
+    public void addThing(final List<?> xs) {}
+}
+

Or the Scala version:

+
class TestEx3 extends TestEx {
+  override def addThing(xs: java.util.List[_]): Unit = ()
+}
+

javac gives us this error:

+
TestEx.java:48: error: name clash: addThing(List<?>) in TestEx2 and
+                addThing(List) in TestEx have the same erasure, yet
+                neither overrides the other
+    public void addThing(final List<?> xs) {}
+                ^
+TestEx.java:47: error: method does not override or implement a method
+                from a supertype
+    @Override
+    ^
+

scalac is forgiving, though. I’m not sure how forgiving it is. + However, the forgiveness is unsound: it lets us return less specific + types when overriding methods than we got out.

+ +

How to fix it

+
    +
  1. +

    Stop using raw types.

    +
  2. +
  3. +

    If you maintain a Java library with raw types in its API, you are + doing a disservice to your users. Eliminate them.

    +
  4. +
  5. +

    If you are using such a library, report a bug, or submit a patch, + to eliminate the raw types. If you add -Xlint:rawtypes to the + javac options, the compiler will tell you where you’re using + them. Fix all the warnings, and you’re definitely not using raw + types anymore.

    +
  6. +
  7. +

    Help Java projects, including your own, avoid introducing raw types + by adding -Xlint:rawtypes permanently to their javac options. + rawtypes is more serious than unchecked; even if you do not + care about unchecked warnings, you should still turn on and fix + rawtypes warnings.

    +
  8. +
+

You may also turn on -Xlint:cast to point out casts that are no + longer necessary now that your types are cleaner. If possible, add + -Werror to your build as well, to convert rawtypes warnings to + errors.

+ +

Why not just add wildcards automatically?

+

Adding wildcards isn’t a panacea. For certain raw types, you need to + add a proper type parameter, even adding type parameters to your own + API. The Internet has no copy and paste solutions to offer you; it + all depends on how to model your specific scenario. Here are a few + possibilities.

+
    +
  1. +

    Pass a type argument representing what’s actually in the structure. + For example, replace List with List<String> if that’s what it + is.

    +
  2. +
  3. +

    Pass a wildcard.

    +
  4. +
  5. +

    Propagate the type argument outward. For example, if you have a + method List doThis(final List xs), maybe it should be <E> +List<E> doThis(final List<E> xs). Or if you have a class +Blah<X> containing a List, maybe it should be a class Blah<A, +X> containing a List<A>. This is often the most flexible + option, but it can take time to implement.

    +
  6. +
  7. +

    Combine any of these. For example, in some circumstances, a more + flexible version of #3 would be to define Blah<A, X> containing a + List<? extends A>.

    +
  8. +
+

Wildcards and existentials are historically misunderstood in the Java + community; Scala developers have the advantage of more powerful + language tools for talking about them. So if you are unsure of how + to eliminate some raw types, consider asking a Scala developer what to + do! Perhaps they will tell you “use Scala instead”, and maybe that’s + worth considering, but you’re likely to get helpful advice regardless + of how you feel about language advocacy.

+ +

The Scala philosophy

+

As you can see, the Java compatibility story in Scala is not as simple + as is advertised. However, I favor the strong stance against this + unsound legacy feature. If Scala can bring an end to the scourge of + raw types, it will have been worth the compatibility trouble.

+

This article was tested with Scala 2.11.5 and javac 1.8.0_31.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/refactoring-monads.html b/blog/refactoring-monads.html new file mode 100644 index 00000000..d7e63cb1 --- /dev/null +++ b/blog/refactoring-monads.html @@ -0,0 +1,536 @@ + + + + + + + + + + + + + + + + + + + + + + + Refactoring with Monads + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Refactoring with Monads

+ + + technical + +
+
+
+
+

Refactoring with Monads

+

I was recently cleaning up some Scala code I'd written a few months + ago when I realized I had been structuring code in a very confusing + way for a very long time. At work, we've been trying to untangle the + knots of code that get written by different authors at different + times, as requirements inevitably evolve. We all know that code should + be made up of short, easily digestible functions but we don't always + get guidance on how to achieve that. In the presence of error handling + and nested data structures, the problem gets even harder.

+

The goal of this blog post is to describe a concrete strategy for + structuring code so that the overall flow of control is clear to the + reader, even months later; and so the smaller pieces are both + digestible and testable. I'll start by giving an example function, + operating on some nested data types. Then I'll explore some ways to + break it into smaller pieces. The key insight is that we can use + computational effects in the form of + monads (more + specifically, + MonadError) to + wrap smaller pieces and ultimately, compose them into an + understandable sequence of computations.

+ +

Example domain: reading a catalog

+

Let's not worry about MonadError yet, but instead look at some + example code. Consider a situation where you need to translate data + from one domain model to another one with different restrictions, and + controlled vocabularies. This can happen in a number of places in a + program, for instance reading a database or an HTTP request to + construct a domain object.

+

Suppose we need to read an object from a relational database. + Unfortunately, rows in the table may represent objects of a variety of + types so we have to read the row and build up the object graph + accordingly. This is the boundary between the weakly typed wilderness + and the strongly typed world within our program.

+

Say our database table represents a library catalog, which might have + print books and ebooks. We'd like to look up a book by ID and get back + a nicely typed record.

+

Here's a simple table

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
idtitleauthorformatdownload_type
45Programming In HaskellHutton, Grahamprintnull
46Programming In HaskellHutton, Grahamebookepub
49Programming In HaskellHutton, Grahamebookpdf
+

We can define a simple domain model:

+
sealed trait Format
+case object Print extends Format
+case object Digital extends Format
+object Format {
+  def fromString(s: String): Try[Format] = ???
+}
+
+sealed trait DownloadType
+case object Epub extends DownloadType
+case object Pdf extends DownloadType
+object DownloadType {
+  def fromString(s: String): Try[DownloadType] = ???
+}
+
+sealed trait Book extends Product with Serializable {
+  def id: Int
+  def title: String
+  def author: String
+  def format: Format
+}
+
+case class PrintBook(
+    id: Int,
+    title: String,
+    author: String,
+) extends Book {
+  override val format: Format = Print
+}
+
+case class EBook(
+    id: Int,
+    title: String,
+    author: String,
+    downloadType: DownloadType
+) extends Book {
+  override val format: Format = Digital
+}
+

We want to be able to define a method such as:

+
def findBookById(id: Int): Try[Book] = ???
+ +

Monolithic function

+

One trivial definition of findBookById might be:

+
import scala.util.{Failure, Success, Try}
+
+def findBookById(id: Int): Try[Book] = {
+  // unsafeQueryUnique returns a `Try[Row]`
+  DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row =>
+    // pick out the properties every book possesses
+    val id = row[Int]("id")
+    val title = row[String]("title")
+    val author = row[String]("author")
+    val formatStr = row[String]("format")
+
+    // now start to determine the types - get the format first
+    Format.fromString(formatStr).flatMap {
+      case Print =>
+        // for print books, we can construct the book and return immediately
+        Success(PrintBook(id, title, author))
+      case Digital =>
+        // for digital books we need to handle the download type
+        row[Option[String]]("download_type") match {
+          case None =>
+            Failure(new AssertionError(s"download type not provided for digital book $id"))
+          case Some(downloadStr) =>
+            DownloadType.fromString(downloadStr).flatMap { dt =>
+              Success(EBook(id, title, author, dt))
+            }
+        }
+    }
+  }
+}
+

Depending on your perspective, that is arguably a long function. If + you think it is not so long, pretend that the table has a number of + other fields that must also be conditionally parsed to construct a + Book.

+ +

Tail refactoring

+

One possible approach is a a strategy I'm going to call + "tail-refactoring", for lack of a better description. Basically, each + function does a little work or some error checking, and then calls the + next appropriate function in the chain.

+

You can imagine what kind of code will result. The functions are + smaller, but it's hard to describe what each function does, and + functions occasionally have to carry along additional parameters that + they will ignore except to pass deeper into the call chain. Let's take + a look at an example refactoring:

+
import scala.util.{Failure, Success, Try}
+
+def extractEBook(
+    id: Int,
+    title: String,
+    author: String,
+    downloadTypeStrOpt: Option[String]): Try[EBook] =
+  downloadTypeStrOpt match {
+    case None => Failure(new AssertionError())
+    case Some(downloadTypeStr) =>
+      DownloadType.fromString(downloadTypeStr).flatMap { dt =>
+        Success(EBook(id, title, author, dt))
+      }
+  }
+
+def extractBook(
+    id: Int,
+    title: String,
+    author: String,
+    formatStr: String,
+    downloadTypeStrOpt: Option[String]): Try[Book] =
+  Format.fromString(formatStr).flatMap {
+    case Print =>
+      Success(PrintBook(id, title, author))
+    case Digital =>
+      extractEBook(id, title, author, downloadTypeStrOpt)
+  }
+
+def findBookById(id: Int): Try[Book] =
+  DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row =>
+    val id = row[Int]("id")
+    val title = row[String]("title")
+    val author = row[String]("author")
+    val formatStr = row[String]("format")
+    val downloadTypeStr = row[Option[String]]("download_type")
+    extractBook(id, title, author, formatStr, downloadTypeStr)
+  }
+

As you can see, this form has more manageably-sized functions, + although they are still a little long. You can also see that the flow + of control is distributed through all three functions, which means + understanding the logic enough to modify or test it requires + understanding all three functions both individually and as a whole. To + follow the logic, we must trace the functions like a recursive descent + parser.

+ +

Refactoring with Monads

+

Without throwing exceptions and catching them at the top, it's going + to be hard to do substantially better than the "tail-refactoring" + approach, unless we start to make use of the fact that we're working + with Try, a data type that supports flatMap. More precisely, Try + has a monad instance - recall that monads let us model computational + effects that take place in sequence.

+

Let's try to factor out smaller functions, each returning Try, and + then use a for-comprehension to specify the sequence of operations:

+
import scala.util.{Failure, Success, Try}
+
+def parseDownloadType(o: Option[String], id: Int): Try[DownloadType] = {
+  o.map(DownloadType.fromString)
+    .getOrElse(Failure(new AssertionError(s"download type not provided for digital book $id")))
+}
+
+def findBookById(id: Int): Try[Book] =
+  for {
+    row <- DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""")
+    format <- Format.fromString(row[String]("format"))
+    id = row[Int]("id")
+    title = row[String]("title")
+    author = row[String]("author")
+    book <- format match {
+      case Print =>
+        Success(PrintBook(id, title, author))
+      case Digital =>
+        parseDownloadType(row[Option[String]]("download_type"), id)
+          .map(EBook(id, title, author, _))
+    }
+  } yield book
+

It's less code, the functions are smaller, and the top-level function + dictates the entire flow of control. No function takes more than 2 + arguments. These are testable, understandable functions. This version + really shows the power of using monads to sequence computation.

+

Now we are truly making use of the fact that Try has a monad instance + and not just another container class. We can simply describe the "happy + path" and trust Try to short-circuit computation if something erroneous + or unexpected occurs. In that case, Try captures the error and stops + computation there. The code does this without the need for explicit + branching logic.

+ +

Abstracting effect type

+

Now, let's take this one step further - here's where we achieve + buzzword compliance. Let's abstract away from the effect, Try, and + instead make use of + MonadError. + This lets us use a more diverse set of effect types, from + IO to + Task, so we can execute our + function in whatever asynchronous context we wish. This has the feel + of a tagless final strategy (although we aren't worrying about + describing interpreters here).

+

Here we go:

+
import cats.MonadError
+import cats.implicits._
+
+def parseDownloadType[F[_]](o: Option[String], id: Int)(
+    implicit me: MonadError[F, Throwable]): F[DownloadType] = {
+  me.fromOption(o, new AssertionError(s"download type not provided for digital book $id"))
+    .flatMap(s => me.fromTry(DownloadType.fromString(s)))
+}
+
+def findBookById[F[_]](id: Int)(implicit me: MonadError[F, Throwable]): F[Book] =
+  for {
+    row <- DB.queryUnique[F](sql"""select * from catalog where id = $id""")
+    format <- me.fromTry(Format.fromString(row[String]("format")))
+    id = row[Int]("id")
+    title = row[String]("title")
+    author = row[String]("author")
+    book <- format match {
+      case Print =>
+        me.pure(PrintBook(id, title, author))
+      case Digital =>
+        parseDownloadType[F](row[Option[String]]("downloadType"), id)
+          .map(EBook(id, title, author, _))
+    }
+  } yield book
+

The code isn't much more complicated than the version using Try but + it adds a lot of flexibility. In a synchronous context, we could still + use Try. In that case, however, the database call is executed + eagerly, which means the function isn't referentially transparent. We + can make the function referentially transparent by using a monad such + as IO or Task as the effect type and delaying the evaluation of + the database call until "the end of the universe".

+

In this example, pay attention to the use of + fromOption + and + fromTry, + which adapt Option and Try to F. If you are using existing APIs + that aren't already generalized to MonadError these methods adapt + common error types, but require very little ceremony to use.

+ +

Refactoring strategy

+

When faced with a similar refactoring problem, consider whether you + can break the problem into a sequence of independently executable + steps, each of which can be wrapped in a monad. If so, begin by + describing the control flow in your refactored function with a monadic + for-comprehension. Don't define the individual functions that comprise + the steps of the for-comprehension until you have filled in the + yield at the end. You can use pseudocode or stubs to minimize the + amount of code churn at the beginning. This is a great time to shuffle + steps around and work out exactly what arguments are needed and when, + as well as where they are coming from.

+

Once the top level function looks plausible, begin implenting the + steps of the for-comprehension. You can replace the stubs or + pseudocode you wrote by refactoring code from your original function. + If the original code did not operate in a monadic context, recall that + you can convert a simple function A => B to F[A] => F[B] using + lift + (thanks, + Functor!). This + makes converting your existing code even easier.

+ +

Conclusion

+

In this post, we have seen how we can use monads as an aid in + refactoring code to improve both readability and testability. We have + also demonstrated that we can do this in many cases without needing to + specify the monad in use a priori. As a result, we gain the + flexibility to choose the appropriate monad for our application, + independently of the program logic.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Mark Tomko + + +
+ Mark is a senior software engineer at the Broad Institute of MIT and Harvard. He lives in Bellingham, Washington. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/rethinking-monaderror.html b/blog/rethinking-monaderror.html new file mode 100644 index 00000000..57677551 --- /dev/null +++ b/blog/rethinking-monaderror.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + Rethinking MonadError + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Rethinking MonadError

+ + + technical + +
+
+
+
+

Rethinking MonadError

+

MonadError is a very old type class, hackage shows me it was originally added in 2001, long before I had ever begun doing functional programming, just check the hackage page. + In this blog post I'd like to rethink the way we use MonadError today. + It's usually used to signal that a type might be capable of error handling and is basically like a type class encoding of Eithers ability to short circuit. + That makes it pretty useful for building computations from sequences of values that may fail and then halt the computation or to catch those errors in order to resume the computation. + It's also parametrized by its error type, making it one of the most common example of multi-parameter type classes. + Some very common instances include Either and IO, but there are a ton more.

+

We can divide instances into 3 loosely defined groups:

+

First we have simple data types like Either, Option or Ior (with Validated not having a Monad instance).

+

Secondly we've got the IO-like types, the various IOs, Tasks and the like. These are used to suspend side effects which might have errors and therefore need to be able to handle these.

+

Thirdly and least importantly, we have monad transformers, which get their instances from their respective underlying monads. Since they basically just propagate their underlying instances we're only going to talk about the first two groups for now.

+

The simple data types all define MonadError instances, but I wager they're not actually used as much. This is because MonadError doesn't actually allow us to deconstruct e.g. an Either to actually handle the errors. We'll see more on that later, next let's look at the IO-like types and their instances.

+

cats.effect.IO currently defines a MonadError[IO, Throwable], meaning that it's fully able to raise and catch errors that might be thrown during evaluation of encapsulated side effects. + Using MonadError with these effect types seems a lot more sensical at first, as you can't escape IO even when you handle errors, so it looks like it makes sense to stay within IO due to the side effect capture.

+

The problem I see with MonadError is that it does not address the fundamental difference between these two types of instances. I can pattern match an Option[A] with a default value to get back an A. With IO that is just not possible. So these two groups of types are pretty different, when does it actually make sense to abstract over both of them? + Well, it turns out there a few instances where it might be useful, but as we'll see later, I'm proposing something that will be equally useful to both groups.

+

Now before we continue, let's look at the MonadError type class in a bit more detail. + MonadError currently comprises two parts, throwing and catching errors. + To begin let's have a look at the throw part, sometimes also called MonadThrow:

+
trait MonadError[F[_], E] extends Monad[F] {
+  def raiseError[A](e: E): F[A]
+
+  ...
+}
+

This looks fine for now, but one thing that strikes me is that the F type seems to "swallow" errors. + If we look at F[A] we have no clue that it might actually yield an error of type E, that fact is not required to be represented at all. + However, that's not a really big issue, so now let's look at the catch part:

+
trait MonadError[F[_], E] extends MonadThrow[F, E] {
+  ...
+
+  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]
+}
+

Immediately I have a few questions, if the errors are handled, why does it return the exact same type? + Furthermore if this is really supposed to handle errors, what happens if I have errors in the E => F[A] function? + This is even more blatant in the attempt function:

+
trait MonadError[F[_], E] extends Monad[F] {
+  def attempt[A](fa: F[A]): F[Either[E, A]]
+}
+

Here there is no way the outer F still has any errors, so why does it have the same type? + Shouldn't we represent the fact that we handled all the errors in the type system? + This means you can't actually observe that the errors are now inside Either. That leads to this being fully legal code:

+
import cats.implicits._
+// import cats.implicits._
+
+Option(42).attempt.attempt.attempt.attempt
+// res0: Option[Either[Unit,Either[Unit,Either[Unit,Either[Unit,Int]]]]] = Some(Right(Right(Right(Right(42)))))
+

Another example that demonstrates this is the fact that calling handleError, which looks like this:

+
def handleError[A](fa: F[A])(f: E => A): F[A]
+

also returns an F[A]. This method takes a pure function E => A and thus can not fail during recovery like handleErrorWith, yet it still doesn't give us any sign that it doesn't throw errors. + For IO-like types this is somewhat excusable as something like an unexceptional IO is still very uncommon, but for simple data types like Either or Some that function should just return an A, since that's the only thing it can be. + Just like with attempt, we can infinitely chain calls to handleError, as it will never change the type.

+

Ideally our type system should stop us from being able to write this nonsensical code and give us a way to show anyone reading the code that we've already handled errors. + Now I'm not saying that the functions on MonadError aren't useful, but only that they could be more constrained and thus more accurate in their representation.

+

For this purpose let's try to write a different MonadError type class, one that's designed to leverage the type system to show when values are error-free, we'll call it MonadBlunder for now.

+

To mitigate the problems with MonadError we have a few options, the first one I'd like to present is using two different type constructors to represent types that might fail and types that are guaranteed not to. So instead of only a single type constructor our MonadBlunder class will have two:

+
trait MonadBlunder[F[_], G[_], E]
+

Our type class now has the shape (* -> *) -> (* -> *) -> * -> *, which is quite a handful, but I believe we can justify its usefulness. + The first type parameter F[_] will represent our error-handling type, which will be able to yield values of type E. + The second type parameter G[_] will represent a corresponding type that does not allow any errors and can therefore guarantee that computations of the form G[A] will always yield a value of type A.

+

Now that we figured out the shape, let's see what we can actually do with it. + For throwing errors, we'll create a raiseError function that should return a value inside F, as it will obviously be able to yield an error.

+
trait MonadBlunder[F[_], G[_], E] {
+  def raiseError[A](e: E): F[A]
+}
+

This definition looks identical to the one defined one MonadError so let's move on to error-handling. + For handled errors, we want to return a value inside G, so our handleErrorWith function should indeed return a G[A]:

+
trait MonadBlunder[F[_], G[_], E] {
+  ...
+
+  def handleErrorWith[A](fa: F[A])(f: E => F[A]): G[A]
+}
+

Looks good so far, right? + Well, we still have the problem that f might return an erronous value, so if we want to guarantee that the result won't have any errors, we'll have to change that to G[A] as well:

+
trait MonadBlunder[F[_], G[_], E] {
+  ...
+
+  def handleErrorWith[A](fa: F[A])(f: E => G[A]): G[A]
+}
+

And now we're off to a pretty good start, we fixed one short coming of MonadError with this approach.

+

Another approach, maybe more obvious to some, might be to require the type constructor to take two arguments, one for the value and one for the error type. + Let's see if we can define raiseError on top of it:

+
trait MonadBlunder[F[_, _]] {
+  def raiseError[E, A](e: E): F[E, A]
+
+  ...
+}
+

This looks pretty similar to what we already have, though now we have the guarantee that our type doesn't actually "hide" the error-type somewhere. + Next up is handleErrorWith. Ideally after we handled the error we should again get back a type that signals that it doesn't have any errors. + We can do exactly that by choosing an unhabited type like Nothing as our error-type:

+
trait MonadBlunder[F[_, _]] {
+  ...
+
+  def handleErrorWith[E, A](fa: F[E, A])(f: E => F[Nothing, A]): F[Nothing, A]
+}
+

And this approach works as well, however now we've forced the two type parameter shape onto implementors. This MonadBlunder has the following kind (* -> * -> *) -> *. + This means we can very easily define instances for types with two type parameters like Either. + However, one issue might be that it's much easier to fit a type with two type parameters onto a type class that expects a single type constructor (* -> *) than to do it the other way around.

+

For example try to implement the above MonadBlunder[F[_, _]] for the standard cats.effect.IO. + It's not going to be simple, whereas with the first encoding we can easily encode both Either and IO. For this reason, I will continue this article with the first encoding using the two different type constructors.

+

Next we're going to look at laws we can define to make sense of the behaviour we want. + The first two laws should be fairly obvious. + If we flatMap over a value created by raiseError it shouldn't propogate:

+
def raiseErrorStops(e: E, f: A => F[A]): Boolean =
+  F.raiseError[A](e).flatMap(f) === F.raiseError[A](e)
+

Next we're going to formulate a law that states, that raising an error and then immediatly handling it with a given function should be equivalent to just calling that function on the error value:

+
def raiseErrorHandleErrorWith(e: E, f: E => G[A]): Boolean =
+  raiseError[A](e).handleErrorWith(f) === f(e)
+

Another law could state that handling errors for a pure value lifted into the F context does nothing and is equal to the pure value in the G context:

+
def handleErrorPureIsPure(a: A, f: E => G[A]): Boolean =
+  a.pure[F].handleErrorWith(f) === a.pure[G]
+

Those should be good for now, but we'll be able to find more when we add more derived functions to our type class. + Also note that none of the laws are set in stone, these are just the ones I came up with for now, it's completely possible that we'll need to revise these in the future.

+

Now let's focus on adding extra functions to our type class. MonadError offer us a bunch of derived methods that can be really useful. For most of those however we need access to methods like flatMap for both F and G, so before we figure out derived combinators, let's revisit how exactly we define the type class.

+

The easiest would be to give both F and G a Monad constraint and move on. + But then we'd have two type classes that both define a raiseError function extends Monad, and we wouldn't be able to use them together, since that would cause ambiguities and as I've said before, the functions on MonadError are useful in some cases.

+

Instead, since I don't really like duplication and the fact that we're not going to deprecate MonadError overnight, I decided to extend MonadBlunder from MonadError for the F type, to get access to the raiseError function. + If raiseError and handleErrorWith were instead separated into separate type classes (as is currently the case in the PureScript prelude), we could extend only the raiseError part. + This also allows us to define laws that our counterparts of functions like attempt and ensure are consistent with the ones defined on MonadError. + So the type signature now looks like this (expressed in Haskell, since it's easier on the eyes):

+
class (MonadError f e, Monad g) => MonadBlunder f g ef -> e, f -> g where
+  ...
+

In Scala, we can't express this as nicely, so we're going to have to use something close to the cats-mtl encoding:

+
trait MonadBlunder[F[_], G[_], E] {
+  val monadErrorF: MonadError[F, E]
+  val monadG: Monad[G]
+  
+  ...
+}
+

Now since this means that any instance of MonadBlunder will also have an instance of MonadError on F, we might want to rename the functions we've got so far. + Here's a complete definition of what we've come up with with raiseError removed and handleErrorWith renamed to handleBlunderWith:

+
trait MonadBlunder[F[_], G[_], E] {
+  val monadErrorF: MonadError[F, E]
+  val monadG: Monad[G]
+
+  def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A]
+}
+

Now let us go back to defining more derived functions for MonadBlunder. + The easiest probably being handleError, so let's see if we can come up with a good alternative:

+
trait MonadBlunder[F[_], G[_], E] {
+  ...
+
+  def handleBlunder[A](fa: F[A])(f: E => A): G[A] = 
+    handleBlunderWith(fa)(f andThen (_.pure[G]))
+}
+

This one is almost exactly like handleBlunderWith, but takes a function from E to A instead of to G[A]. We can easily reuse handleBlunderWith by using pure to go back to E => G[A].

+

Next another function that's really useful is attempt. + Our alternative, let's call it endeavor for now, should return a value in G instead, which doesn't have a MonadError instance and therefore can not make any additional calls to endeavor:

+
trait MonadBlunder[F[_], G[_], E] {
+  ...
+
+  def endeavor[A](fa: F[A]): G[Either[E, A]] =
+    handleBlunder(fa.map(Right(_)))(Left(_))
+}
+

The implementation is fairly straightforward as well, we just handle all the errors by lifting them into the left side of an Either and map successful values to the right side of Either.

+

Next, let's look at the dual to attempt, called rethrow in Cats. + For MonadError it turns an F[Either[E, A]] back into an F, but we're going to use our unexceptional type again:

+
trait MonadBlunder[F[_], G[_], E] {
+  ...
+
+  def absolve[A](fa: G[Either[E, A]]): F[A] = ???
+}
+

But looking at this signature, we quickly realize that we need a way to get back to F[A] from G[A]. + So we're going to add another function to our minimal definition:

+
trait MonadBlunder[F[_], G[_], E] {
+  val monadErrorF: MonadError[F, E]
+  val monadG: Monad[G]
+
+  def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A]
+
+  def accept[A](ga: G[A]): F[A]
+
+}
+

This function accept, allows us to lift any value without errors into a context where errors might be present.

+

We can now formulate a law that values in G never stop propagating, so flatMap should always work, we do this by specifying that calling handleBlunder after calling accept on any G[A], is never going to actually change the value:

+
def gNeverHasErrors(ga: G[A], f: E => A): Boolean =
+  accept(ga).handleBlunder(f) === ga
+

Now we can go back to implementing the absolve function:

+
def absolve[A](gea: G[Either[E, A]]): F[A] =
+  accept(gea).flatMap(_.fold(raiseError[A], _.pure[F]))
+

Now that we've got the equivalent of both attempt and rethrow, let's add a law that states that the two should cancel each other out:

+
def endeavorAbsolve(fa: F[A]): Boolean =
+  absolve(fa.endeavor) === fa
+

We can also add laws so that handleBlunder and endeavor are consistent with their counterparts now that we have accept:

+
def deriveHandleError(fa: F[A], f: E => A): Boolean =
+  accept(fa.handleBlunder(f)) === fa.handleError(f)
+
+def deriveAttempt(fa: F[A]): Boolean =
+  accept(fa.endeavor) === fa.attempt
+

One nice thing about attempt, is that it's really easy to add a derivative combinator that doesn't go to F[Either[E, A]], but to the isomorphic monad transformer EitherT[F, E, A]. + We can do the exact same thing with endeavor:

+
def endeavorT[A](fa: F[A]): EitherT[G, E, A] =
+  EitherT(endeavor(fa))
+

One last combinator I'd like to "port" from MonadError is the ensureOr function. + ensureOr turns a successful value into an error if it does not satisfy a given predicate. + We're going to name the counterpart assureOr:

+
def assureOr[A](ga: G[A])(error: A => E)(predicate: A => Boolean): F[A] =
+  accept(ga).flatMap(a =>
+    if (predicate(a)) a.pure[F] else raiseError(error(a))
+  )
+

This plays nicely with the rest of our combinators and we can again add a law that dictates it must be consistent with ensureOr:

+
def deriveEnsureOr(ga: G[A])(error: A => E)(predicate: A => Boolean): Boolean =
+  ensureOr(accept(ga))(error)(predicate) === assureOr(ga)(error)(predicate)
+

Now we have a great base to work with laws that should guarantee principled and sensible behaviour. + Next we'll actually start defining some instances for our type class.

+

The easiest definitions are for Either and Option, though I'm not going to cover both, as the instances for Option can simply be derived by Either[Unit, A]and I'm going to link to the code at the end. + For Either[E, A], when we handle all errors of type E, all we end up with is A, so the corresponding G type for our instance should be Id. + That leaves us with the following definition:

+
implicit def monadBlunderEither[E]: MonadBlunder[Either[E, ?], Id, E] =
+  new MonadBlunder[Either[E, ?], Id, E] {
+    val monadErrorF = MonadError[Either[E, ?], E]
+    val monadG = Monad[Id]
+
+    def handleBlunderWith[A](fa: Either[E, A])(f: E => A): A = fa match {
+      case Left(e) => f(e)
+      case Right(a) => a
+    }
+
+    def accept[A](ga: A): Either[E, A] = Right(ga)
+  }
+

Fairly straightforward, as Id[A] is just A, but with this instance we can already see a small part of the power we gain over MonadError. + When we handle errors with handleBlunder, we're no longer "stuck" inside the Either Monad, but instead have a guarantee that our value is free of errors. + Sometimes it'll make sense to stay inside Either, but we can easily get back into Either, so we have full control over what we want to do.

+

Next up, we'll look at IO and the type that inspired this whole blog post UIO. + UIO is equivalent to an IO type where all errors are handled and is short for "unexceptional IO". + UIO currently lives inside my own cats-uio library, but if things go well, we might see it inside cats-effect eventually. This would also work for IO types who use two type parameters IO[E, A] where the first represents the error type and the second the actual value. There you'd choose IO[E, A] as the F type and IO[Nothing, A] as the G type. IO[Nothing, A] there is equivalent to UIO[A].

+

As one might expect, you can not simply go from IO[A] to UIO[A], but we'll need to go from IO[A] to UIO[Either[E, A]] instead, which if you look at it, is exactly the definition of endeavor. + Now let's have a look at how the MonadBlunder instance for IO and UIO looks:

+
implicit val monadBlunderIO: MonadBlunder[IO, UIO, Throwable] = 
+  new MonadBlunder[IO, UIO, Throwable] {
+    val monadErrorF = MonadError[IO, Throwable]
+    val monadG = Monad[UIO]
+
+    def handleBlunderWith[A](fa: IO[A])(f: Throwable => UIO[A]): UIO[A] =
+      UIO.unsafeFromIO(fa.handleErrorWith(f andThen accept))
+
+    def accept[A](ga: UIO[A]): IO[A] = UIO.runUIO(ga)
+  }
+

And voila! We've got a fully working implementation that will allow us to switch between these two types whenever we have a guarantee that all errors are handled. + This makes a lot of things much simpler. + For example, if one wants to use bracket with UIO, you just need to flatMap to the finalizer, as flatMap is always guaranteed to not short-circuit.

+

We can also define instances for EitherT and OptionT (being isomorphic to EitherT[F, Unit, A]), where the corresponding unexceptional type is just the outer F, so endeavor is just a call to .value:

+
implicit def catsEndeavorForEitherT[F[_]: Monad, E]: MonadBlunder[EitherT[F, E, ?], F, E] =
+  new MonadBlunder[EitherT[F, E, ?], F, E] {
+    val monadErrorF = MonadError[EitherT[F, E, ?], E]
+    val monadG = Monad[F]
+
+    override def endeavor[A](fa: EitherT[F, E, A]): F[Either[E, A]] =
+      fa.value
+
+    def handleBlunderWith[A](fa: EitherT[F, E, A])(f: E => F[A]): F[A] =
+      fa.value.flatMap {
+        case Left(e) => f(e)
+        case Right(a) => a.pure[F]
+      }
+
+    def accept[A](ga: F[A]): EitherT[F, E, A] =
+      EitherT.liftF(ga)
+
+  }
+

Finally, it's also possible to create instances for other standard monad transformers like WriterT, ReaderT or StateT as long as their underlying monads themselves have instances for MonadBlunder, as is typical in mtl. + As their implementations are very similar we'll only show the StateT transformer instance:

+
implicit def catsEndeavorForStateT[F[_], G[_], S, E]
+  (implicit M: MonadBlunder[F, G, E]): MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] =
+    new MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] {
+      implicit val F: MonadError[F, E] = M.monadErrorF
+      implicit val G: Monad[G] = M.monadG
+
+      val monadErrorF = MonadError[StateT[F, S, ?], E]
+      val monadG = Monad[StateT[G, S, ?]]
+
+      def accept[A](ga: StateT[G, S, A]): StateT[F, S, A] = ga.mapK(new (G ~> F) {
+        def apply[T](ga: G[T]): F[T] = M.accept(ga)
+      })
+
+      def handleBlunderWith[A](fa: StateT[F, S, A])(f: E => StateT[G, S, A]): StateT[G, S, A] =
+        IndexedStateT(s => M.handleBlunderWith(fa.run(s))(e => f(e).run(s)))
+
+    }
+

In practice this means we can call handleBlunderWith on things like StateT[IO, S, A] and get back a StateT[UIO, S, A]. Pretty neat! + You can also create instances for pretty much any MonadError using Unexceptional, e.g.: MonadBlunder[Future, Unexceptional[Future, ?], Throwable]. The Unexceptional type is designed to turn any erroring type into one that doesn't throw errors by catching them with attempt.

+ +

Conclusion

+

In this article, I've tried to present the argument that MonadError is insufficient for principled error handling. + We also tried to build a solution that deals with the shortcomings described earlier. + Thereby it seeks not to replace, but to expand on MonadError to get a great variety of error handling capabilities. + I believe the MonadBlunder type class, or whatever it will be renamed to, can be a great addition not just to the Cats community, but to the functional community at large, especially as it's much easier to express in languages like PureScript and Haskell.

+

For now, all of the code lives inside the cats-uio repo, which houses the MonadBlunder type class the UIO data type and the Unexceptional data type. + I hope that this blog post gave a motivation as to why I created the library and why it might be nice to adopt some of its features into the core typelevel libraries.

+

Note again, that none of this is final or set in stone and before it arrives anywhere might still change a lot, especially in regards to naming (which I'm not really happy with at the moment), so if you have any feedback of any sorts, please do chime in! Would love to hear your thoughts and thank you for reading this far!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/scala-center.html b/blog/scala-center.html new file mode 100644 index 00000000..0f770fe5 --- /dev/null +++ b/blog/scala-center.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel representative at the Scala Center Advisory Board + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel representative at the Scala Center Advisory Board

+ + + governance + +
+
+
+
+

Typelevel representative at the Scala Center Advisory Board

+

It is our pleasure to announce that the Scala Center Advisory Board has invited us to nominate a member of the Typelevel community to serve as a community representative, alongside Bill Venners. + To figure out whether or not we should accept this offer and who we pick we have held an open discussion on GitHub, resulting in my nomination, which I happily accept. + Thanks everyone for their trust! + The corresponding Scala Center announcement can be found here.

+

On a more personal note: + Some years ago, when I initially registered the typelevel.org domain, I could have never anticipated the large community which has gathered around typeful functional programming in Scala. + The offer from the Scala Center to nominate a representative is an acknowledgement, and also a positive signal for community outreach. + So, naturally, I'm very excited about what lies ahead. + Keep rocking!

+

What follows is a summary of the process.

+ +

Scala Center Advisory Board

+

To quote Jon Pretty:

+
+

The Advisory Board is a separate body from the Scala Center, much as many governments have separate legislative and executive branches: the Advisory Board makes recommendations to the Scala Center on the work we should do, but it’s the Scala Center’s job to execute those recommendations.

+

It currently has seven voting members: representatives from each of our six sponsors, plus Bill Venners, the community representative. Additionally the Executive Director of the Scala Center, Heather Miller, sits on the board to report on the Scala Center’s activities, and provide advice on the feasibility of the proposals under consideration, and Martin Odersky is the technical advisor to the board.

+
+

More details, including minutes and bylaws, can be found on their website.

+ +

Process

+

To make the nomination as transparently as possible, I opened an issue shortly after Jon mailed me about the opening. + The discussion period ended on October 12th. + All nominees endorsed me, so no vote was necessary.

+ +

Provisions

+

As indicated in the discussion about the nomination, we have established that the term length will be one year and after that, we will have another vote. + I hope this measure will improve community participation.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/scala-coc.html b/blog/scala-coc.html new file mode 100644 index 00000000..32a03dc0 --- /dev/null +++ b/blog/scala-coc.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + Endorsing the new Scala Code of Conduct + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Endorsing the new Scala Code of Conduct

+ + + governance + +
+
+
+
+

Endorsing the new Scala Code of Conduct

+

A couple of days ago, the new Scala Code of Conduct was published. + It applies to all official Scala channels, including mailing lists, Gitter channels and GitHub repositories. + We would like to take this opportunity to endorse this new Code of Conduct. + From our perspective, it does a good job of listing encouraged behaviour, instead of just banning harassment: It reflects the goal of actively creating a welcoming community. + Also, we consider it to be a decent substitute of our own Code of Conduct. + That means that Typelevel project maintainers are free to switch to the Scala Code of Conduct if they wish.

+

For some more background, please see the discussion on GitHub.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/scala-io-2016-10-27.html b/blog/scala-io-2016-10-27.html new file mode 100644 index 00000000..fa9115fd --- /dev/null +++ b/blog/scala-io-2016-10-27.html @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + Scala.IO + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Scala.IO

+ + + events + +
+
+
+
+

Scala.IO

+
+

"Saone River" by Dennis Jarvis is licensed under CC BY-SA 2.0.

+ +

About the Conference

+

The Scala event in France, organized by French Scala community volunteers, featuring advanced functional programming talks. + CfP is still open until September 5.

+

Learn More

+ +

Venue

+

This event will take place at the CPE School in Lyon, France.

+
+
+ +
+ + + + + + diff --git a/blog/scalaxhack-2016-12-10.html b/blog/scalaxhack-2016-12-10.html new file mode 100644 index 00000000..c6642802 --- /dev/null +++ b/blog/scalaxhack-2016-12-10.html @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + Scala Exchange Hack Day and Unconference + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Scala Exchange Hack Day and Unconference

+ + + events + +
+
+
+
+

Scala Exchange Hack Day and Unconference

+
+

Image by John Nuttall is licensed under CC BY 2.0.

+

We're partnering with Skills Matter + and the London Scala User Group + to organise the second Scala Exchange Hack Day, + a free day of unconference and hack sessions, + co-located with Scala Exchange.

+

The event is open to the whole community, + not just attendees of Scala Exchange, + and will run on 10th December 2016 + (the day after Scala Exchange) + at CodeNode, + Skills Matter's dedicated conference venue and tech hub + near Moorgate Station in central London.

+

The full list of sessions and topics + will be decided by the participants on the day. + There are some preliminary ideas on + the ScalaxHack web site. + We'll include hands-on hack sessions + to help people get involved with Scala open source, + including projects from inside and outside + the Typelevel family.

+

ScalaxHack is a free event. + Lunch will be sponsored by Underscore.

+

Register

+
+
+ +
+ + + + + + diff --git a/blog/semirings.html b/blog/semirings.html new file mode 100644 index 00000000..3b97ecb1 --- /dev/null +++ b/blog/semirings.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + A tale on Semirings + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

A tale on Semirings

+ + + technical + +
+
+
+
+

A tale on Semirings

+

Ever wondered why sum types are called sum types? + Or maybe you've always wondered why the <*> operator uses exactly these symbols? + And what do these things have to do with Semirings? + Read this article and find out!

+

Most of us know and use Monoids and Semigroups. + They're super useful and come with properties that we can directly utilize to gain a higher level of abstractions at very little cost (In case you don't know about them, check out the Cats documentation for some insight). + Sometimes, however, certain types can have multiple Monoid or Semigroup instances. + An easy example are the various numeric types where both multiplication and addition form two completely lawful monoid instances.

+

In abstract algebra there is a an algebraic class for types with two Monoid instances that interact in a certain way. + These are called Semirings (sometimes also Rig) and they are defined as two Monoids with some special laws that define the interactions between them. + Because they are often used to describe numeric data types we usually classify them as Additive and Multiplicative. + Just like with numeric types the laws of Semiring state that multiplication has to distribute over addition and multiplying a value with the additive identity (i.e. zero) absorbs the value and becomes zero.

+

There are different ways to encode this as type classes and different libraries handle this differently, but let's look at how the algebra project handles this. + Specifically, it defines a separate AdditiveSemigroup and MultiplicativeSemigroup and goes from there.

+
import simulacrum._
+
+@typeclass trait AdditiveSemigroup[A] {
+  def +(x: A)(y: A): A
+}
+
+@typeclass trait AdditiveMonoid[A] extends AdditiveSemigroup[A] {
+  def zero: A
+}
+
+@typeclass trait MultiplicativeSemigroup[A] {
+  def *(x: A)(y: A): A
+}
+
+@typeclass trait MultiplicativeMonoid[A] extends MultiplicativeSemigroup[A] {
+  def one: A
+}
+

A Semiring is then just an AdditiveMonoid coupled with a MultiplicativeMonoid with the following extra laws:

+
    +
  1. Additive commutativity, i.e. x + y === y + x
  2. +
  3. Right distributivity, i.e. (x + y) * z === (x * z) + (y * z)
  4. +
  5. Left distributivity, i.e. x * (y + z) === (x * y) + (x * z)
  6. +
  7. Right absorption, i.e. x * zero === zero
  8. +
  9. Left absorption, i.e. zero * x === zero
  10. +
+

To define it as a type class, we simply extend from both additive and multiplicative monoid:

+
@typeclass trait Semiring[A] extends MultiplicativeMonoid[A] with AdditiveMonoid[A]
+

Now we have a Semiring class, that we can use with the various numeric types like Int, Long, BigDecimal etc, but what else is a Semiring and why dedicate a whole blog post to it?

+

It turns out a lot of interesting things can be Semirings, including Booleans, Sets and animations.

+

One very interesting thing I'd like to point out is that we can form a Semiring homomorphism from types to their number of possible inhabitants. + What the hell is that? + Well, bear with me for a while and I'll try to explain step by step.

+ +

Cardinality

+

Okay, so let's start with what I mean by cardinality. + Every type has a specific number of values it can possibly have, e.g. a Boolean has cardinality of 2, because it has two possible values: true and false.

+

So Boolean has two, how many do other primitive types have? + Byte has 2^8, Short has 2^16, Int has 2^32 and Long has 2^64. + So far so good, that makes sense, what about something like String? + String is an unbounded type and therefore theoretically has infinite number of different inhabitants (practically of course, we don't have infinite memory, so the actual number may vary depending on your system).

+

For what other types can we determine their cardinality? + Well a couple of easy ones are Unit, which has exactly one value it can take and also Nothing, which is the "bottom" type in Scala, which means being a subtype of every possible other type and has 0 possible values. I.e you can never instantiate a value of Nothing, which gives it a cardinality of 0.

+

That's neat, maybe we can encode this in actual code. + We could create a type class that should be able to give us the number of inhabitants for any type we give it:

+
trait Cardinality[A] {
+  def cardinality: BigInt
+}
+
+object Cardinality {
+  def of[A: Cardinality]: BigInt = apply[A].cardinality
+
+  def apply[A: Cardinality]: Cardinality[A] = implicitly
+}
+

Awesome! + Now let's try to define some instances for this type class:

+
implicit def booleanCardinality = new Cardinality[Boolean] {
+  def cardinality: BigInt = BigInt(2)
+}
+
+implicit def longCardinality = new Cardinality[Long] {
+  def cardinality: BigInt = BigInt(2).pow(64)
+}
+
+implicit def intCardinality = new Cardinality[Int] {
+  def cardinality: BigInt = BigInt(2).pow(32)
+}
+
+implicit def shortCardinality = new Cardinality[Short] {
+  def cardinality: BigInt = BigInt(2).pow(16)
+}
+
+implicit def byteCardinality = new Cardinality[Byte] {
+  def cardinality: BigInt = BigInt(2).pow(8)
+}
+
+implicit def unitCardinality = new Cardinality[Unit] {
+  def cardinality: BigInt = 1
+}
+
+implicit def nothingCardinality = new Cardinality[Nothing] {
+  def cardinality: BigInt = 0
+}
+

Alright, this is cool, let's try it out in the REPL!

+
scala> Cardinality.of[Int]
+res11: BigInt = 4294967296
+
+scala> Cardinality.of[Unit]
+res12: BigInt = 1
+
+scala> Cardinality.of[Long]
+res13: BigInt = 18446744073709551616
+

Cool, but this is all very simple, what about things like ADTs? + Can we encode them in this way as well? + Turns out, we can, we just have to figure out how to handle the basic product and sum types. + To do so, let's look at an example of both types. + First, we'll look at a simple product type: (Boolean, Byte).

+

How many inhabitants does this type have? + Well, we know Boolean has 2 and Byte has 256. + So we have the numbers from -127 to 128 once with true and once again with false. + That gives us 512 unique instances. + Hmmm....

+

512 seems to be double 256, so maybe the simple solution is to just multiply the number of inhabitants of the first type with the number of inhabitants of the second type. + If you try this with other examples, you'll see that it's exactly true, awesome! + Let's encode that fact in a type class instance:

+
implicit def tupleCardinality[A: Cardinality, B: Cardinality] =
+  new Cardinality[(A, B)] {
+    def cardinality: BigInt = Cardinality[A].cardinality * Cardinality[B].cardinality
+  }
+

Great, now let's look at an example of a simple sum type: Either[Boolean, Byte]. + Here the answer seems even more straight forward, since a value of this type can either be one or the other, we should just be able to add the number of inhabitants of one side with the number of inhabitants of the other side. + So Either[Boolean, Byte] should have 2 + 256 = 258 number of inhabitants. Cool!

+

Let's also code that up and try and confirm what we learned in the REPL:

+
implicit def eitherCardinality[A: Cardinality, B: Cardinality] =
+  new Cardinality[Either[A, B]] {
+    def cardinality: BigInt = Cardinality[A].cardinality + Cardinality[B].cardinality
+  }
+
scala> Cardinality.of[(Boolean, Byte)]
+res14: BigInt = 512
+
+scala> Cardinality.of[Either[Boolean, Byte]]
+res15: BigInt = 258
+
+scala> Cardinality.of[Either[Int, (Boolean, Unit)]]
+res16: BigInt = 4294967298
+

So using sum types seem to add the number of inhabitants whereas product types seem to multiply the number of inhabitants. + That makes a lot of sense given their names!

+

So what about that homomorphism we talked about earlier? + Well, a homomorphism is a structure-preserving mapping function between two algebraic structures of the same sort (in this case a semiring).

+

This means that for any two values x and y and the homomorphism f, we get + 1. f(x * y) === f(x) * f(y) + 2. f(x + y) === f(x) + f(y)

+

Now this might seem fairly abstract, but it applies exactly to what we just did. + If we "add" two types of Byte and Boolean, we get an Either[Byte, Boolean] and if we apply the homomorphism function, number to it, we get the value 258. + This is the same as first calling number on Byte and then adding that to the result of calling number on Boolean.

+

And of course the same applies to multiplication and product types. + However, we're still missing something from a valid semiring, we only talked about multiplication and addition, but not about their respective identities.

+

What we did see, though is that Unit has exactly one inhabitant and Nothing has exactly zero. + So maybe we can use these two types to get a fully formed Semiring?

+

Let's try it out! + If Unit is one then a product type of any type with Unit should be equivalent to just the first type.

+

Turns out, it is, we can easily go from something like (Int, Unit) to Int and back without losing anything and the number of inhabitants also stay exactly the same.

+
scala> Cardinality.of[Int]
+res17: BigInt = 4294967296
+
+scala> Cardinality.of[(Unit, Int)]
+res18: BigInt = 4294967296
+
+scala> Cardinality.of[(Unit, (Unit, Int))]
+res19: BigInt = 4294967296
+

Okay, not bad, but how about Nothing? + Given that it is the identity for addition, any type summed with Nothing should be equivalent to that type. + Is Either[Nothing, A] equivalent to A? + It is! Since Nothing doesn't have any values an Either[Nothing, A] can only be a Right and therefore only an A, so these are in fact equivalent types.

+

We also have to check for the absorption law that says that any value mutliplied with the additive identity zero should be equivalent to zero. + Since Nothing is our zero a product type like (Int, Nothing) should be equivalent to Nothing. + This also holds, given the fact that we can't construct a Nothing so we can never construct a tuple that expects a value of type Nothing either.

+

Let's see if this translates to the number of possible inhabitants as well:

+

Additive Identity:

+
scala> Cardinality.of[Either[Nothing, Boolean]]
+res0: BigInt = 2
+
+scala> Cardinality.of[Either[Nothing, (Byte, Boolean)]]
+res1: BigInt = 258
+

Absorption:

+
scala> Cardinality.of[(Nothing, Boolean)]
+res0: BigInt = 0
+
+scala> Cardinality.of[(Nothing, Long)]
+res1: BigInt = 0
+

Nice! + The only thing left now is distributivity. + In type form this means that (A, Either[B, C]) should be equal to Either[(A, B), (A, C)]. + If we think about it, these two types should also be exactly equivalent, woohoo!

+
scala> Cardinality.of[(Boolean, Either[Byte, Short])]
+res20: BigInt = 131584
+
+scala> Cardinality.of[Either[(Boolean, Byte), (Boolean, Short)]]
+res21: BigInt = 131584
+ +

Higher kinded algebraic structures

+

Some of you might have heard of the Semigroupal type class. + But why is it called that, and what is its relation to a Semigroup? + Let's find out!

+

First, let's have a look at Semigroupal:

+
@typeclass trait Semigroupal[F[_]] {
+  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
+}
+

It seems to bear some similarity to Semigroup, we have two values which we somehow combine, and it also shares Semigroups associativity requirement.

+

So far so good, but the name product seems a bit weird. + It makes sense given we combine the A and the B in a tuple, which is a product type, but if we're using products, maybe this isn't a generic Semigroupal but actually a multiplicative one? + Let's fix this and rename it!

+
@typeclass trait MultiplicativeSemigroupal[F[_]] {
+  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
+}
+

Next, let us have a look at what an additive Semigroupal might look like. + Surely, the only thing we'd have to change is going from a product type to a sum type:

+
@typeclass trait AdditiveSemigroupal[F[_]] {
+  def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]
+}
+

Pretty interesting so far, can we top this and add identities to make Monoidals? + Surely we can! For addition this should again be Nothing and Unit for multiplication:

+
@typeclass trait AdditiveMonoidal[F[_]] extends AdditiveSemigroupal[F] {
+  def nothing: F[Nothing]
+}
+
+@typeclass trait MultiplicativeMonoidal[F[_]] extends MultiplicativeSemigroupal[F] {
+  def unit: F[Unit]
+}
+

So now we have these fancy type classes, but how are they actually useful? + Well, I'm going to make the claim that these type classes already exist in cats today, just under different names.

+

Let's first look at the AdditiveMonoidal. + It is defined by two methods, nothing which returns an F[Nothing] and sum which takes an F[A] and an F[B] to create an F[Either[A, B]].

+

What type class in Cats could be similar? + First, we'll look at the sum function and try to find a counterpart for AdditiveSemigroupal. + Since we gave the lower kinded versions of these type classes symbolic operators, why don't we do the same thing for AdditiveSemigroupal?

+

Since it is additive it should probably contain a + somewhere and it should also show that it's inside some context.

+

Optimally it'd be something like [+], but that's not a valid identifier so let's try <+> instead!

+
def <+>[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]
+

Oh! The <+> function already exists in cats as an alias for combineK which can be found on SemigroupK, but it's sort of different, it takes two F[A]s and returns an F[A], not quite what we have here.

+

Or is it? + These two functions are actually the same, and we can define them in terms of one another as long as we have a functor:

+
def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]
+
+def combineK[A](x: F[A], y: F[A]): F[A] = {
+
+  val feaa: F[Either[A, A]] = sum(x, y)
+  feaa.map(_.merge)
+}
+

So our AdditiveSemigroupal is equivalent to SemigroupK, so probably AdditiveMonoidal is equivalent to MonoidK, right?

+

Indeed, and we can show that quite easily.

+

MonoidK adds an empty function with the following definition:

+
def empty[A]: F[A]
+

This function uses a universal quantifier for A, which means that it works for any A, which then means that it cannot actually include any particular A and is therefore equivalent to F[Nothing] which is what we have for AdditiveMonoidal.

+

Excellent, so we found counterparts for the additive type classes, and we already now that MultiplicativeSemigroupal is equivalent to cats.Semigroupal. + So the only thing left to find out is the counterpart of MultiplicativeMonoidal.

+

I'm going to spoil the fun and make the claim that Applicative is that counterpart. + Applicative adds pure, which takes an A and returns an F[A]. + MultiplicativeMonoidal adds unit, which takes no parameters and returns an F[Unit]. + So how can we go from one to another? + Well the answer is again using a functor:

+
def unit: F[Unit]
+
+def pure(a: A): F[A] = unit.map(_ => a)
+

Applicative uses a covariant functor, but in general we could use invariant and contravariant structures as well. + Applicative also uses <*> as an alias for using product together with map, which seems like further evidence that our intuition that its a multiplicative type class is correct.

+

So in cats right now we have <+> and <*>, is there also a type class that combines both similar to how Semiring combines + and *?

+

There is, it is called Alternative, it extends Applicative and MonoidK and if we were super consistent we'd call it a Semiringal:

+
@typeclass 
+trait Semiringal[F[_]] extends MultiplicativeMonoidal[F] with AdditiveMonoidal[F]
+

Excellent, now we've got both Semiring and a higher kinded version of it. + Unfortunately the lower kinded version can't be found in Cats yet, but hopefully in a future version it'll be available as well.

+

If it were available, we could derive a Semiring for any Alternative the same we can derive a Monoid for any MonoidK or Applicative. + We could also lift any Semiring back into Alternative, by using Const, just like we can lift Monoids into Applicative using Const.

+

To end this blog post, we'll have a very quick look on how to do that.

+
import Semiring.ops._
+
+case class Const[A, B](getConst: A)
+
+implicit def constSemiringal[A: Semiring] = new Semiringal[Const[A, ?]] {
+  def sum[B, C](fa: Const[A, B], fb: Const[A, C]): Const[A, Either[B, C]] =
+    Const(fa.getConst + fb.getConst)
+
+  def product[B, C](fa: Const[A, B], fb: Const[A, C]): Const[A, (B, C)] =
+    Const(fa.getConst * fb.getConst)
+
+  def unit: Const[A, Unit] =
+    Const(Semiring[A].one)
+
+  def nothing: Const[A, Nothing] =
+    Const(Semiring[A].zero)
+}
+ +

Conclusion

+

Rings and Semirings are very interesting algebraic structures and even if we didn't know about them we've probably been using them for quite some time. + This blog post aimed to show how Applicative and MonoidK relate to Monoid and how algebraic data types form a semiring and how these algebraic structures are pervasive throughout Scala and other functional programming languages. + For me personally, realizing how all of this ties together and form some really satisfying symmetry was really mind blowing and I hope this blog post can give some good insight on recognizing these interesting similarities throughout Cats and other libraries based on different mathematical abstractions. + For further material on this topic, you can check out this talk.

+ +

Addendum

+

This article glossed over commutativity in the type class encodings. + Commutativity is very important law for semrings and the code should show that. + However, since this post already contained a lot of different type class definitions, adding extra commutative type class definitions that do nothing but add laws felt like it would distract from what is trying to be taught.

+

Moreover I focused on the cardinality of only the types we need, but for completeness' sake, we could also add instances of Cardinality for things like A => B , Option[A] or Ior[A, B]. + These are: + 1. Cardinality.of[A => B] === Cardinality.of[B].pow(Cardinality.of[A]) + 2. Cardinality.of[Option[A]] === Cardinality.of[A] + 1 + 3. Cardinality.of[Ior[A, B]] === Cardinality.of[A] + Cardinality.of[B] + Cardinality.of[A] * Cardinality.of[B]

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +
+ Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/shared-state-in-fp.html b/blog/shared-state-in-fp.html new file mode 100644 index 00000000..77fef6a9 --- /dev/null +++ b/blog/shared-state-in-fp.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + + + + + + + Shared State in Functional Programming + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Shared State in Functional Programming

+ + + technical + +
+
+
+
+

Shared State in Functional Programming

+

Newcomers to functional programming (FP) are often very confused about the proper way to share state without breaking purity and end up having a mix of pure and impure code that defeats the purpose of having pure FP code in the first place.

+

This is the reason that has motivated me to write a beginner friendly guide :)

+ +

Use Case

+

We have a program that runs three computations at the same time and updates the internal state to keep track of the + tasks that have been completed. When all the tasks are completed we request the final state and print it out.

+

You should get an output similar to the following one:

+
Starting process #1
+Starting process #2
+Starting process #3
+  ... 3 seconds
+Done #2
+  ... 5 seconds
+Done #1
+  ... 10 seconds
+Done #3
+List(#2, #1, #3)
+

We'll use the concurrency primitive Ref[IO, List[String]] to represent our internal state because it's a great fit.

+ +

Getting started

+

So this is how we might decide to start writing our code having some knowledge about cats.effect.IO:

+
import cats.effect._
+import cats.effect.concurrent.Ref
+import cats.instances.list._
+import cats.syntax.all._
+
+import scala.concurrent.duration._
+
+object sharedstate extends IOApp {
+
+  var myState: Ref[IO, List[String]] = _
+
+  def putStrLn(str: String): IO[Unit] = IO(println(str))
+
+  val process1: IO[Unit] = {
+    putStrLn("Starting process #1") *>
+      IO.sleep(5.seconds) *>
+      myState.update(_ ++ List("#1")) *>
+      putStrLn("Done #1")
+  }
+
+  val process2: IO[Unit] = {
+    putStrLn("Starting process #2") *>
+      IO.sleep(3.seconds) *>
+      myState.update(_ ++ List("#2")) *>
+      putStrLn("Done #2")
+  }
+
+  val process3: IO[Unit] = {
+    putStrLn("Starting process #3") *>
+      IO.sleep(10.seconds) *>
+      myState.update(_ ++ List("#3")) *>
+      putStrLn("Done #3")
+  }
+
+  def masterProcess: IO[Unit] = {
+    myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()
+    val ioa = List(process1, process2, process3).parSequence.void
+    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
+  }
+
+  override def run(args: List[String]): IO[ExitCode] =
+    masterProcess.as(ExitCode.Success)
+
+}
+

We defined a var myState: Ref[IO, List[String]] initialized as null so we can create it on startup and all the child processes can have access to it. A so called global state.

+

But now we try to run our application and we encounter our first ugly problem: NullPointerException on line 19. All the processes are defined by using myState which has not yet been initialized. So an easy way to fix it is to define all our processes as lazy val.

+
lazy val process1: IO[Unit] = ???
+lazy val process2: IO[Unit] = ???
+lazy val process3: IO[Unit] = ???
+

That worked, brilliant! We have an application that meets the business criteria and most importantly it works!

+ +

Rethinking our application

+

But let's take a step back and review our code once again, there are at least two pieces of code that should have caught your attention:

+
var myState: Ref[IO, List[String]] = _
+

We are using var and initializing our state to null, OMG! Also the workaround of lazy val should get you thinking...

+

And here's the second obvious one:

+
myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()
+

We require our myState to be of type Ref[IO, List[String] but the smart constructor gives us an IO[Ref[IO, List[String]]] so we are "forced" to call unsafeRunSync() to get our desired type. And there's a reason for that, the creation of a Ref[F, A] is side-effectful, therefore it needs to be wrapped in IO to keep the purity.

+

But wait a minute... that unsafeRunSync() is something that you should only see at the edge of your program, most commonly in the main method that is invoked by the JVM and that is impure by nature (of type Unit). But because we are using IOApp we shouldn't be calling any operations which names are prefixed with unsafe.

+

You say to yourself, yes, I know this is bad and ugly but I don't know a better way to share the state between different computations and this works. But we know you have heard that funcional programming is beautiful so why doing this?

+ +

Functional Programming

+

Okay, can we do better? Of course we do and you wouldn't believe how simple it is!

+

Let's get started by getting rid of that ugly var myState initialized to null and pass it as parameter to the processes that need to access it:

+
import cats.effect._
+import cats.effect.concurrent.Ref
+import cats.instances.list._
+import cats.syntax.all._
+
+import scala.concurrent.duration._
+
+object sharedstate extends IOApp {
+
+  def putStrLn(str: String): IO[Unit] = IO(println(str))
+
+  def process1(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #1") *>
+      IO.sleep(5.seconds) *>
+      myState.update(_ ++ List("#1")) *>
+      putStrLn("Done #1")
+  }
+
+  def process2(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #2") *>
+      IO.sleep(3.seconds) *>
+      myState.update(_ ++ List("#2")) *>
+      putStrLn("Done #2")
+  }
+
+  def process3(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #3") *>
+      IO.sleep(10.seconds) *>
+      myState.update(_ ++ List("#3")) *>
+      putStrLn("Done #3")
+  }
+
+  def masterProcess: IO[Unit] = {
+    val myState: Ref[IO, List[String]] = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()
+
+    val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
+    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
+  }
+
+  override def run(args: List[String]): IO[ExitCode] =
+    masterProcess.as(ExitCode.Success)
+
+}
+

Great! We got rid of that global state and we are now passing our Refas a parameter. Remember that it is a concurrency primitive meant to be accesed and modified in concurrent scenarios, so we are safe here.

+

Notice how all our processes are now defined as def processN(myState: Ref[IO, List[String]]).

+ +

A well known method: flatMap!

+

Now, we still have that unsafeRunSync() hanging around our code, how can we get rid of it? The answer is flatMap!!!

+
def masterProcess: IO[Unit] =
+  Ref.of[IO, List[String]](List.empty[String]).flatMap { myState =>
+    val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
+    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
+  }
+

You only need to call flatMap once up in the call chain where you call the processes to make sure they all share the same state. If you don't do this, a new Ref will be created every time you flatMap (remember creating a Ref is side effectful!) and thus your processes will not be sharing the same state changing the behavior of your program.

+

We now have a purely functional code that shares state in a simple and pure fashion. Here's the entire FP program:

+
import cats.effect._
+import cats.effect.concurrent.Ref
+import cats.instances.list._
+import cats.syntax.all._
+
+import scala.concurrent.duration._
+
+object sharedstate extends IOApp {
+
+  def putStrLn(str: String): IO[Unit] = IO(println(str))
+
+  def process1(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #1") *>
+      IO.sleep(5.seconds) *>
+      myState.update(_ ++ List("#1")) *>
+      putStrLn("Done #1")
+  }
+
+  def process2(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #2") *>
+      IO.sleep(3.seconds) *>
+      myState.update(_ ++ List("#2")) *>
+      putStrLn("Done #2")
+  }
+
+  def process3(myState: Ref[IO, List[String]]): IO[Unit] = {
+    putStrLn("Starting process #3") *>
+      IO.sleep(10.seconds) *>
+      myState.update(_ ++ List("#3")) *>
+      putStrLn("Done #3")
+  }
+
+  def masterProcess: IO[Unit] =
+    Ref.of[IO, List[String]](List.empty[String]).flatMap { myState =>
+      val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
+      ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
+    }
+
+  override def run(args: List[String]): IO[ExitCode] =
+    masterProcess.as(ExitCode.Success)
+
+}
+ +

Mutable reference

+

As I mentioned in one of the sections above, the creation of Ref[F, A] is side-effectful. So what does this mean? Does it write to disk? Does it perform HTTP Requests? Not exactly.

+

It all comes down to wanting to keep the property of referential transparency while sharing and mutating state. So let's again put up an example to follow up along with some explanation:

+
var a = 0
+def set(n: Int) = a = n
+def get: Int = a
+

Here we have imperative and impure code that mutates state. So we can try wrapping things in IO to keep side effects under control:

+
class IORef {
+  var a: Int = 0
+  def set(n: Int): IO[Unit] = IO(a = n)
+  def get: IO[Int] = IO.pure(a)
+}
+

This is way better since now the mutation is encapsulated within IORef but we are now pushing some responsibility to whoever creates an IORef. Consider this:

+
val ref = new IORef()
+

If we have two or more references to ref in our code, they will be referring to the same mutable state and we don't really want that. We can make sure this doesn't happen and a way to achieve this is to wrap the creation of IORef in IO:

+
private class IORef {
+  var a: Int = 0
+  def set(n: Int): IO[Unit] = IO(a = n)
+  def get: IO[Int] = IO.pure(a)
+}
+
+object IORef {
+  def apply: IO[IORef] = IO(new IORef)
+}
+

We have now regained purity. So whenever you create an IORef you'll get an IO[IORef] instead of a mutable reference to IORef. This means that when you invoke flatMap on it twice you'll get two different mutable states, and this is the power of Referential Transparency. It gives you way more control than having a val ref hanging around in your code and gives you local reasoning.

+

All these examples are written in terms of IO for the sake of simplicity but in practice they are polymorphic on the effect type.

+ +

Applying the technique in other libraries

+

Although in the example above we only see how it's done with the cats-effect library, this principle expands to other FP libraries as well.

+

For example, when writing an http4s application you might need to create an HttpClient that needs to be used by more than one of your services. So again, create it at startup and flatMap it once:

+
object HttpServer extends StreamApp[IO] {
+
+  override def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, ExitCode] =
+    for {
+      httpClient <- Http1Client.stream[IO]()
+      endpoint1  = new HttpEndpointOne[IO](httpClient)
+      endpoint2  = new HttpEndpointTwo[IO](httpClient)
+      exitCode   <- BlazeBuilder[F]
+                      .bindHttp(8080, "0.0.0.0")
+                      .mountService(endpoint1)
+                      .mountService(endpoint2)
+                      .serve
+    } yield exitCode
+
+}
+
+class HttpEndpointOne[F[_]](client: Client[F]) { ... }
+class HttpEndpointTwo[F[_]](client: Client[F]) { ... }
+

When writing fs2 applications you can apply the same technique, for example between processes that share a Queue, Topic, Signal, Semaphore, etc.

+

Remember that if you are forced to call unsafeRunSync() other than in your main method it might be a code smell.

+ +

Conclusion

+

To conclude this post I would like to give a big shout out to @SystemFW who has been untiringly teaching this concept in the Gitter channels. And here's a quote from his response on Reddit:

+
+

At the end of the day all the benefits from referential transparency boil down to being able to understand and build code compositionally. That is, understanding code by understanding individual parts and putting them back together, and building code by building individual parts and combining them together. This is only possible if local reasoning is guaranteed, because otherwise there will be weird interactions when you put things back together, and referential transparency is defined as something that guarantees local reasoning.

+

In the specific case of state sharing, this gives rise to a really nice property: since the only way to share is passing things as an argument, the regions of sharing are exactly the same of your call graph, so you transform an important aspect of the behaviour ("who shares this state?") into a straightforward syntactical property ("what methods take this argument"?). This makes shared state in pure FP a lot easier to reason about than its side-effectful counterpart imho.

+
+

In simple terms, remind yourself about this: "flatMap once and pass the reference as an argument!"

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Gabriel Volpe + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/singleton-instance-trick-unsafe.html b/blog/singleton-instance-trick-unsafe.html new file mode 100644 index 00000000..d8dadbf7 --- /dev/null +++ b/blog/singleton-instance-trick-unsafe.html @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + The singleton instance trick is unsafe + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

The singleton instance trick is unsafe

+ + + technical + +
+
+
+
+

The singleton instance trick is unsafe

+

Also, the “fake covariance” trick.

+

Sometimes, Scala programmers notice a nice optimization they can use + in the case of a class that has an invariant type parameter, but in + which that type parameter + appears in variant or phantom position in the actual data involved. + =:= + is an + example of the phantom case.

+
sealed abstract class =:=[From, To] extends (From => To) with Serializable
+

scala.collection.immutable.Set + is an example of the covariant case.

+

Here is the optimization, which is very similar to + the Liskov-lifting previously discussed: + a “safe” cast of the invariant type + parameter can be made, because all operations on the casted result + remain sound. + Here it is for Set, + an example of the “fake covariance” trick:

+
override def toSet[B >: A]: Set[B] = this.asInstanceOf[Set[B]]
+

And + here it is for =:=, + an example of the “singleton instance” trick.

+
private[this] final val singleton_=:= = new =:=[Any,Any] {
+  def apply(x: Any): Any = x
+}
+object =:= {
+  implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
+}
+

Unless you are using + the Scalazzi safe Scala subset, + which forbids referentially nontransparent and nonparametric + operations, these tricks are unsafe.

+ +

Types are erased

+

Many people are confused that they cannot write functions like this:

+
def addone[A](x: A): A = x match {
+  case s: String => s + "one"
+  case i: Int => i + 1
+}
+

Being given an error as follows.

+
<console>:8: error: type mismatch;
+ found   : String
+ required: A
+         case s: String => s + "one"
+                             ^
+<console>:9: error: type mismatch;
+ found   : Int
+ required: A
+         case i: Int => i + 1
+                          ^
+

Let’s consider only one case, the first. In the right-hand side (RHS) + of this case, you have not proved that A is String at all! You + have only proved that, in addition to definitely having type A, x + also definitely has type String. In type relationship language,

+
x.type <: A
+x.type <: String
+

All elephants are grey and are also animals, but it does not follow + that all grey things are animals or vice versa. If you use a cast to + “fix” this, you have produced type-incorrect code, period.

+ +

Type recovery

+

Under special circumstances, however, information about a type + parameter can be recovered, safe and sound. Take this:

+
abstract class Box[A]
+case class SBox(x: String) extends Box[String]
+case class IBox(x: Int) extends Box[Int]
+
+def addone2[A](b: Box[A]): A = b match {
+  case SBox(s) => s + "one"
+  case IBox(x) => x + 1
+}
+

This compiles, and I don’t even have to have data in the box to get at + the type information that A ~ String or A ~ Int. Consider the + first case. On the RHS, I have

+
b.type <: SBox <: Box[String]
+b.type <: Box[A]
+

In addition, A is invariant, so after going up to + Box[String], b couldn’t have widened that type parameter, or + changed it in any way, without an unsafe cast. Additionally, our + supertype tree cannot contain Box twice with different parameters. + So we have proved that A is String, because we proved that + Box[A] is Box[String].

+

This is very useful when defining + GADTs.

+ +

Partial type recovery

+

Let’s consider a similar ADT with the type parameter marked variant.

+
abstract class CovBox[+A]
+case class CovSBox(x: String) extends CovBox[String]
+
+def addone3[A](b: CovBox[A]): A = b match {
+  case CovSBox(s) => s + "one"
+}
+

This works too, because in the RHS of the case, we proved that:

+
b.type <: CovSBox <: CovBox[String] <: CovBox[A]
+String <: A
+

The only transform in type A could have possibly undergone is a + widening, which must have begun at String. A similar example can + be derived for contravariance.

+ +

Singleton surety

+

In our first example, there is one type that we know must be a subtype + of A, no matter what!

+
def addone[A <: AnyRef](x: A): A = x: x.type
+

(Scala doesn’t like it when we talk about singleton types without an + AnyRef upper bound at least. But the underlying principle holds for + all value types.)

+

Where x is an A of stable type, x.type <: A for all possible A + types. You might say, “that’s uninteresting; obviously x is an A + in this code.” But that isn’t what we’re talking about; our premise + is that any value of type x.type is also an A!

+

So if we could prove that something else had the singleton type + x.type, we would also prove that it shared all of x’s types! We + can do that with a singleton type pattern, which is implemented + (soundly in 2.11) with a reference comparison. Scala lets us use + some of the resulting implications.

+
final case class InvBox[A](b: A)
+def maybeeq[A, B](x: InvBox[A], y: InvBox[B]): A = y match {
+  case _: x.type => y.b
+}
+ +

Unsafety

+

To which you might protest, “there’s only one value of any singleton + type!” Well, yes. And here’s where our seemingly innocent + optimization turns nasty. If you'll recall, it depends upon treating + a value with multiple types via an unsafe cast.

+
def unsafeCoerce[A, B]: A => B = {
+  val a = implicitly[A =:= A]
+  implicitly[B =:= B] match {
+    case _: a.type => implicitly[A =:= B]
+  }
+}
+
+def unsafeCoerce2[A, B]: A => B = {
+  val n = Set[Nothing]()
+  val b = n.toSet[B]
+  n.toSet[A] match {
+    case _: b.type => implicitly[A =:= B]
+  }
+}
+

Both of these compile to what is in essence an identity function.

+
scala> Some(unsafeCoerce[String, Int]("hi"))
+res0: Some[Int] = Some(hi)
+
+scala> Some(unsafeCoerce2[String, Int]("hi"))
+res1: Some[Int] = Some(hi)
+

In our invariant Box example we decided that, as it was impossible + to change the type parameter without an unsafe cast, we could use that + knowledge in the consequent types. In unsafeCoerce, where ? + represents the value before the match keyword:

+
?.type <: a.type <: (A =:= A)
+?.type <: (B =:= B)
+A ~ B
+

In unsafeCoerce2,

+
?.type <: b.type <: Set[B]
+?.type <: Set[A]
+A ~ B
+

There is nothing wrong with Scala making this logical inference. The + “optimization” of that cast is not safe.

+

Let me reiterate: Scala’s type inference surrounding pattern + matching should not be “fixed” to make unsafe casts “safer” and steal + our GADTs. Unsafe code is unsafe.

+ +

Scalazzi safe Scala subset saves us

+

For types like these, it is not possible to exploit this unsafety + without a reference check, which is what a singleton type pattern + compiles to. As the Scalazzi safe subset forbids referentially + nontransparent operations, if you follow its rules, these + optimizations become safe again.

+

This is just yet another of countless ways in which following the + Scalazzi rules makes your code safer and easier to reason about.

+

That isn’t to say it’s impossible to derive a situation where the + optimization exposes an unsafeCoerce in Scalazzi code. However, you + must specially craft a type in order to do so.

+
abstract class Oops[A] {
+  def widen[B>:A]: Oops[B] = this.asInstanceOf[Oops[B]]
+}
+case class Bot() extends Oops[Nothing]
+
+def unsafeCoerce3[A, B]: A => B = {
+  val x = Bot()
+  x.widen[A] match {
+    case Bot() => implicitly[A <:< B]
+  }
+}
+

The implication being

+
?.type <: Bot <: Oops[Nothing]
+?.type <: Oops[A]
+Nothing ~ A
+

Scalaz + uses the optimization under consideration in scalaz.IList. + So would generalized Functor-based Liskov-lifting, as discussed at + the end of “When can Liskov be lifted?”, + were it to be implemented. However, these cases do not fit the bill + for exploitation from Scalazzi-safe code.

+

On the other hand, the singleton type pattern approach may be used in + all cases where the optimization may be invoked by a caller, + including standard library code where some well-meaning contributor + might add such a harmless-seeming avoidance of memory allocation + without your knowledge. Purity pays, and often in very nonobvious + ways.

+

This article was tested with Scala 2.11.1.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/social/index.html b/blog/social/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/blog/social/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/spires-ops-macros.html b/blog/spires-ops-macros.html new file mode 100644 index 00000000..f866fc97 --- /dev/null +++ b/blog/spires-ops-macros.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + How to use Spire's Ops macros in your own project + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

How to use Spire's Ops macros in your own project

+ + + technical + +
+
+
+
+

How to use Spire's Ops macros in your own project

+ +

What are Spire's Ops macros?

+

Spire's type classes abstract over very basic operators like + and + *. These operations are normally very fast. This means that any + extra work that happens on a per-operation basis (like boxing or + object allocation) will cause generic code to be slower than its + direct equivalent.

+

Efficient, generic numeric programming is Spire's raison d'être. We + have developed a set of Ops macros to avoid unnecessary object + instantiations at compile-time. This post explains how, and + illustrates how you can use these macros in your code!

+ +

How implicit operators on type classes usually work

+

When using type classes in Scala, we rely on implicit conversions to + "add" operators to an otherwise generic type.

+

In this example, A is the generic type, Ordering is the type + class, and > is the implicit operator. foo1 is the code that the + programmer writes, and foo4 is a translation of that code after + implicits are resolved, and syntactic sugar is expanded.

+
import scala.math.Ordering
+import Ordering.Implicits._
+
+def foo1[A: Ordering](x: A, y: A): A =
+  x > y
+
+def foo2[A](x: A, y: A)(implicit ev: Ordering[A]): A =
+  x > y
+
+def foo3[A](x: A, y: A)(implicit ev: Ordering[A]): A =
+  infixOrderingOps[A](x)(ev) > y
+
+def foo4[A](x: A, y: A)(implicit ev: Ordering[A]): A =
+  new ev.Ops(x) > y
+

(This is actually slightly wrong. The expansion to foo4 won't happen + until runtime, when infixOrderingOps is called. But it helps + illustrate the point.)

+

Notice that we instantiate an ev.Ops instance for every call to + >. This is not a big deal in many cases, but for a call that is + normally quite fast it will add up when done many (e.g. millions) of + times.

+

It is possible to work around this:

+
def bar[A](x: A, y: A)(implicit ev: Ordering[A]): A =
+  ev.gt(x, y)
+

The ev parameter contains the method we actually want (gt), so + instead of instantiating ev.Ops this code calls ev.gt directly. + But this approach is ugly. Compare these two methods:

+
def qux1[A: Field](x: A, y: A): A =
+  ((x pow 2) + (y pow 2)).sqrt
+
+def qux2[A](x: A, y: A)(implicit ev: Field[A]): A =
+  ev.sqrt(ev.plus(ev.pow(x, 2), ev.pow(y, 2)))
+

If you have trouble reading qux2, you are not alone.

+

At this point, it looks like we can either write clean, readable code + (qux1), or code defensively to avoid object allocations (qux2). + Most programmers will just choose one or the other (probably the + former) and go on with their lives.

+

However, since this issue affects Spire deeply, we spent a bit more + time looking at this problem to see what could be done.

+ +

Having our cake and eating it too

+

Let's look at another example, to compare how the "nice" and "fast" + code snippets look after implicits are resolved:

+
def niceBefore[A: Ring](x: A, y: A): A =
+  (x + y) * z
+
+def niceAfter[A](x: A, y: A)(implicit ev: Ring[A]): A =
+  new RingOps(new RingOps(x)(ev).+(y))(ev).*(z)
+
+def fast[A](x: A, y: A)(implicit ev: Ring[A]): A =
+  ev.times(ev.plus(x, y), z)
+

As we can see, niceAfter and fast are actually quite similar. If + we wanted to transform niceAfter into fast, we'd just have to:

+
    +
  1. +

    Figure out the appropriate name for symbolic operators. In this + example, + becomes plus and * becomes times.

    +
  2. +
  3. +

    Rewrite the object instantiation and method call, calling the + method on ev instead and passing x and y as arguments. In + this example, new Ops(x)(ev).foo(y) becomes ev.foo(x, y).

    +
  4. +
+

In a nutshell, this transformation is what Spire's Ops macros do.

+ +

Using the Ops macros

+

Your project must use Scala 2.10+ to be able to use macros.

+

To use Spire's Ops macros, you'll need to depend on the spire-macros + package. If you use SBT, you can do this by adding the following line + to build.sbt:

+
libraryDependencies += "org.spire-math" %% "spire-macros" % "0.6.1"
+

You will also need to enable macros at the declaration site of your + ops classes:

+
import scala.language.experimental.macros
+ +

Let's see an example

+

Consider Sized, a type class that abstracts over the notion of + having a size. Type class instances for Char, Map, and List are + provided in the companion object. Of course, users can also provide + their own instances.

+

Here's the code:

+
trait Sized[A] {
+  def size(a: A): Int
+  def isEmpty(a: A): Boolean = size(a) == 0
+  def nonEmpty(a: A): Boolean = !isEmpty(a)
+  def sizeCompare(x: A, y: A): Int = size(x) compare size(y)
+}
+
+object Sized {
+  implicit val charSized = new Sized[Char] {
+    def size(a: Char): Int = a.toInt
+  }
+
+  implicit def mapSized[K, V] = new Sized[Map[K, V]] {
+    def size(a: Map[K, V]): Int = a.size
+  }
+
+  implicit def listSized[A] = new Sized[List[A]] {
+    def size(a: List[A]): Int = a.length
+    override def isEmpty(a: List[A]): Boolean = a.isEmpty
+    override def sizeCompare(x: List[A], y: List[A]): Int = (x, y) match {
+      case (Nil, Nil) => 0
+      case (Nil, _) => -1
+      case (_, Nil) => 1
+      case (_ :: xt, _ :: yt) => sizeCompare(xt, yt)
+    }
+  }
+}
+

(Notice that Sized[List[A]] overrides some of the "default" + implementations to be more efficient, since taking the full length of + a list is an O(n) operation.)

+

We'd like to be able to call these methods directly on a generic type + A when we have an implicit instance of Sized[A] available. So + let's define a SizedOps class, using Spire's Ops macros:

+
import spire.macrosk.Ops
+import scala.language.experimental.macros
+
+object Implicits {
+  implicit class SizedOps[A: Sized](lhs: A) {
+    def size(): Int = macro Ops.unop[Int]
+    def isEmpty(): Boolean = macro Ops.unop[Boolean]
+    def nonEmpty(): Boolean = macro Ops.unop[Boolean]
+    def sizeCompare(rhs: A): Int = macro Ops.binop[A, Int]
+  }
+}
+

That's it!

+

Here's what it would look like to use this type class:

+
import Implicits._
+
+def findSmallest[A: Sized](as: Iterable[A]): A =
+  as.reduceLeft { (x, y) =>
+    if ((x sizeCompare y) < 0) x else y
+  }
+
+def compact[A: Sized](as: Vector[A]): Vector[A] =
+  as.filter(_.nonEmpty)
+
+def totalSize[A: Sized](as: Seq[A]): Int =
+  as.foldLeft(0)(_ + _.size)
+

Not bad, eh?

+ +

The fine print

+

Of course, there's always some fine-print.

+

In this case, the implicit class must use the same parameter names + as above. The constructor parameter to SizedOps must be called + lhs and the method parameter (if any) must be called + rhs. Also, unary operators (methods that take no parameters, like + size) must have parenthesis.

+

How the macros handle classes with multiple constructor parameters, or + multiple method parameters? They don't. We haven't needed to support + these kinds of exotic classes, but it would probably be easy to extend + Spire's Ops macros to support other shapes as well.

+

If you fail to follow these rules, or if your class has the wrong + shape, your code will fail to compile. So don't worry. If your code + compiles, it means you got it right!

+ +

Symbolic names

+

The previous example illustrates rewriting method calls to avoid + allocations, but what about mapping symbolic operators to method + names?

+

Here's an example showing the mapping from * to times:

+
trait CanMultiply[A] {
+  def times(x: A, y: A): A
+}
+
+object Implicits {
+  implicit class MultiplyOps[A: CanMultiply](lhs: A) {
+    def *(rhs: A): A = macro Ops.binop[A, A]
+  }
+}
+
+object Example {
+  import Implicits._
+
+  def gak[A: CanMultiply](a: A, as: List[A]): A =
+    as.foldLeft(a)(_ * _)
+  }
+}
+

Currently, the Ops macros have a large (but Spire-specific) + mapping + from symbols to names. However, your project may want to use different names + (or different symbols). What then?

+

For now, you are out of luck. In Spire 0.7.0, we plan to make it + possible to use your own mapping. This should make it easier for other + libraries that make heavy use of implicit symbolic operators + (e.g. Scalaz) to use these macros as well.

+ +

Other considerations

+

You might wonder how the Ops macros interact with + specialization. Fortunately, macros are expanded before the + specialization phase. This means you don't need to worry about it! If + your type class is specialized, and you invoke the implicit from a + specialized (or non-generic) context, the result will be a specialized + call.

+

(Of course, using Scala's specialization is tricky, and deserves its + own blog post. The good news is that type classes are some of the + easiest structures to specialize correctly in Scala.)

+

Evaluating the macros at compile-time also means that if there are + problems with the macro, you'll find out about those at compile-time + as well. While we expect that many projects will benefit from the Ops + macros, they were designed specifically for Spire so it's possible + that your project will discover problems, or need new features.

+

If you do end up using these macros, + let us know how + they work for you. If you have problems, please open an + issue, and if you have bug + fixes (or new features) feel free to open a + pull request!

+ +

Conclusion

+

We are used to thinking about abstractions having a cost. So we often + end up doing mental accounting: "Is it worth making this generic? Can + I afford this syntactic sugar? What will the runtime impact of this + code be?" These condition us to expect that code can either be + beautiful or fast, but not both.

+

By removing the cost of implicit object instantiation, Spire's Ops + macros raise the abstraction ceiling. They allow us to make free use + of type classes without compromising performance. Our goal is to close + the gap between direct and generic performance, and to encourage the + widest possible use of generic types and type classes in Scala.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Erik Osheim + + +
+ Erik Osheim is one of the founders of Typelevel, and maintains several Scala libraries including Cats, Spire, and others. He hacks Scala for a living at Stripe, and is committed to having his cake and eating it too when it comes to functional programming. Besides programming he spends time playing music, drinking tea, and cycling around Providence, Rhode Island. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/spotify-foss-fund.html b/blog/spotify-foss-fund.html new file mode 100644 index 00000000..ee1c6795 --- /dev/null +++ b/blog/spotify-foss-fund.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + Spotify FOSS Fund 2024 + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Spotify FOSS Fund 2024

+ + + governance + +
+
+
+
+

Spotify FOSS Fund 2024

+

We're excited to announce that Typelevel has been chosen as a recipient of the 2024 Spotify FOSS Fund! + As a result, Spotify has donated €20,000 to Typelevel's OpenCollective.

+

Our current funding goes towards recurring infrastructure expenses, and sending members of our code of conduct committee for training to better support our community. + With these extra funds we’d like to expand our initiatives to encourage and support new contributors and users! + We’ve specifically discussed funding student or first time contributors through an initiative like Outreachy and engaging a technical writer to help improve documentation.

+

You can read more about the 2024 Spotify FOSS Fund and the other recipients over at Spotify's engineering blog.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/subtype-typeclasses.html b/blog/subtype-typeclasses.html new file mode 100644 index 00000000..a212bfba --- /dev/null +++ b/blog/subtype-typeclasses.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + Subtype type classes don't work + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Subtype type classes don't work

+ + + technical + +
+
+
+
+

Subtype type classes don't work

+

Update: A comprehensive version of this blog post was published at the + 2017 Scala Symposium and is available for free + through the ACM OpenTOC service. The corresponding talk can be found + here.

+

The common encoding of type classes in Scala relies on subtyping. This singular + fact gives us a certain cleanliness in the code, but at what cost?

+ +

Problem

+

Consider the following hierarchy of type classes. A similar hierarchy can be + found in both Cats and Scalaz 7.

+
trait Functor[F[_]]
+
+trait Applicative[F[_]] extends Functor[F]
+
+trait Monad[F[_]] extends Applicative[F]
+
+trait Traverse[F[_]] extends Functor[F]
+

For purposes of demonstration I will be using Cats for the rest of this post, + but the same arguments apply to Scalaz 7.

+

We will also assume that there is syntax accompanying this hierarchy, allowing + us to call methods like map, flatMap, and traverse directly on some + F[A], provided F has the appropriate type class instances (Functor, + Monad, and Traverse, respectively).

+
import cats._
+import cats.implicits._
+

One important consequence is we can use for comprehensions in methods + parameterized over some Monad.

+
def foo[F[_]: Monad]: F[Int] = for {
+  a <- Monad[F].pure(10)
+  b <- Monad[F].pure(20)
+} yield a + b
+

Notice that due to how for comprehensions desugar, there is also + a call to map in there. Since our type class hierarchy is encoded via + subtyping Scala knows a Monad[F] implies a Functor[F], so all is well. + Or is it?

+

Consider a case where we want to abstract over a data type that has + both Monad and Traverse.

+
// Ignore the fact we're not even using `Traverse` - we can't even call `map`!
+def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
+// <console>:19: error: value map is not a member of type parameter F[Int]
+//        def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
+//                                                                   ^
+// <console>:19: error: missing argument list for method identity in object Predef
+// Unapplied methods are only converted to functions when a function type is expected.
+// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`.
+//        def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
+//                                                                       ^
+

We're already in trouble. In order to call map we need F to have a + Functor instance, which it does via Monad as before.. but now also via + Traverse. It is for precisely this reason that this does not work. Because + our encoding of type classes uses subtyping, a Monad[F] is a Functor[F]. + Similarly, a Traverse[F] is a Functor[F]. When implicit resolution + attempts to find a Functor[F], it can't decide between Monad[F]'s or + Traverse[F]'s and bails out. Even though the instances may be, + and arguably should be, the same, the compiler has no way of + knowing that.

+

This problem generalizes to anytime the compiler decides an implicit is ambiguous, + such as method calls.

+
// The fact we don't actually use `Functor` here is irrelevant.
+def bar[F[_]: Applicative: Functor]: F[Int] = Applicative[F].pure(10)
+
def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F]
+// <console>:19: error: ambiguous implicit values:
+//  both value evidence$2 of type cats.Traverse[F]
+//  and value evidence$1 of type cats.Monad[F]
+//  match expected type cats.Functor[F]
+//        def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F]
+//                                                        ^
+

What do we do? For map it is easy enough to arbitrarily pick one + of the instances and call map on that. For function calls you + can thread the implicit through explicitly.

+
def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].map(Monad[F].pure(10))(identity)
+
+def callBar[F[_]: Monad: Traverse]: F[Int] = bar(Monad[F], Monad[F])
+                                       // or bar(Monad[F], Traverse[F])
+

For foo it's not too terrible. For bar though we are + already starting to see it get unwieldy. While we could have passed in + Monad[F] or Traverse[F] for the second parameter which corresponds + to bar's Functor[F] constraint, we can only pass in Monad[F] for + the first parameter to satisfy Applicative[F]. Because implicit resolution + can't disambiguate the Functor[F] by itself we've had to pass it in + explicitly, but by doing so we also have to pass in everything else explicitly! + We become the implicit resolver. And this is with just two constraints, what + if we had three, four, five?

+

And the trouble doesn't end there. We asked for a Monad so let's try using + a for comprehension.

+
def foo[F[_]: Monad: Traverse]: F[Int] = for {
+  a <- Monad[F].pure(10)
+  b <- Monad[F].pure(20)
+} yield a + b
+// <console>:21: error: value map is not a member of type parameter F[Int]
+//          b <- Monad[F].pure(20)
+//                            ^
+

This is also broken! Because of how for comprehensions desugar, a + map call is inevitable which leads to the for comprehension breaking down. + This drastically reduces the ergonomics of doing anything monadic.

+

As with map we could call flatMap on Monad directly, but this quickly + becomes cumbersome.

+
def foo[F[_]: Monad: Traverse]: F[Int] = {
+  val M = Monad[F]
+  M.flatMap(M.pure(10)) { a =>
+    M.map(M.pure(20)) { b =>
+      a + b
+    }
+  }
+}
+

These same problems arise if you ask for two or more type classes that share a + common superclass. Some examples of this:

+
    +
  • Two or more of Monad{Error, Plus, Reader, State, Writer} (ambiguous Monad)
  • +
+
    +
  • This prevents ergonomic use of "MTL-style"
  • +
  • MonadPlus + Monad (ambiguous Monad)
  • +
  • Alternative + Traverse (ambiguous Functor)
  • +
  • MonadRec + MonadPlus (ambiguous Monad)
  • +
+

This suggests every type class have only one subclass. + That is quite limiting as is readily demonstrated by the extremely useful + Applicative and Traverse type classes. What do we do?

+ +

Solution (?)

+

This more or less remains an open problem in Scala. There has been + an interesting alternative prototyped in scato, now making its way to + Scalaz 8, that has received some positive feedback. The gist of the + encoding completely throws out the notion of subtyping, encoding the hierarchy + via members instead.

+
trait Functor[F[_]] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+}
+
+trait Applicative[F[_]] {
+  def functor: Functor[F]
+
+  def pure[A](a: A): F[A]
+  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+}
+
+// Definitions elided for space
+trait Monad[F[_]] { def applicative: Applicative[F] }
+
+trait Traverse[F[_]] { def functor: Functor[F] }
+

Because there is no relation between the type classes, there is no + danger of implicit ambiguity. However, for that very reason, having a + Monad[F] no longer implies having a Functor[F]. Not currently + anyway. What we can do is use implicit conversions to re-encode the + hierarchy.

+
implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] =
+  implicitly[Applicative[F]].functor
+
+implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] =
+  implicitly[Traverse[F]].functor
+

But now we're back to square one.

+
// Syntax for Functor
+implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
+  def map[B](f: A => B): F[B] = F.map(fa)(f)
+}
+
def foo[F[_]: Applicative: Traverse]: F[Int] =
+  implicitly[Applicative[F]].pure(10).map(identity)
+// <console>:18: error: value map is not a member of type parameter F[Int]
+//          implicitly[Applicative[F]].pure(10).map(identity)
+//                                              ^
+// <console>:18: error: missing argument list for method identity in object Predef
+// Unapplied methods are only converted to functions when a function type is expected.
+// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`.
+//          implicitly[Applicative[F]].pure(10).map(identity)
+//                                                  ^
+

Since both implicits have equal priority, the compiler + doesn't know which one to pick. However, Scala has mechanisms for + prioritizing implicits which solves the problem.

+
object Prioritized { // needed for tut, irrelevant to demonstration
+  trait Functor[F[_]] {
+    def map[A, B](fa: F[A])(f: A => B): F[B]
+  }
+
+  // Prioritize implicit conversions - Functor only for brevity
+  trait FunctorConversions1 {
+    implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] =
+      implicitly[Applicative[F]].functor
+  }
+
+  trait FunctorConversions0 extends FunctorConversions1 {
+    implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] =
+      implicitly[Traverse[F]].functor
+  }
+
+  object Functor extends FunctorConversions0
+
+  trait Applicative[F[_]] {
+    def functor: Functor[F]
+
+    def pure[A](a: A): F[A]
+    def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+  }
+
+  // Definition elided for space
+  trait Traverse[F[_]] { def functor: Functor[F] }
+
+  // Syntax for Functor
+  implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
+    def map[B](f: A => B): F[B] = F.map(fa)(f)
+  }
+
+  def foo[F[_]: Applicative: Traverse]: F[Int] = {
+    implicitly[Applicative[F]] // we have Applicative
+    implicitly[Traverse[F]]    // we have Traverse
+    implicitly[Functor[F]]     // we also have Functor!
+
+    // and we have syntax!
+    implicitly[Applicative[F]].pure(10).map(identity)
+  }
+}
+

Because implicit resolution treats implicits in subtypes with higher priority, + we can organize conversions appropriately to prevent ambiguity. Here this means + that applicativeIsFunctor has lower priority than traverseIsFunctor, so + when both Applicative and Traverse instances are in scope and the compiler + is looking for a Functor, traverseIsFunctor wins.

+

One thing to note is that we've baked the implicit hierarchy into Functor + itself - in general this means all superclasses are aware of their subclasses. + This is convenient from a usability perspective as companion objects are + considered during implicit resolution, but from a modularity perspective is + strange and in this case would prevent extensions to the hierarchy from external + sources. This can be solved by removing the hierarchy from the superclasses + (removing Functor's extends FunctorConversions0), but comes at + the cost of needing an import at use sites to bring the implicits into scope.

+
trait Functor[F[_]] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+}
+
+trait Applicative[F[_]] {
+  def functor: Functor[F]
+
+  def pure[A](a: A): F[A]
+  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+}
+
+trait Traverse[F[_]] { def functor: Functor[F] }
+
+implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
+  def map[B](f: A => B): F[B] = F.map(fa)(f)
+}
+
+// Separate from type class definitions
+
+trait FunctorConversions1 {
+  implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] =
+    implicitly[Applicative[F]].functor
+}
+
+trait FunctorConversions0 extends FunctorConversions1 {
+  implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] =
+    implicitly[Traverse[F]].functor
+}
+
+object Prelude extends FunctorConversions0
+
// Need this import to get implicit conversions in scope
+import Prelude._
+
+def foo[F[_]: Applicative: Traverse]: F[Int] = {
+  implicitly[Applicative[F]]
+  implicitly[Traverse[F]]
+  implicitly[Functor[F]]
+
+  implicitly[Applicative[F]].pure(10).map(identity)
+}
+

Prelude could be provided by the base library, but external libraries + with their own type classes can still extend the hierarchy and provide + their own Prelude.

+

A more developed form can be seen in the BaseHierarchy of Scalaz 8.

+

Do we win? I'm not sure. This encoding is certainly more cumbersome than what + we started with, but solves the problems we ran into.

+ +

Compromise?

+

Another thing we can try is to make some compromise of the two. We can + continue to use subtyping for a blessed subset of the hierarchy, and use + members for any branching type class.

+
trait Functor[F[_]] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+}
+
+implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
+  def map[B](f: A => B): F[B] = F.map(fa)(f)
+}
+
+trait Applicative[F[_]] extends Functor[F] {
+  def pure[A](a: A): F[A]
+  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+}
+
+trait Monad[F[_]] extends Applicative[F]
+
+trait Traverse[F[_]] { def functor: Functor[F] }
+
+def foo[F[_]: Applicative: Traverse]: F[Int] =
+  implicitly[Applicative[F]].pure(10).map(identity)
+

This works, but is even messier than the alternatives. We have to + decide which type classes get to live in the subtype hierarchy and which are + doomed (blessed?) to express the relationship with members. But maybe the + pros outweigh the cons. Pull requests with this change have been filed for + Cats and Scalaz 7.3.

+

I'm not convinced that the story is over though. Maybe there's another solution + yet to be discovered.

+

For further reading, there are open tickets for both Cats and + Scalaz 7 documenting the subtyping problem. A discussion around + the Scato encoding for Scalaz 8 can be found here.

+

This article was tested with Scala 2.11.8 and Cats 0.7.2 using tut.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/summit-assistance.html b/blog/summit-assistance.html new file mode 100644 index 00000000..85530dee --- /dev/null +++ b/blog/summit-assistance.html @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + Assistance and Bursaries for the Typelevel Summits + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Assistance and Bursaries for the Typelevel Summits

+ + + summits + +
+
+
+
+

Assistance and Bursaries for the Typelevel Summits

+

Update: The summits are over, which means applying for assistance is not possible any longer.

+

As it says in our code of conduct, we are dedicated to creating a harrassment-free, inclusive community of developers. We want to extend this philosophy to the Typelevel Summits in Philadelphia and Oslo by providing assistance and bursaries to help speakers and attendees who would otherwise not be able to join us.

+ +

Bursaries for Speakers and Attendees

+

We are approaching sponsors and commercial partners to arrange bursaries for conference registration, travel and accommodation. Everyone is welcome to apply. However, note that we may not be able to provide funding in all cases.

+ +

Speaker Assistance: Help with Talk Proposals

+

Many people don’t submit talk proposals to conferences because they can’t think of something to say or don’t think their ideas are interesting.

+

We are offering feedback and suggestions on talk proposals to anyone who requests it. Hopefully we can help you turn a rough idea into a compelling abstract to submit alongside abstracts from other potential speakers.

+

Asking us for help won’t guarantee you a slot in the programme, but it will give you an idea for a talk that would compete against the other submissions.

+

If you have a good talk idea but for some reason it doesn’t make it into the conference, we will help you find another conference or meetup group for your first public speaking experience.

+ +

Speaker Assistance: Speaker Training

+

Many people are concerned or nervous about public speaking. We are offering free workshops to provide newer speakers with advice for tech speaking, whether your abstract is accepted or not.

+

Given the international nature of the Summits, we probably won't be able to organise face-to-face workshops. We will deliver the workshops over Google Hangouts to reach as many attendees as possible.

+ +

Speaker Assistance: Help with Talk Writing

+

If your abstract is accepted to the conference, we are offering free mentoring services to provide advice and feedback as you turn your abstract into a talk.

+

We will assign a mentor to each participating speaker. Your mentor will be available to act as a sounding board as your talk develops from an outline to a fully formed presentation.

+ +

Sound Interesting?

+

If you are planning on attending or speaking in Philadelphia or Oslo, and you would like to apply for any of the services above, please please fill out the application form linked at the top of the page.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Dave Gurnell + + +
+ Dave Gurnell is a Scala consultant and developer working for Underscore in London, UK. He has been a Scala developer since 2010 and a functional programmer for nearly a decade. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/summit-berlin-2018-05-18.html b/blog/summit-berlin-2018-05-18.html new file mode 100644 index 00000000..8fe07779 --- /dev/null +++ b/blog/summit-berlin-2018-05-18.html @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Berlin + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Berlin

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Berlin

+
+

"Berlin-Brandenburg Gate overview" by Cezary Piwowarski is licensed under CC BY-SA 3.0.

+ +

About the Summit

+

The sixth Typelevel Summit will once again happen after the Scala conference: Scala Days, in the same city!

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+

This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the Typelevel Code of Conduct.

+

Special thanks go to Zalando who kindly provide the venue.

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:15Registration
9:00Opening Remarks
9:05Keynote: Just the right kind of Consistency! You need a data store that allows for high throughput and availability, while supporting consistency patterns referential integrity, numerical invariants, or atomic updates? Current designs for data storage forces application developers to decide early in the design cycle, and once and for all, what type of consistency the database should provide. At one extreme, strong consistency requires frequent global coordination; restricting concurrency in this way greatly simplifies application development, but it reduces availability and increases latency. At the opposite extreme, there are systems that provide eventual consistency only: they never sacrifice availability, but application developers must write code to deal with all sorts of concurrency anomalies in order to prevent violation of application invariants. But your system just needs to be consistent enough for the application to be correct! In the talk, I will discuss insights and techniques for analysing the consistency requirements of an application, and show techniques how you can establish them in your system.
10:00Break
10:20A Fistful of Functors Functors show up everywhere in our day-to-day programming. They're so common, we take them for granted - especially in typed functional programming. Beside being common, they're incredibly useful for code reuse. However, functors have several relatively unknown variants: profunctors, bifunctors, contravariant functors, and so on. And guess what - they're amazingly useful, especially combined with other abstractions in the functional programming toolkit! In this talk, we'll cover the many species of functors and see how they can help us with tasks such as serialization, stream processing, and more.
10:55Cancelable IO Task / IO data types have been ported in Scala, inspired by Haskell's monadic IO and are surging in popularity due to the need in functional programming for referential transparency, but also because controlling side effects by means of lawful, FP abstractions makes reasoning about asynchrony and concurrency so much easier. But concurrency brings with it race conditions, i.e. the ability to execute multiple tasks at the same time, possibly interrupting the losers and cleaning up resources afterwards and thus we end up reasoning about preemption. This talk describes the design of Monix's Task for cancelability and preemption, a design that has slowly transpired in cats-effect, first bringing serious performance benefits and now a sane design for cancelation. Topics include how cancelable tasks can be described, along with examples of race conditions that people can relate to, highlighting the challenges faced when people use an IO/Task data type that cannot be interrupted.
11:30Break
11:50Legacy Engineering: Making Criteo Functional Criteo uses a lot of Scala in its code-base. Historically for big data stuff using the usual suspects Spark & Scalding, but more and more for application development. A few Typelevel projects started to appear in our code base as developers started to embrase more sophisticated FP practices in their Scala code. Today most of our Scala projects are built around cats, fs2, doobie, algebra, shapeless, etc. In this presentation we will discuss the challenges of introducing more functional code in a large software company as Criteo and how typelevel projects have helped. We'll talk about what's worked well as well as where the dragons lie.
12:10Introducing namespaces into SQL result sets using nested structural types Many modern programming languages support decent namespaces. Namespaces are commonly structured hierarchies. We bring this power to a database query language, using nested structural types. For this purpose, we hijack table aliases: given a table T containing two columns C of type String and D of type Int, a table T as S is a new table containing two columns S.C of type String and S.D of type Int. In Scala, this is neatly expressed as T : AnyRef { def C : String, def D: Int } T as S : AnyRef { def S: { def C: String, def D: Int } }. We implement the above as operation using the whitebox macro. We rely on Scala's type system's ability to compute Greatest Lower Bounds (GLBs) and Least Upper Bounds (LUBs) of structural types, to enable polymorphic and compositional query creation. To enable GLB and LUB computation for nested structured types, we have patched the Scala compiler.
12:45Lunch Break
14:15Healthy Minds in a Healthy Community Open source communities attract and boast passionate, idealistic people, and many of us invest copious amounts of time and effort to contribute to our projects and support our communities. This underlying emotional attachment can make us more vulnerable to elevated stress, burnout and conflicts. And then there are those of us who also manage mental illness. More often than not, we suffer these struggles in silence, feeling (and fearing) that we're alone in our trouble. Here, our communities can make a huge difference, by building a positive and safe environment where we can blossom and support ourselves and our peers, and feel included. This talk will take a look at open-source communities through the eyes of various mental well-being issues and struggles, and show various things that some communities already do. With this, we hope to support and inspire more communities to help foster healthy minds in a healthy environment.
14:50Typedapi: Define your API on the type level Have you ever thought “I really like Haskell’s Servant. Why don’t we have something like that in Scala?” or “Why can't I just define my APIs as types and Scala does the heavy lifting?”? If so, this talk is made for you. I will tell you a short story about excitement, pain and hate peaking in a climax of type-driven enlightenment. I will tell you my journey of developing Typedapi, a library for building typesafe APIs which moves as many computations to the type level as possible. We will fight many a beast on our way from Scala’s desugaring to folds working just on types. But eventually, we will arrive at our destination, exhausted, with scars but also able to make our code a bit safer again.
15:10Break
15:30An Intuitive Guide to Combining Free Monad and Free Applicative The usage of Free Monads is becoming more well understood, however the lesser known Free Applicative is still somewhat of a mystery to the average Scala developer. In this talk I will explain how you can combine the power of both these constructs in an intuitive and visual manner. You will learn the motivations for using Free Structures in the first place, how we can build up a complex domain, how we can introduce parallelism into our domain and a bunch of other practical tips for designing programs with these structures. This will also give you a deeper understanding of what libraries like Freestyle are doing under the hood and why it is so powerful.
16:05Laws for Free Everyone that uses a functional programming library like cats is aware of the methods that each type class adds and also the properties that the methods need to abide by. But in practice, the properties are not always proved, rather testing that the methods behave as expected. This is a problem waiting to happen, as the algebraic properties are not “optional extras” – if your semigroup's combine is not associative ... then it ain't a semigroup, sorry! So in this talk we will quickly review what we mean by a property and a law and show how to use the cats laws that are available. We'll see that they are simple to use and add literally hundreds of scalacheck tests for free. And impress your boss as well, the tests can be “seen” on the screen!
16:25Break
16:45Lifting Data Structures to the Type-level In this talk, I will give a fast-paced tour of how various features of the Scala type system, many of them under-explored, can be harnessed to construct type-level representations of a number of different datatypes in Scala. The type system offers a limited number of “tools”, such as subtyping, least-upper-bound inference, type unification, singleton types and dependent types and (of course) implicit search, which we can compose in interesting ways to implement type-level operations on these type-level data structures. Value-level operations follow naturally from the types, but this is much less interesting.
17:20Non-academic functional Workflows In this talk I want to report about how we used cats to build a domain specific language that enables us to compile workflows into later executable programs. We started with the idea of having a possibility to combine the multiple unconnected tools that are typically used to analyze an image acquired by our microscopes. The Free Monad in cats looked to us as the perfect fit to write a domain specific language that provides a lot of the advantages of an a modern functional compiler plus enforcing stack safety of the program, which would ultimately provided by third party users. We started developing with a team that had only very little experience in Scala and none with cats. Thanks to the good documentation, Scala Exercises and the straightforward mapping to functional principles, known to us from the university, we were able to get a prototype running for a trade show in 6 weeks.
17:40Closing Remarks
+ +

Venue

+

This event will take place at Zalando.

+ +

Co-located Event

+

The Scala Center will organize a co-located event with roundtables of project maintainers. + Note that because the space is limited, tickets are not on sale for this event. + To register interest, please get in touch via email.

+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Platinum

+

Zalando

+ +

Gold

+

Criteo

+ +

Silver

+

Commercetools + Lightbend + Signify

+
+
+ +
+ + + + + + diff --git a/blog/summit-boston-2018-03-20.html b/blog/summit-boston-2018-03-20.html new file mode 100644 index 00000000..31768d3d --- /dev/null +++ b/blog/summit-boston-2018-03-20.html @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Boston + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Boston

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Boston

+
+

"View from Prudential Tower, Boston" by yeowatzup is licensed under CC BY 2.0.

+ +

About the Summit

+

The fifth Typelevel Summit will once again be co-located with the Northeast Scala Symposium in Cambridge, Massachusetts, with one day of recorded talks and one day of (shared) unconference. + The unconference will happen on March 18, NE Scala on March 19, and finally, the Summit on March 20. + For tickets and other information about attending, please visit the website of the Northeast Scala Symposium.

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+

This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the Typelevel Code of Conduct.

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:15Registration & Breakfast by Clover Food Labs
9:00Opening Remarks
9:05http4s: pure, typeful, functional HTTP in Scala http4s embraces cats.data.Kleisli for service definitions and fs2.Stream for payload processing. With its foundation on these simple types, we’ll take a whirlwind tour of how http4s can: plug into your functional business logic; snap together with other functional libraries; relate servers to clients; test with a simple function call; run on multiple backends; and support multiple higher level DSLs. This talk will not make you an expert in http4s, but show that it’s a relatively short path to become one.
9:40Break
9:55Opaque types: understanding SIP-35 Proposed in SIP-35, opaque types introduce a way to define types which only exist at compile-time. Despite some superficial similarities to value classes, opaque types are significantly more flexible and introduce a number of exciting new possibilities in the Scala design space. Opaque types are motivated by a number of different concerns: desire for a non-class type that exists only at compile-time; efficiency concerns with value classes; limitations of existing type aliases; and need to better support phantom types, type tags, etc. This talk will introduce opaque types, compare them to type aliases and value classes (their two nearest cousins) and then walk through some examples of using opaque types. The focus will be on advantages of using opaque types versus other encodings, including looking at how various types are represented by the JVM at runtime. The talk does not assume in-depth knowledge of the Scala compiler and will motivate the code using plausible real world examples. Attendees will come away from this talk with a better understanding of what SIP-35 means, why it was proposed, and how it could change how we write Scala code for the better.
10:30Big Data at the Intersection of Typed FP and Category Theory Big data, functional programming, and category theory aren’t just three trendy topics smashed into a talk title as bait! Foundational ideas from typed functional programming and category theory have real and practical applications for working with big data and can also be utilized to write more principled pipelines at scale. Whether it’s aggregating with monoids or writing more typesafe Spark jobs, we’ll try and bridge these topics together in a way that can be immediately useful. Some knowledge of Scala and a big data framework like Apache Hadoop, Spark, or Beam is suggested but not necessary.
11:05Break
11:20Tracking with Writer Monad This talk will tell the story of one team at eBay which used to do data tracking in a healthy side-effecting manner. Until the team realized that it’s not that healthy. The solution was found in a Writer Monad (residing in the cats library) as well as in the fact that the writer monad can stay in shades. Some people, especially when they are new to typed FP, don’t like/feel comfortable to see words like Semigroup, Traversable, Writer and such in their domain code. The talk will show how those “scary” parts can be “hidden” by domain specific extension methods.
11:40Duality and How to Delete Half (minus ε) of Your Code In functional programming, we often refer to category theory to explain various concepts. We’ll go over where these concepts do and don’t map well to Scala, as well as what duality is, how we can take advantage of it in Scala, and how to distinguish other concepts that are often confused with it.
12:15Lunch on your own out in Kendall Square
14:00Keynote: Planning for Rainfall Soloway's Rainfall problem, a classic benchmark in computing education research, has proven difficult for many CS1 students. Rainfall tests students' abilities at plan composition, the task of integrating code fragments that implement subparts of a problem into a single program. Nearly all early studies of Rainfall involved students who were learning imperative programming with arrays. Over the last few years, we've conducted studies with students who were learning functional programming instead. These students have produced atypical profiles of compositions and errors on Rainfall (and similar problems). What do these results suggest about the role of programming languages in novice programming education? This talk raises various questions about the relationships between programming languages, program design, curricula, and how students perceive code structure. The talk assumes no experience with having been rained upon.
15:05Break
15:20Why Monads? Monads remain a somewhat mysterious concept in Functional Programming, even though the number of tutorials and blog posts trying to “monadsplain” is at an all-time high. Rather than answering the classical question “What is a Monad?”, we are going to dig more into “Why Monads?”. Building intuition on why monads are useful will help better understand what they are as well. We’ll start with a simple function in a monadless world and we’ll see how annoying it would be to use it in different contexts (List, Maybe, Either). As soon as we are sufficiently frustrated we’ll invoke our friendly Monad and see how much easier our life becomes.
15:55Pants and Monorepos Large or quickly growing projects that consist of many interdependent sub-projects with complex dependencies on third-party libraries can be difficult to handle with standard language build tools. Add on to that code generators and the use of multiple languages and suddenly a lot of your coding life is spent figuring out the right commands to run for the right language, and waiting for all of your code to build. This is where Pants can help! Pants is an open source build tool developed and used by Twitter, Square, Foursquare, Medium, and others. This talk will begin with a brief overview of what Pants is and how it can help, and then discuss new features we have been adding to make the tool faster. In particular, I will discuss the work we have done to restrict what is going on the JVM compile classpaths to make building Scala and Java projects faster, and the work we are doing to implement a remotely executing build system.
16:15Break
16:30Declarative Control Flow with fs2 Stream fs2 is a purely functional streaming library, with support for concurrent and nondeterministic merging of arbitrary streams. Concurrency support means that we can use Stream not only to process data in constant memory, but also as a very general abstraction for control flow: whilst IO gives us an excellent model for a single effectful action, assembling behaviour with it often has a very imperative flavour (pure, but still imperative). This talk will introduce fs2 combinators by example, and will hopefully show how we can model control flow in a declarative, high level, composable fashion. In particular, we will focus on concurrent combinators.
17:05Scalafix @ Twitter scale Scalafix is a fairly popular OSS tool that is useful for performing syntactic and semantic rewrites of Scala code. At Twitter we use it for migration to new library interfaces and maintenance of code health by removal of deprecated code. In this talk we walk through examples of simple and complex Scalafix custom rule specifications for rewrites. We describe the core infrastructure we have set up to support rewrites across our entire monorepo, several orders of magnitude faster than if we were to apply them manually. A simple demo will be included to provide a glimpse of our developer workflow and the user experience with our code base. We envision leveraging this tool for more purposes such as improving performance, upgrading compiler revisions, and assisting developers to automatically recognize and prevent commits of disallowed code patterns.
17:25Closing Remarks
+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Gold

+

Comcast

+
+
+ +
+ + + + + + diff --git a/blog/summit-copenhagen-2017-06-03.html b/blog/summit-copenhagen-2017-06-03.html new file mode 100644 index 00000000..8fd1aec8 --- /dev/null +++ b/blog/summit-copenhagen-2017-06-03.html @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Copenhagen + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Copenhagen

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Copenhagen

+
+

"Nyhavn panorama" by Scythian is licensed under CC BY-SA 3.0.

+ +

About the Summit

+

The fourth Typelevel Summit will be co-located with the Scala conference: Scala Days!

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+

This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the Typelevel Code of Conduct.

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:30Registration
9:00Opening Remarks
9:05Keynote: Inviting everyone to the party Most of today's popular general-purpose programming languages incorporate various aspects of the imperative, object and functional programming paradigms. In some cases, these languages provide clear guidelines as to what style is preferred, and why. As programmers, we have a choice to make about which paradigm(s) to use and to what extent, even if the language provides clear guidelines. How should we think about those choices? Where are the sweet spots to make trade-offs, and what do they depend on? Let's wear the hats of history and science, thinking about the past and looking to the future, examining these apparent conflicts. Paradigm change is not a new thing - perhaps we can learn something from the history books? Wear Some(hat) and party like it's a hat party. With hats.
10:05Break
10:30Refined types for validated configurations Are you tired of writing boilerplate code to load configurations? Have you ever had errors because of bad configuration values? Then this talk is for you! In a live-coding session we’ll see how to encode validation rules on the type-level and load validated settings without any boilerplate code.<br/><br/>In the first part of this talk we’ll look at the challenges associated with loading configurations. We’ll see how typesafe config is typically used, and see how we can eliminate most boilerplate code with Typelevel incubator project PureConfig. We’ll however see that it’s still very much possible to load invalid settings.<br/><br/>In the second part we’ll continue by exploring options to encode type invariants, for enforcing validation, looking at how we can get PureConfig to only load validated settings. We’ll ultimately end up with type-level predicates using Typelevel project refined, and see how we can get PureConfig and refined to work together seamlessly.<br/><br/>The end result is more precise types, with static validation guarantees, and a way of loading validated configurations without boilerplate – finally you can stop worrying about your configurations!
11:10Herding types with Scala macros In Scala we use the term “type safety”, but what it really means? In short, most applications model data types in a form suitable for storage, change, transmission, and use. During the life cycle of the data, we expect to always use the declared type. But reality is a bit more complicated. One of the main practical problems with the use of types occurs when our application interacts with outside world – in requests to external services, different databases or simply with getting data from file. In most cases, an attempt to support type safety leads to writing a lot of code that we always try to avoid. Fortunately we have macros to do all routine job for us! In this talk we will discuss how to use compile-time reflection in library for schemaless key-value database and the benefits of use of macros in production systems.
11:25Break
11:45Monad Stacks or: How I Learned to Stop Worrying and Love the Free Monad In this talk, I will demonstrate various techniques, such as: Monad Transformers, Effects libraries, and Free monads. These techniques can be used to transform scala “spaghetti” code (that is embedded maps, flatmaps and pattern matching) to cleaner code that almost looks like imperative code.
12:25Freestyle: A framework for purely functional FP Apps & Libs Freestyle is a newcomer friendly library encouraging pure FP apps & libs in Scala on top of free monads. In this talk we will discuss design choices and main features including modules, algebras, interpreter composition and what is being planned for future releases.
12:45Lunch Break
14:00Lenses for the masses – introducing Goggles Lenses, or more generally optics, are a technique that is indispensable to modern functional programming. However, implementations have veered between two extremes: incredible abstractive power with a steep learning curve; and limited domain-specific uses that can be picked up in minutes. Why can't we have our cake and eat it too? Goggles is a new Scala macro built over the powerful & popular Monocle optics library. It uses Scala's macros and scandalously flexible syntax to create a compiler-checked mini-language to concisely construct, compose and apply optics, with a gentle, familiar interface, and extravagantly informative compiler errors. In this talk I'll introduce the motivation for lenses and why usability is a problem that so badly needs solving, and how the Goggles library, with Monocle, helps address this in an important way. There'll be some juicy discussion of Scala macro sorcery too!
14:40The power of type classes in big data ETL: a real world use case of combining Spark and Shapeless In this talk, we will explore a type driven approach of big data ETL in Spark. Through code snippets, we will see how to express data processing logic with type classes and singleton types using Shapeless, and how to build a higher level DSL over Spark to make the logic easy to read from the code.
14:55Break
15:15Mastering Typeclass Induction Typeclasses are a powerful feature of the Scala. Using typeclasses to perform type-level induction is a mysterious, yet surprisingly simple, technique used in shapeless, cats, and circe to do generic programming. We will use basic data types to walk you through how this is done and why it’s useful.
15:55Do it with (free?) arrows! DSLs with a monad-based algebra (such as free monads) are becoming popular. Recently, DSLs with an applicative-based algebra (e.g. free applicatives) also aroused interest. It is not new that there exists another notion of computation that sits in between applicative functors and monads: arrows. The goal of this talk is to revisit the relationship between these notions of computation in the context of DSL algebras. Through examples of DSLs based on real world use cases, I will highlight the differences in expressive power between these three notions of computation (and some of their friends) and present the consequences for both interpreters and DSL users. At the end of the talk, you will have a better intuition of what it means that “arrows are more powerful than applicative functors but yet support more interpreters than monads”. You will get a precise understanding of “how much” expressive power you give to your users according to your DSL algebra, and, conversely, “how much” you reduce at the same time the space of the possible DSL interpreters. Finally, you will note that arrows provide an interesting trade off. Notably, they support sequencing, they can be invertible, and their computation graph can be statically analyzed.
16:25Break
16:45Libra: Reaching for the stars with dependent types When we code, we code in numerics - doubles, floats and ints. Those numerics always represent real world quantities. Each problem domain has it’s own kinds of quantities, with its own dimensions. Adding quantities of different dimensions is nonsensical, and can have disastrous consequences. In this talk, we’ll tackle the field of dimensional analysis. We’ll explore dependent types, singleton types, and dive into generic programming along the way. We’ll find that dimensional analysis can be brought much closer to home - in the compilation stage itself! And finally, we’ll end up deriving Libra - a library which brings dimensional analysis to the compile stage for any problem domain.
17:30Reception hosted by 47 Degrees
+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Gold

+

Underscore + Lightbend

+ +

Silver

+

47 Degrees + Soundcloud + Signify + scalac

+
+
+ +
+ + + + + + diff --git a/blog/summit-keynote.html b/blog/summit-keynote.html new file mode 100644 index 00000000..c9d48be5 --- /dev/null +++ b/blog/summit-keynote.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + Keynote at the Philadelphia Summit + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Keynote at the Philadelphia Summit

+ + + summits + +
+
+
+
+

Keynote at the Philadelphia Summit

+

While the CfP for the Philadelphia Summit is still open (have you submitted a proposal yet?), we can already announce our keynote speaker:

+

+ Stephanie Weirich is a Professor at the University of Pennsylvania. Her research centers on programming languages, type theory and machine-assisted reasoning. In particular, she studies generic programming, metaprogramming, dependent type systems, and type inference in the context of functional programming languages. She is currently an Editor of the Journal of Functional Programming and served as the program chair for ICFP in 2010 and the Haskell Symposium in 2009.

+

Stephanie will join the Summit on March 2nd to talk about Dependently-Typed Haskell. + We hope this will give us an exciting opportunity to exchange knowledge between the Haskell and Scala communities.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/summit-lausanne-2019-06-14.html b/blog/summit-lausanne-2019-06-14.html new file mode 100644 index 00000000..de099442 --- /dev/null +++ b/blog/summit-lausanne-2019-06-14.html @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Lausanne + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Lausanne

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Lausanne

+
+

"lauvax" by harmishhk is licensed under CC BY-SA 2.0.

+ +

About the Summit

+

The eight Typelevel Summit will once again be co-located with Scala Days. + Read more about all events in the blog post from the Scala Center.

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:15Registration
9:00Opening Remarks
9:05Keynote: Some Mistakes We Made When Designing Implicits (And Some Things We Got Right) We will talk about the history how Scala's implicits evolved, and about some of the mistakes we could have avoided in hindsight, but also about things that I believe we got right. I'll conclude with a presentation of revised implicits in Scala 3 which fixes most of the current problems and hopefully does not create too many new ones.
10:00Break
10:20Brave New World - tales of PureScript and Haskell in production The rumours are true. Writing code in purely functional languages tends to produce code that is much easier to read, modify and reason about. This talk examines how an experienced Scala team transitioned into writing production code using PureScript in AWS lambda, and services using Haskell.
10:55Actors Design Patterns and Arrowised FRP Object-oriented design patterns combine basic language features to solve coding problems in an extensible way. In functional Scala, we solve those coding problems with functions, combinators, and type-classes, so design patterns are less relevant. Actor design patterns combine basic features of the actors to solve coding problems in an extensible way. Arrowised functional reactive programming (based on languages like Scala and Haskell also offers a way to solve those coding problem using functions, combinators, and type-classes. This talk describes a prototype implementation of AFRP and its primitive types and functions, discusses its similarities to actors, and then describes how some actor design patterns in the existing literature corresponds to constructions of AFRP.
11:30Break
11:45Taking Resources to the Type Level With the Granule project, we are working towards making statically typed functional languages more resource-aware, hence providing a way to enforce stateful protocols regarding memory, file handles, network interaction, etc. Static enforcement of security policies and first-class support for multi-stage programming are further examples of what is possible in a type system based on Linear Logic and Graded Modalities. We present Granule, a functional programming language which combines parametric polymorphism and indexed types with such a type system. Granule programs will probably look very familiar to you, especially if you know some Haskell/ML, but in Granule’s type system we can reason about much more. Hillel Wayne’s Great Theorem Prover Showdown has made a point of the fact that there are many things we can’t easily reason about with functional (programming
12:20Lunch Break at Le Parmentier
13:45Lord of the rings: the Spire numerical towers Spire defines around 80 typeclasses, including 30 coming from algebra and cats-kernel. We’ll see how much of that structure is dictated by mathematical laws, and which parts are the result of design decisions that balance different tradeoffs. In particular, we’ll discuss the different roles played by typeclasses in the Scala ecosystem: as encoding operations obeying well-defined laws; as enabling the use of a particular syntax for those operations, if possible close to the mathematical notation of a domain (and subfields often disagree on the notation!); defining a context in which a combination of typeclasses implicitly imposes additional laws (for example, the ordering of numbers and addition); enabling the user to change the variant of a relation being used (Order); singling out one variant of a structure as canonical (cats: the additive Group for integers); as selecting a particular algorithm for an operation (integer factorization: deterministic or Monte-Carlo). It quickly becomes apparent that these roles conflict. With this in mind, we’ll have a look at some design choices made in Spire. We’ll discuss success stories, such as the clarification of the laws of the % operator, the commutative ring tower that formalizes integer factorization and Euclidean division. We’ll also discuss parts where trade offs have been made, such as the triplication of group structures (Group, AdditiveGroup, MultiplicativeGroup), the problem of coherent instances, especially when various typeclasses are combined. Time permitting, we’ll also discuss issues with law-based property checks (precision, range, time and memory complexity).
+

14:20 | Formal verification of Scala programs with Stainless Everyone knows that writing bug-free code is fundamentally difficult, and that bugs will sometimes sneak in even in the presence of unit- or property-based tests. One solution to this problem is formal software verification. Formal verification allows users to statically verify that software systems will never crash nor diverge, and will in addition satisfy given functional correctness properties. In this talk, I will present Stainless, a verification system for an expressive subset of Scala. I will start by explaining what formal verification is, what are some of the challenges people encounter when putting it into practice, and how it can be made more practical. Then I will give a high-level overview of Stainless, and finally present a few verified programs, such as a small actor system, a parallel map-reduce implementation, as well as a little surprise! I’ll also demonstrate the tooling we have developed around Stainless which lets users easily integrate Stainless in their SBT-based Scala projects. | + 14:55 | Break | + 15:10 | Exploring Scala Tooling ecosystem We are going to explore and compare some build tools with special focus on LSP/BSP implementations, IDEs and text editor support. To help the audience’s judgement about the tools that are suitable for their particular needs this talk aims to get attendees familiar with terms like SemanticDB, Metals, Bloop, SBT, Pants, Bazel, Ensime, IntelliJ IDE, Scala IDE, Dotty IDE and other honorific mentions. | + 15:45 | TwoFace values: a bridge between terms and types Scala 2.13 introduces literal types, and with great types comes great thirst for power to control them. In this talk we get acquainted with the singleton-ops library, a typelevel programming library that enables constraining and performing operations on literal types. We learn about the library’s TwoFace value feature, and how it can be used to bridge the gap between types and terms by converting a type expression to term expression and vice-versa. | + 16:20 | Break | + 16:40 | GADTs in Dotty GADTs (Generalized Algebraic Data Types) are a special case of ADTs (or Dotty enums) that, when we match on them, let us know more about type parameters to enclosing functions. In practice, they are mostly used to associate types with data constructors (case classes and objects in Scala’s case), and to ensure that incorrectly assembling data structures will not typecheck. Two good examples are a database query type that cannot be malformed (no integers as if conditions!) or a red-black tree data type that will only compile if it is balanced. So far Scala’s support for GADTs has been lacking and rife with runtime type errors compared to Haskell. Fortunately, I’ve been working on making it far better in Dotty! During the talk first we’ll walk through examples of GADTs, see what makes them useful and how they can be applied to solve real problems. Next, I’ll explain how GADTs in Scala naturally follow from subtyping and inheritance, completely unlike Haskell or any other language with GADTs. Finally, I’ll talk about how the support for GADTs in Dotty is tightly related to other features such as match types and (the possible) nullable types. | + 17:15 | Want to Diversify the Scala Community? Here is How You Can Help! The Scala community has grown significantly over the past 15 years. As a community, we wrote millions of lines of code and developed hundreds of projects. While the language is thriving, there is still room to contribute to the community. Different from other tech talks, this talk focuses on contributing to the diversity aspect of the community. It explains the significance and benefits of diversity, and it proposes solutions to diversify and improve the community. One of the best ways to grow the community and to bring diversity into the community is to organize ScalaBridge workshops, which are intended to provide resources for people from underrepresented populations to learn Scala. (Diversity comes in many forms: race, gender, age, religion, culture, sexual orientation, socioeconomic background, etc.) While the workshops have positive and lasting impacts, it cannot be done by one individual or by a single organization. In order for the Scala community to become more diverse, we need your help to scale up! Attend this talk to learn about how to contribute to our community! | + 17:50 | Closing Remarks |

+ +

Venue

+

The Summit will take place at EPFL in Ecublens, building CO, lecture hall CO2. + You can find a detailed plan at plan.epfl.ch. + Please note that this is a different venue than Scala Days!

+ +

Sponsors

+ +

Gold

+

Triplequote

+
+
+ +
+ + + + + + diff --git a/blog/summit-nescala-2023-10-26.html b/blog/summit-nescala-2023-10-26.html new file mode 100644 index 00000000..bb09b61d --- /dev/null +++ b/blog/summit-nescala-2023-10-26.html @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit NEScala + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit NEScala

+ + + summits + + events + +
+
+
+
+

Typelevel Summit NEScala

+

Typelevel Summit NEScala

+ +

About the Summit

+

The tenth Typelevel Summit will once again be co-hosted with the Northeast Scala Symposium virtually, with one of day of talks and one day of unconference, accompanied by discussion and socializing online throughout.

+

The schedule for this year is as follows:

+
    +
  • October 26 (Thursday): Typelevel Summit
  • +
  • October 27 (Friday): NE Scala 2023
  • +
  • October 28 (Saturday): Unconference
  • +
+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+

This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. + All attendees, speakers, and organizers must abide by the Typelevel Code of Conduct.

+ +

Speakers and Schedule

+

For information on the programme and recordings, please visit the NE Scala website.

+
+
+ +
+ + + + + + diff --git a/blog/summit-nyc-2017-03-23.html b/blog/summit-nyc-2017-03-23.html new file mode 100644 index 00000000..1f93a5f3 --- /dev/null +++ b/blog/summit-nyc-2017-03-23.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit NYC + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit NYC

+ + + summits + + events + +
+
+
+
+

Typelevel Summit NYC

+

Typelevel Summit NYC

+ +

About the Summit

+

The third Typelevel Summit will once again be co-located with the Northeast Scala Symposium in New York City, with one day of recorded talks and one day of (shared) unconference. + The Summit will happen on March 23, NE Scala on March 24, and finally, the unconference on March 25.

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+

This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the Typelevel Code of Conduct.

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:15Registration & Breakfast
9:00Opening Remarks
9:15Keynote: LiquidHaskell: Liquid Types for Haskell Code deficiencies and bugs constitute an unavoidable part of software systems. In safety-critical systems, like aircrafts or medical equipment, even a single bug can lead to catastrophic impacts such as injuries or death. Formal verification can be used to statically track code deficiencies by proving or disproving correctness properties of a system. However, at its current state formal verification is a cumbersome process that is rarely used by mainstream developers. This talk presents LiquidHaskell, a usable formal verifier for Haskell programs. LiquidHaskell naturally integrates the specification of correctness properties in the development process. Moreover, verification is automatic, requiring no explicit proofs or complicated annotations. At the same time, the specification language is expressive and modular, allowing the user to specify correctness properties ranging from totality and termination to memory safety and safe resource (e.g., file) manipulation. Finally, LiquidHaskell has been used to verify more than 10,000 lines of real-world Haskell programs. LiquidHaskell serves as a prototype verifier in a future where formal techniques will be used to facilitate, instead of hinder, software development. For instance, by automatically providing instant feedback, a verifier will allow a web security developer to immediately identify potential code vulnerabilities.
10:15Break
10:30Introduction to Recursion Schemes Recursion is one of the most fundamental tools in the functional programmer’s kit. As with most fundamental tools, it’s quite powerful, and likely, too powerful for most applications. Abstracting away the explicit recursion from algorithms can make them easier to reason about, understand and maintain. Separating description of the program from interpretation, is a pattern we often see in functional programming. This talk is about applying that idea to recursive algorithms. This talk will attempt to be as self-contained as possible and will hopefully make {cata
11:10A Tale of Two Tails: The Second Act TwoTails is a compiler plugin written to add support to Scala for mutual tail recursion. While Trampolines or trampolined style recursion solve the direct need, they require explicit construction by a developer and add overhead in the form of additional data structures. Unfortunately, building a “native” solution directly into Scalac without using trampolines is not a straight forward task, even with basic tail recursion. In the latest version, a second compilation scheme has been introduced solving an issue peculiar to the JVM which the first scheme was not able to properly address. I’ll discuss both the motivation behind this new scheme and the trade-offs entailed by using it, highlighting which is more appropriate given your circumstances.
11:30Break
11:45Scalable data pipelines with shapeless and cats The data pipeline is the backbone of most modern platforms. Not only is it important to make sure your pipeline is fast and reliable but, a team also needs to be able to deploy new endpoints quickly. This talk uses inductive implicits and typeclasses to make onboarding painless. With only a limited knowledge of shapeless and cats, a developer can create scalable and maintainable data pipeline architectures that are assembled at compile time. With inductive types, pipelines can be combined to create compound pipelines simply and easily. And cats provides ready-made typeclasses which can help cut down on development time.
12:25Frameless: A More Well-Typed Interface for Spark With Spark 2.0, Spark users were introduced to the Dataset API, which sought to combine the static guarantees of types (much like in RDDs) with enhancements from Spark SQL’s Catalyst optimizer, which were previously only available to more a weakly typed DataFrame API. In this introductory level talk, we’ll take a brief look at some of the rough edges encountered when working with Datasets and how Frameless, a Typelevel library attempting to add a more well-typed veneer over Spark, can help.
12:45Lunch Break
14:00Easy and Efficient Data Validation with Cats Often when we create a client/server application, we need to validate the requests: can the user associated to the request perform this operation? Can they access or modify the data? Is the input well-formed? When the data validation component in our application is not well designed, the code can quickly become not expressive enough and probably difficult to maintain. Business rules don’t help, adding more and more requirements to add in our validation, making it more and more complex to clearly represent and maintain. At the same time when the validation fails, it should be fairly straight forward to understand why the request was rejected, so that actions can be taken accordingly. This talk introduces Cats, a Scala library based on category theory, and some of its most interesting components for data validation. In particular, we’ll discuss some options to achieve efficient and expressive data validation. We will also argue that, compared to other options in the language, Cats is particularly suited for the task thanks to its easy-to-use data types and more approachable syntax. Throughout the talk, you will see numerous examples on how data validation can be achieved in a clean and robust way, and how we can easily integrate it in our code, without any specific knowledge of category theory.
14:40Finding the Free Way Free Monads are quickly being adopted as the best technique for developing in a pure functional style. Unfortunately, the details for how to best apply them is often left as “an exercise for the reader.” Recently my team began using Free Monads to build Web Services within the Play Framework. We wanted to use Free Monads in an easy to follow way with minimum boilerplate, while still slotting naturally into the Play Framework. In this talk I’ll outline how we took some wrong turns, hit a few potholes, but ultimately found a way to use Free that works for us.
15:00Break
15:15A Type Inferencer for ML in 200 Lines of Scala Scala is both acclaimed and criticized for its type inference capabilities. But most of this criticism stems from Scala’s object-functional nature, so how does type inference look like and work in functional languages without objects, such as Standard ML or Haskell? This talk aims to show one way to achieving that. We will present Wand’s type inference algorithm, a lesser known, but easier to understand and extend alternative to the classic Damas-Hindley-Milner algorithm. We’ll use a small subset of Standard ML as a vehicle language and Scala as the implementation language.
15:55Extensible Effects: A Leaner Cake for Purely Functional Code Purely functional algorithms and data structures are one thing, but purely functional program architectures are a completely different beast. Constructors and dependency injection frameworks compete in the object oriented landscape; in Scala, we have the Cake Pattern as well. Regardless, we aren’t doing purely functional programming just to pass around mutable objects with state, and the Cake Pattern has a similar problem with hiding effects from the user. Extensible effects provide not only a uniform interface to monadic effects, but a dependency injection mechanism that is aware of them. Finally tagless encodings provide an object-oriented view of the problem, which compared to the initial ADT encoding can be not only easier to understand for newcomers but more efficient.
16:30Break
16:45Let the Scala compiler work for you Programming in some languages can feel like you’re working for the compiler - the type checker is naggy, the type system limiting, and much of your code is extraneous. This is backwards. The compiler should be working for you, helping you check your code, allowing you to express the abstractions you want, and enabling you to write clean, beautiful code. In Scala we are lucky to have such a compiler. In this talk we will explore a variety of techniques, libraries, and compiler plugins for Scala that demonstrate the utility of having a compiler that works for you.
17:25Adopting Scala: The Next Steps Six months into learning Scala, I summarised my experience and delivered a talk to help others going through the same process. This covered effective learning methods, an initial list of topics, and some tips so that others could be effective quickly whilst avoiding some common mistakes. Over a year later, I will reflect on those methods and their result, talk about how I extended my knowledge of functional programming, and explore how to introduce key concepts without feeling overwhelmed. My aim is to present the insights and challenges encountered when learning functional programming to make the experience as approachable as possible.
17:45Closing Remarks
18:00After party at the venue hosted by Tapad
+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Platinum

+

Weight Watchers + Cake Solutions + Lightbend + Tapad

+ +

Gold

+

Rally Health + Giphy + Driver + Comcast + Data Monsters

+ +

Silver

+

Underscore + iHeartRadio

+ +

After Party Sponsor

+

Meetup

+
+
+ +
+ + + + + + diff --git a/blog/summit-nyc-2020-03-12.html b/blog/summit-nyc-2020-03-12.html new file mode 100644 index 00000000..b67a1108 --- /dev/null +++ b/blog/summit-nyc-2020-03-12.html @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit New York City + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit New York City

+ + + summits + + events + +
+
+
+
+

Typelevel Summit New York City

+

Typelevel Summit New York City

+ +

About the Summit

+

The ninth Typelevel Summit will once again be co-located with the Northeast Scala Symposium in New York City, with one day of recorded talks and one day of unconference. + The schedule for this year is as follows:

+
    +
  • March 12 (Thursday): Typelevel Summit
  • +
  • March 13 (Friday): NE Scala 2020
  • +
  • March 14 (Saturday): Unconference
  • +
+ +

Important Update

+

Due to the current situation regarding COVID-19, we are changing the Typelevel Summit and NE Scala to be online-only. Many attendees have been asked by their employers not to travel or attend conferences. We apologize to anyone who's inconvenienced by this, but we hope you'll still attend remotely. + For more information please visit the NE Scala website.

+ +

Speakers and Schedule

+

For information on the programme and recordings, please visit the NE Scala website.

+
+
+ +
+ + + + + + diff --git a/blog/summit-oslo-2016-05-04.html b/blog/summit-oslo-2016-05-04.html new file mode 100644 index 00000000..cff4686b --- /dev/null +++ b/blog/summit-oslo-2016-05-04.html @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Oslo + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Oslo

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Oslo

+
+

"Oslo opera house" by Tobias Van Der Elst is licensed under CC BY-SA 2.0.

+ +

About the Summit

+

The second Typelevel Summit was co-located with flatMap(Oslo).

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here!

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:30Registration
9:00Opening Remarks
9:15Keynote: How to bake "How to Bake Pi" Mathematics is a very misunderstood subject. Many people associate it only with painful experiences with childhood, or think it's all about numbers and calculations, or that it's a cold subject with clear rights and wrongs. My mission is to bring my love of mathematics to more people, and as part of this journey I need to show the beauty and the power of abstract thinking. In this talk I will present my experiences of this, starting with the book I wrote for a very general audience, and the Category Theory course I teach to art students at the School of the Art Institute of Chicago. Using a variety of surprising examples, I will show that it is possible to convince maths phobics and maths sceptics that abstract mathematics can be relevant and useful for everyone.
10:15Break
10:30A Year living Freely The Free monad and the Interpreter Pattern has gained significant interest in the Scala community of late. It is a pattern that has helped unlock the problems of separating pure functions from effects. At REA Group we have had an explosion of interest in FP and Scala in the last two years. Beginning with just a couple of experienced functional programmers to now multiple teams and dozens of developers, we have experienced the growing pains of introducing FP and Scala to a large organisation. The Free monad has been a key element in our journey. As we grew, we were particularly conscious of what patterns we could lay down, especially for beginners, that promoted the integral values of FP such as referential transparency and to allow obvious ways that software should grow. After many experiments and much research, we discovered that the Free monad and interpreter pattern has been something that tangibly isolates effects, maintains referential transparency, subsumes dependency injection, is modular and is surprisingly accessible to FP/Scala new comers. This talk briefly covers the mechanics of the Free monad and the interpreter pattern but largely looks at how a year with the Free monad has allowed us to make novice teams productive while they learn and embrace FP and Scala.
11:10What is macro-compat and why you might be interested in using it Despite macros being an experimental feature of Scala, a number of libraries find them to provide great value and choose to make use of them. However in different Scala versions the macro support and API is different. That means that libraries that cross-build for multiple Scala versions have then had to deal with these differences. Macro-compat is a solution to this problem. In this talk I will introduce macro-compat, starting with an overview of the problems it's trying to solve, the prior art of how these problems are dealt with, how to use it and how it works.
11:25Break
11:45Monitoring and controlling power plants with Monix This talk is about my experience in dealing with modeling behavior by processing asynchronous soft-real time signals from different source using Monix, the library for building asynchronous and event-based logic. It's an experience report from my work at E.On, in monitoring and controlling power plants. We do this by gathering signals in real time and modeling state machines that give us the state in which an asset is in. The component, for lack of inspiration named Asset-Engine, is the one component in the project that definitely adheres to FP principles, the business logic being described with pure functions and data-structures and the communication being handled by actors and by Observable streams. I want to show how I pushed side effects at the edges, in a very pragmatic setup.
12:25Fetch: Simple & Efficient data access Fetch is a Scala library for simplifying and optimizing access to data such as files systems, databases, or web services. These data sources usually have a latency cost, and we often have to trade code clarity for performance when querying them. We can easily end up with code that complects the business logic performed on the data we're fetching with explicit synchronization or optimizations such as caching and batching. Fetch can automatically request data from multiple sources concurrently, batch multiple requests to the same data source, and cache previous requests' results without having to use any explicit concurrency construct. It does so by separating data fetch declaration from interpretation, building a tree with the data dependencies where you can express concurrency with the applicative bind, and sequential dependency with monadic bind. It borrows heavily from the Haxl (Haskell, open sourced) and Stitch (Scala, not open sourced) projects. This talk will cover the problem Fetch solves, an example of how you can benefit from using it, and a high-level look at its implementation.
12:45Lunch Break
14:00Decorate your types with refined Scala has a powerful type system that allows to create very expressive types. But sometimes we need guarantees about our values beyond what the type system can usually check, for example integers in the range from zero to fifty-nine, or chars that are either a letter or a digit. One way to realize these constraints is known as smart constructors, where the construction mechanism validates at runtime that our values satisfy the constraint. Unfortunately this technique requires some boilerplate and always incur runtime checks even if the values are kown at compile-time. This talk will introduce a library for refining types with type-level predicates, which abstracts over smart constructors. We'll go from the idea of refinement types to examples of the library using the rich set of predicates it provides, and show how it can be used at compile- and runtime alike. On that way we'll see how we can make good use of literal-based singleton types that are proposed in SIP-23. I'll also demonstrate how refined integrates with other libraries like circe, Monocle, or scodec.
14:40Discovering Types (from Strings) with Cats and Shapeless This talk is about a simple problem which can be solved using parts of Cats and Shapeless. While helping data scientists to use the nice, well-typed Scala tools that we build for them, we are often presented with tabular data in raw text files (CSV, PSV, etc.). These files usually have some consistent, but unknown, internal schema. Data scientists are often familiar with dynamic languages like R and Python, in which fields can be parsed speculatively, or on-demand by particular operations at runtime. They usually expect Scala tools to do the same, and they particularly dislike having to specify schemas manually up-front. This mis-match can be addressed by a spectrum of different approaches, which range from handling types outside the language proper (boo! - but it works quite well in practice), to discovering and pre-generating a schema that can be used for compile-time checking. The problem of discovering the schemas of these files in a composable way makes for an interesting tour of some features of Shapeless and Cats. It's useful for beginners because the problem is quite easy to understand. I'll discuss some approaches to this, some of the remaining challenges, and provide attendees with enough background to implement the basics of a working system. I'll focus specifically on a solution that involves Cats and Shapeless for schema pre-generation, rather than macro-based approaches of manifesting schemas.
14:55Break
15:15Building functional programs with bananas, catalysts, shacl's and shapes This is a talk that combines both the practical, but often overlooked, topic of SBT with cutting edge distributed data technologies. The practical aspect is presented by giving an overview of catalysts, where it came from (Scalaz and banana-rdf, actually), how it evolved and how it came to be what and where it is today; and why it should be used. The evolution of catalysts then leads naturally to why current build systems play such an import role in language ecosystems and why these ecosystems can't work as they are today. This is where RDF naturally has a place, along with Shapes and Shapes Constraint Language (SHACL).
15:55Growing a DSL for financial calculations Rabobank is a Dutch multinational banking and financial services company headquartered in Utrecht, the Netherlands. One of their services is providing mortgage loans. Determining the height of the loans involves some rather complex calculations. They were struggling to represent these calculations in an understandable and reliably testable way for both domain experts and developers. We helped them develop an internal DSL in Scala that allows them to express these complex calculations in an idiomatic way that is not just easy to read for both developers and business analysts, but more testable as well. Harnessing functional programming principles and the strong Scala compiler, it also provides full typesafety with a syntax that lies very close to human language, allowing fully typesafe constructs such as 'amount per month' and 'amount per year'. In this talk, I will explain the concepts behind the DSL, how we implemented them without adding any dependencies to the project (except ScalaTest, of course), and the design decisions we had to make along the way.
16:25Break
16:45Dotty and types: the story so far Dotty is a new, experimental compiler for Scala. One of the main goal of Dotty is to provide a better type system for Scala that is both theoretically sound and better in practice. In this talk I'll focus on some of the practical improvements to the type system we've made in Dotty, like the new type parameter inference algorithm that, while not formally specified, should be easier to reason about and work in more cases. I will also try to shed some light on the challenges we face, like getting a set of features (like union types, singleton types and type inference) to interact well with each other, or properly implementing higher-kinded types.
+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Platinum

+

Commonwealth Bank of Australia + 47 Degrees

+ +

Gold

+

Underscore + Arktekk

+ +

Silver

+

Lightbend

+

Thanks to the generous private supporters (in alphabetic order): Frank S. Thomas, Eric Torreborre, and the anonymous patrons.

+
+
+ +
+ + + + + + diff --git a/blog/summit-philadelphia-2016-03-02.html b/blog/summit-philadelphia-2016-03-02.html new file mode 100644 index 00000000..d77ad5df --- /dev/null +++ b/blog/summit-philadelphia-2016-03-02.html @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Philadelphia + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Philadelphia

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Philadelphia

+
+

"I wish I was a little bit taller" by Ryan Hallock is licensed under CC BY 2.0.

+ +

About the Summit

+

The first Typelevel Summit was co-located with the Northeast Scala Symposium in Philadelphia, with one day of recorded talks and one day of unconference.

+

You can find photos from the summit here. Thanks to Brian Clapper and + Alexy Khrabrov who also documented the event.

+

The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. + Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. + If you're interested in types and pure functional programming we'd love to see you here! + Check our front page for upcoming events.

+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:30Registration
9:00Opening Remarks
9:10Becoming a cat(s) person Want to contribute to Cats? Let’s head over to the Cats Issues list and do some live coding! Along the way we will see how the codebase is organized, the various bits of automation provided, and how you can use our various channels to get feedback on your work.
9:40Break
9:55End to End and On The Level This talk answers the burning question 'Can I build a complete web service using solely Typelevel libraries?' In Scala we are spoiled for choice for web frameworks, database layers, JSON libraries, and a thousand other essential tools for application development. So much so, it's easy to become a victim of choice paralysis when starting a new project. There's so much choice, many developers favour groups of libraries that work well together. The Typesafe Reactive Platform (colloquially the 'Typesafe Stack'), is widely known as a set of interoperable libraries providing all the functionality required to build entire web applications without looking elsewhere. Enter Typelevel, endorsing a fleet of interoperable free/open source libraries providing all manner of functionality. The phrase 'Typelevel Stack' has been used frequently in the community, raising some intersting questions: Can we build complete web services using Typelevel libraries alone? What would that look like? What will the developer experience be like in terms of tooling, support, and documentation? In this talk, Dave will discuss his adventures building a web framework completely 'on the level', capturing thoughts on design, process, documentation, support, and community along the way.
10:35Probabilistic Programming: What It Is and How It Works Probabilistic programming is the other Big Thing to happen in machine learning alongside deep learning. It is also closely tied to functional programming. In this talk I will explain the goals of probabilistic programming and how we can implement a probabilistic programming language in Scala. Probabilistic models are one of the main approaches in machine learning. Probabilistic programming aims to make expressive probabilistic models cheaper to develop. This is achieved by expressing the model within an embedded DSL, and then compiling learning (inference) algorithms from the model description. This automates one of the main tasks in building a probabilistic model, and provides the same benefits as a compiler for a traditional high-level language. With the close tie of functional programming to mathematics, and the use of techniques like the free monad, functional programming languages are an ideal platform for embedding probabilistic programming.
11:05Break
11:20Introducing Typelevel Scala into an OO environment Its difficult enough trying to introduce a new language into an established environment. This problem is compounded when the new language comes with a paradigm shift. This talk will detail one process which successfully introduced Functional Scala into an Object Oriented Java shop. The talk will explain how to bridge the OO-FP impedance mismatch when communicating ideas across project boundaries. The discussion will focus on migrating from Java style mutability, loops, get/set and coupling into Typelevel style immutability, combinators, case classes and type classes.
12:00Efficient compiler passes using Cats, Monocle, and Shapeless Centered around a new standalone recursion scheme library (Matryoshka), this talk shows how to take advantage of various Typelevel projects to write many conceptually-independent data transformations, but have them efficiently combined into a small number of passes. Matryoshka also uses other Typelevel projects, including kind-projector and simulacrum.
12:30Lunch Break
14:00Keynote: Dependently-Typed Haskell Is Haskell a dependently typed programming language? The Glasgow Haskell Compiler's many type-system features, such as Generalized Algebraic Datatypes (GADTs), datatype promotion, multiparameter type classes, type families, and more recent extensions give programmers the ability to encode domain-specific invariants in their types. Clever Haskell programmers have used these features to enhance the reasoning capabilities of static type checking. But how far have we come? Could we do more?
15:00Break
15:20Evaluation in Cats: the Good, the Bad, and the Lazy A unique part of Cats' design is its Eval type. This type abstracts over evaluation strategies, and is the primary way to encode laziness in Cats APIs. It also includes a trampoline to allow safe, efficient implementations of algorithms that require laziness. Eval serves as a building block for other types, such as the Streaming data type and the Foldable type class. This talk will cover the basic design of Eval. It will walk through several different examples to help explain how the evalutation strategies work, cover some common pitfalls, and show off some interesting uses of laziness. It will also try to highlight some of the shortcomings of laziness in Scala, as well as alternate approaches.
15:40Easy, intuitive, direct-style syntax for Monad-comprehensions! Easy, intuitive, direct-style syntax for monad comprehensions! Like Scala async or SBT .value, but generalized to any monad. Implemented, ready to be used and requiring only vanilla Scala 2.10/2.11 and blackbox macros. Future extensions could include automatic use of Applicative where possible, support for more embedded control-flow operations, comprehensions over multiple compatible monads at once for user-defined notions of compatible and compiler tweaks for syntactic improvements.
16:00Scala Exercises Scala Exercises is a web based community tool open sourced by 47 Degrees. It contains multiple koan and free form style exercises maintained by library authors and maintainers to help you master some of the most important tools in the Scala Ecosystem. Version 2 comes with a brand new backend and exercise tracking where you can login simply using your Github account and track your progress throughout exercises and libraries. Version 2 will launch with exercises for the stdlib, Cats, Shapeless and other well known libraries and frameworks part of the Scala ecosystem.
16:15Break
16:30From Simulacrum to Typeclassic Simulacrum simplifies development of type class libraries. It is used in a number of open source libraries, including Cats. In this talk, we’ll tour the features of Simulacrum, and look at the forthcoming Typeclassic project, which merges Simulacrum with complementary projects like machinist and export-hook.
+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Platinum

+

47 Degrees

+ +

Gold

+

Underscore + Verizon

+ +

Silver

+

Lightbend + MediaMath + Comcast + Box + Scotiabank

+

Thanks to the generous private supporters (in alphabetic order): + Steve Buzzard, Jeff Clites, Ryan Delucchi, Pedro Furlanetto, Rob Norris, Erik Osheim, Michael Pilquist, SlamData, Stewart Stewart, Frank S. Thomas, and the anonymous patrons.

+
+
+ +
+ + + + + + diff --git a/blog/summit-philadelphia-2019-04-01.html b/blog/summit-philadelphia-2019-04-01.html new file mode 100644 index 00000000..7c632c06 --- /dev/null +++ b/blog/summit-philadelphia-2019-04-01.html @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Summit Philadelphia + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Summit Philadelphia

+ + + summits + + events + +
+
+
+
+

Typelevel Summit Philadelphia

+
+

"I wish I was a little bit taller" by Ryan Hallock is licensed under CC BY 2.0.

+ +

About the Summit

+

The seventh Typelevel Summit will once again be co-located with the Northeast Scala Symposium in Philadelphia, with one day of recorded talks and one day of unconference. + The schedule for this year is as follows:

+
    +
  • April 1st: Typelevel Summit
  • +
  • April 2nd: Northeast Scala Symposium
  • +
  • April 3rd: Unconference
  • +
+ +

Speakers and Schedule

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTalk
8:15Registration & Breakfast sponsored by Coatue
8:55Opening Remarks
9:00Keynote: Shared Session Types for Safe, Practical Concurrency Message-passing concurrency abstracts over the details of how programs are compiled to machine instructions and has been adopted by various practical languages, such as Erlang, Go, and Rust. For example, Mozilla's Servo, a next-generation browser engine being written in Rust, exploits message-passing concurrency to parallelize loading and rendering of webpage elements, done sequentially in existing web browsers. Messages are exchanged along channels, which are typed with enumeration types. Whereas typing ensures in this setting that only messages of the appropriate type are communicated along channels, it fails to guarantee adherence to the intended protocol of message exchange. In this talk I show how session types can be used to type communication channels to check protocol adherence at compile-time. Session types were conceived in the context of process calculi, but made their ways into various practical languages using libraries. A key restriction of prior session type work is linearity. Whereas linear session types enjoy strong properties such as race freedom, protocol adherence, and deadlock-freedom, their insistance on a single client rules out common programing scenarios, such as multi-producer-consumer queues or shared databases or output devices. I report on my work on shared session types, which accommodates those programing scenarios, while upholding the guarantees of linear session types. First, I introduce manifest sharing, a discipline in which linear and shared sessions coexist, but the type system ensures that clients of shared sessions run in mutual exclusion from each other. Manifest sharing guarantees race freedom and protocol adherence, but permits deadlocks. Next, I introduce manifest deadlock freedom, which makes shared and linear sessions deadlock-free by construction. Finally, I give an overview of my current and future research plans.
9:55Break
10:10Systematic Software with Scala Scala is a very flexible language, and this flexibility can make it difficult to know how to effectively design Scala code. In the nearly ten years I've been using Scala, my approach to using the language has coalesced around a ten or so strategies, which are similar to OO design patterns but broader in scope and borrow many ideas from functional programming. Using these strategies I can create code in a systematic and repeatable way. In this talk I will present the majority of my strategies, and illustrate their use by live coding a simple graphics system where the majority of the code is systematically derived by applying strategies. The strategies allow me to work at a higher-level of abstraction, and the coding itself becomes formulaic. This means I can get more work done and my code is simpler to read and use. I hope that my strategies will also enable you to design better code in Scala.
10:45Journey to an FP Test Harness The hardest part of the pure-FP journey for many people is taking that first real step. Even after you’ve read all the books and done all the exercises, you need to start committing real code to truly grok the FP mindset. This little case study will trace my journey over that line, in building a new test harness to an existing Play application. In the course of it, we’ll explore how my assumptions evolved: from stateful members to consistent use of StateT; from Play’s native Future-centricity to IO; becoming a little more nuanced about test state using IndexedStateT; moving away from an ever-growing cake to focus on imports instead; and the payoff, being able to refactor the test code to be modular, readable and robust. The goal here is to show that, while there are a bunch of parts, none of this is rocket science. In the end, the resulting code is delightfully elegant, and the general approach should work for many Play applications.
11:20Break
11:35The Monoiad: an epic poem on monoids Monoids provide a vast landscape of concepts that we rely on in FP. Applicatives, monads, categories – all of them are monoids, as is much else. The epic takes us on a journey with this fundamental structure. We’ll move between everyday Scala, some niche areas of the language, and category theory.
12:10Lunch sponsored by Simple
13:45Keynote: Higher Inductive Types in Homotopy Type Theory Homotopy type theory is a new field of mathematics based on the recently-discovered correspondence between constructive type theory and abstract homotopy theory. Higher inductive types, which form a crucial part of this new system, generalize ordinary inductive types such as the natural numbers to higher dimensions. We will look at a few different examples of higher inductive types such as the integers, circles, and the torus, and indicate how we can use their associated induction principles to reason about them, e.g., to prove that the torus is equivalent to the product of two circles.
14:40Break
14:55Telling the Truth with Types There are many problems one faces when building effective solutions. (1) Outlining proper behavior, such that desired outcomes are achieved. (2) Simplifying the problem space, such that solutions are extensible and maintainable. (3) Interfacing with existing code. Together we will walk through typical problems, and apply a set of processes to more effectively meet these criteria. We will identify what information we need to make available and how we can consume that information to build out systems which behave as we expect. We will use the type system as our guide, to lift our reasoning directly into our codebases. Whether you are just starting out, or an experienced functional programmer this talk will deliver a set of tools to approach the next set of challenges.
15:30Composable concurrency with Ref + Deferred fs2 offers a very powerful and composable set of concurrent combinators and data structures, which are all built out of two deceptively simple primitives: Ref and Deferred. This talk will explain what they are, the design principles behind them, and how to use them to build your own business logic abstractions. In the process, we will discover a general pattern in the form of concurrent state machines, and see how it integrates with final tagless on one hand, and streaming control flow on the other. If you have ever wondered how to translate that complicated piece of actor logic in pure FP, or how fs2’s Queues, Topics and Signals work under the hood, this is the talk for you.
16:05Break
16:20Extending your HTTP library with monad transformers A tour of monad transformers and how stacking various effects onto IO can extend our HTTP library in new and interesting ways. We’ll review OptionT from last year’s talk, derive something akka-http like with EitherT, and demonstrating tracing with TraceT.
16:55Portable, type-fancy multidimensional arrays Zarr is a multidimensional-array container format that's gaining momentum in several scientific domains. It hails from the Python world, and primarily caters to numpy- and xarray-wielding scientists. It shines as a more remote- and parallel-processing-friendly HDF5 replacement. I implemented the Zarr spec in portable Scala, leveraging dependent- and higher-kinded-types. The resulting arrays have a unique type-safety profile. In this talk I'll: contextualize Zarr's use in the single-cell-sequencing domain, examine the freewheeling DSLs that scientific-Python exposes for array processing (including remote and distributed), discuss possibilities for Scala (and types!) to make inroads in these ecosystems, and show what worked well and poorly about my attempt.
17:30Closing
+ +

Venue

+

The Science History Institute collects and shares the stories of innovators and of discoveries that shape our lives, preserving and interpreting the history of chemistry, chemical engineering, and the life sciences. + Headquartered in Philadelphia, with offices in California and Europe, the Institute houses an archive and a library for historians and researchers, a fellowship program for visiting scholars from around the globe, a community of researchers who examine historical and contemporary issues, an acclaimed museum that is free and open to the public, and a state-of-the-art conference center.

+ +

Code of Conduct

+

The Code of Conduct & reporting of incidents are handled together with the team of the Northeast Scala Symposium. + You can find details on their website. + In short: there is a Slack team where you can report incidents to the organizers, to which every ticket holder should have received an invitation. + It is possible to file anonymous reports. + The list of organizers can be found here.

+ +

Sponsors

+

We'd like to thank all our sponsors who help to make the Summit happen:

+ +

Platinum

+

Bridgewater

+ +

Gold

+

Comcast + Azavea + Chariot Solutions + Simple + Coatue

+
+
+ +
+ + + + + + diff --git a/blog/summit-programme.html b/blog/summit-programme.html new file mode 100644 index 00000000..2742ebbc --- /dev/null +++ b/blog/summit-programme.html @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + First batch of talks at the Philadelphia Summit + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

First batch of talks at the Philadelphia Summit

+ + + summits + +
+
+
+
+

First batch of talks at the Philadelphia Summit

+

The work on the programme for the Philadelphia Summit is in full swing! + As announced earlier, we're happy to share with you the first batch of accepted talks. + Don't worry though, there's still time until the end of the week to submit a proposal.

+ +

Becoming a cat(s) person

+

Want to contribute to Cats? + Let's head over to the Cats Issues list and do some live coding! + Along the way we will see how the codebase is organized, the various bits of automation provided, and how you can use our various channels to get feedback on your work.

+

+ Adelbert Chang is a Software Engineer at Box and a recent graduate from UC Santa Barbara where he studied Computer Science and researched graph querying and modeling. He enjoys helping with functional programming education and learning more about programming.

+ +

Direct syntax for monad comprehensions

+

Easy, intuitive, direct-style syntax for monad comprehensions! + Like Scala async or SBT .value, but generalized to any monad. + Implemented, ready to be used and requiring only vanilla Scala 2.10/2.11 and blackbox macros. + Future extensions could include automatic use of Applicative where possible, support for more embedded control-flow operations, comprehensions over multiple compatible monads at once for user-defined notions of compatible and compiler tweaks for syntactic improvements.

+

+ Chris Vogt. Slick co-author, Compossible records author, frequent Scala conference/user group speaker, former member of Martin's team at LAMP/EPFL, based in NYC, Senior Software Engineer at x.ai

+

+ Chris Hodapp. Several-time Scala GSOC student and eventually mentor, author of the ill-fated Comprehensive Comprehensions project. He's hoping to see tooling and techniques from the FP/Typelevel community improve the leverage of the average developer. Based in the SF Bay Area.

+ +

Scala Exercises

+

Scala Exercises is a web based community tool open sourced by 47 Degrees. + It contains multiple koan and free form style exercises maintained by library authors and maintainers to help you master some of the most important tools in the Scala Ecosystem. + Version 2 comes with a brand new backend and exercise tracking where you can login simply using your Github account and track your progress throughout exercises and libraries. + Version 2 will launch with exercises for the stdlib, Cats, Shapeless and other well known libraries and frameworks part of the Scala ecosystem.

+

+ Raul Raja. Functional programming enthusiast, CTO and Co-founder at 47 Degrees, a functional programming consultancy specialized in Scala.

+ +

Probabilistic Programming: What It Is and How It Works

+

Probabilistic programming is the other Big Thing to happen in machine learning alongside deep learning. + It is also closely tied to functional programming. In this talk I will explain the goals of probabilistic programming and how we can implement a probabilistic programming language in Scala. + Probabilistic models are one of the main approaches in machine learning. + Probabilistic programming aims to make expressive probabilistic models cheaper to develop. + This is achieved by expressing the model within an embedded DSL, and then compiling learning (inference) algorithms from the model description. + This automates one of the main tasks in building a probabilistic model, and provides the same benefits as a compiler for a traditional high-level language. + With the close tie of functional programming to mathematics, and the use of techniques like the free monad, functional programming languages are an ideal platform for embedding probabilistic programming.

+

+ Noel Welsh is a partner at Underscore, a consultancy that specializes in Scala. He's been using Scala for 6 years in all sorts of applications. He's the author of Advanced Scala, which is in the process of being rewritten to use Cats.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lars Hupel + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/summits/index.html b/blog/summits/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/blog/summits/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/support-typelevel-thanks-to-triplequote-hydra.html b/blog/support-typelevel-thanks-to-triplequote-hydra.html new file mode 100644 index 00000000..810d56dd --- /dev/null +++ b/blog/support-typelevel-thanks-to-triplequote-hydra.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + Support Typelevel thanks to Triplequote Hydra and compile Scala faster! + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Support Typelevel thanks to Triplequote Hydra and compile Scala faster!

+ + + governance + +
+
+
+
+

Support Typelevel thanks to Triplequote Hydra and compile Scala faster!

+

Hello Community!

+

As you all know, back in April we announced the Typelevel Sustainability Program and we have been delighted by the overwhelming support we received both from companies and individuals: thank you all!

+

In just a little bit more than one month we reached 10% of the fundraising goal we set to $150,000. While this is an excellent start, to successfully support the long term sustainability of our ecosystem, we need you to keep advocating for us.

+

One difficulty we are aware of is that it can be hard to convince a company to donate money even if it benefits extensively from our ecosystem. This, despite developers knowing how important it is to sustain the maintenance and development of the open source libraries they love and use every day.

+ +

Announcing: Give 25%, Get 25%, Compile Scala 75% faster!

+

To help us get over this hurdle, we are stoked to announce that Triplequote, the creator of Triplequote Hydra - the only multicore Scala compiler with built-in compile-time monitoring - has kindly offered to donate 25% of each yearly Hydra license purchased online until June 30th using the discount code TYPELEVEL25. But that’s not it, by using the discount code you also benefit from a 25% discount on their list pricing!

+

Using Hydra has proven to be incredibly valuable for Scala projects that rely on our ecosystem, and it delivered impressive compilation speedups. Nowadays, even Cats is compiled with Hydra. Therefore, if you are looking for a way to help us reach our fundraising goal, while also profiting from a great product, don’t let this opportunity slip away. Head over to the Hydra trial page and get started with it in no time using your preferred development tool, whether that is sbt, Maven, Gradle, or IntelliJ IDEA! Just don’t forget to use the discount code TYPELEVEL25

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/symbolic-operators.html b/blog/symbolic-operators.html new file mode 100644 index 00000000..843c027c --- /dev/null +++ b/blog/symbolic-operators.html @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Symbolic operators and type classes for Cats + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Symbolic operators and type classes for Cats

+ + + technical + +
+
+
+
+

Symbolic operators and type classes for Cats

+

This post is an introduction into how operators are implemented in Cats and has been originally published in August 2015. + Some more details can be found in the previous post.

+

One of the simplest and most recognizable type classes is the semigroup. + This type class abstracts over the ability to combine values of a certain + type in an associative manner.

+

What does associativity mean? + We call an operation \oplus associative, if for all a a , b b and c c , a(bc)=(ab)c a \oplus (b \oplus c) = (a \oplus b) \oplus c holds. + Read more about this in the README of the algebra repository.

+

Cats provides cats.Semigroup[A] to model semigroups. + The combine method takes two values of the type A and returns an A value.

+

In addition, Cats defines syntax allowing the binary operator |+| to be + used in place of the combine method.

+ +

Small example

+

Here is a small method that provides a generic way to combine the elements + of a list:

+
import cats.Semigroup
+import cats.implicits._
+
+def gsum[A: Semigroup](values: List[A]): Option[A] =
+  if (values.isEmpty) None else Some(values.reduceLeft((x, y) => x |+| y))
+

(A similar method is built into Cats as Semigroup.combineAllOption.)

+ +

How does it work?

+

One of the parts of gsum that might be hard to understand is where + the |+| method comes from. Since x and y are totally generic values + (of type A) how can we call a method on them?

+

To boil the example down further, consider this simpler example:

+
import cats.implicits._
+19 |+| 20 // produces 39
+

How does this work? We know that the Int type does not have a |+| method. + Experienced Scala developers will suspect that implicits play a role here, + but what are the details?

+ +

In detail

+

Let's walk through how the expression 19 |+| 20 is compiled.

+

First, a |+| method is needed on Int. Since + Int + does not provide one, the compiler searches for an implicit conversion to a + type that does have a |+| method.

+

Due to our import, it will find the + semigroupSyntax[A] + method, which returns a type that has a |+| method (specifically + SemigroupOps[A]). + However, semigroupSyntax requires an implicit Semigroup[A] value to be in scope. + Do we have a Semigroup[Int] in scope?

+

Yes we do. Our import also provides an implicit value intGroup + of type AdditiveCommutativeGroup[Int]. Leaving aside what additive, commutative, + and group mean here, this is a subtype of Semigroup[Int], so it matches.

+

Let's continue with our current example. At this point we have gone from:

+
19 |+| 20 // produces 39
+

to:

+
semigroupSyntax[Int](19)(intGroup) |+| 20
+

But we aren't out of the woods yet! We still need to see how this expression + is evaluated.

+ +

Of macros and machinists

+

Looking at how the |+| method is + implemented + reveals the cryptic macro Ops.binop[A, A]. What is this?

+

Following the rabbit hole farther, we come to + cats.macros.Ops + which provides the macro implementation that |+| is using. Aside from a + suggestively named + item in the operatorNames map, we don't have any clues what is going on.

+

The machinist project was created + to optimize exactly this kind of implicit syntax problem. What will happen here + is that operatorNames describes how to rewrite expressions using type + classes. Long-story short, we will transform:

+
semigroupSyntax[Int](19)(intGroup) |+| 20
+

into:

+
intGroup.combine(19, 20)
+

The aforementioned suggestive map item tells us that we should rewrite the |+| operator + to method calls on the given type class (i.e. intGroup) using .combine.

+ +

Finishing up

+

Just to confirm that we're done, let's look at what intGroup.combine will do. + We started with a call to AdditiveCommutativeGroup[Int], which will find + intAlgebra. + Then we call the .additive + method on it to produce a CommutativeGroup[Int].

+

So putting that together, we can see that calling intGroup.combine(19, 20) + will call intAlgebra.plus(19, 20), and that this is defined as 19 + 20, + as we would expect.

+

Whew!

+ +

Conclusion

+

This is a lot of machinery. The incredibly terse and expressive syntax it + enables is quite nice, but you can see that even leaving out one import + will cause the whole edifice to come tumbling down.

+

The easiest way to use Cats is to just import cats.implicits._. That + way, you can be sure that you have all of it. There are individual imports + from cats.syntax and cats.std which can be used to pinpoint the exact + values and method you want to put into scope, but getting these right + can be a bit tricky, especially for newcomers.

+

Some more examples of Machinist can be found in the README.

+ +

Errata

+

You may also decide that the syntax convenience is not worth it. To write our + original example without syntax implicits (but still using implicit values) + you could say:

+
import cats.Semigroup
+import cats.implicits._
+
+def gsum[A](values: List[A])(implicit s: Semigroup[A]): Option[A] =
+  if (values.isEmpty) None else Some(values.reduceLeft((x, y) => s.combine(x, y)))
+
+// values.reduceLeft(s.combine) would also work
+

Whether to use syntax implicits or explicit method calls is mostly a matter + of preference. Personally, I like using syntax explicits to help make generic + code read in a clearer manner, but as always, your mileage may vary.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Erik Osheim + + +
+ Erik Osheim is one of the founders of Typelevel, and maintains several Scala libraries including Cats, Spire, and others. He hacks Scala for a living at Stripe, and is committed to having his cake and eating it too when it comes to functional programming. Besides programming he spends time playing music, drinking tea, and cycling around Providence, Rhode Island. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/tagless-final-streaming.html b/blog/tagless-final-streaming.html new file mode 100644 index 00000000..77dc1556 --- /dev/null +++ b/blog/tagless-final-streaming.html @@ -0,0 +1,392 @@ + + + + + + + + + + + + + + + + + + + + + + + Tagless Final Algebras and Streaming + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Tagless Final Algebras and Streaming

+ + + technical + +
+
+
+
+

Tagless Final Algebras and Streaming

+

There have been a couple of really nice blog posts about Tagless Final and some related topics. However, I have faced some design problems when writing some algebras and haven't seen anybody talking about. So please let me introduce this problem to you.

+ +

Algebra definition

+

Given the following data definition:

+
case class ItemName(value: String) extends AnyVal
+case class Item(name: ItemName, price: BigDecimal)
+

Consider the following algebra:

+
trait ItemRepository[F[_]] {
+  def findAll: F[List[Item]]
+  def find(name: ItemName): F[Option[Item]]
+  def save(item: Item): F[Unit]
+  def remove(name: ItemName): F[Unit]
+}
+

Let's go through each method's definition:

+
    +
  • findAll needs to return many Items, obtainable inside a context: F[List[Item]].
  • +
  • find might or might not return an Item inside a context: F[Option[Item]].
  • +
  • save and remove will perform some actions without returning any actual value: F[Unit].
  • +
+

Everything is clear and you might have seen this kind of pattern before, so let's create an interpreter for it:

+
import doobie.implicits._
+import doobie.util.transactor.Transactor
+import cats.effect.Sync
+
+// Doobie implementation (not fully implemented, what matters here are the types).
+class PostgreSQLItemRepository[F[_]](xa: Transactor[F])
+                                    (implicit F: Sync[F]) extends ItemRepository[F] {
+
+  override def findAll: F[List[Item]] = sql"select name, price from items"
+                                           .query[Item]
+                                           .to[List]
+                                           .transact(xa)
+
+  override def find(name: ItemName): F[Option[Item]] = F.pure(None)
+  override def save(item: Item): F[Unit] = F.unit
+  override def remove(name: ItemName): F[Unit] = F.unit
+}
+

Here we are using Doobie, defined as A principled JDBC layer for Scala and one of the most popular DB libraries in the Typelevel ecosystem. And it comes with one super powerful feature: it supports Streaming results, since it's built on top of fs2.

+

Now it could be very common to have a huge amount of Items in our DB that a List will not fit into memory and / or it will be a very expensive operation. So we might want to stream the results of findAll instead of have them all in memory on a List, making Doobie a great candidate for the job. But wait... We have a problem now. Our ItemRepository algebra has fixed the definition of findAll as F[List[Item]] so we won't be able to create an interpreter that returns a streaming result instead.

+ +

Rethinking our algebra

+

We should think about abstracting over that List and two of the most common abstractions that immediately come to mind are Foldable and Traverse. But although these typeclasses are very useful, they are not enough to represent a stream of values, so we should come up with a better abstraction.

+

Well, it seems that our options are either adding another higher-kinded parameter G[_] to our algebra or just define an abstract member G[_]. So let's go with the first one:

+
trait ItemRepository[F[_], G[_]] {
+  def findAll: G[Item]
+  def find(name: ItemName): F[Option[Item]]
+  def save(item: Item): F[Unit]
+  def remove(name: ItemName): F[Unit]
+}
+

Great! This looks good so far.

+ +

Streaming support interpreter

+

Now let's write a new PostgreSQL interpreter with streaming support:

+
import doobie.implicits._
+import doobie.util.transactor.Transactor
+import fs2.Stream
+
+// Doobie implementation (not fully implemented, what matters here are the types).
+class StreamingItemRepository[F[_]](xa: Transactor[F])
+                                   (implicit F: Sync[F]) extends ItemRepository[F, Stream[F, ?]] {
+
+  override def findAll: Stream[F, Item] = sql"select name, price from items"
+                                           .query[Item]
+                                           .stream
+                                           .transact(xa)
+
+  override def find(name: ItemName): F[Option[Item]] = F.pure(None)
+  override def save(item: Item): F[Unit] = F.delay(println(s"Saving item: $item"))
+  override def remove(name: ItemName): F[Unit] = F.delay(println(s"Removing item: $item"))
+}
+

Voilà! We got our streaming implementation of findAll.

+ +

Test interpreter

+

That's all we wanted, but what about testing it? Sure, we might prefer to have a simple implementation by just using a plain List, so what can we possibly do?

+
object MemRepository extends ItemRepository[Id, List] {
+  private val mem = MutableMap.empty[String, Item]
+
+  override def findAll: List[Item] = mem.headOption.map(_._2).toList
+  override def find(name: ItemName): Id[Option[Item]] = mem.get(name.value)
+  override def save(item: Item): Id[Unit] = mem.update(item.name.value, item)
+  override def remove(name: ItemName): Id[Unit] = {
+    mem.remove(name.value)
+    ()
+  }
+}
+

That's pretty much it! We managed to abstract over the return type of findAll by just adding an extra parameter to our algebra.

+ +

About composition

+

At this point the avid reader might have thought, what if I want to write a generic function that takes all the items (using findAll), applies some discounts and writes them back to the DB (using save)?

+

Short answer is, you might want to define a different algebra where findAll and save have the same types (eg: both of them are streams) but in case you find yourself wanting to make this work with the current types then let's try and find out!

+
class DiscountProcessor[F[_], G[_]: Functor](repo: ItemRepository[F, G], join: G[F[Unit]] => F[Unit]) {
+
+  def process(discount: Double): F[Unit] = {
+    val items: G[Item] = repo.findAll.map(item => item.copy(price = item.price * (1 - discount)))
+    val saved: G[F[Unit]] = items.map(repo.save)
+    join(saved)
+  }
+}
+

We defined a join function responsible for evaluating the effects and flatten the result to F[Unit]. As you can see below, this works for both a streaming interpreter and a list interpreter (shout out to fthomas for proposing this solution):

+
object StreamingDiscountInterpreter {
+
+  private val join: Stream[IO, IO[Unit]] => IO[Unit] = _.evalMap(identity).compile.drain
+
+  def apply(repo: ItemRepository[IO, Stream[IO, ?]]): DiscountProcessor[IO, Stream[IO, ?]] =
+    new DiscountProcessor[IO, Stream[IO, ?]](repo, join)
+
+}
+
+object ListDiscountInterpreter {
+
+  private val join: List[IO[Unit]] => IO[Unit] = list => Traverse[List].sequence(list).void
+
+  def apply(repo: ItemRepository[IO, List]): DiscountProcessor[IO, List] =
+    new DiscountProcessor[IO, List](repo, join)
+
+}
+

While in this case it was possible to make it generic I don't recommend to do this at home because:

+
    +
  1. it involves some extra boilerplate and the code becomes harder to understand / maintain.
  2. +
  3. as soon as the logic gets more complicated you might run out of options to make it work in a generic way.
  4. +
  5. you lose the ability to use the fs2 DSL which is super convenient.
  6. +
+

What I recommend instead, is to write this kind of logic in the streaming interpreter itself. You could also write a generic program that implements the parts that can be abstracted (eg. applying a discount to an item f: Item => Item) and leave the other parts to the interpreter.

+ +

Design alternative

+

Another possible and very interesting alternative suggested by Michael Pilquist, would be to define our repository as follows:

+
trait ItemRepository[F[_], S[_[_], _]] {
+  def findAll: S[F, Item]
+}
+

Where the second type parameter matches the shape of fs2.Stream. In this case our streaming repository will remain the same (it should just extend ItemRepository[F, Stream] instead of ItemRepository[F, Stream[F, ?]]) but our in memory interpreter will now rely on fs2.Stream instead of a parametric G[_], for example:

+
object MemRepositoryAlt extends ItemRepository[Id, Stream] {
+
+  override def findAll: Stream[Id, Item] = {
+    sql"select name, price from items"
+      .query[Item]
+      .stream
+      .transact(xa)
+  }
+
+}
+

I think it's an alternative worth exploring further that might require a blog post on its own, so I'll leave it here for reference :)

+ +

Source of inspiration

+

I've come up with most of the ideas presented here during my work on Fs2 Rabbit, a stream based client for Rabbit MQ, where I make heavy use of this technique as I originally described in this blog post.

+

Another great source of inspiration was this talk given by Luka Jacobowitz at Scale by the Bay.

+ +

Abstracting over the effect type

+

One thing you might have noticed in the examples above is that both ItemRepository interpreters are not fixed to IO or Task or any other effect type but rather requiring a parametric F[_] and an implicit instance of Sync[F]. This is a quite powerful technique for both library authors and application developers. Well know libraries such as Http4s, Monix and Fs2 make a heavy use of it.

+

And by requiring a Sync[F] instance we are just saying that our implementation will need to suspend synchronous side effects.

+

Once at the edge of our program, commonly the main method, we can give F[_] a concrete type. At the moment, there are two options: cats.effect.IO and monix.eval.Task. But hopefully soon we'll have a Scalaz 8 IO implementation as well (fingers crossed).

+ +

Principle of least power

+

Abstracting over the effect type doesn't only mean that we should require Sync[F], Async[F] or Effect[F]. It also means that we should only require the minimal typeclass instance that satisfies our predicate. For example:

+
import cats.Functor
+import cats.implicits._
+
+def bar[F[_]: Applicative]: F[Int] = 1.pure[F]
+
+def foo[F[_]: Functor](fa: F[Int]): F[String] =
+  fa.map(_.toString)
+

Here our bar method just returns a pure value in the F context, thus we need an Applicative[F] instance that defines pure. On the other hand, our foo method just converts the inner Int into String, what we call a pure data transformation. So all we need here is a Functor[F] instance. Another example:

+
import cats.Monad
+
+def fp[F[_]: Monad]: F[String] =
+`  for {
+    a <- bar[F]
+    b <- bar[F]
+  } yield a + b
+

The above implementation makes use of a for-comprehension which is a syntactic sugar for flatMap and map, so all we need is a Monad[F] instance because we also need an Applicative[F] instance for bar, otherwise we could just use a FlatMap[F] instance.

+ +

Final thoughts

+

I think we got quite far with all these abstractions, giving us the chance to write clean and elegant code in a pure functional programming style, and there's even more! Other topics worth mentioning that might require a blog post on their own are:

+
    +
  • Dependency Injection
  • +
+
    +
  • Tagless Final + implicits (MTL style) enables DI in an elegant way.
  • +
  • Algebras Composition
  • +
+
    +
  • It is very common to have multiple algebras with a different F[_] implementation. In some cases, FunctionK (a.k.a. natural transformation) can be the solution.
  • +
+

What do you think about it? Have you come across a similar design problem? I'd love to hear your thoughts!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Gabriel Volpe + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/technical/index.html b/blog/technical/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/blog/technical/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/blog/testing-in-the-wild.html b/blog/testing-in-the-wild.html new file mode 100644 index 00000000..a892015d --- /dev/null +++ b/blog/testing-in-the-wild.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + Testing in the wild + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Testing in the wild

+ + + technical + +
+
+
+
+

Testing in the wild

+

Writing tests seems like a wonderful idea in theory but real systems can be a real pain to test. Today I want to show a few tips on how to + use specs2 + ScalaCheck to make some real-world testing somewhat bearable.

+

I am currently refactoring a big piece of code. Such refactoring is more like a small rewrite and some of our previous tests also have to be + rewritten from scratch. I will try to introduce you to the problem first.

+ +

Creating articles

+

The system-du-jour is called "Panda" (we have animal names for many of our services in my team) and is tasked with the creation of articles + on our legacy platform. An article is already a complicated beast. We have 3 levels of descriptions:

+
    +
  • Model: the description for a pair of RunFast shoes, with the brand, the gender it applies to, the size chart it uses and so on
  • +
+
    +
  • Config: the description for a specific combination of colours - black RunFast -, images, material,... There can be several configs per model
  • +
+
    +
  • +

    Simple: the description of a specific size - 37, 38, 39 -, price, stock, EAN (European Article Number),... There can be several simples + per config

    +

    This data can be created in our legacy catalog by calling a bunch of SOAP (yes you read that right) APIs and getting back, at each level, + some identifiers for the model, the configs, the simples.

    +

    This in itself can already be quite complicated and the creation of an article could fail in many ways. But it gets a lot more complex + considering that:

    +
      +
    1. +

      we need the service to be idempotent and not try to recreate an existing model/config/simple twice if we receive the same event twice + (we are using Kafka as our events system)

      +
    2. +
    3. +

      the articles sent by a merchant can be created incrementally, so some parts of the model/config/simple might have been already created + in a previous call

      +
    4. +
    5. +

      we are not the only ones creating articles in the system! Indeed, our team creates articles coming from external merchants but there is + also an internal "wholesale" department buying their own articles and creating them in the catalog. In that case a merchant might add + a new config to an existing model or some simples to an existing config

      +
    6. +
    7. +

      any step in the process could break and we have no support for transactions making sure that everything is created at once

      +
    8. +
    +

    So many things which can go wrong, how would you go about testing it?

    +
  • +
+ +

Combinations, the key to testing

+

After I started rewriting the tests I realized that our current approach was barely scratching the surface of all the possible combinations. + In a similar case your first thought should be "ScalaCheck"! But this time I am going to use ScalaCheck with a twist. Instead of only modelling + input data (model/config/simples) I am also modelling the system state:

+
    +
  • we have a "mapping table" to store merchant articles that have already been created. In which states can it be?
  • +
+
    +
  • +

    the legacy catalog can also be in many different states: does a particular model/config/simple already exist or not?

    +

    If we translate this into a specs2 + ScalaCheck specification, we get a property like this:

    +
  • +
+
class ArticleServiceSpec extends Specification with ScalaCheck { def is = s2"""
+
+  <insert long description of the problem and what we want to test>
+
+  run tests for model creation $modelCreation
+
+"""
+
+  def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) =>
+
+    ok // for now
+
+
+  }.setGen1(genArticleOneConfig)
+}
+

We are creating a ScalaCheck property, with a specs2 method prop which gives us some additional power over row ScalaCheck properties. + One thing we do here is to restrict the kind of generated Article to articles containing only one new configuration because we want to + focus first on all the possible cases of model creation. So we pass a specific generator to the property, just for the first argument + with setGen1.

+

Then, as you can see above, we return ok which is a specs2 result. This is because prop allows us to return anything that specs2 recognizes + as a Result (with the org.specs2.execute.AsResult typeclass) and then we are not limited to booleans in our ScalaCheck properties but + we can use specs2 matchers as well (we are going use this in the next step).

+

Now, for testing we need to do the following:

+
    +
  1. capture the state before the article creation
  2. +
+
    +
  1. execute the article creation
  2. +
+
    +
  1. capture the state after the article creation
  2. +
+
    +
  1. compare the resulting state to expected values
  2. +
+
  val before = createBeforeState(reviewed, catalog, mappings)
+  val result = run(createService(catalog, mappings).createArticles[R](reviewed))
+  val after  = createAfterState(reviewed, catalog, mappings, result)
+

What are BeforeState and AfterState? They are custom case classes modelling the variables we are interested in:

+
 case class BeforeState(
+   modelIdProvided:       Boolean,
+   modelNeedsToBeCreated: Boolean,
+   modelExistsInCatalog:  Boolean,
+   mappingExists:         Boolean)
+
+ case class AfterState(
+   modelExistsInCatalog: Boolean,
+   mappingExists:        Boolean,
+   exception:            Option[Throwable])
+

The first 2 variables of BeforeState are a bit curious. The first one gives us a ModelId if upstream systems know that a model already + exist. Then how could modelNeedsToBeCreated be true? Well, the events we receive don't rule out this possibility. This is the + current state of our domain data and arguably we should model things differently and reject malformed events right away. This is where the + saying "Listen to your tests" comes in :-).

+

If we count the number of combinations we end up with 16 possibilities for our "before state" and 8 possible outcomes. How can we represent + all those combinations in our test?

+ +

DataTables

+

Specs2 offers to possibility to create tables of data directly inside the code for better readability of actual and expected values + when you have lots of different possible combinations. Here is what we can do here

+
  val results =
+  "#" | "model-id" | "create" | "in catalog" | "mapping" || "in catalog"  | "mapping" | "exception" | "comment" |
+  1   !  true      ! true     ! true         ! true      !! true          ! true      ! false       ! "no model is created, because it can be found in the catalog, creation data is ignored" |
+  2   !  true      ! true     ! true         ! false     !! true          ! true      ! false       ! "we just updated the mapping" |
+  3   !  true      ! true     ! false        ! true      !! false         ! true      ! true        ! "the config creation must fail, no existing model" |
+  4   !  true      ! true     ! false        ! false     !! true          ! true      ! false       ! "the given model-id is ignored (a warning is logged)" |
+  5   !  true      ! false    ! true         ! true      !! true          ! true      ! false       ! "no model is created, because it can be found in the catalog" |
+  6   !  true      ! false    ! true         ! false     !! true          ! false     ! false       ! "the mappings are not updated because we did not create the model" |
+  7   !  true      ! false    ! false        ! true      !! false         ! true      ! true        ! "no corresponding model in the catalog" |
+  8   !  true      ! false    ! false        ! false     !! false         ! false     ! true        ! "no corresponding model in the catalog" |
+  9   !  false     ! true     ! true         ! true      !! true          ! true      ! false       ! "we use the mapping table to retrieve the model id and the catalog for the model" |
+  10  !  false     ! true     ! true         ! false     !! true          ! true      ! false       ! "in this case the model already exists in the catalag but we have no way to know" |
+  11  !  false     ! true     ! false        ! true      !! false         ! true      ! true        ! "the mapping exists but not the data in the catalog" |
+  12  !  false     ! true     ! false        ! false     !! true          ! true      ! false       ! "regular model + config creation case" |
+  13  !  false     ! false    ! true         ! true      !! true          ! true      ! true        ! "there is no model id and no creation data" |
+  14  !  false     ! false    ! true         ! false     !! true          ! false     ! true        ! "the model exists in the catalog but we have no way to retrieve it" |
+  15  !  false     ! false    ! false        ! true      !! false         ! true      ! true        ! "model id found in the mapping but not in the catalog" |
+  16  !  false     ! false    ! false        ! false     !! false         ! false     ! true        ! "not enough data to create the model nor the mapping"
+
+ checkState(before, after, parseTable(results))
+

This looks like a strange piece of code but this is actually all valid Scala syntax! results is a specs2 DataTable created out of:

+
    +
  • a header where column names are separated with |
  • +
+
    +
  • rows that are also separated with |
  • +
+
    +
  • cells on each row, separated with !
  • +
+

We can also use || and !! as separators and we use this possibility here to visually distinguish input columns from expected results + columns.

+ +

Running the tests

+

The table above is like a big "truth table" for all our input conditions. Running a test consists in:

+
    +
  1. using the 'before state' to locate one of the row
  2. +
+
    +
  1. getting the expected 'after state' from the expected columns
  2. +
+
    +
  1. comparing the actual 'after state' with the expected one
  2. +
+

The funny thing is that before executing the test I did not exactly know what the code would actually do! So I just let the test guide me. + I put some expected values, run the test and in case of a failure, inspect the input values, think hard about why the code is not behaving the + way I think it should.

+

One question comes to mind: since this is a ScalaCheck property, how can we be sure we hit all the cases in the table? The first thing we + can do is to massively increase the number of tests that are going to be executed for this property, like 10000. With specs2 you have many + ways to do this. You can set the minTestsOk ScalaCheck property directly in the code:

+
def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) =>
+ ...
+}.setGen1(genArticleOneConfig).set(minTestsOk = 10000)
+

But you can also do it from sbt:

+
sbt> testOnly *ArticleServiceSpec -- scalacheck.mintestsok 10000
+

This is quite cool because this means that you don't have to recompile the code if you just want to run a ScalaCheck property with more tests.

+ +

Checking the results

+

As I wrote, when a specific combination would fail I had to inspect the inputs/outputs and think hard, maybe my expectations are wrong and + I needed to change the expected values? To this end I added a "line number" column to the table and reported it in the result:

+
 [error]  > On line 6
+ [error]
+ [error]  Before
+ [error]    model id set:             true
+ [error]    model creation data set:  false
+ [error]    model exists in catalog : true
+ [error]    model id mapping exists:  false
+ [error]
+ [error]  After
+ [error]    model exists in catalog:  true
+ [error]      expected:               true
+ [error]
+ [error]    model id mapping exists:  false
+ [error]      expected:               false
+ [error]
+ [error]    exception thrown:         None
+ [error]      expected:               Some
+

This reporting is all done in the checkState method which is:

+
    +
  • doing the comparison between actual and expected values
  • +
+
    +
  • displaying the before / after states
  • +
+
    +
  • +

    displaying the difference between expected and actual values

    +

    Actually I even enhanced the display of actual/expected values by coloring them in green or red in the console, using one of specs2 helper + classes org.specs2.text.AnsiColors:

    +
  • +
+
import org.specs2.text.AnsiColors
+
+def withColor[A](actual: A, expected: A, condition: (A, A) => Boolean = (a:A, e:A) => a == e): String =
+  // color the expected value in green or red, depending on the test success
+  color(expected.toString, if (condition(actual, expected)) green else red)
+
+withColor(after.modelExistsInCatalog, expected.modelExistsInCatalog)
+

Both the line numbering and the coloring really helps in fixing issues fast!

+ +

Replaying tests

+

A vexing issue with property-based testing is that being random, it will generate random failures every time you re-run a property. So you + can't re-run a property with the exact same input data. But that was before ScalaCheck 1.14! Now we can pass the seed that is used by the random + generator to faithfully re-run a failing test. Indeed when a property fails, specs2 will display the current seed value:

+
[error]  The seed is 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI=
+

And you can pass this value on the command line to re-run with exactly the failing input data:

+
sbt> testOnly *ArticleServiceSpec -- scalacheck.seed 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI=
+

This is super-convenient for debugging!

+ +

Comments

+

Finally when a given row in the table passes, there is a comment column to register the reason for this specific outcomes so that future + generations have a sense of why the code is behaving that way. In that sense this whole approach is a bit like having "golden tests" which + are capturing the behaviour of the system as a series of examples

+ +

Conclusion

+

This post shows how we can leverage features from both specs2 and ScalaCheck to make our tests more exhaustive, more readable, more debuggable. + The reality is still more complicated than this:

+
    +
  • the total number of combinations would make our table very large. So there are actually several tables (one for model creation, one for + config creation,...) where we assume that some variables are fixed while others can move
  • +
+
    +
  • specs2 datatables are currently limited to 10 columns. The DataTable code is actually code generated and the latest version only has 10 + columns. One easy first step would be to generate more code (and go up to the magic 22 number for example) or to re-implement this functionality + as some kind of HList
  • +
+
    +
  • the input state is not trivial to generate because the objects are dependent. The ModelId of a generated model must be exactly the same + as the one used in the Mappings component to register that a model has already been created. So in reality the 2 generators for Article and + Mappings are not totally independent
  • +
+
    +
  • the Arbitrary instance for Article can give us articles with 5 Configs and 10 Simples but for this test, one Config and one Simple + are enough. Unfortunately we miss a nice language to express those generation condition and easily tweak the default Arbitrary[Article] (I + will explore a solution to this problem during the next Haskell eXchange)
  • +
+
    +
  • why are we even using ScalaCheck to generate all the cases since we already statically know all the possible 16 input conditions? We could + invert this relation and have a ScalaCheck property generated for each row of the datatable with some arbitrary data for the model (and some + fixed data given by the current row). This would not necessarily lead to easier code to implement.
  • +
+

Anyway despite those remaining questions and issues I hope this post gives you some new ideas on how to be more effective when writing tests + with specs2 and ScalaCheck, please comment on your own experiments!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Eric Torreborre + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/three-types-of-strings.html b/blog/three-types-of-strings.html new file mode 100644 index 00000000..6bc0fa83 --- /dev/null +++ b/blog/three-types-of-strings.html @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + There are at least three types of strings + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

There are at least three types of strings

+ + + technical + +
+
+
+
+

There are at least three types of strings

+

Newtype mechanisms + are a great way to introduce wrapper-free, global distinctions of + “different” instances of the same type. But we can do that on a local + level, too, + by using type parameters.

+

Consider these two signatures.

+
def mungeIDs(uids: List[String], gids: List[String],
+             oids: List[String]): Magic[String, String, String]
+
+def mungeIDsSafely[UID <: String, GID <: String, OID <: String]
+            (uids: List[UID], gids: List[GID],
+             oids: List[OID]): Magic[UID, GID, OID] 
+

The second function is a strictly more general interface; the first, + concrete signature can be implemented by calling the second function, + passing [String, String, String] as the type arguments. There is no + need to even have the first signature; anywhere in your program where + you pass three List[String]s as arguments to mungeIDsSafely, the + proper type arguments will be inferred.

+

Yet, assuming you don’t wish mungeIDs to be oracular (i.e. a source + of UIDs, GIDs, and OIDs), the second signature is probably much more + reliable, because type parameters are quite as + mysterious + as the opaque abstract type members of the newtype mechanism.

+
    +
  1. mungeIDsSafely can’t invent new IDs, not even with null.
  2. +
  3. It can’t combine them to produce new IDs.
  4. +
  5. It can treat the three list arguments as List[String]. However, + it cannot convert any String back into an ID; any UIDs, GIDs, or + OIDs that appear in the result Magic[UID, GID, OID] must have + come from one of the argument lists, directly. (That’s not to say + that mungeIDsSafely can’t use the string-nature to make that + decision; for example, it could always choose the + smallest-as-string UID to put into the resulting Magic. But, that + UID is still enforced to be a proper element of the uids + argument, and cannot be gotten from anywhere else.
  6. +
  7. Perhaps most importantly, it cannot mix up UIDs, GIDs, and + OIDs. Even though, “really”, they’re all strings!
  8. +
+

It is entirely irrelevant that you cannot subclass String in Scala, + Java, or whatever. + There are more types than classes.

+

Given the advantages, it’s very unfortunate that the signature of + mungeIDsSafely is so much noisier than that of mungeIDs. At least + you have the small consolation of eliminating more useless unit tests.

+

This is a good first approximation at moving away from the dangers of + concreteness in Scala, and has the advantage of working in Java, too + (sort of; the null prohibition is sadly relaxed).

+ +

Non-supertype constraints

+

In Scala, you can also use implicits to devise arbitrary constraints, + similar to typeclasses in Haskell, and sign your functions using + implicits instead, for much finer-grained control, improved safety, + and types-as-documentation.

+
// a typeclass for "IDish types" (imagine instances)
+sealed trait IDish[A]
+
+def mungeIDsTCey[UID: IDish, GID: IDish, OID: IDish]
+            (uids: List[UID], gids: List[GID],
+             oids: List[OID]): Magic[UID, GID, OID] 
+

Though all three types have the same constraint, IDish, they are + still distinct types. And now, the coupling with String is broken; + as the program author, you get to decide whether you want that or not.

+ +

Pitfalls avoided for you

+

Luckily, Java doesn’t make the mistake of “reified generics”. If it + did, you could ask whether UID = GID = OID = String, and all your + safety guarantees would be gone. Forcing all generics to be reified + does not grant you any new expressive power; all it does is + permanently close off large swaths of the spectrum of mystery to you, + forbidding you from using the full scope of the design space to + improve the reliability of your well-typed programs.

+

The same goes for claiming that null ought to be a default member of + every type, even the abstract ones that ought to be a little more + mysterious; it’s easy to add new capabilities (e.g. Scala’s >: Null + constraint, if you really must use null), but taking them away is + much, much harder.

+

Furthering this spirit of making good programs easier to write and bad + programs harder to write, a useful area of research in Scala might be + making signatures such as that of mungeIDsSafely nicer, or + signatures such as that of mungeIDs uglier.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/towards-scalaz-1.html b/blog/towards-scalaz-1.html new file mode 100644 index 00000000..27b7e0ba --- /dev/null +++ b/blog/towards-scalaz-1.html @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Towards Scalaz (Part 1) + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Towards Scalaz (Part 1)

+ + + technical + +
+
+
+
+

Towards Scalaz (Part 1)

+

A lot of people see Scalaz as a hard fringe, ivory tower, + not suited for real-world applications library, which is + unfortunate. The goal of this blog post series is to introduce + various components of Scalaz, and hopefully through this + allow folks to gain an understanding towards the power of + Scalaz.

+

As a prerequisite, I assume knowledge of type classes as they + are implemented and used in Scala.

+ +

Part 1: Learning to Add

+

Our motivation for the inaugural post of the series will be + summing a List of something. Lets start out with Int, + which is simple enough.

+
def sum(l: List[Int]): Int = l.reduce(_ + _)
+

And this works (kind of, it fails on empty Lists but we'll get to that). + But what if we want to sum a List[Double]?

+
def sumDoubles(l: List[Double]): Double = l.reduce(_ + _)
+

The code is the same, modulo the type parameter. In fact, the + code would be the same whether it is Int, Double, or BigInt. + Being the good programmers that we are, let's make this generic + in that respect with the help of scala.math.Numeric.

+
def sumNumeric[A](l: List[A])(implicit A: Numeric[A]): A =
+  l.reduce(A.plus)
+ +

Problem

+

Awesome. We can now sum List[Int], List[Double], List[BigInt], + and many more.

+

But let's give this a bit more thought - what if we wanted to + "sum" a List[String] - that is, we concatenate all the Strings + together to create one large String ?

+
def sumStrings(l: List[String]): String = l.reduce(_ + _)
+

This looks exactly like summing Int and Doubles! This however + does not work with our sumNumeric - there is no (sane) way to define + a Numeric[String].

+

Another way to look at this is that we only use the plus method + on Numeric, never any of the other methods that also make sense + for numeric types. So while our function works for summing a List + of numeric types, it does not work for anything else that is not + numeric but can still be "added" (String and string concatenation, + List[A] and List#++).

+ +

Making it generic

+

So what do we want? We want a type class that only requires instances + to be able to "add" two As to get another A.

+
trait Addable[A] {
+  def plus(x: A, y: A): A
+}
+

And let's define an instance of Addable for all Numeric types and String.

+
object Addable {
+  implicit def numericIsAddable[A](implicit A: Numeric[A]): Addable[A] =
+    new Addable[A] {
+      def plus(x: A, y: A): A = A.plus(x, y)
+    }
+
+  implicit val stringIsAddable: Addable[String] =
+    new Addable[String] {
+      def plus(x: String, y: String): String = x + y
+    }
+}
+

And here's our shiny new generic summer function!

+
def sumGeneric[A](l: List[A])(implicit A: Addable[A]): A =
+  l.reduce(A.plus)
+

And now this works for Int, Double, String, and many more.

+

A good exercise at this point is to define an Addable instance for List[A].

+ +

Making an Exception

+

What happens when we pass in an empty List to our summer function though? + We get an exception! How do we prevent this? A common answer I get is + "Oh I know it won't happen" – this is not ideal, we want to guarantee safety + as much as possible without having to rely on human judgement.

+

How then do we write a safer summer function? Lets turn to an alternative + way of implementing sum on List[Int].

+
// Old, bad version
+def sum(l: List[Int]): Int = l.reduce(_ + _)
+
+// Shiny, new version
+def sum(l: List[Int]): Int = l.foldLeft(0)(_ + _)
+

What happens now when we pass an empty List into the sum function? We get 0, + not an exception! Note that before all we gave the program was a binary + operation (what Addable defines), where now we give a binary option and a + "zero" or starting value (the 0). As it stands, we cannot write this with + Addable since it has no "zero".

+

It may be tempting to just add a zero method to Addable, but then we may run + into the same issues we had with Numeric later on – we don't always need + a "zero", sometimes a binary operation is good enough. So instead, let's create + an AddableWithZero type class.

+
trait AddableWithZero[A] extends Addable[A] {
+  def zero: A
+}
+

Note that while you dont see the plus method in here, the fact + it extends Addable without implementing the plus method propagates the need to + implement that method, so programmers who want to create an AddableWithZero[A] instance + need to implement both.

+

Programmers can now write functions that depend only on Addable, or perhaps if they + need a bit more power use AddableWithZero. Types that have AddableWithZero instances + also have Addable instances automatically due to subtyping.

+

Lets move our Addable instances to the AddableWithZero object.

+
object AddableWithZero {
+  implicit def numericIsAddableZero[A](implicit A: Numeric[A]): AddableWithZero[A] =
+    new AddableWithZero[A] {
+      def plus(x: A, y: A): A = A.plus(x, y)
+      def zero: A = A.zero
+    }
+
+  implicit val stringIsAddableZero: AddableWithZero[String] =
+    new AddableWithZero[String] {
+      def plus(x: String, y: String): String = x + y
+      def zero: String = ""
+    }
+}
+

And finally, our shiny new generic sum function!

+
def sumGeneric[A](l: List[A])(implicit A: AddableWithZero[A]): A =
+  l.foldLeft(A.zero)(A.plus)
+

Hurrah!

+ +

Plot Twist

+

It turns out that our Addable and AddableWithZero type classes is not just us being + sly and clever, but an actual thing! They are called Semigroup and + Monoid (respectively), taken from the wonderful field of abstract algebra. Abstract + algebra is a field dedicated to studying algebraic structures as opposed + to just numbers as we may be used to. The field looks into what properties + and operations various structures have in common, such as integers and + matrices. For instance, we can add two integers, as well as two matrices of the same size. + This is analogous to how we noticed the plus worked on not only Numeric + but String and List[A] as well! This is the kind of generecity we're looking for.

+

Here's what sumGeneric looks like in Scalaz land.

+
import scalaz.Monoid
+
+def sumGeneric[A](l: List[A])(implicit A: Monoid[A]): A =
+  l.foldLeft(A.zero)((x, y) => A.append(x, y))
+

Thankfully we dont have to create our own versions of Semigroup and Monoid – + Scalaz has one for us! In fact, the developers of Scalaz have been kind enough to define + several Monoid instances for common types such as Numeric, String, List[A], etc. + There are also instances for tuples – if we have a tuple, say of type (A, B, C), + and all three types have Monoid instances themselves, then the whole tuple has an + instance where the zero is the tuple (A.zero, B.zero, C.zero) and the plus is + appending corresponding pairs between the two tuples. Look for instances that may already + be defined before defining your own on existing types.

+

If you are interested in learning more about numeric programming, check out + the spire library, as well as the + accompanying post about generic numeric programming.

+ +

Law-Abiding Citizen

+

To close this post off, I confess one thing: defining a Monoid (and Semigroup) instance + should not be done without some thought. It is not enough that you simply have a zero and + a binary operation – to truly have a Monoid or Semigroup certain laws must be obeyed. + These laws are as follows:

+

Call the plus operation + + and the zero value 0 0 . Arbitrary values of type A will be + referred to as a a , b b , etc.

+

The Semigroup law requires + + to be associative. That is:

+ (a+b)+c=a+(b+c) (a + b) + c = a + (b + c) + +

In addition to the Semigroup law for the binary operation, the Monoid law relates + + + and 0 0 :

+ (a+0)=(0+a)=a (a + 0) = (0 + a) = a + +

To check these laws, Scalaz provides ScalaCheck + bindings to help you, but that is a topic for another day.

+

Note that a particular type can have several Semigroup or Monoids that make sense. + For instance, Int has a Monoid on (+,0) (+, 0) as well as on (,1) (*, 1) . Convince yourself + (using the above laws) that this makes sense.

+

This raises the question of how we get both + + and * Monoids for Int without + making scalac freak out about ambiguous implicit values. The answer is "tagged types", + again a topic for another day.

+ +

Getting Help

+

If you have any questions/comments/concerns, feel free to hop onto the IRC channel on + Freenode at #scalaz.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/towards-scalaz-2.html b/blog/towards-scalaz-2.html new file mode 100644 index 00000000..fa49e2a7 --- /dev/null +++ b/blog/towards-scalaz-2.html @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + Towards Scalaz (Part 2) + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Towards Scalaz (Part 2)

+ + + technical + +
+
+
+
+

Towards Scalaz (Part 2)

+

A lot of people see Scalaz as a hard fringe, ivory tower, + not suited for real-world applications library, which is + unfortunate. The goal of this blog post series is to introduce + various components of Scalaz, and hopefully through this + allow folks to gain an understanding towards the power of + Scalaz.

+

As a prerequisite, I assume knowledge of type classes as they + are implemented and used in Scala, higher kinded types, + and sum types (e.g. Option/Some/None, Either/Left/Right).

+

For a tutorial/review on (higher) kinds, I recommend the following resources:

+ + +

Part 2: Summations of a Higher Kind

+

Last time we left off after + writing our own generic sum function:

+
import scalaz.Monoid
+
+def sumGeneric[A](l: List[A])(implicit A: Monoid[A]): A =
+  l.foldLeft(A.zero)((x, y) => A.append(x, y))
+

This allowed us to sum a list not only of numeric types like + Int, but also others that could be added and had a "zero" such as + String via string concatenation and the empty string, as well as + List[A] via list concatenation and the empty list.

+

But, we can do better! Why limit ourselves to List? What if we want + to sum over a Vector, or even a tree? We could use Seq and that + would allow us to pass in List or Vector, but it still brings up + the problem of trees, and any other data structure that may not fit + the Seq bill.

+ +

What do we want? Folds!

+

Recall when we "came up with" Semigroup and Monoid last time - + what did we do? We simply looked at what operations we needed + (add/append and zero) and factored it out into a type class. + Let's try doing the same this time.

+

So what are we doing with List in our implementation? Nothing much + really, we're just folding over it. If we think about it, we could + "fold" over say, a tree as well. Let's take this operation out into + a type class, and aptly name it Foldable.

+
trait Foldable[F[_]] {
+  // Instead of requiring the contents to be monoidal, let's
+  // make it flexible by allowing a fold as long as we can convert
+  // the contents to a type that has a `Monoid`.
+  def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B
+}
+

And let's implement instances of this type class for List and our own + Tree.

+

Our tree definition:

+
sealed trait Tree[A]
+case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
+case class Leaf[A]() extends Tree[A]
+

and our instances:

+
object Foldable {
+  implicit val listIsFoldable: Foldable[List] =
+    new Foldable[List] {
+      def foldMap[A, B](fa: List[A])(f: A => B)(implicit B: Monoid[B]): B =
+        fa.foldLeft(B.zero)((acc, elem) => B.append(acc, f(elem)))
+    }
+
+  implicit val treeIsFoldable: Foldable[Tree] =
+    new Foldable[Tree] {
+      def foldMap[A, B](fa: Tree[A])(f: A => B)(implicit B: Monoid[B]): B =
+        fa match {
+          case Leaf() =>
+            B.zero
+          case Node(value, left, right) =>
+            B.append(f(value), B.append(foldMap(left)(f), foldMap(right)(f)))
+        }
+    }
+}
+

and finally, our new summing function:

+
def sumGeneric[F[_], A](fa: F[A])(implicit F: Foldable[F], A: Monoid[A]): A =
+  fa.foldMap(identity)
+ +

Scalaz to the rescue

+

As with last time, Scalaz defines the Foldable type class for us. However, + to really be "foldable", not only should you define foldMap, but foldRight + as well. Some of you may be wondering why foldRight and not foldLeft, or both? + The reasons for this decision are that

+
    +
  • foldLeft can be defined in terms of foldRight (a fun exercise is to try this for yourself)
  • +
  • foldLeft fails on infinite lists (think Stream in Scala)
  • +
+

That being said, Scalaz defines instances of Foldable for many of the standard + Scala types (List, Vector, Stream, Option), as well as its own (Tree, EphemeralStream). + The methods available on the type class not only include foldMap and foldRight which + are required to be implemented, but several derived ones as well including fold (foldMap with + identity), foldLeft, toList/IndexedSeq/Stream, among others.

+

So our code with scalaz.Foldable now looks like:

+
import scalaz.{ Foldable, Monoid }
+
+// Note that this is equivalent to scalaz.Foldable#fold
+def sumGeneric[F[_], A](fa: F[A])(implicit F: Foldable[F], A: Monoid[A]): A =
+  fa.fold
+

Note that the implementation of the function is rather plain, but that's a good thing! + This shows the level of genericity type classes, folds, and Scalaz is capable of. If you ever + find yourself needing to fold something down, look at the methods available on + scalaz.Foldable. By simply adding an instance of Foldable to your F[_] by implementing + the two methods above, you get "for free" a bunch of + derived ones!

+ +

An Aside: Taming the Elephant

+

In recent days, the word "Hadoop" has become synonymous with "big data." The MapReduce + system made popular by Google + has made it's way into several companies looking to glean information from their data.

+

Why am I mentioning this in a typelevel.scala blog post? Well, think about the reduce phase – + what is really happening? For a particular key, we're given a list of values emitted + for that key, and we want to reduce those values into a single value. Sound familiar? + Sounds a bit like fold, doesn't it? Note that not all reductions in MapReduce have to follow + monoid laws, but a surprising amount do as demonstrated by Twitter's + Algebird project.

+

Going back to fold, recall that in order to just fold we need to have something + Foldable that contains something that already has a Monoid instance. A more general + approach, as taken by scalaz.Foldable, is to also provide a foldMap function which + lets us also pass in a function mapping each element of the Foldable to something + that is a Monoid, and reduce over that instead.

+

So. Given something, say a List[A], we want to Map each element of the list to + an element of a type that has a Monoid instance, and then we want to Reduce the + list down to a single value. What is this? All together now: MapReduce!

+

Unfortunately, Hadoop MapReduce by itself does not give you anything like a List. + Fortunately, our good friends at NICTA have developed + and open sourced the wonderful Scoobi project, + which abstracts over Hadoop MapReduce by providing a List-like interface, called a + DList (distributed list). Users treat the DList very similarly to how they would + a regular Scala List, and perform operations on it that get compiled down into + MapReduce jobs. Such operations include not only the familiar (and expected) map + and reduce combinators, but also our friends foldMap and fold. While DList's + do not have a proper Foldable instance due to the difficulty of implementing foldRight + for the MapReduce, I find it to be a great example of the power of abstractions and + genericity abstract algebra and Scalaz provides to us as programmers.

+ +

Further Reading

+ + +

Getting Help

+

If you have any questions/comments/concerns, feel free to hop onto the IRC channel on + Freenode at #scalaz.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/treelog.html b/blog/treelog.html new file mode 100644 index 00000000..7493a080 --- /dev/null +++ b/blog/treelog.html @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + Treelog + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Treelog

+ + + technical + +
+
+
+
+

Treelog

+

Lance Walton's Treelog is the result of a real problem that arose in a trading system that we were working on: + > How can everything that happens to a trade be audited?

+

The first (and tedious) answer is copius logging by writing to some kind of audit data type or simple logger.

+

There are a number of problems with this approach:

+
    +
  • writing logging around computations often complicates the code as values must be extracted, recorded and then applied
  • +
  • separating logic from the computation can lead to a mismatch between the log and the computation
  • +
  • linear logs are very difficult to follow
  • +
  • its not easy to control how much of a linear log to show a user if you do not know what is detail and what isn't
  • +
+

Treelog resolves these issues by making the log itself a tree, reflecting the computational tree it logs, and uses techniques described in the Typeclassopedia to bring logging closer to the computation: the Writer Monad, a Monad Transformer, and a cunning Monoid.

+

Note that this post is a more technical description of how Treelog was written. For a quick introduction of use please refer to the README. + I will also refer you to Eugene Yokota's excellent Scalaz tutorial to study the details of Scalaz where appropriate.

+ +

Logging with Treelog

+

Here is an example which illustrates how Treelog is used (more examples):

+
val simple: DescribedComputation[Int] = 
+  "Calculating sum" ~< {
+    for {
+      x11 ~> ("x = " + _)
+      y2 ~> ("y = " + _)
+      sum ← (x + y) ~> ("Sum is " + _)
+    } yield sum
+

DescribedComputation[Value] is just a type alias for EitherT[LogTreeWriter, String, Value]. EitherT, a Monad Transformer, enables success and failure to be represented and will be covered below.

+

The log and value can be retrieved with result.run.written and result.run.value respectively. The written tree will look like this:

+
Calculating sum
+  x = 11
+  y = 2
+  Sum is 13
+

and the value will be \/-(13), which is Scalaz's version of Right.

+ +

Tree Nodes

+

The nodes of the tree contain a LogTreeLabel:

+
sealed trait LogTreeLabel[A] {
+  def success: Boolean
+  def annotations: Set[A]
+  // ...
+}
+
+case class DescribedLogTreeLabel[A](description: String, success: Boolean, annotations: Set[A]) extends LogTreeLabel[A]
+
+case class UndescribedLogTreeLabel[A](success: Boolean, annotations: Set[A]) extends LogTreeLabel[A]
+

The node is able to represent success or failure, may have a description, and a set of annotations. Annotations allow extra information to be carried in a Node which may be useful when working with the audit later. For our trading system that was other trades that were affected by the process as a side effect of processing a trade.

+

Treelog distinguishes between tree nodes that describe a computation, a DescribedLogTreeLabel, and an UndescribedLogTreeLabel which is a Tree with no description in the root. In the example above, the root node is a DescribedLogTreeLabel containing the text Calculating sum. This is an important distinction that informs the way trees must be combined by our Treelog monoid, which the Writer needs (see below).

+

Note that originally, LogTreeLabel was a case class defined like this:

+
case class LogTreeLabel(description: Option[String], success: Boolean, annotations: Set[Annotation])
+

This meant that DescribedLogTreeLabel and UndescribedLogTreeLabel were not necessary because the optional description carried the equivalent information. However, we found the formulation above easier to work with.

+ +

Syntactic Sugar

+

Treelog makes use of some syntactic sugar inspired by Tony Morris's post on Writer. In the example above, ~> is a method on an implicitly constructed class which takes any value x: T and returns a DescribedComputation[T], representing the value x and a leaf node containing the description.

+

There is special support for Booleans, Options, Eithers and Traversables which you can learn about from the Treelog README.

+ +

Writer and Monoid

+
Writer allows us to do computations while making sure that all the log values are combined into one log value that then gets attached to the result. – LYAH
+

Writers allow us to write a log embedded within a computation.

+

Here is a simple example using Scalaz, see the references for more detailed examples.

+
val r: Writer[String, Int] = 
+  for {
+    a3.set("Got a 3.")
+    b5.set("Got a 5.")
+  } yield a * b
+
+println(r.written) // Got a 3.Got a 5.
+println(r.value) // 15
+

The Writer uses a monoid for the written value (a String in this case) to combine the logs (concatenation for Strings). For Lists it is ::: and Nil, etc.

+ +

Treelog's Monoid

+

Treelog uses a Scalaz Writer, Tree and a custom Monoid implementation to record logs.

+

The monoid has to provide two things: a zero value, and a binary operation that combines two trees in a meaningful way. The zero value for Treelog is just a constant used internally to the `Monoid´ implementation and never leaks out since there is always at least one value being logged.

+

Combining trees is done as follows:

+
    +
  • a zero tree with a tree is just the tree
  • +
  • two undescribed trees become a new undescribed tree with the children of the right tree appended to the children of the left tree
  • +
  • an undescribed tree T1, and a described tree, T2, becomes an undescribed tree with T2 appended to the children of T1
  • +
  • a described tree, T1, and an undescribed tree, T2, is an undescribed tree with T1 prepended to the children of T2
  • +
  • two described trees are combined by creating an undescribed tree with the two trees as children
  • +
+

Note that the result is always an undescribed tree since there is no meaningful way to combine descriptions of child nodes. In the example above the tree contains two leaves: "Got a 3" and "Got a 5". Concatenating those descriptions isn't as meaningful as "Summing a and b", which could be done like this:

+
val r: Writer[String, Int] = 
+  "Summing a and b" ~< for {
+    a3.set("Got a 3.")
+    b5.set("Got a 5.")
+  } yield a * b
+

The quadratic roots example is a good one to see this.

+ +

Success and Failure – EitherT

+

The purpose of Treelog is to audit a computation, return the log and result, and indicate whether the computation was successful or not. The Writer with the Monoid described above satisfies the first two requirements, but not the third. To add success and failure, the writer needs to be combined with Either. What we need is a Monad Transformer.

+
Monad Transformers are special types that allow us to roll two monads into a single one that shares the behaviour of both. – Haskell Wikibook
+

EitherT is a monad transformer that combines some monad with Either, which is exactly what is needed. It is constructed with three types: EitherT[M, A, B] where M is the monad, A is the failure type and B is the success type. In Treelog, M is a Writer, A is a String and B is the type of the result.

+

Logtree includes the methods def failure[V](description: String): DescribedComputation[V] and def success[V](value: V, description: String): DescribedComputation[V] to support failure and success. They ensure that the failure case is included in the tree and that the nodes in the tree now reflect that the computation has failed.

+

Here is an example from Treelog:

+
val foo: String \/ Int = 11.right[String]
+val bar: String \/ Int = "fubar".left[Int]
+
+val leftEithers: DescribedComputation[Int] = 
+  "Calculating left either sum" ~< {
+    for {
+      xfoo ~>? ("x = " + _)
+      ybar ~>? ("y = " + _)
+      sum ← (x + y) ~> (v"Sum is " + v)
+    } yield sum
+  }
+
+val leftEitherWriter: LogTreeWriter[String \/ Int] = leftEithers.run
+println(leftEithers.run.written.shows)
+

To retrieve the underying value back from EitherT, we call run which returns the Writer containing Scalaz's version of Either (which is more useful than Scala's built-in Either).

+

The written value is:

+
Failed: Calculating left either sum
+  x = 11
+  Failed: fubar
+
+Failure: Calculating left either sum
+

So the written log indicates that the whole computation failed, and the result is -\/, the Left for a Scalaz Either, containing "Failure: Calculating left either sum".

+ +

In Practice

+

Treelog is being used in earnest in a trading system, and the results have been a resounding success. And the level of accurate detail the system is able to show users has been invaluable in reducing support questions, which is always welcome.

+ +

Further Reading

+ +
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Channing Walton + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/tuple-announcement.html b/blog/tuple-announcement.html new file mode 100644 index 00000000..85f71367 --- /dev/null +++ b/blog/tuple-announcement.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + New Typelevel Tuple Team + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

New Typelevel Tuple Team

+ + + governance + +
+
+
+
+

New Typelevel Tuple Team

+

We are pleased to announce that we have set up a Typelevel team for Tuple, a remote pair-programming application. + Tuple provides screen-sharing, audio and video calls (video is optional), as well as a very helpful screen drawing feature for whiteboarding. + Tuple clients are available for MacOS and Linux (beta).

+

We hope that Tuple will help the community by enabling maintainers to collaborate together more easily, and as a new way to onboard and share knowledge with new contributors.

+

There are instructions in the Governance repo that cover how maintainers can join the Tuple team, and how others can get started. + Happy pairing!

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/type-equality-to-leibniz.html b/blog/type-equality-to-leibniz.html new file mode 100644 index 00000000..0570b3a1 --- /dev/null +++ b/blog/type-equality-to-leibniz.html @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + A function from type equality to Leibniz + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

A function from type equality to Leibniz

+ + + technical + +
+
+
+
+

A function from type equality to Leibniz

+

The Scala standard library provides evidence of two types being equal + at the data level: a value of type + (A =:= B) + witnesses that A and B are the same type. Accordingly, it provides + an implicit conversion from A to B. So you can write Int-summing + functions on your generic foldable types.

+
final case class XList[A](xs: List[A]) {
+  def sum(implicit ev: A =:= Int): Int =
+    xs.foldLeft(0)(_ + _)
+}
+

That works because ev is inserted as an implicit conversion over + that lambda's second parameter.

+ +

Fragility

+

That's not really what we want, though. In particular, flipping A + and Int in the ev type declaration will break it:

+
….scala:5: overloaded method value + with alternatives:
+  (x: Int)Int <and>
+  (x: Char)Int <and>
+  (x: Short)Int <and>
+  (x: Byte)Int
+ cannot be applied to (A)
+    xs.foldLeft(0)(_ + _)
+                     ^
+

That doesn't make sense, though. Type equality is symmetric: Scala + knows it goes both ways, so why is this finicky?

+

Additionally, we apply the conversion for each Int. It is a logical + implication that, if A is B, then List[A] must be List[B] as + well. But we can't get that cheap, single conversion without a cast.

+ +

Substitution

+

Scalaz instead provides + Leibniz, + a more perfect type equality. A simplified version follows, which we + will use for the remainder.

+
sealed abstract class Leib[A, B] {
+  def subst[F[_]](fa: F[A]): F[B]
+}
+

This reads “Leib[A, B] can replace A with B in any type + function”. That “any” is pretty important: it gives us both the + theorem that we want, and a tremendous consequent power that gives us + most of what we can get in Scala from value-level type equality, by + choosing the right F type parameter to subst.

+ +

What could it be?

+

Following the Scalazzi rules, where no null, type testing or + casting, or AnyRef-defined functions are permitted, what might go in + the body of that function? Even if you know what A is, as a Leib + implementer, it's hidden behind the unknown F. Even if you know that + B is a supertype of A, you don't know that F is covariant, + by scalac or otherwise. + Even if you know that A is Int and B is Double, what are you + going to do with that information?

+

So there's only one thing this Leib could be, because you do + have an F of something.

+
implicit def refl[A]: Leib[A, A] = new Leib[A, A] {
+  override def subst[F[_]](fa: F[A]): F[A] = fa
+}
+

Every type is equal to itself. Every well-formed Leib instance + starts out this way, in this function.

+ +

Recovery

+

So, it's great that we know the implication of the subst method's + generality. But that's not good enough; we had that with =:= + already. We want to write well-typed operations that represent all the + implications of the Leib type equality as new Leibs representing + those type equalities.

+

First, let's solve the original problem, using infix type application + to show the similarity to =:=:

+
def sum2(implicit ev: A Leib Int): Int =
+  ev.subst[List](xs).foldLeft(0)(_ + _)
+

There is no more implicit conversion, the result of subst is the same + object as the argument, and [List] would be inferred, but I have + merely specified it for clarity in this example.

+

This doesn't compose, though. What if, having substed Int into + that List type, I now want to subst List[A] for List[Int] in + some type function? Specifically, what about a Leib that represents + that type equality? To handle that, we can subst into Leib itself!

+
def lift[F[_], A, B](ab: Leib[A, B]): Leib[F[A], F[B]] =
+  ab.subst[Lambda[X => Leib[F[A], F[X]]]](Leib.refl[F[A]])
+

Again, the final [F[A]] could be inferred.

+

As an exercise, define the symm and compose operations, which + represent that Leib is symmetric and transitive as well. Hints: the + symm body is the same except for the type parameters given, and + compose doesn't use refl.

+
def symm[A, B](ab: Leib[A, B]): Leib[B, A]
+def compose[A, B, C](ab: Leib[A, B], bc: Leib[B, C]): Leib[A, C]
+ +

Leib power

+

In Scalaz, Leibniz is already defined, and + used in a few places. + Though their subst definitions are completely incompatible at the + scalac level, they have a weird equivalence due to the awesome power + of subst.

+
import scalaz.Leibniz, Leibniz.===
+
+def toScalaz[A, B](ab: A Leib B): A === B =
+  ab.subst[A === ?](Leibniz.refl)
+
+def toLeib[A, B](ab: A === B): A Leib B =
+  ab.subst[A Leib ?](Leib.refl)
+

…where ? is to type-lambdas as _ is to Scala lambdas, thanks to + the Kind Projector plugin.

+

And so it would be with any pair of Leibniz representations with such + subst methods that you might define. Unfortunately, =:= cannot + participate in this universe of isomorphisms; it lacks the subst + method that serves as the Leibniz certificate of authenticity. You can + get a =:= from a Leibniz, but not vice versa.

+

Why would you want that weak sauce anyway?

+ +

Looking up

+

These are just the basics. Above:

+
    +
  • The weakness of Scala's own =:=,
  • +
  • the sole primitive Leibniz operator subst,
  • +
  • how to logically derive other type equalities,
  • +
  • the isomorphism between each Leibniz representation and all + others.
  • +
+

In the next part, we'll + look at:

+
    +
  • Why it matters that subst always executes to use a type equality,
  • +
  • the Haskell implementation,
  • +
  • higher-kinded type equalities and their Leibnizes,
  • +
  • why + the =:= singleton trick + is unsafe,
  • +
  • simulating GADTs with Leibniz members of data constructors.
  • +
+

This article was tested with Scala 2.11.1, Scalaz 7.0.6, and Kind + Projector 0.5.2.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/type-members-parameters.html b/blog/type-members-parameters.html new file mode 100644 index 00000000..91b3eb7a --- /dev/null +++ b/blog/type-members-parameters.html @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + + + + + Type members are almost type parameters + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Type members are almost type parameters

+ + + technical + +
+
+
+
+

Type members are almost type parameters

+

This is the first of a series of articles on “Type Parameters and Type + Members”.

+

Type members like Member

+
class Blah {
+  type Member
+}
+

and parameters like Param

+
class Blah2[Param]
+

have more similarities than differences. The choice of which to use + for a given situation is usually a matter of convenience. In brief, a + rule of thumb: a type parameter is usually more convenient and + harder to screw up, but if you intend to use it existentially in most + cases, changing it to a member is probably better.

+

Here, and in later posts, we will discuss what on earth that means, + among other things. In this series of articles on Type Parameters + and Type Members, I want to tackle a variety of Scala types that look + very different, but are really talking about the same thing, or + almost.

+ +

Two lists, all alike

+

To illustrate, let’s see two versions of + the functional list. + Typically, it isn’t used existentially, so the usual choice of + parameter over member fits our rule of thumb above. It’s instructive + anyway, so let’s see it.

+
sealed abstract class PList[T]
+final case class PNil[T]() extends PList[T]
+final case class PCons[T](head: T, tail: PList[T]) extends PList[T]
+
+sealed abstract class MList {self =>
+  type T
+  def uncons: Option[MCons {type T = self.T}]
+}
+sealed abstract class MNil extends MList {
+  def uncons = None
+}
+sealed abstract class MCons extends MList {self =>
+  val head: T
+  val tail: MList {type T = self.T}
+  def uncons = Some(self: MCons {type T = self.T})
+}
+

We’re not quite done; we’re missing a way to make MNils and + MConses, which PNil and PCons have already provided + for themselves, by virtue of being case classes. But it’s already + pretty clear that a type parameter is a more straightforward way to + define this particular data type.

+

The instance creation takes just a bit more scaffolding for our + examples:

+
def MNil[T0](): MNil {type T = T0} =
+  new MNil {
+    type T = T0
+  }
+
+def MCons[T0](hd: T0, tl: MList {type T = T0})
+  : MCons {type T = T0} =
+  new MCons {
+    type T = T0
+    val head = hd
+    val tail = tl
+  }
+ +

Why all the {type T = ...}?

+

After all, isn’t the virtue of type members that we don’t have to pass + the type around everywhere?

+

Let’s see what happens when we attempt to apply that theory. Suppose + we remove only one of the + refinements + above, as these {...} rainclouds at the type level are called. + Let’s remove the one in val tail, so class MCons looks like this:

+
sealed abstract class MCons extends MList {self =>
+  val head: T
+  val tail: MList
+}
+

Now let us put a couple members into the list, and add them together.

+
scala> val nums = MCons(2, MCons(3, MNil())): MCons{type T = Int}
+nums: tmtp.MCons{type T = Int} = tmtp.MList$$anon$2@3c649f69
+
+scala> nums.head
+res1: nums.T = 2
+
+scala> res1 + res1
+res2: Int = 4
+
+scala> nums.tail.uncons.map(_.head)
+res3: Option[nums.tail.T] = Some(3)
+
+scala> res3.map(_ - res2)
+<console>:21: error: value - is not a member of nums.tail.T
+       res3.map(_ - res2)
+                  ^
+

When we took the refinement off of tail, we eliminated any evidence + about what its type T might be. We only know that it must be some + type. That’s what existential means.

+

In terms of type parameters, MList is like PList[_], and MList +{type T = Int} is like PList[Int]. For the former, we say that + the member, or parameter, is existential.

+ +

When is existential OK?

+

Despite the limitation implied by the error above, there are useful + functions that can be written on the existential version. Here’s one + of the simplest:

+
def mlength(xs: MList): Int =
+  xs.uncons match {
+    case None => 0
+    case Some(c) => 1 + mlength(c.tail)
+  }
+

For the type parameter equivalent, the parameter on the argument is + usually carried out or lifted to the function, like so:

+
def plengthT[T](xs: PList[T]): Int =
+  xs match {
+    case PNil() => 0
+    case PCons(_, t) => 1 + plengthT(t)
+  }
+

By the conversion rules above, though, we should be able to write an + existential equivalent of mlength for PList, and indeed we can:

+
def plengthE(xs: PList[_]): Int =
+  xs match {
+    case PNil() => 0
+    case PCons(_, t) => 1 + plengthE(t)
+  }
+

There’s another simple rule we can follow when determining whether we + can rewrite in an existential manner.

+
    +
  1. When a type parameter appears only in one argument, and
  2. +
  3. appears nowhere in the result type,
  4. +
+

we should always, ideally, be able to write the function in an + existential manner. (We will discuss why it’s only “ideally” in + the next article.)

+

You can demonstrate this to yourself by having the parameterized + variant (e.g. plengthT) call the existential variant + (e.g. plengthE), and, voilà, it compiles, so it must be right.

+

This hints at what is usually, though not always, an advantage for + type parameters: you have to ask for an existential, rather than + silently getting one just because you forgot a refinement. We will + discuss + what happens when you forget one in a later post.

+ +

Equivalence as a learning tool

+

Scala is large enough that very few understand all of it. Moreover, + there are many aspects of it that are poorly understood in general.

+

So why focus on how different features are similar? When we + understand one area of Scala well, but another one poorly, we can form + sensible ideas about the latter by drawing analogies with the former. + This is how we solve problems with computers in general: we create an + informal model in our heads, which we translate to a + mathematical statement that a program can interpret, and it gives back + a result that we can translate back to our informal model.

+

My guess is that type parameters are much better understood than type + members, but that existentials via type members are better understood + than existentials introduced by _ or forSome, though I’d wager + that neither form of existential is particularly well understood.

+

By knowing about equivalences and being able to discover more, you + have a powerful tool for understanding unfamiliar aspects of Scala: + just translate the problem back to what you know and think about what + it means there, because the conclusion will still hold when you + translate it forward. (Category theorists, eat your hearts out.)

+

In this vein, we will next generalize the above rule about existential + methods, discovering a simple tool for determining whether two + method types in general are equivalent, whereby things you know + about one easily carry over to the other. We will also explore + methods that cannot be written in the existential style, at least + under Scala’s restrictions.

+

That all happens in + the next part, “When are two methods alike?”.

+

This article was tested with Scala 2.11.7.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/type-projection.html b/blog/type-projection.html new file mode 100644 index 00000000..e09912d1 --- /dev/null +++ b/blog/type-projection.html @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + Type projection isn't that specific + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Type projection isn't that specific

+ + + technical + +
+
+
+
+

Type projection isn't that specific

+

This is the fourth of a series of articles on “Type Parameters and + Type Members”. If you haven’t yet, you should + start at the beginning, + which introduces code we refer to throughout this article without + further ado.

+

In the absence of the Aux trick presented at the end of + the previous article, + the continuous use of structural refinement to accomplish basic tasks + admittedly imposes a high cognitive load. That is to say, it’s a lot + of work to say something that ought to be very simple.

+

Some people go looking for a solution, and find something that almost + seems to make sense: + type projection, + or MList#T in terms of + our ongoing example. + But type projection is, in almost all cases, too vague to really + solve problems you have using type members.

+ +

A good reason to use type members

+

Let’s see a simple example. Here’s a sort of “value emitter”, that + operates in the space of some state, emitting a new value with each + step.

+
sealed abstract class StSource[A] {
+  type S
+  def init: S            // create the initial state
+  def emit(s: S): (A, S) // emit a value, and update state
+}
+
+object StSource {
+  type Aux[A, S0] = StSource[A] {type S = S0}
+
+  def apply[A, S0](i: S0)(f: S0 => (A, S0)): Aux[A, S0] =
+    new StSource[A] {
+      type S = S0
+      def init = i
+      def emit(s: S0) = f(s)
+    }
+}
+

Unlike MList, there are actually good reasons to use type members + for the “state” in this sort of type definition; i.e. there are + reasonable designs in which you want to use member S existentially. + Thus, depending on how we intend to use it, it seems to meet our first + rule of thumb about when to use type members, as described in + the first article of this series.

+ +

A failed attempt at simplified emitting

+

So, under this theory, you’ve got some values of type StSource[A] + lying around. And you want a simple function to take a source and its + state, and return the “next” value and the new state.

+
def runStSource[A](ss: StSource[A], s: ??): (A, ??) = ss.emit(s)
+

But what do you put where the ?? is? The surprising guess is often + StSource[A]#S. After all, it means “the StSource’s S”, and + we’re trying to talk about an StSource’s S, right?

+
def runStSource[A](ss: StSource[A], s: StSource[A]#S)
+  : (A, StSource[A]#S) = ss.emit(s)
+
+TmTp4.scala:22: type mismatch;
+ found   : s.type (with underlying type tmtp4.StSource[A]#S)
+ required: ss.S
+  : (A, StSource[A]#S) = ss.emit(s)
+                                 ^
+

Setting aside that it won’t compile with the above signature—the usual + outcome of experiments with type projection, that the types aren’t + strong enough to be workable without cheating by casting—the reality + sounds so close to the above that it is understandable that type + projection is often confused with something useful.

+

There are uses for type projection. But they are so rare, so + exotic (they look + like this), + and even the legitimate ones better off rewritten to avoid them, + that the safer assumption is that you’ve gone down the wrong path if + you’re trying to use them at all. My suggestion can usually be + phrased something like “move it to a companion object”.

+

In reality, StSource[A]#S means some StSource’s S. Not the + one you gave, just any particular one. It’s the supertype of all + possible S choices. So, the failure of the above signature is like + the failure of mdropFirstE from + the second post of this series: + a failure to relate types strongly enough. The problem with + mdropFirstE was failure to relate the result type to argument type, + whereas the problem with runStSource is to fail to relate the two + arguments’ types to each other.

+ +

Type parameters see existentially

+

As with mdropFirstE, one correct solution here is, again, lifting the + member to a method type parameter.

+
def runStSource[A, S](ss: StSource.Aux[A, S], s: S): (A, S) = ss.emit(s)
+

The surprising feature of this sort of signature is that it can be + invoked on ss arguments of type StSource[A].

+
scala> val ss: StSource[Int] = StSource(0){i: Int => (i, i)}
+ss: tmtp4.StSource[Int] = tmtp4.StSource$$anon$1@300b5011
+
+scala> runStSource(ss, ss.init)
+res0: (Int, ss.S) = (0,0)
+

In other words, methods can assign names to unspecified, existential + type members. So even though we have a value whose type doesn’t + refine S, Scala still infers this type as the S argument to pass + to runStSource.

+

By analogy with type parameters, though, this isn’t too surprising. + We’ve already seen + that copyToZeroE inferred its argument’s existential parameter to + pass along to the named parameter to copyToZeroP, in the second part + of this series. We even saw it apply directly to type members when + mdropFirstE was able to invoke mdropFirstT. However, for whatever + reason, we’re used to existential parameters being able to do this; + even Java manages the task. But it just seems odder that merely + calling a method can create a whole refinement {...} raincloud, from + scratch, filling in the blanks with sensible types along the way.

+

It’s completely sound, though. An StSource (that exists as a value) + must have an S, even if we existentialized it away. So, as with + _s, let’s just give it a name to pass as the inferred type + parameter. It makes a whole lot more sense than supposing + StSource[A]#S will just do what I mean.

+

In a future post, we’ll use this “infer the whole refinement” feature + to demonstrate that some of the most magical-seeming Scala type system + features aren’t really so magical. But before we get to that, we need + to see just why existentials are anything but “wildcards”, and why it + doesn’t always make sense to be able to lift existentials like S + to type parameters. That’s coming in + the next post, “Nested existentials”.

+

This article was tested with Scala 2.11.7.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typedapi.html b/blog/typedapi.html new file mode 100644 index 00000000..dd0b72a0 --- /dev/null +++ b/blog/typedapi.html @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + Typedapi or how to derive your clients and servers from types + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typedapi or how to derive your clients and servers from types

+ + + technical + +
+
+
+
+

Typedapi or how to derive your clients and servers from types

+

In this blog post, I will show you how to leverage Scala's type system to derive an HTTP client function from a single type. This will also be the story of how I started to work on Typedapi which is basically the attempt to bring Haskell's Servant to Scala.

+ +

Servant in a nutshell and how it began

+

For everyone not knowing Servant, it is a library which lets you define your web apis as types and derives the client and server functions from it. When I saw it for the first time while working on a pet project I immediately loved the idea. Creating web server and clients this way reduces your code to a mere type, you get extra type safety and you can use the api types as contracts between your server and its clients.

+

I couldn't find any viable alternative in Scala at the time and decided to build it on my own. But I just wanted to start with a single feature to not overwhelm myself and abandon the project after a short time. Therefore, I set out to make Scala able to derive a client function from a single api type, as we will do in this post.

+ +

Derive a client function from a type. How hard can it be?

+

Let's start with an example we will use later on to ease understanding. Consider the following api:

+
GET /users/:name?minAge=:age -> List[User]
+

It only consists of a single endpoint which returns a list of Users:

+
final case class User(name: String, age: Int)
+

with a given name: String. Furthermore, you filter the resulting users by their age: Int. Our big goal is to end up with a function which is derived from a type-level representation of our endpoint:

+
(name: String, minAge: Int) => F[List[User]]
+ +

Represent the api as a type

+

Question: how do you represent the above api as a type in Scala? I think the best way is to break it apart and try to find type-level representations for each element. After that, we "just" merge them together.

+

When we take a closer look at our endpoint we see that it consists of: + * a method GET to identify which kind of operation we want to do and which also describes the expected return type + * constant path elements identifying an endpoint: /users + * dynamic path elements called "segments" which represent input parameters with a name and type: :name + * queries which again represent input parameters with a name and type: minAge=[age]

+

Or in other words, just a plain HTTP definition of a web endpoint. Now that we know what we are working with let's try and find a type-level representation.

+

But how do you transform a value-level information as a type? First of all, the value has to be known at compile time which leaves us with literals. If we would work with Dotty we could leverage a concept called literal type:

+
type Path = "users"
+

But since we want to stay in Vanilla Scala this will not work. We have to take another route by using a tool probably every developer has to use when it comes to working on the type-level called shapeless. It has this nifty class Witness which comes with an abstract type T. And T is exactly what we need here as it transforms our literals into types.

+
import shapeless.Witness
+
+val usersW = Witness("users")
+

But this isn't a pure type declaration, you will say. And you are right, but right now there is no other way in Scala. We have to go the ordinary value road first to create our types.

+

Now that we know how to get a type representation from a String which describes our path we should clearly mark it as a path element:

+
sealed trait Path[P]
+
+type users = Path[usersW.T]
+

That's it. That is the basic concept of how we can describe our apis as types. We just reuse this concept now for the remaining elements like the segment.

+
val nameW = Witness('name)
+
+sealed trait Segment[K, V]
+
+type name = Segment[nameW.T, String]
+

Do you see how we included the segment's identifier in the type? This way we are not only gain information about the expected type but also what kind of value we want to see. By the way, I decided to use Symbols as identifiers, but you could also switch to String literals. The remaining definitions look pretty similar:

+
val minAgeW = Witness('minAge)
+
+sealed trait Query[K, V]
+
+type minAge = Query[minAgeW.T, Int]
+
+sealed trait Method
+sealed trait Get[A] extends Method
+

Here, A in Get[A] represents the expected result type of our api endpoint.

+

Now that we know how to obtain the types of our api elements we have to put them together into a single type representation. After looking through shapeless's features we will find HLists, a list structure which can store elements of different types.

+
import shapeless.{::, HNil}
+
+type Api = Get[List[User]] :: users :: name :: minAge :: HNil
+

Here you go. Api is an exact representation of the endpoint we defined at the beginning. But you don't want to write Witness and HLists all the time so let's wrap it up into a convenient function call:

+
def api[M <: Method, P <: HList, Q <: HList, Api <: HList]
+       (method: M, path: PathList[P], queries: QueryList[Q])
+       (implicit prepQP: Prepend.Aux[Q, P, Api]): ApiTypeCarrier[M :: Api] = ApiTypeCarrier()
+      
+val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge)))
+

Not clear what is happening? Let's take a look at the different elements of def api(...): + * method should be obvious. It takes some method type. + * PathList is a type carrier with a function def /(...) to concatenate path elements and segments. In the end, PathList only stores the type of an HList and nothing more.

+
final case class PathList[P <: HList]() {
+  
+  def /[S](path: Witness.Lt[S]): PathList[S :: P] = PathList()
+  ...
+}
+
+val Root = PathList[HNil]()
+
    +
  • Same is true for QueryList.
  • +
+
    +
  • The last step is to merge all these HLists types into a single one. Shapeless comes again with a handy type class called Prepend which provides us with the necessary functionality. Two HList types go in, a single type comes out. And again, we use a type carrier here to store the api type.
  • +
+

Whoho, we did it. One thing we can mark as done on our todo list. Next step is to derive an actual client function from it.

+ +

Clients from types

+

So far we have a type carrier describing our api as type:

+
ApiTypeCarrier[Get[List[User]] :: Query[minAgeW.T, Int] :: Segment[nameW.T, String] :: usersW.T :: HNil]
+

Now we want to transform that into a function call (name: String, minAge: Int) => F[List[User]]. So what we need is the following: + * the types of our expected input + * the output type + * the path to the endpoint we want to call

+

All information are available but mixed up and we need to separate them. Usually, when we work with collections and want to change their shape we do a fold and alas shapeless has type classes to fold left and right over an HList. But we only have a type. How do we fold that?

+ +

Type-level FoldLeft

+

What we want is to go from Api <: HList to (El <: HList, KIn <: HList, VIn <: HList, M, Out) with: + * El al the elements in our api: "users".type :: SegmentInput :: QueryInput :: GetCall :: HNil + * KIn the input key types: nameW.T :: minAgeW.T :: HNil + * VIn the input value types: String :: Int :: HNil + * the method type: GetCall + * and Out: List[User]

+

Here, we introduced new types SegmentInput and QueryInput which act as placeholders and indicate that our api has the following inputs. This representation will come in handy when we construct our function.

+

Now, how to fold on the type-level? The first step, we have to define a function which describes how to aggregate two types:

+
trait FoldLeftFunction[In, Agg] { type Out }
+

That's it. We say what goes in and what comes out. You need some examples to get a better idea? Here you go:

+
implicit def pathTransformer[P, El <: HList, KIn <: HList, VIn <: HList, M, Out] = 
+  FoldLeftFunction[Path[P], (El, KIn, VIn, M, Out)] { type Out = (P :: El, KIn, VIn, Out) }
+

We expect a Path[P] and intermediate aggregation state (El, KIn, VIn, M, Out). We merge the two by adding P to our list of api elements. The same technique is also used for more involved aggregations:

+
implicit def segmentTransformer[K <: Symbol, V, El <: HList, KIn <: HList, VIn <: HList, M, Out] = 
+  FoldLeftFunction[Segment[K, V], (El, KIn, VIn, M, Out)] { type Out = (SegmentInput :: El, K :: KIn, V :: VIn, Out) }
+

Here, we get some Segment with a name K and a type V and an intermediate aggregation state we will update by adding a placeholder to El, the name to KIn and the value type to VIn.

+

Now that we can aggregate types we need a vehicle to traverse our HList type and transform it on the fly by using our FoldLeftFunction instances. I think yet another type class can help us here.

+
trait TypeLevelFoldLeft[H <: HList, Agg] { type Out }
+
+object TypeLevelFoldLeft {
+
+  implicit def returnCase[Agg] = new TypeLevelFoldLeft[HNil, Agg] {
+    type Out = Agg
+  }
+
+  implicit def foldCase[H, T <: HList, Agg, FfOut, FOut](implicit f: FoldLeftFunction.Aux[H, Agg, FfOut], 
+                                                                  next: Lazy[TypeLevelFoldLeft.Aux[T, FfOut, FOut]]) = 
+    new TypeLevelFoldLeft[H :: T, Agg] { type Out = FOut }
+}
+

The above definition describes a recursive function which will apply the FoldLeftFunction on H and the current aggregated type Agg and continues with the resulting FfOut and the remaining list. And before you bang your head against the wall for hours until the clock strikes 3 am, like I did, a small hint, make next lazy. Otherwise, Scala is not able to find next. My guess is that Scala is not able to infer next, because it depends on FfOut which is also unknown. So we have to defer next's inference to give the compiler some time to work.

+

And another hint, you can start with Unit as the initial type for your aggregate.

+ +

Collect all the request data

+

We folded our api type into the new representation making it easier now to derive a function which collects all the data necessary to make a request.

+
// path to our endpoint described by Path and Segment
+type Uri = List[String]
+
+// queries described by Query
+type Queries = Map[String, List[String]]
+
+VIn => (Uri, Queries)
+

This function will form the basis of our client function we try to build. It generates the Uri and a Map of Queries which will be used later on to do a request using some HTTP library.

+

By now, you should be already comfortable with type classes. Therefore, it shouldn't shock you that I will introduce yet another one to derive the above function.

+
trait RequestDataBuilder[El <: HList, KIn <: HList, VIn <: HList] {
+
+  def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries)
+}
+

Instances of this type class update uri and queries depending on the types they see. For example, if the current head of El is a path element we prepend its String literal to uri. Just keep in mind to reverse the List before returning it.

+
implicit def pathBuilder[P, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[P], next: RequestDataBuilder[T, KIn, VIn]) = 
+  new RequestDataBuilder[P :: T, KIn, VIn] {
+    def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries) =
+      next(inputs, wit.value.toString() :: uri, queries, headers)
+  }
+

Or if we encounter a query input we derive the key's type-literal, pair it with the given input value and add both to queries:

+
implicit def queryBuilder[K <: Symbol, V, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[K], next: RequestDataBuilder[T, KIn, VIn]) = 
+  new RequestDataBuilder[QueryInput :: T, K :: KIn, V :: VIn] {
+    def apply(inputs: V :: VIn, uri: Uri, queries: Queries): (Uri, Queries) =
+      next(inputs.tail, uri, Map(wit.value.name -> List(inputs.head.toString())) ++ queries)
+  }
+

The other cases are looking quite similar and it is up to the interested reader to find the implementations.

+

What we end up with is a nested function call structure which will take an HList and returns the uri and queries.

+
val builder = implicitly[RequestDataBuilder[El, KIn, VIn]]
+
+val f: VIn => (Uri, Queries) = input => builder(input, Nil, Map.empty)
+
+"joe" :: 42 :: HNil => (List("users", "joe"), Map("minAge" -> List("42")))
+

Here, "joe" and 42 are our expected inputs (VIn) which we derived from the segments and queries of our Api.

+ +

Make the request

+

We have all the data we need to make an IO request but nothing to execute it. We change that now. By adding an HTTP backend. But we don't want to expose this implementation detail through our code. What we want is a generic description of a request action and that sounds again like a job for type classes.

+
trait ApiRequest[M, F[_], C, Out] {
+
+  def apply(data: (Uri, Queries), client: C): F[Out]
+}
+

We have to specialize that for the set of methods we have:

+
trait GetRequest[C, F[_], Out] extends ApiRequest[GetCall, C, F, Out]
+
+...
+
+val request = implicitly[ApiRequest[GetCall, IO, C, List[User]]]
+
+val f: VIn => IO[List[User]] = 
+  input => request(builder(input, Nil, Map.empty), c)
+

Let's say we want http4s as our backend. Then we just have to implement these traits using http4s functionality.

+ +

Make it a whole

+

We have a bunch of type classes which in theory do a request, but so far they are completely useless. To make a working piece of code out of it we have to connect them.

+
def derive[Api <: HList, El <: HList, KIn <: HList, VIn <: HList, M, Out, F[_], C]
+  (api: ApiTypeCarrier[Api], client: C)
+  (implicit fold: Lazy[TypeLevelFoldLeft.Aux[Api, Fold], (El, KIn, VIn, M, Out)]
+            builder: RequestBuilder[El, KIn, VIn],
+            request: ApiRequest[M, F, C, Out]): VIn => F[Out] = vin => request(builder.apply(vin, List.newBuilder, Map.empty), client)
+

The first approach gives us the desired function. It transforms our api type into a (El, KIn, VIn, Method, Out) representation, derives a function to collect all data to do a request, and finds an IO backend to actually do the request. But it has a major drawback. You have to fix F[_] somehow and the only way is to set it explicitly. But by doing that you are forced to provide definitions for all the type parameters. Furthermore, this function isn't really convenient. To use it you have to create and pass an HList and as we said before, we don't want to expose something like that.

+

To fix the first problem we simply add a helper class which moves the step of defining the higher kind F[_] to a separate function call:

+
final class ExecutableDerivation[El <: HList, KIn <: HList, VIn <: HList, M, O](builder: RequestDataBuilder[El, KIn, VIn], input: VIn) {
+
+  final class Derivation[F[_]] {
+
+    def apply[C](client: C)(implicit req: ApiRequest[M, C, F, O]): F[O] = {
+      val data = builder(input, List.newBuilder, Map.empty, Map.empty)
+
+      req(data, cm)
+    }
+  }
+
+  def run[F[_]]: Derivation[F] = new Derivation[F]
+}
+

Making a function of arity Length[VIn] out of Vin => F[O]is possible by using shapeless.ops.function.FnFromProduct.

+

When we apply both solutions we end up with:

+
def derive[H <: HList, Fold, El <: HList, KIn <: HList, VIn <: HList, M, Out]
+  (apiList: ApiTypeCarrier[H])
+  (implicit fold: Lazy[TypeLevelFoldLeft.Aux[H, Unit, (El, KIn, VIn, M, Out)]],
+            builder: RequestDataBuilder[El, KIn, VIn],
+            vinToFn: FnFromProduct[VIn => ExecutableDerivation[El, KIn, VIn, M, Out]]): vinToFn.Out = 
+  vinToFn.apply(input => new ExecutableDerivation[El, KIn, VIn, M, Out](builder, input))
+

I already hear the "your function signature is so big ..." jokes incoming, but this is basically what we will (and want to) end up with when doing type-level programming. In the end, our types have to express the logic of our program and that needs some space.

+

But finally, we can say we did it! We convinced the Scala compiler to derive a client function from a type. Let's have a look at our example to see how it works.

+
import cats.effect.IO
+import org.http4s.client.Client
+
+val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge)))
+val get = derive(Api)
+
+get("joe", 42).run[IO](Client[IO]) // IO[List[User]]
+ +

Conclusion

+

When you take a closer look at the code above you will see that we were able to move most of the heavy lifting to the compiler or shapeless therefore reducing our code to a relatively small set of "simple" type classes. And when literal types are in thing in Scala we can also remove most of the boilerplate necessary to create our api types.

+

This, again, shows me how powerful Scalas type system is and how much you can gain when you embrace it.

+ +

Next Step - Typedapi

+

Now that we are able to derive a single client function from a type we should also be able to do the same for a collection of api types. And if we are already on it, let's add server-side support. Or ... you just use Typedapi. It already comes with the following features: + * client function derivation + * server function derivation + * single and multi api type handling + * support for htt4s + * support for akka-http in the making + * simple interface to add more HTTP frameworks/libraries

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Paul Heymann + + +
+ Paul entered the realm of functional and type-level programming three years ago when he was caught by a Scala meetup. After that, he started doing Scala professionally as a Data Engineer for the social network XING. There he works on recommender systems and the ontology infrastructure which are serving requests of millions of users every day. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-boulder.html b/blog/typelevel-boulder.html new file mode 100644 index 00000000..32ae817e --- /dev/null +++ b/blog/typelevel-boulder.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + The Typelevel Summit in Boulder is Cancelled + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

The Typelevel Summit in Boulder is Cancelled

+ + + summits + +
+
+
+
+

The Typelevel Summit in Boulder is Cancelled

+

As a result of + LambdaConf's decision + to invite Curtis Yarvin as a speaker, the organizers of the Typelevel + Summit Boulder have decided that affiliation with LambdaConf is no + longer compatible with Typelevel's goals, and we are cancelling the + event, which was scheduled to happen on the Wednesday before the + conference.

+

Yarvin is an unapologetic proponent of bigotry. As a result of his + modest celebrity in this regard, it is not possible for his views to + be "left at the door." By extending a speaking invitation, LambdaConf + places him in a position of prestige and tacit endorsement that + Typelevel cannot accept.

+

We recognize LambdaConf's goal of "harmony in diversity" and applaud + them for sharing their deliberations, but respectfully disagree with + the outcome.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-native.html b/blog/typelevel-native.html new file mode 100644 index 00000000..12c9aa8d --- /dev/null +++ b/blog/typelevel-native.html @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Native + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Native

+ + + technical + +
+
+
+
+

Typelevel Native

+

We recently published several major Typelevel projects for the Scala Native platform, most notably Cats Effect, FS2, and http4s. This blog post explores what this new platform means for the Typelevel ecosystem as well as how it works under-the-hood.

+ +

What is Scala Native?

+

Scala Native is an optimizing ahead-of-time compiler for the Scala language. Put simply: it enables you to compile Scala code directly to native executables.

+

It is an ambitious project following in the steps of Scala.js. Instead of targeting JavaScript, the Scala Native compiler targets the LLVM IR and uses its toolchain to generate native executables for a range of architectures, including x86, ARM, and in the near future web assembly.

+ +

Why is this exciting?

+

For Scala in general, funnily enough I think GraalVM Native Image does a great job summarizing the advantages of native executables, namely:

+
    +
  • instant startup that immediately achieves peak performance, without requiring warmup or the heavy footprint of the JVM
  • +
  • packagable into small, self-contained binaries for easy deployment and distribution
  • +
+

It is worth mentioning that in benchmarks Scala Native handily beats GraalVM Native Image on startup time, runtime footprint, and binary size.

+

Moreover, breaking free from the JVM is an opportunity to design a runtime specifically optimized for the Scala language itself. This is the true potential of the Scala Native project.

+

For Typelevel in particular, Scala Native opens new doors for leveling up our ecosystem. Our flagship libraries are largely designed for deploying high performance I/O-bounded microservices and for the first time ever we now have direct access to kernel I/O APIs.

+

I am also enthusiastic to use Cats Effect with (non-Scala) native libraries that expose a C API. Resource and more generally MonadCancel are powerful tools for safely navigating manual memory management with all the goodness of error-handling and cancelation.

+ +

How can I try it?

+

Christopher Davenport has put up a scala-native-ember-example and reported some benchmark results!

+ +

How does it work?

+

The burden of cross-building the Typelevel ecosystem for Scala Native fell almost entirely to Cats Effect and FS2.

+ +

Event loop runtime

+

To cross-build Cats Effect for Native we had to get creative because Scala Native currently does not support multithreading (although it will in the next major release). This is a similar situation to the JavaScript runtime, which is also fundamentally single-threaded. But an important difference is that JS runtimes are implemented with an event loop and offer callback-based APIs for scheduling timers and performing non-blocking I/O. An event loop is a type of runtime that enables compute tasks, timers, and non-blocking I/O to be interleaved on a single thread (although not every event loop does all these things).

+

Meanwhile, Scala Native core does not implement an event loop nor offer such APIs. There is the scala-native-loop project, which wraps the libuv event loop runtime, but we did not want to bake such an opinionated dependency into Cats Effect core.

+

Fortunately Daniel Spiewak had the fantastic insight that the “dummy runtime” which I created to initially cross-build Cats Effect for Native could be reformulated into a legitimate event loop implementation by extending it with the capability to “poll” for I/O events: a PollingExecutorScheduler.

+

The PollingExecutorScheduler implements both ExecutionContext and Scheduler and maintains two queues:

+
    +
  • a queue of tasks (read: fibers) to execute
  • +
  • a priority queue of timers (read: IO.sleep(...)), sorted by expiration
  • +
+

It also defines an abstract method:

+
def poll(timeout: Duration): Boolean
+

The idea of this method is very similar to Thread.sleep() except that besides sleeping it may also “poll” for I/O events. It turns out that APIs like this are ubiquitous in C libraries that perform I/O.

+

To demonstrate the API contract, consider invoking poll(3.seconds):

+

I have nothing to do for the next 3 seconds. So wake me up then, or earlier if there is an incoming I/O event that I should handle. But wake me up no later!

+

Oh, and don’t forget to tell me whether there are still outstanding I/O events (true) or not (false) so I know if I need to call you again. Thanks!

+

With tasks, timers, and the capability to poll for I/O, we can express the event loop algorithm. A single iteration of the loop looks like this:

+
    +
  1. +

    Check the current time and execute any expired timers.

    +
  2. +
  3. +

    Execute up to 64 tasks, or until there are none left. We limit to 64 to ensure we are fair to timers and I/O.

    +
  4. +
  5. +

    Poll for I/O events. There are three cases to consider:

    +
      +
    • There is at least one task to do. Call poll(0.nanos), so it will process any available I/O events and then immediately return control.
    • +
    • There is at least one outstanding timer. Call poll(durationToNextTimer), so it will sleep until the next I/O event arrives or the timeout expires, whichever comes first.
    • +
    • There are no tasks to do and no outstanding timers. Call poll(Duration.Infinite), so it will sleep until the next I/O event arrives.
    • +
    +
  6. +
+

This algorithm is not a Cats Effect original: the libuv event loop works in essentially the same way. It is however a first step toward the much grander Cats Effect I/O Integrated Runtime Concept. The big idea is that every WorkerThread in the WorkStealingThreadPool that underpins the Cats Effect JVM runtime can run an event loop exactly like the one described above, for exceptionally high-performance I/O.

+ +

Non-blocking I/O

+

So, how do we implement poll? The bad news is that the answer is OS-specific, which is a large reason why projects such as libuv exist. Furthermore, the entire purpose of polling is to support non-blocking I/O, which falls outside of the scope of Cats Effect. This brings us to FS2, and specifically the fs2-io module where we want to implement non-blocking TCP Sockets.

+

One such polling API is epoll, available only on Linux:

+
#include <sys/epoll.h>
+
+int epoll_create1(int flags);
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
+
+int epoll_wait(int epfd, struct epoll_event *events,
+                      int maxevents, int timeout);
+

After creating an epoll instance (identified by a file descriptor) we can register sockets (also identified by file descriptors) with epoll_ctl. Typically we will register to be notified of the “read-ready” (EPOLLIN) and “write-ready” (EPOLLOUT) events on that socket. Finally, the actual polling is implemented with epoll_wait, which sleeps until the next I/O event is ready or the timeout expires. Thus we can use it to implement a PollingExecutorScheduler.

+

As previously mentioned, these sorts of polling APIs are ubiquitous and not just for working directly with sockets. For example, libcurl (the C library behind the well-known CLI) exposes a function for polling for I/O on all ongoing HTTP requests.

+
#include <curl/curl.h>
+
+CURLMcode curl_multi_poll(CURLM *multi_handle,
+                          struct curl_waitfd extra_fds[],
+                          unsigned int extra_nfds,
+                          int timeout_ms,
+                          int *numfds);
+

Indeed, this function underpins the CurlExecutorScheduler in http4s-curl.

+

On macOS and BSDs the kqueue API plays an analogous role to epoll. We will not talk about Windows today :)

+

Long story short, I did not want the FS2 codebase to absorb all of this cross-OS complexity. So in collaboration with Lee Tibbert we repurposed my cheeky epollcat experiment into an actual library implementing JDK NIO APIs (specifically, AsynchronousSocketChannel and friends). Since these are the same APIs used by the JVM implementation of fs2-io, it actually enables the Socket code to be completely shared with Native.

+

epollcat implements an EpollExecutorScheduler for Linux and a KqueueExecutorScheduler for macOS. They additionally provide an API for monitoring a socket file descriptor for read-ready and write-ready events.

+
def monitor(fd: Int, reads: Boolean, writes: Boolean)(
+  cb: EventNotificationCallback
+): Runnable // returns a `Runnable` to un-monitor the file descriptor
+
+trait EventNotificationCallback {
+  def notifyEvents(readReady: Boolean, writeReady: Boolean): Unit
+}
+

These are then used to implement the callback-based read and write methods of the JDK AsynchronousSocketChannel.

+

It is worth pointing out that the JVM actually implements AsynchronousSocketChannel with an event loop as well. The difference is that on the JVM, this event loop is used only for I/O and runs on a separate thread from the compute pool used for fibers and the scheduler thread used for timers. Meanwhile, epollcat is an example of an I/O integrated runtime where fibers, timers, and I/O are all interleaved on a single thread.

+ +

TLS

+

The last critical piece of the cross-build puzzle was a TLS implementation for TLSSocket and related APIs in FS2. Although the prospect of this was daunting, in the end it was actually fairly straightforward to directly integrate with s2n-tls, which exposes a well-designed and well-documented C API. This is effectively the only non-Scala dependency required to use the Typelevel stack on Native.

+

Finally, special thanks to Ondra Pelech and Lorenzo Gabriele for cross-building scala-java-time and scala-java-locales for Native and David Strawn for developing idna4s. These projects fill important gaps in the Scala Native re-implementation of the JDK and were essential to seamless cross-building.

+

And ... that is pretty much it. From here, any library or application that is built using Cats Effect and FS2 cross-builds for Scala Native effectively for free. Three spectacular examples of this are:

+
    +
  • http4s Ember, a server+client duo with HTTP/2 support
  • +
  • Skunk, a Postgres client
  • +
  • rediculous, a Redis client
  • +
+

These libraries in turn unlock projects such as feral, Grackle, and smithy4s.

+ +

What’s next and how can I get involved?

+

Please try the Typelevel Native stack! And even better deploy it, and do so loudly!

+

Besides that, here is a brain-dump of project ideas and existing projects that would love contributors. I am happy to help folks get started on any of these, or ideas of your own!

+
    +
  • +

    Creating example applications, templates, and tutorials:

    + +
  • +
  • +

    Cross-building existing libraries and developing new, Typelevel-stack ones:

    +
      +
    • Go feral and implement a pure Scala custom AWS Lambda runtime that cross-builds for Native.
    • +
    • A pure Scala gRPC implementation built on http4s would be fantastic, even for the JVM. Christopher Davenport has published a proof-of-concept.
    • +
    • fs2-data has pure Scala support for a plethora of data formats. The http4s-fs2-data integration needs your help to get off the ground!
    • +
    • Lack of cross-platform cryptography is one of the remaining sore points in cross-building. I started the bobcats project to fill the gap but I am afraid it needs love from a more dedicated maintainer.
    • +
    +
  • +
  • +

    Integrations with native libraries:

    +
      +
    • I kick-started http4s-curl and would love to see someone take the reigns!
    • +
    • An NGINX Unit server backend for http4s promises exceptional performance. snunit pioneered this approach.
    • +
    • Using quiche for HTTP/3 looks yummy!
    • +
    • An idiomatic wrapper for SQLite. See also davenverse/sqlite-sjs#1 which proposes cross-platform API backed by Doobie on the JVM.
    • +
    +
  • +
  • +

    Developing I/O-integrated runtimes:

    +
      +
    • epollcat supports Linux and macOS and has plenty of opportunity for optimization and development.
    • +
    • A libuv-based runtime would have solid cross-OS support, including Windows. Prior art in scala-native-loop.
    • +
    • Personally I am excited to work on an io_uring runtime.
    • +
    +
  • +
  • +

    Tooling. Anton Sviridov has spear-headed two major projects in this area:

    +
      +
    • sbt-vcpkg is working hard to solve the native dependency problem.
    • +
    • sn-bindgen generates Scala Native bindings to native libraries directly from *.h header files. I found it immensely useful while working on http4s-curl, epollcat, and the s2n-tls integration in FS2.
    • +
    • Also: we are badly in need of a pure Scala port of the Java Microbenchmark Harness. Not the whole thing obviously, but just enough to run the existing Cats Effect benchmarks for example.
    • +
    +
  • +
  • +

    Scala Native itself. Lots to do there!

    +
  • +
+ +

Ember native benchmark

+
$ hey -z 30s http://localhost:8080
+
+Summary:
+  Total:    30.0160 secs
+  Slowest:    0.3971 secs
+  Fastest:    0.0012 secs
+  Average:    0.0131 secs
+  Requests/sec:    3815.4647
+
+  Total data:    1145250 bytes
+  Size/request:    10 bytes
+
+Response time histogram:
+  0.001 [1]    |
+  0.041 [114486]    |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
+  0.080 [7]    |
+  0.120 [5]    |
+  0.160 [5]    |
+  0.199 [3]    |
+  0.239 [5]    |
+  0.278 [3]    |
+  0.318 [4]    |
+  0.357 [3]    |
+  0.397 [3]    |
+
+
+Latency distribution:
+  10% in 0.0119 secs
+  25% in 0.0121 secs
+  50% in 0.0122 secs
+  75% in 0.0125 secs
+  90% in 0.0133 secs
+  95% in 0.0224 secs
+  99% in 0.0234 secs
+
+Details (average, fastest, slowest):
+  DNS+dialup:    0.0000 secs, 0.0012 secs, 0.3971 secs
+  DNS-lookup:    0.0000 secs, 0.0000 secs, 0.0011 secs
+  req write:    0.0000 secs, 0.0000 secs, 0.0013 secs
+  resp wait:    0.0131 secs, 0.0011 secs, 0.3941 secs
+  resp read:    0.0000 secs, 0.0000 secs, 0.0010 secs
+
+Status code distribution:
+  [200]    114525 responses
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-org-built-with-typelevel.html b/blog/typelevel-org-built-with-typelevel.html new file mode 100644 index 00000000..08cb2fc9 --- /dev/null +++ b/blog/typelevel-org-built-with-typelevel.html @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + typelevel.org built with Typelevel + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

typelevel.org built with Typelevel

+ + + community + +
+
+
+
+

typelevel.org built with Typelevel

+

We are proud to share that our website is now built with Laika, a Typelevel Organization project for generating static sites! As cool as it is that we are self-hosting, the intention of this revamp was to make it easier for our community to develop and contribute to the website. We chose technologies that we hope balance familiarity and ease-of-use with functionality and stability. Notably, this new website can be generated in its entirety by running a Scala script: scala build.scala. Stay tuned for a future blog post that dives into the details, but for now you may peruse the PR.

+

Finally, we would like to express gratitude to our friends at 47 Degrees who generously built the previous version of the website for us.

+ +

What’s next and how you can help

+

Truthfully, so far this is a "minimally viable website" and we invite you to help us iterate on it. Broadly, our goals are to explain:

+
    +
  1. Who we are, and how you can join our community.
  2. +
  3. What we build, and how you can use it.
  4. +
+

The next phase of development will largely focus on creating new content to support these goals (and the infrastructure to support that content). Here are a few ideas we have:

+
    +
  • + Educational and tutorial content to facilitate onboarding. +
      +
    • How to Get Started with Typelevel using our Toolkit.
    • +
    • Curated pathways to Learn how to use Typelevel in different scenarios: web services, serverless, CLIs, UIs, etc.
    • +
    • How to Get Started Contributing both to existing projects and also by publishing new libraries with sbt-typelevel.
    • +
    +
  • +
  • A Typelevel Project Index for exploring Organization and Affiliate projects, à la Scaladex. We are imagining a webapp built with Calico, with features for browsing projects, finding version numbers, and scaffolding new applications.
  • +
  • + Content-agnostic enhancements to the website itself. +
      +
    • Upstreaming customizations from our build to Laika.
    • +
    • Integrating mdoc, for typechecking code.
    • +
    • Improvements to layout, styling, and theme.
    • +
    +
  • +
+

We are accepting ideas and help in many forms! Please use our issue tracker and join the discussion on the #website channel in our Discord server.

+ +

In memoriam

+

This project would not have been possible without Jens Halm and his vision for a documentation tool that is native to our ecosystem. Jens raised the bar for open source stewardship: beyond the technical excellence of his work on Laika, he consistently published feature roadmaps, detailed issue and PR descriptions, and thorough documentation. Indeed, by creating a documentation tool that integrated so well with our tech stack, he has empowered all of us to become exemplary maintainers. Moreover, Jens' enthusiasm to support our community (including entertaining our numerous feature requests with in-depth responses full of context and design insights!) was his most generous gift to us.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + +
+ I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel! + + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-scala.html b/blog/typelevel-scala.html new file mode 100644 index 00000000..b684212e --- /dev/null +++ b/blog/typelevel-scala.html @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Scala and the future of the Scala ecosystem + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Scala and the future of the Scala ecosystem

+ + + technical + +
+
+
+
+

Typelevel Scala and the future of the Scala ecosystem

+

tl;dr Typelevel is forking Scala; we call on all stakeholders in the Scala ecosystem to collaborate on the creation of an independent, non-profit, open source foundation to safeguard the interests of the entire Scala community.

+

Last week I tweeted the following question:

+
How much interest would there be in a community sponsored fork of the #Scala toolchain? RTs and fav's please. + — Miles Sabin (@milessabin) August 25, 2014
+

It generated a lively response, both on Twitter and privately. The responses were sometimes perplexed, but typically excited and invariably positive. What I want to do here is provide some background to the question and sketch out the directions that positive answers lead.

+

As the Scala community has matured, a number of different strands of development of the language have emerged,

+
    +
  • The Typesafe Scala compiler is focused on stability and compatibility with Java.
  • +
  • The LAMP/EPFL Dotty compiler is focused on providing a practical implementation of the DOT calculus.
  • +
  • The Scala.js compiler is focused on targeting JavaScript as a backend platform.
  • +
  • The Scala Virtualized compiler is focused on language virtualization and staging.
  • +
  • The Scala.Meta system aims to provide portable metaprogramming facilities across a variety of Scala compilers.
  • +
  • There are several research and private variants of the Scala compiler in development and use by a variety of academic and commercial organizations.
  • +
  • The IDEs provide their own Scala variants which more or less accurately approximate Typesafe Scala.
  • +
+

Of these, the only compiler targeting the JVM which is generally suitable for use by the Scala community at large in the near term is the Typesafe Scala compiler. The current roadmap for the 2.12 release of this compiler, due in early 2016, has very modest goals, consistent with Typesafe's focus. Beyond that, the Next Steps roadmap mentions many things of interest, however it is clear that these are all a long way out – it will be 2017 at the earliest before any of this sees the light of day.

+

Typesafe's motivation for focusing on stability and Java 8 compatibility is very easy to understand. Typesafe is a commercial entity with products to sell in the JVM-based enterprise middleware space. Its primary software offerings, Akka and Play, are probably the most Java-friendly Scala projects of any significance, and simple arithmetic should tell you that a very large proportion (probably a large majority) of the potential customers for these (and the associated consultancy, training and support) are mainly Java enterprises with wholly or largely Java codebases and development teams. In this context it should be easy to see that for them an emphasis on Scala as a complement to Java, rather as its successor, is paramount.

+

Whilst this is entirely reasonable, and meets the needs of many, there is nevertheless a significant constituency at the core of the Scala community whose needs are not being fully met. This constituency is the segment of the Scala community which puts greater emphasis on typeful functional programming styles and which has a strong interest in current developments in functional programming in the wider world beyond Scala. The projects gathered here under the Typelevel umbrella are prime examples of that constituency.

+

As the producers and consumers of these and other projects we continually find ourselves running up against limitations of current Scala. Sometimes these limitations are minor and amenable to simple workarounds, many of which have passed into Scala folklore. Other limitations are more serious and can only be worked around with cumbersome encodings or otherwise elaborate and confusing hacks. These have the unfortunate consequence that elegant solutions to important problems are obscured by layers of cruft which exist only to sidestep quirks of the current compiler.

+

What makes this all the more frustrating is that many of these limitations are comparatively easy to remove. Fixes for some of them are purely syntactic – for instance type constructor partial application, of huge importance to Scalaz and its users, has a clunky encoding ("type lambdas") which could be given first class syntactic support without any impact on Scala's semantics and in a completely binary compatible way. Similarly, syntactic support for singleton types for literal values (see SIP-23) would be of enormous value to shapeless and Spire and their users. And the addition of literals for Bytes and Shorts would be welcomed by Spire, Scodec and many others. Other fixes, whilst affecting semantics, would do so only in a conservative way – programs which are valid when compiled with the Typesafe Scala compiler would have the same meaning and binary representation when compiled with the fixes in place.

+

With this in mind, we intend to create a new Scala distribution, as a conservative fork of the Typesafe Scala compiler, with a focus on meeting the needs of the part of the Scala community which has coalesced around the Typelevel stack. As a conservative fork, our aim is to be merge compatible from the Typesafe Scala compiler, and a key objective is to feed pull requests (many of which will resolve long standing Scala bugs or feature requests) back to the Typesafe Scala compiler. Our goal is to have a language which continues to evolve at the pace we saw until a couple of years ago, but with the difference that this will now be an opt-in process and the priorities will be set by the community.

+

Of course the devil is in the details. Forking a compiler is only a small part of the story – in many ways more important is the surrounding ecosystem of libraries. As part of this initiative we intend to publish compatible builds of at least the Typelevel libraries – taking our lead from the Typesafe community build (which attempts to track ecosystem coherence over time by building a selection of community libraries against the development Scala compiler as it evolves) and Scala.js (which has ported a selection of important community libraries to its compiler).

+

We welcome the participation of all other parties, individuals or organizations, who share our general goals – both those who want to contribute to the development of the compiler and those who would like their libraries and frameworks to be part of a Typelevel community build. It's early days, but we hope that with enough enthusiastic participation we will be able to produce useful binaries well before the end of the year.

+

We anticipate a number of objections to this initiative,

+
    +
  • +

    That it will split the community and fragment the language.

    +

    As I observed earlier, there are already several variants of the language in existence and it has been clear for a long time that different sections of the community have different interests. We shouldn't be afraid of acknowledging this fact – attempting to ignore it will be (arguably is already) counterproductive. Instead we should embrace diversity as a sign of a healthy and vigorous platform and community.

    +
  • +
  • +

    That we don't have the resources or the expertise to pull this off.

    +

    We disagree – the community around the Typelevel projects contains many of the most able Scala programmers on the planet. Between us we have a deep understanding of Scala's type system and other semantics (both as specified and as implemented), of compiler construction in general and of Typesafe Scala compiler internals in particular. We are intimately familiar with the Scala toolchain, which many of us have been using at scale for years in our day jobs. We are also intimately familiar with the issues that we seek to address – they are ones we face daily.

    +

    We also have the existence proof of the other Scala compiler variants. The number of full-time-equivalent people working on these projects is really very small – we believe that in practice this can be matched or exceeded by an open, inclusive and enthusiastic open source project.

    +
  • +
  • +

    That we underestimate the difficulty of maintaining binary and/or merge compatibility.

    +

    No, we really don't. We fully expect this to be the most challenging part of the whole exercise. That said, we have the benefit of years of experience of Scala binary compatibility issues, and we know now that a combination of a community-build style model along with effective use of the Migration Manager (already a component of the Typelevel SBT plugin) is enormously helpful in keeping on top of the issue.

    +

    There is a real risk here, and care will be needed. One thing is for sure though – if we don't try, we'll never know if it's possible.

    +
  • +
  • +

    That the fork is too conservative.

    +

    It's certainly true that restricting ourselves to only changes which are merge compatible with the Typesafe Scala compiler puts fairly strict limits on what we can do. Many highly desirable changes fall well beyond, and some people want to explore those possibilities.

    +

    We think that this is completely reasonable, and we don't think the two are mutually exclusive – a merge compatible Typelevel compiler meets many of our immediate needs, but we want to enable people to push further just as is being done by Scala.Meta, Scala Virtualized and Dotty.

    +

    We believe that the same infrastructure (community builds, MiMa) that will help the merge-compatible Typelevel compiler stay close to the Typesafe compiler will also be of great assistance to people who want to experiment with more radical changes. At a minimum, community build infrastructure will enable people to work with not just a bare compiler with but a core set of compatible libraries as well. We believe that such infrastructure would also benefit Scala Virtualized, Scala.Meta and Dotty.

    +
  • +
+

This brings me to the final part of this message. It has become clear to us that there are many distinct stakeholders in the Scala ecosystem with a mixture of shared and divergent interests. This is a good thing and is something we should jointly strive to support. To that end, we believe that it is time for the formation of an independent, non-profit, open source foundation to safeguard the interests of the entire Scala community – we call on all organizations and individuals who want to see a flourishing Scala ecosystem to join with us in that project.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Miles Sabin + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-sustainability-program-announcement.html b/blog/typelevel-sustainability-program-announcement.html new file mode 100644 index 00000000..8f7cc94f --- /dev/null +++ b/blog/typelevel-sustainability-program-announcement.html @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Sustainability Program Announcement + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Sustainability Program Announcement

+ + + governance + +
+
+
+
+

Typelevel Sustainability Program Announcement

+

We are excited to announce the Typelevel sustainability program. + The ultimate goal for this program is to provide ways for the user community to ensure the long-term sustainability of the development and maintenance of some Typelevel libraries. + Currently, these libraries are maintained in their contributors' spare time. This arrangement has worked so far but we want to firmly secure their long term sustainability with an institution dedicated to supporting the maintenance of these mission critical libraries.

+

Based on our Cats ecosystem community survey, roughly 70% of the users will gain more confidence + in the future of the ecosystem if there are compensated maintainers. We believe that our pure FP + Scala ecosystem should have an institution supporting it, somewhat like the Scala Center + supporting the language, and Lightbend supporting language as well as ecosystem libs.

+

There are also some concerns, 3% of users suggested paid maintainers will reduce their confidence, the remainder are not sure one way or another. Given these numbers, we believe that the gain of confidence will outweigh the concerns especially if we design our paid maintainership program with deliberations to address them. The worries we received fall into four categories:

+
    +
  • Vendor favoritism and influence
  • +
  • Conflicts of interest
  • +
  • Discouraging non-compensated maintainers and contributors
  • +
  • Paid maintainer over contributing unnecessary features
  • +
+ +

We believe we can mitigate these concerns by adopting the following principles:

+
    +
  • Paid maintainers focus on supporting the community contributors. In another sentence, + paid maintainers’ first task is to maintain the community-driven development. + More specific responsibilities are listed here.
  • +
  • Main obligations to sponsors (individual or corporate) are limited to
  • +
+
    +
  • The existing license remains unchanged.
  • +
+
    +
  • Timely security patches and mission critical bug fixes.
  • +
+
    +
  • Monetary contributions will NOT grant contributors extra influence over the development.
  • +
+
    +
  • Monetary contributors can influence how their contribution is distributed among projects.
  • +
  • The program is governed by an independent committee.
  • +
  • As part of our community, sponsors hold values compatible with the Scala Code of Conduct.
  • +
+

For more details, including goals and responsibilities for maintainers, funding sources, please go to the program's main document.

+

Today we are launching the program with the help of several founding sponsors.

+

+ + + +

+

Thanks to their generosity we are on an excellent start for the sustainability program. However, to successfully support the long term sustainability for our ecosystem, we need every bit of help we can get. For 2019, we have the following initiatives that require a significant investment of maintainer time.

+
    +
  • Refactor build configuration
  • +
  • Continue support Scala 2.11 through backporting
  • +
  • Complete Scala 2.13 migration
  • +
  • Support for Scala 3.0 and Scala native
  • +
  • Revamp guidance documentation for contributors
  • +
  • Revamp Documentations Navigation
  • +
  • More tutorial/example documentation
  • +
  • Complete cats-tagless' migration away from scala-meta
  • +
  • Merge in typelevel/algebra
  • +
  • A community build for the ecosystem
  • +
+

Our initial fundraising goal is $150,000. Among other things, achieving this goal will allow us to have a dedicated half-time (20 hr/week) maintainer for at least 2019. Why half time? We want to start with a committed maintainer to bring some certainties for our projects, and yet we are not sure how much support we will be getting from the community. Hence a half-time maintainer for the year is a minimum viable solution for the program.

+

Please consider talking to your employer about supporting the OSS libraries they are using. Any amount, either $5 per month from a one-person start-up or $5000 per month from a billion dollar corporation, will bring us closer to our goal.

+

Aside from monetary assistance, your company can also support us by:

+
    +
  • Providing computing resources/tools such as CI systems, communication platforms, development tools, etc.
  • +
  • Paid employees' time for code contributions
  • +
  • Sponsoring events such as free training, conferences, by providing the venue, food, etc.
  • +
  • Donations of training/support services or coupons that we can then exchange for monetary contributions.
  • +
+

For individual developers, another way to support us monetarily is to help our fundraising effort by:

+
    +
  • spreading the news about our fundraising campaign
  • +
  • mentioning and linking to our donation page when you write a blog post or give a talk about one of these libraries
  • +
  • advocating for the sustainable OSS development
  • +
+

Please don't hesitate to reach out with questions. Our contact address is sponsor-contact@typelevel.org. + Thank you for reading this and considering supporting us.

+

Donate at OpenCollective

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-switches-to-scala-code-of-conduct.html b/blog/typelevel-switches-to-scala-code-of-conduct.html new file mode 100644 index 00000000..b560b148 --- /dev/null +++ b/blog/typelevel-switches-to-scala-code-of-conduct.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Switches to the Scala Code of Conduct + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Switches to the Scala Code of Conduct

+ + + governance + +
+
+
+
+

Typelevel Switches to the Scala Code of Conduct

+

Typelevel is pleased to announce that we are retiring the Typelevel Code of + Conduct in favour of the Scala Code of Conduct. Many of the major projects + under the Typelevel umbrella have already made the switch and we will be asking + new projects that join Typelevel to adopt the new code.

+

The Scala Code of Conduct was developed by the Scala Center with input from + Typelevel and Lightbend, and improves on Typelevel's original in several ways. + It can be thought of as the "Typelevel code of conduct 2.0". We endorsed it + from the outset and have + decided that now the time is right to simplify things and move to the new code + wholesale.

+

A shared code of conduct means a shared standard of good behaviour. Having a + single code smooths the path for participants moving between different + organizations, projects, and events in the Scala ecosystem.

+

The Scala Code of Conduct is supported by key Scala organizations and events, + both community and commercial: the Scala Center, Lightbend, ScalaBridge, Scala + Days amongst many others. And now Typelevel and the Typelevel Summits.

+

We'd love you to join us.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/typelevel-toolkit.html b/blog/typelevel-toolkit.html new file mode 100644 index 00000000..3bd5164e --- /dev/null +++ b/blog/typelevel-toolkit.html @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Toolkit + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Toolkit

+ + + technical + +
+
+
+
+

Typelevel Toolkit

+

Getting started in the wondrous world of functional programming using Typelevel libraries can be daunting. Before you can even write your first pure "Hello, World!" you'll need to install a Java runtime, editor tooling and build tools. Then you'll need to setup some project using sbt or mill. As an added consequence, after all the setup, the idea of using these battle-tested libraries for small scripts will seem like a chore. This is where Typelevel Toolkit comes in. It provides an easy start for beginning and experienced developers with Scala and functional programming.

+ +

scala-cli to the rescue

+

scala-cli is a command-line interface to quickly develop and experiment with Scala, it's even on track to becoming the new scala command. The interface has a lot of advantages, but one of the most important ones is that it makes learning, developing and building Scala scripts and small applications friction-less and easy to use.

+

You can get scala-cli by following the installation instructions described here: https://scala-cli.virtuslab.org/docs/overview#installation. The great part here is that once you have scala-cli installed, it will take care of the rest: Java runtimes, editor tooling, compilation targets(including a native target!) and you can even use dependencies in your scripts!

+ +

An example of setting up your script

+

First off, let's create a new directory that will contain our script(s).

+
mkdir myscript && cd myscript
+

Now we can use scala-cli to create all the files necessary for editor tooling to work:

+
scala-cli setup-ide .
+

Finally, let's create a Scala file and try to compile it.

+
touch Main.scala
+scala-cli compile --watch Main.scala
+

The last command also includes a --watch flag, so we can hack on our script and scala-cli will try to compile the file on every save. + This creates a very nice feedback loop!

+ +

Putting the fun in functional

+

Typelevel Toolkit uses scala-cli and Typelevel libraries to provide a runway for your next Scala script or command-line interface. With a single line, Typelevel Toolkit gives you:

+
    +
  • Cats Effect a production proven pure asynchronous runtime.
  • +
  • fs2 an amazing streaming I/O library.
  • +
  • http4s-ember-client for a full-fledged HTTP client.
  • +
  • circe for dealing with JSON.
  • +
  • MUnit an unit testing library and an integration to easily unit test pure functional programs.
  • +
  • fs2-data-csv for handling CSV files.
  • +
  • decline a composable commandline parser and it's integration with Cats Effect.
  • +
+

Typelevel Toolkit shines with scala-cli, but it can also be used by sbt or mill if that is preferred. + More concretely this means your next ad-hoc script won't be Bash or Python spaghetti, but Scala code that can be a joy to hack on as time goes on, without the boilerplate.

+

You can use the toolkit by using a + scala-cli directive

+
//> using dep "org.typelevel::toolkit::0.0.4"
+

This will pull in the typelevel/toolkit dependency and then you're just an import away from your first pure functional "Hello, World!":

+
import cats.effect.*
+
+object Hello extends IOApp.Simple {
+  def run = IO.println("Hello, World!")
+}
+

You can compile and run this program by using a single command: scala-cli run Main.scala.

+

A "Hello, World!" is only the start, the goal here is to make functional programming friendly and practical. As such, Typelevel toolkit comes with examples that introduces beginners on how one can use the included libraries to achieve common tasks.

+

For the full list of libraries included in Typelevel Toolkit, please see the overview: https://typelevel.org/toolkit/#overview. If you feel like anything is missing, join the discussion.

+ +

We can have nice things

+

Typelevel libraries are production-proven, well tested, build upon rock solid semantics, and almost all have Scala 3 support. + However their entry-point is higher than your usual scripting language. Pure functional programming has a reputation of being hard to learn, and Typelevel Toolkit is a way to play in that world, without learning an entire ecosystem first.

+

The libraries in the toolkit compliment each other and target common usecases, thus providing a coherent mini-ecosystem, that scales extremely well, thanks to Cats Effect. With support for other runtimes besides the JVM. This means that your scripts can run in a JavaScript environment, thanks to scala-js. Or you can use scala-native to get a native binary, just like Rust and Go!

+

scala-cli again, makes things easy for us by having simple commands to compile to a certain target:

+
# To compile to JavaScript
+scala-cli Main.scala --js  
+
+# To target native
+scala-cli Main.scala --native
+

If you just want to explore Typelevel Toolkit, you can quickly open a REPL using the following command:

+
scala-cli repl --dep "org.typelevel::toolkit::0.0.4"
+

With scala-cli, there a few other cool things you get here:

+ + +

Summary

+

scala-cli is great. It's easy to get started with and great to use. Typelevel Toolkit leverages its versatility and provides a "pure functional standard library" in a single directive. This will enable you to create and develop scripts fast with high refactorability, an awesome developer experience and lots of functions that compose well! All those benefits while remaining beginner-friendly.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Rishad Sewnarain + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/update-about-sustainability-program.html b/blog/update-about-sustainability-program.html new file mode 100644 index 00000000..d608dddc --- /dev/null +++ b/blog/update-about-sustainability-program.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + Update About Sustainability Program + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Update About Sustainability Program

+ + + governance + +
+
+
+
+

Update About Sustainability Program

+

Six months ago, we launched the Typelevel sustainability program to provide more ways for our community to help support Typelevel projects. Since then, we received numerous donations from individuals as well as corporations, now bringing our estimated annual budget to over $18,000. We are incredibly grateful for the generosity of our community. From the bottom of our hearts, thank you!

+

Our original plan was to use the donation to hire a part-time dedicated maintainer to help support participating projects. That plan requires a higher annual budget, namely $150,000. We decided that we are going to adjust this plan to be more aligned with the current level of donations. We will start to distribute money received from donations directly to maintainers through OpenCollective. + Maintainers of pariticpating projects who are interested in being paid report their hours to the program admin each month. + If non-maintainer contributors are interested in contributing paid work, please request approvals from maintainers ahead of time. + At the end of the month, we calculate how much each maintainer can invoice based on total recorded hours and monthly budget received. Invoices can then be submitted to opencollective as expenses and paid.

+

We believe that this new plan will support the long-term sustainability of our projects in a more budget flexible way. Please reach out to us if you have any concerns or feedback. We'll happily refund donors who prefer their donations not being distributed this way.

+

From now on, please donate through the Typelevel sustainability program's opencollective page. The old donorbox portal will be shut down. If you haven't, please consider talking to your employer about supporting the OSS libraries they are using. Our sponsor levels remain unchanged: annual donation from $2,000 to $5,000 for silver, from $5,000 to $10,000 for gold, and from $10,000 to $50,000 for platinum. Individual donations are much much appreciated too. The more budget we have, the more supported office hours the maintainers can have.

+

Thanks again for your support! We look forward to continuing building a strong and active community for Functional programming in Scala.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/using-scalaz-Unapply.html b/blog/using-scalaz-Unapply.html new file mode 100644 index 00000000..97f2fe48 --- /dev/null +++ b/blog/using-scalaz-Unapply.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + Using scalaz.Unapply + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Using scalaz.Unapply

+ + + technical + +
+
+
+
+

Using scalaz.Unapply

+

Once you've started really taking advantage of Scalaz's typeclasses + for generic programming, you might have noticed a need to write + typelambdas to use some of your neat abstractions, or use syntax like + traverse or kleisli with a strangely-shaped type as an argument. + Here's a simple generalization, a List-based traverse.

+
import scalaz.Applicative, scalaz.syntax.applicative._
+
+def sequenceList[F[_]: Applicative, A](xs: List[F[A]]): F[List[A]] =
+  xs.foldRight(List.empty[A].point[F])((a, b) => ^(a, b)(_ :: _))
+

This works fine for a while.

+
scala> import scalaz.std.option._
+import scalaz.std.option._
+
+scala> sequenceList(List(some(1),some(2)))
+res1: Option[List[Int]] = Some(List(1, 2))
+
+scala> sequenceList(List(some(1),none))
+res2: Option[List[Int]] = None
+ +

The problem

+

The type of the input in the above example, List[Option[Int]], can be + neatly destructured into the F and A type params needed by + sequenceList. It has the “shape” F[x], so F can be picked out by + Scala easily.

+

Consider something else with a convenient Applicative instance, + though.

+
scala> import scalaz.\/
+import scalaz.$bslash$div
+
+scala> sequenceList(List(\/.right(42), \/.left(NonEmptyList("oops"))))
+<console>:23: error: no type parameters for method 
+  sequenceList: (xs: List[F[A]])(implicit evidence$1: scalaz.Applicative[F])F[List[A]]
+  exist so that it can be applied to arguments
+  (List[scalaz.\/[scalaz.NonEmptyList[String],Int]])
+ --- because ---
+argument expression's type is not compatible with formal parameter type;
+ found   : List[scalaz.\/[scalaz.NonEmptyList[String],Int]]
+ required: List[?F]
+
+              sequenceList(List(\/.right(42), \/.left(NonEmptyList("oops"))))
+              ^
+

Here, ?F meaning it couldn't figure out that you meant ({type λ[α] += NonEmptyList[String] \/ α})#λ.

+
scala> sequenceList[({type λ[α] = NonEmptyList[String] \/ α})#λ, Int
+                  ](List(\/.right(42), \/.left(NonEmptyList("oops"))))
+res5: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] =
+        -\/(NonEmptyList(oops))
+

The problem is that NonEmptyList[String] \/ Int has the shape + F[A, B], with F of kind * -> * -> * after a fashion, whereas the + F it wants must have kind * -> *, and Scala kinds aren't curried + at all.

+ +

Finding an Unapply instance

+

Unapply, though, does have implicit instances matching the + F[A, B] shape, unapplyMAB1 and unapplyMAB2, in its companion so + effectively always visible. What's special about them is that their + type parameters match the “shape” you're working with, F[A, B].

+

You should + look at their source + to follow along.

+

Let's see if one of them works. For implicit resolution to finish, + it's important that exactly one of them works.

+
scala> import scalaz.Unapply
+import scalaz.Unapply
+
+scala> Unapply.unapplyMAB1[Applicative, \/, NonEmptyList[String], Int]
+<console>:23: error: could not find implicit value for parameter
+TC0: scalaz.Applicative[[α]scalaz.\/[α,Int]]
+              Unapply.unapplyMAB1[Applicative, \/, NonEmptyList[String], Int]
+                                 ^
+
+scala> Unapply.unapplyMAB2[Applicative, \/, NonEmptyList[String], Int]
+res7: scalaz.Unapply[scalaz.Applicative,
+                     scalaz.\/[scalaz.NonEmptyList[String],Int]]{
+        type M[X] = scalaz.\/[scalaz.NonEmptyList[String],X];
+        type A = Int
+      } = scalaz.Unapply_0$$anon$13@5402af61
+

Here, the type res7.M represents the typelambda being passed to + sequenceList. You can see that work.

+
scala> sequenceList[res7.M, res7.A](
+                   List(\/.right(42), \/.left(NonEmptyList("oops"))))
+res8: res7.M[List[res7.A]] = -\/(NonEmptyList(oops))
+
+scala> res8 : NonEmptyList[String] \/ List[Int]
+res9: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] =
+        -\/(NonEmptyList(oops))
+

The res8 conformance test shows that Scala can still reduce the + path-dependent res7.M and res7.A types at this level, outside + sequenceList.

+ +

Searching for the right shape

+

Implicit resolution can pick the call to unapplyMAB2 partly because + it can pick all of its type parameters without weird typelambda + structures. But in Scalaz, we use typeclasses to guide its choice.

+

Why didn't unapplyMAB1 work? In this case, you can trust scalac + to say exactly the right thing: it looked for + Applicative[[α]scalaz.\/[α,Int]], and didn't find one. Sure enough, + \/ being right-biased means we don't offer that instance.

+

Incidentally, if you were to introduce that instance, you'd break code + relying on right-biased Unapply resolution to work.

+

unapplyMAB2 needs evidence of TC[({type λ[α] = M0[A0, α]})#λ]. + But that's okay, because we have that, where TC=Applicative, + M0=\/, and A0=NonEmptyList[String]!

+
scala> Applicative[({type λ[α] = \/[NonEmptyList[String], α]})#λ]
+res10: scalaz.Applicative[[α]scalaz.\/[scalaz.NonEmptyList[String],α]]
+         = scalaz.DisjunctionInstances2$$anon$1@2f658816
+

Scala doesn't need to figure out any typelambda itself for this to + work; we did everything by putting the typelambda right into + unapplyMAB2's evidence requirement, so it just has to find the + conforming implicit value.

+ +

Using Unapply generically

+

Now you can write a sequenceList wrapper that works for \/ and + many other shapes, including user-provided shapes in the form of new + Unapply implicit instances. If you're using Scala 2.9 (still?!) you + need to add -Ydependent-method-types to scalacOptions to write + this function.

+
def sequenceListU[FA](xs: List[FA])
+                     (implicit U: Unapply[Applicative, FA]
+                     ): U.M[List[U.A]] =
+  sequenceList(U.leibniz.subst(xs))(U.TC)
+

Instead of xs being List[F[A]], it's List[FA], and that's + destructured into U.M and U.A. The latter are path-dependent + types on U, the conventional name of the Unapply parameter. We + have also followed the convention of naming the Unapply-taking + variant function ending with a U.

+

And that works great!

+
scala> sequenceListU(List(\/.right(42), \/.left(NonEmptyList("oops"))))
+res11: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] =
+   -\/(NonEmptyList(oops))
+

Of course, there's that strange-looking function body to consider, + still.

+ +

Using the U evidence

+

The type equalities of the original U.M and U.A to the original + types can be seen where res8 is refined to res9 above. But only + the caller of the function knows those equalities, because it + produced and supplied the unapplyMAB2 call, which has a structural + type containing those equalities.

+

The body of sequenceListU doesn't know those things. In particular, + it still can't pick type parameters to pass to sequenceList + without a little help.

+

The leibniz member is a reified type equality of FA === U.M[U.A], + meaning those are the same at the type level, even though Scala can't + see it in this context. It represents genuine evidence that those two + types are equal, and is much more powerful than scala-library's own + =:=. We're using the core Leibniz operator, subst, directly to + prove that, as a consequence of that type equality, List[FA] === +List[U.M[U.A]] is also a type equality, and that therefore this + (constant-time) coercion is valid. This lifting is applicable in all + contexts, not just covariant ones like List's. Take a look at + the full API + for more, though you'll typically just need to come up with the right + type parameter for subst.

+

You can't ask for an Unapply and also ask for an + Applicative[U.M]; Scala won't allow it. So, because we needed to + resolve the typeclass anyway to find the Unapply implicit to use, we + just cart it along with the U and give it to the function, which + almost always needs to use it anyway. Because it's not implicitly + available, you usually need to grab it, U.TC, and use it directly.

+ +

Using in scalaz.syntax

+

map comes from functor syntax; it's not a method on Function1. So + how come this works?

+
scala> import scalaz.std.function._
+import scalaz.std.function._
+
+scala> ((_:Int) + 42) map (_ * 33)
+res13: Int => Int = <function1>
+
+scala> res13(1)
+res14: Int = 1419
+

When you import syntax, as Functor syntax was imported with + scalaz.syntax.applicative._ above, you get at least two conversions: + the plain one, like ToFunctorOps[F[_],A], which works if you have + the right shape, and the fancy one, ToFunctorOpsUnapply[FA], which + uses an Unapply to effectively invoke ToFunctorOps as in the + above. The latter is lower-priority, so Scala will pick the former if + the value has the F[A] shape.

+

That gives access to all the methods in FunctorOps, and other ops + classes, with only one special U-taking method. If you have several + functions operating on the same value type, or you can make that type + similar with Leibnizian equality as implicit arguments to your + methods, I suggest grouping them in this way, too, to cut down on + boilerplate.

+ +

Provide both anyway

+

We sometimes get asked “why not just provide the Unapply version of + the function or ops?”

+

We do it, and suggest it for your own code, despite the confusion, + because it's easier to work with real type equalities than with + Leibnizian equality, which you can do in your “real” function + implementation, and as seen in res8 above, the path-dependent type + resolution can leave funny artifacts in the inferred result. Here's + an extreme example from + an earlier demonstration.

+
scala> val itt = IdentityT lift it
+itt: IdentityT[scalaz.Unapply[scalaz.Monad,IdentityT[scalaz.Unapply[scalaz.Monad,Identity[Int]]
+                                                       {type M[X] = Identity[X]; type A = Int}#M,
+                                                     Int]]
+                 {type M[X] = IdentityT[scalaz.Unapply[scalaz.Monad,Identity[Int]]
+                                          {type M[X] = Identity[X]; type A = Int}#M,
+                                        X];
+                  type A = Int}#M,
+               Int]
+  = IdentityT(IdentityT(Identity(42)))
+ +

Credits

+

Jason Zaugg implemented Scalaz + Unapply, based on + ideas + from Miles Sabin and + Paul Chiusano.

+

Leibnizian equality was implemented for Scalaz by + Edward Kmett.

+

Lars Hupel's talk + (slides, + video) + on the features in the then-upcoming Scalaz 7 at nescala 2013, + including Unapply, gave me the missing “guided by typeclasses” + detail, inspiring me to tell more people about the whole thing at the + conference, and then, much later, write it down here.

+

This article was tested with Scala 2.10.2 & Scalaz 7.0.3.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/values-never-change-types.html b/blog/values-never-change-types.html new file mode 100644 index 00000000..4da10ba8 --- /dev/null +++ b/blog/values-never-change-types.html @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Values never change types + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Values never change types

+ + + technical + +
+
+
+
+

Values never change types

+

This is the sixth of a series of articles on “Type Parameters and + Type Members”. If you haven’t yet, you should + start at the beginning, + which introduces code we refer to throughout this article without + further ado.

+

In a subtyping language such as Java or Scala, first-class + existentials are a helpful ally for modeling values and methods that + abstract over subtypes in different ways. In a language with + mutation, such as Java or Scala, existentials help deal with certain + kinds of state changes without violating type safety.

+

But values, themselves, never change types. When you first practice + with existentials, the Java and Scala compilers seem to become + veritable minefields of type errors—do something slightly different in + your code, and everything falls apart. But this is just about + nothing being a free lunch: + the wide variety of values meeting any given existential type, + combined with the possibility for mutation, means a sound typechecker + must be very conservative about what it permits when using those + values.

+

So, in this article, we’ll explore some of these type error pitfalls, + see why it’s perfectly reasonable for the compilers to complain about + these pieces of code, and fix the errors using the equivalence rules + we’ve learned about in previous articles. This is a big topic, so in + the next article, we’ll talk about taking advantage of the freedoms + that the compilers are concerned about in this one.

+ +

Aliasing prevents replacement

+

Let’s say you have a list of strings, strs: List[String]. But you + want that value to change: you want it to be a List[Int] instead. + Maybe every value in the list parses to an integer and you want to + “map in place”; maybe you want to use -1 for every string that can’t + be parsed.

+

Generic systems like those of Java, Scala, and the ML family don’t let + you do this for a few reasons.

+

First, with regard to structures like List, let’s suppose we are + adding this feature to the type system, and that the list has type + List[String] before replacement, and List[Int] after. What type + does it have when we’re halfway done? The type system requires us to + guarantee that there are no strings, only ints, left once we’re + done; how do we track our progress? Remember that vague promises of + “I tested the code” are meaningless to the type system; you have to + prove it mathematically. This is a solvable problem, but the known + solutions far complicate the type system beyond the design goals of + these languages: the cost is far too high for the benefit.

+

Second, let us suppose that we’ve solved the first problem. Or, let + us suppose that we introduce a structure for which this isn’t a + problem.

+
final case class OneThing[T](var value: T)
+

There, no chance of “halfway done” there! But something else happens.

+
val strs: OneThing[String] = OneThing("hi")
+strs.value = 42
+// won't compile, just a thought experiment
+

Now, presumably, completely aside from the value, the variable strs + has changed types to OneThing[Int]. Not just the value in it, the + variable itself. OK, so what if that variable came from someplace + else?

+
def replaceWithInt(ote: OneThing[_]): Unit =
+  t.value = 42
+
+val strs: OneThing[String] = OneThing("hi")
+replaceWithInt(strs)
+// also won't compile, thought experiment
+

Now, the type of replaceWithInt must contain a note that “by the + way, the type of ote, and any variables that refer to it, and any + variables that refer to those variables, and so on until it stops, + will change as a result of this call”.

+

This is a problem of aliases, all the locations that may refer to a + value. If you change the type of the value, you also have to update + every reference to it, at compile time! This is the type system; + your promise that you have no other references is not good enough. + You have to prove it.

+

As with the previous problem, the known solutions to this problem + would complicate the type systems of Java and Scala beyond their + design goals. In a sense, this aspect of their type systems can be + considered to encourage functional programming. A type-changing map + that builds a new list of the new type, or what have you, instead of + mutating the old one, is trivial in the Java/Scala generics systems. + There are no chances of aliasing problems, because no one could + possibly have an unknown reference to the value you just constructed. + This is a even more obvious design choice in the ML family languages, + which make no secret of favoring functional programming.

+ +

Assignment rewrites existentials

+

We saw earlier that a simple get from a + List<?>, followed by adding that value right back to the same list, + didn’t work, but if we took that xs and passed it to a + type-parameterized version, everything worked fine. Why is that?

+

If you have a mutable variable of an existential type, the + existentialized part of the type may have different (type) values at + different parts of the program. Let’s use + the StSource from the projection post. + Note that the S member is existential, because we did not bind it.

+
var mxs: StSource[String] = StSource(0)(x => (x.toString, x + 1))
+// at this point in the program, the S is Int
+val s1 = mxs.init
+// now I'm going to change mxs
+mxs = StSource("ab")(x => (x, x.reverse))
+// at this point in the program, the S is String
+mxs.emit(s1)
+
+TmTp6.scala:14: type mismatch;
+ found   : tmtp6.Tmtp4Funs.s1.type (with underlying type tmtp4.StSource[String]#S)
+ required: _2.S where val _2: tmtp4.StSource[String]
+mxs.emit(s1)
+         ^
+

And it’s good that this happens, because the value we got from init + is definitely incompatible with the argument type to emit.

+

If we don’t want to track when this happens—and we certainly can’t + decide, in all cases, when a mutable variable such as this has been + overwritten so as to change its existentials, given the freedoms + afforded by Java—how can we treat a mutable variable with existentials + as safe? The type system makes a simplifying assumption: every + reference to the variable gets fresh values to fill in the + existentials.

+

If it helps, you can think of a mutable variable as an immutable + variable that wraps its type with an extra layer. In fact, that's + what scalac does when you capture a var. + So mxs is, in a sense, of type Ref[StSource[String]], where

+
trait Ref[T] {
+  def value: T
+  def update(t: T): Unit
+}
+

So, by substitution, the variable mxs is really a pair of functions, + () => StSource[String] and StSource[String] => Unit. The “getter” + returns StSource[String]; each time you invoke that getter, you + might get an StSource[String] with a different S member, because + the forSome effectively occurs inside the body, as described in + the substitutions of “Nested existentials”.

+

Of course, this means you can take advantage of this in your own + designs, to get some of the behavior of a type-changing value + mutation. The use of variable references to delineate existentials + means that, even when we replace mxs above, the behavior of the + variable in the context of an instance hasn’t really changed, so + nothing about the containing value’s type has changed. We thus + preserve our property, that values never change types.

+

When you make a single reference, it has to be consistent; subsequent + mutations have no effect on the value we got from that reference. So + we can pass that reference somewhere that asserts that this doesn’t + happen in its own context, such as a type-parameterized + m \equiv_{m} method. If you have a mutable variable + of type List<T>, even if you don’t know what T is, you know that + any updates to that variable will keep the same T.

+ +

Making variables read-only matters

+

If I change the variable to final in Java, and remove mutation, I + shouldn’t have this problem anymore. Surprisingly, I do; this is what + happened in + the original copyToZero example, + where the argument was declared final. I assume that this is just a + simplifying assumption in javac, that the extra guarantee of + unchanging existentials offered by final isn’t understood by the + compiler.

+

In the case of Scala, though, when you are using existential type + members, Scala can understand the implications of an immutable + variable, declared with val.

+
val imxs: StSource[String] = StSource(0)(x => (x.toString, x + 1))
+val s1 = imxs.init
+// you can do whatever you want here; the compiler will stop you from
+// changing imxs
+imxs.emit(s1)
+

It can’t pull off this trick for type parameters, having just as much + trouble as Java there. So this is another reason for + our original rule of thumb.

+ +

Naming the existential

+

The benefit we get from + passing copyToZeroP’s argument to copyToZeroT + is that we name the existential for the single reference to the + argument that we make. We name it T there, for the scope of its + invocation.

+

Likewise, in Scala, each val introduces, while it is in scope, each + existential member it has, as a type name. There are + a lot of rules in Scala + for exactly when this happens, but you may want to simply experiment. + We got a hint of what that name is + when we used StSource existentially in the REPL. + Here’s the previous example again, with a type annotation for s1.

+
val imxs: StSource[String] = StSource(0)(x => (x.toString, x + 1))
+val s1: imxs.S = imxs.init
+imxs.emit(s1)
+

We have gained convenience, not power, with this path-dependent + types feature; we can always pass into a type-parameterized local + method, with only the inconvenience of having to write out the whole + polymorphic method and call dance. Moreover, this is nowhere near + a solution to the type projection problem; + there are too many things that a type parameter can do that we can’t + with this feature. But we’ll dive into that in a later post.

+ +

By-name existential arguments aren’t equivalent!

+

There is another little corner case in method equivalence to consider.

+
def copyToZeroNP(xs: => PList[_]): Unit
+def copyToZeroNT[T](xs: => PList[T]): Unit
+

These method types are not equivalent! That’s because copyToZeroNP + can be called with a by-name that evaluates to a list with a + different element type each time; copyToZeroNT doesn’t allow this.

+
def time: PList[_] = {
+  val t = System.currentTimeMillis
+  if (t % 2 == 0) PCons("even", PNil())
+  else PCons(66, PNil())
+}
+
+copyToZeroNP(time)  // ok
+copyToZeroNT(time)  // not ok
+

In effect, => is like a type constructor; we can think of these + arguments as byname[PList[_]] and byname[PList[T]]. So we have + exactly the same problem as we had with + plenLength and plenLengthTP.

+

Unfortunately, + Scala currently accepts this, where it shouldn’t.

+

The difference between these two methods gives us a hint about working + with existentials: if we can shift the scope for a given existential + outside the function that keeps giving us different types on every + reference, we might have an easier time working with it, even if we + can’t change it to a val; maybe it needs to be lazy and not saved, + for example. So, despite the occasional convenience of path-dependent + types, type parameterized methods are still your best friends when + working with existential types.

+

In + the next article, “To change types, change values”, + we’ll look at some programs that make use of the two kinds of “type + changing” discussed above. After that, we’ll finally talk about + methods that return values of existential type, rather than merely + taking them as arguments.

+

This article was tested with Scala 2.11.7.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/variance-and-functors.html b/blog/variance-and-functors.html new file mode 100644 index 00000000..6a174737 --- /dev/null +++ b/blog/variance-and-functors.html @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + Of variance and functors + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Of variance and functors

+ + + technical + +
+
+
+
+

Of variance and functors

+

Scala's type system allows us to annotate type parameters with their variance: covariant, contravariant, invariant. + Variance allows us to define the subtyping relationships between type constructors – that is, under which + conditions F[A] is a subtype of F[B].

+

Similarly in functional programming, there are covariant functors, contravariant functors, and invariant functors. The + similarity in names is not coincidental.

+ +

Covariance

+

The common example is List[+A] which is covariant in its type parameter, denoted by the + next to the A. + A type constructor with a covariant type parameter means that if there is a subtyping relationship between the + type parameter, there is a subtyping relationship between the two instances of the type constructor. + For example if we have a List[Circle], we can substitute it anywhere we have a List[Shape].

+ +

Read

+

Another example of covariance is in parsing, for example in the following Read type class.

+
trait Read[+A] {
+  def read(s: String): Option[A]
+}
+

It makes sense to make Read covariant because if we can read a subtype, then we can read the supertype by + reading the subtype and throwing away the subtype-specific information. For instance, if we can read a + Circle, we can read a valid Shape by reading the Circle and ignoring any Circle-specific information.

+ +

Array

+

A type that cannot safely be made covariant is Array. If Array were covariant, we could substitute + an Array[Circle] for an Array[Shape]. This can get us in a nasty situation.

+
val circles: Array[Circle] = Array.fill(10)(Circle(..))
+val shapes: Array[Shape] = circles // works only if Array is covariant
+shapes(0) = Square(..) // Square is a subtype of Shape
+

If Array was covariant this would compile fine, but fail at runtime. In fact, Java arrays are + covariant and so the analogous Java code would compile, throwing an ArrayStoreException when + run. The compiler accepts this because it is valid to upcast an Array[Circle] into an Array[Shape], + and it is valid to insert a Shape into an Array[Shape]. However the runtime representation of + shapes is still an Array[Circle] and inserting a Square into that isn't allowed.

+ +

Read-only and covariance

+

In general, a type can be made safely covariant if it is read-only. If we know how to read a specific type, we know + how to read a more general type by throwing away any extra information. List is safe to to make + covariant because it is immutable and we can only ever read information off of it. With Array, we + cannot make it covariant because we are able to write to it.

+ +

Functor

+

As we've just seen, covariance states that when A subtypes B, then F[A] subtypes F[B]. Put differently, + if A can be turned into a B, then F[A] can be turned into an F[B]. We can encode this behavior + literally in the notion of a Functor.

+
trait Functor[F[_]] {
+  def map[A, B](f: A => B): F[A] => F[B]
+}
+

This is often encoded slightly differently by changing the order of the arguments:

+
trait Functor[F[_]] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+}
+

We can implement Functor for List and Read.

+
val listFunctor: Functor[List] =
+  new Functor[List] {
+    def map[A, B](fa: List[A])(f: A => B): List[B] = fa match {
+      case Nil => Nil
+      case a :: as => f(a) :: map(as)(f)
+    }
+  }
+
+val readFunctor: Functor[Read] =
+  new Functor[Read] {
+    def map[A, B](fa: Read[A])(f: A => B): Read[B] =
+      new Read[B] {
+        def read(s: String): Option[B] =
+          fa.read(s) match {
+            case None => None
+            case Some(a) => Some(f(a))
+          }
+      }
+  }
+

With that we can do useful things like

+
val circles: List[Circle] = List(Circle(..), Circle(..))
+val shapes: List[Shape] = listFunctor.map(circles)(circle => circle: Shape) // upcast
+
+val parseCircle: Read[Circle] = ...
+val parseShape: Read[Shape] = readFunctor.map(parseCircle)(circle => circle: Shape) // upcast
+

or more generally:

+
def upcast[F[_], A, B <: A](functor: Functor[F], fb: F[B]): F[A] =
+  functor.map(fb)(b => b: A)
+

upcast's behavior does exactly what covariance does – given some supertype A (Shape) and a subtype B (Circle), + we can mechanically (and safely) turn an F[B] into an F[A]. Put differently, anywhere we expect an F[A] we can provide + an F[B], i.e. covariance. For this reason, Functor is sometimes referred to in full as covariant functor.

+ +

Contravariance

+

Contravariance flips the direction of the relationship in covariance – an F[Shape] is considered a + subtype of F[Circle]. This seems strange – when I was first learning about variance I couldn't + come up with a situation where this would make sense.

+

If we have a List[Shape] we cannot safely treat it as a List[Circle] – doing so comes with all the usual + warnings about downcasting. Similarly if we have a Read[Shape], we cannot treat it as a Read[Circle] – + we know how to parse a Shape, but we don't know how to parse any additional information Circle may need.

+ +

Show

+

It appears fundamentally read-only types cannot be treated as contravariant. However, given that contravariance + is covariance with the direction reversed, can we also reverse the idea of a read-only type? Instead of reading + a value from a String, we can write a value to a String.

+
trait Show[-A] {
+  def show(a: A): String
+}
+

Show is the other side of Read – instead of going from a String to an A, we go from an A into + a String. This reversal allows us to define contravariant behavior – if we are asked to provide a way + to show a Circle (Show[Circle]), we can give instead a way to show just a Shape. This is a valid + substitution because we can show a Circle by throwing away Circle-specific information and showing just + the Shape bits. This means that Show[Shape] is a subtype of Show[Circle], despite Circle being a + subtype of Shape.

+

In general, we can show (or write) a subtype if we know how to show a supertype by tossing away subtype-specific + information (an upcast) and showing the remainder. Again, this means Show[Supertype] is substitutable, or a + subtype of, Show[Subtype].

+

For similar reasons that read-only types can be made covariant, write-only types can be made contravariant.

+ +

Array, again

+

Arrays cannot be made contravariant either. If they were, we could do unsafe reads:

+
val shapes: Array[Shape] = Array.fill(10)(Shape(..), Shape(..))
+val circles: Array[Circle] = shapes // Works only if Array is contravariant
+val circle: Circle = circles(0)
+

circle, having been read from an Array[Circle] has type Circle. To the compiler this would be fine, but + at runtime, the underlying Array[Shape] may give us a Shape that is not a Circle and crash the program.

+ +

Contravariant

+

Our Functor interface made explicit the behavior of covariance - we can define a similar interface that + captures contravariant behavior. If B can be used where A is expected, then F[A] can be used where an + F[B] is expected. To encode this explicitly:

+
trait Contravariant[F[_]] {
+  // Alternative encoding:
+  // def contramap[A, B](f: B => A): F[A] => F[B]
+
+  // More typical encoding
+  def contramap[A, B](fa: F[A])(f: B => A): F[B]
+}
+

We can implement an instance for Show.

+
val showContravariant: Contravariant[Show] =
+  new Contravariant[Show] {
+    def contramap[A, B](fa: Show[A])(f: B => A): Show[B] =
+      new Show[B] {
+        def show(b: B): String = {
+          val a = f(b)
+          fa.show(a)
+        }
+      }
+  }
+

Here we are saying if we can show an A, we can show a B by turning a B into an A before showing it. + Upcasting is a specific case of this, when B is a subtype of A.

+
def contraUpcast[F[_], A, B >: A](contra: Contravariant[F], fb: F[B]): F[A] =
+  contra.contramap(fb)((a: A) => a: B)
+

Going back to Shapes and Circles, we can show a Circle by upcasting it into a Shape and showing that.

+ +

Function variance

+

We observed that read-only types are covariant and write-only types are contravariant. This can be + seen in the context of functions and what function types are subtypes of others.

+ +

Parameters

+

An example function:

+
// Right now we only care about the input
+def squiggle(circle: Circle): Unit = ???
+
+// or
+
+val squiggle: Circle => Unit = ???
+

What type is a valid subtype of Circle => Unit? An important note is we're not + looking for what subtypes we can pass in to the function, we are looking for a value with a type + that satisfies the entirety of the function type Circle => Unit.

+

A first guess may involve some subtype of Circle like Dot (a circle with a radius of 0), such + as Dot => Unit.

+
val squiggle: Circle => Unit =
+  (d: Dot) => d.someDotSpecificMethod()
+

This doesn't work – we are asserting with the moral equivalent of a downcast that any + Circle input to the function is a Dot, which is not safe to assume.

+

What if we used a supertype of Circle?

+
val squiggle: Circle => Unit =
+  (s: Shape) => s.shapeshift()
+

This is valid – from the outside looking in we have a function that takes a Circle and + returns Unit. Internally, we can take any Circle, upcast it into a Shape, and go from there. + Showing things a bit differently reveals better the relationship:

+
type Input[A] = A => Unit
+val inputSubtype: Input[Shape] = (s: Shape) => s.shapeshift()
+val input: Input[Circle] = inputSubtype
+

We have Input[Shape] <: Input[Circle], with Circle <: Shape, so function parameters are contravariant.

+

The type checker enforces this when we try to use covariant type parameters in contravariant positions.

+
scala> trait Foo[+A] { def foo(a: A): Int = 42 }
+<console>:15: error: covariant type A occurs in contravariant position in type A of value a
+       trait Foo[+A] { def foo(a: A): Int = 42 }
+                               ^
+

Since type parameters are contravariant, a type in that position cannot also be covariant. To solve this + we "reverse" the constraint imposed by the covariant annotation by parameterizing with a supertype B.

+
scala> trait Foo[+A] { def foo[B >: A](a: B): Int = 42 }
+defined trait Foo
+ +

Return

+

Let's do the same exercise with function return types.

+
val squaggle: Unit => Shape = ???
+

Since using the supertype seemed to work with parameters, let's pick a supertype here, Object.

+
val squaggle: Unit => Shape =
+  (_: Unit) => somethingThatReturnsObject()
+

For similar issues with using a subtype for the input parameter, we cannot use + a supertype for the output. The function type states the return type is Shape, but we're + returning an Object which may or may not be a valid Shape. As far as the type checker is concerned, + this is invalid and the checker rejects the program.

+

Trying instead with a subtype:

+
val squaggle: Unit => Shape =
+  (_: Unit) => Circle(..)
+

This makes sense – the function type says it returns a Shape and inside we return a Circle which is + a perfectly valid Shape.

+

As before, rephrasing the type signatures leads to some insights.

+
type Output[A] = Unit => A
+val outputSubtype: Output[Circle] = (_: Unit) => Circle(..)
+val output: Output[Shape] = outputSubtype
+

That is Output[Circle] <: Output[Shape] with Circle <: Shape – function return types are covariant.

+

Again the type checker will enforce this:

+
scala> trait Bar[-A] { def bar(): A = ??? }
+<console>:15: error: contravariant type A occurs in covariant position in type ()A of method bar
+       trait Bar[-A] { def bar(): A = ??? }
+                           ^
+

As before, we solve this by "reversing" the contraint imposed by the variance annotation.

+
scala> trait Bar[-A] { def bar[B <: A](): B = ??? }
+defined trait Bar
+ +

All together now

+

Function inputs are contravariant and function outputs are covariant. Taking the previous examples together, + a function type Shape => Circle can be put in a place expecting a function type Circle => Shape.

+

We arrived at this conclusion by observing the behavior of subtype variance and the corresponding functors. Taken + in the context of functional programming where the only primitive is a function, we can draw a conclusion in + the other direction. Where function inputs are contravariant, types in positions where computations are + done (e.g. input or read-only positions) are also contravariant (similarly for covariance).

+ +

Invariance

+

Unannotated type parameters are considered invariant – the only relationship that holds is if a type A + is equal to a type B, then F[A] is equal to F[B]. Otherwise different instantiations of a + type constructor have no relationship with one another. Given invariant + F[_], an F[Circle] is not a subtype of F[Shape] – you need to explicitly provide the conversion.

+ +

Array once more

+

Arrays are invariant in Scala because they can be neither covariant nor contravariant. If we make it + covariant, we can get unsafe writes. If we make it contravariant, we can get unsafe reads. Since + read-only types can only be covariant and write-only types contravariant, our compromise is to make + types that support both invariant.

+

In order to treat an Array of one type as an Array of another, we need to have conversions + in both directions. This must be provided manually as the type checker has no way of knowing what the + conversion would be.

+ +

Invariant

+

Similar to (covariant) Functor and Contravariant, we can write Invariant.

+
trait Invariant[F[_]] {
+  def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]
+}
+

For demonstration purposes we write our own Array type

+
class Array[A] {
+  private var repr = ListBuffer.empty[A]
+
+  def read(i: Int): A =
+    repr(i)
+  def write(i: Int, a: A): Unit =
+    repr(i) = a
+}
+

and define Invariant[Array].

+
val arrayInvariant: Invariant[Array] =
+  new Invariant[Array] {
+    def imap[A, B](fa: Array[A])(f: A => B)(g: B => A): Array[B] =
+      new Array[B] {
+        // Convert read A to B before returning – covariance
+        override def read(i: Int): B =
+          f(fa.read(i))
+
+        // Convert B to A before writing – contravariance
+        override def write(i: Int, b: B): Unit =
+          fa.write(i, g(a))
+      }
+  }
+ +

Serialization

+

Another example of a read-write type that doesn't involve Arrays (or mutation) can be + found by just combining the Read and Show interfaces:

+
trait Serializer[A] extends Read[A] with Show[A] {
+  def read(s: String): Option[A]
+  def show(a: A): String
+}
+

Serializer both reads (from a String) and writes (to a String). We can't make it + covariant because that would cause issues with show, and we can't make it contravariant + because that would cause issues with read. Therefore our only choice is to keep it + invariant.

+
val serializerInvariant: Invariant[Serializer] =
+  new Invariant[Serializer] {
+    def imap[A, B](fa: Serializer[A])(f: A => B)(g: B => A): Serializer[B] =
+      new Serializer[B] {
+        def read(s: String): Option[B] = fa.read(s).map(f)
+        def show(b: B): String = fa.show(g(b))
+      }
+  }
+ +

Bringing everything together

+

We can see the Invariant interface is more general than both Functor and Contravariant – + where Invariant requires functions going in both directions, Functor and Contravariant only + require one. We can make Functor and Contravariant subtypes of Invariant by ignoring + the direction we don't care about.

+
trait Functor[F[_]] extends Invariant[F] {
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+
+  def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] =
+    map(fa)(f)
+}
+
+trait Contravariant[F[_]] extends Invariant[F] {
+  def contramap[A, B](fa: F[A])(f: B => A): F[B]
+
+  def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] =
+    contramap(fa)(g)
+}
+

Going back to treating Array and Serializer as a read/write store, if we make it read-only (like a read-only + handle on a resource) we can safely treat it as if it were covariant. If we are asked to read + Shapes and we know how to read Circles, we can read a Circle and upcast it into a Shape + before handing it over.

+

Similarly if we make it write-only (like a write-only handle on a resource) we can safely treat + it as contravariant. If we are asked to store Circles and we know how to store Shapes, + we can upcast each Circle into a Shape before storing it.

+

Variance manifests in two levels: one at the type level where subtyping relationships are defined, and + the other at the value level where it is encoded as an interface which certain types can conform to.

+ +

One more thing

+

Thus far we have seen the three kinds of variances Scala supports:

+
1. invariance: A = BF[A] = F[B]
+2. covariance: A <: BF[A] <: F[B]
+3. contravariance: A >: BF[A] <: F[B]
+

This gives us the following graph:

+
   invariance
+     ↑   ↑
+    /      \
+   -        +
+

Completing the diamond implies a fourth kind of variance, one that takes contravariance and + covariance together. This is known as phantom variance or anyvariance, a variance with no constraints on the + type parameters: F[A] = F[B] regardless of what A and B are. Unfortunately Scala's type system is + missing this kind of variance which leaves us just short of a nice diamond, but we can still encode it + in an interface.

+
trait Phantom[F[_]] {
+  def pmap[A, B](fa: F[A]): F[B]
+}
+

Given any F[A], we can turn that into an F[B], for all choices of A and B. With this power we can + implement covariant and contravariant functors.

+
trait Phantom[F[_]] extends Functor[F] with Contravariant[F] {
+  def pmap[A, B](fa: F[A]): F[B]
+
+  def map[A, B](fa: F[A])(f: A => B): F[B] = pmap(fa)
+
+  def contramap[A, B](fa: F[A])(f: B => A): F[B] = pmap(fa)
+}
+

This completes our diamond of variance.

+
   invariance
+     ↑   ↑
+    /      \
+   -        +
+   ↑        ↑
+    \      /
+    phantom
+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Adelbert Chang + + +
+ Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/variance-phantom.html b/blog/variance-phantom.html new file mode 100644 index 00000000..31e91527 --- /dev/null +++ b/blog/variance-phantom.html @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + Choosing variance for a phantom type + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Choosing variance for a phantom type

+ + + technical + +
+
+
+
+

Choosing variance for a phantom type

+

When you use a type parameter to abstract over actual data in your + ADT, there is typically only one + variance that makes + sense, if you choose to incorporate subtyping into your designs at + all. This is + the natural, “parametrically sound” variance.

+
sealed abstract class MyModel[P, I, -T, +V]
+
+final case class Running[I, T, V](
+  run: (I, T) => V
+) extends MyModel[String, I, T, V]
+
+final case class Inject[I](
+  config: I
+) extends MyModel[Int, I, Any, Nothing]
+

There are only four interesting possibilities, each of which is + illustrated above.

+
    +
  1. V occurs in only covariant positions, so can be marked covariant.
  2. +
  3. T occurs in only contravariant positions, so can be marked + contravariant.
  4. +
  5. I occurs in a pattern that meets neither of standards 1 and 2, so + may only be marked invariant, while still making sense.
  6. +
  7. P meets both standards 1 and 2, so…now what?
  8. +
+

The fourth case is interesting to me, firstly, because the design of + variance in Scala has not accounted for it; it is “phantom”, + the missing fourth variance. + I like to write it as I did in + “The missing diamond of Scala variance”:

+
sealed abstract class MyModel[👻P, I, -T, +V]
+

Second, and more practically, it illuminates the role of variance in + pattern matching in a way that can be difficult to see with that + confusing data in the way.

+ +

It can be covariant

+

The rule for a type parameter being parametrically covariant says we + have to look at all the positions in the data where the type parameter + occurs; if every one of them is a covariant position, then the type + parameter may be marked covariant. Consider a simplified version of + MyModel.

+
sealed abstract class Gimme[P]
+case object AStr extends Gimme[String]
+case object AnInt extends Gimme[Int]
+

The type parameter P appears in no positions, so it vacuously + satisfies the requirement “every occurrence is in covariant position”.

+

So let us mark P covariant.

+
sealed abstract class Gimme[+P]
+// otherwise the same
+ +

It can be contravariant

+

The rule for contravariance is also based on the occurrences of the + type parameter: if every occurrence is in contravariant position, then + the type parameter may be contravariant.

+

This rule seems to be contradict the rule for covariance, except that + all “every” statements are always true when the set under + consideration is empty.

+
    +
  1. Set S is empty.
  2. +
  3. Every element of set S is a dog.
  4. +
  5. No element of set S is a dog.
  6. +
+

2 and 3 can be true at the same time, but only if 1 is true, too. So + let us mark P contravariant, in a renamed ADT.

+
sealed abstract class Gotme[-P]
+case object UnStr extends Gotme[String]
+case object UnInt extends Gotme[Int]
+ +

The usual relationships

+

Since you can choose any variance for phantom parameters, the + important question is: what kind of type relationships should + exist within my ADT?

+

At first, this seems to be merely a question of how values of Gimme + and Gotme types ought to widen.

+
    +
  1. Every Gimme[Cat] is a Gimme[Animal], and
  2. +
  3. every Gotme[Animal] is a Gotme[Cat]. Moreover,
  4. +
  5. every Gimme[Nothing] is a Gimme[T] no matter what T is, and
  6. +
  7. every Gotme[Any] is a Gotme[T] no matter what T is.
  8. +
+

Obviously, if neither of these behaviors—the 1/3 nor the 2/4—is + desirable, you shouldn’t use variance. In my experience, this is the + case for most phantom types. If one is desirable, then it may be fine, + but there’s more to consider.

+ +

Extracting the covariant

+

Pattern-matching on the covariant Gimme reveals fully safe type + information. Unlike ClassTag and TypeTag, which are egregiously + broken for this use case, this method of carrying type information + forward into runtime is closed and + Scalazzi-safe.

+

What type information is revealed?

+
def gimme[P](g: Gimme[P]): (P, P) = g match {
+  case AStr =>
+    // implicitly[P =:= String]   will fail
+    // implicitly[P <:< String]   will fail
+    implicitly[String <:< P]
+    ("hi", "there")
+  case AnInt => (42, 84)
+}
+

If we left Gimme’s type parameter invariant, all three tests above + would succeed. In the case of this code, on the other hand,

+
    +
  1. AStr.type (the type of AStr) widens to Gimme[String],
  2. +
  3. Gimme[String] can widen to Gimme[P] as long as P is a + supertype of String.
  4. +
+

Because we’re reversing this process, we have to assume that #2 could + have happened.

+

The expression ("hi", "there") still compiles because P, while + otherwise mysterious, surely is a supertype of String. So the two + Strings can widen to P.

+

Things do not work out so well for all such functions.

+ +

Extracting the contravariant

+

Matching on the contravariant Gotme likewise reveals fully safe + type information.

+
def mklength[P](g: Gotme[P]): P => Int = g match {
+  case UnStr =>
+    // implicitly[P =:= String]   will fail
+    implicitly[P <:< String]
+    // implicitly[String <:< P]   will fail
+    (s: String) => s.length
+  case UnInt => identity[Int]
+}
+

Now P <:< String, which failed for the covariant form but succeeds + for the contravariant. On the other hand, we lost String <:< P, + which only works for the covariant form. That’s because

+
    +
  1. UnStr.type widens to Gotme[String];
  2. +
  3. Gotme[String] can widen to Gotme[P] as long as P is a + subtype of String.
  4. +
+

In the covariant form, we knew that every String was a P. In this + code, we know instead that every P is a String. Functions that can + handle any String are thus able to handle any P, logically, so the + type String => Int widens to P => Int.

+ +

Extracting the invariant

+

gimme would not work with the contravariant GADT; likewise, + mklength would not work with the covariant GADT.

+

An invariant GADT supports both, as well as some supported by + neither. For example, we could produce a (P, P) => P from a pattern + match. We can do this because the equivalent of AStr for invariant + Gimme tells us P = String, so all three implicitly checks + succeed.

+

From the behavior of pattern matching over these three sorts of GADTs, + I take away two lessons about variance in Scala.

+
    +
  1. It is impractical to infer variance in Scala, because you cannot + mechanically infer what sort of GADT pattern matching functions + ought to be possible to write.
  2. +
  3. The type flexibility of a generic type with variance comes at the + cost of decreased flexibility in pattern-matching + code. There ain’t no such thing as a free lunch.
  4. +
+ +

A GADT skolem

+

The “reverse widening” of pattern matching lifts the veil on one of + the more confusing references in type errors, a “GADT skolem”.

+
def uncons[A](as: List[A]): Option[::[A]] = as match {
+  case c@(_ :: _) => Some(c)
+  //                      ↑
+  // [error] type mismatch;
+  //  found   : ::[?A1] where type ?A1 <: A
+  //            (this is a GADT skolem)
+  //  required: ::[A]
+  case _ => None
+}
+

These “GADT skolems” appear all the time in sensible, compiling + code. Take a List with some variance carelessly tossed in.

+
sealed abstract class MyList[+A]
+final case class MyCons[A](head: A, tail: MyList[A])
+  extends MyList[A]
+case object MyNil extends MyList[Nothing]
+

Constructing MyCons[String], here’s what can happen.

+
    +
  1. MyCons[String] widens to MyList[String].
  2. +
  3. MyList[String] can widen to MyList[U] for any supertype U of + String.
  4. +
+

So in this code, we cannot reverse MyList[A] down to + MyCons[A]. But we can get MyList[L], where L is an otherwise + mysterious subtype of A. L is the GADT skolem, similar to ?A1 in + the above compiler error. The difference is that this code compiles.

+
def drop1[A](as: MyList[A]): MyList[A] =
+  as match {
+    case MyNil => MyNil
+    case MyCons(_, tl) => tl
+    // tl: MyList[L]  (L is a GADT skolem)
+    // L <: A, therefore
+    // MyList[L] <: MyList[A] by covariance
+  }
+ +

MyList’s type parameter is a phantom

+

We saw earlier that variance has a strong influence on the usability + of pattern matching. MyList has something important in common with + Gimme: the class definition does not use A, it only defines + it. So the scalac-enforced variance rules do not apply, and we can + make MyList contravariant instead.

+
sealed abstract class BadList[-A]
+final case class BadCons[A](head: A, tail: BadList[A])
+  extends BadList[A]
+case object BadNil extends BadList[Any]
+

Curiously, drop1 still works.

+
def baddrop1[A](as: BadList[A]): BadList[A] =
+  as match {
+    case BadNil => BadNil
+    case BadCons(_, tl) => tl
+    // tl: BadList[U]  (U is a GADT skolem)
+    // A <: U, therefore
+    // BadList[U] <: BadList[A] by contravariance
+  }
+

Other obvious functions will not work for non-obvious reasons.

+
def badHeadOption[A](as: BadList[A]): Option[A] =
+  as match {
+    case BadNil => None
+    case BadCons(hd, _) => Some(hd)
+    // [error] type mismatch;   ↑
+    //  found   : hd.type (with underlying type Any)
+    //  required: A
+  }
+

This fails because the skolem from a contravariant parameter is a + supertype instead of subtype. So

+
    +
  1. hd: U (U is a GADT skolem),
  2. +
  3. A <: U,
  4. +
  5. we’re stuck; there is no A value.
  6. +
+

This is not to imply something as silly as “covariance good, + contravariance bad”; you can just as well get these errors by marking + a parameter covariant that can only meaningfully be marked + contravariant. If anything, contravariance is more important than + covariance. The problem you must face is that the compiler is less + helpful in determining what “meaningful” marking, if any, should be + applied.

+

MyModel, from the beginning of this article, demonstrates three + situations in which each supported variance is natural. You may use it + as a guide, but its sanity is not compiler-checked. Your variances’ + sanity, or lack thereof, only becomes apparent when implementing + practical functions over a datatype.

+ +

Extracting the phantom

+

Suppose the phantom variance was defined, and we revisit the + String-and-Int GADT one more time.

+
sealed abstract class BooGimme[👻P]
+case object BooStr extends BooGimme[String]
+case object BooInt extends BooGimme[Int]
+

The trouble with letting the compiler infer covariance or + contravariance is that, on the face of it, either is as good as the + other. With phantom, we choose both.

+

But this variance makes the GADT utterly useless. Consider how + BooStr becomes BooGimme[P].

+
    +
  1. BooStr widens to BooGimme[String].
  2. +
  3. BooGimme[String] can widen to BooGimme[P] where P is…oops, + there are no conditions this time! P can be anything at all and + the widen will still work.
  4. +
+

The match tells us nothing about the type parameter; all three of the + type relationship checks via implicitly from the examples above + fail. We maximize the flexibility of the type parameter at the cost of + making GADT pattern matching impossible.

+

Likewise, if you mark MyList[A]’s type parameter phantom, there are + no bounds on the GADT skolem, so there’s little you can do with the + elements of the list.

+ +

The case for choosing no variance

+

My scalac error message pet peeve is the one suggesting that you + should add a variance annotation. This message treats the addition of + variance like a mechanical change: “if it compiles, it works”. On the + contrary, we have seen that

+
    +
  1. The flexibility of variance costs flexibility elsewhere;
  2. +
  3. the compiler cannot predict how this might harm your APIs’ + practicality;
  4. +
  5. the semantics of pattern matching are more complex in the face of + variance.
  6. +
+

Even if variance is applicable to your datatype, these costs, and the + cost of the additional complexity burden, should give you pause. Yet, + I stand by the claim I made in “The missing diamond + of Scala variance”: subtyping is incomplete without variance, so if + variance is too complicated, so is subtyping.

+

I don’t think subtyping—and its necessary component, variance—are too + complex for the working programmer to understand. Indeed, it can be a + fascinating exercise, with plenty of practical implications.

+

But, to me, the consequence of working out such exercises is that + neither variance nor subtyping ought to be used in the design of + practical programs, especially when higher-kinded type parameters and + members are available, offering far more flexibility at a better + price. There is no need to struggle in the face of all-too-often + missing features.

+

This article was tested with Scala 2.11.8.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/weaver-test-release.html b/blog/weaver-test-release.html new file mode 100644 index 00000000..26c97877 --- /dev/null +++ b/blog/weaver-test-release.html @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Weaver released + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Typelevel Weaver released

+ + + technical + +
+
+
+
+

Typelevel Weaver released

+

We are delighted to announce the release of weaver-test under Typelevel.

+ +

What is weaver?

+

Weaver is a test framework for integration and end-to-end testing. It makes tests faster and easier to debug by using cats, cats-effect and fs2.

+

Weaver provides a high quality experience when writing and running tests:

+
    +
  • Tests within a suite are run in parallel for the quickest results possible. This is especially suited to IO heavy tests, such as those making API calls or reading files.
  • +
  • Expectations (ie assertions) are composable values. This enables + developers to separate the scenario of the test from the checks they perform, + generally keeping tests cleaner and clearer.
  • +
  • Failures are aggregated and reported at the end of the run. This prevents the developer from having to "scroll up" forever when trying to understand what failed.
  • +
  • A lazy logger is provided for each test, and log statements are only displayed in case of a test failure. This lets the developer enrich their tests with clues and works perfectly well with parallel runs. Even though all tests are run in parallel, the developer can browse a sequential log of the test failure.
  • +
  • “beforeAll” and “afterAll” logic is represented using a cats.effect.Resource. This ensures that shared resources, such as HTTP clients, connection pools and file handles, are cleaned up correctly and predictably.
  • +
+ +

Why is weaver moving under the Typelevel umbrella?

+

Weaver makes heavy use of the cats-effect and fs2 Typelevel projects. These enable weaver to run tests concurrently, provide safe resource handling, composable assertions and much more. By becoming part of the Typelevel umbrella, weaver can be maintained more easily alongside its core dependencies.

+ +

Migrating to the 0.9.0 release

+

If you use Scala Steward, you will migrate automatically. If not, read the 0.9.0 migration guide.

+
+
+
+
+ +
+
+ +
+
+

+ Zainab Ali + + +
+ Zainab is a functional programmer who converted from object oriented design. A physicist at heart, she was excited to find an application of dimensional analysis and dependent types to real world problems. She is the author of Libra and a contributor to many typelevel libraries, such as cats and fs2. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/welcoming-new-steering-committee-members.html b/blog/welcoming-new-steering-committee-members.html new file mode 100644 index 00000000..8a6d2626 --- /dev/null +++ b/blog/welcoming-new-steering-committee-members.html @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + Welcoming New Steering Committee Members + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Welcoming New Steering Committee Members

+ + + governance + +
+
+
+
+

Welcoming New Steering Committee Members

+

Since our last post, the Typelevel Steering Committee has happily welcomed six new members to our group: Zach McCoy, Sam Pillsworth, Jasna Rodulfa-Blemberg, Andrew Valencik, Vasil Vasilev, and Mark Waks (aka Justin du Coeur). In their own words:

+

Zach McCoy (he/him)

+

I've been programming in Scala professionally for 10 years. I currently work at Jack Henry where I get to work in Scala and deal with observability in systems. I'm based in Iowa where I work remotely. I can usually be found reading or playing Magic: The Gathering.

+

Sam Pillsworth (she/her)

+

I’ve been writing code in industry for about 7 years, and the programming before that doesn’t count because it was in Fortran. Currently I'm a senior data developer writing Scala to power search in the Shopify Help Center. I’m based out of Toronto, Ontario, Canada. In my free time I read a lot of books, play with my dog Janeway, and futz with my emacs config.

+

Jasna Rodulfa-Blemberg (she/they)

+

I've been programming in Scala for the past 5 years, and more broadly in the JVM for most of my 9-year career. I currently work for Etsy, where I help scale and maintain their search platform using Scala. I work remotely from the Washington, DC, USA area with my partner and the wild rabbits in my yard.

+

I love cooking, gaming, reading, writing, and staying fit. More recently, I've begun reviving my Japanese language skills from college.

+

Andrew Valencik (he/him)

+

I've been writing Scala for 6ish years, on and off. Currently I manage a data science team and a developer team at Shopify working on search systems in the support area. I live in Ottawa, Ontario, Canada.

+

I like cooking when I have all day to do it, being outside, and rotating through hobbies.

+

Vasil Vasilev (he/him)

+

I've been doing Scala for about 5 years now and for about 3 years in the open-source community. I'm a software developer at JetBrains, working on the Scala Plugin team out of Munich, Germany.

+

I like to split my free time between cycling, gaming and reading, as well as Scala open-source contributions, especially Cats Effect.

+

Mark Waks, aka Justin du Coeur (he/him)

+

I've been programming for about 40 years in a wide variety of languages, the past 10 full-time in Scala. My current dayjob is at Troops (now part of Slack/Salesforce), working remotely from Somerville, MA; in my spare time, I run Querki, a sort of wiki/database hybrid written entirely in Scala.

+

I'm a pretty generalized geek, active in SCA, LARP, SF/F fandom, board games, comics, etc. I'm in charge of the Boston Scala Meetup, and on the board of the NE Scala conference (both currently dormant due to covid).

+
+

These new members are excited to continue Typelevel's efforts to foster an inclusive, welcoming, and safe environment around functional programming in Scala. You can find the members throughout the Typelevel GitHub space and on our Discord server with the @steering role.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Typelevel Steering Committee + + +
+ The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025. + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/who-implements-typeclass.html b/blog/who-implements-typeclass.html new file mode 100644 index 00000000..45d2381d --- /dev/null +++ b/blog/who-implements-typeclass.html @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + Who implements the typeclass instance? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Who implements the typeclass instance?

+ + + technical + +
+
+
+
+

Who implements the typeclass instance?

+

The typeclass pattern in Scala invites you to place + implementation-specific knowledge directly in the typeclass instances, + with the interface defined as the typeclass’s abstract interface.

+

However, GADTs permit a different organization of code. It is even + possible to define a typeclass that seems to do nothing at all, yet + still permits full type-safe typeclass usage.

+

The possibilities between these two extremes form a design space. If + you wish to practice ad-hoc polymorphism in Scala, this space is well + worth exploring.

+ +

A glorified overloader

+

Refactoring a set of overloads into a typeclass is a fine way to get + some free flexibility and dynamism, because expressing overloads as a + typeclass gives you free fixes for common overload problems.

+
    +
  1. Methods calling the overloaded method do not themselves need to be + overloaded just to avoid suppressing the flexibility of the + overload beneath. (See addThree and zipAdd below for + examples.)
  2. +
  3. Return-type overloading works, even in Scala, where it does not + when attempting to write overloads in the Java style, i.e. multiple + methods with the same name.
  4. +
  5. Overloads may be defined as recursive type rules, admitting a + combinatorial explosion or even infinite “effective overloads”.
  6. +
+

Let’s make a quick example of something like a typical overload.

+
object OverAdd {
+  def add(x: Int, y: Int): Int = x + y
+  
+  def add(x: String, y: String): String = s"$x$y"
+  
+  def add[A](l: Vector[A], r: Vector[A]): Vector[A] =
+    l ++ r
+}
+

This mechanically translates to a newly introduced type, some implicit + instances of that type, and a function to let us call add the same + way we used to.

+
// typeclasses are often defined with trait, but this is not required
+final case class Adder[A](addImpl: (A, A) => A)
+
+// easier if all implicits are in this block
+object Adder {
+  implicit val addInts: Adder[Int] = Adder((x, y) => x + y)
+  
+  implicit val addStrings: Adder[String] =
+    Adder((x, y) => s"$x$y")
+    
+  implicit def addVects[A]: Adder[Vector[A]] =
+    Adder((l, r) => l ++ r)
+}
+
+// and to tie it back together
+def add[A](x: A, y: A)(implicit adder: Adder[A]): A =
+  adder.addImpl(x, y)
+ +

Overloaded wrapping without overloading

+

While a bit more ceremonious, this allows us to write some nice + functions more easily. Here’s a function to add three values.

+
def addThree[A: Adder](l: A, m: A, r: A): A =
+  add(l, add(m, r))
+

addThree supports all three “overloads” of add.

+
scala> addThree(1, 2, 3)
+res0: Int = 6
+
+scala> addThree("a", "ba", "cus")
+res1: String = abacus
+
+scala> addThree(Vector(1, 1), Vector(0, 0), Vector(1, 0, 0, 1))
+res2: scala.collection.immutable.Vector[Int] =
+  Vector(1, 1, 0, 0, 1, 0, 0, 1)
+

With the overload style, we need three variants of this function, too, + each with the exact same body. The typeclass version need only be + written once, and automatically supports new overloads, that is, new + instances of Adder.

+

Same with this function.

+
def zipAdd[A: Adder](l: List[A], r: List[A]): List[A] =
+  l zip r map {case (x, y) => add(x, y)}
+

Functions like addThree and zipAdd are called derived + combinators. The more that you can do in derived combinators, the + more abstract and orthogonal your program will be.

+
   +=============+            |   +=============+
+   |   derived   |  (open     |   |  primitive  |  (closed
+   | combinators |    set)    |   | combinators |      set)
+   +=============+            |   +=============+
+                              |
+   +----------+               |    +----------+
+   | addThree |---→---→---→---→---→| Adder    |
+   +----------+       calls   |    | -addImpl |    +===========+
+    ↑                       |→---→ +----------+    | Instances |
+    | +--------+            | |                    +===========+
+    | | zipAdd |---→---→---→- |
+    | +--------+    calls     |  +------+ +---------+ +-------+
+    ↑        ↑                |  | Ints | | Strings | | Vects |
+    |   calls|                |  +------+ +---------+ +-------+
+    |      +-----+            |      |
+    |      | ??? |            |      |
+    ↑      +-----+            |      |
+    |  (derived combinators          ↓
+    |   can derive from each other)  ---→---→---→
+    |                                           |
+    ↑          -------------------------------  |
+    |          To evaluate `addThree(1, 2, 3)`  |
+    |          -------------------------------  ↓
+    |          1. Fetch `Adder` implicitly      |
+    |-←---←---←---←---←---←---←---←---←---←---←-|
+               2. Pass to `addThree`
+               3. `addThree` uses the abstract interface to
+                  invoke the primitive `add` combinator on what,
+                  to it, is an abstract type, `A`.
+ +

Infinite overloads via recursion

+

Making derived combinators easier to write is very useful, but + typeclasses go further by letting you describe overloading rules that + would be impossible with normal overloading.

+

Given that I can add Int and Int together, I should be able to add + (Int, Int) and (Int, Int) to get (Int, Int).

+
implicit val addIntPairs: Adder[(Int, Int)] =
+  Adder{case ((x1, x2), (y1, y2)) =>
+    (x1 + y1, x2 + y2)}
+    
+scala> add((2, 7), (3, 8))
+res3: (Int, Int) = (5,15)
+

But I should also be able to add pairs of String. And (Int, +String) pairs. And (String, Vector[Boolean]) pairs. And pairs of + pairs of pairs.

+

Typeclasses let you declare newly supported types recursively, with an + implicit argument list to the implicit def.

+
implicit def addPairs[A: Adder, B: Adder]
+    : Adder[(A, B)] =
+  Adder{case ((a1, b1), (a2, b2)) =>
+    (add(a1, a2), add(b1, b2))
+  }
+ +

Surely this must be going somewhere new

+

If you’re familiar with type classes, all this must be old hat. But + this time, we’re going to expand the boundaries of the typeclass + design space, by exploiting GADT pattern matching.

+

We could have designed the Adder type class to include addThree as + a primitive combinator, and implemented it afresh for each of the four + instances we’ve defined so far, as well as any future instances + someone might define. Thinking orthogonally, however, shows us that + there’s a more primitive concept which strictly generalizes it: if we + primitively define a two-value adder, we can use it to add three + items, simply by using it twice.

+

This has a direct impact on how we structure the functions related to + Adder. The primitives must be split up, their separate + implementations appearing directly in the implicit instances. Derived + combinators may occur anywhere that is convenient to us: outside the + typeclass for full flexibility of location, or within the typeclass + for possible overrides for performance.

+

But how much of the primitive implementations must occur in the + instances, really?

+ +

Empty tags as instances

+

There is a progression of design refinements here.

+
    +
  1. Ad hoc overloads, Java-style, impossible to abstract over.
  2. +
  3. Flip into a typeclass.
  4. +
  5. Refine the primitive/derived distinction to minimize code in + instances.
  6. +
+

For some typeclasses, no code needs to be put in the instances. For + example, if we want to support only Int, String, and Vector, + here is a perfectly sensible typeclass definition.

+
sealed trait ISAdder[A]
+
+object ISAdder {
+  implicit object AddInts extends ISAdder[Int]
+  implicit object AddStrs extends ISAdder[String]
+  
+  final case class AddVects[E]() extends ISAdder[Vector[E]]
+  
+  implicit def addVects[E]: ISAdder[Vector[E]] =
+    AddVects()
+}
+

If the instances cannot add values of the types indicated by the type + parameters, surely that code must exist somewhere! And it has a place, + in the definition of add.

+

If you recall, this method merely called addImpl on the typeclass + instance before. Now there is no such thing; the instances are empty.

+

Well, they are not quite empty; they contain a type. So we can define + add, with complete type safety, as follows.

+
def isadd[A](x: A, y: A)(implicit adder: ISAdder[A]): A =
+  adder match {
+    case ISAdder.AddInts => x + y
+    case ISAdder.AddStrings => s"$x$y"
+    case ISAdder.AddVects() =>
+      x ++ y
+  }
+

More specifically, they contain a runtime tag, which allows + information about the type of A to be extracted with a pattern + match. For example, determining that adder is AddInts reveals that + A = Int, because that’s what the extends clause says. This is + GADT pattern matching.

+

The Vector case is a little tricky here, because we can only + determine that A is Vector[e] for some unknown e, but that’s + enough information to invoke ++ and get a result also of Vector[e] + for the same e.

+

You can see this in action by using a variable type + pattern + to assign the name e (a lowercase type parameter is required for + this usage), so you can refer to it in types.

+
    case _: ISAdder.AddVects[e] =>
+      (x: Vector[e]) ++ y
+ +

The lowercase e names a GADT skolem

+

In the AddVects[e] pattern immediately above, e is a variable + type pattern. This is a type that exists only in the scope of the + case.

+

It’s existential because we don’t know what it is, only that it is + some type and we don’t get to pick here what that is. In this way, + it is no different from a type parameter’s treatment by the + implementation, which is + existential on the inside.

+

It’s a GADT skolem because it was bound by the pattern matching + mechanism to a “fresh” type, unequal to any other. Recall the way + AddVects was defined:

+
AddVects[E] extends ISAdder[Vector[E]]
+

Matching ISAdder with AddVects doesn’t tell us anything about + bounds on the type passed to AddVects at construction time. This + isn’t true of all + GADT skolems, + but is only natural for this one.

+

scalac will create this GADT skolem regardless of whether we give + it a name. In the pattern case AddVects(), it’s still known that + A = Vector[e] for some e; the only difference is that you haven’t + bound the e name, so you can’t actually refer to this unspeakable + type.

+

Usually, you do not need to assign names such as e to such types; + _ is sufficient. However, if you have problems getting scalac to + apply all the type equalities it ought to know about, a good first + step is to assign names to any skolems and try type + ascriptions. You’ll need a variable type pattern in other situations + that don’t infer, too. By contrast, with the e name bound, we can + confirm that x: Vector[e] in the above example, and y is + sufficiently well-typed for the whole expression to type-check.

+ +

Porting addPairs and other recursive cases

+

Suppose we add support for pairs to ISAdder.

+
final case class AddPairs[A, B](
+    val fst: ISAdder[A],
+    val snd: ISAdder[B]
+  ) extends ISAdder[(A, B)]
+

This should permit us to pattern-match in isadd to make complex + determinations about the A type given to isadd. This ought to be + a big win for GADT-style typeclasses, allowing “short-circuiting” + patterns that work in an obvious way.

+
// this pattern means A=(Int, String)
+case AddPairs(AddInts, AddStrs) =>
+
+// this pattern means A=(ea, Vector[eb])
+// where ea and eb are GADT skolems
+case AddPairs(fst, _: AddVects[eb]) =>
+
+// here, A=(ea, eb) (again, GADT skolems)
+// calling `isadd` recursively is the most
+// straightforward implementation
+case AddPairs(fst, snd) =>
+  val (f1, s1) = x
+  val (f2, s2) = y
+  (isadd(f1, f2)(fst), isadd(s1, s2)(snd))
+

The final case’s body is fine. scalac effectively introduces + skolems ea and eb so that A = (ea, eb), fst: Adder[ea], and so + on, and everything lines up nicely. We are not so lucky with the other + cases.

+
....scala:76: pattern type is incompatible with expected type;
+ found   : ISAdder.AddInts.type
+ required: ISAdder[Any]
+      case AddPairs(AddInts, AddStrs) =>
+                    ^
+....scala:76: pattern type is incompatible with expected type;
+
+ found   : ISAdder.AddStrs.type
+ required: ISAdder[Any]
+      case AddPairs(AddInts, AddStrs) =>
+                             ^
+....scala:79: pattern type is incompatible with expected type;
+ found   : ISAdder.AddVects[eb]
+ required: ISAdder[Any]
+      case AddPairs(fst, _: AddVects[eb]) =>
+                            ^
+

This is nonsensical; the underlying code is sound, we just have to go + the long way around so that scalac doesn’t get confused. Instead of + the above form, you must assign names to the AddPairs skolems as we + described above, and do a sub-pattern-match.

+
case p: AddPairs[ea, eb] =>
+  val (f1, s1) = x
+  val (f2, s2) = y
+  (p.fst, p.snd) match {
+    case (AddInts, AddStrs) =>
+    case (fst, _: AddVects[eb]) =>
+    case (fst, snd) =>
+

Note that we had to give up on the AddPairs pattern entirely, + because

+
    +
  1. More complex situations require type ascription.
  2. +
  3. You cannot ascribe with skolems unless you’ve bound the skolems to + names with variable type patterns.
  4. +
  5. You can’t use variable type patterns with the structural + “ADT-style” patterns; you must instead use inelegant and + inconvenient (non-variable) type patterns. (This may be + improved in Typelevel Scala 4.)
  6. +
+

Yet this remains entirely up to shortcomings in the current pattern + matcher implementation. An improved pattern matcher could make the + nice version work, safely and soundly.

+

As such, I don’t want these shortcomings to discourage you from trying + out the pure type-tagging, “GADT-style” typeclasses. It is simply + nicer for many applications, and you aren’t going to code yourself + into a hole with them, because should you wind up in the buggy + territory we’ve been exploring, there’s still a way out.

+ +

Same typeclass, new “primitive” combinators

+

“Empty” typeclasses like ISAdder contain no implementations of + primitive combinators, only “tags”. As such, they are in a sense the + purest form of “typeclass”; to classify types is the beginning and + end of what they do!

+

Every type that is a member of the “class of types” ISAdder is + either

+
    +
  1. the type Int,
  2. +
  3. the type String,
  4. +
  5. a type Vector[e], where e is any type, or
  6. +
  7. a type (x, y) where x and y are types that are also in the + ISAdder class.
  8. +
+

This is the end of ISAdder’s definition; in particular, there is + nothing here about “adding two values to get a value”. All that + is said is what types are in the class!

+

Given this ‘undefinedness’, if we have another function we want to + write over the exact same class-of-types, we can just write it without + making any changes to ISAdder.

+
def backwards[X](x: X)(implicit adder: ISAdder[X]): X = adder match {
+  case AddInts => -x
+  case AddStrs => x.reverse
+  case _: AddVects[e] => x.reverse
+  case p: AddPairs[ea, eb] =>
+    val (a, b): (ea, eb) = x
+    (backwards(a)(p.fst), backwards(b)(p.snd))
+}
+

Set aside the question of whether the class of “backwards-able” types + ought to remain in lockstep with the class of “addable” + types. Supposing that it should, the class need be defined only + once.

+

More practically speaking, if you expose the subclasses of a typeclass + to users of your library, they can define primitives “in lockstep”, + too. The line between primitive and derived combinators is also + blurred: a would-be derived combinator can pattern-match on the + typeclass to supply special cases for improved performance, becoming + “semi-primitive” in the process. You decide whether these are good + things or not.

+ +

Hybrid “clopen” typeclasses

+

Pattern-matching typeclass GADTs is subject to the same exhaustiveness + concerns and compiler warnings as pattern-matching ordinary ADTs. If + you eliminate a case from def isadd, you’ll see something like

+
....scala:57: match may not be exhaustive.
+It would fail on the following input: AddInts
+    adder match {
+    ^
+

We could unseal ISAdder, which would eliminate the warning, but + wouldn’t really solve anything. The function would still crash upon + encountering the missing case.

+

Pattern matches of unsealed hierarchies typically include a “fallback” + case, code used when none of the “special” cases match. However, for + pure typeclasses like ISAdder, this strategy is a dead end + too. Consider a hypothetical fallback case.

+
  case _ => ???
+

Each of the other patterns in isadd, by their success, taught us + something useful about the A type parameter. For example, case +AddInts tells us that A = Int, and accordingly x: Int and y: +Int. It also meant that the expected result type of that block is + also Int. That’s plenty of information to actually implement + “adding”.

+

By contrast, case _ tells us nothing about the A type. We don’t + know anything new about x, y, or the type of value we ought to + return. All we can do is return either x or y without further + combination; while this is a sort of “adding” in abstract + algebra, + there’s a good chance it’s not really what the caller was expecting.

+

Instead, we can reformulate a closed typeclass like ISAdder with one + extension point, where the typeclass is specially encoded in the usual + “embedded implementation” style. It’s closed and open, so + “clopen”.

+ +

sealed doesn’t seal subclasses

+

Our GADT typeclass instances work by embedding type information within + the instances, to be rediscovered at runtime. To support open + extension, we need a data case that contains functions instead of + types. We know how to encode that, because that is how standard, + non-GADT typeclasses work.

+
sealed trait ISOAdder[A]
+
+trait ExtISOAdder[A] extends ISOAdder[A] {
+  val addImpl: (A, A) => A
+}
+
+object ISOAdder {
+  implicit object AddInts extends ISOAdder[Int]
+  implicit object AddStrs extends ISOAdder[String]
+  
+  final class AddVects[A] extends ISOAdder[Vector[A]]
+  
+  implicit def addVects[A]: ISOAdder[Vector[A]] =
+    new AddVects
+    
+  def isoadd[A](x: A, y: A)(implicit adder: ISOAdder[A]): A =
+    adder match {
+      case AddInts => x + y
+      case AddStrs => s"$x$y"
+      case _: AddVects[e] =>
+        (x: Vector[e]) ++ y
+      // NB: no unchecked warning here, which makes sense
+      case e: ExtISOAdder[A] =>
+        e.addImpl(x, y)
+    }
+}
+

By sealing ISOAdder, we ensure that the pattern match in isoadd + remains exhaustive. However, one of those cases, ExtISOAdder, admits + new subclasses, itself! This is fine because no matter how many + subclasses of ExtISOAdder we make, they’ll still match the last + pattern of isoadd.

+

We could also define ExtISOAdder as a final case class. The point + is that you can make this “extension point” in your otherwise-closed + typeclass using whatever style you like.

+

One caveat, though: “clopen” typeclasses cannot have arbitrary new + primitive combinators added to them. They are like ordinary open + typeclasses in that regard. Consider a version of backwards for + ISOAdder: what you could do in the ExtISOAdder case?

+ +

Whoever you like

+

With type parameters vs. members, you can get pretty far with + the “rule of thumb”. + Beyond that, even bugs in scalac typechecking can guide you to the + “right” choice.

+

There is no similar rule for this design space. It might seem that + typeclass newcomers might have an easier time with the OO-style + “unimplemented method” signposts in the open style, but I have also + seen them lament the loss of flexibility that would be provided by the + GADT style.

+

Likewise, as an advanced practitioner, your heart will be rent by the + tug-of-war between the boilerplate of the open style and the + pattern-matcher’s finickiness with the GADT style. You may then be + tempted to adopt the hybrid ‘clopen’ style, but this, too, is too + often a form of design excess.

+

Given all that, the only help I can offer, aside from describing the + design space above, is “pick whichever you like”. You know your + program; if you are not sure which will be nicer, try both!

+

This article was tested with Scala 2.12.4.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/blog/why-is-adt-pattern-matching-allowed.html b/blog/why-is-adt-pattern-matching-allowed.html new file mode 100644 index 00000000..37b7ad92 --- /dev/null +++ b/blog/why-is-adt-pattern-matching-allowed.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + Why is ADT pattern matching allowed? + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

Why is ADT pattern matching allowed?

+ + + technical + +
+
+
+
+

Why is ADT pattern matching allowed?

+

One of the rules of + the Scalazzi Safe Scala Subset + is “no type casing”; in other words, testing the type via + isInstanceOf or type patterns isn’t allowed. It’s one of the most + important rules therein for preservation of free theorems. Common + functional programming practice in Scala seems to violate this rule + in a subtle way. However, as we will see, that practice carves out a + very specific exception to this rule that, morally, isn’t an exception + at all, carrying convenient advantages and none of the drawbacks.

+ +

Why forbid type tests?

+

With the “no type tests” rule, we forbid writing functions like this:

+
def revmaybe[T](xs: List[T]): List[T] = {
+  val allInts = xs.forall{case _:Int => true
+                          case _ => false}
+  if (allInts) xs.reverse else xs
+}
+

Which violates the + free theorem + of revmaybe’s type revmaybe(xs map f) = revmaybe(xs) map f, as + follows.

+
val xs = List(1, 2, 3)
+def f(i:Int) = Some(i)
+
+scala> revmaybe(xs map f)
+res2: List[Some[Int]] = List(Some(1), Some(2), Some(3))
+
+scala> revmaybe(xs) map f
+res3: List[Some[Int]] = List(Some(3), Some(2), Some(1))
+ +

ADTs are OK to go

+

On the other hand, the Scalazzi rules are totally cool with pattern + matching to separate the parts of + ADTs. For + example, this is completely fine.

+
def headOption[T](xs: List[T]): Option[T] = xs match {
+  case x :: _ => Some(x)
+  case _ => None
+}
+

Even more exotic matches, where we bring type information forward into + runtime, are acceptable, as long as they’re in the context of ADTs.

+
sealed abstract class Expr[T]
+final case class AddExpr(x: Int, y: Int) extends Expr[Int]
+
+def eval[T](ex: Expr[T]): T = ex match {
+  case AddExpr(x, y) => x + y
+}
+ +

ADTs use type tests

+

Let’s look at the compiled code of the eval body, specifically, the + case line.

+
         2: aload_2
+         3: instanceof    #60                 // class adts/AddExpr
+         6: ifeq          39
+         9: aload_2
+        10: checkcast     #60                 // class adts/AddExpr
+        13: astore_3
+        14: aload_3
+        15: invokevirtual #63                 // Method adts/AddExpr.x:()I
+        18: istore        4
+        20: aload_3
+        21: invokevirtual #66                 // Method adts/AddExpr.y:()I
+        24: istore        5
+        26: iload         4
+        28: iload         5
+        30: iadd
+

So, instead of calling unapply to presumably check whether AddExpr + matches, scalac checks and casts its argument to AddExpr. Why does + it do that? Let’s see if we could use AddExpr.unapply instead.

+
scala> AddExpr.unapply _
+res4: adts.AddExpr => Option[(Int, Int)] = <function1>
+

In other words, the unapply call can’t tell you whether an Expr is + an AddExpr; it can’t be called with arbitrary Expr.

+

The only actual check here is inserted by scalac as part of compiling + the pattern match expression, and it is a type test, supposedly + verboten under Scalazzi rules. headOption, too, is implemented with + type tests and casts, not unapply calls.

+

We’ve exhorted Scala users to avoid type tests, but then turn around + and say that type tests are OK! What’s going on?

+ +

An equivalent form

+

In every case where we use pattern matching on an ADT, there’s an + equivalent way we could write the expression without pattern matching, + by adding an encoding of the whole ADT as a method on the class or + trait we use as the base type. Let’s redefine the Option type with + such a method to see how this is done.

+
sealed abstract class Maybe[T] {
+  def fold[Z](nothing: => Z, just: T => Z): Z
+}
+
+final case class MNothing[T]() extends Maybe[T] {
+  override def fold[Z](nothing: => Z, just: T => Z): Z =
+    nothing
+}
+
+final case class Just[T](get: T) extends Maybe[T] {
+  override def fold[Z](nothing: => Z, just: T => Z): Z =
+    just(get)
+}
+

It’s key to our reasoning that we completely avoid match in our + implementations; in other words, the fold is matchless.

+

With the fold method, the following two expressions are equivalent, + notwithstanding scalac’s difficulty optimizing the latter, even in the + presence of inlining.

+
(selector: Maybe[A]) match {
+  case MNothing() => "default case"
+  case Just(x) => justcase(x)
+}
+
+selector.fold("default case", x => justcase(x))
+

It’s a simple formula: the fold takes as many arguments as there are + cases, always returns the given, sole type parameter, and each + argument is a function that results in that same parameter. There’s a + free theorem that a fold implementation on data structures without + recursion, like Maybe, can only invoke one of these arguments and + return the result directly, just as the pattern match does.

+

If you prefer the clarity of named cases, just use Scala’s named + arguments. Here’s that last fold:

+
selector.fold(nothing = "default case",
+              just = x => justcase(x))
+ +

GADT folds

+

Encoding Expr is a little bit more complicated. For the full power + of the type, we have to turn to Leibniz to encode the matchless + fold.

+
import scalaz.Leibniz, Leibniz.{===, refl}
+
+sealed abstract class Expr2[T] {
+  def fold[Z](add: (Int, Int, Int === T) => Z,
+              concat: (String, String, String === T) => Z): Z
+}
+

What does this mean? The type Int === T, seen in the add argument + signature, is inhabited if and only if the type T is the type + Int. So an implementation of fold can only call the add + function if it can prove that type equality. There is, of course, one + that can:

+
final case class AddExpr2(x: Int, y: Int) extends Expr2[Int] {
+  override
+  def fold[Z](add: (Int, Int, Int === Int) => Z,
+              concat: (String, String, String === Int) => Z): Z =
+    add(x, y, refl)
+}
+

Not only does AddExpr2 know that Expr2’s type parameter is Int, + we must make the type substitution when implementing methods from + Expr2! At that point it is enough to mention refl, the evidence + that every type is equal to itself, to satisfy add’s signature.

+

This may seem a little magical, but it is no less prosaic than + implementing java.lang.Comparable by making this substitution. So + you can do this sort of thing every day even in Java.

+
public interface Comparable<T> {
+  int compareTo(T o);
+}
+
+class MyData implements Comparable<MyData> {
+  @Override
+  public int compareTo(MyData o) {   // note T is replaced by MyData
+    // ...
+  }
+}
+

If only Java had higher kinds, you could go the rest of the way and + actually implement + GADTs.

+

Moving on, let’s see another case for Expr2, and finally to tie it + all together, eval2 with some extra constant data in for good + measure.

+
final case class ConcatExpr2(x: String, y: String) extends Expr2[String] {
+  override
+  def fold[Z](add: (Int, Int, Int === String) => Z,
+              concat: (String, String, String === String) => Z): Z =
+    concat(x, y, refl)
+}
+
+def eval2[T](ex: Expr2[T]): T =
+  ex.fold((x, y, intIsT) => intIsT(1 + x + y),
+          (x, y, strIsT) => strIsT("one" + x + y))
+

Using the Leibniz proof is, unfortunately, more involved than + producing it in the fold implementations. See my previous posts, + “A function from type equality to Leibniz” + and + “Higher Leibniz”, + for many + details on applying Leibniz proof to make type transformations.

+

While the pattern matching eval didn’t have to explicitly apply type + equality evidence -- it just knew that Int was T when the + IntExpr pattern matched -- Scala has holes in its implementation, + discussed in the aforementioned posts on Leibniz, that sometimes + make the above implementation strategy an attractive choice even + though pattern matching is available.

+ +

We could, but that’s good enough, so we won’t

+

You might have noticed that adding another case to Expr caused us + not only to implement an extra fold, but to add another argument to + the base fold to represent the new case, and then go through every + implementation to add that argument. This isn’t so bad for just two + cases, but indeed has quadratic growth, to the point that adding a new + case to a large datatype is a majorly annoying project all by itself.

+

There is an interesting property of fold, though: the strategy isn’t + available for our first function, revmaybe, to discriminate + arguments of arbitrary type! To do that, we would have to add a + signature like this to Any.

+
def fold[Z](int: Int => Z, any: Any => Z): Z = any(this)
+// and, in the body of class Int
+override def fold[Z](int: Int => Z, any: Any => Z): Z = int(this)
+

Obviously, you cannot do this.

+

You can only add fold methods to types you know; I can only call + fold in expr2 by virtue of the fact that I know that the argument + has type Expr2[T] for some T. If the argument was just T, I + wouldn’t have enough static type information to call fold. So the + use of folds doesn’t break parametricity. Equivalently, a pattern + match that could be implemented using a matchless fold also does not + break parametricity.

+

As we have seen, it is unfortunately inconvenient to actually go + through the bother of writing fold methods, when pattern matching is + there. But it is enough to reason that we could write a matchless + fold and replace the pattern matching with it, to prove that the + pattern matching is safe, no matter how many underlying type tests + scalac might use to implement it.

+

A simple test follows: if you could write a matchless fold, and use + that instead, the pattern match is type-safe.

+ +

A selector subtlety

+

Here’s a pattern match that violates parametricity.

+
selector match {
+  case MNothing() => "default case"
+  case Just(x) => justcase(x)
+}
+

Wait, but didn’t we rewrite that using a fold earlier? Not quite. + Oh, I didn’t mention? The type of selector is T, because we’re in + a function like this:

+
def notIdentity[T](selector: T) =
+  // match expression above goes here
+

Scala will permit this pattern match to go forward. It doesn’t + require us to prove that the selector is of the ADT root type we + happened to define; that’s an arbitrary point as far as Scala’s + subtyping system is concerned. All that is required is that the + static type of selector be a supertype of each of MNothing[_] and + Just[_], which T is, not being known to be more refined than + Any.

+

The test works here, though! What is ambiguous to scalac is a bright + line in our reasoning. We can’t define a matchless fold that can be + invoked on this selector, so we reach the correct conclusion, that + the match violates parametricity.

+ +

The rule revisited

+

So we’ve carved out a clear “exception” to the “no type tests” + Scalazzi rule, and seen that it isn’t an exception at all. There’s a + straightforward test you can apply to your pattern matches,

+

If and only if I could, hypothetically, write a matchless fold, or + use an existing one, and rewrite this in its terms, this pattern + match is safe.

+

but beware the subtle case where the match’s selector has a wider type + than you anticipated.

+

Finally, this is a rule specifically about expressions that don’t + violate our ability to reason about code. This doesn’t hold for + arbitrary type-unsafe rewrites: that you could write a program safely + means you should write it safely. Unlike arbitrary rewrites into + nonfunctional code, the pattern match uses no + non-referentially-transparent and no genuinely non-parametric + expressions.

+

This article was tested with Scala 2.11.4 and Scalaz 7.1.0.

+
+
+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Stephen Compall + + + + +

+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + diff --git a/build.scala b/build.scala deleted file mode 100644 index b52cbb09..00000000 --- a/build.scala +++ /dev/null @@ -1,664 +0,0 @@ -//> using dep org.http4s::http4s-ember-server::0.23.34 -//> using dep org.typelevel::laika-preview::1.3.2 -//> using dep com.monovore::decline-effect::2.6.2 -//> using dep org.graalvm.js:js:25.0.3 -//> using dep org.webjars.npm:katex:0.16.47 -//> using dep org.webjars.npm:fortawesome__fontawesome-free:7.2.0 -//> using dep pink.cozydev::protosearch-laika:0.0-7f79720-SNAPSHOT -//> using repository https://central.sonatype.com/repository/maven-snapshots -//> using option -deprecation - -import cats.effect.* -import cats.syntax.all.* -import com.monovore.decline.Opts -import com.monovore.decline.effect.CommandIOApp - -// Welcome to the typelevel.org build script! -// This script builds the site and can serve it locally for previewing. -// -// Main -- Entry point -// LaikaBuild -- Laika build, markdown in html out -// LaikaCustomizations -- Custom directives - -object Main extends CommandIOApp("build", "builds the site") { - import com.comcast.ip4s.* - import fs2.io.file.{Files, Path} - import laika.io.model.FilePath - import laika.preview.{ServerBuilder, ServerConfig} - import org.http4s.server.Server - - enum Subcommand { - case Serve(port: Port) - case Build(output: Path) - } - - val portOpt = Opts - .option[Int]("port", "bind to this port") - .mapValidated(Port.fromInt(_).toValidNel("Invalid port")) - .withDefault(port"8000") - - val destinationOpt = Opts - .option[String]("out", "site output directory") - .map(Path(_)) - .withDefault(Path("target")) - - val opts = Opts - .subcommand("serve", "serve the site")(portOpt.map(Subcommand.Serve(_))) - .orElse(destinationOpt.map(Subcommand.Build(_))) - - def main = opts.map { - case Subcommand.Build(destination) => - Files[IO].deleteRecursively(destination).voidError *> - LaikaBuild.build(FilePath.fromFS2Path(destination)).as(ExitCode.Success) - - case Subcommand.Serve(port) => - val serverConfig = ServerConfig.defaults - .withPort(port) - .withBinaryRenderers(LaikaBuild.binaryRenderers) - val server = ServerBuilder[IO](LaikaBuild.parser, LaikaBuild.input) - .withConfig(serverConfig) - .build - server.evalTap(logServer(_)).useForever - } - - def logServer(server: Server) = - IO.println(s"Serving site at ${server.baseUri}") -} - -object LaikaBuild { - import java.net.{URI, URL} - import laika.api.* - import laika.api.format.* - import laika.ast.* - import laika.config.* - import laika.format.* - import laika.io.config.* - import laika.io.model.* - import laika.io.syntax.* - import laika.parse.code.languages.ScalaSyntax - import laika.theme.* - import pink.cozydev.protosearch.analysis.{IndexFormat, IndexRendererConfig} - import pink.cozydev.protosearch.ui.SearchUI - - def input = { - val securityPolicy = new URI( - "https://raw.githubusercontent.com/typelevel/.github/refs/heads/main/SECURITY.md" - ).toURL() - - InputTree[IO] - .addDirectory("src") - // Laika skips .dotfiles by default - .addDirectory("src/.well-known", Path.Root / ".well-known") - .addInputStream( - IO.blocking(securityPolicy.openStream()), - Path.Root / "security.md" - ) - .addClassResource[this.type]( - "laika/helium/css/code.css", - Path.Root / "css" / "code.css" - ) - .merge(Redirects.inputTree) - } - - def theme = { - val provider = new ThemeProvider { - def build[F[_]: Async] = - ThemeBuilder[F]("typelevel.org") - .addRenderOverrides(LaikaCustomizations.overrides) - .build - } - - provider.extendWith(SearchUI.standalone) - } - - def parser = MarkupParser - .of(Markdown) - .using( - Markdown.GitHubFlavor, - SyntaxHighlighting.withSyntaxBinding("scala", ScalaSyntax.Scala3), - LaikaCustomizations.Directives, - LaikaCustomizations.RssExtensions - ) - .withConfigValue( - LinkValidation.Global(excluded = Seq(Path.Root / "blog" / "feed.rss")) - ) - .withConfigValue(LaikaKeys.siteBaseURL, "https://typelevel.org/") - .parallel[IO] - .withTheme(theme) - .build - - val binaryRenderers = List( - IndexRendererConfig(true), - BinaryRendererConfig( - "rss", - LaikaCustomizations.Rss, - artifact = Artifact( - basePath = Path.Root / "blog" / "feed", - suffix = "rss" - ), - false, - false - ) - ) - - def build(destination: FilePath) = parser.use { parser => - val html = Renderer - .of(HTML) - .withConfig(parser.config) - .parallel[IO] - .withTheme(theme) - .build - val rss = Renderer - .of(LaikaCustomizations.Rss) - .withConfig(parser.config) - .parallel[IO] - .build - val index = - Renderer.of(IndexFormat).withConfig(parser.config).parallel[IO].build - - (html, rss, index).tupled.use { (html, rss, index) => - parser.fromInput(input).parse.flatMap { tree => - html.from(tree).toDirectory(destination).render *> - rss.from(tree).toFile(destination / "blog" / "feed.rss").render *> - index - .from(tree) - .toFile(destination / "search" / "searchIndex.idx") - .render - } - } - } -} - -object LaikaCustomizations { - import cats.data.NonEmptySet - import java.time.OffsetDateTime - import java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME - import laika.config.* - import laika.api.bundle.* - import laika.api.config.* - import laika.api.format.* - import laika.ast.* - import laika.format.* - import laika.theme.* - - def addAnchorLinks(fmt: TagFormatter, h: Header) = { - val link = h.options.id.map { id => - SpanLink - .internal(RelativePath.CurrentDocument(id))( - RawContent(NonEmptySet.of("html"), Icons("fa-link")) - ) - .withOptions( - Styles("anchor-link") - ) - } - val linkedContent = link.toList ++ h.content - fmt.newLine + fmt.element( - "h" + h.level.toString, - h.withContent(linkedContent) - ) - } - - def renderFigure(fmt: TagFormatter, fig: Figure) = { - val renderedImg = HTML.defaultRenderer(fmt, fig.image) - val contentBlock = BlockSequence(fig.content) - val renderedContent = HTML.defaultRenderer(fmt, contentBlock) - val renderedCaption = - fmt.rawElement("figcaption", contentBlock, renderedContent) - fmt.rawElement( - "figure", - fig.clearOptions, - s"${renderedImg}\n${renderedCaption}" - ) - } - - val overrides = HTML.Overrides { - case (fmt, h: Header) => - addAnchorLinks(fmt, h) - case (fmt, f: Figure) => - renderFigure(fmt, f) - } - - object RssExtensions extends ExtensionBundle { - def description = "RSS-specific extensions" - - override def extendPathTranslator = - ctx => ExtendedTranslator(ctx.baseTranslator) - - private final class ExtendedTranslator(delegate: PathTranslator) - extends PathTranslator { - export delegate.{translate, getAttributes} - - def forReferencePath(path: Path) = - ExtendedTranslator(delegate.forReferencePath(path)) - - override def translate(target: Target) = target match { - case InternalTarget.Resolved(absolutePath, relativePath, formats) => - delegate.translate( - InternalTarget.Resolved( - absolutePath, - relativePath, - TargetFormats.Selected("html") // force HTML links in RSS feed - ) - ) - case target => - delegate.translate(target) - } - } - } - - object Directives extends DirectiveRegistry { - - val templateDirectives = Seq( - // custom Laika template directive for listing blog posts - TemplateDirectives.eval("forBlogPosts") { - import TemplateDirectives.dsl.* - - (cursor, parsedBody, source).mapN { (c, b, s) => - def contentScope(value: ConfigValue) = - TemplateScope(TemplateSpanSequence(b), value, s) - - val posts = c.parent.allDocuments.flatMap { d => - d.config.get[OffsetDateTime]("date").toList.tupleLeft(d) - } - - posts - .sortBy(_._2)(using summon[Ordering[OffsetDateTime]].reverse) - .traverse { (d, _) => - d.config.get[ConfigValue]("").map(contentScope(_)) - } - .leftMap(_.message) - .map(TemplateSpanSequence(_)) - } - }, - TemplateDirectives.create("svg") { - import TemplateDirectives.dsl.* - attribute(0).as[String].evalMap { icon => - Icons.get(icon).toRight(s"Unknown SVG icon '$icon'").map { svg => - TemplateElement(RawContent(NonEmptySet.of("html", "rss"), svg)) - } - } - } - ) - - val linkDirectives = Seq.empty - val spanDirectives = Seq( - SpanDirectives.create("math") { - import SpanDirectives.dsl.* - rawBody.evalMap { body => - (KaTeX.render(body, false), KaTeX.render(body, false, "mathml")).mapN( - (katexStr, mathmlStr) => - SpanSequence( - RawContent(NonEmptySet.of("html"), katexStr), - RawContent(NonEmptySet.of("rss"), mathmlStr) - ) - ) - } - } - ) - val blockDirectives = Seq( - BlockDirectives.create("math") { - import BlockDirectives.dsl.* - rawBody.evalMap { body => - (KaTeX.render(body, true), KaTeX.render(body, true, "mathml")).mapN( - (katexStr, mathmlStr) => - BlockSequence( - RawContent( - NonEmptySet.of("html", "rss"), - katexStr, - Styles("bulma-has-text-centered") - ), - RawContent(NonEmptySet.of("rss"), mathmlStr) - ) - ) - } - }, - BlockDirectives.create("figure") { - import BlockDirectives.dsl.* - ( - attribute(0).as[String].widen, - attribute("intrinsicWidth").as[Double].optional, - attribute("intrinsicHeight").as[Double].optional, - attribute("style").as[String].optional, - attribute("alt").as[String].optional, - attribute("title").as[String].optional, - parsedBody, - cursor - ).mapN { (src, width, height, style, alt, title, body, cursor) => - val img = Image( - InternalTarget(VirtualPath.parse(src)).relativeTo(cursor.path), - width.map(LengthUnit.px(_)), - height.map(LengthUnit.px(_)), - alt, - title - ) - val options = Styles(style.getOrElse("default-image-block")) - Figure(SpanSequence(img), Seq.empty, body, options) - } - }, - BlockDirectives.create("html") { - import BlockDirectives.dsl.* - rawBody.map { body => - RawContent(NonEmptySet.of("html", "rss"), body) - } - } - ) - } - - object Rss - extends TwoPhaseRenderFormat[TagFormatter, BinaryPostProcessor.Builder] { - - def interimFormat = new { - def fileSuffix = "rss" - - val defaultRenderer = { - case (fmt, Title(_, _)) => - "" // don't render title b/c it is in the RSS metadata - case (fmt, RawContent(formats, content, options)) => - if (formats.contains("rss")) // only render content designated for RSS - HTML.defaultRenderer( - fmt, - RawContent(NonEmptySet.of("html"), content, options) - ) - else "" - case (fmt, elem) => HTML.defaultRenderer(fmt, elem) - } - - export HTML.formatterFactory - } - - def prepareTree(tree: DocumentTreeRoot) = Right(tree) - - def postProcessor: BinaryPostProcessor.Builder = new { - def build[F[_]: Async](config: Config, theme: Theme[F]) = - Resource.pure { (result, output, config) => - val posts = result.allDocuments - .flatMap { d => - d.config.get[OffsetDateTime]("date").toList.tupleLeft(d) - } - .sortBy(_._2)(using summon[Ordering[OffsetDateTime]].reverse) - - output.resource.use { os => - Async[F].blocking { - val pw = new java.io.PrintWriter(os) - pw.print("""| - | - | - |Typelevel Blog - |https://typelevel.org/blog/ - |The Typelevel Blog RSS Feed - |""".stripMargin) - - posts - .takeWhile(_._2.isAfter(OffsetDateTime.now().minusYears(1))) - .foreach { (doc, _) => - pw.print(doc.content) - } - - pw.print("""| - | - |""".stripMargin) - pw.flush() - } - } - } - } - } - - val Icons = { - val fa = WebJar("fortawesome__fontawesome-free") - def loadFaIcon(prefix: String, name: String) = - fa.load(s"svgs/$prefix/$name.svg") - - Map( - // brands - "fa-bluesky" -> loadFaIcon("brands", "bluesky"), - "fa-discord" -> loadFaIcon("brands", "discord"), - "fa-github" -> loadFaIcon("brands", "github"), - "fa-linkedin" -> loadFaIcon("brands", "linkedin"), - "fa-mastodon" -> loadFaIcon("brands", "mastodon"), - "fa-signal-messenger" -> loadFaIcon("brands", "signal-messenger"), - "fa-youtube" -> loadFaIcon("brands", "youtube"), - // solids - "fa-book" -> loadFaIcon("solid", "book"), - "fa-envelope" -> loadFaIcon("solid", "envelope"), - "fa-globe" -> loadFaIcon("solid", "globe"), - "fa-hand-holding-heart" -> loadFaIcon("solid", "hand-holding-heart"), - "fa-link" -> loadFaIcon("solid", "link"), - "fa-magnifying-glass" -> loadFaIcon("solid", "magnifying-glass"), - "fa-person-chalkboard" -> loadFaIcon("solid", "person-chalkboard"), - "fa-puzzle-piece" -> loadFaIcon("solid", "puzzle-piece"), - "fa-square-rss" -> loadFaIcon("solid", "square-rss") - ) - } -} - -object KaTeX { - import org.graalvm.polyglot.* - import scala.jdk.CollectionConverters.* - - private val katexJar = WebJar("katex") - - private def loadKaTeX(): String = katexJar.load("dist/katex.js") - - private lazy val katex = { - val ctx = Context - .newBuilder("js") - .allowAllAccess(true) - .build() - ctx.eval("js", loadKaTeX()) - ctx.getBindings("js").getMember("katex") - } - - def render( - latex: String, - displayMode: Boolean = false, - output: String = "htmlAndMathml" - ): Either[String, String] = - synchronized { - // https://katex.org/docs/options - val options = Map( - "throwOnError" -> true, - "strict" -> true, - "displayMode" -> displayMode, - "output" -> output - ).asJava - try { - Right(katex.invokeMember("renderToString", latex, options).asString) - } catch { - case ex: Exception => Left(ex.getMessage) - } - } - -} - -// Provides access to resources of a bundled WebJar. -class WebJar(artifactId: String) { - import java.io.FileNotFoundException - private val classLoader = classOf[WebJar].getClassLoader - - private val version: String = - val propsPath = s"META-INF/maven/org.webjars.npm/$artifactId/pom.properties" - val stream = classLoader.getResourceAsStream(propsPath) - if stream == null then - throw FileNotFoundException( - s"Could not find pom.properties for webjar '$artifactId' on the classpath." - ) - val props = java.util.Properties() - props.load(stream) - props.getProperty("version") - - // Load a resource from this WebJar as a String, uses relative paths. - def load(path: String): String = - val resourcePath = s"META-INF/resources/webjars/$artifactId/$version/$path" - val stream = classLoader.getResourceAsStream(resourcePath) - if stream == null then - throw FileNotFoundException( - s"Could not find resource '$path' in webjar '$artifactId' ($version)." - ) - String(stream.readAllBytes()) -} - -object Redirects { - import laika.io.model.InputTree - import laika.ast.Path.Root - - def inputTree = map.foldLeft(InputTree[IO]) { case (tree, (from, to)) => - tree.addString(mkRedirect(to), Root / (from.stripSuffix(".html") + ".md")) - } - - private def mkRedirect(to: String) = - s"""{% laika.html.template = "/templates/redirect.template.html", laika.targetFormats: [html], target = "$to" %}""" - - private val map = Map( - "/about/index.html" -> "/foundation/README.md", - "/about/contributing/index.html" -> "/foundation/README.md", - "/blog/2013/04/04/inauguration.html" -> "/blog/inauguration.md", - "/blog/2013/06/24/deriving-instances-1.html" -> "/blog/deriving-instances-1.md", - "/blog/2013/07/07/generic-numeric-programming.html" -> "/blog/generic-numeric-programming.md", - "/blog/2013/09/11/using-scalaz-Unapply.html" -> "/blog/using-scalaz-Unapply.md", - "/blog/2013/10/13/spires-ops-macros.html" -> "/blog/spires-ops-macros.md", - "/blog/2013/10/13/towards-scalaz-1.html" -> "/blog/towards-scalaz-1.md", - "/blog/2013/10/18/treelog.html" -> "/blog/treelog.md", - "/blog/2013/11/17/discipline.html" -> "/blog/discipline.md", - "/blog/2013/12/15/towards-scalaz-2.html" -> "/blog/towards-scalaz-2.md", - "/blog/2014/01/18/implicitly_existential.html" -> "/blog/implicitly-existential.md", - "/blog/2014/02/21/error-handling.html" -> "/blog/error-handling.md", - "/blog/2014/03/09/liskov_lifting.html" -> "/blog/liskov-lifting.md", - "/blog/2014/04/14/fix.html" -> "/blog/fix.md", - "/blog/2014/06/22/mapping-sets.html" -> "/blog/mapping-sets.md", - "/blog/2014/07/02/type_equality_to_leibniz.html" -> "/blog/type-equality-to-leibniz.md", - "/blog/2014/07/06/singleton_instance_trick_unsafe.html" -> "/blog/singleton-instance-trick-unsafe.md", - "/blog/2014/09/02/typelevel-scala.html" -> "/blog/typelevel-scala.md", - "/blog/2014/09/20/higher_leibniz.html" -> "/blog/higher-leibniz.md", - "/blog/2014/11/10/why_is_adt_pattern_matching_allowed.html" -> "/blog/why-is-adt-pattern-matching-allowed.md", - "/blog/2015/02/26/rawtypes.html" -> "/blog/rawtypes.md", - "/blog/2015/07/13/type-members-parameters.html" -> "/blog/type-members-parameters.md", - "/blog/2015/07/16/method-equiv.html" -> "/blog/method-equiv.md", - "/blog/2015/07/19/forget-refinement-aux.html" -> "/blog/forget-refinement-aux.md", - "/blog/2015/07/23/type-projection.html" -> "/blog/type-projection.md", - "/blog/2015/07/27/nested-existentials.html" -> "/blog/nested-existentials.md", - "/blog/2015/07/30/values-never-change-types.html" -> "/blog/values-never-change-types.md", - "/blog/2015/08/06/machinist.html" -> "/blog/machinist.md", - "/blog/2015/08/07/symbolic-operators.html" -> "/blog/symbolic-operators.md", - "/blog/2015/09/21/change-values.html" -> "/blog/change-values.md", - "/blog/2015/12/11/announcement_summit.html" -> "/blog/announcement-summit.md", - "/blog/2016/01/14/summit_assistance.html" -> "/blog/summit-assistance.md", - "/blog/2016/01/20/summit_keynote.html" -> "/blog/summit-keynote.md", - "/blog/2016/01/28/existential-inside.html" -> "/blog/existential-inside.md", - "/blog/2016/01/28/summit_programme.html" -> "/blog/summit-programme.md", - "/blog/2016/02/04/variance-and-functors.html" -> "/blog/variance-and-functors.md", - "/blog/2016/03/13/information-hiding.html" -> "/blog/information-hiding.md", - "/blog/2016/03/24/typelevel-boulder.html" -> "/blog/typelevel-boulder.md", - "/blog/2016/05/10/internal-state.html" -> "/blog/internal-state.md", - "/blog/2016/08/21/hkts-moving-forward.html" -> "/blog/hkts-moving-forward.md", - "/blog/2016/09/19/variance-phantom.html" -> "/blog/variance-phantom.md", - "/blog/2016/09/21/edsls-part-1.html" -> "/blog/edsls-part-1.md", - "/blog/2016/09/30/subtype-typeclasses.html" -> "/blog/subtype-typeclasses.md", - "/blog/2016/10/17/minicheck.html" -> "/blog/minicheck.md", - "/blog/2016/10/18/scala-center.html" -> "/blog/scala-center.md", - "/blog/2016/10/26/edsls-part-2.html" -> "/blog/edsls-part-2.md", - "/blog/2016/11/17/heaps.html" -> "/blog/heaps.md", - "/blog/2016/12/17/scala-coc.html" -> "/blog/scala-coc.md", - "/blog/2017/02/13/more-types-than-classes.html" -> "/blog/more-types-than-classes.md", - "/blog/2017/03/01/four-ways-to-escape-a-cake.html" -> "/blog/four-ways-to-escape-a-cake.md", - "/blog/2017/04/02/equivalence-vs-equality.html" -> "/blog/equivalence-vs-equality.md", - "/blog/2017/05/02/io-monad-for-cats.html" -> "/blog/io-monad-for-cats.md", - "/blog/2017/06/13/libra.html" -> "/blog/libra.md", - "/blog/2017/06/21/ciris.html" -> "/blog/ciris.md", - "/blog/2017/08/04/cats-1.0-mf.html" -> "/blog/cats-1.0-mf.md", - "/blog/2017/09/05/three-types-of-strings.html" -> "/blog/three-types-of-strings.md", - "/blog/2017/12/20/who-implements-typeclass.html" -> "/blog/who-implements-typeclass.md", - "/blog/2017/12/25/cats-1.0.0.html" -> "/blog/cats-1.0.0.md", - "/blog/2017/12/27/optimizing-final-tagless.html" -> "/blog/optimizing-final-tagless.md", - "/blog/2018/04/13/rethinking-monaderror.html" -> "/blog/rethinking-monaderror.md", - "/blog/2018/05/09/product-with-serializable.html" -> "/blog/product-with-serializable.md", - "/blog/2018/05/09/tagless-final-streaming.html" -> "/blog/tagless-final-streaming.md", - "/blog/2018/06/07/shared-state-in-fp.html" -> "/blog/shared-state-in-fp.md", - "/blog/2018/06/15/typedapi.html" -> "/blog/typedapi.md", - "/blog/2018/06/27/optimizing-tagless-final-2.html" -> "/blog/optimizing-tagless-final-2.md", - "/blog/2018/07/12/testing-in-the-wild.html" -> "/blog/testing-in-the-wild.md", - "/blog/2018/08/07/refactoring-monads.html" -> "/blog/refactoring-monads.md", - "/blog/2018/08/25/http4s-error-handling-mtl.html" -> "/blog/http4s-error-handling-mtl.md", - "/blog/2018/09/04/chain-replacing-the-list-monoid.html" -> "/blog/chain-replacing-the-list-monoid.md", - "/blog/2018/09/29/monad-transformer-variance.html" -> "/blog/monad-transformer-variance.md", - "/blog/2018/10/06/intro-to-mtl.html" -> "/blog/intro-to-mtl.md", - "/blog/2018/11/02/semirings.html" -> "/blog/semirings.md", - "/blog/2018/11/28/http4s-error-handling-mtl-2.html" -> "/blog/http4s-error-handling-mtl-2.md", - "/blog/2019/01/30/cats-ecosystem-community-survey-results.html" -> "/blog/cats-ecosystem-community-survey-results.md", - "/blog/2019/02/06/algebraic-api-design.html" -> "/blog/algebraic-api-design.md", - "/blog/2019/04/24/typelevel-sustainability-program-announcement.html" -> "/blog/typelevel-sustainability-program-announcement.md", - "/blog/2019/05/01/typelevel-switches-to-scala-code-of-conduct.html" -> "/blog/typelevel-switches-to-scala-code-of-conduct.md", - "/blog/2019/05/29/support-typelevel-thanks-to-triplequote-hydra.html" -> "/blog/support-typelevel-thanks-to-triplequote-hydra.md", - "/blog/2019/09/05/jdg.html" -> "/blog/jdg.md", - "/blog/2019/11/13/Update-about-sustainability-program.html" -> "/blog/update-about-sustainability-program.md", - "/blog/2020/06/17/confronting-racism.html" -> "/blog/confronting-racism.md", - "/blog/2020/10/30/concurrency-in-ce3.html" -> "/blog/concurrency-in-ce3.md", - "/blog/2021/02/21/fibers-fast-mkay.html" -> "/blog/fibers-fast-mkay.md", - "/blog/2021/04/27/community-safety.html" -> "/blog/community-safety.md", - "/blog/2021/05/05/discord-migration.html" -> "/blog/discord-migration.md", - "/blog/2021/11/15/on-recent-events.html" -> "/blog/on-recent-events.md", - "/blog/2022/01/19/governing-documents.html" -> "/blog/governing-documents.md", - "/blog/2022/04/01/call-for-steering-committee-members.html" -> "/blog/call-for-steering-committee-members.md", - "/blog/2022/07/25/welcoming-new-steering-committee-members.html" -> "/blog/welcoming-new-steering-committee-members.md", - "/blog/2022/09/06/new-website-layout.html" -> "/blog/new-website-layout.md", - "/blog/2022/09/12/tuple-announcement.html" -> "/blog/tuple-announcement.md", - "/blog/2022/09/19/typelevel-native.html" -> "/blog/typelevel-native.md", - "/blog/2022/11/10/fabric.html" -> "/blog/fabric.md", - "/blog/2023/02/23/gsoc.html" -> "/blog/gsoc-2023.md", - "/blog/2023/04/03/typelevel_toolkit.html" -> "/blog/typelevel-toolkit.md", - "/blog/2023/11/03/charter-changes.html" -> "/blog/charter-changes.md", - "/blog/2024/03/02/gsoc.html" -> "/blog/gsoc-2024.md", - "/blog/2024/03/10/github-seats.html" -> "/blog/github-seats.md", - "/blog/2024/03/11/code-of-conduct.html" -> "/blog/code-of-conduct.md", - "/blog/2024/08/24/call-for-code-of-conduct-committee-members.html" -> "/blog/call-for-code-of-conduct-committee-members.md", - "/blog/2024/11/21/new-code-of-conduct-committee-members.html" -> "/blog/new-code-of-conduct-committee-members.md", - "/blog/2024/12/22/gsoc24-going-feral-on-the-cloud.html" -> "/blog/gsoc24-going-feral-on-the-cloud.md", - "/blog/2025/02/21/spotify-foss-fund.html" -> "/blog/spotify-foss-fund.md", - "/blog/2025/02/27/gsoc.html" -> "/blog/gsoc-2025.md", - "/blog/2025/06/10/weaver-test-release.html" -> "/blog/weaver-test-release.md", - "/blog/2025/08/19/evolving-typelevel.html" -> "/blog/evolving-typelevel.md", - "/blog/2025/09/02/custom-error-types.html" -> "/blog/custom-error-types.md", - "/blog/governance/index.html" -> "/blog/README.md", - "/blog/social/index.html" -> "/blog/README.md", - "/blog/technical/index.html" -> "/blog/README.md", - "/blog/summits/index.html" -> "/blog/README.md", - "/blog/Update-about-sustainability-program.html" -> "/blog/update-about-sustainability-program.md", - "/code-of-conduct.html" -> "/code-of-conduct/README.md", - "/conduct.html" -> "/code-of-conduct/README.md", - "/event/2016-03-summit-philadelphia/index.html" -> "/blog/summit-philadelphia-2016-03-02.md", - "/event/2016-05-summit-oslo/index.html" -> "/blog/summit-oslo-2016-05-04.md", - "/event/2016-06-hackday/index.html" -> "/blog/hackday-2016-06-11.md", - "/event/2016-07-hackday/index.html" -> "/blog/hackday-2016-07-16.md", - "/event/2016-08-hackday/index.html" -> "/blog/hackday-2016-08-13.md", - "/event/2016-09-conf-cadiz/index.html" -> "/blog/conf-cadiz-2016-09-30.md", - "/event/2016-09-hackday/index.html" -> "/blog/hackday-2016-09-17.md", - "/event/2016-09-lake-district-workshop/index.html" -> "/blog/lake-district-workshop-2016-09-14.md", - "/event/2016-10-hackday/index.html" -> "/blog/hackday-2016-10-15.md", - "/event/2016-10-scala-io/index.html" -> "/blog/scala-io-2016-10-27.md", - "/event/2016-11-hackday/index.html" -> "/blog/hackday-2016-11-12.md", - "/event/2016-12-scalaxhack/index.html" -> "/blog/scalaxhack-2016-12-10.md", - "/event/2017-01-hackday/index.html" -> "/blog/hackday-2017-01-21.md", - "/event/2017-03-summit-nyc/index.html" -> "/blog/summit-nyc-2017-03-23.md", - "/event/2017-06-summit-copenhagen/index.html" -> "/blog/summit-copenhagen-2017-06-03.md", - "/event/2017-10-conf-cadiz/index.html" -> "/blog/conf-cadiz-2017-10-26.md", - "/event/2018-03-summit-boston/index.html" -> "/blog/summit-boston-2018-03-20.md", - "/event/2018-05-summit-berlin/index.html" -> "/blog/summit-berlin-2018-05-18.md", - "/event/2019-04-summit-philadelphia/index.html" -> "/blog/summit-philadelphia-2019-04-01.md", - "/event/2019-06-summit-lausanne/index.html" -> "/blog/summit-lausanne-2019-06-14.md", - "/event/2020-03-summit-nyc/index.html" -> "/blog/summit-nyc-2020-03-12.md", - "/event/2023-10-summit-nescala/index.html" -> "/blog/summit-nescala-2023-10-26.md", - "/event/2025-08-meetup-lausanne/index.html" -> "/blog/meetup-lausanne-2025-08-22.md", - "/events/index.html" -> "/blog/README.md", - "/gsoc/ideas/index.html" -> "/gsoc/ideas.md", - "/gsoc/projects/index.html" -> "/gsoc/README.md", - "/license/index.html" -> "/colophon.md", - "/platforms/index.html" -> "/projects/README.md", - "/platforms/js/index.html" -> "/projects/README.md", - "/platforms/jvm/index.html" -> "/projects/README.md", - "/platforms/native/index.html" -> "/projects/README.md", - "/projects/organization/index.html" -> "/projects/README.md", - "/projects/affiliate/index.html" -> "/projects/README.md", - "/steering-committee/index.html" -> "/foundation/people.md", - "/steering-committee.html" -> "/foundation/people.md" - ) -} diff --git a/code-of-conduct.html b/code-of-conduct.html new file mode 100644 index 00000000..bd579b06 --- /dev/null +++ b/code-of-conduct.html @@ -0,0 +1,4 @@ + + + + diff --git a/code-of-conduct/enforcement.html b/code-of-conduct/enforcement.html new file mode 100644 index 00000000..ec149237 --- /dev/null +++ b/code-of-conduct/enforcement.html @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Code of Conduct Committee Enforcement Procedures + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Typelevel Code of Conduct Committee Enforcement Procedures

+

This document summarizes the procedures the Typelevel Code of Conduct Committee uses to enforce the Code of Conduct.

+ +

Summary of processes

+

When the committee receives a report of a possible Code of Conduct violation, it will:

+
    +
  1. Acknowledge the receipt of the report.
  2. +
  3. Evaluate conflicts of interest.
  4. +
  5. Call a meeting of committee members without a conflict of interest.
  6. +
  7. Evaluate the reported incident.
  8. +
  9. Propose a behavior adjustment.
  10. +
  11. Propose consequences for the reported behavior.
  12. +
  13. Vote on behavior adjustment and consequences for the reported person.
  14. +
  15. Contact online community administrators/moderators to approve the behavior adjustment and consequences.
  16. +
  17. Follow up with the reported person.
  18. +
  19. Decide further responses.
  20. +
  21. Follow up with the reporter.
  22. +
+ +

Affiliate project processes

+

Typelevel affiliate projects are also covered by the Typelevel Code of Conduct. An affiliate project may choose to nominate dedicated moderators, who will work with the Typelevel Code of Conduct Committee to handle reports.

+

If a moderator of an affiliate project receives a report, they should:

+
    +
  1. Acknowledge the receipt of the report.
  2. +
  3. Contact the Code of Conduct Committee, stating "I have a report that involves [REPORTED PERSON]" so that Code of Conduct Committee members can evaluate conflicts of interest.
  4. +
  5. The reporting process will proceed as usual, with the affiliate moderator/s included as acting members of the Code of Conduct Committee.
  6. +
+

If the Code of Conduct Committee receives a report regarding an affiliate project, the affiliate moderator/s will be included in the conflict of interest evaluation. If no conflict exists, the affiliate moderator/s will be included in the reporting process as acting Code of Conduct committee members.

+ +

Moderator Processes

+

Typelevel Code of Conduct committee members, affiliate project moderators, and moderators in other spaces covered by the Typelevel Code of Conduct are empowered to enforce the Code of Conduct pro-actively. Code of Conduct violations should still be reported as a follow up, with a note about any action taken in the moment.

+ +

Acknowledge the report

+

Reporters should receive an acknowledgment of the receipt of their report within 24 hours, via their preferred communication channel.

+ +

Conflict of interest policy

+

Examples of conflicts of interest include but are not limited to the following circumstances, where the reporter or reported person is

+ +

Committee members do not need to state why they have a conflict of interest, only that one exists. Other committee members should not ask why the person has a conflict of interest.

+

Anyone who has a conflict of interest will remove themselves from the discussion of the incident, and recuse themselves from voting on a response to the report.

+ +

Evaluating a report

+ +

Jurisdiction

+ + +

Impact

+ + +

Risk

+ +

Reports which involve higher risk or higher impact may face more severe consequences than reports which involve lower risk or lower impact.

+ +

Propose a behavior adjustment

+

The committee will propose a concrete behavior adjustment that ensures the inappropriate behavior is not repeated. The committee will also discuss what actions may need to be taken if the reported person does not agree to the proposed behavioral adjustment.

+

What follows are examples of possible behavioral adjustments for incidents that occur in online spaces under the scope of this Code of Conduct. This behavioral adjustment list is not exhaustive, and the Typelevel Code of Conduct Committee reserves the right to take any action it deems necessary.

+ + +

Propose consequences

+

What follows are examples of possible consequences to an incident report. This consequences list is not exhaustive, and the Typelevel Code of Conduct Committee reserves the right to take any action it deems necessary.

+

Possible private responses to an incident include:

+ +

If deemed necessary for community safety, a public account of an incident, and consequences, may be published.

+ +

Committee vote

+

Some committee members may have a conflict of interest and may be excluded from discussions of a particular incident report. Excluding those members, decisions on the behavioral adjustment and consequences will be determined by a two-thirds majority vote of the Typelevel Code of Conduct Committee.

+ +

Administrators/moderators Communication

+

Once the committee has approved the proposed behavioral adjustment and consequences, they will communicate the recommended response to the Typelevel Foundation Board of Directors, and any specific administrators/moderators of the related community space (ex. Discord moderators, GitHub organization administrators, etc.) The committee should not state who reported this incident. They should attempt to anonymize any identifying information from the report.

+

Administrators/moderators are required to respond back with whether they accept the recommended response to the report. If they disagree with the recommended response, they should provide a detailed response or additional context as to why they disagree. Administrators/moderators are encouraged to respond within a week.

+

In cases where the administrators/moderators disagree on the suggested resolution for a report, the Typelevel Code of Conduct Committee shall notify the Typelevel Foundation Board of Directors.

+ +

Initial follow-up with the reported person

+

The Typelevel Code of Conduct Committee will draft a response to the reported person. + The response should contain:

+ +

The committee should not state who reported this incident. They should attempt to anonymize any identifying information from the report. The reported person should be discouraged from contacting the reporter to discuss the report.

+ +

Further responses

+

The reported person may respond with additional context. Depending on the response, the Typelevel Code of Conduct Committee may re-evaluate the proposed behavior adjustment and consequences. If the reported person wishes to apologize to the reporter, the committee can accept the apology on behalf of the reporter.

+ +

Follow-up with the reporter

+

A person who makes a report should receive a follow up, via their preferred communication channel, stating what action was taken in response to the report. If the committee decided no response was needed, they should explain why it was not a Code of Conduct violation. Reports that are not made in good faith (such as "reverse sexism" or "reverse racism") may receive no response.

+

The follow up should be sent no later than one week after the receipt of the report. If deliberation or follow up with the reported person takes longer than one week, the committee should send a status update to the reporter.

+ +

Documentation and Privacy Policies

+ +

Committee shared email address

+

It is convenient for all members of the Code of Conduct committee to be reached by a single email address. The committee should use an email alias which forwards email to individual members.

+

Using a mailing list is not recommended. This is because mailing lists typically archive all emails. This means new committee members gain access to all past archives. They can deliberately or accidentally see past reports where they have a conflict of interest. In order to prevent potential conflicts of interest, it is recommended to not have a mailing list archive.

+ +

Committee online discussion

+

The Code of Conduct Committee will use an encrypted service for online discussion and deliberation. The Committee may choose something that works for the majority of current members; two possible recommendations are Matrix or Signal.

+

When a report comes in and a discussion needs to happen in an online space, care needs to be taken to avoid conflicts of interest. In the committee chat channel, state 'We have a report that involves [REPORTED PERSON]'. Do not say who was the reporter or who were witnesses if the report was sent to an individual committee member. Ask which committee members do not have a conflict of interest. Add those committee members to a group discussion, separate from the committee channel. If a committee member does not respond, do not add them to the new group discussion. If a committee member finds they have a conflict of interest because of who reported the incident or who witnessed it, they should recuse themselves from the discussion.

+ +

Shared Documentation

+

The Code of Conduct committee should keep two types of shared documents:

+ + +

Status Spreadsheet

+

The spreadsheet for Typelevel Code of Conduct reports is linked in the private steering repository README. + Keep resolutions and notes vague enough that enforcement team members with a conflict of interest don't know the details of the incident. Use gender neutral language when describing the reported person in the spreadsheet.

+ +

Report Documentation

+

A template report document is linked in the status spreadsheet, as well as the private steering repository README. Report documentation must be encrypted to protect personally identifiable information (PII).

+ +

Privacy Concerns

+

There are some common privacy pitfalls to online tools like Google Docs. Make sure to always share the document with committee members who don't have a conflict of interest, rather than turning link sharing on. This prevents people outside of the committee from accessing the documents.

+

Another common issue is that when a folder is shared with the whole committee, even if a person doesn't have edit or view access to an individual report, they can still see the document's title. This can give information away, and that's why the report template instructs naming the document with a random phrase.

+

When on-boarding new committee members, they should be provided with a list of names of people who have been reported in a Code of Conduct incident. The new committee member should state whether they have any conflicts of interest with reviewing documentation for those cases. If not, they will be given access to the report documents.

+ +

Changes to Code of Conduct

+

When discussing a change to the Typelevel Code of Conduct or enforcement policies, the Typelevel Code of Conduct Committee will follow this decision-making process:

+ + +

Current list of Code of Conduct Committee members

+

Sam Pillsworth, Andrew Valencik, Kateu Herbert, Arman Bilge, Lucas Satabin

+ +

Attribution

+

This enforcement policy is a modified version of the Python Software Foundation Code of Conduct Enforcement Policy, part of the Python Software Foundation Code of Conduct, licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.

+ +
+ +
+ + + + + + diff --git a/code-of-conduct/index.html b/code-of-conduct/index.html new file mode 100644 index 00000000..9fd19dca --- /dev/null +++ b/code-of-conduct/index.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Code of Conduct + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Typelevel Code of Conduct

+

The Typelevel community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. + It is through these differences that our community experiences great successes and continued growth. + When you're working with members of the community, this Code of Conduct will help steer your interactions and keep Typelevel a positive, successful, and growing community. + Whether you are new or familiar with our community, we care about making it a welcoming and safe place for you and we're here to support you.

+ +

Our Community

+

Members of the Typelevel community are open, considerate, and respectful. + Behaviors that reinforce these values contribute to a positive environment, and include:

+ + +

Our Standards

+

Every member of our community has the right to have their identity respected. + The Typelevel community is dedicated to providing a positive experience for everyone, regardless of age, gender identity and expression, sexual orientation, disability, neurodivergence, physical appearance, body size, ethnicity, nationality, race, or religion (or lack thereof), education, or socio-economic status.

+ +

Inappropriate Behavior

+

Examples of unacceptable behavior by participants include:

+ +

Community members asked to stop any inappropriate behavior are expected to comply immediately.

+ +

Consequences

+

If a participant engages in behavior that violates our standards, the Typelevel Code of Conduct Committee will take any action they deem appropriate, including but not limited to: warning the offender, or expelling them from the community or current community events with no refund of event tickets.

+

The full list of consequences for inappropriate behavior is listed in the Enforcement Procedures.

+ +

Scope

+

The enforcement policies listed above apply to all official Typelevel channels, including but not limited to the following: mailing lists, both organization and affiliate GitHub repositories, Typelevel Discord server, and Typelevel venues and events. + If unaffiliated projects adopt the Typelevel Code of Conduct, please contact the maintainers of those projects for enforcement.

+ +

Contact

+

For questions related to our code of conduct, or to report possible violations, please immediately contact the Typelevel Code of Conduct Committee or one of its members:

+ + +

Attribution

+

This code of conduct is a modified version of the Python Software Foundation Code of Conduct, licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.

+

Additional language was incorporated from the following:

+ +
+ +
+ + + + + + diff --git a/colophon.html b/colophon.html new file mode 100644 index 00000000..497f31c4 --- /dev/null +++ b/colophon.html @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + Colophon + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Colophon

+

We build this website with our own Scala-based tooling! It is statically generated with Laika, a Typelevel project for transforming Markdown into HTML sites and e-books. The search bar is powered by Protosearch, an in-memory search library with advanced querying features. Both projects use Cats and other Typelevel libraries.

+

UI components are provided by the Bulma CSS framework and icons by Font Awesome. Mathematical expressions are rendered during the build with KaTeX running on GraalJS.

+

Finally, all of this is brought together in a Scala script that we use to deploy to GitHub Pages.

+

If you encounter a problem with our website or have feedback, please open an issue on the repository. We also welcome contributions!

+ +

License

+

In general, the content on this website is licensed under the Creative Commons Attribution 4.0 International License, except where otherwise noted.

+

Blog posts written before 2026 are licensed under the Creative Commons Attribution 3.0 Unported License.

+

The Typelevel logo is adapted from the "Progress" Pride Flag by Daniel Quasar and licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

+ +

In memoriam

+

Laika was created and lovingly maintained by Jens Halm. This website is possible thanks to his vision for a documentation tool that is native to our ecosystem. Jens raised the bar for open source stewardship: beyond the technical excellence of his work on Laika, he consistently published feature roadmaps, detailed issue and PR descriptions, and thorough documentation. Indeed, by creating a documentation tool that integrated so well with our tech stack, he has empowered all of us to become exemplary maintainers. Moreover, Jens' enthusiasm to support our community (including entertaining our numerous feature requests with in-depth responses full of context and design insights!) was his most generous gift to us.

+
+ +
+ + + + + + diff --git a/community/index.html b/community/index.html new file mode 100644 index 00000000..aa153067 --- /dev/null +++ b/community/index.html @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Community + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+ +

Join the Typelevel Community

+

Here are a few ways to participate in our community!

+ +
+
+
+
+ + + +

Discord Server

+
+

Our Discord server is a community hub where we hang out to learn functional programming, discuss project development, and share cat pics. We embrace curiosity, love teaching, and are not afraid to deep-dive into technical details. Please join and introduce yourself!

+
+ + Join Server + +
+
+
+
+
+
+ + + +

Virtual Meetups

+
+

Join our monthly virtual meetup! Learn about Typelevel projects and functional programming, ask questions in maintainer office hours, and make friends. Volunteer to give a presentation and practice your speaking skills in a friendly, supportive space.

+
+ + View Calendar + +
+
+
+
+
+
+ + + +

Affiliate Projects

+
+

Typelevel boasts an impressive ecosystem of affiliate projects, built by our community. These projects represent our broad interests across network protocols, streaming data, UX, AI/ML, and tooling. Publish your own project and apply to become an affiliate.

+
+ + Explore Projects + +
+
+
+
+
+
+ + + +

Code of Conduct

+
+

The Typelevel community is dedicated to providing a positive experience for everyone. Whether you are new or familiar with our community, we care about making it a welcoming and safe place for you and we are here to support you.

+
+ + Read the Code of Conduct + +
+
+
+
+
+ +
+ + + + + + diff --git a/community/meetups.html b/community/meetups.html new file mode 100644 index 00000000..453fcb21 --- /dev/null +++ b/community/meetups.html @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + Virtual Meetups + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Virtual Meetups

+

Every month, we host a friendly virtual meetup for the Typelevel Community! Each meetup begins with an icebreaker activity to get to know each other, followed by a presentation on a technical topic.

+

Our meetups are open to everyone! All participants must follow the Typelevel Code of Conduct. We do not record virtual meetups to respect the privacy of attendees while providing a relaxed, safe space to participate.

+

To receive notifications of future meetups subscribe to the calendar below or ask to join the Google Group.

+
+ +
+
+ +
+ + + + + + diff --git a/conduct.html b/conduct.html new file mode 100644 index 00000000..bd579b06 --- /dev/null +++ b/conduct.html @@ -0,0 +1,4 @@ + + + + diff --git a/css/code.css b/css/code.css new file mode 100644 index 00000000..b55e4cfe --- /dev/null +++ b/css/code.css @@ -0,0 +1,62 @@ +pre { + display: block; + background-color: var(--syntax-base1); + border-radius: 5px; + padding: 12px 9px 9px 15px; + margin: 0 0 var(--block-spacing); + line-height: 1.4; + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; +} +code { + font-family: var(--code-font); + color: var(--primary-color); + font-size: var(--code-font-size); + font-weight: 500; + white-space: nowrap; + padding: 0 0.1em; +} +a code { + color: inherit; +} +pre code { + color: var(--syntax-base5); + background-color: transparent; + padding: 0; + border: 0; + white-space: pre-wrap; +} + +code .identifier { + color: var(--syntax-base4); +} +code .tag-punctuation { + color: var(--syntax-base3); +} +code .comment, code .xml-cdata, code .markup-quote { + color: var(--syntax-base2); +} + +code .substitution, code .xml-processing-instruction, code .markup-emphasized, code .annotation { + color: var(--syntax-wheel1); +} +code .keyword, code .escape-sequence, code .markup-headline { + color: var(--syntax-wheel2); +} +code .attribute-name, .markup-link-target, code .declaration-name { + color: var(--syntax-wheel3); +} +code .number-literal, .string-literal, .literal-value, .boolean-literal, .char-literal, .symbol-literal, .regex-literal, .markup-link-text { + color: var(--syntax-wheel4); +} +code .type-name, code .tag-name, code .xml-dtd-tag-name, code .markup-fence { + color: var(--syntax-wheel5); +} + +code .diff-added { + background-color: rgb(0 175 0 / 40%); +} +code .diff-removed { + background-color: rgb(250 0 0 / 40%); +} diff --git a/event/2016-03-summit-philadelphia/index.html b/event/2016-03-summit-philadelphia/index.html new file mode 100644 index 00000000..ffd228d8 --- /dev/null +++ b/event/2016-03-summit-philadelphia/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-05-summit-oslo/index.html b/event/2016-05-summit-oslo/index.html new file mode 100644 index 00000000..ca5b98cc --- /dev/null +++ b/event/2016-05-summit-oslo/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-06-hackday/index.html b/event/2016-06-hackday/index.html new file mode 100644 index 00000000..4fc733ed --- /dev/null +++ b/event/2016-06-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-07-hackday/index.html b/event/2016-07-hackday/index.html new file mode 100644 index 00000000..14ad246c --- /dev/null +++ b/event/2016-07-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-08-hackday/index.html b/event/2016-08-hackday/index.html new file mode 100644 index 00000000..376a2a07 --- /dev/null +++ b/event/2016-08-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-09-conf-cadiz/index.html b/event/2016-09-conf-cadiz/index.html new file mode 100644 index 00000000..12509904 --- /dev/null +++ b/event/2016-09-conf-cadiz/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-09-hackday/index.html b/event/2016-09-hackday/index.html new file mode 100644 index 00000000..99c88fda --- /dev/null +++ b/event/2016-09-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-09-lake-district-workshop/index.html b/event/2016-09-lake-district-workshop/index.html new file mode 100644 index 00000000..0e4cd260 --- /dev/null +++ b/event/2016-09-lake-district-workshop/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-10-hackday/index.html b/event/2016-10-hackday/index.html new file mode 100644 index 00000000..ff2e77da --- /dev/null +++ b/event/2016-10-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-10-scala-io/index.html b/event/2016-10-scala-io/index.html new file mode 100644 index 00000000..37bb91ef --- /dev/null +++ b/event/2016-10-scala-io/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-11-hackday/index.html b/event/2016-11-hackday/index.html new file mode 100644 index 00000000..fe26f889 --- /dev/null +++ b/event/2016-11-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2016-12-scalaxhack/index.html b/event/2016-12-scalaxhack/index.html new file mode 100644 index 00000000..84986263 --- /dev/null +++ b/event/2016-12-scalaxhack/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2017-01-hackday/index.html b/event/2017-01-hackday/index.html new file mode 100644 index 00000000..ee6d0ce3 --- /dev/null +++ b/event/2017-01-hackday/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2017-03-summit-nyc/index.html b/event/2017-03-summit-nyc/index.html new file mode 100644 index 00000000..1abd9977 --- /dev/null +++ b/event/2017-03-summit-nyc/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2017-06-summit-copenhagen/index.html b/event/2017-06-summit-copenhagen/index.html new file mode 100644 index 00000000..c4c72670 --- /dev/null +++ b/event/2017-06-summit-copenhagen/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2017-10-conf-cadiz/index.html b/event/2017-10-conf-cadiz/index.html new file mode 100644 index 00000000..e7a780d4 --- /dev/null +++ b/event/2017-10-conf-cadiz/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2018-03-summit-boston/index.html b/event/2018-03-summit-boston/index.html new file mode 100644 index 00000000..dabae05c --- /dev/null +++ b/event/2018-03-summit-boston/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2018-05-summit-berlin/index.html b/event/2018-05-summit-berlin/index.html new file mode 100644 index 00000000..194da5e7 --- /dev/null +++ b/event/2018-05-summit-berlin/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2019-04-summit-philadelphia/index.html b/event/2019-04-summit-philadelphia/index.html new file mode 100644 index 00000000..da03e75e --- /dev/null +++ b/event/2019-04-summit-philadelphia/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2019-06-summit-lausanne/index.html b/event/2019-06-summit-lausanne/index.html new file mode 100644 index 00000000..0bfb0e06 --- /dev/null +++ b/event/2019-06-summit-lausanne/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2020-03-summit-nyc/index.html b/event/2020-03-summit-nyc/index.html new file mode 100644 index 00000000..a76b4888 --- /dev/null +++ b/event/2020-03-summit-nyc/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2023-10-summit-nescala/index.html b/event/2023-10-summit-nescala/index.html new file mode 100644 index 00000000..01f08464 --- /dev/null +++ b/event/2023-10-summit-nescala/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/event/2025-08-meetup-lausanne/index.html b/event/2025-08-meetup-lausanne/index.html new file mode 100644 index 00000000..fed5494b --- /dev/null +++ b/event/2025-08-meetup-lausanne/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/events/index.html b/events/index.html new file mode 100644 index 00000000..6537b04c --- /dev/null +++ b/events/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/foundation/index.html b/foundation/index.html new file mode 100644 index 00000000..535aaaac --- /dev/null +++ b/foundation/index.html @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + Typelevel Foundation + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+ +

About the Typelevel Foundation

+

The Typelevel Foundation is a nonprofit 501(c)(3) public charity (EIN: 39-3611111). Our mission is to:

+ +

We are committed to transparency and publish our governing documents, meeting minutes, and financial records.

+ +

Supporting the Foundation

+

If your organization relies on Typelevel projects, please consider financially sponsoring the Foundation to support our work. 100% of your donation is tax-deductible to the extent allowed by US law.

+
+
+ Donate + +
+
+

We also accept donations on GitHub Sponsors and Open Collective and are listed in employee giving platforms such as Benevity, CyberGrants, and YourCause.

+

German donors: we have partnered with the Maecenata Foundation to accept tax-deductible donations from Germany. Please use their donation form and be sure to designate TG26017 Typelevel Foundation as the recipient organization.

+

Swiss donors: we have partnered with the Swiss Philanthropy Foundation to accept tax-deductible donations from Switzerland. Please contact them at international@swissphilanthropy.ch to facilitate a donation to us.

+

UK donors: we have partnered with Global Giving UK to claim Gift Aid on donations from the United Kingdom. Please donate via our project page on their website and make sure to tick the box to add Gift Aid to your donation.

+

European donors: if your country participates in the Giving Europe network, we can accept tax-efficient donations from you. Please contact us to arrange this.

+

Have questions or specific needs? Please email us at donate@typelevel.org.

+ +

+ + + + Board of Directors +

+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + + +
+ Executive Director + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + + +
+ Director + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Justin du Coeur (aka Mark Waks) + + + +
+ Treasurer + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + +
+ Secretary + + +

+
+ +
+
+ +
+ +
+ +

+ + + + Foundation Sponsors +

+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ + + + + + diff --git a/foundation/people.html b/foundation/people.html new file mode 100644 index 00000000..bb5ad609 --- /dev/null +++ b/foundation/people.html @@ -0,0 +1,1181 @@ + + + + + + + + + + + + + + + + + + + + + + + Leadership + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+ +

Leadership

+ +

+ + + + Board of Directors +

+
+

The Board of Directors of the Typelevel Foundation is the governing body of Typelevel. Their primary responsibility is to provide oversight for the Foundation's operations, especially legal and financial matters.

+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Arman Bilge he/him + + + +
+ Executive Director + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + + +
+ Director + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Justin du Coeur (aka Mark Waks) + + + +
+ Treasurer + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + + +
+ Secretary + + +

+
+ +
+
+ +
+ +
+ +

+ + + + Technical Steering Committee +

+
+

The Technical Steering Committee supports the day-to-day open source work of Typelevel. They advise the Board on overall technical priorities for the Foundation, especially the designation and stewardship of Organization Projects, and are also responsible for curating the portfolio of Typelevel Affiliate Projects.

+
+
+ +
+ + +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Luka Jacobowitz + + +

+
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Michael Pilquist he/him + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Daniel Spiewak + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Justin du Coeur (aka Mark Waks) + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + +

+
+ +
+
+ +
+ +
+ +

+ + + + Code of Conduct Committee +

+
+

The Code of Conduct Committee upholds the Typelevel Code of Conduct following the procedures described in the Enforcement Policy. Members of this committee have completed code of conduct enforcement training with Otter Technology.

+
+
+ +
+ + +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Herbert Kateu + + +

+
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Lucas Satabin he/him + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Andrew Valencik + + +

+
+ +
+
+ +
+ +
+ +

+ + + + Security Team +

+
+

The Security Team receives and handles reports of security issues following the procedures described in the Typelevel Security Policy.

+
+
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Ross A. Baker he/him + + +

+
+ +
+
+ +
+ +
+ + +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Brian P. Holt he/him + + +

+
+ +
+
+ +
+ +
+
+ +
+

+ +

+
+ +
+
+

+ Antonio Jimenez he/him + + +

+
+ +
+
+ +
+ +
+ + +
+ + + + + + + diff --git a/src/funding.json b/funding.json similarity index 100% rename from src/funding.json rename to funding.json diff --git a/gsoc/ai.html b/gsoc/ai.html new file mode 100644 index 00000000..0140a097 --- /dev/null +++ b/gsoc/ai.html @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + GSoC AI Policy + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Google Summer of Code

+ +
+ +
+ +
+

Typelevel GSoC AI Policy

+

Our AI policy is based on the following ideas.

+
    +
  • +

    Respect maintainer time. Our maintainers and GSoC mentors are volunteers who invest many hours reviewing open source contributions to ensure that Typelevel projects have high-quality, maintainable codebases. Please respect their time by submitting your best work.

    +
  • +
  • +

    GSoC is about learning first, delivering second. We prefer that you take your time and ask for help when contributing to our projects, rather than using AI to quickly write code that may work, but you do not understand. Our midterm and final evaluations of your work will consider how you grew as an open source contributor, in addition to what you created.

    +
  • +
  • +

    AI has become a part of the contemporary developer's toolkit. We do not want to blanket ban the use of AI. We recognize its value when used thoughtfully and want to support you in learning when and how to use AI effectively.

    +
  • +
+

With these principles in mind, to contribute to Typelevel as part of GSoC we ask you to follow these rules.

+
    +
  1. +

    Demonstrate ownership of your code, no matter how you wrote it. This means understanding why it is appropriate to Typelevel's needs and how it works, being capable of explaining that, and also creating and performing tests to confirm that it works correctly, both for yourself and for your collaborators. It is okay to ask for help!

    +
  2. +
  3. +

    Never use AI to generate descriptions for PRs or issues. Similarly, please do not use AI to write your GSoC proposal or other communications such as email and Discord messages. The purpose of writing in these contexts is to organize your thoughts and directly communicate your ideas, not to craft a "polished" document. However, you may use AI to translate between English and another (natural) language.

    +
  4. +
  5. +

    Always communicate when you have used AI to write code, typically in your PR description. It is also helpful to explain where and how you used it, such as by including any prompts.

    +
  6. +
+
+
+ +
+ + + + + + diff --git a/gsoc/events.html b/gsoc/events.html new file mode 100644 index 00000000..2966c581 --- /dev/null +++ b/gsoc/events.html @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + GSoC Events + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Google Summer of Code

+ +
+ +
+ +
+

Events

+

Join one of our virtual events to learn more about Typelevel and how you can get involved.

+ +

Past events

+

We publish recordings of past events to our YouTube Channel.

+ + +

Calendar

+
+ +
+
+
+ +
+ + + + + + diff --git a/gsoc/ideas.html b/gsoc/ideas.html new file mode 100644 index 00000000..1e0a66cd --- /dev/null +++ b/gsoc/ideas.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + + + + + + + GSoC Ideas + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Google Summer of Code

+ +
+ +
+ +
+

Our community has identified project ideas that we believe will significantly enhance the Typelevel ecosystem. Nothing is set in stone: we may be able to adjust a project’s length and difficulty to make it the right fit for you. So if you see something here that interests you or have an idea of your own, please get in touch!

+
+ +
+ +
+
+
+
+

Serverless integrations for Feral

+
+ + cloud + + programming languages + +
+

Feral is a Typelevel library for building serverless functions that currently supports AWS Lambda and Google Cloud Run Functions. We want to add support for more types of serverless events and more cloud providers.

+

Prerequisites
Scala, ideally experience with serverless

+

Expected Difficulty
Medium.

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@armanbilge @bpholt @Chingles2404

+

Related Repositories
feral

+
+
+
+
+ +
+
+
+
+

Native I/O backend for FS2 JVM

+
+ + operating systems + + programming languages + +
+

FS2 on the JVM currently implements its networking API using JDK NIO. Unfortunately this indirection incurs a non-trivial performance penalty. We want to replace the use of JDK NIO with direct calls to system I/O APIs such as epoll and kqueue.

+

Prerequisites
Scala, ability to read C

+

Expected Difficulty
Medium.

+

Expected Length
Long (~ 350 hours)

+

Mentors
@antoniojimeneznieto @djspiewak @mpilquist @armanbilge

+

Related Repositories
fs2

+
+
+
+
+ +
+
+
+
+

FS2 Connection API

+
+ + operating systems + + programming languages + +
+

TCP-based protocols are common (e.g. HTTP, Postgres, Redis) and are implemented by clients to interface with these services (e.g. Ember, Skunk, Rediculous). The goal of this project is to create a connection API that supports pooling, error conditions, and metrics and can be shared by all of our client libraries.

+

Prerequisites
Scala, ideally some knowledge of networking

+

Expected Difficulty
Hard.

+

Expected Length
Long (~ 350 hours)

+

Mentors
@mpilquist @armanbilge

+

Related Repositories
fs2

+
+
+
+
+ +
+
+
+
+

Web Components for Calico

+
+ + web + + programming languages + +
+

Calico is a reactive UI library built with Cats Effect and FS2. Web Components are a standard for creating framework-agnostic, reusable UI elements. The goal of this project is to enable Calico users to access the vast array of web components available by improving its DSL and code-generation.

+

Prerequisites
Scala, ideally experience with Web APIs

+

Expected Difficulty
Medium.

+

Expected Length
Long (~ 350 hours)

+

Mentors
@armanbilge

+

Related Repositories
calico

+
+
+
+
+ +
+
+
+
+

Upgrade sbt-typelevel to sbt 2

+
+ + development tools + +
+

sbt-typelevel is a plugin for sbt, the Scala build tool, used by hundreds of open source and enterprise projects. sbt 2 is in the final stages of development. We want to upgrade sbt-typelevel to sbt 2 and adopt its new features, such as project matrix for cross-building.

+

Prerequisites
Scala

+

Expected Difficulty
Medium.

+

Expected Length
Long (~ 350 hours)

+

Mentors
@mzuehlke @armanbilge

+

Related Repositories
sbt-typelevel

+
+
+
+
+ +
+
+
+
+

Refresh Davenverse projects

+
+ + development tools + + programming languages + +
+

The Davenverse is a collection of several popular Typelevel libraries, including Mules and cats-scalacheck. Unfortunately, we have fallen behind on their maintenance. We want to move these libraries under the Typelevel org, refresh their build tooling, and bring them up-to-date to ensure their longevity.

+

Prerequisites
Scala

+

Expected Difficulty
Medium.

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@armanbilge @valencik

+

Related Repositories
davenverse

+
+
+
+
+ +
+
+
+
+

Cats Effect & FS2 on Wasm/WASI

+
+ + web + + cloud + + operating systems + + programming languages + +
+

Web Assembly and its System Interface are emerging technologies for deploying secure, modular applications. The goal of this project is to prototype porting the Cats Effect runtime and FS2 streaming I/O to the Wasm/WASI platform, also possibly generating feedback for the Scala WASM and WASI teams.

+

Prerequisites
Scala, ideally some experience with Wasm/WASI

+

Expected Difficulty
Hard. Wasm/WASI support in Scala is experimental.

+

Expected Length
Long (~ 350 hours)

+

Mentors
@armanbilge @tanishiking @valencik

+

Related Repositories
cats-effect fs2

+
+
+
+
+ +
+
+
+
+

Laika enhancements for typelevel.org

+
+ + web + + programming languages + +
+

Laika is a purely functional site and e-book generator and customizable text markup transformer. We recently migrated the Typelevel website from Jekyll to Laika. The goal of this project is improve and streamline Laika's support for generating non-documentation websites, such as blogs.

+

Prerequisites
Scala

+

Expected Difficulty
Medium.

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@armanbilge @valencik

+

Related Repositories
Laika typelevel.org

+
+
+
+
+ +
+
+
+
+

A faster immutable list datatype

+
+ + web + + programming languages + +
+

Immutable linked lists are a core datatype in functional programming languages. The goal of this project is to explore implementing a list-like datatype with enhanced performance. Along the way, you will learn about algebraic datatypes, Cats typeclasses, and mechanical sympathy.

+

Prerequisites
Interest in functional programming

+

Expected Difficulty
Medium. This is a good project for beginners!

+

Expected Length
Long (~ 350 hours)

+

Mentors
@armanbilge @johnynek

+

Related Repositories
Cats Collections

+
+
+
+
+ +
+
+
+
+

Doodle Immediate Mode Algebra

+
+ + media + + programming languages + +
+

Design and implement an API for Doodle that allows low-level bitmap based operations.

+

Prerequisites
Proficient with Scala and some understanding of computer graphics.

+

Expected Difficulty
Medium

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@noelwelsh

+

Related Repositories
Doodle

+
+
+
+
+ +
+
+
+
+

Doodle Skia Backend

+
+ + media + + programming languages + +
+

Add a Skia backend to Doodle, greatly improving the performance and expressivity available on the JVM.

+

Prerequisites
Proficient with Scala and some understanding of computer graphics. This involves working with a library (Skija) that is not well documented and itself wraps a C++ library with patchy documentation. Hence a willingness to dive into foreign code bases is necessary.

+

Expected Difficulty
Medium

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@noelwelsh

+

Related Repositories
Doodle

+
+
+
+
+ +
+
+
+
+

Krop Template System

+
+ + web + + programming languages + +
+

Add a template system to the Krop web framework that works client- and server-side.

+

Prerequisites
Proficient with Scala and a reasonable understanding of parsing.

+

Expected Difficulty
Medium

+

Expected Length
Medium (~ 175 hours)

+

Mentors
@noelwelsh @j-mie6

+

Related Repositories
Krop

+
+
+
+
+ +
+
+ +
+
+
+

Are you interested in working on a GSoC project with mentorship from Typelevel maintainers?

+
+ Submit Proposal +
+
+ + +
+ + + + + + diff --git a/gsoc/ideas/index.html b/gsoc/ideas/index.html new file mode 100644 index 00000000..fc441d28 --- /dev/null +++ b/gsoc/ideas/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/gsoc/index.html b/gsoc/index.html new file mode 100644 index 00000000..99da0772 --- /dev/null +++ b/gsoc/index.html @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + Google Summer of Code + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+

Google Summer of Code

+ +
+ +
+ +
+

Welcome!

+

Typelevel is an ecosystem of projects and a community of people united to foster an inclusive, welcoming, and safe environment around functional programming in Scala. + We work together to develop projects that apply functional programming to challenging problems relevant in industry. + Our community culture embraces curiosity and mentoring and we don’t shy away from experimenting with new and exciting ideas. + Most of all, we love to make programming joyful and social.

+

Due to overwhelming participation in GSoC 2026, we are only able to consider proposals from applicants who complete our onboarding process by Monday, March 16th. + If you missed this deadline, we appreciate your interest and hope you will apply next year!

+ +

Getting Started

+

We are excited to be a Mentoring Organization in Google Summer of Code 2026! If you are interested to join Typelevel as a GSoC Contributor, here are some ways to get started:

+ +

We cannot wait to meet you! + You can also reach us at gsoc@typelevel.org with any questions.

+ +

Learning Resources

+

To learn Scala and functional programming, we recommend these books.

+ +
+
+ +
+ + + + + + diff --git a/gsoc/projects/index.html b/gsoc/projects/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/gsoc/projects/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/img/apple-touch-icon.png b/img/apple-touch-icon.png similarity index 100% rename from src/img/apple-touch-icon.png rename to img/apple-touch-icon.png diff --git a/src/img/authors/arman.jpg b/img/authors/arman.jpg similarity index 100% rename from src/img/authors/arman.jpg rename to img/authors/arman.jpg diff --git a/src/img/authors/hkateu.jpg b/img/authors/hkateu.jpg similarity index 100% rename from src/img/authors/hkateu.jpg rename to img/authors/hkateu.jpg diff --git a/src/img/favicon.ico b/img/favicon.ico similarity index 100% rename from src/img/favicon.ico rename to img/favicon.ico diff --git a/src/img/favicon.svg b/img/favicon.svg similarity index 100% rename from src/img/favicon.svg rename to img/favicon.svg diff --git a/src/img/logo.png b/img/logo.png similarity index 100% rename from src/img/logo.png rename to img/logo.png diff --git a/src/img/logo.svg b/img/logo.svg similarity index 100% rename from src/img/logo.svg rename to img/logo.svg diff --git a/src/img/media/2018-survey/3ne70PU.png b/img/media/2018-survey/3ne70PU.png similarity index 100% rename from src/img/media/2018-survey/3ne70PU.png rename to img/media/2018-survey/3ne70PU.png diff --git a/src/img/media/2018-survey/5WuSHVP.png b/img/media/2018-survey/5WuSHVP.png similarity index 100% rename from src/img/media/2018-survey/5WuSHVP.png rename to img/media/2018-survey/5WuSHVP.png diff --git a/src/img/media/2018-survey/8VbOtFS.png b/img/media/2018-survey/8VbOtFS.png similarity index 100% rename from src/img/media/2018-survey/8VbOtFS.png rename to img/media/2018-survey/8VbOtFS.png diff --git a/src/img/media/2018-survey/BA1ShtM.png b/img/media/2018-survey/BA1ShtM.png similarity index 100% rename from src/img/media/2018-survey/BA1ShtM.png rename to img/media/2018-survey/BA1ShtM.png diff --git a/src/img/media/2018-survey/C5OdxEK.png b/img/media/2018-survey/C5OdxEK.png similarity index 100% rename from src/img/media/2018-survey/C5OdxEK.png rename to img/media/2018-survey/C5OdxEK.png diff --git a/src/img/media/2018-survey/C60Td4Q.png b/img/media/2018-survey/C60Td4Q.png similarity index 100% rename from src/img/media/2018-survey/C60Td4Q.png rename to img/media/2018-survey/C60Td4Q.png diff --git a/src/img/media/2018-survey/DoZYt4i.png b/img/media/2018-survey/DoZYt4i.png similarity index 100% rename from src/img/media/2018-survey/DoZYt4i.png rename to img/media/2018-survey/DoZYt4i.png diff --git a/src/img/media/2018-survey/JjK3muU.png b/img/media/2018-survey/JjK3muU.png similarity index 100% rename from src/img/media/2018-survey/JjK3muU.png rename to img/media/2018-survey/JjK3muU.png diff --git a/src/img/media/2018-survey/TiDFfzZ.png b/img/media/2018-survey/TiDFfzZ.png similarity index 100% rename from src/img/media/2018-survey/TiDFfzZ.png rename to img/media/2018-survey/TiDFfzZ.png diff --git a/src/img/media/2018-survey/XpVHZar.png b/img/media/2018-survey/XpVHZar.png similarity index 100% rename from src/img/media/2018-survey/XpVHZar.png rename to img/media/2018-survey/XpVHZar.png diff --git a/src/img/media/2018-survey/YDmhHTo.png b/img/media/2018-survey/YDmhHTo.png similarity index 100% rename from src/img/media/2018-survey/YDmhHTo.png rename to img/media/2018-survey/YDmhHTo.png diff --git a/src/img/media/2018-survey/cOhZ8W3.png b/img/media/2018-survey/cOhZ8W3.png similarity index 100% rename from src/img/media/2018-survey/cOhZ8W3.png rename to img/media/2018-survey/cOhZ8W3.png diff --git a/src/img/media/2018-survey/evmpmZK.png b/img/media/2018-survey/evmpmZK.png similarity index 100% rename from src/img/media/2018-survey/evmpmZK.png rename to img/media/2018-survey/evmpmZK.png diff --git a/src/img/media/2018-survey/mNbUBxW.png b/img/media/2018-survey/mNbUBxW.png similarity index 100% rename from src/img/media/2018-survey/mNbUBxW.png rename to img/media/2018-survey/mNbUBxW.png diff --git a/src/img/media/2018-survey/q2WjZ1d.png b/img/media/2018-survey/q2WjZ1d.png similarity index 100% rename from src/img/media/2018-survey/q2WjZ1d.png rename to img/media/2018-survey/q2WjZ1d.png diff --git a/src/img/media/2018-survey/slzrAHi.png b/img/media/2018-survey/slzrAHi.png similarity index 100% rename from src/img/media/2018-survey/slzrAHi.png rename to img/media/2018-survey/slzrAHi.png diff --git a/src/img/media/2018-survey/uqUxPWf.png b/img/media/2018-survey/uqUxPWf.png similarity index 100% rename from src/img/media/2018-survey/uqUxPWf.png rename to img/media/2018-survey/uqUxPWf.png diff --git a/src/img/media/2018-survey/xGjcmj6.png b/img/media/2018-survey/xGjcmj6.png similarity index 100% rename from src/img/media/2018-survey/xGjcmj6.png rename to img/media/2018-survey/xGjcmj6.png diff --git a/src/img/media/cats-effect-diagram.png b/img/media/cats-effect-diagram.png similarity index 100% rename from src/img/media/cats-effect-diagram.png rename to img/media/cats-effect-diagram.png diff --git a/src/img/media/fibers/async.png b/img/media/fibers/async.png similarity index 100% rename from src/img/media/fibers/async.png rename to img/media/fibers/async.png diff --git a/src/img/media/fibers/few-threads.png b/img/media/fibers/few-threads.png similarity index 100% rename from src/img/media/fibers/few-threads.png rename to img/media/fibers/few-threads.png diff --git a/src/img/media/fibers/fibers.png b/img/media/fibers/fibers.png similarity index 100% rename from src/img/media/fibers/fibers.png rename to img/media/fibers/fibers.png diff --git a/src/img/media/fibers/many-threads.png b/img/media/fibers/many-threads.png similarity index 100% rename from src/img/media/fibers/many-threads.png rename to img/media/fibers/many-threads.png diff --git a/src/img/media/fibers/overhead.png b/img/media/fibers/overhead.png similarity index 100% rename from src/img/media/fibers/overhead.png rename to img/media/fibers/overhead.png diff --git a/src/img/media/fibers/work-stealing.png b/img/media/fibers/work-stealing.png similarity index 100% rename from src/img/media/fibers/work-stealing.png rename to img/media/fibers/work-stealing.png diff --git a/src/img/media/hackday-thumb.jpg b/img/media/hackday-thumb.jpg similarity index 100% rename from src/img/media/hackday-thumb.jpg rename to img/media/hackday-thumb.jpg diff --git a/src/img/media/hackday.jpg b/img/media/hackday.jpg similarity index 100% rename from src/img/media/hackday.jpg rename to img/media/hackday.jpg diff --git a/src/img/media/highscore.png b/img/media/highscore.png similarity index 100% rename from src/img/media/highscore.png rename to img/media/highscore.png diff --git a/src/img/media/hkt-inflection.png b/img/media/hkt-inflection.png similarity index 100% rename from src/img/media/hkt-inflection.png rename to img/media/hkt-inflection.png diff --git a/src/img/media/ieoti-duzzle.png b/img/media/ieoti-duzzle.png similarity index 100% rename from src/img/media/ieoti-duzzle.png rename to img/media/ieoti-duzzle.png diff --git a/src/img/media/ieoti-fizzle.png b/img/media/ieoti-fizzle.png similarity index 100% rename from src/img/media/ieoti-fizzle.png rename to img/media/ieoti-fizzle.png diff --git a/src/img/media/ieoti-wazzle.png b/img/media/ieoti-wazzle.png similarity index 100% rename from src/img/media/ieoti-wazzle.png rename to img/media/ieoti-wazzle.png diff --git a/src/img/media/nescala-hero-thumb.jpg b/img/media/nescala-hero-thumb.jpg similarity index 100% rename from src/img/media/nescala-hero-thumb.jpg rename to img/media/nescala-hero-thumb.jpg diff --git a/src/img/media/nescala-hero.jpg b/img/media/nescala-hero.jpg similarity index 100% rename from src/img/media/nescala-hero.jpg rename to img/media/nescala-hero.jpg diff --git a/src/img/media/samegame.png b/img/media/samegame.png similarity index 100% rename from src/img/media/samegame.png rename to img/media/samegame.png diff --git a/src/img/media/speakers/aaronlevin.jpg b/img/media/speakers/aaronlevin.jpg similarity index 100% rename from src/img/media/speakers/aaronlevin.jpg rename to img/media/speakers/aaronlevin.jpg diff --git a/src/img/media/speakers/adelbertchang.jpeg b/img/media/speakers/adelbertchang.jpeg similarity index 100% rename from src/img/media/speakers/adelbertchang.jpeg rename to img/media/speakers/adelbertchang.jpeg diff --git a/src/img/media/speakers/alejandrogomez.jpg b/img/media/speakers/alejandrogomez.jpg similarity index 100% rename from src/img/media/speakers/alejandrogomez.jpg rename to img/media/speakers/alejandrogomez.jpg diff --git a/src/img/media/speakers/alexandrunedelcu.jpg b/img/media/speakers/alexandrunedelcu.jpg similarity index 100% rename from src/img/media/speakers/alexandrunedelcu.jpg rename to img/media/speakers/alexandrunedelcu.jpg diff --git a/src/img/media/speakers/andreamagnorsky.jpg b/img/media/speakers/andreamagnorsky.jpg similarity index 100% rename from src/img/media/speakers/andreamagnorsky.jpg rename to img/media/speakers/andreamagnorsky.jpg diff --git a/src/img/media/speakers/annettebieniusa.jpg b/img/media/speakers/annettebieniusa.jpg similarity index 100% rename from src/img/media/speakers/annettebieniusa.jpg rename to img/media/speakers/annettebieniusa.jpg diff --git a/src/img/media/speakers/battermann.jpg b/img/media/speakers/battermann.jpg similarity index 100% rename from src/img/media/speakers/battermann.jpg rename to img/media/speakers/battermann.jpg diff --git a/src/img/media/speakers/cameronjoannidis.jpg b/img/media/speakers/cameronjoannidis.jpg similarity index 100% rename from src/img/media/speakers/cameronjoannidis.jpg rename to img/media/speakers/cameronjoannidis.jpg diff --git a/src/img/media/speakers/chrisdavenport.jpg b/img/media/speakers/chrisdavenport.jpg similarity index 100% rename from src/img/media/speakers/chrisdavenport.jpg rename to img/media/speakers/chrisdavenport.jpg diff --git a/src/img/media/speakers/chrishodapp.jpg b/img/media/speakers/chrishodapp.jpg similarity index 100% rename from src/img/media/speakers/chrishodapp.jpg rename to img/media/speakers/chrishodapp.jpg diff --git a/src/img/media/speakers/chrismyers.jpg b/img/media/speakers/chrismyers.jpg similarity index 100% rename from src/img/media/speakers/chrismyers.jpg rename to img/media/speakers/chrismyers.jpg diff --git a/src/img/media/speakers/chrisvogt.jpg b/img/media/speakers/chrisvogt.jpg similarity index 100% rename from src/img/media/speakers/chrisvogt.jpg rename to img/media/speakers/chrisvogt.jpg diff --git a/src/img/media/speakers/dalewijnand.jpg b/img/media/speakers/dalewijnand.jpg similarity index 100% rename from src/img/media/speakers/dalewijnand.jpg rename to img/media/speakers/dalewijnand.jpg diff --git a/src/img/media/speakers/danielasfregola.png b/img/media/speakers/danielasfregola.png similarity index 100% rename from src/img/media/speakers/danielasfregola.png rename to img/media/speakers/danielasfregola.png diff --git a/src/img/media/speakers/danielspiewak.jpg b/img/media/speakers/danielspiewak.jpg similarity index 100% rename from src/img/media/speakers/danielspiewak.jpg rename to img/media/speakers/danielspiewak.jpg diff --git a/src/img/media/speakers/davecleaver.jpg b/img/media/speakers/davecleaver.jpg similarity index 100% rename from src/img/media/speakers/davecleaver.jpg rename to img/media/speakers/davecleaver.jpg diff --git a/src/img/media/speakers/davegurnell.jpg b/img/media/speakers/davegurnell.jpg similarity index 100% rename from src/img/media/speakers/davegurnell.jpg rename to img/media/speakers/davegurnell.jpg diff --git a/src/img/media/speakers/denisrosset.png b/img/media/speakers/denisrosset.png similarity index 100% rename from src/img/media/speakers/denisrosset.png rename to img/media/speakers/denisrosset.png diff --git a/src/img/media/speakers/diegoalonso.png b/img/media/speakers/diegoalonso.png similarity index 100% rename from src/img/media/speakers/diegoalonso.png rename to img/media/speakers/diegoalonso.png diff --git a/src/img/media/speakers/dorothyordogh.jpg b/img/media/speakers/dorothyordogh.jpg similarity index 100% rename from src/img/media/speakers/dorothyordogh.jpg rename to img/media/speakers/dorothyordogh.jpg diff --git a/src/img/media/speakers/edmundnoble.jpg b/img/media/speakers/edmundnoble.jpg similarity index 100% rename from src/img/media/speakers/edmundnoble.jpg rename to img/media/speakers/edmundnoble.jpg diff --git a/src/img/media/speakers/erikosheim.jpg b/img/media/speakers/erikosheim.jpg similarity index 100% rename from src/img/media/speakers/erikosheim.jpg rename to img/media/speakers/erikosheim.jpg diff --git a/src/img/media/speakers/etorreborre.jpg b/img/media/speakers/etorreborre.jpg similarity index 100% rename from src/img/media/speakers/etorreborre.jpg rename to img/media/speakers/etorreborre.jpg diff --git a/src/img/media/speakers/eugeneplatonov.jpg b/img/media/speakers/eugeneplatonov.jpg similarity index 100% rename from src/img/media/speakers/eugeneplatonov.jpg rename to img/media/speakers/eugeneplatonov.jpg diff --git a/src/img/media/speakers/eugeniacheng.jpg b/img/media/speakers/eugeniacheng.jpg similarity index 100% rename from src/img/media/speakers/eugeniacheng.jpg rename to img/media/speakers/eugeniacheng.jpg diff --git a/src/img/media/speakers/fabiolabella.jpeg b/img/media/speakers/fabiolabella.jpeg similarity index 100% rename from src/img/media/speakers/fabiolabella.jpeg rename to img/media/speakers/fabiolabella.jpeg diff --git a/src/img/media/speakers/felixmulder.jpg b/img/media/speakers/felixmulder.jpg similarity index 100% rename from src/img/media/speakers/felixmulder.jpg rename to img/media/speakers/felixmulder.jpg diff --git a/src/img/media/speakers/frankthomas.png b/img/media/speakers/frankthomas.png similarity index 100% rename from src/img/media/speakers/frankthomas.png rename to img/media/speakers/frankthomas.png diff --git a/src/img/media/speakers/gregpfeil.jpg b/img/media/speakers/gregpfeil.jpg similarity index 100% rename from src/img/media/speakers/gregpfeil.jpg rename to img/media/speakers/gregpfeil.jpg diff --git a/src/img/media/speakers/guillaumebort.jpg b/img/media/speakers/guillaumebort.jpg similarity index 100% rename from src/img/media/speakers/guillaumebort.jpg rename to img/media/speakers/guillaumebort.jpg diff --git a/src/img/media/speakers/guillaumemartres.jpg b/img/media/speakers/guillaumemartres.jpg similarity index 100% rename from src/img/media/speakers/guillaumemartres.jpg rename to img/media/speakers/guillaumemartres.jpg diff --git a/src/img/media/speakers/gvolpe.jpg b/img/media/speakers/gvolpe.jpg similarity index 100% rename from src/img/media/speakers/gvolpe.jpg rename to img/media/speakers/gvolpe.jpg diff --git a/src/img/media/speakers/harrylaoulakos.png b/img/media/speakers/harrylaoulakos.png similarity index 100% rename from src/img/media/speakers/harrylaoulakos.png rename to img/media/speakers/harrylaoulakos.png diff --git a/src/img/media/speakers/ionutstan.png b/img/media/speakers/ionutstan.png similarity index 100% rename from src/img/media/speakers/ionutstan.png rename to img/media/speakers/ionutstan.png diff --git a/src/img/media/speakers/itamarravid.jpg b/img/media/speakers/itamarravid.jpg similarity index 100% rename from src/img/media/speakers/itamarravid.jpg rename to img/media/speakers/itamarravid.jpg diff --git a/src/img/media/speakers/janouwens.jpg b/img/media/speakers/janouwens.jpg similarity index 100% rename from src/img/media/speakers/janouwens.jpg rename to img/media/speakers/janouwens.jpg diff --git a/src/img/media/speakers/jefersonossa.jpg b/img/media/speakers/jefersonossa.jpg similarity index 100% rename from src/img/media/speakers/jefersonossa.jpg rename to img/media/speakers/jefersonossa.jpg diff --git a/src/img/media/speakers/jonathanmerritt.jpg b/img/media/speakers/jonathanmerritt.jpg similarity index 100% rename from src/img/media/speakers/jonathanmerritt.jpg rename to img/media/speakers/jonathanmerritt.jpg diff --git a/src/img/media/speakers/jonpretty.jpg b/img/media/speakers/jonpretty.jpg similarity index 100% rename from src/img/media/speakers/jonpretty.jpg rename to img/media/speakers/jonpretty.jpg diff --git a/src/img/media/speakers/julienrf.jpg b/img/media/speakers/julienrf.jpg similarity index 100% rename from src/img/media/speakers/julienrf.jpg rename to img/media/speakers/julienrf.jpg diff --git a/src/img/media/speakers/kathifisler.jpg b/img/media/speakers/kathifisler.jpg similarity index 100% rename from src/img/media/speakers/kathifisler.jpg rename to img/media/speakers/kathifisler.jpg diff --git a/src/img/media/speakers/kenscambler.jpg b/img/media/speakers/kenscambler.jpg similarity index 100% rename from src/img/media/speakers/kenscambler.jpg rename to img/media/speakers/kenscambler.jpg diff --git a/src/img/media/speakers/kristinasojakova.jpg b/img/media/speakers/kristinasojakova.jpg similarity index 100% rename from src/img/media/speakers/kristinasojakova.jpg rename to img/media/speakers/kristinasojakova.jpg diff --git a/src/img/media/speakers/longcao.jpg b/img/media/speakers/longcao.jpg similarity index 100% rename from src/img/media/speakers/longcao.jpg rename to img/media/speakers/longcao.jpg diff --git a/src/img/media/speakers/lucabelli.jpg b/img/media/speakers/lucabelli.jpg similarity index 100% rename from src/img/media/speakers/lucabelli.jpg rename to img/media/speakers/lucabelli.jpg diff --git a/src/img/media/speakers/lukajacobowitz.jpg b/img/media/speakers/lukajacobowitz.jpg similarity index 100% rename from src/img/media/speakers/lukajacobowitz.jpg rename to img/media/speakers/lukajacobowitz.jpg diff --git a/src/img/media/speakers/marcushenry.jpg b/img/media/speakers/marcushenry.jpg similarity index 100% rename from src/img/media/speakers/marcushenry.jpg rename to img/media/speakers/marcushenry.jpg diff --git a/src/img/media/speakers/marinasigaeva.jpg b/img/media/speakers/marinasigaeva.jpg similarity index 100% rename from src/img/media/speakers/marinasigaeva.jpg rename to img/media/speakers/marinasigaeva.jpg diff --git a/src/img/media/speakers/martinodersky.jpg b/img/media/speakers/martinodersky.jpg similarity index 100% rename from src/img/media/speakers/martinodersky.jpg rename to img/media/speakers/martinodersky.jpg diff --git a/src/img/media/speakers/michaelpilquist.png b/img/media/speakers/michaelpilquist.png similarity index 100% rename from src/img/media/speakers/michaelpilquist.png rename to img/media/speakers/michaelpilquist.png diff --git a/src/img/media/speakers/nikivazou.jpg b/img/media/speakers/nikivazou.jpg similarity index 100% rename from src/img/media/speakers/nikivazou.jpg rename to img/media/speakers/nikivazou.jpg diff --git a/src/img/media/speakers/noelwelsh.png b/img/media/speakers/noelwelsh.png similarity index 100% rename from src/img/media/speakers/noelwelsh.png rename to img/media/speakers/noelwelsh.png diff --git a/src/img/media/speakers/oweinreese.jpg b/img/media/speakers/oweinreese.jpg similarity index 100% rename from src/img/media/speakers/oweinreese.jpg rename to img/media/speakers/oweinreese.jpg diff --git a/src/img/media/speakers/paulheymann.jpg b/img/media/speakers/paulheymann.jpg similarity index 100% rename from src/img/media/speakers/paulheymann.jpg rename to img/media/speakers/paulheymann.jpg diff --git a/src/img/media/speakers/ratansebastian.jpg b/img/media/speakers/ratansebastian.jpg similarity index 100% rename from src/img/media/speakers/ratansebastian.jpg rename to img/media/speakers/ratansebastian.jpg diff --git a/src/img/media/speakers/raulraja.jpg b/img/media/speakers/raulraja.jpg similarity index 100% rename from src/img/media/speakers/raulraja.jpg rename to img/media/speakers/raulraja.jpg diff --git a/src/img/media/speakers/romainruetschi.jpg b/img/media/speakers/romainruetschi.jpg similarity index 100% rename from src/img/media/speakers/romainruetschi.jpg rename to img/media/speakers/romainruetschi.jpg diff --git a/src/img/media/speakers/rossbaker.jpg b/img/media/speakers/rossbaker.jpg similarity index 100% rename from src/img/media/speakers/rossbaker.jpg rename to img/media/speakers/rossbaker.jpg diff --git a/src/img/media/speakers/ryanwilliams.jpg b/img/media/speakers/ryanwilliams.jpg similarity index 100% rename from src/img/media/speakers/ryanwilliams.jpg rename to img/media/speakers/ryanwilliams.jpg diff --git a/src/img/media/speakers/sasharomijn.jpg b/img/media/speakers/sasharomijn.jpg similarity index 100% rename from src/img/media/speakers/sasharomijn.jpg rename to img/media/speakers/sasharomijn.jpg diff --git a/src/img/media/speakers/sofiacole.jpg b/img/media/speakers/sofiacole.jpg similarity index 100% rename from src/img/media/speakers/sofiacole.jpg rename to img/media/speakers/sofiacole.jpg diff --git a/src/img/media/speakers/stefanschneider.jpg b/img/media/speakers/stefanschneider.jpg similarity index 100% rename from src/img/media/speakers/stefanschneider.jpg rename to img/media/speakers/stefanschneider.jpg diff --git a/src/img/media/speakers/stephaniebalzer.jpg b/img/media/speakers/stephaniebalzer.jpg similarity index 100% rename from src/img/media/speakers/stephaniebalzer.jpg rename to img/media/speakers/stephaniebalzer.jpg diff --git a/src/img/media/speakers/sweirich.jpg b/img/media/speakers/sweirich.jpg similarity index 100% rename from src/img/media/speakers/sweirich.jpg rename to img/media/speakers/sweirich.jpg diff --git a/src/img/media/speakers/viktorloevgren.png b/img/media/speakers/viktorloevgren.png similarity index 100% rename from src/img/media/speakers/viktorloevgren.png rename to img/media/speakers/viktorloevgren.png diff --git a/src/img/media/speakers/vilemliepelt.jpg b/img/media/speakers/vilemliepelt.jpg similarity index 100% rename from src/img/media/speakers/vilemliepelt.jpg rename to img/media/speakers/vilemliepelt.jpg diff --git a/src/img/media/speakers/zainabali.jpg b/img/media/speakers/zainabali.jpg similarity index 100% rename from src/img/media/speakers/zainabali.jpg rename to img/media/speakers/zainabali.jpg diff --git a/src/img/media/speakers/zhenhaoli.jpg b/img/media/speakers/zhenhaoli.jpg similarity index 100% rename from src/img/media/speakers/zhenhaoli.jpg rename to img/media/speakers/zhenhaoli.jpg diff --git a/src/img/media/sponsors/47_degrees.png b/img/media/sponsors/47_degrees.png similarity index 100% rename from src/img/media/sponsors/47_degrees.png rename to img/media/sponsors/47_degrees.png diff --git a/src/img/media/sponsors/arktekk.png b/img/media/sponsors/arktekk.png similarity index 100% rename from src/img/media/sponsors/arktekk.png rename to img/media/sponsors/arktekk.png diff --git a/src/img/media/sponsors/azavea.png b/img/media/sponsors/azavea.png similarity index 100% rename from src/img/media/sponsors/azavea.png rename to img/media/sponsors/azavea.png diff --git a/src/img/media/sponsors/box.png b/img/media/sponsors/box.png similarity index 100% rename from src/img/media/sponsors/box.png rename to img/media/sponsors/box.png diff --git a/src/img/media/sponsors/bridgewater.png b/img/media/sponsors/bridgewater.png similarity index 100% rename from src/img/media/sponsors/bridgewater.png rename to img/media/sponsors/bridgewater.png diff --git a/src/img/media/sponsors/cake.jpg b/img/media/sponsors/cake.jpg similarity index 100% rename from src/img/media/sponsors/cake.jpg rename to img/media/sponsors/cake.jpg diff --git a/src/img/media/sponsors/chariot.png b/img/media/sponsors/chariot.png similarity index 100% rename from src/img/media/sponsors/chariot.png rename to img/media/sponsors/chariot.png diff --git a/src/img/media/sponsors/coatue.png b/img/media/sponsors/coatue.png similarity index 100% rename from src/img/media/sponsors/coatue.png rename to img/media/sponsors/coatue.png diff --git a/src/img/media/sponsors/comcast.png b/img/media/sponsors/comcast.png similarity index 100% rename from src/img/media/sponsors/comcast.png rename to img/media/sponsors/comcast.png diff --git a/src/img/media/sponsors/commbank.png b/img/media/sponsors/commbank.png similarity index 100% rename from src/img/media/sponsors/commbank.png rename to img/media/sponsors/commbank.png diff --git a/src/img/media/sponsors/commercetools.png b/img/media/sponsors/commercetools.png similarity index 100% rename from src/img/media/sponsors/commercetools.png rename to img/media/sponsors/commercetools.png diff --git a/src/img/media/sponsors/commercetools_2.png b/img/media/sponsors/commercetools_2.png similarity index 100% rename from src/img/media/sponsors/commercetools_2.png rename to img/media/sponsors/commercetools_2.png diff --git a/src/img/media/sponsors/crite_o.png b/img/media/sponsors/crite_o.png similarity index 100% rename from src/img/media/sponsors/crite_o.png rename to img/media/sponsors/crite_o.png diff --git a/src/img/media/sponsors/crite_o_labs.png b/img/media/sponsors/crite_o_labs.png similarity index 100% rename from src/img/media/sponsors/crite_o_labs.png rename to img/media/sponsors/crite_o_labs.png diff --git a/src/img/media/sponsors/data-monsters.png b/img/media/sponsors/data-monsters.png similarity index 100% rename from src/img/media/sponsors/data-monsters.png rename to img/media/sponsors/data-monsters.png diff --git a/src/img/media/sponsors/driver.png b/img/media/sponsors/driver.png similarity index 100% rename from src/img/media/sponsors/driver.png rename to img/media/sponsors/driver.png diff --git a/src/img/media/sponsors/giphy.png b/img/media/sponsors/giphy.png similarity index 100% rename from src/img/media/sponsors/giphy.png rename to img/media/sponsors/giphy.png diff --git a/src/img/media/sponsors/iheartradio.png b/img/media/sponsors/iheartradio.png similarity index 100% rename from src/img/media/sponsors/iheartradio.png rename to img/media/sponsors/iheartradio.png diff --git a/src/img/media/sponsors/inner-product.png b/img/media/sponsors/inner-product.png similarity index 100% rename from src/img/media/sponsors/inner-product.png rename to img/media/sponsors/inner-product.png diff --git a/src/img/media/sponsors/iterators.png b/img/media/sponsors/iterators.png similarity index 100% rename from src/img/media/sponsors/iterators.png rename to img/media/sponsors/iterators.png diff --git a/src/img/media/sponsors/lightbend.png b/img/media/sponsors/lightbend.png similarity index 100% rename from src/img/media/sponsors/lightbend.png rename to img/media/sponsors/lightbend.png diff --git a/src/img/media/sponsors/linkyard.png b/img/media/sponsors/linkyard.png similarity index 100% rename from src/img/media/sponsors/linkyard.png rename to img/media/sponsors/linkyard.png diff --git a/src/img/media/sponsors/mediamath.png b/img/media/sponsors/mediamath.png similarity index 100% rename from src/img/media/sponsors/mediamath.png rename to img/media/sponsors/mediamath.png diff --git a/src/img/media/sponsors/meetup.png b/img/media/sponsors/meetup.png similarity index 100% rename from src/img/media/sponsors/meetup.png rename to img/media/sponsors/meetup.png diff --git a/src/img/media/sponsors/rally.png b/img/media/sponsors/rally.png similarity index 100% rename from src/img/media/sponsors/rally.png rename to img/media/sponsors/rally.png diff --git a/src/img/media/sponsors/scalac.png b/img/media/sponsors/scalac.png similarity index 100% rename from src/img/media/sponsors/scalac.png rename to img/media/sponsors/scalac.png diff --git a/src/img/media/sponsors/scotiabank.png b/img/media/sponsors/scotiabank.png similarity index 100% rename from src/img/media/sponsors/scotiabank.png rename to img/media/sponsors/scotiabank.png diff --git a/src/img/media/sponsors/signify.png b/img/media/sponsors/signify.png similarity index 100% rename from src/img/media/sponsors/signify.png rename to img/media/sponsors/signify.png diff --git a/src/img/media/sponsors/simple.png b/img/media/sponsors/simple.png similarity index 100% rename from src/img/media/sponsors/simple.png rename to img/media/sponsors/simple.png diff --git a/src/img/media/sponsors/soundcloud.png b/img/media/sponsors/soundcloud.png similarity index 100% rename from src/img/media/sponsors/soundcloud.png rename to img/media/sponsors/soundcloud.png diff --git a/src/img/media/sponsors/tapad.png b/img/media/sponsors/tapad.png similarity index 100% rename from src/img/media/sponsors/tapad.png rename to img/media/sponsors/tapad.png diff --git a/src/img/media/sponsors/triplequote.png b/img/media/sponsors/triplequote.png similarity index 100% rename from src/img/media/sponsors/triplequote.png rename to img/media/sponsors/triplequote.png diff --git a/src/img/media/sponsors/underscore.png b/img/media/sponsors/underscore.png similarity index 100% rename from src/img/media/sponsors/underscore.png rename to img/media/sponsors/underscore.png diff --git a/src/img/media/sponsors/underscore_2.png b/img/media/sponsors/underscore_2.png similarity index 100% rename from src/img/media/sponsors/underscore_2.png rename to img/media/sponsors/underscore_2.png diff --git a/src/img/media/sponsors/verizon.png b/img/media/sponsors/verizon.png similarity index 100% rename from src/img/media/sponsors/verizon.png rename to img/media/sponsors/verizon.png diff --git a/src/img/media/sponsors/weight_watchers.png b/img/media/sponsors/weight_watchers.png similarity index 100% rename from src/img/media/sponsors/weight_watchers.png rename to img/media/sponsors/weight_watchers.png diff --git a/src/img/media/sponsors/zalando.png b/img/media/sponsors/zalando.png similarity index 100% rename from src/img/media/sponsors/zalando.png rename to img/media/sponsors/zalando.png diff --git a/src/img/media/triplequote/triplequote-compile-scala-3.2x-faster-front.jpg b/img/media/triplequote/triplequote-compile-scala-3.2x-faster-front.jpg similarity index 100% rename from src/img/media/triplequote/triplequote-compile-scala-3.2x-faster-front.jpg rename to img/media/triplequote/triplequote-compile-scala-3.2x-faster-front.jpg diff --git a/src/img/places/berlin-thumb.jpg b/img/places/berlin-thumb.jpg similarity index 100% rename from src/img/places/berlin-thumb.jpg rename to img/places/berlin-thumb.jpg diff --git a/src/img/places/berlin.jpg b/img/places/berlin.jpg similarity index 100% rename from src/img/places/berlin.jpg rename to img/places/berlin.jpg diff --git a/src/img/places/cadiz-thumb.jpg b/img/places/cadiz-thumb.jpg similarity index 100% rename from src/img/places/cadiz-thumb.jpg rename to img/places/cadiz-thumb.jpg diff --git a/src/img/places/cadiz.jpg b/img/places/cadiz.jpg similarity index 100% rename from src/img/places/cadiz.jpg rename to img/places/cadiz.jpg diff --git a/src/img/places/cambridge-thumb.jpg b/img/places/cambridge-thumb.jpg similarity index 100% rename from src/img/places/cambridge-thumb.jpg rename to img/places/cambridge-thumb.jpg diff --git a/src/img/places/cambridge.jpg b/img/places/cambridge.jpg similarity index 100% rename from src/img/places/cambridge.jpg rename to img/places/cambridge.jpg diff --git a/src/img/places/copenhagen-thumb.jpg b/img/places/copenhagen-thumb.jpg similarity index 100% rename from src/img/places/copenhagen-thumb.jpg rename to img/places/copenhagen-thumb.jpg diff --git a/src/img/places/copenhagen.jpg b/img/places/copenhagen.jpg similarity index 100% rename from src/img/places/copenhagen.jpg rename to img/places/copenhagen.jpg diff --git a/src/img/places/lakedistrict-thumb.jpg b/img/places/lakedistrict-thumb.jpg similarity index 100% rename from src/img/places/lakedistrict-thumb.jpg rename to img/places/lakedistrict-thumb.jpg diff --git a/src/img/places/lakedistrict.jpg b/img/places/lakedistrict.jpg similarity index 100% rename from src/img/places/lakedistrict.jpg rename to img/places/lakedistrict.jpg diff --git a/src/img/places/lausanne-thumb.jpg b/img/places/lausanne-thumb.jpg similarity index 100% rename from src/img/places/lausanne-thumb.jpg rename to img/places/lausanne-thumb.jpg diff --git a/src/img/places/lausanne.jpg b/img/places/lausanne.jpg similarity index 100% rename from src/img/places/lausanne.jpg rename to img/places/lausanne.jpg diff --git a/src/img/places/london-thumb.jpg b/img/places/london-thumb.jpg similarity index 100% rename from src/img/places/london-thumb.jpg rename to img/places/london-thumb.jpg diff --git a/src/img/places/london.jpg b/img/places/london.jpg similarity index 100% rename from src/img/places/london.jpg rename to img/places/london.jpg diff --git a/src/img/places/lyon-thumb.jpg b/img/places/lyon-thumb.jpg similarity index 100% rename from src/img/places/lyon-thumb.jpg rename to img/places/lyon-thumb.jpg diff --git a/src/img/places/lyon.jpg b/img/places/lyon.jpg similarity index 100% rename from src/img/places/lyon.jpg rename to img/places/lyon.jpg diff --git a/src/img/places/nyc-thumb.jpg b/img/places/nyc-thumb.jpg similarity index 100% rename from src/img/places/nyc-thumb.jpg rename to img/places/nyc-thumb.jpg diff --git a/src/img/places/nyc.jpg b/img/places/nyc.jpg similarity index 100% rename from src/img/places/nyc.jpg rename to img/places/nyc.jpg diff --git a/src/img/places/oslo-thumb.jpg b/img/places/oslo-thumb.jpg similarity index 100% rename from src/img/places/oslo-thumb.jpg rename to img/places/oslo-thumb.jpg diff --git a/src/img/places/oslo.jpg b/img/places/oslo.jpg similarity index 100% rename from src/img/places/oslo.jpg rename to img/places/oslo.jpg diff --git a/src/img/places/philly-thumb.jpg b/img/places/philly-thumb.jpg similarity index 100% rename from src/img/places/philly-thumb.jpg rename to img/places/philly-thumb.jpg diff --git a/src/img/places/philly.jpg b/img/places/philly.jpg similarity index 100% rename from src/img/places/philly.jpg rename to img/places/philly.jpg diff --git a/src/img/sponsors/aruna.webp b/img/sponsors/aruna.webp similarity index 100% rename from src/img/sponsors/aruna.webp rename to img/sponsors/aruna.webp diff --git a/src/img/sponsors/famly.svg b/img/sponsors/famly.svg similarity index 100% rename from src/img/sponsors/famly.svg rename to img/sponsors/famly.svg diff --git a/src/img/sponsors/input-objects.svg b/img/sponsors/input-objects.svg similarity index 100% rename from src/img/sponsors/input-objects.svg rename to img/sponsors/input-objects.svg diff --git a/src/img/sponsors/shopify.svg b/img/sponsors/shopify.svg old mode 100755 new mode 100644 similarity index 100% rename from src/img/sponsors/shopify.svg rename to img/sponsors/shopify.svg diff --git a/src/img/sponsors/spotify.svg b/img/sponsors/spotify.svg similarity index 100% rename from src/img/sponsors/spotify.svg rename to img/sponsors/spotify.svg diff --git a/index.html b/index.html new file mode 100644 index 00000000..e55da01c --- /dev/null +++ b/index.html @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + Typelevel + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ +
+
+

+ We develop industry-proven,
+ state-of-the-art libraries for
+ functional programming. +

+

+ Start building scalable, performant applications
+ that you can grow and maintain + with confidence. +

+
+
+
+
+

+ Typelevel is an ecosystem of Scala-based projects and a community of people united to foster an inclusive, welcoming, and safe + environment around functional programming. +

+
+
+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+

+ Typelevel is built by a vibrant global community and backed by a nonprofit Foundation with the support of our + industry sponsors. +

+
+
+ +
+ + + + + + diff --git a/license/index.html b/license/index.html new file mode 100644 index 00000000..0a80fe82 --- /dev/null +++ b/license/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/main.css b/main.css similarity index 100% rename from src/main.css rename to main.css diff --git a/src/main.js b/main.js similarity index 100% rename from src/main.js rename to main.js diff --git a/platforms/index.html b/platforms/index.html new file mode 100644 index 00000000..46df44c2 --- /dev/null +++ b/platforms/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/platforms/js/index.html b/platforms/js/index.html new file mode 100644 index 00000000..5998a700 --- /dev/null +++ b/platforms/js/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/platforms/jvm/index.html b/platforms/jvm/index.html new file mode 100644 index 00000000..5998a700 --- /dev/null +++ b/platforms/jvm/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/platforms/native/index.html b/platforms/native/index.html new file mode 100644 index 00000000..5998a700 --- /dev/null +++ b/platforms/native/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/projects/affiliate/index.html b/projects/affiliate/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/projects/affiliate/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/projects/index.html b/projects/index.html new file mode 100644 index 00000000..e016017e --- /dev/null +++ b/projects/index.html @@ -0,0 +1,2564 @@ + + + + + + + + + + + + + + + + + + + + + + + Project Index + + + + + + +
+
+
+
+

+ + + + +

+
+
+
+ +
+
+ +
+ + +
+

Project Index

+
+ +
+
+
+

+ argonaut-shapeless +

+ + + + +
+
+

Automatic derivation for argonaut

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ banana-rdf +

+ + + + +
+
+

RDF, SPARQL and Linked Data technologies

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ calico +

+ + + + + + + + +
+
+

Pure, reactive UI library for building web applications with Cats Effect + FS2

+
+ + Affiliate Project + + + js + +
+
+
+
+ +
+
+
+

+ cats-actors +

+ + + + +
+
+

An Actor Model implementation built on top of Cats-Effect, providing a higher-level abstraction for managing concurrency.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ case-insensitive +

+ + + + +
+
+

A case-insensitive string for Scala

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ catapult +

+ + + + +
+
+

A thin wrapper for the Launch Darkly Java server SDK using cats-effect and fs2

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ catbird +

+ + + + +
+
+

Cats instances for various Twitter Open Source Scala projects

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ Cats +

+ + + + + + + + +
+
+

A library intended to provide abstractions for functional programming in Scala, leveraging its unique features. Design goals are approachability, modularity, documentation and efficiency.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Cats Collections +

+ + + + +
+
+

Data structures that facilitate pure functional programming with cats

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Cats-Effect +

+ + + + +
+
+

The IO Monad for Scala, plus type classes for general effect types.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Cats MTL +

+ + + + +
+
+

Monad transformers made easy

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ cats-parse +

+ + + + +
+
+

A parsing library for the cats ecosystem

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ cats-scalatest +

+ + + + +
+
+

Scalatest bindings for Cats.

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ cats-stm +

+ + + + +
+
+

A STM implementation for Cats Effect

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Cats Tagless +

+ + + + +
+
+

A library of utilities for tagless final algebras

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Cats-Time +

+ + + + +
+
+

Instances for Cats Typeclasses for Java 8 Time

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Circe +

+ + + + +
+
+

Yet another JSON library for Scala

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Ciris +

+ + + + +
+
+

Functional Configurations for Scala

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ coulomb +

+ + + + +
+
+

A statically typed unit analysis library for Scala

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ cron4s +

+ + + + +
+
+

Cross-platform CRON expression parsing for Scala

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ decline +

+ + + + +
+
+

A composable command-line parser for Scala.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ discipline +

+ + + + +
+
+

Originally intended for internal use in spire, this library helps libraries declaring type classes to precisely state the laws which instances need to satisfy, and takes care of not checking derived laws multiple times.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ doobie +

+ + + + +
+
+

A pure functional JDBC layer for Scala. It is not an ORM, nor is it a relational algebra; it just provides a principled way to construct programs (and higher-level libraries) that use JDBC.

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ edomata +

+ + + + + + + + +
+
+

Event-driven automata for Scala, Scala.js and scala native. This library provides purely functional state machines that can be used to create event sourced and/or CQRS style applications. It also includes production ready backends.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ eff +

+ + + + + + + + +
+
+

Extensible effects are an alternative to monad transformers for computing with effects in a functional way. This library is based on the “free-er” monad and an “open union” of effects described by Oleg Kiselyov in “Freer monads, more extensible effects”

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ endless4s +

+ + + + + + + + +
+
+

Sharded and event-sourced entities using tagless-final algebras

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ Extruder +

+ + + + +
+
+

Populate case classes from any configuration source

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ fabric +

+ + + + +
+
+

Object-Notation Abstraction for JSON, binary, HOCON, etc.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Feral +

+ + + + +
+
+

Feral cats are homeless, feral functions are serverless

+
+ + Organization Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ ff4s +

+ + + + +
+
+

A purely functional web frontend framework for Scala.js.

+
+ + Affiliate Project + + + js + +
+
+
+
+ +
+
+
+

+ Fetch +

+ + + + +
+
+

Library built on top of Cats that provides efficient data access from heterogeneous dataurces

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ Finch +

+ + + + +
+
+

Purely functional basic blocks atop of Finagle for building composable HTTP APIs

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ Frameless +

+ + + + +
+
+

Frameless is a library for working with Spark using more expressive types.

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ fs2-aes +

+ + + + +
+
+

Micro library providing AES encryption/decryption of fs2.Stream[F, Byte].

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ fs2-compress +

+ + + + +
+
+

Compression Algorithms for Fs2

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ fs2-data +

+ + + + +
+
+

Parse and transform data (CBOR, CSV, JSON, XML) in a streaming manner

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ fs2-dom +

+ + + + +
+
+

Idiomatic Cats Effect + FS2 integrations for Web APIs

+
+ + Affiliate Project + + + js + +
+
+
+
+ +
+
+
+

+ fs2-grpc +

+ + + + +
+
+

gRPC implementation for FS2/cats-effect

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ fs2 +

+ + + + +
+
+

FS2 is a library for purely functional, effectful, and polymorphic stream processing library in the Scala programming language. Its design goals are compositionality, expressiveness, resource safety, and speed. The name is a modified acronym for Functional Streams for Scala (FSS, or FS2).

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Grackle +

+ + + + +
+
+

Functional GraphQL server for the Typelevel stack

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Hammock +

+ + + + +
+
+

Purely functional HTTP client

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ http4s +

+ + + + +
+
+

A typeful, purely functional HTTP library for client and server applications

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ imp +

+ + + + +
+
+

Summoning implicit values

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ jawn-fs2 +

+ + + + +
+
+

Integration of jawn and fs2 for streaming, incremental JSON parsing

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ keypool +

+ + + + +
+
+

A Keyed Pool Implementation for Scala

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ kind-projector +

+ + + + +
+
+

Plugin for nicer type-lambda syntax

+
+ + Organization Project + + + jvm + +
+
+
+
+ +
+
+
+

+ Kittens +

+ + + + +
+
+

Automatic type class derivation

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Laika +

+ + + + +
+
+

Site and e-book generator and customizable text markup transformer for sbt, Scala and Scala.js

+
+ + Organization Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ LDBC +

+ + + + +
+
+

Pure functional JDBC layer with Cats Effect 3 and Scala 3

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Lepus +

+ + + + + + + + +
+
+

Purely functional, non-blocking RabbitMQ client for scala, scala js and scala native built on top of fs2.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Libra +

+ + + + +
+
+

Compile time dimensional analysis for any problem domain

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ literally +

+ + + + +
+
+

Compile time validation of literal values built from strings

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ log4cats +

+ + + + +
+
+

Logging Tools For Interaction with cats-effect

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Monix +

+ + + + + + + + +
+
+

High-performance library for composing asynchronous, event-based programs, exposing a Reactive Streams implementation along with primitives for dealing with concurrency and side-effects.

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ Monocle +

+ + + + +
+
+

Optics library offering a simple yet powerful API to access and transform immutable data

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Mouse +

+ + + + +
+
+

Enrichments to standard library classes to ease functional programming

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Natchez +

+ + + + +
+
+

functional tracing for cats

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ otel4s +

+ + + + +
+
+

An OpenTelemetry library based on cats-effect

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Outwatch +

+ + + + +
+
+

The Functional and Reactive Web-Frontend Library for Scala.js

+
+ + Affiliate Project + + + js + +
+
+
+
+ +
+
+
+

+ parsley-cats +

+ + + + +
+
+

The parsley-cats library exposes Cats instances for Parsley parsing library.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Peloton +

+ + + + +
+
+

An actor library for Cats Effect

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ perspective +

+ + + + +
+
+

Provides tools for generic programming, and typeclasses for monad transformers and higher kinded data.

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ PureConfig +

+ + + + +
+
+

A boilerplate-free library for loading configuration files

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ refined +

+ + + + +
+
+

Tools for refining types with type-level predicates which constrain the set of values described by the refined type, for example restricting to positive or negative numbers.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ ScalaCheck +

+ + + + + + + + +
+
+

ScalaCheck is a library for automated property-based testing. It contains generators for randomized test data and combinators for properties.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ scalacheck-shapeless +

+ + + + +
+
+

Automatic derivation for ScalaCheck

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Scala Exercises +

+ + + + +
+
+

Platform and framework for Scala devs to learn about Scala libraries

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ scala-steward +

+ + + + +
+
+

A robot that helps keeping Scala projects up-to-date

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ scodec +

+ + + + +
+
+

scodec is a combinator library for working with binary data. It focuses on contract-first and pure functional encoding and decoding of binary data and provides integration with shapeless.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Scoverage +

+ + + + +
+
+

Code coverage tool for Scala

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Shapeless +

+ + + + +
+
+

Shapeless is a generic programming library. Starting with implementations of Scrap your boilerplate and higher rank polymorphism in Scala, it quickly grew to provide advanced abstract tools like heterogenous lists and automatic instance derivation for type classes.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ simulacrum +

+ + + + +
+
+

First-class syntax for type classes

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Simulacrum Scalafix +

+ + + + +
+
+

Simulacrum as Scalafix rules

+
+ + Organization Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ singleton-ops +

+ + + + +
+
+

Operations for primitive and String singleton types

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ Skunk +

+ + + + +
+
+

A data access library for Scala + Postgres

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ sonic +

+ + + + +
+
+

Property-based testing with integrated shrinking

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ specs2 +

+ + + + + + + + +
+
+

specs2 is a library for writing executable software specifications, aiming for conciseness, readability and extensibility.

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ spire +

+ + + + +
+
+

Spire is a numeric library for Scala which is intended to be generic, fast, and precise. Using features such as specialization, macros, type classes, and implicits, Spire works hard to defy conventional wisdom around performance and precision trade-offs.

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Squants +

+ + + + +
+
+

The Scala API for Quantities, Units of Measure and Dimensional Analysis

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ Twiddles +

+ + + + +
+
+

Micro-library for building effectful protocols

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ TwoTails +

+ + + + +
+
+

A compiler plugin adding support for mutual tail recursion

+
+ + Affiliate Project + + + jvm + +
+
+
+
+ +
+
+
+

+ typelevel.g8 +

+ + + + +
+
+

A Giter8 template for sbt-typelevel

+
+ + Organization Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ typelevel-nix +

+ + + + +
+
+

Development tools for Typelevel projects

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ uniform-scala +

+ + + + +
+
+

Functional user journeys

+
+ + Affiliate Project + + + js + + jvm + +
+
+
+
+ +
+
+
+

+ upperbound +

+ + + + +
+
+

A purely functional, interval based rate limiter

+
+ + Affiliate Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+
+

+ vault +

+ + + + +
+
+

Type-safe, persistent storage for values of arbitrary types

+
+ + Organization Project + + + js + + jvm + + native + +
+
+
+
+ +
+
+ + +
+ + + + + + diff --git a/projects/organization/index.html b/projects/organization/index.html new file mode 100644 index 00000000..dfbf0aef --- /dev/null +++ b/projects/organization/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/search/docs.css b/search/docs.css new file mode 100644 index 00000000..bb6b02e0 --- /dev/null +++ b/search/docs.css @@ -0,0 +1,52 @@ +/* Protosearch - Docs Result Styles */ + +/* Individual result */ +.ps-result { + padding: 1rem; + border: 1px solid var(--ps-border); + border-radius: 4px; + background: var(--ps-bg); +} + +/* Result title text */ +.ps-result header { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.ps-result header a { + color: var(--ps-link); + text-decoration: none; +} + +.ps-result header a:hover { + text-decoration: underline; +} + +.ps-result header mark { + background: var(--ps-highlight); + padding: 0 2px; + border-radius: 2px; +} + +/* Result snippet / preview text */ +.ps-preview { + margin: 0 0 0.5rem; + line-height: 1.5; +} + +.ps-preview mark { + background: var(--ps-highlight); + padding: 0 2px; + border-radius: 2px; +} + +/* Result metadata (score, path) */ +.ps-meta { + font-size: 0.85rem; + color: var(--ps-text-muted); + display: flex; + gap: 1rem; +} + diff --git a/search/docs.js b/search/docs.js new file mode 100644 index 00000000..a2864e99 --- /dev/null +++ b/search/docs.js @@ -0,0 +1,40 @@ +// Protosearch - Docs Renderer +// Renders search results for Laika documentation pages +// Fields: path, title, body + +function renderDocs(hit, config) { + const path = hit.fields.path.startsWith("/") ? hit.fields.path.slice(1) : hit.fields.path + const htmlPath = `${path}.html` + const link = new URL(htmlPath, config.baseUrl) + const title = hit.highlights["title"] || hit.fields["title"] + const preview = hit.highlights["body"] + const score = hit.score.toFixed(4) + + const previewHtml = config.showPreview && preview + ? `

${preview}

` + : "" + + const scoreHtml = config.showScore + ? `score: ${score}` + : "" + + const pathHtml = config.showPath + ? `${path}` + : "" + + const metaHtml = (config.showScore || config.showPath) + ? `` + : "" + + return ` +
+
+ ${title} +
+ ${previewHtml} + ${metaHtml} +
` +} + +window.Protosearch.registerRenderer("docs", renderDocs) + diff --git a/search/protosearch.js b/search/protosearch.js new file mode 100644 index 00000000..060f5940 --- /dev/null +++ b/search/protosearch.js @@ -0,0 +1,1641 @@ +let QuerierBuilder,Hit,Querier; +(function(){ +'use strict';var e;function ba(a){this.yg=a}ba.prototype.toString=function(){return String.fromCharCode(this.yg)};function ca(a,b){this.h=a;this.l=b}ca.prototype.toString=function(){return da(this.h,this.l)}; +function ea(a){switch(typeof a){case "string":return l(fa);case "number":return ia(a)?a<<24>>24===a?l(ja):a<<16>>16===a?l(la):l(na):oa(a)?l(pa):l(sa);case "boolean":return l(ta);case "undefined":return l(ua);default:return a instanceof ca?l(va):a instanceof ba?l(wa):a&&a.$classData?l(a.$classData):null}} +function xa(a){switch(typeof a){case "string":return"java.lang.String";case "number":return ia(a)?a<<24>>24===a?"java.lang.Byte":a<<16>>16===a?"java.lang.Short":"java.lang.Integer":oa(a)?"java.lang.Float":"java.lang.Double";case "boolean":return"java.lang.Boolean";case "undefined":return"java.lang.Void";default:return a instanceof ca?"java.lang.Long":a instanceof ba?"java.lang.Character":a&&a.$classData?a.$classData.name:null.x1()}} +function ya(a,b){return"string"===typeof a?a.charCodeAt(b):a.So(b)}function za(a,b){switch(typeof a){case "string":return Aa(a,b);case "number":return Ba(Ca(),a,b);case "boolean":return a===b?0:a?1:-1;default:if(a instanceof ca){var c=a.h;a=a.l;var d=b.h;b=b.l;c=a===b?c===d?0:c>>>0>>0?-1:1:aa?-2147483648:a|0}function Na(a){return String.fromCharCode(a)} +var Oa=new DataView(new ArrayBuffer(8));function Pa(a){Oa.setFloat32(0,a,!0);return Oa.getInt32(0,!0)}function Qa(a){Oa.setInt32(0,a,!0);return Oa.getFloat32(0,!0)}function Sa(a,b,c,d,f){if(a!==c||d-b>>>0>f>>>0)for(var g=0;g>=BigInt(32);return b;case "boolean":return a?1231:1237;case "undefined":return 0;case "symbol":return a=a.description,void 0===a?0:Ha(a);default:if(null===a)return 0;b=Ua.get(a);void 0===b&&(Ta=b=Ta+1|0,Ua.set(a,b));return b}}function Wa(a){return"number"===typeof a&&a<<24>>24===a&&1/a!==1/-0} +function Xa(a){return"number"===typeof a&&a<<16>>16===a&&1/a!==1/-0}function ia(a){return"number"===typeof a&&(a|0)===a&&1/a!==1/-0}function oa(a){return"number"===typeof a&&(a!==a||Math.fround(a)===a)}function p(a){return new ba(a)}function r(a,b){return new ca(a,b)}var Ya=r(0,0);function $a(a){return null===a?0:a.yg}function ab(a){return null===a?Ya:a}function Ea(){}Ea.prototype.constructor=Ea;function t(){}t.prototype=Ea.prototype;Ea.prototype.p=function(){return Va(this)}; +Ea.prototype.d=function(a){return this===a};Ea.prototype.f=function(){var a=this.p();return xa(this)+"@"+(a>>>0).toString(16)};Ea.prototype.toString=function(){return this.f()};function x(a){if("number"===typeof a){this.a=Array(a);for(var b=0;bh===g;g.name=c;g.Qv=!0;g.Pv=()=>!1;void 0!==d&&(g.Bv=mb(g,d,f,"J"===b));return g} +function z(a,b,c,d){var f=new kb,g=Object.getOwnPropertyNames(c)[0];f.wb=c;f.Cm="L"+b+";";f.Lm=h=>!!h.wb[g];f.name=b;f.EH=1===a;f.Pv=d||(h=>!!(h&&h.$classData&&h.$classData.wb[g]));"number"!==typeof a&&(a.prototype.$classData=f);return f} +function mb(a,b,c,d,f){var g=new kb;b.prototype.$classData=g;var h="["+a.Cm;g.P=b;g.wb={Ed:1,b:1};g.gy=a;g.Po=a;g.Qo=1;g.Cm=h;g.name=h;g.ly=!0;g.Lm=f||(k=>g===k);g.Np=d?k=>{for(var m=k.length|0,n=new b(m),q=n.a,u=0;unew b(new c(k)):k=>new b(k);g.Pv=k=>k instanceof b;return g} +function nb(a){function b(k){if("number"===typeof k){this.a=Array(k);for(var m=0;m{var m=k.Qo;return m===f?d.Lm(k.Po):m>f&&d===ob};c.Lm=h;c.Np=k=>new b(k);c.Pv=k=> +{k=k&&k.$classData;return!!k&&(k===c||h(k))};return c}function B(a){a.Bv||(a.Bv=nb(a));return a.Bv}function l(a){a.oB||(a.oB=new qb(a));return a.oB}function rb(a,b){return a===b||a.Lm(b)}function sb(a){return a.gy?l(a.gy):null}function tb(a,b){if(a===ub)throw vb();return new (B(a).P)(b)}var ob=new kb;ob.wb={};ob.Cm="Ljava.lang.Object;";ob.Lm=a=>!a.Qv;ob.name="java.lang.Object";ob.Pv=a=>null!==a;ob.Bv=mb(ob,x,void 0,!1,a=>{var b=a.Qo;return 1===b?!a.Po.Qv:1Pb(d,m,c.g(n),new Qb((q,u)=>Rb(q,u)));if(Tb(a))for(var g=0,h=a.q(),k=f;;){if(g===h)return k;f=1+g|0;g=a.J(g);k=b(k,g);g=f}else{for(h=f;a.n();)g=a.j(),h=b(h,g);return h}}z(Lb,"cats.Traverse$",{DQ:1});var Ub;function Vb(){Ub||(Ub=new Lb);return Ub}function Wb(){}Wb.prototype=new t;Wb.prototype.constructor=Wb;function Xb(){}Xb.prototype=Wb.prototype; +function Yb(a,b){a=b.Qa(1);return 0>a?Ob().Qf:0>>16|0));a=Math.imul(-1028477387,a^(a>>>13|0));return a^(a>>>16|0)};function Ec(){Fc=this;new Gc;new Hc;Ic();Kc||(Kc=new Lc);Mc||(Mc=new Nc);Oc||(Oc=new Pc);Qc||(Qc=new Rc)}Ec.prototype=new t;Ec.prototype.constructor=Ec;z(Ec,"cats.package$",{eU:1});var Fc;function Ic(){Fc||(Fc=new Ec)}function Vc(){this.CE=this.Zp=null;Wc=this;this.Zp=new Xc;this.CE=new Yc}Vc.prototype=new t;Vc.prototype.constructor=Vc;z(Vc,"cats.parse.Accumulator0$",{kU:1}); +var Wc;function Zc(){Wc||(Wc=new Vc);return Wc}function $c(){this.Uz=null;ad=this;this.Uz=new ed}$c.prototype=new t;$c.prototype.constructor=$c;z($c,"cats.parse.Appender$",{oU:1});var ad;function fd(){ad||(ad=new $c);return ad}function gd(a,b){return new hd(new id(new jd(0,new G(c=>kd(b,1+(c|0)|0))),new G(c=>0<=(c|0))),new G(c=>p(65535&(a+(c|0)|0))))}function ld(){}ld.prototype=new t;ld.prototype.constructor=ld;function md(){}md.prototype=ld.prototype; +function nd(a,b){return od(pd(),new td(b,new G(c=>{if(null!==c)return gd(c.Y|0,c.V);throw new D(c);})))}var yd=function ud(a,b){if(b===a.dx.length){if(0===b)return vd().EE;var d=ud(a,b-1|0);if(null!==d){vd();var f=d.Ql;d=d.Pl}else throw new D(d);f|=0;d|=0;return a.HE?(vd(),new wd(1+f|0,0,b)):(vd(),new wd(f,1+d|0,b))}f=xd(H(),a.Vz,b);if(0>f)return f=~(1+f|0),a=b-a.Vz.a[f]|0,vd(),new wd(f,a,b);vd();return new wd(f,0,b)}; +function zd(a){this.IE=this.dx=null;this.HE=!1;this.Vz=null;this.dx=a;this.IE=Ad(a,"\n",-1);this.HE=0f.length));a=new hd(new Ed(a),new G(f=>{if(null!==f){var g=f.Y|0,h=!!f.V;if(!0===h)return 1+g|0;if(!1===h)return g}throw new D(f);}));if(0<=a.E()){var b=new y(a.E());Fd(a,b,0,2147483647);a=b}else{b=null;for(b=[];a.n();){var c=a.j();b.push(null===c?0:c)}a=new y(new Int32Array(b))}c=b=0;for(var d=new y(1+a.a.length|0);c{var f=ae();return be(f,ke(d))});le(I(),a,b);a=Xd(I(),46);b=this.Wz;a=Ud(I(),a,b);b=Pd(I(),me("eE"));var c=Zd(Pd(I(),me("+-")));b=Ud(I(), +b,c);c=this.Wz;b=Ud(I(),b,c);b=Wd(I(),b);c=this.Xz;a=Zd(a);a=Ud(I(),c,a);b=Zd(b);a=Ud(I(),a,b);Yd(I(),a)}Nd.prototype=new t;Nd.prototype.constructor=Nd;z(Nd,"cats.parse.Numbers$",{zU:1});var Od;function ne(){}ne.prototype=new t;ne.prototype.constructor=ne;function oe(){}oe.prototype=ne.prototype;ne.prototype.Fm=function(){return this instanceof pe?new J(this.Ul,this.Gj.Fm()):N()}; +function qe(a,b){for(a=N();;){var c=b;if(N().d(c)){a=re(a);for(var d=b=null;a!==N();){c=a.u();for(c=(c instanceof se?c.Bd:new J(c,N())).i();c.n();){var f=new J(c.j(),N());null===d?b=f:d.Z=f;d=f}a=a.A()}a=null===b?N():b;if(N().d(a))return new te;if(a instanceof J&&(b=a.Z,d=a.Fa,N().d(b)))return d;ue();b=new ve(a,ue().bx);d=new G(n=>n instanceof we?new E(n.Yi):F());c=ue().cx;b=b.Xw.kf(b.Ww,d,c);if(b instanceof E)return new we(new se(b.zc));if(F()===b){ue();b=new ve(a,ue().bx);d=new G(n=>n instanceof +xe?new E(n.Yc):F());c=ue().cx;b=b.Xw.kf(b.Ww,d,c);if(b instanceof E)return new xe(new se(b.zc));if(F()===b)return new se(a);throw new D(b);}throw new D(b);}if(c instanceof J){b=c.Fa;d=c.Z;if(N().d(d)){d=N();a=new J(b,a);b=d;continue}if(d instanceof J){c=d.Z;f=d.Fa;var g=Ee(He(),b,f);if(g instanceof se){var h=g.Bd;if(h instanceof J){var k=h.Z;h=h.Fa;if(k instanceof J){var m=k.Z;k=k.Fa;if(N().d(m)&&h===b&&k===f){a=new J(b,a);b=d;continue}}}}b=new J(g,c);continue}}throw new D(c);}} +function Ie(){this.UE=this.TE=null;Je=this;this.TE=(Ke(),new Nb(Ob().Qf));Qd(new Rd(0),p(65535));this.UE=new E(void 0)}Ie.prototype=new t;Ie.prototype.constructor=Ie;function Le(a,b){if(N().d(b))return new te;if(b instanceof J){a=b.Z;var c=b.Fa;if(N().d(a))return c}ue();a=new ve(b,ue().bx);c=new G(f=>f instanceof Me?new E(f):F());var d=ue().cx;a=a.Xw.kf(a.Ww,c,d);if(a instanceof E)b=new se(a.zc);else if(F()===a)b=new Ne(b);else throw new D(a);return b} +function Oe(a,b){a:{for(a=b;!a.e();){if(!(a.u()instanceof Me)){a=!1;break a}a=a.A()}a=!0}if(a)a=qe(0,b);else a:for(a=N();;){var c=b;if(N().d(c)){a=re(a);for(var d=b=null;a!==N();){c=a.u();for(c=(c instanceof se?c.Bd:c instanceof Ne?c.Ge:new J(c,N())).i();c.n();){var f=new J(c.j(),N());null===d?b=f:d.Z=f;d=f}a=a.A()}a=null===b?N():b;a=Le(He(),a);break a}if(c instanceof J){d=c.Fa;b=c.Z;if(N().d(b)){b=N();a=new J(d,a);continue}if(b instanceof J){c=b.Z;f=b.Fa;var g=Pe(He(),d,f);if(g instanceof Ne){var h= +g.Ge;if(h instanceof J){var k=h.Z;h=h.Fa;if(k instanceof J){var m=k.Z;k=k.Fa;if(N().d(m)&&h===d&&k===f){a=new J(d,a);continue}}}}if(g instanceof se&&(h=g.Bd,h instanceof J&&(k=h.Z,h=h.Fa,k instanceof J&&(m=k.Z,k=k.Fa,N().d(m)&&h===d&&k===f)))){a=new J(d,a);continue}b=new J(g,c);continue}}throw new D(c);}return a} +function Qe(a,b){for(;;){a:{if(!(b instanceof Re||b instanceof Se||Te()===b||b instanceof Ue||b instanceof Ve||We()===b||Xe()===b||ef()===b||jf()===b||b instanceof kf||b instanceof te||b instanceof lf||b instanceof mf))break a;return!0}if(b instanceof nf)b=b.Xi;else if(b instanceof of)b=b.Pk;else{if(b instanceof pf){var c=b.Vh;if(Qe(a,b.Uh)){b=c;continue}else return!1}if(b instanceof qf)if(c=b.Sk,Qe(a,b.Rk)){b=c;continue}else return!1;if(b instanceof rf)b=b.Xh;else if(b instanceof sf)b=b.Jj;else{if(b instanceof +Ne){for(a=b.Ge;!a.e();){b=a.u();if(!Qe(He(),b))return!1;a=a.A()}return!0}if(b instanceof se){for(a=b.Bd;!a.e();){b=a.u();if(!Qe(He(),b))return!1;a=a.A()}return!0}if(b instanceof tf)b=b.Wh;else if(b instanceof xe)b=b.Yc;else return!1}}}} +function uf(a,b){for(a=b;;){a:{b:if(!(a instanceof we||a instanceof mf||a instanceof te)){if(null!==a&&(b=vf().Jh(a),!b.e())){b.oa();break b}break a}return!0}if(a instanceof se){for(a=a.Bd;!a.e();){b=a.u();if(!uf(He(),b))return!1;a=a.A()}return!0}if(a instanceof Ne){for(a=a.Ge;!a.e();){b=a.u();if(!uf(He(),b))return!1;a=a.A()}return!0}if(a instanceof sf)a=a.Jj;else if(a instanceof rf)a=a.Xh;else return!1}} +function wf(a,b){for(;;){a:{if(ef()!==b&&jf()!==b&&!(b instanceof kf))break a;return!0}if(b instanceof nf)b=b.Xi;else{if(b instanceof pf){var c=b.Vh;if(wf(a,b.Uh)){b=c;continue}else return!1}if(b instanceof xf)if(c=b.Uf,wf(a,b.Th)){b=c;continue}else return!1;if(b instanceof rf)b=b.Xh;else return!1}}} +function yf(a,b){for(;;){a:{if(ef()!==b&&jf()!==b&&!(b instanceof kf))break a;return!0}if(b instanceof nf)b=b.Xi;else{if(b instanceof pf){var c=b.Vh;if(yf(a,b.Uh)){b=c;continue}else return!1}if(b instanceof xf)if(c=b.Uf,yf(a,b.Th)){b=c;continue}else return!1;if(b instanceof rf)b=b.Xh;else if(b instanceof Ne)b=b.Ge.tf();else return!1}}} +function zf(a,b){for(;;){if(b instanceof kf)return new E(b.mf);if(null!==b){var c=Af().Jh(b);if(!c.e())return new E(p($a(c.oa())))}if(b instanceof nf)return a=b.Yl,a instanceof Bf?new E(a.Ij):F();if(b instanceof of)return a=b.Zl,a instanceof Bf?new E(a.Ij):F();if(b instanceof pf){c=b.Vh;a=zf(a,b.Uh);if(a.e())return F();a=a.oa();b=zf(He(),c);return b.e()?F():new E(new C(a,b.oa()))}if(b instanceof xf){c=b.Uf;a=zf(a,b.Th);if(a.e())return F();a=a.oa();b=zf(He(),c);return b.e()?F():new E(new C(a,b.oa()))}if(b instanceof +qf){c=b.Sk;a=zf(a,b.Rk);if(a.e())return F();a=a.oa();b=zf(He(),c);return b.e()?F():new E(new C(a,b.oa()))}if(b instanceof Cf){c=b.am;a=zf(a,b.$l);if(a.e())return F();a=a.oa();b=zf(He(),c);return b.e()?F():new E(new C(a,b.oa()))}if(b instanceof se&&(c=b.Bd,c instanceof J)){b=c.Z;a=zf(a,c.Fa);if(a.e())b=!1;else a:{for(;!b.e();){c=b.u();c=zf(He(),c);if(null===c?null!==a:!c.d(a)){b=!1;break a}b=b.A()}b=!0}return b?a:F()}if(b instanceof Ne&&(c=b.Ge,c instanceof J)){b=c.Z;a=zf(a,c.Fa);if(a.e())b=!1;else a:{for(;!b.e();){c= +b.u();c=zf(He(),c);if(null===c?null!==a:!c.d(a)){b=!1;break a}b=b.A()}b=!0}return b?a:F()}if(b instanceof sf)b=b.Jj;else if(b instanceof rf)b=b.Xh;else if(b instanceof Se)b=b.Wl;else if(b instanceof Re)b=b.Vl;else{a:{if(!(b instanceof lf||b instanceof Df||b instanceof xe||b instanceof tf||We()===b||Xe()===b||b instanceof Ve))break a;return a.UE}a:{if(!(b instanceof Ef||b instanceof Ff||b instanceof Gf||jf()===b||ef()===b||b instanceof te||b instanceof Ue||Te()===b||b instanceof we||b instanceof se&& +N().d(b.Bd)||b instanceof Ne&&N().d(b.Ge)||b instanceof Hf||b instanceof If||b instanceof mf))break a;return F()}throw new D(b);}}} +function Jf(a,b){for(a=b;;){if(a instanceof kf)return Da(a.mf,void 0);a:{if(We()!==a&&Xe()!==a&&!(a instanceof xe||a instanceof tf||a instanceof Ve||a instanceof te||a instanceof lf||a instanceof Df))break a;return!0}if(a instanceof se){for(a=a.Bd;!a.e();){b=a.u();if(!Jf(He(),b))return!1;a=a.A()}return!0}if(a instanceof Ne){for(a=a.Ge;!a.e();){b=a.u();if(!Jf(He(),b))return!1;a=a.A()}return!0}if(a instanceof sf)a=a.Jj;else if(a instanceof rf)a=a.Xh;else if(a instanceof Se)a=a.Wl;else if(a instanceof +Re)a=a.Vl;else{a:{if(!(a instanceof we||a instanceof mf||a instanceof Cf||a instanceof qf||a instanceof of||a instanceof Hf||a instanceof Ff||a instanceof Ef||Te()===a||a instanceof Ue||ef()===a||jf()===a||a instanceof xf||a instanceof pf||a instanceof nf||a instanceof If||a instanceof Gf))break a;return!1}throw new D(a);}}} +function Vf(a,b){for(;;){var c=b;if(c instanceof Me)return Wf(a,c);a:{if(jf()!==c&&ef()!==c&&!(c instanceof kf))break a;return I().Wf}if(wf(a,c))return I().Wf;if(c instanceof nf)b=c.Xi;else{if(c instanceof If)return new If(c.fq,Vf(a,c.gq));if(c instanceof tf)return c.Wh;if(c instanceof lf||c instanceof Df)return c;if(c instanceof Re)return b=c.Vl,I(),a=Vf(a,b),a instanceof Me?Xf(0,a):Qe(He(),a)?a:a instanceof tf?new tf(new Re(a.Wh)):new Re(a);if(c instanceof Ne){var d=c.Ge;if(d===N())c=N();else{c= +d.u();c=Vf(He(),c);var f=c=new J(c,N());for(d=d.A();d!==N();){var g=d.u();g=Vf(He(),g);g=new J(g,N());f=f.Z=g;d=d.A()}}c=Oe(0,c);f=b;if(null===c?null===f:c.d(f))return b;b=c}else{if(c instanceof xf){b=c.Uf;c=Vf(a,c.Th);if(c instanceof xf)return new xf(c.Th,Vf(a,new xf(new tf(c.Uf),b)));if(c===I().Wf)continue;a=Vf(a,b);return a===I().Wf?c:new xf(c,a)}if(c instanceof pf){b=c.Vh;c=Vf(a,c.Uh);if(c instanceof pf)return new pf(c.Uh,Vf(a,new pf(new tf(c.Vh),b)));if(c===I().Wf)continue;a=Vf(a,b);return a=== +I().Wf?c:new pf(c,a)}if(c instanceof Gf)return a=c.Xn,a instanceof Yf?b:new Gf(new Yf(a));if(c instanceof rf)return new rf(c.kq,Vf(a,c.Xh));a:{if(We()!==c&&Xe()!==c)break a;return b}throw new D(c);}}}}function Zf(a){if(a instanceof Me)return a;$f();a="violated invariant: "+a+" should be a Parser";throw ag(new bg,a);} +function Wf(a,b){for(;;){var c=b;if(c instanceof of)b=c.Pk;else{if(c instanceof Hf)return new Hf(c.hq,Vf(a,c.iq));if(c instanceof we)return c.Yi;if(c instanceof xe)return c.Yc;if(c instanceof Se)return b=c.Wl,Xf(I(),Wf(a,b));if(c instanceof se){var d=c.Bd;if(d===N())c=N();else{c=d.u();c=Wf(He(),c);var f=c=new J(c,N());for(d=d.A();d!==N();){var g=d.u();g=Wf(He(),g);g=new J(g,N());f=f.Z=g;d=d.A()}}c=qe(0,c);f=b;if(null===c?null===f:c.d(f))return b;b=c}else{if(c instanceof Cf){b=c.am;c=Vf(a,c.$l);if(c instanceof +xf)return f=c.Uf,new Cf(c.Th,Vf(a,cg(I(),f.Te(),b)));if(c instanceof Cf)return f=c.am,new Cf(c.$l,Vf(a,cg(I(),f.Te(),b)));if(c===I().Wf){b=Zf(b);continue}a=Vf(a,b);return a===I().Wf?Zf(c):new Cf(c,a)}if(c instanceof qf){b=c.Sk;c=Vf(a,c.Rk);if(c instanceof pf)return f=c.Vh,new qf(c.Uh,Vf(a,dg(I(),f.Te(),b)));if(c instanceof qf)return f=c.Sk,new qf(c.Rk,Vf(a,dg(I(),f.Te(),b)));if(c===I().Wf){b=Zf(b);continue}a=Vf(a,b);return a===I().Wf?Zf(c):new qf(c,a)}if(c instanceof Ff)return a=c.Xl,a instanceof +eg?b:new Ff(new eg(a));if(c instanceof Ef)return b=c.Yn,f=c.Qk,new Ef(Wf(a,c.bm),b,f,Zc().CE);if(c instanceof sf)return new sf(c.lq,Wf(a,c.Jj));a:{if(Te()!==c&&!(c instanceof Ue||c instanceof Ve||c instanceof mf||c instanceof te))break a;return b}throw new D(c);}}}}function fg(a,b,c){a=c.Wa;b=b.qa(c);null!==c.bb&&(c.Wa=a);return b}function gg(a,b,c){a=hg(c,new G(d=>d instanceof ig&&d.Un===b?!1:!0));return a.e()?(Ob(),new $b(new ig(b))):a} +function jg(a,b,c){var d=c.Wa;a=a.TE;for(var f=0;fm=>lg(k,new G((n=>q=>jc(Ob(),n,q))(m))))(h)));c.bb=null;f=1+f|0}c.bb=lg(a,new G(k=>gg(He(),d,k)));return null}function mg(a,b,c,d){var f=d.Wa;a=ng(b,d.Yh,f);if(null===a)return d.bb=(Ke(),new og(new pg(()=>{Ob();return new $b(new qg(f,rg(N(),c)))}))),null;d.Wa=f+a.length|0;return a} +function sg(a,b,c,d){a=b.qa(d);return null===d.bb?(c=c.qa(d),d.We&&null===d.bb?new C(a,c):null):null}function tg(a,b,c,d){a=d.Wa;b=b.qa(d);if(null===d.bb){var f=d.Wa;c=c.qa(d);return null!==d.bb?(d.Wa===f&&(d.Wa=a),null):d.We?new C(b,c):null}return null}function Bg(a,b,c,d){a=b.qa(d);return null===d.bb&&d.We?c.g(a):null} +function Cg(a,b,c,d){a=d.We;d.We=!0;b=b.qa(d);d.We=a;if(null===d.bb){if(b instanceof Dg)return b=b.lj,c=c.qa(d),a&&null===d.bb?new Dg(new C(b,c)):null;if(b instanceof Eg)return ue(),b;throw new D(b);}return null}function Fg(a,b){for(a=b;;){a=a.za();if(a instanceof Ff)return Gg(0,a.Xl);if(a instanceof Gf)a=a.Xn;else return a}}function Gg(a,b){for(a=b;;)if(a=a.za(),a instanceof Ff)a=a.Xl;else return a} +function Hg(a){return rg(N(),new hd(nd(Ig(),new Jg(new J(new C(a.Hj,a.Ok),N()))).i(),new G(b=>""+Na($a(b)))))} +function Pe(a,b,c){for(var d=c;;){c=b;var f=d;if(c instanceof Me&&f instanceof Me)return Ee(a,c,f);if(yf(a,b))return b;if(c instanceof te)return d;if(f instanceof te)return b;if(c instanceof Ne&&f instanceof se)d=new Ne(f.Bd);else{if(c instanceof se){var g=c.Bd;if(f instanceof Ne){b=new Ne(g);continue}}if(c instanceof Ne&&(g=c.Ge,f instanceof Ne)){var h=f.Ge;if(h instanceof J){c=h.Z;b=h.Fa;b=Pe(a,g.tf(),b);a:{if(!(b instanceof se||b instanceof Ne))break a;return new Ne(Kg(h,g))}b=new Ne(g.$j().pa(b)); +if(c instanceof J&&(d=c.Z,f=c.Fa,N().d(d))){d=f;continue}d=c=new Ne(c);continue}}if(f instanceof Ne&&(g=f.Ge,g instanceof J)){b=g.Z;a=Pe(a,c,g.Fa);a:{if(!(a instanceof se||a instanceof Ne))break a;return new Ne(new J(c,g))}return new Ne(new J(a,b))}if(f instanceof se&&(g=f.Bd,g instanceof J)){b=g.Z;a:{a=Pe(a,c,g.Fa);b:{if(!(a instanceof se||a instanceof Ne))break b;a=new Ne(new J(c,g));break a}a=a instanceof Me?new se(new J(a,b)):new Ne(new J(a,b))}return a}if(c instanceof Ne){c=c.Ge;a=Pe(a,c.tf(), +f);a:{if(!(a instanceof se||a instanceof Ne))break a;return new Ne(Lg(c,f))}return new Ne(c.$j().pa(a))}if(c instanceof se){c=c.Bd;a:{a=Pe(a,c.tf(),f);b:{if(!(a instanceof se||a instanceof Ne))break b;a=new Ne(Lg(c,f));break a}a=a instanceof Me?new se(c.$j().pa(a)):new Ne(c.$j().pa(a))}return a}return c instanceof tf&&(g=c.Wh,f instanceof tf)?Pe(a,g,f.Wh).Te():c instanceof tf&&(g=c.Wh,Jf(0,f))||c instanceof xe&&(g=c.Yc,Jf(0,f))?Pe(a,g,f).Te():f instanceof tf&&(g=f.Wh,Jf(0,c))?Pe(a,c,g).Te():f instanceof +xe&&(f=f.Yc,Jf(0,c))?Pe(a,c,f).Te():new Ne(new J(b,new J(d,N())))}}} +function Ee(a,b,c){for(var d=c,f=b;;){var g=f,h=d;if(g instanceof te)return d;if(h instanceof te)return f;if(g instanceof se){var k=g.Bd;if(h instanceof se){var m=h.Bd;if(m instanceof J){var n=m.Z,q=m.Fa,u=Ee(a,k.tf(),q);if(u instanceof se)return new se(Kg(m,k));var v=new se(k.$j().pa(u));if(n instanceof J){var w=n.Z,A=n.Fa;if(N().d(w)){f=v;d=A;continue}}var K=new se(n);f=v;d=K;continue}}}if(h instanceof se){var M=h.Bd;if(M instanceof J){var S=M.Z,L=Ee(a,g,M.Fa);if(L instanceof se)return new se(new J(g, +M));if(0<=S.Qa(2)){var aa=new se(S);f=L;d=aa}else{var U=S.u();f=L;d=U}continue}}if(g instanceof se){var ka=g.Bd,ra=Ee(a,ka.tf(),h);if(ra instanceof se)return new se(Lg(ka,h));var ha=ka.$j();f=0<=ha.Qa(2)?new se(ha):ha.u();d=ra}else{if(g instanceof Ue&&Te()===h)return Te();if(Te()===g)a:{if(!(h instanceof Ue||h instanceof Ve||h instanceof mf))break a;return Te()}if(g instanceof Ue){var fb=g.Hj,xb=g.Ok;if(h instanceof Ue){var pb=h.Hj,Ra=h.Ok;return Pd(I(),nd(Ig(),new Jg(new J(new C(fb,xb),new J(new C(pb, +Ra),N())))))}}if(g instanceof xe){var xc=g.Yc;if(xc instanceof Ue&&h instanceof Ve){I();var ma=Hg(xc);if(ma===N())var Za=N();else{for(var qd=ma.u(),Sc=new J(new Ve(qd),N()),qc=Sc,qa=ma.A();qa!==N();){var Eb=qa.u(),Fa=new J(new Ve(Eb),N());qc=qc.Z=Fa;qa=qa.A()}Za=Sc}return Mg(0,Kg(new J(d,N()),Za))}}if(g instanceof we){var Sb=g.Yi;if(Sb instanceof Ue)a:{b:{if(null!==h){var Tc=vf().Jh(h);if(!Tc.e()){Tc.oa();break b}}if(!(h instanceof mf))break a}f=new mf(Ng().gi(Hg(Sb),(ue(),Og(ue().Yg))));continue}}if(g instanceof +Ve){var fh=g.Vf;if(h instanceof xe){var Kf=h.Yc;if(Kf instanceof Ue){I();var ce=new Ve(fh),ye=Hg(Kf);if(ye===N())var Lf=N();else{for(var ug=ye.u(),gh=new J(new Ve(ug),N()),de=gh,Gd=ye.A();Gd!==N();){var yb=Gd.u(),vg=new J(new Ve(yb),N());de=de.Z=vg;Gd=Gd.A()}Lf=gh}return Mg(0,new J(ce,Lf))}}}a:{b:{if(null!==g){var ze=vf().Jh(g);if(!ze.e()){ze.oa();break b}}if(!(g instanceof mf))break a}if(h instanceof we){var Hd=h.Yi;if(Hd instanceof Ue){d=new mf(Ng().gi(Hg(Hd),(ue(),Og(ue().Yg))));continue}}}if(g instanceof +Ve){var Ae=g.Vf;if(h instanceof Ve){var Ye=h.Vf;return Ye.startsWith(Ae)?f:new xe(new mf(Ng().gi(Pg(Qg(),new (B(fa).P)([Ae,Ye])),(ue(),Og(ue().Yg)))))}}if(null!==g){var Mf=vf().Jh(g);if(!Mf.e()){var Ze=Mf.oa();if(null!==h){var Nf=vf().Jh(h);if(!Nf.e()){var bd=Nf.oa();if(bd.startsWith(Ze))return f;if(0===(1^Ze.length|1^bd.length)){var Id=Pd(I(),new J(p(Rg(Cd(),Ze)),new J(p(Rg(Cd(),bd)),N())));return Yd(I(),Id)}return new mf(Ng().gi(Pg(Qg(),new (B(fa).P)([Ze,bd])),(ue(),Og(ue().Yg))))}}}}if(g instanceof +mf){var Uc=g.Zg;if(null!==h){var Be=vf().Jh(h);if(!Be.e()){for(var $e=Be.oa(),af=!1,Of=Uc.i();!af&&Of.n();){var Ce=Of.j();af=!!$e.startsWith(Ce)&&Ce.length<=$e.length}return af?f:new mf(Uc.ih($e))}}}if(g instanceof xe){var rd=g.Yc;if(rd instanceof mf){var ee=rd.Zg;if(h instanceof Ve){for(var De=h.Vf,yc=!1,wg=ee.i();!yc&&wg.n();){var bf=wg.j();yc=!!De.startsWith(bf)&&bf.length<=De.length}return yc?f:new xe(new mf(ee.ih(De)))}}}if(null!==g){var cf=vf().Jh(g);if(!cf.e()){var xg=cf.oa();if(h instanceof +mf){for(var df=h.Zg,Pf=new Sg(df.Ba),rc=new Tg(df.Ta,F(),df.Ba);rc.n();){var sc=rc.j();!0!==!!sc.startsWith(xg)&&Ug(Pf,sc)}var Jc=Vg(Pf);return Jc.e()?f:new mf(Wg(Jc,xg))}}}if(g instanceof Ve){var Jd=g.Vf;if(h instanceof xe){var Kd=h.Yc;if(Kd instanceof mf){for(var ff=Kd.Zg,Qf=new Sg(ff.Ba),Rf=new Tg(ff.Ta,F(),ff.Ba);Rf.n();){var fe=Rf.j();!0!==!!fe.startsWith(Jd)&&Ug(Qf,fe)}var gf=Vg(Qf);return gf.e()?f:new xe(new mf(Wg(gf,Jd)))}}}if(g instanceof mf){var Sf=g.Zg;if(h instanceof mf){for(var Tf=h.Zg, +zc=new Sg(Tf.Ba),sd=new Tg(Tf.Ta,F(),Tf.Ba);sd.n();){for(var Ld=sd.j(),ge=!1,he=Sf.i();!ge&&he.n();){var yg=he.j();ge=!!Ld.startsWith(yg)&&yg.length<=Ld.length}!0!==ge&&Ug(zc,Ld)}var Ac=Vg(zc);return Ac.e()?f:new mf(Sf.Em(Ac))}}if(g instanceof xe){var Fe=g.Yc;if(Fe instanceof mf){var Md=Fe.Zg;if(h instanceof xe){var ie=h.Yc;if(ie instanceof Ue){for(var cd=Ng().gi(Hg(ie),(ue(),Og(ue().Yg))),Ge=new Sg(cd.Ba),hf=new Tg(cd.Ta,F(),cd.Ba);hf.n();){for(var qi=hf.j(),hh=!1,Sj=Md.i();!hh&&Sj.n();){var Tj= +Sj.j();hh=!!qi.startsWith(Tj)&&Tj.length<=qi.length}!0!==hh&&Ug(Ge,qi)}var ri=Vg(Ge);return ri.e()?f:new xe(new mf(Md.Em(ri)))}}}}if(g instanceof xe){var si=g.Yc;if(si instanceof Ue&&h instanceof xe){var dd=h.Yc;if(dd instanceof mf){for(var zg=dd.Zg,je=Ng().gi(Hg(si),(ue(),Og(ue().Yg))),Uf=new Sg(zg.Ba),Ag=new Tg(zg.Ta,F(),zg.Ba);Ag.n();){for(var ih=Ag.j(),rl=!1,Oq=je.i();!rl&&Oq.n();){var IA=Oq.j();rl=!!ih.startsWith(IA)&&IA.length<=ih.length}!0!==rl&&Ug(Uf,ih)}var sl=Vg(Uf);return sl.e()?f:new xe(new mf(je.Em(sl)))}}}if(g instanceof +xe){var XG=g.Yc;if(h instanceof xe){var Pq=Ee(a,XG,h.Yc);return Wd(I(),Pq)}}if(g instanceof we){var JA=g.Yi;if(h instanceof we){var tl=Ee(a,JA,h.Yi);return Yd(I(),tl)}}if(g instanceof xe){var Uj=g.Yc;if(Jf(0,h)){var Qu=Ee(a,Uj,h);return Wd(I(),Qu)}}if(h instanceof xe){var ul=h.Yc;if(Jf(0,g)){var KA=Ee(a,g,ul);return Wd(I(),KA)}}return new se(new J(f,new J(d,N())))}}}z(Ie,"cats.parse.Parser$Impl$",{NU:1});var Je;function He(){Je||(Je=new Ie);return Je}function Xg(){}Xg.prototype=new t; +Xg.prototype.constructor=Xg;z(Xg,"cats.parse.Parser$Impl$CharsRange$",{SU:1});var Yg;function Zg(){}Zg.prototype=new t;Zg.prototype.constructor=Zg;Zg.prototype.Jh=function(a){if(a instanceof kf&&""===a.mf)return new E("");if(a instanceof of){var b=a.Pk;a=a.Zl;if(a instanceof Bf&&(a=a.Ij,"string"===typeof a)){if(b instanceof Ve){var c=b.Vf;if(c===a)return new E(c)}if(null!==b&&(b=Af().Jh(b),!b.e()&&(b=$a(b.oa()),1===a.length&&a.charCodeAt(0)===b)))return new E(a)}}return F()}; +z(Zg,"cats.parse.Parser$Impl$DefiniteString$",{WU:1});var $g;function vf(){$g||($g=new Zg);return $g}function ah(){}ah.prototype=new t;ah.prototype.constructor=ah;ah.prototype.Jh=function(a){if(a instanceof Ue){var b=a.Hj;Ig();a=a.Ok;var c=0;for(var d=bh(a),f=0;fd.V);return le(I(),a,b)}z(mh,"cats.parse.Parser$With1$",{GV:1});var ph;function qh(){ph||(ph=new mh);return ph}function rh(){this.fb=0;this.gb=!1} +rh.prototype=new t;rh.prototype.constructor=rh;function sh(){}sh.prototype=rh.prototype;function Zd(a){return th(I(),new J(uh(I(),a,new G(b=>new E(b))),I().YE))}e=rh.prototype;e.Te=function(){return oh(I(),this)};e.Ez=function(a){return cg(I(),this,a)};e.Fz=function(a){return this.Te().Ez(a).Nm(new G(b=>b.V))};function vh(a,b){return th(I(),new J(a,new J(b,N())))}e.Nm=function(a){return uh(I(),this,a)}; +e.yB=function(a){I();if(this instanceof Me)a=wh(0,this,a);else{var b=this.Te();a=(He(),Da(a,void 0))?b:wf(He(),b)?new kf(a):new nf(b,new Bf(a))}return a};e.p=function(){this.gb||(this.fb=xh(this),this.gb=!0);return this.fb};var yh=z(0,"cats.parse.Parser0",{vb:1});function zh(a){Ah();a=null===a?null:Bh(Ch(),a);return Dh(a,"[",", ","]")}function Eh(a,b,c,d){this.ox=a;this.bF=b;this.dF=c;this.cF=d}Eh.prototype=new t;Eh.prototype.constructor=Eh; +Eh.prototype.f=function(){var a=zh(this.dF),b=zh(this.cF);return"RadixNode("+this.ox+", "+this.bF+", "+a+", "+b+")"};function ng(a,b,c){if(0>c||c>b.length)b=null;else a:for(var d=c;;)if(dp(Rg(Cd(),v)))));a=Lh(a);b=1+a|0;var q=Mh(Nh(),l(fa),new y(new Int32Array([b]))),u=Mh(Nh(),l(Gh),new y(new Int32Array([b])));Oh||(Oh= +new Ph);b=Oh.Sd();for(d=d.i();d.n();)g=d.j(),h=Rg(Cd(),g)&a,Qh(b,h,new Rh(()=>new Jh)).Ia(g);d=Sh().sw;for(b=b.i();b.n();){g=b.j();if(null===g)throw new D(g);d=Th(d,g.Y,g.V.Sa())}d.Be.Oa(new G(v=>{if(null!==v){var w=v.Y|0;v=v.V;var A=(U,ka)=>Uh().ZE.BB(U,ka);b:{if(Tb(v)&&0Wh(Cd(),U,aa.length);if(v===N())v=N();else{M=v.u();K=M=new J(S(M),N());for(v=v.A();v!==N();)L=v.u(),L=new J(S(L),N()),K=K.Z=L,v=v.A();v=M}u.a[w]=Hh(A,n,""+c+aa,v)}else throw new D(v);}));return new Eh(n,a,q,u)};function Lh(a){for(var b=0;;){if(65535===b)return b;var c=a.X();if((1+b|0)>=c&&od(pd(),new hd(a.i(),new G((d=>f=>$a(f)&d)(b)))).X()===c)return b;b=1|b<<1}} +function Yh(){this.ZE=this.$E=this.aF=null;Zh=this;this.aF=Mh(Nh(),l(fa),new y(new Int32Array([1])));this.$E=Mh(Nh(),l(Gh),new y(new Int32Array([1])));this.ZE=new $h}Yh.prototype=new t;Yh.prototype.constructor=Yh;z(Yh,"cats.parse.RadixNode$",{IV:1});var Zh;function Uh(){Zh||(Zh=new Yh);return Zh} +function ai(){this.mq=this.$z=this.gF=this.Zn=this.fF=this.eF=null;bi=this;this.eF=Vd(Pd(I(),Qd(new Rd(65),p(90))),Pd(I(),Qd(new Rd(97),p(122))));Pd(I(),Qd(new Rd(48),p(49)));Pd(I(),Qd(new Rd(1),p(127)));Xd(I(),13);Xd(I(),10);this.fF=ci(I(),"\r\n");di(I(),127,Qd(new Rd(0),p(31)));Od||(Od=new Nd);this.Zn=Od.ex;Xd(I(),34);Vd(this.Zn,ei());this.gF=Xd(I(),9);this.$z=Xd(I(),32);this.mq=Vd(this.$z,this.gF);Sd(I(),Vd(this.mq,fi(this.fF,this.mq)),new gi).Te();Pd(I(),Qd(new Rd(0),p(255)));Pd(I(),Qd(new Rd(33), +p(126)))}ai.prototype=new t;ai.prototype.constructor=ai;z(ai,"cats.parse.Rfc5234$",{KV:1});var bi;function hi(){bi||(bi=new ai);return bi}function ii(){}ii.prototype=new t;ii.prototype.constructor=ii;z(ii,"cats.syntax.EitherUtil$",{sW:1});var ji;function ki(){}ki.prototype=new t;ki.prototype.constructor=ki;function li(a){mi||(mi=new ni);var b=mi.dE;a=oi().Oz.eh(a,pi(),new Qb((c,d)=>ti(ti(c,b.Lw(d)),"\n")));return""+(a.e()?"":ui(Cd(),a.Hb.F))}z(ki,"cats.syntax.FoldableOps$",{wW:1});var vi; +function wi(a,b){this.iF=a;this.hF=b}wi.prototype=new t;wi.prototype.constructor=wi;wi.prototype.zg=function(a){return(Ic(),this.hF).I(this.iF,a)};z(wi,"cats.syntax.OrderOps",{RW:1});function xi(a,b,c,d,f,g){this.JN=a;this.NH=b;this.NN=c;this.MN=d;this.LN=f;this.KN=g}xi.prototype=new t;xi.prototype.constructor=xi;z(xi,"java.lang.Long$StringRadixInfo",{S1:1});function yi(){}yi.prototype=new t;yi.prototype.constructor=yi;z(yi,"java.lang.Math$",{T1:1});var zi;function Ai(){zi||(zi=new yi)} +function Bi(){this.OH=null;Ci=this;this.OH=new Di(!1)}Bi.prototype=new t;Bi.prototype.constructor=Bi;z(Bi,"java.lang.System$Streams$",{a2:1});var Ci; +function Ei(){this.PH=this.SB=null;Fi=this;this.SB={"java.version":"1.8","java.vm.specification.version":"1.8","java.vm.specification.vendor":"Oracle Corporation","java.vm.specification.name":"Java Virtual Machine Specification","java.vm.name":"Scala.js","java.vm.version":"1.20.2","java.specification.version":"1.8","java.specification.vendor":"Oracle Corporation","java.specification.name":"Java Platform API Specification","file.separator":"/","path.separator":":","line.separator":"\n"};this.PH=null} +Ei.prototype=new t;Ei.prototype.constructor=Ei;function Gi(a,b,c){return null!==a.SB?(a=a.SB,Hi().Tv.call(a,b)?a[b]:c):Gi(a.PH,b,c)}z(Ei,"java.lang.System$SystemProperties$",{b2:1});var Fi;function Ii(){Fi||(Fi=new Ei);return Fi}function Ji(){this.Tv=null;Ki=this;this.Tv=Object.prototype.hasOwnProperty}Ji.prototype=new t;Ji.prototype.constructor=Ji;z(Ji,"java.lang.Utils$Cache$",{c2:1});var Ki;function Hi(){Ki||(Ki=new Ji);return Ki} +function Li(a){return!!(a&&a.$classData&&1===a.$classData.Qo&&a.$classData.Po.wb.QN)}var ua=z(0,"java.lang.Void",{QN:1},a=>void 0===a),Ni=function Mi(a,b,c,d){var g=d.a[c],h=tb(b.ba,g);c=1+c|0;if(c>>1|0;else if(b instanceof ib)a=b.a.length;else if(b instanceof jb)a=b.a.length;else throw Qi("argument type mismatch");return a}z(Oi,"java.lang.reflect.Array$",{d2:1});var Ri;function Nh(){Ri||(Ri=new Oi);return Ri} +function Si(a,b){this.oF=a;this.pF=b}Si.prototype=new t;Si.prototype.constructor=Si;function Ti(a){return new (B(Ui).P)([a.oF,a.pF])}z(Si,"java.math.BigInteger$QuotAndRem",{FX:1});function Vi(){}Vi.prototype=new t;Vi.prototype.constructor=Vi;function Wi(a,b){if(0===b.ca)return 0;a=b.fa<<5;var c=b.N.a[b.fa-1|0];0>b.ca&&Xi(b)===(b.fa-1|0)&&(c=c-1|0);return a=a-Math.clz32(c)|0} +function Yi(a,b,c){a=c>>>5|0;c&=31;var d=(b.fa+a|0)+(0===c?0:1)|0;Zi();if(67108863>>0)throw new La("BigInteger would overflow supported range");var f=new y(d);$i(0,f,b.N,a,c);b=aj(b.ca,d,f);bj(b);return b}function $i(a,b,c,d,f){if(0===f)c.G(0,b,d,b.a.length-d|0);else{a=32-f|0;b.a[b.a.length-1|0]=0;for(var g=b.a.length-1|0;g>d;){var h=g;b.a[h]=b.a[h]|c.a[(g-d|0)-1|0]>>>a|0;b.a[g-1|0]=c.a[(g-d|0)-1|0]<>>31|0;f=1+f|0}0!==a&&(b.a[d]=a)}function dj(a,b,c){a=c>>>5|0;var d=31&c;if(a>=b.fa)return 0>b.ca?Zi().px:Zi().Mj;c=b.fa-a|0;var f=new y(1+c|0);ej(0,f,c,b.N,a,d);if(0>b.ca){for(var g=0;g>>g|0|d.a[1+(a+f|0)|0]<>>g|0}}z(Vi,"java.math.BitLevel$",{GX:1});var fj;function gj(){fj||(fj=new Vi);return fj} +function hj(){this.dA=this.eA=null;ij=this;this.eA=new y(new Int32Array([-1,-1,31,19,15,13,11,11,10,9,9,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5]));this.dA=new y(new Int32Array([-2147483648,1162261467,1073741824,1220703125,362797056,1977326743,1073741824,387420489,1E9,214358881,429981696,815730721,1475789056,170859375,268435456,410338673,612220032,893871739,128E7,1801088541,113379904,148035889,191102976,244140625,308915776,387420489,481890304,594823321,729E6,887503681,1073741824,1291467969, +1544804416,1838265625,60466176]))}hj.prototype=new t;hj.prototype.constructor=hj; +function jj(a,b){a=b.ca;var c=b.fa,d=b.N;if(0===a)return"0";if(1===c)return b=(d.a[0]>>>0).toString(10),0>a?"-"+b:b;b="";var f=new y(c);for(d.G(0,f,0,c);;){var g=0;for(d=c-1|0;0<=d;){var h=f.a[d];g=g+((h&~h)>>>31|0)|0;var k=65535&h,m=h>>>16|0,n=Math.imul(48770,k),q=Math.imul(4832,k),u=Math.imul(48770,m);k=n+((q+u|0)<<16)|0;n=(n>>>16|0)+u|0;n=(Math.imul(4832,m)+(n>>>16|0)|0)+(((65535&n)+q|0)>>>16|0)|0;u=65535&h;m=h>>>16|0;q=Math.imul(19247,u);u=Math.imul(28009,u);var v=Math.imul(19247,m);q=(q>>>16| +0)+v|0;q=(Math.imul(28009,m)+(q>>>16|0)|0)+(((65535&q)+u|0)>>>16|0)|0;m=k+q|0;n=n+((k&q|(k|q)&~m)>>>31|0)|0;q=65535&g;k=g>>>16|0;u=Math.imul(48770,q);v=Math.imul(4832,q);var w=Math.imul(48770,k);q=u+((v+w|0)<<16)|0;u=(u>>>16|0)+w|0;u=(Math.imul(4832,k)+(u>>>16|0)|0)+(((65535&u)+v|0)>>>16|0)|0;k=q+n|0;n=u+((q&n|(q|n)&~k)>>>31|0)|0;v=65535&g;q=g>>>16|0;u=Math.imul(19247,v);v=Math.imul(28009,v);var A=Math.imul(19247,q);w=u+((v+A|0)<<16)|0;u=(u>>>16|0)+A|0;q=((Math.imul(28009,q)+(u>>>16|0)|0)+(((65535& +u)+v|0)>>>16|0)|0)+((m&w|(m|w)&~(m+w|0))>>>31|0)|0;m=k+q|0;k=n+((k&q|(k|q)&~m)>>>31|0)|0;n=h-m|0;g=(g-k|0)+((~h&m|~(h^m)&n)>>31)|0;n=n>>>1|0|g<<31;q=n+m|0;g=q>>>29|0|(((g>>>1|0)+k|0)+((n&m|(n|m)&~q)>>>31|0)|0)<<3;f.a[d]=g;m=65535&g;g=h-(Math.imul(51712,m)+((Math.imul(15258,m)+Math.imul(51712,g>>>16|0)|0)<<16)|0)|0;d=d-1|0}d=""+g;for(b="000000000".substring(d.length)+d+b;0!==c&&0===f.a[c-1|0];)c=c-1|0;if(0===c)break}f=0;for(c=b.length;fa? +"-"+b:b} +function kj(a,b,c,d){if(0===(b|c))switch(d){case 0:return"0";case 1:return"0.0";case 2:return"0.00";case 3:return"0.000";case 4:return"0.0000";case 5:return"0.00000";case 6:return"0.000000";default:return(0>d?"0E+":"0E")+(-2147483648===d?"2147483648":""+(-d|0))}else{a=0>c;var f="";var g=18;if(a){var h=-b|0,k=h;c=(-c|0)+((b|h)>>31)|0}else k=b;for(;;){h=b=k;k=c;c=k>>31;var m=65535&h,n=h>>>16|0,q=Math.imul(26214,m),u=Math.imul(26214,m),v=Math.imul(26214,n);m=q+((u+v|0)<<16)|0;q=(q>>>16|0)+v|0;n=(Math.imul(26214, +n)+(q>>>16|0)|0)+(((65535&q)+u|0)>>>16|0)|0;u=65535&h;h=h>>>16|0;q=Math.imul(26215,u);u=Math.imul(26214,u);v=Math.imul(26215,h);q=(q>>>16|0)+v|0;q=(Math.imul(26214,h)+(q>>>16|0)|0)+(((65535&q)+u|0)>>>16|0)|0;h=m+q|0;n=n+((m&q|(m|q)&~h)>>>31|0)|0;q=65535&k;m=k>>>16|0;u=Math.imul(26214,q);v=Math.imul(26214,q);var w=Math.imul(26214,m);q=u+((v+w|0)<<16)|0;u=(u>>>16|0)+w|0;u=((Math.imul(1717986918,c)+Math.imul(26214,m)|0)+(u>>>16|0)|0)+(((65535&u)+v|0)>>>16|0)|0;m=q+n|0;n=(u+(n>>31)|0)+((q&n|(q|n)&~m)>>> +31|0)|0;v=65535&k;q=k>>>16|0;u=Math.imul(26215,v);v=Math.imul(26214,v);var A=Math.imul(26215,q);w=u+((v+A|0)<<16)|0;u=(u>>>16|0)+A|0;h=(((Math.imul(1717986919,c)+Math.imul(26214,q)|0)+(u>>>16|0)|0)+(((65535&u)+v|0)>>>16|0)|0)+((h&w|(h|w)&~(h+w|0))>>>31|0)|0;c=m+h|0;h=(n+(h>>31)|0)+((m&h|(m|h)&~c)>>>31|0)|0;c=c>>>2|0|h<<30;m=k>>>31|0;k=c+m|0;c=(h>>2)+((c&m|(c|m)&~k)>>>31|0)|0;g=g-1|0;h=k;f=""+(b-(Math.imul(10,65535&h)+(Math.imul(10,h>>>16|0)<<16)|0)|0)+f;if(0===(k|c))break}k=18-g|0;c=k-d|0;b=c-1|0; +k=((((k>>31)-(d>>31)|0)+((~k&d|~(k^d)&c)>>31)|0)-1|0)+((c|~b)>>>31|0)|0;if(0>>0:-1>>31|0)|0;var q=pj(qj(),n,g,d).h;g=q;var u=65535&q,v=q>>>16|0,w=65535&d,A=d>>>16|0;q=Math.imul(u,w);v=Math.imul(v,w);u=Math.imul(u,A);n=n-(q+((v+u|0)<<16)|0)|0;if(0!==g)for(g=1+g|0;;){q=g=g-1|0;A=k.a[h-2|0];u=65535&q;q=q>>>16|0;w=65535&A;A=A>>>16|0;v=Math.imul(u, +w);w=Math.imul(q,w);var K=Math.imul(u,A);u=v+((w+K|0)<<16)|0;v=(v>>>16|0)+K|0;q=(Math.imul(q,A)+(v>>>16|0)|0)+(((65535&v)+w|0)>>>16|0)|0;A=a.a[f-2|0];v=n+((A&~A)>>>31|0)|0;w=n+d|0;if(0===((n&d|(n|d)&~w)>>>31|0)&&(n=w,q===v?u>>>0>A>>>0:q>>>0>v>>>0))continue;break}}if(n=0!==g){rj();n=a;u=f-h|0;A=k;q=h;v=g;var M;for(K=w=M=0;K>>16|0;var U=65535&v,ka=v>>>16|0,ra=Math.imul(aa,U);U=Math.imul(L,U);var ha=Math.imul(aa,ka);aa=ra+((U+ha|0)<<16)|0;ra=(ra>>>16|0)+ +ha|0;ka=(Math.imul(L,ka)+(ra>>>16|0)|0)+(((65535&ra)+U|0)>>>16|0)|0;L=aa+M|0;M=ka+((aa&M|(aa|M)&~L)>>>31|0)|0;ka=n.a[u+S|0];aa=ka-L|0;ra=w;w=aa+ra|0;L=(((~ka&L|~(ka^L)&aa)>>31)+(ra>>31)|0)+((aa&ra|(aa|ra)&~w)>>>31|0)|0;n.a[u+S|0]=w;w=L;K=1+K|0}A=n.a[u+q|0];v=M;K=A-v|0;S=K+w|0;n.a[u+q|0]=S;n=0!==((((~A&v|~(A^v)&K)>>31)+(w>>31)|0)+((K&w|(K|w)&~S)>>>31|0)|0)}if(n)for(g=g-1|0,n=q=A=0;n>>31|0)|0)+((v&S|(v|S)&~A)>>>31|0)|0,a.a[(f- +h|0)+u|0]=A,A=q,q=0,n=1+n|0;null!==b&&(b.a[c]=g);f=f-1|0;c=c-1|0}if(0!==m)return ej(gj(),k,h,a,0,m),k;a.G(0,k,0,h);return a}function tj(a,b,c,d,f){a=0;for(d=d-1|0;0<=d;){var g=a;a=c.a[d];g=pj(qj(),a,g,f).h;var h=65535&g,k=g>>>16|0,m=65535&f;a=a-(Math.imul(h,m)+((Math.imul(k,m)+Math.imul(h,f>>>16|0)|0)<<16)|0)|0;b.a[d]=g;d=d-1|0}return a}z(nj,"java.math.Division$",{IX:1});var uj;function rj(){uj||(uj=new nj);return uj} +function vj(a,b,c,d){var f=new y(1+b|0),g=1,h=a.a[0],k=c.a[0],m=h+k|0;f.a[0]=m;h=(h&k|(h|k)&~m)>>>31|0;if(b>=d){for(;g>>31|0)+((n&q|(n|q)&~h)>>>31|0)|0;f.a[g]=h;h=k;g=1+g|0}for(;g>>31|0,f.a[g]=d,h=c,g=1+g|0}else{for(;g>>31|0)+((n&q|(n|q)&~h)>>>31|0)|0,f.a[g]=h,h=k,g=1+g|0;for(;g>>31|0,f.a[g]=b,h=a,g= +1+g|0}0!==h&&(f.a[g]=h);return f}function wj(a,b,c,d){for(var f=new y(b),g=0,h=0;g>31)+(q>>31)|0)+((n&q|(n|q)&~h)>>>31|0)|0;f.a[g]=h;h=k;g=1+g|0}for(;g>31)+((c&k|(c|k)&~d)>>>31|0)|0,f.a[g]=d,h=c,g=1+g|0;return f}function xj(){}xj.prototype=new t;xj.prototype.constructor=xj; +function yj(a,b,c){a=b.ca;var d=c.ca,f=b.fa,g=c.fa;if(0===a)return c;if(0===d)return b;if(2===(f+g|0)){b=b.N.a[0];c=c.N.a[0];if(a===d)return d=b+c|0,c=(b&c|(b|c)&~d)>>>31|0,0===c?zj(a,d):aj(a,2,new y(new Int32Array([d,c])));d=Zi();0>a?(a=f=c-b|0,c=(~c&b|~(c^b)&f)>>31):(a=f=b-c|0,c=(~b&c|~(b^c)&f)>>31);return Aj(d,a,c)}if(a===d)c=f>=g?vj(b.N,f,c.N,g):vj(c.N,g,b.N,f);else{var h=f!==g?f>g?1:-1:Bj(0,b.N,c.N,f);if(0===h)return Zi().Mj;1===h?c=wj(b.N,f,c.N,g):(a=d,c=wj(c.N,g,b.N,f))}a=aj(a|0,c.a.length, +c);bj(a);return a}function Bj(a,b,c,d){for(a=d-1|0;0<=a&&b.a[a]===c.a[a];)a=a-1|0;return 0>a?0:b.a[a]>>>0>>0?-1:1} +function Cj(a,b,c){var d=b.ca;a=c.ca;var f=b.fa,g=c.fa;if(0===a)return b;if(0===d)return Dj(c);if(2===(f+g|0)){var h=b.N.a[0];b=0;f=c.N.a[0];c=0;0>d&&(d=h,h=g=-d|0,b=(-b|0)+((d|g)>>31)|0);0>a&&(a=f,f=d=-a|0,c=(-c|0)+((a|d)>>31)|0);a=Zi();d=h;h=f;f=d-h|0;return Aj(a,f,(b-c|0)+((~d&h|~(d^h)&f)>>31)|0)}h=f!==g?f>g?1:-1:Bj(Ej(),b.N,c.N,f);if(0===(d^a|h))return Zi().Mj;-1===h?(h=-a|0,b=d===a?wj(c.N,g,b.N,f):vj(c.N,g,b.N,f)):d===a?(h=d,b=wj(b.N,f,c.N,g)):(h=d,b=vj(b.N,f,c.N,g));b=aj(h|0,b.a.length,b);bj(b); +return b}z(xj,"java.math.Elementary$",{JX:1});var Fj;function Ej(){Fj||(Fj=new xj);return Fj}function Gj(a,b){this.Vk=a;this.bo=b}Gj.prototype=new t;Gj.prototype.constructor=Gj;Gj.prototype.d=function(a){if(a instanceof Gj&&this.Vk===a.Vk){var b=this.bo;a=a.bo;return null===b?null===a:b===a}return!1};Gj.prototype.p=function(){return this.Vk<<3|this.bo.sy};Gj.prototype.f=function(){return"precision\x3d"+this.Vk+" roundingMode\x3d"+this.bo};z(Gj,"java.math.MathContext",{KX:1}); +function Hj(){this.rF=null;Ij=this;this.rF=new Gj(34,Jj().fA);Jj();Jj();Jj()}Hj.prototype=new t;Hj.prototype.constructor=Hj;z(Hj,"java.math.MathContext$",{LX:1});var Ij; +function Kj(a,b,c,d,f){var g;for(a=g=0;a>>16|0;var n=65535&f,q=f>>>16|0,u=Math.imul(m,n);n=Math.imul(k,n);var v=Math.imul(m,q);m=u+((n+v|0)<<16)|0;u=(u>>>16|0)+v|0;q=(Math.imul(k,q)+(u>>>16|0)|0)+(((65535&u)+n|0)>>>16|0)|0;k=m+g|0;g=q+((m&g|(m|g)&~k)>>>31|0)|0;b.a[h]=k;a=1+a|0}return g}function Lj(a,b){for(var c=new y(a),d=c.a[0]=1;dc;){var d=c;if(18>=d){sj().Wk.a[d]=Aj(Zi(),b,a);var f=sj().Xk,g=Zi(),h=b,k=a;f.a[d]=Aj(g,0===(32&d)?h<>>1|0)>>>(31-d|0)|0|k<>>16|0;d=Math.imul(5,65535&d);f=Math.imul(5,a);a=d+(f<<16)|0;d=(d>>>16|0)+f|0;d=Math.imul(5,b)+(d>>>16|0)|0;b=a;a=d}else sj().Wk.a[d]=Oj(sj().Wk.a[d-1|0],sj().Wk.a[1]),sj().Xk.a[d]=Oj(sj().Xk.a[d- +1|0],Zi().Lj);c=1+c|0}}Mj.prototype=new t;Mj.prototype.constructor=Mj; +function Pj(a,b,c){for(var d,f=0;f>>16|0;var v=65535&n;n=n>>>16|0;var w=Math.imul(u,v);v=Math.imul(m,v);var A=Math.imul(u,n);u=w+((v+A|0)<<16)|0;w=(w>>>16|0)+A|0;n=(Math.imul(m,n)+(w>>>16|0)|0)+(((65535&w)+v|0)>>>16|0)|0;w=u+q|0;m=w+d|0;q=(n+((u&q|(u|q)&~w)>>>31|0)|0)+((w&d|(w|d)&~m)>>>31|0)|0;c.a[g+k|0]=m;d=q;h=1+h|0}c.a[g+b|0]=d;f=1+f|0}cj(gj(),c,c,b<<1);for(g=f=d=0;f>>16|0,n=65535&m,m=m>>>16|0,u=Math.imul(d,n),n=Math.imul(q,n),w=Math.imul(d,m),d=u+((n+w|0)<<16)|0,u=(u>>>16|0)+w|0,m=(Math.imul(q,m)+(u>>>16|0)|0)+(((65535&u)+n|0)>>>16|0)|0,u=d+h|0,q=u+k|0,h=(m+((d&h|(d|h)&~u)>>>31|0)|0)+((u&k|(u|k)&~q)>>>31|0)|0,c.a[g]=q,g=1+g|0,q=c.a[g],k=h+q|0,h=(h&q|(h|q)&~k)>>>31|0,c.a[g]=k,d=h,f=1+f|0,g=1+g|0;return c} +function Qj(a,b,c){if(c.fa>b.fa)var d=c;else d=b,b=c;var f=d,g=b;if(63>g.fa){d=f.fa;b=g.fa;c=d+b|0;a=f.ca!==g.ca?-1:1;if(2===c){d=f.N.a[0];b=g.N.a[0];c=65535&d;d=d>>>16|0;g=65535&b;b=b>>>16|0;f=Math.imul(c,g);g=Math.imul(d,g);var h=Math.imul(c,b);c=f+((g+h|0)<<16)|0;f=(f>>>16|0)+h|0;d=(Math.imul(d,b)+(f>>>16|0)|0)+(((65535&f)+g|0)>>>16|0)|0;a=0===d?zj(a,c):aj(a,2,new y(new Int32Array([c,d])))}else{f=f.N;g=g.N;h=new y(c);if(0!==d&&0!==b)if(1===d)h.a[b]=Kj(0,h,g,b,f.a[0]);else if(1===b)h.a[d]=Kj(0, +h,f,d,g.a[0]);else if(f===g&&d===b)Pj(f,d,h);else for(var k=0;k>>16|0,S=65535&w;w=w>>>16|0;var L=Math.imul(K,S);S=Math.imul(M,S);var aa=Math.imul(K,w);K=L+((S+aa|0)<<16)|0;L=(L>>>16|0)+aa|0;w=(Math.imul(M,w)+(L>>>16|0)|0)+(((65535&L)+S|0)>>>16|0)|0;L=K+A|0;M=L+n|0;A=(w+((K&A|(K|A)&~L)>>>31|0)|0)+((L&n|(L|n)&~M)>>>31|0)|0;h.a[m+v|0]=M;n=A;u=1+u|0}h.a[m+b|0]=n;k=1+k|0}a=aj(a,c,h);bj(a)}return a}d=(-2& +f.fa)<<4;b=Rj(f,d);c=Rj(g,d);f=Cj(Ej(),f,Vj(b,d));k=Cj(Ej(),g,Vj(c,d));g=Qj(a,b,c);h=Qj(a,f,k);a=Qj(a,Cj(Ej(),b,f),Cj(Ej(),k,c));a=yj(Ej(),yj(Ej(),a,g),h);a=Vj(a,d);g=Vj(g,d<<1);return yj(Ej(),yj(Ej(),g,a),h)} +function Wj(a,b,c){var d=a.Xk.a.length,f=d>>31;if(c===f?b>>>0>>0:c=b>>>0:0>c)return Xj(Zi().Lj,b);if(0===c?2147483647>=b>>>0:0>c)return Vj(Xj(a.Wk.a[1],b),b);var g=Xj(a.Wk.a[1],2147483647);d=g;var h=b-2147483647|0;f=h;h=(c-1|0)+((b|~h)>>>31|0)|0;var k=c>>31,m=65535&b,n=b>>>16|0,q=m<<15,u=m+((q+n|0)<<16)|0;m=(m>>>16|0)+n|0;q=(((-b|0)+(n<<15)|0)+(m>>>16|0)|0)+(((65535&m)+q|0)>>>16|0)|0;m=b>>>16|0;n=Math.imul(3,65535&b);m=Math.imul(3,m);m=((n>>>16|0)+m|0)>>>16|0;n= +u+m|0;q=q+((u&m|(u|m)&~n)>>>31|0)|0;var v=65535&c;u=c>>>16|0;var w=v<<15;m=v+((w+u|0)<<16)|0;v=(v>>>16|0)+u|0;w=(((Math.imul(-2147483647,k)+(-c|0)|0)+(u<<15)|0)+(v>>>16|0)|0)+(((65535&v)+w|0)>>>16|0)|0;u=m+q|0;q=(w+(q>>31)|0)+((m&q|(m|q)&~u)>>>31|0)|0;w=c>>>16|0;m=Math.imul(3,65535&c);v=Math.imul(3,w);w=m+(v<<16)|0;m=(m>>>16|0)+v|0;k=(Math.imul(3,k)+(m>>>16|0)|0)+((n&w|(n|w)&~(n+w|0))>>>31|0)|0;n=u+k|0;m=n+b|0;k=(m>>>30|0|((((q+(k>>31)|0)+((u&k|(u|k)&~n)>>>31|0)|0)+c|0)+((n&b|(n|b)&~m)>>>31|0)|0)<< +2)+(c>>>31|0)|0;n=65535&k;u=k>>>16|0;k=Math.imul(65535,n);n=Math.imul(32767,n);u=Math.imul(65535,u);for(k=b-(k+((n+u|0)<<16)|0)|0;;)if(n=f,u=h,0===u?2147483647>>0:0>>31|0)|0;else break;d=Oj(d,Xj(a.Wk.a[1],k));d=Vj(d,2147483647);f=a=b-2147483647|0;for(h=(c-1|0)+((b|~a)>>>31|0)|0;;)if(b=f,c=h,0===c?2147483647>>0:0>>31|0)|0,f=b,h=c;else break;return Vj(d,k)} +z(Mj,"java.math.Multiplication$",{MX:1});var Nj;function sj(){Nj||(Nj=new Mj);return Nj}function Yj(a,b){a.He=b;a.aa=a.He;a.L=0;a.Pd=-1}function Zj(){this.Pd=this.L=this.aa=this.He=0}Zj.prototype=new t;Zj.prototype.constructor=Zj;function ak(){}ak.prototype=Zj.prototype;Zj.prototype.yb=function(a){if(0>a||a>this.aa)throw vb();this.L=a;this.Pd>a&&(this.Pd=-1)};Zj.prototype.Zo=function(a){if(0>a||a>this.He)throw vb();this.aa=a;this.L>a&&(this.L=a,this.Pd>a&&(this.Pd=-1))}; +Zj.prototype.vH=function(){this.Pd=-1;this.aa=this.L;this.L=0};Zj.prototype.f=function(){return xa(this)+"[pos\x3d"+this.L+" lim\x3d"+this.aa+" cap\x3d"+this.He+"]"};function bk(){}bk.prototype=new t;bk.prototype.constructor=bk;z(bk,"java.nio.ByteBuffer$",{RX:1});var ck;function dk(a){this.FM=a}dk.prototype=new t;dk.prototype.constructor=dk;dk.prototype.f=function(){return this.FM};z(dk,"java.nio.ByteOrder",{SX:1}); +function ek(){this.zF=this.hA=null;this.AF=!1;fk=this;this.hA=new dk("BIG_ENDIAN");this.zF=new dk("LITTLE_ENDIAN");if("undefined"===typeof Int32Array)var a=!0;else a=new ArrayBuffer(4),(new Int32Array(a))[0]=16909060,a=1===((new Int8Array(a))[0]|0);this.AF=a}ek.prototype=new t;ek.prototype.constructor=ek;z(ek,"java.nio.ByteOrder$",{TX:1});var fk;function gk(){fk||(fk=new ek);return fk}function hk(){}hk.prototype=new t;hk.prototype.constructor=hk; +function ik(a){jk||(jk=new hk);if(0>a)throw vb();a=new db(a);var b=a.a.length,c=a.a.length;if(0>c||c>a.a.length)throw kk();if(0>b||b>c)throw kk();return new lk(c,a,0,0,b,!1)}z(hk,"java.nio.CharBuffer$",{UX:1});var jk;function mk(){}mk.prototype=new t;mk.prototype.constructor=mk;function nk(a,b,c,d,f){if(0>c||(0+c|0)>b.a.length)throw kk();a=d+f|0;if(0>d||0>f||a>c)throw kk();return new ok(c,b,0,d,a,!1)}z(mk,"java.nio.HeapByteBuffer$",{WX:1});var pk;function qk(){pk||(pk=new mk);return pk} +function sk(){}sk.prototype=new t;sk.prototype.constructor=sk;z(sk,"java.nio.StringCharBuffer$",{$X:1});var tk;function uk(){}uk.prototype=new t;uk.prototype.constructor=uk;z(uk,"java.nio.TypedArrayByteBuffer$",{bY:1});var vk;function wk(a){if(0===a.He)return ik(1);var b=ik(a.He<<1);Zj.prototype.vH.call(a);xk(b,a);return b}function yk(a){a.qx=1;a.qq="\ufffd";a.rx=zk().vx;a.sx=zk().vx;a.Yk=1}function Ak(){this.qx=0;this.sx=this.rx=this.qq=null;this.Yk=0}Ak.prototype=new t; +Ak.prototype.constructor=Ak;function Bk(){}Bk.prototype=Ak.prototype;function Ck(a,b){this.Zi=a;this.ux=b}Ck.prototype=new t;Ck.prototype.constructor=Ck;function Dk(a){var b=a.Zi;switch(b){case 1:throw new Ek;case 0:throw new Fk;case 2:throw new Gk(a.ux);case 3:throw new Hk(a.ux);default:throw Ik(new Jk,b);}}z(Ck,"java.nio.charset.CoderResult",{dY:1}); +function Kk(){this.mA=this.CF=this.co=this.tx=this.Xf=this.sg=this.Oj=null;Lk=this;this.Oj=new Ck(1,-1);this.sg=new Ck(0,-1);this.Xf=new Ck(2,1);this.tx=new Ck(2,2);this.co=new Ck(2,3);this.CF=new Ck(2,4);this.mA=[]}Kk.prototype=new t;Kk.prototype.constructor=Kk;z(Kk,"java.nio.charset.CoderResult$",{eY:1});var Lk;function Mk(){Lk||(Lk=new Kk);return Lk}function Nk(a){this.KM=a}Nk.prototype=new t;Nk.prototype.constructor=Nk;Nk.prototype.f=function(){return this.KM}; +z(Nk,"java.nio.charset.CodingErrorAction",{fY:1});function Ok(){this.vx=this.EF=this.DF=null;Pk=this;this.DF=new Nk("IGNORE");this.EF=new Nk("REPLACE");this.vx=new Nk("REPORT")}Ok.prototype=new t;Ok.prototype.constructor=Ok;z(Ok,"java.nio.charset.CodingErrorAction$",{gY:1});var Pk;function zk(){Pk||(Pk=new Ok);return Pk}function Qk(){}Qk.prototype=new t;Qk.prototype.constructor=Qk;function Rk(a,b){var c=Sk(),d=Sk(),f=b.a.length;16>>1|0;16>>1|0),0,f,c,d):Uk(b,0,f,c,d)}function Xk(a,b){var c=Yk(),d=Yk(),f=b.a.length;16>>31|0)|0)>>1)|0;Tk(a,b,c,d,m,g,h);Tk(a,b,c,m,f,g,h);for(var n=a=d,q=m;a=f||0>=g.I(h.ef(b,n),h.ef(b,q)))?(h.Fi(c,a,h.ef(b,n)),n=1+n|0):(h.Fi(c,a,h.ef(b,q)),q=1+q|0),a=1+a|0;c.G(d,b,d,k)}else Uk(b,d,f,g,h)} +function Uk(a,b,c,d,f){c=c-b|0;if(2<=c){var g=f.ef(a,b),h=f.ef(a,1+b|0);0d.I(h,f.ef(a,(b+g|0)-1|0))){for(var k=b,m=(b+g|0)-1|0;1<(m-k|0);){var n=(k+m|0)>>>1|0;0>d.I(h,f.ef(a,n))?m=n:k=n}k=k+(0>d.I(h,f.ef(a,k))?0:1)|0;for(m=b+g|0;m>k;)f.Fi(a,m,f.ef(a,m-1|0)),m=m-1|0;f.Fi(a,k,h)}g=1+g|0}}} +function xd(a,b,c){a=0;for(var d=b.a.length;;){if(a===d)return-1-a|0;var f=(a+d|0)>>>1|0,g=b.a[f];g=c===g?0:cg)d=f;else{if(0===g)return f;a=1+f|0}}}function fl(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length>>>1|0;if((c.a.length>>>1|0)!==a)return!1;for(var d=0;d!==a;){var f=b.a,g=d<<1,h=c.a,k=d<<1;if(0!==(f[g]^h[k]|f[g+1|0]^h[k+1|0]))return!1;d=1+d|0}return!0} +function gl(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length;if(c.a.length!==a)return!1;for(var d=0;d!==a;){if(b.a[d]!==c.a[d])return!1;d=1+d|0}return!0}function hl(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length;if(c.a.length!==a)return!1;for(var d=0;d!==a;){if(b.a[d]!==c.a[d])return!1;d=1+d|0}return!0} +function il(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length;if(c.a.length!==a)return!1;for(var d=0;d!==a;){if(b.a[d]!==c.a[d])return!1;d=1+d|0}return!0}function jl(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length;if(c.a.length!==a)return!1;for(var d=0;d!==a;){if(b.a[d]!==c.a[d])return!1;d=1+d|0}return!0} +function kl(a,b,c){if(b===c)return!0;if(null===b||null===c)return!1;a=b.a.length;if(c.a.length!==a)return!1;for(var d=0;d!==a;){if(b.a[d]!==c.a[d])return!1;d=1+d|0}return!0}function ll(a,b,c){if(0>c)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length>>>1|0;a=cc)throw new ml;a=b.a.length;a=cc)throw new ml;a=b.a.length;a=cd)throw Qi(c+" \x3e "+d);a=d-c|0;d=b.a.length-c|0;d=a=b)return"00000000000000000000".substring(0,b);for(a="";20b)return new Fl(a.ol,"0",0);if(b>=d)return a;if(53>c.charCodeAt(b))return 0===b?new Fl(a.ol,"0",0):new Fl(a.ol,c.substring(0,b),a.bk-(d-b|0)|0);for(b=b-1|0;0<=b&&57===c.charCodeAt(b);)b=b-1|0;c=0>b?"1":c.substring(0,b)+Na(65535&(1+c.charCodeAt(b)|0));return new Fl(a.ol,c,a.bk-(d-(1+b|0)|0)|0)}function Fl(a,b,c){this.ol=a;this.ck=b;this.bk=c}Fl.prototype=new t;Fl.prototype.constructor=Fl; +function Gl(a,b){Dl();if(!(0>>19|0)-430675100|0}; +Jl.prototype.cg=function(a,b){b=Math.imul(-862048943,b);b=Math.imul(461845907,b<<15|b>>>17|0);return a^b};Jl.prototype.T=function(a,b){a^=b;a=Math.imul(-2048144789,a^(a>>>16|0));a=Math.imul(-1028477387,a^(a>>>13|0));return a^(a>>>16|0)};z(Jl,"java.util.internal.MurmurHash3$",{J2:1});var Kl;function Ll(){Kl||(Kl=new Jl);return Kl}function Ml(a,b){throw new Nl(b,a.Wd,a.o);}function Ol(a,b){for(var c="",d=b.length,f=0;f!==d;){var g=Pl(b,f);c=""+c+Ql(a,g);f=f+(65536<=g?2:1)|0}return c} +function Ql(a,b){var c=Rl(Sl(),b);if(128>b)switch(b){case 94:case 36:case 92:case 46:case 42:case 43:case 63:case 40:case 41:case 91:case 93:case 123:case 125:case 124:return"\\"+c;default:return 2!==(66&a.Ub)?c:25>=(b-65|0)>>>0?"["+c+Rl(Sl(),32+b|0)+"]":25>=(b-97|0)>>>0?"["+Rl(Sl(),b-32|0)+c+"]":c}else return 56320===(-1024&b)?"(?:"+c+")":c} +function Tl(a){for(var b=a.Wd,c=b.length;;){if(a.o!==c)switch(b.charCodeAt(a.o)){case 32:case 9:case 10:case 11:case 12:case 13:a.o=1+a.o|0;continue;case 35:Ul(a);continue}break}} +function Vl(a,b,c){var d=a.Wd,f=d.length,g=a.o;g=g===f?46:d.charCodeAt(g);if(42===g||63===g||43===g||123===g){switch(c.charCodeAt(0)){case 94:case 36:var h=!0;break;case 40:h=63===c.charCodeAt(1)&&58!==c.charCodeAt(2);break;case 92:h=c.charCodeAt(1);h=66===h||98===h;break;default:h=!1}c=h?"(?:"+c+")":c;h=a.Wd;var k=a.o;a.o=1+a.o|0;if(123===g){g=h.length;for(a.o!==g&&9>=(h.charCodeAt(a.o)-48|0)>>>0||Ml(a,"Illegal repetition");a.o!==g&&9>=(h.charCodeAt(a.o)-48|0)>>>0;)a.o=1+a.o|0;a.o===g&&Ml(a,"Illegal repetition"); +if(44===h.charCodeAt(a.o))for(a.o=1+a.o|0;a.o!==g&&9>=(h.charCodeAt(a.o)-48|0)>>>0;)a.o=1+a.o|0;a.o!==g&&125===h.charCodeAt(a.o)||Ml(a,"Illegal repetition");a.o=1+a.o|0}g=h.substring(k,a.o);if(a.o!==f)switch(d.charCodeAt(a.o)){case 43:return a.o=1+a.o|0,Wl(a,b,c,g);case 63:return a.o=1+a.o|0,""+c+g+"?";default:return""+c+g}else return""+c+g}else return c} +function Wl(a,b,c,d){for(var f=a.kh.length|0,g=0;gb&&(a.kh[h]=1+k|0);g=1+g|0}c=c.replace(Sl().cI,(m,n,q)=>{var u=n.length,v=u>>>31|0;if(0===((1&(u+v|0))-v|0))return m;q=parseInt(q,10)|0;return q>b?""+n+(1+q|0):m});a.jh=1+a.jh|0;return"(?:(?\x3d("+c+d+"))\\"+(1+b|0)+")"} +function Xl(a){var b=a.Wd,c=b.length;(1+a.o|0)===c&&Ml(a,"\\ at end of pattern");a.o=1+a.o|0;var d=b.charCodeAt(a.o);switch(d){case 100:case 68:case 104:case 72:case 115:case 83:case 118:case 86:case 119:case 87:case 112:case 80:switch(a=Yl(a,d),b=a.YB,b){case 0:return"\\p{"+a.dk+"}";case 1:return"\\P{"+a.dk+"}";case 2:return"["+a.dk+"]";case 3:return Zl(Sl(),a.dk);default:throw Ik(new Jk,b);}case 98:if("b{g}"===b.substring(a.o,4+a.o|0))Ml(a,"\\b{g} is not supported");else if(0!==(320&a.Ub))$l(a, +"\\b with UNICODE_CASE");else return a.o=1+a.o|0,"\\b";break;case 66:if(0!==(320&a.Ub))$l(a,"\\B with UNICODE_CASE");else return a.o=1+a.o|0,"\\B";break;case 65:return a.o=1+a.o|0,"^";case 71:Ml(a,"\\G in the middle of a pattern is not supported");break;case 90:return a.o=1+a.o|0,"(?\x3d"+(0!==(1&a.Ub)?"\n":"(?:\r\n?|[\n\u0085\u2028\u2029])")+"?$)";case 122:return a.o=1+a.o|0,"$";case 82:return a.o=1+a.o|0,"(?:\r\n|[\n-\r\u0085\u2028\u2029])";case 88:Ml(a,"\\X is not supported");break;case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:var f= +a.o;for(d=1+f|0;;){if(d!==c&&9>=(b.charCodeAt(d)-48|0)>>>0){var g=b.substring(f,1+d|0);g=(parseInt(g,10)|0)<=((a.kh.length|0)-1|0)}else g=!1;if(g)d=1+d|0;else break}b=b.substring(f,d);b=parseInt(b,10)|0;b>((a.kh.length|0)-1|0)&&Ml(a,"numbered capturing group \x3c"+b+"\x3e does not exist");b=a.kh[b]|0;a.o=d;return"(?:\\"+b+")";case 107:return a.o=1+a.o|0,a.o!==c&&60===b.charCodeAt(a.o)||Ml(a,"\\k is not followed by '\x3c' for named capturing group"),a.o=1+a.o|0,b=am(a),d=a.zy,Hi().Tv.call(d,b)||Ml(a, +"named capturing group \x3c"+b+"\x3e does not exit"),b=a.kh[d[b]|0]|0,a.o=1+a.o|0,"(?:\\"+b+")";case 81:d=1+a.o|0;c=b.indexOf("\\E",d)|0;if(0>c)return a.o=b.length,Ol(a,b.substring(d));a.o=2+c|0;return Ol(a,b.substring(d,c));default:return Ql(a,bm(a))}} +function bm(a){var b=a.Wd,c=Pl(b,a.o);switch(c){case 48:return cm(a);case 120:return b=a.Wd,c=1+a.o|0,c!==b.length&&123===b.charCodeAt(c)?(c=1+c|0,b=b.indexOf("}",c)|0,0>b&&Ml(a,"Unclosed hexadecimal escape sequence"),c=dm(a,c,b,"hexadecimal"),a.o=1+b|0,a=c):(b=dm(a,c,2+c|0,"hexadecimal"),a.o=2+c|0,a=b),a;case 117:a:{b=a.Wd;var d=1+a.o|0;c=4+d|0;d=dm(a,d,c,"Unicode");a.o=c;var f=2+c|0,g=4+f|0;if(55296===(-1024&d)&&"\\u"===b.substring(c,f)&&(b=dm(a,f,g,"Unicode"),56320===(-1024&b))){a.o=g;a=(64+(1023& +d)|0)<<10|1023&b;break a}a=d}return a;case 78:Ml(a,"\\N is not supported");break;case 97:return a.o=1+a.o|0,7;case 116:return a.o=1+a.o|0,9;case 110:return a.o=1+a.o|0,10;case 102:return a.o=1+a.o|0,12;case 114:return a.o=1+a.o|0,13;case 101:return a.o=1+a.o|0,27;case 99:return a.o=1+a.o|0,a.o===b.length&&Ml(a,"Illegal control escape sequence"),b=Pl(b,a.o),a.o=a.o+(65536<=b?2:1)|0,64^b;default:return(25>=(c-65|0)>>>0||25>=(c-97|0)>>>0)&&Ml(a,"Illegal/unsupported escape sequence"),a.o=a.o+(65536<= +c?2:1)|0,c}}function cm(a){var b=a.Wd,c=b.length,d=a.o,f=(1+d|0)>>0&&Ml(a,"Illegal octal escape sequence");var g=(2+d|0)>>0)return a.o=2+a.o|0,f;if(3>>0)return a.o=3+a.o|0,(f<<3)+g|0;a.o=4+a.o|0;return((f<<6)+(g<<3)|0)+b|0} +function dm(a,b,c,d){var f=a.Wd,g=f.length;(c===b||c>g)&&Ml(a,"Illegal "+d+" escape sequence");for(g=b;g=(h-48|0)>>>0||5>=(h-65|0)>>>0||5>=(h-97|0)>>>0||Ml(a,"Illegal "+d+" escape sequence");g=1+g|0}6<(c-b|0)?b=1114112:(b=f.substring(b,c),b=parseInt(b,16)|0);1114111f&&Ml(a,"Unclosed character family");a.o=f;c=c.substring(d,f)}else c=c.substring(d,1+d|0);d=Sl().aC;Hi().Tv.call(d,c)||$l(a,"Unicode character family");c=2!==(66&a.Ub)||"Lower"!== +c&&"Upper"!==c?c:"Alpha";c=Sl().aC[c];a.o=1+a.o|0;a=c;break;default:throw Ik(new Jk,p(b));}97<=b?b=a:a.XB?b=a.ZB:(b=a,b.XB||(b.ZB=new em(1^b.YB,b.dk),b.XB=!0),b=b.ZB);return b} +var km=function fm(a){var c=a.Wd,d=c.length;a.o=1+a.o|0;var f=a.o!==d&&94===c.charCodeAt(a.o);f&&(a.o=1+a.o|0);for(f=new gm(2===(66&a.Ub),f);a.o!==d;){var g=Pl(c,a.o);a:{switch(g){case 93:return a.o=1+a.o|0,a=f,c=hm(a),""===a.yy?c:"(?:"+a.yy+c+")";case 38:a.o=1+a.o|0;if(a.o!==d&&38===c.charCodeAt(a.o)){a.o=1+a.o|0;g=f;var h=hm(g);g.yy+=g.WH?h+"|":"(?\x3d"+h+")";g.Fg="";g.Sc=""}else im(a,38,d,c,f);break a;case 91:g=fm(a);f.Fg=""===f.Fg?g:f.Fg+"|"+g;break a;case 92:switch(a.o=1+a.o|0,a.o===d&&Ml(a, +"Illegal escape sequence"),h=c.charCodeAt(a.o),h){case 100:case 68:case 104:case 72:case 115:case 83:case 118:case 86:case 119:case 87:case 112:case 80:g=f;h=Yl(a,h);var k=h.YB;switch(k){case 0:g.Sc=g.Sc+("\\p{"+h.dk)+"}";break;case 1:g.Sc=g.Sc+("\\P{"+h.dk)+"}";break;case 2:g.Sc=""+g.Sc+h.dk;break;case 3:h=Zl(Sl(),h.dk);g.Fg=""===g.Fg?h:g.Fg+"|"+h;break;default:throw Ik(new Jk,k);}break a;case 81:a.o=1+a.o|0;g=c.indexOf("\\E",a.o)|0;0>g&&Ml(a,"Unclosed character class");h=f;k=c;for(var m=g,n=a.o;n!== +m;){var q=Pl(k,n);jm(h,q);n=n+(65536<=q?2:1)|0}a.o=2+g|0;break a;default:im(a,bm(a),d,c,f);break a}case 32:case 9:case 10:case 11:case 12:case 13:if(0!==(4&a.Ub)){a.o=1+a.o|0;break a}break;case 35:if(0!==(4&a.Ub)){Ul(a);break a}}a.o=a.o+(65536<=g?2:1)|0;im(a,g,d,c,f)}}Ml(a,"Unclosed character class")}; +function lm(a){var b=a.Wd,c=b.length,d=a.o;if((1+d|0)===c||63!==b.charCodeAt(1+d|0))return a.o=1+d|0,a.jh=1+a.jh|0,a.kh.push(a.jh),"("+mm(a,!0)+")";(2+d|0)===c&&Ml(a,"Unclosed group");var f=b.charCodeAt(2+d|0);if(58===f||61===f||33===f)return a.o=3+d|0,""+b.substring(d,3+d|0)+mm(a,!0)+")";if(60===f){(3+d|0)===c&&Ml(a,"Unclosed group");b=b.charCodeAt(3+d|0);if(25>=(b-65|0)>>>0||25>=(b-97|0)>>>0)return a.o=3+d|0,d=am(a),b=a.zy,Hi().Tv.call(b,d)&&Ml(a,"named capturing group \x3c"+d+"\x3e is already defined"), +a.jh=1+a.jh|0,a.kh.push(a.jh),a.zy[d]=(a.kh.length|0)-1|0,a.o=1+a.o|0,"("+mm(a,!0)+")";33!==b&&61!==b&&Ml(a,"Unknown look-behind group");$l(a,"Look-behind group")}else{if(62===f)return a.o=3+d|0,a.jh=1+a.jh|0,d=a.jh,"(?:(?\x3d("+mm(a,!0)+"))\\"+d+")";Ml(a,"Embedded flag expression in the middle of a pattern is not supported")}} +function am(a){for(var b=a.Wd,c=b.length,d=a.o;;){if(a.o!==c){var f=b.charCodeAt(a.o);f=25>=(f-65|0)>>>0||25>=(f-97|0)>>>0||9>=(f-48|0)>>>0}else f=!1;if(f)a.o=1+a.o|0;else break}a.o!==c&&62===b.charCodeAt(a.o)||Ml(a,"named capturing group is missing trailing '\x3e'");return b.substring(d,a.o)} +function im(a,b,c,d,f){0!==(4&a.Ub)&&Tl(a);a.o!==c&&45===d.charCodeAt(a.o)?(a.o=1+a.o|0,0!==(4&a.Ub)&&Tl(a),a.o===c&&Ml(a,"Unclosed character class"),c=Pl(d,a.o),91===c||93===c?(jm(f,b),jm(f,45)):(a.o=a.o+(65536<=c?2:1)|0,c=92===c?bm(a):c,cc?c:90,a<=d&&(d=32+d|0,f.Sc+=nm(32+a|0)+"-"+nm(d)),b=97c?c:122,b<=c&&(c=c-32|0,f.Sc+=nm(b-32|0)+"-"+nm(c))))):jm(f,b)} +function om(a,b){this.Wd=a;this.Ub=b;this.bC=!1;this.jh=this.o=0;this.kh=[0];this.zy={}}om.prototype=new t;om.prototype.constructor=om;function $l(a,b){Ml(a,b+" is not supported because it requires RegExp features of ECMAScript 2018.\nIf you only target environments with ES2018+, you can enable ES2018 features with\n scalaJSLinkerConfig ~\x3d { _.withESFeatures(_.withESVersion(ESVersion.ES2018)) }\nor an equivalent configuration depending on your build tool.")} +function mm(a,b){for(var c=a.Wd,d=c.length,f="";a.o!==d;){var g=Pl(c,a.o);a:{switch(g){case 41:return b||Ml(a,"Unmatched closing ')'"),a.o=1+a.o|0,f;case 124:a.bC&&!b&&Ml(a,"\\G is not supported when there is an alternative at the top level");a.o=1+a.o|0;f+="|";break a;case 32:case 9:case 10:case 11:case 12:case 13:if(0!==(4&a.Ub)){a.o=1+a.o|0;break a}break;case 35:if(0!==(4&a.Ub)){Ul(a);break a}break;case 63:case 42:case 43:case 123:Ml(a,"Dangling meta character '"+Rl(Sl(),g)+"'")}var h=a.jh;switch(g){case 92:g= +Xl(a);break;case 91:g=km(a);break;case 40:g=lm(a);break;case 94:a.o=1+a.o|0;g="^";break;case 36:a.o=1+a.o|0;g="$";break;case 46:a.o=1+a.o|0;g=0!==(32&a.Ub)?"":0!==(1&a.Ub)?"\n":"\n\r\u0085\u2028\u2029";g=Zl(Sl(),g);break;default:a.o=a.o+(65536<=g?2:1)|0,g=Ql(a,g)}f=""+f+Vl(a,h,g)}}b&&Ml(a,"Unclosed group");return f}function Ul(a){for(var b=a.Wd,c=b.length;;){if(a.o!==c){var d=b.charCodeAt(a.o);d=!(10===d||13===d||133===d||8232===d||8233===d)}else d=!1;if(d)a.o=1+a.o|0;else break}} +z(om,"java.util.regex.PatternCompiler",{N2:1});function pm(a){try{return RegExp("",a),!0}catch(b){return!1}} +function qm(){this.cI=this.bI=null;this.$B=!1;this.aC=this.ZH=this.aI=this.YH=this.$H=this.XH=null;rm=this;this.bI=RegExp("^\\(\\?([idmsuxU]*)(?:-([idmsuxU]*))?\\)");this.cI=RegExp("(\\\\+)(\\d+)","g");this.$B=pm("us");pm("d");this.XH=new em(2,"0-9");this.$H=new em(2,"\t \u00a0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000");this.YH=new em(2,"\t-\r ");this.aI=new em(2,"\n-\r\u0085\u2028\u2029");this.ZH=new em(2,"a-zA-Z_0-9");var a={};a.Lower=new em(2,"a-z");a.Upper=new em(2,"A-Z");a.ASCII=new em(2,"\x00-\u007f"); +a.Alpha=new em(2,"A-Za-z");a.Digit=new em(2,"0-9");a.Alnum=new em(2,"0-9A-Za-z");a.Punct=new em(2,"!-/:-@[-`{-~");a.Graph=new em(2,"!-~");a.Print=new em(2," -~");a.Blank=new em(2,"\t ");a.Cntrl=new em(2,"\x00-\u001f\u007f");a.XDigit=new em(2,"0-9A-Fa-f");a.Space=new em(2,"\t-\r ");this.aC=a}qm.prototype=new t;qm.prototype.constructor=qm; +function sm(a,b){a=new om(b,0);0!==(256&a.Ub)&&(a.Ub|=64);b=0!==(16&a.Ub);if(!b){var c=Sl().bI.exec(a.Wd);if(null!==c){var d=c[1];if(void 0!==d)for(var f=d.length,g=0;g=(a-92|0)>>>0||45===a||94===a?"\\"+b:b}function gm(a,b){this.VH=a;this.WH=b;this.Sc=this.Fg=this.yy=""}gm.prototype=new t;gm.prototype.constructor=gm; +function jm(a,b){var c=nm(b);a.Sc=56320===(-1024&b)?""+c+a.Sc:""+a.Sc+c;a.VH&&(25>=(b-65|0)>>>0?a.Sc=""+a.Sc+Rl(Sl(),32+b|0):25>=(b-97|0)>>>0&&(a.Sc=""+a.Sc+Rl(Sl(),b-32|0)))}z(gm,"java.util.regex.PatternCompiler$CharacterClassBuilder",{P2:1});function em(a,b){this.ZB=null;this.XB=!1;this.YB=a;this.dk=b}em.prototype=new t;em.prototype.constructor=em;z(em,"java.util.regex.PatternCompiler$CompiledCharClass",{Q2:1});function da(a,b){return vm(qj(),a,b)}function wm(){}wm.prototype=new t; +wm.prototype.constructor=wm;function vm(a,b,c){if(c===b>>31)return""+b;if(0===(-2097152&(c^c>>10)))return""+(4294967296*c+(b>>>0));a=c>>31;var d=b^a;b=d-a|0;a=+Math.floor(1E-9*(4294967296*(((c^a)+((d&~b)>>>31|0)|0)>>>0)+(b>>>0)));b=b-Math.imul(1E9,a|0)|0;0>b?(--a,b=1E9+b|0):1E9<=b&&(a+=1,b=b-1E9|0);b=""+b;a=""+a+"000000000".substring(b.length)+b;return 0>c?"-"+a:a} +function xm(a,b,c,d,f){a=c>>31;var g=b^a;b=g-a|0;a=(c^a)+((g&~b)>>>31|0)|0;g=f>>31;var h=d^g;d=h-g|0;var k=(f^g)+((h&~d)>>>31|0)|0;if(0===(k|-2097152&d))g=(a>>>0)/(Ka(d)>>>0)|0,b=(4294967296*(a-Math.imul(d,g)|0)+(b>>>0))/d|0,a=g;else if(0===(-1073741824&k)){h=(4294967296*(a>>>0)+(b>>>0))/(4294967296*(k>>>0)+(d>>>0));g=h|0;h=h/4294967296|0;var m=65535&d,n=d>>>16|0,q=65535&g,u=g>>>16|0,v=Math.imul(m,q);q=Math.imul(n,q);var w=Math.imul(m,u);m=v+((q+w|0)<<16)|0;v=(v>>>16|0)+w|0;u=(((Math.imul(d,h)+Math.imul(k, +g)|0)+Math.imul(n,u)|0)+(v>>>16|0)|0)+(((65535&v)+q|0)>>>16|0)|0;n=b-m|0;b=(a-u|0)+((~b&m|~(b^m)&n)>>31)|0;0>b?(b=a=g-1|0,a=(h-1|0)+((g|~a)>>>31|0)|0):(b===k?n>>>0>=d>>>0:b>>>0>k>>>0)?(b=a=1+g|0,a=h+((g&~a)>>>31|0)|0):(b=g,a=h)}else a=ym(b,a,d,k,!0),b=a.h,a=a.l;if(0<=(c^f))return r(b,a);c=-b|0;return r(c,(-a|0)+((b|c)>>31)|0)} +function pj(a,b,c,d){if(0===(0|-2097152&d))return a=(c>>>0)/(Ka(d)>>>0)|0,d=(4294967296*(c-Math.imul(d,a)|0)+(b>>>0))/d|0,r(d,a);var f=(4294967296*(c>>>0)+(b>>>0))/(d>>>0);a=f|0;f=f/4294967296|0;var g=65535&d,h=d>>>16|0,k=65535&a,m=a>>>16|0,n=Math.imul(g,k);k=Math.imul(h,k);var q=Math.imul(g,m);g=n+((k+q|0)<<16)|0;n=(n>>>16|0)+q|0;m=(((Math.imul(d,f)+Math.imul(0,a)|0)+Math.imul(h,m)|0)+(n>>>16|0)|0)+(((65535&n)+k|0)>>>16|0)|0;h=b-g|0;b=(c-m|0)+((~b&g|~(b^g)&h)>>31)|0;return 0>b?(d=a-1|0,r(d,(f-1| +0)+((a|~d)>>>31|0)|0)):(0===b?h>>>0>=d>>>0:0>>0)?(d=1+a|0,r(d,f+((a&~d)>>>31|0)|0)):r(a,f)}function ym(a,b,c,d,f){var g=0;if(0<=d){var h=c<<1,k=c>>>31|0|d<<1;if(b===k?a>>>0>=h>>>0:b>>>0>k>>>0){g=2;var m=a-h|0,n=m;a=(b-k|0)+((~a&h|~(a^h)&m)>>31)|0}else n=a,a=b}else n=a,a=b;b=a===d?n>>>0>>0:a>>>0>>0;if(f)return b?r(g,0):r(1+g|0,0);if(b)return r(n,a);f=n-c|0;return r(f,(a-d|0)+((~n&c|~(n^c)&f)>>31)|0)}z(wm,"org.scalajs.linker.runtime.RuntimeLong$",{X0:1});var zm; +function qj(){zm||(zm=new wm);return zm}function Am(){}Am.prototype=new t;Am.prototype.constructor=Am;function Bm(a,b,c){if(1===b.X())return oi(),b=b.mc.md(c),oi(),new Cm(b,N());a=b.fc.$j().pa(b.tf().md(c));oi();return new Cm(b.mc,a)}z(Am,"pink.cozydev.lucille.Query$",{oY:1});var Dm;function Em(){Dm||(Dm=new Am);return Dm}function Fm(){}Fm.prototype=new t;Fm.prototype.constructor=Fm; +function Gm(a,b){if(N().d(b))throw Qi("Cannot create And query from empty list");if(b instanceof J){a=b.Z;if(N().d(a))throw Qi("Cannot create And query from single element list");return new Hm(b.Fa,a)}throw new D(b);}z(Fm,"pink.cozydev.lucille.Query$And$",{pY:1});var Im;function Jm(){Im||(Im=new Fm);return Im}function Km(){}Km.prototype=new t;Km.prototype.constructor=Km;z(Km,"pink.cozydev.lucille.Query$Or$",{AY:1});var Lm; +function Mm(a){a=fi(Nm(new Om(ci(I(),"NOT")),Pm().$i),a);var b=new G(c=>new Qm(c));return le(I(),a,b)}function Rm(a){var b=Xd(I(),43),c=new G(d=>new Sm(d));return fi(b,le(I(),a,c))}function Tm(a){var b=Xd(I(),45),c=new G(d=>new Um(d));return fi(b,le(I(),a,c))}function Vm(a,b){a=a.TF;b=Ud(I(),a,b);a=new G(c=>{if(null!==c)return new Wm(c.Y,c.V);throw new D(c);});return le(I(),b,a)} +function Xm(a,b){a=Vd(Vd(Vd(Vm(a,b),Pm().AA),Pm().xA),Ym(a,b));a=Nm(new Om(Zm(I(),a,"limitedQ")),fi(Xd(I(),94),Pm().JF).lg(Pm().yA));b=new G(c=>new $m(c.Y,Math.fround(c.V)));return le(I(),a,b)}function an(a){var b=fi(Xd(I(),64),Pm().Fx).lg(Pm().yA);a=bn(cn(a),Xd(I(),40),Xd(I(),41));b=Nm(new Om(a),b);a=new G(c=>{if(null!==c)return new dn(c.Y,c.V|0);throw new D(c);});return le(I(),b,a)} +function en(a){var b=Pm().$i;b=nh(qh(),b,Pm().MF);var c=hi().$z,d=new fn;b=b.lg(Td(I(),c,d));b=Ud(I(),b,a);a=Pm().$i.Fz(vh((I(),Xe()),a));c=I();a=gn(c,a);a=nh(qh(),a,b);b=new gi;return Sd(I(),a,b)}function Ym(a,b){a=bn(hn(a,b),Xd(I(),40),Xd(I(),41));b=Xd(I(),64);a=a.lg(gn(I(),b));b=new G(c=>new jn(c));return le(I(),a,b)} +function hn(a,b){b=cn(b);var c=new G(d=>{if(null!==d){oi();var f=d.mc,g=d.fc;if(N().d(g))return f;if(g instanceof J)return d=g.Z,g=g.Fa,a.SF?new kn(f,d,g):new ln(f,d,g)}throw new D(d);});return le(I(),b,c)} +function mn(a){vi||(vi=new ki);var b=a.Rl,c=k=>{k=new nn(k,on().NE);return k.NJ.Lw(k.MJ)};oi();var d=c(b.mc),f=b.fc;if(f===N())c=N();else{b=f.u();var g=b=new J(c(b),N());for(f=f.A();f!==N();){var h=f.u();h=new J(c(h),N());g=g.Z=h;f=f.A()}c=b}d=li(new Cm(d,c));return"Parse error at offset "+a.Sl+", with expectations:\n "+d} +function pn(a){this.SF=!1;this.UF=this.TF=null;this.SF=a;this.TF=(new Om(Pm().Hx)).lg(Xd(I(),58));a=qn(I(),new G(b=>Mg(I(),rg(N(),Pg(Qg(),new (B(rn).P)([Rm(b),Tm(b),Mm(b),Vm(this,b),Pm().QF,sn(),Pm().LF,an(b),Xm(this,b),Pm().AA,Pm().RF,Pm().xA,Ym(this,b)]))))));this.UF=hn(this,a).lg(Pm().$i)}pn.prototype=new t;pn.prototype.constructor=pn; +function cn(a){var b=a.lg(Pm().$i);Pm();var c=new tn;b=Td(I(),b,c);c=Pm().$i;qh();a=en(a);a=nh(0,c,Ud(I(),b,a));b=new G(d=>{if(null!==d){var f=d.Y,g=d.V;if(null!==f){d=f.Y;f=f.V;if(N().d(d)&&N().d(g))return un(vn().Sp,f);if(N().d(g))return g=Yb(Ob(),d),ic(),hc(oc(),un(vn().Sp,f),g);if(N().d(d))return un(vn().Sp,wn(xn(),f,g));d=Yb(Ob(),d);ic();return hc(oc(),un(vn().Sp,wn(xn(),f,g)),d)}}throw new D(d);});a=le(I(),a,b);b=Pm().$i.Ez((I(),Xe()));Pm();c=new yn;return zn(I(),a,b,c)} +pn.prototype.hC=function(a){var b=new lh(a),c=this.UF.qa(b),d=b.bb;b=b.Wa;null===d?b===a.length?a=new Eg(c):(An||(An=new Bn),c=(oi(),new Cm(new Cn(b,a.length),N())),a=new Dg(new Dn(new E(a),b,c))):(An||(An=new Bn),c=En(on(),Fn(oi(),d.Kh().ub())),a=new Dg(new Dn(new E(a),b,c)));if(a instanceof Dg)return new Dg(mn(a.lj));if(a instanceof Eg)return ji||(ji=new ii),a;throw new D(a);};z(pn,"pink.cozydev.lucille.QueryParser",{SY:1});function Gn(){this.GF=null;Hn=this;this.GF=new pn(!0)}Gn.prototype=new t; +Gn.prototype.constructor=Gn;z(Gn,"pink.cozydev.lucille.QueryParser$",{TY:1});var Hn; +function In(){this.MF=this.IF=this.OF=this.RF=this.AA=this.LF=this.KF=this.QF=this.PF=this.xA=this.wA=this.Hx=this.yA=this.zA=this.vA=this.NF=this.JF=this.Fx=this.$i=this.Gx=null;Jn=this;Pd(I(),od(pd(),Kn(Qg(),new db(new Uint16Array([34,8220,8221])))));var a=hi().mq,b=new fn;a=Td(I(),a,b);this.Gx=Wd(I(),a);this.$i=Zd(this.Gx).Te();a=hi().Zn;b=new fn;a=Td(I(),a,b).lg(gn(I(),Xd(I(),46)));a=Yd(I(),a);b=new G(g=>Ln(dh(),g,214748364));this.Fx=le(I(),a,b);a=Xd(I(),46);b=hi().Zn;var c=new fn;a=fi(a,Td(I(), +b,c));b=hi().Zn;c=new fn;b=Td(I(),b,c);a=Zd(a);a=Ud(I(),b,a);a=Mn(Yd(I(),a),new G(g=>{try{Nn||(Nn=new On);var h=Nn.uB(Pn(Qn(),g))}catch(k){if(k instanceof Rn)h=F();else throw k;}return h}));this.JF=Zm(I(),a,"float");a=od(pd(),Qd(new Rd(32),p(65535)));b=od(pd(),Kn(Qg(),new db(new Uint16Array([34,8220,8221]))));this.NF=od(pd(),Kn(Qg(),new db(new Uint16Array([43,45,33,40,41,123,125,91,93,94,34,126,42,63,58,92,47]))));var d=this.NF.Em(b).ih(p(32));this.vA=b.ih(p(92));c=fi(Xd(I(),92),Pd(I(),d));var f= +Pd(I(),a.aw(d));this.zA=od(pd(),Pg(Qg(),new (B(fa).P)("OR || AND \x26\x26 NOT + - /".split(" "))));d=vh(vh(hi().mq,(I(),Xe())),Xd(I(),41));this.yA=Sn(I(),d);qh();d=gn(I(),Tn(I(),this.zA));c=Vd(f,c);f=Zc().Zp;this.Hx=nh(0,d,Td(I(),c,f));c=fi(Xd(I(),92),Pd(I(),this.vA));c=Vd(Pd(I(),a.aw(this.vA)),c);d=Zc().Zp;c=Td(I(),c,d);b=Pd(I(),b);b=this.wA=bn(c,b,b);c=new G(g=>new Un(g));this.xA=le(I(),b,c);b=this.PF=(new Om(this.wA)).lg(Xd(I(),126));c=this.Fx;b=Ud(I(),b,c);c=new G(g=>{if(null!==g)return new Vn(g.Y, +g.V|0);throw new D(g);});this.QF=le(I(),b,c);b=this.KF=(new Om(this.Hx)).lg(Xd(I(),126));c=Zd(this.Fx);b=Ud(I(),b,c);c=new G(g=>{if(null!==g)return new Wn(g.Y,g.V);throw new D(g);});this.LF=le(I(),b,c);b=Xd(I(),63);Xn||(Xn=new Yn);c=Xn;b=wh(I(),b,c);c=Xd(I(),42);d=Zn();c=wh(I(),c,d);d=this.Hx;f=new G(g=>new $n(g));d=le(I(),d,f);b=Vd(Vd(b,c),d);c=new fn;b=Td(I(),b,c);c=new G(g=>{if(null!==g){oi();var h=g.mc,k=g.fc;if(h instanceof $n){h=h.Dx;if(N().d(k))return new ao(h);if(k instanceof J){var m=k.Z; +if(Zn()===k.Fa&&N().d(m))return new bo(h)}}}return new co(g)});this.AA=le(I(),b,c);a=Pd(I(),a.ej(p(92)).ej(p(47)));a=Vd(Wd(I(),a),fi(Xd(I(),92),Xd(I(),47)));b=new fn;a=Td(I(),a,b);a=Yd(I(),a);b=Xd(I(),47);a=bn(a,b,b);b=new G(g=>new eo(g));this.RF=le(I(),a,b);a=Vd(ci(I(),"OR"),ci(I(),"||"));b=fo();this.OF=wh(I(),a,b);a=Vd(ci(I(),"AND"),ci(I(),"\x26\x26"));b=go();this.IF=wh(I(),a,b);a=Vd(this.OF,this.IF);this.MF=Zm(I(),a,"infixOp")}In.prototype=new t;In.prototype.constructor=In; +function sn(){var a=Pm(),b=di(I(),123,Kn(Qg(),new db(new Uint16Array([91])))),c=new G(h=>91===$a(h));b=le(I(),b,c).lg(a.$i);c=a.$i;var d=di(I(),125,Kn(Qg(),new db(new Uint16Array([93])))),f=new G(h=>93===$a(h));c=c.Fz(le(I(),d,f));d=Xd(I(),42);f=F();d=wh(I(),d,f);f=hi().eF;f=Wd(I(),f);var g=hi().Zn;f=Vd(Vd(f,Wd(I(),g)),Xd(I(),46));g=new fn;f=Td(I(),f,g);f=Yd(I(),f);g=new G(h=>new E(h));f=le(I(),f,g);d=Vd(d,nh(qh(),gn(I(),Tn(I(),a.zA)),f));a=fi(a.Gx,ci(I(),"TO")).lg(a.Gx);b=Ud(I(),b,d);a=Ud(I(),b, +a);a=Ud(I(),a,d);a=Ud(I(),a,c);b=new G(h=>{if(null!==h){var k=h.Y;if(null!==k){var m=k.Y;if(null!==m&&(m=m.Y,null!==m))return new ho(m.V,k.V,!!m.Y,!!h.V)}}throw new D(h);});return le(I(),a,b)}z(In,"pink.cozydev.lucille.QueryParserHelpers$",{UY:1});var Jn;function Pm(){Jn||(Jn=new In);return Jn} +function io(){this.WF=null;jo=this;var a=ko().NA,b=lo,c=mo(),d=no().qf;oo||(oo=new po);b=new qo("postings",b(c,d,oo.pG,new ro(l(so))));this.WF=new to(new G(f=>new uo(f.Kx,f.jo,f.km)),new G(f=>{if(null!==f)return new vo(f.dh,f.ch,f.bh|0);throw new D(f);}),wo(new xo(new qo("numDocs",no().qf),new yo(b,a)),zo()))}io.prototype=new t;io.prototype.constructor=io;z(io,"pink.cozydev.protosearch.FrequencyIndex$",{eZ:1});var jo;function Ao(){}Ao.prototype=new t;Ao.prototype.constructor=Ao; +z(Ao,"pink.cozydev.protosearch.LastTermRewrite$",{hZ:1});var Bo;function Co(){this.ZF=null;Eo=this;var a=ko().NA,b=lo,c=mo(),d=no().qf;Fo||(Fo=new Go);b=new qo("postings",b(c,d,Fo.rG,new ro(l(Ho))));this.ZF=new to(new G(f=>new uo(f.BA,f.lm,f.al)),new G(f=>{if(null!==f)return new Io(f.dh,f.ch,f.bh|0);throw new D(f);}),wo(new xo(new qo("numDocs",no().qf),new yo(b,a)),zo()))}Co.prototype=new t;Co.prototype.constructor=Co;z(Co,"pink.cozydev.protosearch.PositionalIndex$",{nZ:1});var Eo; +function Jo(a){this.aG=this.$F=null;this.$F=new Ko(new Lo(150,"\x3cmark\x3e","\x3c/mark\x3e"));Mo||(Mo=new No);this.aG=new Oo(a,this.$F)}Jo.prototype=new t;Jo.prototype.constructor=Jo; +function Po(a,b,c,d){c=void 0===c?F():new E(c);c=c.e()?F():new E(rg(N(),Qo(new Ro,c.oa())));d=void 0===d?F():new E(d);b=new So(b,10,c,d.e()?F():new E(rg(N(),Qo(new Ro,d.oa()))),!0);a=To(a.aG,b);if(a instanceof Uo)Vo||(Vo=new Wo),Xo(Vo.iI.wI,a.Mx+"\n"),a=N();else if(a instanceof Yo)a=a.Ox;else throw new D(a);c=a;a=g=>{var h=g.zq,k=g.Aq,m={};g.xq.Oa(new Zo(q=>{if(null!==q)m[q.Y]=q.V;else throw new D(q);}));var n={};g.yq.Oa(new Zo(q=>{if(null!==q)n[q.Y]=q.V;else throw new D(q);}));return new ($o())(h, +k,m,n)};ap||(ap=new bp);if(c===N())a=N();else{b=c.u();d=b=new J(a(b),N());for(c=c.A();c!==N();){var f=c.u();f=new J(a(f),N());d=d.Z=f;c=c.A()}a=b}return cp(a)}Jo.prototype.search=function(a,...b){return Po(this,a,void 0===b[0]?void 0:b[0],void 0===b[1]?void 0:b[1])};z(Jo,"pink.cozydev.protosearch.Querier",{pZ:1});function dp(){}dp.prototype=new t;dp.prototype.constructor=dp; +function ep(a){return a.arrayBuffer().then(b=>{fp();gp();gp();hp||(hp=new ip);vk||(vk=new uk);b=new Int8Array(b);b=new jp(b,0,b.length|0,!1);b.Ye=gk().AF;b=b.zz();kp();var c=b.aa-b.L|0;b=new lp(new mp(new np(b),0,0,c,c>>31));b=op(pp().YF,qp(rp(),b)).gI();return new Jo(b)})}dp.prototype.load=function(a){return ep(a)};z(dp,"pink.cozydev.protosearch.QuerierBuilder$",{qZ:1});var sp;function fp(){sp||(sp=new dp);return sp} +function tp(a,b,c){this.dG=null;this.cG=a;this.Lx=b;this.QM=c;for(b=new up;!a.e();)c=a.u(),c.wq&&vp(b,c.jm),a=a.A();this.dG=wp(b)}tp.prototype=new t;tp.prototype.constructor=tp;function xp(a,b){var c=a.cG;if(c===N())var d=N();else{a=c.u();d=a=new J(new C(a.jm,a.io),N());for(c=c.A();c!==N();){var f=c.u();f=new J(new C(f.jm,f.io),N());d=d.Z=f;c=c.A()}d=a}yp||(yp=new zp);a=d.u();d=d.A();return new Ap(b,Bp(Cp(),new J(a,d.ub())))}z(tp,"pink.cozydev.protosearch.Schema",{rZ:1}); +function Dp(){this.bG=null;Ep=this;var a=Fp(no(),no().qf,Gp().VF),b=new qo("defaultField",no().No),c=new qo("defaultOR",no().Mo);this.bG=new to(new G(d=>new uo(d.cG,d.Lx,d.QM)),new G(d=>{if(null!==d)return new tp(d.bh,d.ch,!!d.dh);throw new D(d);}),wo(new xo(a,new yo(b,c)),zo()))}Dp.prototype=new t;Dp.prototype.constructor=Dp;z(Dp,"pink.cozydev.protosearch.Schema$",{sZ:1});var Ep;function Hp(){this.fG=this.eG=null;Ip=this;this.eG=new Jp;this.fG=new Kp}Hp.prototype=new t;Hp.prototype.constructor=Hp; +z(Hp,"pink.cozydev.protosearch.ScoreFunction$",{tZ:1});var Ip;function Lp(){Ip||(Ip=new Hp);return Ip}function Mp(){this.lG=null;Np=this;this.lG=new qo("termList",lo(this,no().qf,no().No,new ro(l(fa))))}Mp.prototype=new t;Mp.prototype.constructor=Mp; +function lo(a,b,c,d){return new Op(new pg(()=>"arrayOfN("+b+", "+c+")"),Pp(Qp(),new Rp(new G(f=>new Sp(c,new E(f|0),d)),b),new G(f=>{if(null!==f){var g=f.Y|0,h=f.V;if(Pi(Nh(),h)===g)return new Tp(h);f=Up(c.ia());f.e()?(f=c.ia(),f=r(f.te,f.se)):f=f.oa();var k=ab(f);f=k.h;k=k.l;var m=g>>31,n=65535&g,q=g>>>16|0,u=65535&f,v=f>>>16|0,w=Math.imul(n,u);u=Math.imul(q,u);var A=Math.imul(n,v);n=w+((u+A|0)<<16)|0;w=(w>>>16|0)+A|0;g=(((Math.imul(g,k)+Math.imul(m,f)|0)+Math.imul(q,v)|0)+(w>>>16|0)|0)+(((65535& +w)+u|0)>>>16|0)|0;h=Pi(Nh(),h);m=h>>31;A=65535&h;q=h>>>16|0;u=65535&f;v=f>>>16|0;w=Math.imul(A,u);u=Math.imul(q,u);var K=Math.imul(A,v);A=w+((u+K|0)<<16)|0;w=(w>>>16|0)+K|0;f=(((Math.imul(h,k)+Math.imul(m,f)|0)+Math.imul(q,v)|0)+(w>>>16|0)|0)+(((65535&w)+u|0)>>>16|0)|0;return new Vp(Wp(n,g,A,f))}throw new D(f);}),new G(f=>new C(Pi(Nh(),f),f))))}z(Mp,"pink.cozydev.protosearch.codecs.IndexCodecs$",{GZ:1});var Np;function mo(){Np||(Np=new Mp);return Np}function Xp(){}Xp.prototype=new t; +Xp.prototype.constructor=Xp;function Yp(a){if(0<=a.E()){var b=new (B(Zp).P)(a.E());a.hc(b,0,2147483647)}else{b=[];for(a=a.i();a.n();){var c=a.j();b.push(null===c?null:c)}b=new (B(Zp).P)(b)}return new $p(b)}z(Xp,"pink.cozydev.protosearch.internal.AndIter$",{KZ:1});var aq;function bq(a){this.Kq=a}bq.prototype=new t;bq.prototype.constructor=bq;var so=z(bq,"pink.cozydev.protosearch.internal.FrequencyPostingsList",{NZ:1}); +function po(){this.pG=null;oo=this;no();var a=new qo("typeFlag",cq(no(),dq(Qg(),new y(new Int32Array([65]))))),b=new G(g=>g.Kq),c=new G(g=>new bq(g));mo();var d=no().qf,f=no().qf;this.pG=eq(a,new to(b,c,new Op(new pg(()=>"arrayOfN("+d+", "+f+")"),Pp(Qp(),new Rp(new G((g=>h=>new Sp(f,new E(h|0),g))(fq())),d),new G(g=>{if(null!==g){var h=g.Y|0,k=g.V;if(Pi(Nh(),k)===h)return new Tp(k);g=Up(f.ia());g.e()?(g=f.ia(),g=r(g.te,g.se)):g=g.oa();var m=ab(g);g=m.h;m=m.l;var n=h>>31,q=65535&h,u=h>>>16|0,v=65535& +g,w=g>>>16|0,A=Math.imul(q,v);v=Math.imul(u,v);var K=Math.imul(q,w);q=A+((v+K|0)<<16)|0;A=(A>>>16|0)+K|0;h=(((Math.imul(h,m)+Math.imul(n,g)|0)+Math.imul(u,w)|0)+(A>>>16|0)|0)+(((65535&A)+v|0)>>>16|0)|0;k=Pi(Nh(),k);n=k>>31;K=65535&k;u=k>>>16|0;v=65535&g;w=g>>>16|0;A=Math.imul(K,v);v=Math.imul(u,v);var M=Math.imul(K,w);K=A+((v+M|0)<<16)|0;A=(A>>>16|0)+M|0;g=(((Math.imul(k,m)+Math.imul(n,g)|0)+Math.imul(u,w)|0)+(A>>>16|0)|0)+(((65535&A)+v|0)>>>16|0)|0;return new Vp(Wp(q,h,K,g))}throw new D(g);}),new G(g=> +new C(Pi(Nh(),g),g))))))}po.prototype=new t;po.prototype.constructor=po;z(po,"pink.cozydev.protosearch.internal.FrequencyPostingsList$",{OZ:1});var oo;function gq(){}gq.prototype=new t;gq.prototype.constructor=gq;function hq(){}hq.prototype=gq.prototype;function iq(){}iq.prototype=new t;iq.prototype.constructor=iq;z(iq,"pink.cozydev.protosearch.internal.NextPositionToMatch$",{RZ:1});var jq;function kq(){}kq.prototype=new t;kq.prototype.constructor=kq; +function lq(a,b,c){if(1>b.a.length)throw Qi("queries array must not be empty");if(0>c)throw Qi("minShouldMatch must be positive");return new mq(b,c)}function nq(a,b){if(0<=a.E()){var c=new (B(Zp).P)(a.E());a.hc(c,0,2147483647)}else{c=[];for(a=a.i();a.n();){var d=a.j();c.push(null===d?null:d)}c=new (B(Zp).P)(c)}return lq(0,c,b)}z(kq,"pink.cozydev.protosearch.internal.OrQueryIterator$",{VZ:1});var oq;function pq(){oq||(oq=new kq);return oq}function qq(){}qq.prototype=new t; +qq.prototype.constructor=qq; +function rq(a,b){var c=b.q(),d=1>c,f=1+(c-1|0)|0;if(d)var g=0;else 0>=f&&sq(tq(),1,c,1,!0),g=f;if(0<=g){d?g=0:(0>=f&&sq(tq(),1,c,1,!0),g=f);g=new y(g);var h=new uq(1,1,c,d),k=0;d?c=0:(0>=f&&sq(tq(),1,c,1,!0),c=f);c=-1===c?g.a.length:c;d=g.a.length;c=dc?0:c;k{var n=a.al;m=yq(n,m,n.Nq);return 0>m?F():new E(a.lm.a[m])});k=zq().cE; +b=b.Xw.kf(b.Ww,h,k);if(b.e())return F();c=b.oa();b=m=>new Aq(Lp().eG,m);if(c===N())h=N();else for(h=c.u(),k=h=new J(b(h),N()),c=c.A();c!==N();)d=c.u(),d=new J(b(d),N()),k=k.Z=d,c=c.A();if(0<=h.E())b=new (B(Bq).P)(h.E()),Fd(h,b,0,2147483647);else{b=null;b=[];for(h=h.i();h.n();)k=h.j(),b.push(null===k?null:k);b=new (B(Bq).P)(b)}return new E(new Cq(b,g))}z(qq,"pink.cozydev.protosearch.internal.PhraseIterator$",{XZ:1});var Dq;function Eq(a){this.Sj=a}Eq.prototype=new t;Eq.prototype.constructor=Eq; +var Ho=z(Eq,"pink.cozydev.protosearch.internal.PositionalPostingsList",{YZ:1}); +function Go(){this.rG=null;Fo=this;no();var a=new qo("typeFlag",cq(no(),dq(Qg(),new y(new Int32Array([66]))))),b=new G(g=>g.Sj),c=new G(g=>new Eq(g));mo();var d=no().qf,f=no().qf;this.rG=eq(a,new to(b,c,new Op(new pg(()=>"arrayOfN("+d+", "+f+")"),Pp(Qp(),new Rp(new G((g=>h=>new Sp(f,new E(h|0),g))(fq())),d),new G(g=>{if(null!==g){var h=g.Y|0,k=g.V;if(Pi(Nh(),k)===h)return new Tp(k);g=Up(f.ia());g.e()?(g=f.ia(),g=r(g.te,g.se)):g=g.oa();var m=ab(g);g=m.h;m=m.l;var n=h>>31,q=65535&h,u=h>>>16|0,v=65535& +g,w=g>>>16|0,A=Math.imul(q,v);v=Math.imul(u,v);var K=Math.imul(q,w);q=A+((v+K|0)<<16)|0;A=(A>>>16|0)+K|0;h=(((Math.imul(h,m)+Math.imul(n,g)|0)+Math.imul(u,w)|0)+(A>>>16|0)|0)+(((65535&A)+v|0)>>>16|0)|0;k=Pi(Nh(),k);n=k>>31;K=65535&k;u=k>>>16|0;v=65535&g;w=g>>>16|0;A=Math.imul(K,v);v=Math.imul(u,v);var M=Math.imul(K,w);K=A+((v+M|0)<<16)|0;A=(A>>>16|0)+M|0;g=(((Math.imul(k,m)+Math.imul(n,g)|0)+Math.imul(u,w)|0)+(A>>>16|0)|0)+(((65535&A)+v|0)>>>16|0)|0;return new Vp(Wp(q,h,K,g))}throw new D(g);}),new G(g=> +new C(Pi(Nh(),g),g))))))}Go.prototype=new t;Go.prototype.constructor=Go;z(Go,"pink.cozydev.protosearch.internal.PositionalPostingsList$",{ZZ:1});var Fo;function Fq(){}Fq.prototype=new t;Fq.prototype.constructor=Fq;function Gq(){}Gq.prototype=Fq.prototype;var Zp=z(0,"pink.cozydev.protosearch.internal.QueryIterator",{aj:1});function Hq(){}Hq.prototype=new t;Hq.prototype.constructor=Hq;z(Hq,"pink.cozydev.protosearch.internal.QueryIteratorSearch$",{b_:1});var Iq; +function Jq(a,b){this.MA=null;this.dN=a;this.sG=b;this.MA=a.ko.g(a.$k.Lx)}Jq.prototype=new t;Jq.prototype.constructor=Jq; +Jq.prototype.cf=function(a){if(a&&a.$classData&&a.$classData.wb.Zk)return(new Kq(this.MA,this.sG)).cf(a);if(a instanceof Wm){var b=a.eo;a=a.fo;var c=this.dN.ko.hh(b);b=c.e()?new Dg("Unsupported field: '"+b+"'"):new Eg(c.oa());return b instanceof Eg?(new Kq(b.ib,this.sG)).cf(a):b}if(a instanceof jn)return this.cf(a.go);if(a instanceof Lq)return a=Mq(a.Yf,new G(d=>this.cf(d)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,aq||(aq=new Xp),new Eg(Yp(new J(a.mc,a.fc)))):a;if(a instanceof Rq)return a=Mq(a.$e,new G(d=> +this.cf(d)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,pq(),new Eg(nq(new J(a.mc,a.fc),1))):a;if(a instanceof $m)return b=a.em,a=this.cf(a.fm),a instanceof Eg?new Eg(new Sq(a.ib,b)):a;if(a instanceof dn)return b=a.gm,a=Mq(a.hm,new G(d=>this.cf(d)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,pq(),new Eg(nq(new J(a.mc,a.fc),b))):a;if(a instanceof Qm)return a=this.cf(a.im),a instanceof Eg?new Eg(new Tq(a.ib,this.MA.fC())):a;if(a instanceof Um)throw new Uq;if(a instanceof Sm)throw new Uq;throw new D(a);}; +z(Jq,"pink.cozydev.protosearch.internal.QueryIteratorSearch$MultiIndexQueryIteratorSearcher",{d_:1});function Vq(a,b){if(null!==b){var c=b.sq;b=b.tq;return c instanceof E&&(c=c.zc,b instanceof E)?new Eg(a.oo.rH(c,b.zc,a.Qx)):new Dg("Unsupported TermRange error?")}throw new D(b);}function Wq(a,b){try{var c=b.Cx,d=new Xq;N();var f=sm(Sl(),c);d.yC=f;var g=d.yC}catch(h){if(h instanceof Nl)return new Dg("Invalid regex query "+b);throw h;}return new Eg(a.oo.sH(g,a.Qx))} +function Kq(a,b){this.oo=a;this.Qx=b}Kq.prototype=new t;Kq.prototype.constructor=Kq; +Kq.prototype.cf=function(a){if(a instanceof ao)return new Eg(this.oo.tH(a.ho,this.Qx));if(a instanceof bo)return new Eg(this.oo.qH(a.xx,this.Qx));if(a instanceof ho)return Vq(this,a);if(a instanceof eo)return Wq(this,a);if(a instanceof Un){var b=this.oo;if(b instanceof Yq){Dq||(Dq=new qq);b=rq(b,rg(N(),Zq(Ah(),Ad(a.rq," ",0))));if(b instanceof E)return new Eg(b.zc);if(F()===b)return new Dg("Some terms in phrase '"+a+"' could not be found in index");throw new D(b);}return new Dg("Index does not support phrase queries")}if(a instanceof +Wm)return new Dg("Unsupported nested field query: "+a);if(a instanceof jn)return this.cf(a.go);if(a instanceof Lq)return a=Mq(a.Yf,new G(c=>this.cf(c)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,aq||(aq=new Xp),new Eg(Yp(new J(a.mc,a.fc)))):a;if(a instanceof Rq)return a=Mq(a.$e,new G(c=>this.cf(c)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,pq(),new Eg(nq(new J(a.mc,a.fc),1))):a;if(a instanceof Qm)return a=this.cf(a.im),a instanceof Eg?new Eg(new Tq(a.ib,this.oo.fC())):a;if(a instanceof $m)return b=a.em, +a=this.cf(a.fm),a instanceof Eg?new Eg(new Sq(a.ib,b)):a;if(a instanceof dn)return b=a.gm,a=Mq(a.hm,new G(c=>this.cf(c)),(Nq(),new Qq)),a instanceof Eg?(a=a.ib,pq(),new Eg(nq(new J(a.mc,a.fc),b))):a;if(a instanceof Um)throw new Uq;if(a instanceof Sm)throw new Uq;if(a instanceof Vn)throw new Uq;if(a instanceof Wn)throw new Uq;if(a instanceof co)throw new Uq;throw new D(a);};z(Kq,"pink.cozydev.protosearch.internal.QueryIteratorSearch$SingleIndexQueryIteratorSearcher",{e_:1}); +function yq(a,b,c){for(var d=0;;){if(c<=d)return-1;var f=(c-d|0)-1|0;f=d+((f+(f>>>31|0)|0)>>1)|0;var g=Aa(b,a.Tj.a[f]);g=g>>31|(-g|0)>>>31|0;if(-1===g)c=f;else if(1===g)d=1+f|0;else return f}}function $q(a){this.Tj=a;this.Nq=a.a.length}$q.prototype=new t;$q.prototype.constructor=$q;$q.prototype.f=function(){return"TermDictionary("+this.Nq+" terms)"};function ar(a,b){var c=a.Tj;a:{for(var d=0;d{fr(new gr(b,n))&&hr(c,d.lc);d.lc=1+d.lc|0},g=a.a.length,h=0;if(null!==a)for(;ha.Tj),new G(a=>new $q(a)),mo().lG)}kr.prototype=new t;kr.prototype.constructor=kr;z(kr,"pink.cozydev.protosearch.internal.TermDictionary$",{g_:1});var lr;function ko(){lr||(lr=new kr);return lr}function mr(){this.iC=this.bw=null;nr=this;this.bw=new y(0);this.iC=new x(0)}mr.prototype=new t;mr.prototype.constructor=mr;z(mr,"scala.Array$EmptyArrays$",{Z2:1});var nr; +function or(){nr||(nr=new mr);return nr}function pr(a,b){return new Zo(c=>b.g(a.g(c)))}function qr(){}qr.prototype=new t;qr.prototype.constructor=qr;function rr(){}rr.prototype=qr.prototype;function sr(){this.mI=this.Om=null;tr=this;this.Om=new Zo(()=>ur().Om);this.mI=new vr}sr.prototype=new t;sr.prototype.constructor=sr;function wr(a,b){return a.Om===b}z(sr,"scala.PartialFunction$",{g3:1});var tr;function ur(){tr||(tr=new sr);return tr}function xr(){yr=this}xr.prototype=new t; +xr.prototype.constructor=xr; +function zr(a,b,c,d){a=0a){if(b instanceof x)return yl(H(),b,a,d);if(b instanceof y){H();if(a>d)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+" \x3e "+d);d=d-a|0;c=(b.a.length>>>1|0)-a|0;c=d +d)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=dd)throw Qi(a+ +" \x3e "+d);d=d-a|0;c=b.a.length-a|0;c=d=a)return Pr(Qg(),b);if(b instanceof x)return a=ll(H(),b,a),cl(H(),a,c),a;if(b instanceof y){if(c===Qr())return c=ql(H(),b,a),Rk(H(),c),c}else if(b instanceof hb){if(c===Rr())return c=vl(H(),b,a),Vk(H(),c),c}else if(b instanceof db){if(c===Sr())return c=wl(H(),b,a),Zk(H(),c),c}else if(b instanceof eb){if(c===Tr())return c=ol(H(),b,a),al(H(),c),c}else if(b instanceof gb){if(c===Ur())return c=pl(H(),b,a),Xk(H(),c),c}else if(b instanceof cb&&c===Vr())return c=xl(H(), +b,a),Xr(Yr(),c,c.a.length,Vr()),c;if(300>a)a=Pr(Qg(),b),Xr(Yr(),a,Pi(Nh(),a),c),c=a;else{if(rb(ob,sb(ea(b).ba).ba))var d=nl(H(),b,a,l(B(ob)));else d=new x(a),Zr($r(),b,0,d,0,Pi(Nh(),b));cl(H(),d,c);$r();c=d;b=Ar(Br(),sb(ea(b).ba));d=b.od();null!==d&&d===l(ub)?c=as(a):rb(d.ba,sb(ea(c).ba).ba)?c=d.ba.Qv?bs(c,a):nl(H(),c,a,ea(tb(d.ba,0))):(a=b.le(a),Zr($r(),c,0,a,0,Pi(Nh(),c)),c=a)}return c}z(xr,"scala.collection.ArrayOps$",{b5:1});var yr;function Dd(){yr||(yr=new xr);return yr}function cs(){} +cs.prototype=new t;cs.prototype.constructor=cs;function ds(a,b){a=b+~(b<<9)|0;a^=a>>>14|0;a=a+(a<<4)|0;return a^(a>>>10|0)}z(cs,"scala.collection.Hashing$",{p5:1});var es;function fs(){es||(es=new cs);return es}function gs(a,b){for(a=a.i();a.n();)b.g(a.j())}function hs(a,b){var c=!0;for(a=a.i();c&&a.n();)c=!!b.g(a.j());return c}function is(a,b){var c=!1;for(a=a.i();!c&&a.n();)c=!!b.g(a.j());return c}function js(a,b,c){if(Tb(a))return ks(0,a.q(),b,c,a);for(a=a.i();a.n();)b=c.Ca(b,a.j());return b} +function ls(a,b){if(Tb(a)&&0c?a:a-c|0;d=ad?0:d)|0;g=d?"":b.substring(a,d)}function xs(a,b){return ys(zs(),b.Ra(new Zo(c=>{Cd();return c instanceof As?c.AJ():c})).Wg(Bs()))}function Rg(a,b){if(""===b)throw new Cs("head of empty String");return b.charCodeAt(0)}function Bd(a,b){if(""===b)throw new Cs("last of empty String");return b.charCodeAt(b.length-1|0)}function Ds(a,b,c){Cd();a=b.length;return ws(0,b,0,c{Q();return Ps(new Os,b.za(),(Q(),Ns(new Os,a)))}))} +z(Ls,"scala.collection.immutable.LazyList$Deferrer$",{N6:1});var Qs;function Rs(){}Rs.prototype=new t;Rs.prototype.constructor=Rs;z(Rs,"scala.collection.immutable.LazyList$EmptyMarker$",{O6:1});var Ss;function Ts(){Ss||(Ss=new Rs);return Ss}function Us(){this.WC=null}Us.prototype=new t;Us.prototype.constructor=Us;function Vs(a){a=a.WC;if(null===a)throw Ws("uninitialized");return a.za()}function Xs(a,b){if(null!==a.WC)throw Ws("already initialized");a.WC=b} +z(Us,"scala.collection.immutable.LazyList$LazyBuilder$DeferredState",{Q6:1});function Ys(){}Ys.prototype=new t;Ys.prototype.constructor=Ys;z(Ys,"scala.collection.immutable.LazyList$MidEvaluation$",{S6:1});var Zs;function $s(){Zs||(Zs=new Ys);return Zs}function at(){this.bJ=null;bt=this;this.bJ=new ct(0,0,new x(0),new y(0),0,0)}at.prototype=new t;at.prototype.constructor=at;z(at,"scala.collection.immutable.MapNode$",{j7:1});var bt; +function dt(a,b){var c=new et;a=b+" is out of bounds (min 0, max "+(Pi(Nh(),a)-1|0);ft(c,a);return c}function gt(){}gt.prototype=new t;gt.prototype.constructor=gt;function ht(){}ht.prototype=gt.prototype;function it(a,b){if(0>b)throw dt(a,b);if(b>(a.a.length-1|0))throw dt(a,b);var c=new y(a.a.length-1|0);a.G(0,c,0,b);a.G(1+b|0,c,b,(a.a.length-b|0)-1|0);return c} +function jt(a,b,c){if(0>b)throw dt(a,b);if(b>a.a.length)throw dt(a,b);var d=new y(1+a.a.length|0);a.G(0,d,0,b);d.a[b]=c;a.G(b,d,1+b|0,a.a.length-b|0);return d}var kt=z(0,"scala.collection.immutable.Node",{kz:1});function lt(){this.yw=0;mt=this;this.yw=Ma(+Math.ceil(6.4))}lt.prototype=new t;lt.prototype.constructor=lt;function nt(a,b,c){return 31&(b>>>c|0)}function ot(a,b){return 1<k?Bt(b,It(a,b.K,c,d,f,g)):0h?Gt(b,Mt(a,b.M,c-h|0,d,f)):b},Rt=function Pt(a,b,c){for(;;){if(null===b||0>=c)return b;if(c>=(2147483647&b.D))return null;var f=Nt(0,b.K);if(c>f)c=(c-f|0)-1|0, +b=b.M;else return c===f?Qt(a,null,b.ga,b.Aa,b.M):Qt(a,Pt(a,b.K,c),b.ga,b.Aa,b.M)}},Tt=function St(a,b,c){for(;;){if(null===b||0>=c)return null;if(c>=(2147483647&b.D))return b;var f=Nt(0,b.K);if(c<=f)b=b.K;else return c===(1+f|0)?(a=Ot(a,b.K,c,b.ga,b.Aa),null===a||0>a.D||(b=a.K,null!==b&&0<=b.D?b=!0:(b=a.M,b=null!==b&&0<=b.D),a=b?yt(a):a)):a=Qt(a,b.K,b.ga,b.Aa,St(a,b.M,(c-f|0)-1|0)),a}},au=function Ut(a,b,c,d){if(null===b)return null;var g=d.I(c,b.ga);if(0>g){a=Ut(a,b.K,c,d);if(a===b.K)return b;c= +b.K;null!==c&&0>c.D?b=Vt(b,a,b.M):a===b.K&&0<=b.D||(c=b.M,b=new At(b.ga,b.Aa,a,b.M,1+((null===a?0:2147483647&a.D)+(null===c?0:2147483647&c.D)|0)|0));return b}if(0c.D)if(c=b.K,null!==a&&0<=a.D)b=Wt(b,c,yt(a));else if(null!==c&&0>c.D)b=Xt(b,Yt(c),a);else if(null!==c&&0<=c.D?(d=c.M,d=null!==d&&0>d.D):d=!1,d)b=Wt(c.M,Xt(c,Yt(c.K),c.M.K),Zt(b,c.M.M,a));else throw $f(),ag(new bg,"Defect: invariance violation");else a===b.M&&0<=b.D||(c=b.K,b=new At(b.ga, +b.Aa,b.K,a,1+((null===c?0:2147483647&c.D)+(null===a?0:2147483647&a.D)|0)|0));return b}return $t(a,b.K,b.M)}; +function Xt(a,b,c){if(null!==b&&0<=b.D){if(null!==c&&0<=c.D)return Wt(a,yt(b),yt(c));var d=b.K;if(null!==d&&0<=d.D)return Ct(b,yt(b.K),Zt(a,b.M,c));d=b.M;return null!==d&&0<=d.D?Ct(b.M,Et(b,b.M.K),Zt(a,b.M.M,c)):Zt(a,b,c)}if(null!==c&&0<=c.D){d=c.M;if(null!==d&&0<=d.D)return Ct(c,Zt(a,b,c.K),yt(c.M));d=c.K;return null!==d&&0<=d.D?Ct(c.K,Zt(a,b,c.K.K),Zt(c,c.K.M,c.M)):Zt(a,b,c)}return Zt(a,b,c)} +function Vt(a,b,c){if(null!==b&&0<=b.D)return Wt(a,yt(b),c);if(null!==c&&0>c.D)return Xt(a,b,Yt(c));if(null!==c&&0<=c.D){var d=c.K;d=null!==d&&0>d.D}else d=!1;if(d)return Wt(c.K,Zt(a,b,c.K.K),Xt(c,c.K.M,Yt(c.M)));$f();throw ag(new bg,"Defect: invariance violation");} +var $t=function bu(a,b,c){return null===b?c:null===c?b:0<=b.D?0<=c.D?(a=bu(a,b.M,c.K),null!==a&&0<=a.D?Ct(a,Ht(b,a.K),Ft(c,a.M)):Ht(b,Ft(c,a))):Ht(b,bu(a,b.M,c)):0>c.D?(a=bu(a,b.M,c.K),null!==a&&0<=a.D?Ct(a,Ht(b,a.K),Ft(c,a.M)):Vt(b,b.K,Ft(c,a))):Ft(c,bu(a,b,c.K))},eu=function cu(a,b,c,d,f,g,h){if((null===b?0:0>b.D?(g-1|0)<<1:(g<<1)-1|0)===(h+(h>>>31|0)|0)>>1<<1)return Jt(c,d,b,f);var m=null!==b&&0>b.D;a=cu(a,b.M,c,d,f,m?g-1|0:g,h);m&&null!==a&&0<=a.D?(c=a.M,c=null!==c&&0<=c.D):c=!1;return c?Jt(a.ga, +a.Aa,du(b.ga,b.Aa,b.K,a.K),yt(a.M)):zt(m,b.ga,b.Aa,b.K,a)},gu=function fu(a,b,c,d,f,g,h){if((null===f?0:0>f.D?(h-1|0)<<1:(h<<1)-1|0)===(g+(g>>>31|0)|0)>>1<<1)return Jt(c,d,b,f);var m=null!==f&&0>f.D;a=fu(a,b,c,d,f.K,g,m?h-1|0:h);m&&null!==a&&0<=a.D?(b=a.K,b=null!==b&&0<=b.D):b=!1;return b?Jt(a.ga,a.Aa,yt(a.K),du(f.ga,f.Aa,a.M,f.M)):zt(m,f.ga,f.Aa,a,f.M)},ju=function hu(a,b,c,d){if(null===b)return new iu(null,null,null,c);var g=d.I(c,b.ga);if(0===g)return new iu(b.K,b,b.M,b.ga);if(0>g){c=hu(a,b.K, +c,d);if(null===c)throw new D(c);d=c.jl;return new iu(c.Uj,c.Am,Qt(a,c.Vj,b.ga,b.Aa,b.M),d)}c=hu(a,b.M,c,d);if(null===c)throw new D(c);d=c.Am;g=c.Vj;var h=c.jl;return new iu(Qt(a,b.K,b.ga,b.Aa,c.Uj),d,g,h)},lu=function ku(a,b){if(null===b.M)return new uo(b.K,b.ga,b.Aa);var d=ku(a,b.M);if(null===d)throw new D(d);var f=d.ch,g=d.dh;return new uo(Qt(a,b.K,b.ga,b.Aa,d.bh),f,g)},nu=function mu(a,b,c,d){if(null===b||b===c)return c;if(null===c)return b;var g=ju(a,b,c.ga,d);if(null===g)throw new D(g);var h= +g.Vj;b=g.jl;g=mu(a,g.Uj,c.K,d);d=mu(a,h,c.M,d);return Qt(a,g,b,c.Aa,d)},pu=function ou(a,b,c,d){if(null===b||null===c)return b;if(b===c)return null;var g=ju(a,b,c.ga,d);if(null===g)throw new D(g);b=g.Vj;g=ou(a,g.Uj,c.K,d);c=ou(a,b,c.M,d);if(null===g)a=c;else if(null===c)a=g;else{d=lu(a,g);if(null===d)throw new D(d);a=Qt(a,d.bh,d.ch,d.dh,c)}return a},ru=function qu(a,b,c,d,f){switch(c){case 0:return null;case 1:return zt(b!==d||1===b,f.j(),null,null,null);default:var h=c-1|0;h=(h+(h>>>31|0)|0)>>1; +var k=qu(a,1+b|0,h,d,f);return du(f.j(),null,k,qu(a,1+b|0,(c-1|0)-h|0,d,f))}},tu=function su(a,b,c,d,f){switch(c){case 0:return null;case 1:a=d.j();if(null===a)throw new D(a);return zt(b!==f||1===b,a.Y,a.V,null,null);default:var h=c-1|0;h=(h+(h>>>31|0)|0)>>1;var k=su(a,1+b|0,h,d,f),m=d.j();if(null===m)throw new D(m);return du(m.Y,m.V,k,su(a,1+b|0,(c-1|0)-h|0,d,f))}};function uu(a){for(var b=0;;){if(null===a)return 1+b|0;b=0>a.D?1+b|0:b;a=a.K}}function vu(){}vu.prototype=new t; +vu.prototype.constructor=vu;function wu(a,b,c,d){a=xu(0,b,c,d);return null===a?F():new E(a.Aa)}function xu(a,b,c,d){for(;;){if(null===b)return null;a=d.I(c,b.ga);if(0>a)b=b.K;else if(0h?(a=eu(a,b,c,d,f,g,null===f?0:0>f.D?(h-1|0)<<1:(h<<1)-1|0),null!==a&&0<=a.D?(b=a.M,b=null!==b&&0<=b.D):b=!1,b?yt(a):a):h>g?(a=gu(a,b,c,d,f,null===b?0:0>b.D?(g-1|0)<<1:(g<<1)-1|0,h),null!==a&&0<=a.D?(b=a.K,b=null!==b&&0<=b.D):b=!1,b?yt(a):a):zt(null!==b&&0<=b.D||null!==f&&0<=f.D,c,d,b,f)}z(vu,"scala.collection.immutable.RedBlackTree$",{t7:1});var Au;function Bu(){Au||(Au=new vu);return Au}function Cu(){this.ig=null}Cu.prototype=new t; +Cu.prototype.constructor=Cu;function Du(){}Du.prototype=Cu.prototype;function Eu(a){return null===a?a:0===(2147483647&a.D)?Fu(Gu(a)):yt(a)}function Hu(a,b){if(0<=b.D){var c=b.K,d=b.M;if(null!==c&&0<=c.D)return c=Gu(c),d=Iu(a,d),Ju(b,c,d);if(null!==d&&0<=d.D)return c=d.M,b=Ku(b,d.K),a=Iu(a,c),Ju(d,b,a)}a.K===b?d=a:0===(2147483647&a.D)?(a.K=b,d=a):d=new At(a.ga,a.Aa,b,a.M,-2147483648&a.D);return d} +function Lu(a,b){if(0<=b.D){var c=b.K;if(null!==c&&0<=c.D){var d=Ku(a,c.K);b=Iu(b,c.M);return Ju(c,d,b)}d=b.M;if(null!==d&&0<=d.D)return c=Ku(a,c),d=Gu(d),Ju(b,c,d)}a.M===b?b=a:0===(2147483647&a.D)?(a.M=b,b=a):b=new At(a.ga,a.Aa,a.K,b,-2147483648&a.D);return b}function At(a,b,c,d,f){this.ga=a;this.Aa=b;this.K=c;this.M=d;this.D=f}At.prototype=new t;At.prototype.constructor=At;At.prototype.f=function(){return(0<=this.D?"RedTree":"BlackTree")+"("+this.ga+", "+this.Aa+", "+this.K+", "+this.M+")"}; +function Fu(a){if(0===(2147483647&a.D)){var b=1;null!==a.K&&(Fu(a.K),b=b+(2147483647&a.K.D)|0);null!==a.M&&(Fu(a.M),b=b+(2147483647&a.M.D)|0);a.D|=b}return a}function Gu(a){return 0>a.D?a:0===(2147483647&a.D)?(a.D=-2147483648,a):new At(a.ga,a.Aa,a.K,a.M,-2147483648)}function Mu(a,b){return Object.is(b,a.Aa)?a:0===(2147483647&a.D)?(a.Aa=b,a):new At(a.ga,b,a.K,a.M,-2147483648&a.D)} +function Ju(a,b,c){return a.K===b&&a.M===c?a:0===(2147483647&a.D)?(a.K=b,a.M=c,a):new At(a.ga,a.Aa,b,c,-2147483648&a.D)}function Iu(a,b){return a.K===b&&0>a.D?a:0===(2147483647&a.D)?(a.D=-2147483648,a.K=b,a):new At(a.ga,a.Aa,b,a.M,-2147483648)}function Ku(a,b){return a.M===b&&0>a.D?a:0===(2147483647&a.D)?(a.D=-2147483648,a.M=b,a):new At(a.ga,a.Aa,a.K,b,-2147483648)}function yt(a){return 0>a.D?a:new At(a.ga,a.Aa,a.K,a.M,-2147483648^a.D)} +function Yt(a){return 0<=a.D?a:new At(a.ga,a.Aa,a.K,a.M,-2147483648^a.D)}function Kt(a,b){return Object.is(b,a.Aa)?a:new At(a.ga,b,a.K,a.M,a.D)}function Ft(a,b){if(b===a.K)return a;var c=a.M;return new At(a.ga,a.Aa,b,a.M,-2147483648&a.D|1+((null===b?0:2147483647&b.D)+(null===c?0:2147483647&c.D)|0)|0)}function Ht(a,b){if(b===a.M)return a;var c=a.K;return new At(a.ga,a.Aa,a.K,b,-2147483648&a.D|1+((null===c?0:2147483647&c.D)+(null===b?0:2147483647&b.D)|0)|0)} +function Dt(a,b){if(b===a.K&&0>a.D)return a;var c=a.M;return new At(a.ga,a.Aa,b,a.M,1+((null===b?0:2147483647&b.D)+(null===c?0:2147483647&c.D)|0)|-2147483648)}function Et(a,b){if(b===a.M&&0>a.D)return a;var c=a.K;return new At(a.ga,a.Aa,a.K,b,1+((null===c?0:2147483647&c.D)+(null===b?0:2147483647&b.D)|0)|-2147483648)}function Ct(a,b,c){return b===a.K&&c===a.M?a:new At(a.ga,a.Aa,b,c,-2147483648&a.D|1+((null===b?0:2147483647&b.D)+(null===c?0:2147483647&c.D)|0)|0)} +function Wt(a,b,c){return b===a.K&&c===a.M&&0<=a.D?a:new At(a.ga,a.Aa,b,c,1+((null===b?0:2147483647&b.D)+(null===c?0:2147483647&c.D)|0)|0)}function Zt(a,b,c){return b===a.K&&c===a.M&&0>a.D?a:new At(a.ga,a.Aa,b,c,1+((null===b?0:2147483647&b.D)+(null===c?0:2147483647&c.D)|0)|-2147483648)}var Nu=z(At,"scala.collection.immutable.RedBlackTree$Tree",{z7:1});function Ou(){this.Bn=null;Pu=this;this.Bn=new Ru(0,0,new x(0),new y(0),0,0)}Ou.prototype=new t;Ou.prototype.constructor=Ou; +z(Ou,"scala.collection.immutable.SetNode$",{N7:1});var Pu;function Su(){Pu||(Pu=new Ou);return Pu} +var Vu=function Tu(a,b,c,d,f){for(;;){if(1===b){b=c;var h=d,k=f;Uu(a,1,0===(h|k^b.a.length)?b:yl(H(),b,h,k))}else{h=Math.imul(5,b-1|0);var m=1<>>h|0;h=f>>>h|0;d&=m-1|0;f&=m-1|0;if(0===d)if(0===f)f=c,Uu(a,b,0===(k|h^f.a.length)?f:yl(H(),f,k,h));else{h>k&&(d=c,Uu(a,b,0===(k|h^d.a.length)?d:yl(H(),d,k,h)));h=c.a[h];b=b-1|0;c=h;d=0;continue}else if(h===k){h=c.a[k];b=b-1|0;c=h;continue}else if(Tu(a,b-1|0,c.a[k],d,m),0===f)h>(1+k|0)&&(f=c,k=1+k|0,Uu(a,b,0===(k|h^f.a.length)?f:yl(H(),f,k,h)));else{h> +(1+k|0)&&(d=c,k=1+k|0,Uu(a,b,0===(k|h^d.a.length)?d:yl(H(),d,k,h)));h=c.a[h];b=b-1|0;c=h;d=0;continue}}break}};function Uu(a,b,c){b<=a.If?b=11-b|0:(a.If=b,b=b-1|0);a.ha.a[b]=c} +var Xu=function Wu(a,b){if(null===a.ha.a[b-1|0])if(b===a.If)a.ha.a[b-1|0]=a.ha.a[11-b|0],a.ha.a[11-b|0]=null;else{Wu(a,1+b|0);var d=a.ha.a[(1+b|0)-1|0];a.ha.a[b-1|0]=d.a[0];1===d.a.length?(a.ha.a[(1+b|0)-1|0]=null,a.If===(1+b|0)&&null===a.ha.a[11-(1+b|0)|0]&&(a.If=b)):a.ha.a[(1+b|0)-1|0]=yl(H(),d,1,d.a.length)}},Zu=function Yu(a,b){if(null===a.ha.a[11-b|0])if(b===a.If)a.ha.a[11-b|0]=a.ha.a[b-1|0],a.ha.a[b-1|0]=null;else{Yu(a,1+b|0);var d=a.ha.a[11-(1+b|0)|0];a.ha.a[11-b|0]=d.a[d.a.length-1|0];1=== +d.a.length?(a.ha.a[11-(1+b|0)|0]=null,a.If===(1+b|0)&&null===a.ha.a[(1+b|0)-1|0]&&(a.If=b)):a.ha.a[11-(1+b|0)|0]=yl(H(),d,0,d.a.length-1|0)}};function $u(a,b){this.ha=null;this.If=this.Gp=this.xi=0;this.kJ=a;this.jJ=b;this.ha=new (B(B(ob)).P)(11);this.If=this.Gp=this.xi=0}$u.prototype=new t;$u.prototype.constructor=$u;function av(a,b,c){var d=Math.imul(c.a.length,1<f&&(Vu(a,b,c,f,g),a.xi=a.xi+(g-f|0)|0);a.Gp=a.Gp+d|0} +$u.prototype.lh=function(){if(32>=this.xi){if(0===this.xi)return bv();var a=this.ha.a[0],b=this.ha.a[10];if(null!==a)if(null!==b){var c=ll(H(),a,a.a.length+b.a.length|0);b.G(0,c,a.a.length,b.a.length);var d=c}else d=a;else if(null!==b)d=b;else{var f=this.ha.a[1];d=null!==f?f.a[0]:this.ha.a[9].a[0]}return new cv(d)}Xu(this,1);Zu(this,1);var g=this.If;if(6>g){var h=this.ha.a[this.If-1|0],k=this.ha.a[11-this.If|0];if(null!==h&&null!==k)if(30>=(h.a.length+k.a.length|0)){var m=this.ha,n=this.If,q=ll(H(), +h,h.a.length+k.a.length|0);k.G(0,q,h.a.length,k.a.length);m.a[n-1|0]=q;this.ha.a[11-this.If|0]=null}else g=1+g|0;else 30<(null!==h?h:k).a.length&&(g=1+g|0)}var u=this.ha.a[0],v=this.ha.a[10],w=u.a.length,A=g;switch(A){case 2:var K=T().Ua,M=this.ha.a[1];if(null!==M)var S=M;else{var L=this.ha.a[9];S=null!==L?L:K}return new dv(u,w,S,v,this.xi);case 3:var aa=T().Ua,U=this.ha.a[1],ka=null!==U?U:aa,ra=T().Lc,ha=this.ha.a[2];if(null!==ha)var fb=ha;else{var xb=this.ha.a[8];fb=null!==xb?xb:ra}var pb=T().Ua, +Ra=this.ha.a[9];return new ev(u,w,ka,w+(ka.a.length<<5)|0,fb,null!==Ra?Ra:pb,v,this.xi);case 4:var xc=T().Ua,ma=this.ha.a[1],Za=null!==ma?ma:xc,qd=T().Lc,Sc=this.ha.a[2],qc=null!==Sc?Sc:qd,qa=T().jf,Eb=this.ha.a[3];if(null!==Eb)var Fa=Eb;else{var Sb=this.ha.a[7];Fa=null!==Sb?Sb:qa}var Tc=Fa,fh=T().Lc,Kf=this.ha.a[8],ce=null!==Kf?Kf:fh,ye=T().Ua,Lf=this.ha.a[9],ug=w+(Za.a.length<<5)|0;return new fv(u,w,Za,ug,qc,ug+(qc.a.length<<10)|0,Tc,ce,null!==Lf?Lf:ye,v,this.xi);case 5:var gh=T().Ua,de=this.ha.a[1], +Gd=null!==de?de:gh,yb=T().Lc,vg=this.ha.a[2],ze=null!==vg?vg:yb,Hd=T().jf,Ae=this.ha.a[3],Ye=null!==Ae?Ae:Hd,Mf=T().Dl,Ze=this.ha.a[4];if(null!==Ze)var Nf=Ze;else{var bd=this.ha.a[6];Nf=null!==bd?bd:Mf}var Id=Nf,Uc=T().jf,Be=this.ha.a[7],$e=null!==Be?Be:Uc,af=T().Lc,Of=this.ha.a[8],Ce=null!==Of?Of:af,rd=T().Ua,ee=this.ha.a[9],De=w+(Gd.a.length<<5)|0,yc=De+(ze.a.length<<10)|0;return new gv(u,w,Gd,De,ze,yc,Ye,yc+(Ye.a.length<<15)|0,Id,$e,Ce,null!==ee?ee:rd,v,this.xi);case 6:var wg=T().Ua,bf=this.ha.a[1], +cf=null!==bf?bf:wg,xg=T().Lc,df=this.ha.a[2],Pf=null!==df?df:xg,rc=T().jf,sc=this.ha.a[3],Jc=null!==sc?sc:rc,Jd=T().Dl,Kd=this.ha.a[4],ff=null!==Kd?Kd:Jd,Qf=T().sz,Rf=this.ha.a[5];if(null!==Rf)var fe=Rf;else{var gf=this.ha.a[5];fe=null!==gf?gf:Qf}var Sf=fe,Tf=T().Dl,zc=this.ha.a[6],sd=null!==zc?zc:Tf,Ld=T().jf,ge=this.ha.a[7],he=null!==ge?ge:Ld,yg=T().Lc,Ac=this.ha.a[8],Fe=null!==Ac?Ac:yg,Md=T().Ua,ie=this.ha.a[9],cd=w+(cf.a.length<<5)|0,Ge=cd+(Pf.a.length<<10)|0,hf=Ge+(Jc.a.length<<15)|0;return new hv(u, +w,cf,cd,Pf,Ge,Jc,hf,ff,hf+(ff.a.length<<20)|0,Sf,sd,he,Fe,null!==ie?ie:Md,v,this.xi);default:throw new D(A);}};$u.prototype.f=function(){return"VectorSliceBuilder(lo\x3d"+this.kJ+", hi\x3d"+this.jJ+", len\x3d"+this.xi+", pos\x3d"+this.Gp+", maxDim\x3d"+this.If+")"};z($u,"scala.collection.immutable.VectorSliceBuilder",{o8:1}); +function iv(){this.sz=this.Dl=this.jf=this.Lc=this.Ua=this.dD=null;jv=this;this.dD=new x(0);this.Ua=new (B(B(ob)).P)(0);this.Lc=new (B(B(B(ob))).P)(0);this.jf=new (B(B(B(B(ob)))).P)(0);this.Dl=new (B(B(B(B(B(ob))))).P)(0);this.sz=new (B(B(B(B(B(B(ob)))))).P)(0)}iv.prototype=new t;iv.prototype.constructor=iv;function kv(a,b,c){a=b.a.length;var d=new x(1+a|0);b.G(0,d,0,a);d.a[a]=c;return d}function lv(a,b,c){a=ll(H(),b,1+b.a.length|0);a.a[a.a.length-1|0]=c;return a} +function mv(a,b,c){a=new x(1+c.a.length|0);c.G(0,a,1,c.a.length);a.a[0]=b;return a}function nv(a,b,c){a=tb(sb(ea(c).ba).ba,1+c.a.length|0);c.G(0,a,1,c.a.length);a.a[0]=b;return a}function ov(a,b,c,d){var f=0,g=c.a.length;if(0===b)for(;fc)return null;a=a.be}}rv.prototype.Cg=function(a){for(var b=this;;)if(a.Ca(b.Hn,b.Di),null!==b.be)b=b.be;else break};rv.prototype.f=function(){return"Node("+this.Hn+", "+this.Di+", "+this.Ck+") -\x3e "+this.be}; +var tv=z(rv,"scala.collection.mutable.HashMap$Node",{$8:1});function uv(a,b,c){this.Bj=a;this.Gh=b;this.ce=c}uv.prototype=new t;uv.prototype.constructor=uv;uv.prototype.Oa=function(a){for(var b=this;;)if(a.g(b.Bj),null!==b.ce)b=b.ce;else break};uv.prototype.f=function(){return"Node("+this.Bj+", "+this.Gh+") -\x3e "+this.ce};var vv=z(uv,"scala.collection.mutable.HashSet$Node",{g9:1});function wv(){}wv.prototype=new t;wv.prototype.constructor=wv; +function xv(a,b,c){if(c!==b)throw new yv("mutation occurred during iteration");}z(wv,"scala.collection.mutable.MutationTracker$",{o9:1});var zv;function Av(){zv||(zv=new wv);return zv}function Bv(){this.hp=this.dw=null}Bv.prototype=new t;Bv.prototype.constructor=Bv;function Cv(){}Cv.prototype=Bv.prototype;function Dv(a,b){return a.hp.me(a.dw,b)}function Ev(a){return a.hp.Wv(a.dw)} +function Fv(a,b){if(b instanceof ba)return b=$a(b),a.oy()&&a.Dd()===b;if(Wa(b))return b|=0,a.ny()&&a.Ro()===b;if(Xa(b))return b|=0,a.py()&&a.Kp()===b;if(ia(b))return b|=0,a.Rv()&&a.Dd()===b;if(b instanceof ca){var c=ab(b);b=c.h;c=c.l;a=a.uf();return 0===(a.h^b|a.l^c)}return oa(b)?(b=Math.fround(b),a.Im()===b):"number"===typeof b?(b=+b,a.dj()===b):!1}function Gv(){}Gv.prototype=new t;Gv.prototype.constructor=Gv;z(Gv,"scala.math.package$",{q4:1});var Hv; +function Iv(){this.vI=this.uI=null;this.tl=0;Jv=this;Kv();Lv();Ih();N();Mv();Q();Nv();tq();Ov||(Ov=new Pv);Qv||(Qv=new Rv);Sv||(Sv=new Tv)}Iv.prototype=new t;Iv.prototype.constructor=Iv;function ae(){var a=Uv();0===(2&a.tl)<<24>>24&&0===(2&a.tl)<<24>>24&&(a.vI=Vv(),a.tl=(2|a.tl)<<24>>24);return a.vI}z(Iv,"scala.package$",{r4:1});var Jv;function Uv(){Jv||(Jv=new Iv);return Jv}function Wv(){}Wv.prototype=new t;Wv.prototype.constructor=Wv; +function V(a,b,c){if(!(a=b===c)){if(Xv(b))a:if(Xv(c))c=Yv(0,b,c);else{if(c instanceof ba){if("number"===typeof b){c=+b===c.yg;break a}if(b instanceof ca){b=ab(b);c=c.yg;c=0===(b.h^c|b.l^c>>31);break a}}c=null===b?null===c:Da(b,c)}else c=b instanceof ba?Zv(b,c):null===b?null===c:Da(b,c);a=c}return a} +function Yv(a,b,c){return"number"===typeof b?(a=+b,"number"===typeof c?a===+c:c instanceof ca?(c=ab(c),a===4294967296*c.l+(c.h>>>0)):c instanceof As&&c.d(a)):b instanceof ca?(b=ab(b),a=b.h,b=b.l,c instanceof ca?(c=ab(c),0===(a^c.h|b^c.l)):"number"===typeof c?4294967296*b+(a>>>0)===+c:c instanceof As&&c.d(r(a,b))):null===b?null===c:Da(b,c)} +function Zv(a,b){return b instanceof ba?a.yg===b.yg:Xv(b)?"number"===typeof b?+b===a.yg:b instanceof ca?(b=ab(b),a=a.yg,0===(b.h^a|b.l^a>>31)):null===b?null===a:Da(b,a):null===a&&null===b}z(Wv,"scala.runtime.BoxesRunTime$",{O9:1});var $v;function W(){$v||($v=new Wv);return $v}var ps=z(0,"scala.runtime.Null$",{T9:1});function aw(){}aw.prototype=new t;aw.prototype.constructor=aw;z(aw,"scala.runtime.RichBoolean$",{W9:1});var bw;function cw(){}cw.prototype=new t;cw.prototype.constructor=cw; +z(cw,"scala.runtime.RichChar$",{Y9:1});var dw;function ew(){}ew.prototype=new t;ew.prototype.constructor=ew;z(ew,"scala.runtime.RichInt$",{$9:1});var fw;function gw(){}gw.prototype=new t;gw.prototype.constructor=gw;function hw(){iw();throw jw("tried to cast away nullability, but value is null");}z(gw,"scala.runtime.Scala3RunTime$",{a$:1});var kw;function iw(){kw||(kw=new gw)}function lw(){}lw.prototype=new t;lw.prototype.constructor=lw; +function mw(a,b,c){if(b instanceof x||b instanceof y||b instanceof jb)return b.a[c];if(b instanceof hb)return a=b.a,c<<=1,r(a[c],a[c+1|0]);if(b instanceof ib)return b.a[c];if(b instanceof db)return p(b.a[c]);if(b instanceof eb||b instanceof gb||b instanceof cb)return b.a[c];if(null===b)throw Nr();throw new D(b);} +function ms(a,b,c,d){if(b instanceof x)b.a[c]=d;else if(b instanceof y)b.a[c]=d|0;else if(b instanceof jb)b.a[c]=+d;else if(b instanceof hb)a=ab(d),b=b.a,c<<=1,b[c]=a.h,b[c+1|0]=a.l;else if(b instanceof ib)b.a[c]=Math.fround(d);else if(b instanceof db)b.a[c]=$a(d);else if(b instanceof eb)b.a[c]=d|0;else if(b instanceof gb)b.a[c]=d|0;else if(b instanceof cb)b.a[c]=!!d;else{if(null===b)throw Nr();throw new D(b);}} +function Pr(a,b){if(b instanceof x||b instanceof y||b instanceof jb||b instanceof hb||b instanceof ib||b instanceof db||b instanceof eb||b instanceof gb||b instanceof cb)return b.H();if(null===b)throw Nr();throw new D(b);}function nw(a){Qg();return Dh(a.x(),a.v()+"(",",",")")}function ow(a,b){return null===b?null:pw(qw(),b)}function Pg(a,b){null===b?a=null:0===b.a.length?(a=qw(),a=a.dz?a.ez:rw(a)):a=new sw(b);return a}function dq(a,b){return null!==b?new tw(b):null} +function Kn(a,b){return null!==b?new uw(b):null}z(lw,"scala.runtime.ScalaRunTime$",{b$:1});var vw;function Qg(){vw||(vw=new lw);return vw}function ww(){}ww.prototype=new t;ww.prototype.constructor=ww;ww.prototype.m=function(a,b){a=this.cg(a,b);return Math.imul(5,a<<13|a>>>19|0)-430675100|0};ww.prototype.cg=function(a,b){b=Math.imul(-862048943,b);b=Math.imul(461845907,b<<15|b>>>17|0);return a^b};ww.prototype.T=function(a,b){return this.zB(a^b)}; +ww.prototype.zB=function(a){a=Math.imul(-2048144789,a^(a>>>16|0));a=Math.imul(-1028477387,a^(a>>>13|0));return a^(a>>>16|0)};function xw(a,b,c){return c===b>>31?b:b^c} +function yw(a,b){a=Ma(b);if(a===b)return a;qj();if(-0x7fffffffffffffff>b)var c=r(0,-2147483648);else 0x7fffffffffffffff<=b?c=r(-1,2147483647):(a=b|0,c=b/4294967296|0,c=r(a,0>b&&0!==a?c-1|0:c));a=c.h;c=c.l;if(4294967296*c+(a>>>0)===b)return a^c;a=b|0;if(a===b&&-Infinity!==1/b)return a;if(b!==b)return 2146959360;Oa.setFloat64(0,b,!0);return(Oa.getInt32(0,!0)|0)^(Oa.getInt32(4,!0)|0)}function zw(a,b){return null===b?0:"number"===typeof b?yw(0,+b):b instanceof ca?(a=ab(b),xw(0,a.h,a.l)):Ga(b)} +function Aw(a,b){throw O(new P,""+b);}z(ww,"scala.runtime.Statics$",{e$:1});var Bw;function X(){Bw||(Bw=new ww);return Bw} +function Cw(a,b){if(Dw()===b)return new Ew(a);if(b instanceof Ew)return new C(a,b.Oo);if(b instanceof C)return new uo(a,b.Y,b.V);if(b instanceof uo)return new iu(a,b.bh,b.ch,b.dh);if(b instanceof iu)return new Fw(a,b.Uj,b.Am,b.Vj,b.jl);if(b instanceof Fw)return new Gw(a,b.Tu,b.Uu,b.Vu,b.Wu,b.Xu);if(b instanceof Gw)return new Hw(a,b.Yu,b.Zu,b.$u,b.av,b.bv,b.cv);if(b instanceof Hw)return new Iw(a,b.dv,b.ev,b.fv,b.gv,b.hv,b.iv,b.jv);if(b instanceof Iw)return new Jw(a,b.kv,b.lv,b.mv,b.nv,b.ov,b.pv,b.qv, +b.rv);if(b instanceof Jw)return new Kw(a,b.sv,b.tv,b.uv,b.vv,b.wv,b.xv,b.yv,b.zv,b.Av);if(b instanceof Kw)return new Lw(a,b.Wq,b.Yq,b.Zq,b.$q,b.ar,b.br,b.cr,b.dr,b.er,b.Xq);if(b instanceof Lw)return new Mw(a,b.fr,b.ir,b.jr,b.kr,b.lr,b.mr,b.nr,b.or,b.pr,b.gr,b.hr);if(b instanceof Mw)return new Nw(a,b.qr,b.ur,b.vr,b.wr,b.xr,b.yr,b.zr,b.Ar,b.Br,b.rr,b.sr,b.tr);if(b instanceof Nw)return new Ow(a,b.Cr,b.Hr,b.Ir,b.Jr,b.Kr,b.Lr,b.Mr,b.Nr,b.Or,b.Dr,b.Er,b.Fr,b.Gr);if(b instanceof Ow)return new Pw(a,b.Pr, +b.Vr,b.Wr,b.Xr,b.Yr,b.Zr,b.$r,b.as,b.bs,b.Qr,b.Rr,b.Sr,b.Tr,b.Ur);if(b instanceof Pw)return new Qw(a,b.cs,b.ks,b.ls,b.ms,b.ns,b.os,b.ps,b.qs,b.rs,b.ds,b.es,b.fs,b.gs,b.hs,b.js);if(b instanceof Qw)return new Rw(a,b.ss,b.As,b.Bs,b.Cs,b.Ds,b.Es,b.Fs,b.Gs,b.Hs,b.ts,b.us,b.vs,b.ws,b.xs,b.ys,b.zs);if(b instanceof Rw)return new Sw(a,b.Is,b.Rs,b.Ss,b.Ts,b.Us,b.Vs,b.Ws,b.Xs,b.Ys,b.Js,b.Ks,b.Ls,b.Ms,b.Ns,b.Os,b.Ps,b.Qs);if(b instanceof Sw)return new Tw(a,b.Zs,b.it,b.jt,b.kt,b.lt,b.mt,b.nt,b.ot,b.pt,b.$s,b.at, +b.bt,b.ct,b.dt,b.et,b.ft,b.gt,b.ht);if(b instanceof Tw)return new Uw(a,b.qt,b.Bt,b.Ct,b.Dt,b.Et,b.Ft,b.Gt,b.Ht,b.It,b.rt,b.st,b.tt,b.ut,b.vt,b.wt,b.xt,b.yt,b.zt,b.At);if(b instanceof Uw)return new Vw(a,b.Jt,b.Ut,b.Wt,b.Xt,b.Yt,b.Zt,b.$t,b.au,b.bu,b.Kt,b.Lt,b.Mt,b.Nt,b.Ot,b.Pt,b.Qt,b.Rt,b.St,b.Tt,b.Vt);if(b instanceof Vw)return new Ww(a,b.cu,b.nu,b.qu,b.ru,b.su,b.tu,b.uu,b.vu,b.wu,b.du,b.eu,b.fu,b.gu,b.hu,b.iu,b.ju,b.ku,b.lu,b.mu,b.ou,b.pu);if(b instanceof Ww)return new Xw(new x([a,b.xu,b.Iu,b.Mu, +b.Nu,b.Ou,b.Pu,b.Qu,b.Ru,b.Su,b.yu,b.zu,b.Au,b.Bu,b.Cu,b.Du,b.Eu,b.Fu,b.Gu,b.Hu,b.Ju,b.Ku,b.Lu]));throw new D(b);}function Yw(){}Yw.prototype=new t;Yw.prototype.constructor=Yw; +function Zw(a,b){switch(b.t()){case 0:return Dw();case 1:return b instanceof Ew?b:new Ew(b.c(0));case 2:return b instanceof C?b:new C(b.c(0),b.c(1));case 3:return b instanceof uo?b:new uo(b.c(0),b.c(1),b.c(2));case 4:return b instanceof iu?b:new iu(b.c(0),b.c(1),b.c(2),b.c(3));case 5:return b instanceof Fw?b:new Fw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4));case 6:return b instanceof Gw?b:new Gw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5));case 7:return b instanceof Hw?b:new Hw(b.c(0),b.c(1),b.c(2),b.c(3), +b.c(4),b.c(5),b.c(6));case 8:return b instanceof Iw?b:new Iw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7));case 9:return b instanceof Jw?b:new Jw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8));case 10:return b instanceof Kw?b:new Kw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9));case 11:return b instanceof Lw?b:new Lw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10));case 12:return b instanceof Mw?b:new Mw(b.c(0),b.c(1), +b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11));case 13:return b instanceof Nw?b:new Nw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12));case 14:return b instanceof Ow?b:new Ow(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13));case 15:return b instanceof Pw?b:new Pw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14)); +case 16:return b instanceof Qw?b:new Qw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15));case 17:return b instanceof Rw?b:new Rw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15),b.c(16));case 18:return b instanceof Sw?b:new Sw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15),b.c(16),b.c(17)); +case 19:return b instanceof Tw?b:new Tw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15),b.c(16),b.c(17),b.c(18));case 20:return b instanceof Uw?b:new Uw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15),b.c(16),b.c(17),b.c(18),b.c(19));case 21:return b instanceof Vw?b:new Vw(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12), +b.c(13),b.c(14),b.c(15),b.c(16),b.c(17),b.c(18),b.c(19),b.c(20));case 22:return b instanceof Ww?b:new Ww(b.c(0),b.c(1),b.c(2),b.c(3),b.c(4),b.c(5),b.c(6),b.c(7),b.c(8),b.c(9),b.c(10),b.c(11),b.c(12),b.c(13),b.c(14),b.c(15),b.c(16),b.c(17),b.c(18),b.c(19),b.c(20),b.c(21));default:if(b instanceof Xw)return b;a=new hd(b.x(),new G(d=>d));if(0<=a.E())b=new x(a.E()),Fd(a,b,0,2147483647),a=b;else{b=null;for(b=[];a.n();){var c=a.j();b.push(null===c?null:c)}a=new x(b)}return new Xw(a)}} +function $w(a,b,c){c instanceof Xw?(a=new x(1+c.t()|0),a.a[0]=b,c.Gi.G(0,a,1,c.t()),b=new Xw(a)):b=Cw(b,c);return b}z(Yw,"scala.runtime.Tuples$",{g$:1});var ax;function bx(){ax||(ax=new Yw);return ax}function bp(){}bp.prototype=new t;bp.prototype.constructor=bp;function cp(a){if(a instanceof Ro)return a.Fk;var b=[];for(a=a.i();a.n();){var c=a.j();b.push(c)|0}return b}z(bp,"scala.scalajs.js.JSConverters$JSRichIterableOnce$",{v9:1});var ap;function ip(){}ip.prototype=new t; +ip.prototype.constructor=ip;z(ip,"scala.scalajs.js.typedarray.TypedArrayBuffer$",{z9:1});var hp;function cx(){}cx.prototype=new t;cx.prototype.constructor=cx;z(cx,"scala.sys.package$",{Q4:1});var dx;function $f(){dx||(dx=new cx)}function ex(a){this.wI=a}ex.prototype=new t;ex.prototype.constructor=ex;ex.prototype.f=function(){return"DynamicVariable("+this.wI+")"};z(ex,"scala.util.DynamicVariable",{R4:1});function fx(){}fx.prototype=new t;fx.prototype.constructor=fx; +z(fx,"scala.util.Either$MergeableEither$",{S4:1});var gx;function hx(){}hx.prototype=new t;hx.prototype.constructor=hx; +function ix(a,b,c,d){c=c-b|0;if(!(2>c)){if(0d.I(g,mw(Qg(),a,(b+f|0)-1|0))){for(var h=b,k=(b+f|0)-1|0;1<(k-h|0);){var m=(h+k|0)>>>1|0;0>d.I(g,mw(Qg(),a,m))?k=m:h=m}h=h+(0>d.I(g,mw(Qg(),a,h))?0:1)|0;for(k=b+f|0;k>h;)ms(Qg(),a,k,mw(Qg(),a,k-1|0)),k=k-1|0;ms(Qg(),a,h,g)}f=1+f|0}}} +function jx(a,b,c,d,f,g,h){if(32>(d-c|0))ix(b,c,d,f);else{var k=(c+d|0)>>>1|0;g=null===g?h.le(k-c|0):g;jx(a,b,c,k,f,g,h);jx(a,b,k,d,f,g,h);kx(b,c,k,d,f,g)}}function kx(a,b,c,d,f,g){if(0f.I(mw(Qg(),a,h),mw(Qg(),g,m))?(ms(Qg(),a,b,mw(Qg(),a,h)),h=1+h|0):(ms(Qg(),a,b,mw(Qg(),g,m)),m=1+m|0),b=1+b|0;for(;mc)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(a,b,tb(sb(ea(b).ba).ba,b.a.length),0,c,d,f):Uk(b,0,c,d,f)}else if(b instanceof y)if(d===Qr()){d=H();a=Sk();f=Sk();if(0>c)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(d,b,new y(b.a.length),0,c,a,f):Uk(b,0,c,a,f)}else if(f=fq(),32>(c-0|0))ix(b,0,c,d);else{var g=(0+c|0)>>>1|0,h=new y(g-0|0);if(32>(g-0| +0))ix(b,0,g,d);else{var k=(0+g|0)>>>1|0;jx(a,b,0,k,d,h,f);jx(a,b,k,g,d,h,f);kx(b,0,k,g,d,h)}32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h));kx(b,0,g,c,d,h)}else if(b instanceof jb)f=lx(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new jb(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else if(b instanceof +hb)if(d===Rr()){d=H();a=Wk();f=Wk();if(0>c)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(d,b,new hb(b.a.length>>>1|0),0,c,a,f):Uk(b,0,c,a,f)}else f=mx(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new hb(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else if(b instanceof ib)f=nx(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new ib(g- +0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else if(b instanceof db)if(d===Sr()){d=H();a=$k();f=$k();if(0>c)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(d,b,new db(b.a.length),0,c,a,f):Uk(b,0,c,a,f)}else f=ox(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new db(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f), +jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else if(b instanceof eb)if(d===Tr()){d=H();a=bl();f=bl();if(0>c)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(d,b,new eb(b.a.length),0,c,a,f):Uk(b,0,c,a,f)}else f=px(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new eb(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k= +(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else if(b instanceof gb)if(d===Ur()){d=H();a=Yk();f=Yk();if(0>c)throw Qi("fromIndex(0) \x3e toIndex("+c+")");16<(c-0|0)?Tk(d,b,new gb(b.a.length),0,c,a,f):Uk(b,0,c,a,f)}else f=qx(),32>(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new gb(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c, +d,h)),kx(b,0,g,c,d,h));else if(b instanceof cb)if(d===Vr()){for(a=d=0;d(c-0|0)?ix(b,0,c,d):(g=(0+c|0)>>>1|0,h=new cb(g-0|0),32>(g-0|0)?ix(b,0,g,d):(k=(0+g|0)>>>1|0,jx(a,b,0,k,d,h,f),jx(a,b,k,g,d,h,f),kx(b,0,k,g,d,h)),32>(c-g|0)?ix(b,g,c,d):(k=(g+c|0)>>>1|0,jx(a,b,g,k,d,h,f),jx(a,b,k,c,d,h,f),kx(b,g,k,c,d,h)),kx(b,0,g,c,d,h));else{if(null===b)throw Nr();throw new D(b);}} +z(hx,"scala.util.Sorting$",{X4:1});var sx;function Yr(){sx||(sx=new hx);return sx}function tx(){}tx.prototype=new t;tx.prototype.constructor=tx;function ux(){}ux.prototype=tx.prototype;tx.prototype.m=function(a,b){a=this.cg(a,b);return Math.imul(5,a<<13|a>>>19|0)-430675100|0};tx.prototype.cg=function(a,b){b=Math.imul(-862048943,b);b=Math.imul(461845907,b<<15|b>>>17|0);return a^b};tx.prototype.T=function(a,b){return vx(a^b)}; +function vx(a){a=Math.imul(-2048144789,a^(a>>>16|0));a=Math.imul(-1028477387,a^(a>>>13|0));return a^(a>>>16|0)}function wx(a,b,c){var d=a.m(-889275714,Ha("Tuple2"));d=a.m(d,b);d=a.m(d,c);return a.T(d,2)}function xx(a,b){var c=Y(),d=a.t();if(0===d)return b;for(var f=0;f>>1|0;switch(c){case 0:return a.T(d,0);case 1:return c=d,X(),b=b.a,a.T(a.m(c,xw(0,b[0],b[1])),1);default:X();var f=b.a;f=xw(0,f[0],f[1]);var g=d=a.m(d,f);X();var h=b.a;h=xw(0,h[2],h[3]);var k=h-f|0;for(f=2;fc.Zf))} +function Qx(a,b,c,d){d=d.Da();var f=b;b=0;c=(c.e()?2147483647:c.oa())|0;for(var g=F();bnew C(f,g)))} +function Tx(a,b,c,d,f){return b.ka(d).Lv(new G(g=>c.ka(g.$g).jj(new G((h=>k=>new Rx(f.Ca(h.Zf,k.Zf),k.$g))(g)))))}function Ux(a){return""+(a.Fm().e()?"":Dh(a.Fm(),"","/",": "))+a.dC()}function Vx(){this.Sx=this.Tx=null;Wx=this;this.Tx=Xx();Yx||(Yx=new Zx);this.Sx=Yx}Vx.prototype=new t;Vx.prototype.constructor=Vx;z(Vx,"scodec.Platform$",{F_:1});var Wx;function $x(){Wx||(Wx=new Vx);return Wx}function ay(a,b,c){return new by(new G(d=>new Tp(c.g(d))),new G(d=>new Tp(b.g(d))),a)} +function Pp(a,b,c,d){return new by(new G(f=>new Tp(d.g(f))),c,b)}function cy(a,b,c,d){return new by(d,new G(f=>new Tp(c.g(f))),b)}function dy(){}dy.prototype=new t;dy.prototype.constructor=dy;function ey(){}ey.prototype=dy.prototype;dy.prototype.ey=function(a,b,c){var d=Mh(Nh(),l(Ab),new y(new Int32Array([c])));this.hy(d,0,a,b,c);return nk(qk(),d,d.a.length,0,d.a.length)}; +dy.prototype.hy=function(a,b,c,d,f){for(var g=0;g>31)|0)+((c&h|(c|h)&~k)>>>31|0)|0);g=1+g|0}};function fy(){}fy.prototype=new t;fy.prototype.constructor=fy;function gy(){}gy.prototype=fy.prototype;function hy(){}hy.prototype=new t;hy.prototype.constructor=hy;function iy(){}iy.prototype=hy.prototype;function jy(){}jy.prototype=new t;jy.prototype.constructor=jy;function ky(){}ky.prototype=jy.prototype; +function ly(){this.PG=null;my=this;this.PG=new G(a=>{a|=0;return 0<=a?r(a,a>>31):r(a,0)})}ly.prototype=new t;ly.prototype.constructor=ly;z(ly,"scodec.codecs.VarIntCodec$",{I0:1});var my;function ny(){this.RG=this.SG=null;oy=this;this.SG=new G(a=>{a|=0;return a<<1^a>>31});this.RG=new G(a=>{a|=0;return(a>>>1|0)^(-(1&a)|0)})}ny.prototype=new t;ny.prototype.constructor=ny;z(ny,"scodec.codecs.VarIntZigZagCodec$",{K0:1});var oy;function py(){oy||(oy=new ny);return oy} +function qy(){this.gB=this.Vq=this.hB=this.iB=0;ry=this;this.iB=127;this.hB=0;this.Vq=128;this.gB=7}qy.prototype=new t;qy.prototype.constructor=qy;z(qy,"scodec.codecs.VarLongCodec$",{M0:1});var ry;function sy(){ry||(ry=new qy);return ry}function ty(){this.YG=this.ZG=null;uy=this;this.ZG=new G(a=>{var b=ab(a);a=b.h;b=b.l;return r(a<<1^b>>31,(a>>>31|0|b<<1)^b>>31)});this.YG=new G(a=>{var b=ab(a);a=b.h;b=b.l;var c=1&a,d=-c|0;return r((a>>>1|0|b<<31)^d,(b>>>1|0)^(c|d)>>31)})}ty.prototype=new t; +ty.prototype.constructor=ty;z(ty,"scodec.codecs.VarLongZigZagCodec$",{O0:1});var uy;function vy(){uy||(uy=new ty);return uy} +function wy(){this.No=this.kH=this.fH=this.Mo=this.qf=this.iH=this.jH=this.by=this.hH=this.gH=null;xy=this;this.gH=new yy;Qp();var a=new G(c=>zy(c)),b=new G(c=>qp(rp(),c));new Op(new pg(()=>"bytes"),ay(no().gH,a,b));new Ay(8,!0);new By(8,!1,Cy());new By(16,!0,Cy());new Dy(8,!0,Cy());new Dy(16,!0,Cy());new Dy(24,!0,Cy());this.hH=new Dy(32,!0,Cy());this.by=new Ey(64,!0,Cy());this.jH=new Dy(2,!1,Cy());new Dy(4,!1,Cy());new Dy(8,!1,Cy());new Dy(16,!1,Cy());new Dy(24,!1,Cy());new Ey(32,!1,Cy());new Fy(64, +!1,Cy());new By(16,!0,Gy());new Dy(8,!0,Gy());new Dy(16,!0,Gy());new Dy(24,!0,Gy());this.iH=new Dy(32,!0,Gy());new Ey(64,!0,Gy());new Dy(2,!1,Gy());new Dy(4,!1,Gy());new Dy(8,!1,Gy());new Dy(16,!1,Gy());new Dy(24,!1,Gy());new Ey(32,!1,Gy());new Fy(64,!1,Gy());this.qf=new Hy(Cy());new Hy(Gy());new Iy(Cy());new Iy(Gy());new Jy(Cy());new Jy(Gy());new Ky(Cy());new Ky(Gy());new Ly(Cy());new Ly(Gy());new My(Cy());new My(Gy());Ny||(Ny=new Oy);this.Mo=Ny;this.fH=(no(),new Py($x().Sx));this.kH=(no(),new Py($x().Tx)); +Qy();Ry(no(),$x().Sx);Sy(no(),$x().Sx);this.No=Ry(no(),$x().Tx);Sy(no(),$x().Tx);Ty||(Ty=new Uy)}wy.prototype=new t;wy.prototype.constructor=wy;function Qy(){no();var a=no().fH,b=new J("Does not contain a 'NUL' termination byte.",N());new Op(new pg(()=>"nulTerminatedString("+a+")"),(no(),new Vy(new Wy(b),a)))}function Ry(a,b){return new Op(new pg(()=>"string32("+b.nf+")"),Xy(no(),no().hH,(no(),new Py(b))))} +function Sy(a,b){new Op(new pg(()=>"string32("+b.nf+")"),Xy(no(),no().iH,(no(),new Py(b))))}function cq(a,b){a=Yy();no();rp();b=qp(0,Zy(gp(),b,a));return new $y(b,!0)}function Xy(a,b,c){a=(no(),0);no();b=az(no(),b);return new bz(b,c,a,a>>31)} +function az(a,b){return new Op(new pg(()=>b.f()),cy(Qp(),b,new G(c=>{c|=0;return r(c,c>>31)}),new G(c=>{var d=ab(c);c=d.h;d=d.l;return(0===d?2147483647>>0:0c>>>0:-1>d)?new Vp(cz(new dz,da(c,d)+" cannot be converted to an integer")):new Tp(c)})))} +function Fp(a,b,c){return new Op(new pg(()=>"listOfN("+b+", "+c+")"),Pp(Qp(),new Rp(new G(d=>new ez(c,new E(d|0))),b),new G(d=>{if(null!==d){var f=d.Y|0,g=d.V;if(g.q()===f)return new Tp(g);d=Up(c.ia());d.e()?(d=c.ia(),d=r(d.te,d.se)):d=d.oa();var h=ab(d);d=h.h;h=h.l;var k=f>>31,m=65535&f,n=f>>>16|0,q=65535&d,u=d>>>16|0,v=Math.imul(m,q);q=Math.imul(n,q);var w=Math.imul(m,u);m=v+((q+w|0)<<16)|0;v=(v>>>16|0)+w|0;f=(((Math.imul(f,h)+Math.imul(k,d)|0)+Math.imul(n,u)|0)+(v>>>16|0)|0)+(((65535&v)+q|0)>>> +16|0)|0;g=g.q();k=g>>31;w=65535&g;n=g>>>16|0;q=65535&d;u=d>>>16|0;v=Math.imul(w,q);q=Math.imul(n,q);var A=Math.imul(w,u);w=v+((q+A|0)<<16)|0;v=(v>>>16|0)+A|0;d=(((Math.imul(g,h)+Math.imul(k,d)|0)+Math.imul(n,u)|0)+(v>>>16|0)|0)+(((65535&v)+q|0)>>>16|0)|0;return new Vp(Wp(m,f,w,d))}throw new D(d);}),new G(d=>new C(d.q(),d))))}z(wy,"scodec.codecs.codecs$package$",{Q0:1});var xy;function no(){xy||(xy=new wy);return xy}function fz(){}fz.prototype=new t;fz.prototype.constructor=fz;function gz(){} +gz.prototype=fz.prototype;function lg(a,b){return kg(a,new G(c=>new Nb(b.g(c))))}function kg(a,b){return a instanceof hz?new iz(a,b):a instanceof jz?new kz(a,b):new lz(b,a)}function mz(){}mz.prototype=new Kb;mz.prototype.constructor=mz;function nz(){}nz.prototype=mz.prototype;function nn(a,b){this.MJ=a;this.NJ=b}nn.prototype=new t;nn.prototype.constructor=nn;z(nn,"cats.Show$ToShowOps$$anon$3",{CQ:1,AQ:1});function oz(){}oz.prototype=new Xb;oz.prototype.constructor=oz;function pz(){}pz.prototype=oz.prototype; +function qz(){}qz.prototype=new t;qz.prototype.constructor=qz;function rz(){}e=rz.prototype=qz.prototype;e.e=function(){return!(this instanceof lc)};function Rb(a,b){return jc(Ob(),a,(Ob(),new $b(b)))}function sz(a,b){if(a instanceof Zb)return new Zb(a.Lk.Ra(b));Ob();a=new hd(a.i(),b);return Yb(0,tz(Nv(),a))}e.Bg=function(a,b){for(var c=this.i();c.n();)a=b.Ca(a,c.j());return a}; +function hg(a,b){b=new uz(b);var c=Ob().Qf;for(a=a.i();a.n();){var d=a.j();d=b.af(d,Ob().Lz);c=d!==Ob().Lz?Rb(c,d):c}return c}e.i=function(){return this instanceof Zb?this.Lk.i():this instanceof $b?new vz(this.Nl):this instanceof mc?new wz(this):Lv().da};e.ub=function(){return this instanceof Zb?this.Lk.ub():this instanceof $b?new J(this.Nl,N()):this instanceof mc?rg(N(),new wz(this)):N()}; +e.Ln=function(){if(this instanceof Zb)return this.Lk.Ln();if(this instanceof $b){var a=this.Nl;return(Nv(),bv()).we(a)}if(this instanceof mc)return a=new wz(this),tz(Nv(),a);Nv();return bv()}; +function xz(a){var b=new yz,c=zz(),d=new Az(!0),f=k=>{if(d.oD){k=b.Lw(k);var m=c.Hb;m.F=""+m.F+k;d.oD=!1}else k=", "+b.Lw(k),m=c.Hb,m.F=""+m.F+k;return!1};a:if(a instanceof lc){var g=a;for(a=N();null!==g;)if(g instanceof $b){if(f(g.Nl))break;a.e()?g=null:(g=a.u(),a=a.A())}else if(g instanceof mc){var h=g.Zw;a=new J(g.$w,a);g=h}else if(g instanceof Zb){for(g=g.Lk.i();g.n();)if(h=g.j(),f(h))break a;a.e()?g=null:(g=a.u(),a=a.A())}else throw new D(g);}Bz(c,41);return c.Hb.F}e.f=function(){return xz(this)}; +e.d=function(a){var b;if(b=a instanceof qz)a:if(b=(Ic(),new Cz),this===a)b=!0;else{var c=this.i();for(a=a.i();c.n()&&a.n();)if(!b.Ud(c.j(),a.j())){b=!1;break a}b=c.n()===a.n()}return b}; +e.p=function(){Ic();var a=new Dz;Ez||(Ez=new Fz);var b=Ez;a:{var c=this.i().i(),d=Y().Uc;if(c.n()){var f=c.j();if(c.n()){var g=c.j(),h=a.ke(f);f=d=Y().m(d,h);g=a.ke(g);h=g-h|0;for(var k=2;c.n();){d=Y().m(d,g);var m=a.ke(c.j());if(h!==(m-g|0)){d=Y().m(d,m);for(k=1+k|0;c.n();)d=Y().m(d,a.ke(c.j())),k=1+k|0;a=Y().T(d,k);break a}g=m;k=1+k|0}a=b.zB(Y().m(Y().m(f,h),g))}else a=Y().T(Y().m(d,a.ke(f)),1)}else a=Y().T(d,0)}return a};function Gz(){}Gz.prototype=new bc;Gz.prototype.constructor=Gz; +function Hz(){}Hz.prototype=Gz.prototype;function Iz(){}Iz.prototype=new dc;Iz.prototype.constructor=Iz;function Jz(){}Jz.prototype=Iz.prototype;function Kz(){}Kz.prototype=new fc;Kz.prototype.constructor=Kz;function Lz(){}Lz.prototype=Kz.prototype;function Mz(){}Mz.prototype=new tc;Mz.prototype.constructor=Mz;function Nz(){}Nz.prototype=Mz.prototype;function Oz(){this.Sp=null;Pz=this;this.Sp=ic();Qz||(Qz=new Rz)}Oz.prototype=new vc;Oz.prototype.constructor=Oz;z(Oz,"cats.data.package$",{VR:1,UR:1}); +var Pz;function vn(){Pz||(Pz=new Oz);return Pz}function Sz(){}Sz.prototype=new Bc;Sz.prototype.constructor=Sz;function Tz(){}Tz.prototype=Sz.prototype;function Uz(){}Uz.prototype=new Bc;Uz.prototype.constructor=Uz;function Vz(){}Vz.prototype=Uz.prototype;function Fz(){}Fz.prototype=new Dc;Fz.prototype.constructor=Fz;z(Fz,"cats.kernel.instances.StaticMethods$",{ST:1,fT:1});var Ez;function fn(){}fn.prototype=new t;fn.prototype.constructor=fn;fn.prototype.$o=function(a){return new Wz(a)}; +z(fn,"cats.parse.Accumulator$$anon$12",{iU:1,Yp:1});function Wz(a){this.Sz=null;this.BM=a;this.Sz=new Jh}Wz.prototype=new t;Wz.prototype.constructor=Wz;Wz.prototype.GB=function(){oi();return new Cm(this.BM,this.Sz.Sa())};Wz.prototype.kl=function(a){this.Sz.Ia(a);return this};Wz.prototype.Hm=function(){return this.GB()};z(Wz,"cats.parse.Accumulator$$anon$13",{jU:1,$p:1});function ed(){}ed.prototype=new t;ed.prototype.constructor=ed;ed.prototype.kl=function(){return this};ed.prototype.Hm=function(){}; +z(ed,"cats.parse.Appender$$anon$1",{pU:1,$p:1});function Xz(){this.Tz=null;this.Tz=Yz(new Zz)}Xz.prototype=new t;Xz.prototype.constructor=Xz;function $z(a,b){var c=a.Tz;b=""+Na(b);c.F+=b;return a}Xz.prototype.kl=function(a){return $z(this,$a(a))};Xz.prototype.Hm=function(){return this.Tz.F};z(Xz,"cats.parse.Appender$$anon$2",{qU:1,$p:1});function aA(a){this.DE=a}aA.prototype=new t;aA.prototype.constructor=aA;aA.prototype.Hm=function(){return this.DE.Sa()}; +aA.prototype.kl=function(a){this.DE.Ia(a);return this};z(aA,"cats.parse.Appender$$anon$4",{rU:1,$p:1});function bA(){}bA.prototype=new md;bA.prototype.constructor=bA;z(bA,"cats.parse.BitSetUtil$",{sU:1,tU:1});var cA;function Ig(){cA||(cA=new bA);return cA}function Me(){this.fb=0;this.gb=!1}Me.prototype=new sh;Me.prototype.constructor=Me;function dA(){}dA.prototype=Me.prototype;function fi(a,b){a=Wd(I(),a);b=Ud(I(),a,b);a=new G(c=>c.V);return le(I(),b,a)}e=Me.prototype; +e.lg=function(a){a=a.Te();a=Ud(I(),this,a);var b=new G(c=>c.Y);return le(I(),a,b)};function Mn(a,b){var c=new Dg(void 0),d=new G(f=>{f=b.g(f);if(f instanceof E)return new Eg(f.zc);if(F()===f)return c;throw new D(f);});a=le(I(),a,d);return eA(I(),a)}function Vd(a,b){return Mg(I(),new J(a,new J(b,N())))}function bn(a,b,c){b=b.Te();c=c.Te();a=Ud(I(),a,c);a=$d(I(),b,a);b=new G(d=>{if(null!==d){var f=d.V;if(null!==f)return f.Y}throw new D(d);});return le(I(),a,b)}e.Te=function(){return Wd(I(),this)}; +e.Ez=function(a){return Ud(I(),this,a)};e.Fz=function(a){return fi(this,a)};e.Nm=function(a){return le(I(),this,a)};e.yB=function(a){return wh(I(),this,a)};var rn=z(0,"cats.parse.Parser",{Ve:1,vb:1});function Om(a){this.VE=a}Om.prototype=new kh;Om.prototype.constructor=Om;function Nm(a,b){return fA(I(),a.VE,b)}Om.prototype.lg=function(a){a=fA(I(),this.VE,oh(I(),a));var b=new G(c=>c.Y);return le(I(),a,b)};z(Om,"cats.parse.Parser$Soft",{DV:1,EV:1}); +function gA(a){0===(32&a.ry)<<24>>24&&0===(32&a.ry)<<24>>24&&(a.GH=new y(new Int32Array([1632,1776,1984,2406,2534,2662,2790,2918,3046,3174,3302,3430,3558,3664,3792,3872,4160,4240,6112,6160,6470,6608,6784,6800,6992,7088,7232,7248,42528,43216,43264,43472,43504,43600,44016,65296,66720,68912,69734,69872,69942,70096,70384,70736,70864,71248,71360,71472,71904,72016,72784,73040,73120,73552,92768,92864,93008,120782,120792,120802,120812,120822,123200,123632,124144,125264,130032])),a.ry=(32|a.ry)<<24>>24);return a.GH} +function hA(){this.GH=null;this.ry=0}hA.prototype=new t;hA.prototype.constructor=hA;function iA(a,b){if(1114111>>0)throw vb();return String.fromCodePoint(b)}function jA(a,b){if(256>b)a=9>=(b-48|0)>>>0?b-48|0:25>=(b-65|0)>>>0?b-55|0:25>=(b-97|0)>>>0?b-87|0:-1;else if(25>=(b-65313|0)>>>0)a=b-65303|0;else if(25>=(b-65345|0)>>>0)a=b-65335|0;else{var c=xd(H(),gA(a),b);c=0>c?-2-c|0:c;0>c?a=-1:(a=b-gA(a).a[c]|0,a=9a?a:-1}function kA(a,b){return 256>b?48===b:0<=xd(H(),gA(a),b)} +function lA(a,b){return 65535&mA(b)}function mA(a){switch(a){case 8115:case 8131:case 8179:return 9+a|0;default:if(47>=(a-8064|0)>>>0)return 8|a;var b=iA(0,a).toUpperCase();switch(b.length){case 1:return b.charCodeAt(0);case 2:var c=b.charCodeAt(0);b=b.charCodeAt(1);return-671032320===(-67044352&(c<<16|b))?(64+(1023&c)|0)<<10|1023&b:a;default:return a}}}function nA(a,b){return 65535&oA(b)} +function oA(a){if(304===a)return 105;var b=iA(0,a).toLowerCase();switch(b.length){case 1:return b.charCodeAt(0);case 2:var c=b.charCodeAt(0);b=b.charCodeAt(1);return-671032320===(-67044352&(c<<16|b))?(64+(1023&c)|0)<<10|1023&b:a;default:return a}}z(hA,"java.lang.Character$",{E1:1,b:1});var pA;function qA(){pA||(pA=new hA);return pA}function rA(a){throw new Rn('For input string: "'+a+'"');}function sA(){this.HH=this.IH=null;this.nl=0}sA.prototype=new t;sA.prototype.constructor=sA; +function tA(a,b){0===(1&a.nl)<<24>>24&&0===(1&a.nl)<<24>>24&&(a.IH=RegExp("^[\\x00-\\x20]*([+-]?(?:NaN|Infinity|(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)[fFdD]?)[\\x00-\\x20]*$"),a.nl=(1|a.nl)<<24>>24);var c=a.IH.exec(b);if(null!==c)b=+parseFloat(c[1]);else{0===(2&a.nl)<<24>>24&&0===(2&a.nl)<<24>>24&&(a.HH=RegExp("^[\\x00-\\x20]*([+-]?)0[xX]([0-9A-Fa-f]*)\\.?([0-9A-Fa-f]*)[pP]([+-]?\\d+)[fFdD]?[\\x00-\\x20]*$"),a.nl=(2|a.nl)<<24>>24);var d=a.HH.exec(b);null===d&&rA(b);a=d[1];c=d[2];var f=d[3];d= +d[4];""===c&&""===f&&rA(b);b=uA(0,c,f,d,15);b="-"===a?-b:b}return b} +function uA(a,b,c,d,f){a=""+b+c;c=-(c.length<<2)|0;b=a.length-1|0;for(var g=0;g!==b&&48===a.charCodeAt(g);)g=1+g|0;a=a.substring(g);g=a.length;if(b=g>f){for(var h=!1,k=f;!h&&k!==g;)48!==a.charCodeAt(k)&&(h=!0),k=1+k|0;g=h?"1":"0";g=a.substring(0,f)+g}else g=a;f=c+(b?(a.length-(1+f|0)|0)<<2:0)|0;a:{Ai();b=+parseInt(g,16);d=Ma(+parseInt(d,10))+f|0;Oa.setFloat64(0,b,!0);f=Oa.getInt32(0,!0)|0;c=Oa.getInt32(4,!0)|0;h=2047&(c>>>20|0);a=h+d|0;g=f;k=-2146435073&c;if(!(2045>=(h-1|0)>>>0)){g=2147483647&c;if(0=== +(f|g)||2146435072<=g){d=b;break a}b=1048575&c;b=0!==b?Math.clz32(b):32+Math.clz32(f)|0;a=a-(b-12|0)|0;b=b-11|0;g=0===(32&b)?f<>>1|0)>>>(31-b|0)|0|c<=(a-1|0)>>>0?(d=a<<20|k,Oa.setInt32(0,g,!0),Oa.setInt32(4,d,!0),d=+Oa.getFloat64(0,!0)):53>=(53+a|0)>>>0?(d=(54+a|0)<<20|k,Oa.setInt32(0,g,!0),Oa.setInt32(4,d,!0),d=5.551115123125783E-17*+Oa.getFloat64(0,!0)):(d=(2047&~(d>>31))<<20|-2147483648&c,Oa.setInt32(0,0,!0),Oa.setInt32(4,d,!0),d=+Oa.getFloat64(0, +!0))}return d}function Ba(a,b,c){return b!==b?c!==c?0:1:c!==c?-1:b===c?0===b?(a=1/b,a===1/c?0:0>a?-1:1):0:b>20;if(0===c)throw Ik(new Jk,"parseFloatCorrection was given a subnormal mid: "+g);g=1048576|1048575&k;g=Aj(Zi(),h,g);c=c-1075|0;0<=b?0<=c?(a=Oj(a,Xj(Zi().Lj,b)),b=Vj(g,c),a=xA(a,b)):a=xA(Vj(Oj(a,Xj(Zi().Lj,b)),-c|0),g):0<=c?(b=-b|0,a=xA(a,Vj(Oj(g,Xj(Zi().Lj,b)),c))):(a=Vj(a,-c|0),b=-b|0,b=Oj(g,Xj(Zi().Lj,b)),a=xA(a,b));return 0>a?d:0>31)|0;h=2139095040!==(2139095040&(k|(h^k)>>8))?Qa(k):Math.fround(Math.fround(g+g)+1.401298464324817E-45);k=(g+h)/2;b=d===k?wA(b,c,f,g,h,k):g}else Ai(),h=Pa(g),k=h-(1|h>>31)|0,h=2139095040!==(2139095040&(k|(h^k)>>8))?Qa(k):Math.fround(Math.fround(g+g)-1.401298464324817E-45),k=(g+ +h)/2,b=d===k?wA(b,c,f,h,g,k):g}else b=a[10],c=a[11],f=a[12],b=void 0!==b?b:"",c=""+(void 0!==c?c:"")+(void 0!==f?f:""),f=a[13],b=Math.fround(uA(Ca(),b,c,f,7));return"-"===a[1]?Math.fround(-b):b}z(yA,"java.lang.Float$",{L1:1,b:1});var zA;function Qn(){zA||(zA=new yA);return zA}function AA(){}AA.prototype=new t;AA.prototype.constructor=AA;function BA(a,b){throw new Rn('For input string: "'+b+'"');} +function Ln(a,b,c){null===b&&BA(dh(),b);a=b.length;0===a&&BA(dh(),b);var d=qA(),f=b.charCodeAt(0),g=45===f,h=g?-1:0;f=g||43===f?1:0;f>=a&&BA(dh(),b);for(g=0;f!==a;){var k=jA(d,b.charCodeAt(f));(0>k||g>>>0>c>>>0)&&BA(dh(),b);g=Math.imul(g,10)+k|0;f=1+f|0}g>>>0>(2147483647-h|0)>>>0&&BA(dh(),b);return(g^h)-h|0}function ch(a,b){a=b-(1431655765&b>>1)|0;a=(858993459&a)+(858993459&a>>2)|0;return Math.imul(16843009,252645135&(a+(a>>4)|0))>>24}z(AA,"java.lang.Integer$",{N1:1,b:1});var CA; +function dh(){CA||(CA=new AA);return CA}function DA(a){if(!a.uy){for(var b=[],c=0;2>c;)b.push(null),c=1+c|0;for(c=2;36>=c;){for(var d=c,f=1073741824/(Ka(d)>>>0)|0,g=d,h=1,k="0";g<=f;)g=Math.imul(g,d),h=1+h|0,k+="0";d=pj(qj(),-1,-1,g);b.push(new xi(h,g,1/g,k,d.h,d.l));c=1+c|0}a.vy=b;a.uy=!0}return a.vy}function EA(a,b,c){for(var d=0;a!==b;){var f=jA(qA(),c.charCodeAt(a));-1===f&&BA(dh(),c);d=Math.imul(d,10)+f|0;a=1+a|0}return d}function FA(){this.vy=null;this.uy=!1}FA.prototype=new t; +FA.prototype.constructor=FA;function GA(a,b,c){return 0!==c?(a=(c>>>0).toString(16),b=(b>>>0).toString(16),a+(""+"00000000".substring(b.length)+b)):(b>>>0).toString(16)}z(FA,"java.lang.Long$",{R1:1,b:1});var HA;function LA(){HA||(HA=new FA);return HA}function MA(){}MA.prototype=new t;MA.prototype.constructor=MA;function NA(){}NA.prototype=MA.prototype;function Xv(a){return a instanceof MA||"number"===typeof a||a instanceof ca}function OA(){}OA.prototype=new t;OA.prototype.constructor=OA; +function PA(a,b,c,d){a=c+d|0;if(0>c||c>a||a>b.a.length)throw b=new QA,ft(b,null),b;for(d="";c!==a;)d+=""+Na(b.a[c]),c=1+c|0;return d} +function ys(a,b){var c=new RA,d=SA();c.Mm=null;c.UN=d;c.pl="";c.TB=!1;if(c.TB)throw new TA;for(var f=0,g=0,h=53,k=0;k!==h;){var m="BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)".indexOf("%",k)|0;if(0>m){UA(c,"BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)".substring(k));break}UA(c,"BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)".substring(k,m));var n=1+m|0,q=Dl().SH;q.lastIndex=n;var u=q.exec("BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)"); +if(null===u||(u.index|0)!==n){var v=n===h?37:"BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)".charCodeAt(n);throw new VA(""+Na(v));}k=q.lastIndex|0;for(var w="BitmapIndexedSetNode(size\x3d%s, dataMap\x3d%x, nodeMap\x3d%x)".charCodeAt(k-1|0),A,K=u[2],M=25>=(w-65|0)>>>0?256:0,S=K.length,L=0;L!==S;){var aa=K.charCodeAt(L);switch(aa){case 45:var U=1;break;case 35:U=2;break;case 43:U=4;break;case 32:U=8;break;case 48:U=16;break;case 44:U=32;break;case 40:U=64;break;case 60:U=128;break; +default:throw Ik(new Jk,p(aa));}if(0!==(M&U))throw new WA(""+Na(aa));M|=U;L=1+L|0}A=M;var ka=XA(u[3]),ra=XA(u[4]);if(-2===ka)throw new YA(-2147483648);-2===ra&&ZA(-2147483648);if(110===w){-1!==ra&&ZA(ra);if(-1!==ka)throw new YA(ka);0!==A&&$A(A);UA(c,"\n")}else if(37===w){-1!==ra&&ZA(ra);17!==(17&A)&&12!==(12&A)||$A(A);if(0!==(1&A)&&-1===ka)throw new aB("%"+u[0]);0!==(-2&A)&&bB(37,A,-2);cB(c,A,ka,"%")}else{var ha=0!==(256&A)?65535&(32+w|0):w,fb=Dl().RH.a[ha-97|0];if(-1===fb||0!==(256&A&fb))throw new VA(""+ +Na(w));if(0!==(17&A)&&-1===ka)throw new aB("%"+u[0]);17!==(17&A)&&12!==(12&A)||$A(A);-1!==ra&&0!==(512&fb)&&ZA(ra);0!==(A&fb)&&bB(ha,A,fb);if(0!==(128&A))var xb=g;else{var pb=XA(u[1]);if(-1===pb)xb=f=1+f|0;else{if(0>=pb)throw new dB(0===pb?"Illegal format argument index \x3d 0":"Format argument index: (not representable as int)");xb=pb}}if(0>=xb||xb>b.a.length)throw new eB("%"+u[0]);g=xb;var Ra=b.a[xb-1|0];if(null===Ra&&98!==ha&&115!==ha)fB(c,SA(),A,ka,ra,"null");else{var xc=void 0,ma=void 0,Za=void 0, +qd=void 0,Sc=void 0,qc=c,qa=Ra,Eb=ha,Fa=A,Sb=ka,Tc=ra;switch(Eb){case 98:fB(qc,SA(),Fa,Sb,Tc,!1===qa||null===qa?"false":"true");break;case 104:SA();var fh=Ga(qa);fB(qc,0,Fa,Sb,Tc,(fh>>>0).toString(16));break;case 115:qa&&qa.$classData&&qa.$classData.wb.k2?qa.w1(qc,(0!==(1&Fa)?1:0)|(0!==(2&Fa)?4:0)|(0!==(256&Fa)?2:0),Sb,Tc):(0!==(2&Fa)&&bB(Eb,Fa,2),fB(qc,0,Fa,Sb,Tc,""+qa));break;case 99:if(qa instanceof ba)var Kf=""+Na($a(qa));else{ia(qa)||gB(Eb,qa);var ce=qa|0;if(1114111>>0)throw new hB(ce);Kf= +65536>ce?String.fromCharCode(ce):String.fromCharCode((ce>>10)-64|55296,56320|1023&ce)}fB(qc,0,Fa,Sb,-1,Kf);break;case 100:if(ia(qa))var ye=""+(qa|0);else if(qa instanceof ca){var Lf=ab(qa),ug=Lf.h,gh=Lf.l;ye=vm(qj(),ug,gh)}else qa instanceof iB||gB(Eb,qa),ye=jj(mj(),qa);jB(qc,Fa,Sb,ye,"");break;case 111:case 120:var de=111===Eb,Gd=0===(2&Fa)?"":de?"0":0!==(256&Fa)?"0X":"0x";if(qa instanceof iB){var yb=de?8:16;SA();var vg=mj(),ze=qa.ca,Hd=qa.fa,Ae=qa.N;if(0===ze)Sc="0";else if(1===Hd){var Ye=Ae.a[Hd- +1|0],Mf=0;if(0>ze){var Ze=Ye,Nf=-Ze|0;Ye=Nf;Mf=(-Mf|0)+((Ze|Nf)>>31)|0}var bd=LA(),Id=Ye,Uc=Mf;if(10===yb||34<(yb-2|0)>>>0)qd=vm(qj(),Id,Uc);else if(Id>>31===Uc)qd=Id.toString(yb);else if(0===(-2097152&(Uc^Uc>>10)))qd=(4294967296*Uc+(Id>>>0)).toString(yb);else{var Be=Uc>>31,$e=Id^Be,af=$e-Be|0,Of=(Uc^Be)+(($e&~af)>>>31|0)|0,Ce=(bd.uy?bd.vy:DA(bd))[yb],rd=Ce.NH,ee=Ce.MN,De=+Math.floor((4294967296*(Of>>>0)+(af>>>0))*Ce.NN),yc=af-Math.imul(rd,De|0)|0;0>yc?(--De,yc=yc+rd|0):yc>=rd&&(De+=1,yc=yc-rd|0); +var wg=yc.toString(yb),bf=""+De.toString(yb)+ee.substring(wg.length)+wg;qd=0>Uc?"-"+bf:bf}Sc=qd}else if(10===yb||34<(yb-2|0)>>>0)Sc=jj(mj(),qa);else{var cf=0;cf=+Math.log(yb)/+Math.log(2);var xg=0>ze?1:0,df=Wi(gj(),kB(qa)),Pf=1+Ma(df/cf+xg)|0,rc=null;rc="";var sc=0;sc=Pf;var Jc=0;Jc=0;if(16!==yb){var Jd=new y(Hd);Ae.G(0,Jd,0,Hd);var Kd=0;Kd=Hd;for(var ff=vg.eA.a[yb],Qf=vg.dA.a[yb-2|0];;){Jc=tj(rj(),Jd,Jd,Kd,Qf);for(var Rf=sc;;){sc=sc-1|0;qA();var fe=Jc%Ka(yb)|0;if(34<(yb-2|0)>>>0||fe>>>0>=yb>>>0)var gf= +0;else{var Sf=fe-10|0;gf=65535&(0>Sf?48+fe|0:97+Sf|0)}rc=""+Na(gf)+rc;Jc=Jc/Ka(yb)|0;if(0===Jc||0===sc)break}for(var Tf=(ff-Rf|0)+sc|0,zc=0;zcge&&0>(ge<<2),sc=sc-1|0,rc=""+(Jc>>>0).toString(16)+rc,ge=1+ge|0;sd=1+sd|0}for(var he=0;48===rc.charCodeAt(he);)he=1+he|0;0!==he&&(rc=rc.substring(he));Sc=-1===ze?"-"+ +rc:rc}jB(qc,Fa,Sb,Sc,Gd)}else{if(ia(qa))var yg=qa|0,Ac=de?(yg>>>0).toString(8):(yg>>>0).toString(16);else{qa instanceof ca||gB(Eb,qa);var Fe=ab(qa),Md=Fe.h,ie=Fe.l;if(de){LA();var cd=1073741823&Md,Ge=1073741823&((Md>>>30|0)+(ie<<2)|0),hf=ie>>>28|0;if(0!==hf){var qi=(hf>>>0).toString(8),hh=(Ge>>>0).toString(8),Sj="0000000000".substring(hh.length),Tj=(cd>>>0).toString(8);Za=qi+(""+Sj+hh)+(""+"0000000000".substring(Tj.length)+Tj)}else if(0!==Ge){var ri=(Ge>>>0).toString(8),si=(cd>>>0).toString(8);Za= +ri+(""+"0000000000".substring(si.length)+si)}else Za=(cd>>>0).toString(8)}else Za=GA(LA(),Md,ie);Ac=Za}0!==(76&Fa)&&bB(Eb,Fa,76);lB(qc,SA(),Fa,Sb,Gd,mB(Fa,Ac))}break;case 101:case 102:case 103:if("number"===typeof qa){var dd=+qa;if(dd!==dd||Infinity===dd||-Infinity===dd)nB(qc,Fa,Sb,dd);else{Dl();if(0===dd)ma=new Fl(0>1/dd,"0",0);else{var zg=0>dd,je=""+(zg?-dd:dd),Uf=oB(je,101),Ag=0>Uf?0:parseInt(je.substring(1+Uf|0))|0,ih=0>Uf?je.length:Uf,rl=oB(je,46);if(0>rl)ma=new Fl(zg,je.substring(0,ih),-Ag| +0);else{for(var Oq=""+je.substring(0,rl)+je.substring(1+rl|0,ih),IA=Oq.length,sl=0;sl>>20|0),Fx=0===Tc?1:12Qu?"-":0!==(4&Fa)?"+":0!==(8&Fa)?" ":"";if(0===KA)if(0===(Uj|ul))var pD="0",qD=r(0,0),rD=0;else if(-1===Fx)pD="0",qD=r(Uj,ul),rD=-1022;else{var Do=(0!==ul?Math.clz32(ul):32+Math.clz32(Uj)|0)-11|0;pD="1";qD=r(0===(32&Do)?Uj<>>1|0)>>>(31-Do|0)|0|ul<>>31|0)|0,uR=lj>>>1|0|Wr<<31,tD=Wr>>1,rk=gJ&~iJ,Ix=hJ&~tR,vR=gJ&iJ,uD=hJ&tR;if(uD===tD?vR>>>0>>0:uD>>0>uR>>>0:uD>tD){var wR=rk+lj|0;Gx=wR;Hx=(Ix+Wr|0)+((rk&lj|(rk|lj)&~wR)>>>31|0)|0}else if(0===(rk&lj|Ix&Wr))Gx=rk,Hx=Ix;else{var xR=rk+lj|0;Gx=xR;Hx=(Ix+Wr|0)+((rk&lj|(rk|lj)&~xR)>>>31|0)|0}}var yR= +GA(LA(),Gx,Hx),vD=""+"0000000000000".substring(yR.length)+yR;Dl();if(13!==vD.length)throw Ik(new Jk,"padded mantissa does not have the right number of bits");for(var VX=1>Fx?1:Fx,Jx=vD.length;Jx>VX&&48===vD.charCodeAt(Jx-1|0);)Jx=Jx-1|0;var WX=vD.substring(0,Jx),XX=""+UX;lB(qc,SA(),Fa,Sb,SX+(0!==(256&Fa)?"0X":"0x"),mB(Fa,TX+"."+WX+"p"+XX))}}else gB(Eb,qa);break;default:throw Ik(new Jk,"Unknown conversion '"+Na(Eb)+"' was not rejected earlier");}}}}return c.f()}z(OA,"java.lang.String$",{Y1:1,b:1}); +var sB;function zs(){sB||(sB=new OA);return sB}function ft(a,b){a.QH=b;"[object Error]"!==Object.prototype.toString.call(a instanceof tB?a.Lp:a)&&(void 0===Error.captureStackTrace||Object.isSealed(a)?Error():Error.captureStackTrace(a))} +class uB extends Error{constructor(){super();this.QH=null}xe(){return this.QH}f(){var a=xa(this),b=this.xe();return null===b?a:a+": "+b}p(){return Ea.prototype.p.call(this)}d(a){return Ea.prototype.d.call(this,a)}get message(){var a=this.xe();return null===a?"":a}get name(){return xa(this)}toString(){return this.f()}} +function vB(){this.nF=this.aA=this.lF=this.nq=this.bA=this.mF=null;wB=this;this.mF=xB(0,0);xB(1,0);xB(10,0);this.bA=yB(28,5);for(var a=this.bA.a.length>>>1|0,b=new y(a),c=0;c>>1|0;b=new y(a);for(c=0;cb;)c=b,a.a[c]=xB(c,0),b=1+b|0;this.lF=a;a=new (B(BB).P)(11);for(b=0;11>b;)c=b,a.a[c]=xB(0, +c),b=1+b|0;this.aA=a;this.nF="0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}vB.prototype=new t;vB.prototype.constructor=vB;function CB(a,b,c,d){return 0===d?DB(a,b,c):0===(b|c)&&0<=d&&db>>>0:0>c)?a.lF.a[b]:EB(new qB,b,c,0)} +function yB(a,b){var c=new hb(a),d=c.a;d[0]=1;d[1]=0;for(d=1;d>31;g=65535&k;var n=k>>>16|0,q=65535&b,u=b>>>16|0,v=Math.imul(g,q);q=Math.imul(n,q);var w=Math.imul(g,u);g=v+((q+w|0)<<16)|0;v=(v>>>16|0)+w|0;k=(((Math.imul(k,m)+Math.imul(h,b)|0)+Math.imul(n,u)|0)+(v>>>16|0)|0)+(((65535&v)+q|0)>>>16|0)|0;h=c.a;f<<=1;h[f]=g;h[f+1|0]=k;d=1+d|0}return c} +function FB(a,b,c,d){a=c>>31;a=(c^a)-a|0;var f=c>>31|(-c|0)>>>31|0,g=Jj().yF;if(null===g?null===d:g===d)return f;g=Jj().tF;if(null===g?null===d:g===d)return 0;g=Jj().sF;if(null===g?null===d:g===d)return 0f?f:0;g=Jj().wF;if(null===g?null===d:g===d)return 5<=a?f:0;g=Jj().vF;if(null===g?null===d:g===d)return 5b>>>0:-1>c)||(0===c?2147483647>>0:0c?(a=~b,c=~c):a=b;return 64-(0!==c?Math.clz32(c):32+Math.clz32(a)|0)|0}function HB(a,b,c){return!IB(0,b,c)}function IB(a,b,c){a=c.a.length;for(var d=0;d!==a;){if(c.a[d]===b)return!0;d=1+d|0}return!1}z(vB,"java.math.BigDecimal$",{CX:1,b:1});var wB;function zB(){wB||(wB=new vB);return wB} +function JB(){this.cA=this.qF=this.px=this.Mj=this.Lj=this.$n=null;KB=this;this.$n=zj(1,1);this.Lj=zj(1,10);this.Mj=zj(0,0);this.px=zj(-1,1);this.qF=new (B(Ui).P)([this.Mj,this.$n,zj(1,2),zj(1,3),zj(1,4),zj(1,5),zj(1,6),zj(1,7),zj(1,8),zj(1,9),this.Lj]);for(var a=new (B(Ui).P)(32),b=0;32>b;){var c=b,d=Zi();a.a[c]=Aj(d,0===(32&c)?1<c?0!==(~b|~c)?(a=-b|0,LB(-1,a,(-c|0)+((b|a)>>31)|0)):a.px:(0===c?10>=b>>>0:0>c)?a.qF.a[b]:LB(1,b,c)}z(JB,"java.math.BigInteger$",{EX:1,b:1});var KB;function Zi(){KB||(KB=new JB);return KB} +function MB(){this.xF=this.fA=this.vF=this.wF=this.uF=this.sF=this.tF=this.yF=null;NB=this;this.yF=new OB("UP",0);this.tF=new OB("DOWN",1);this.sF=new OB("CEILING",2);this.uF=new OB("FLOOR",3);this.wF=new OB("HALF_UP",4);this.vF=new OB("HALF_DOWN",5);this.fA=new OB("HALF_EVEN",6);this.xF=new OB("UNNECESSARY",7)}MB.prototype=new t;MB.prototype.constructor=MB;z(MB,"java.math.RoundingMode$",{OX:1,b:1});var NB;function Jj(){NB||(NB=new MB);return NB}function PB(){this.nf=null}PB.prototype=new t; +PB.prototype.constructor=PB;function QB(){}QB.prototype=PB.prototype;PB.prototype.d=function(a){return a instanceof PB&&this.nf===a.nf};PB.prototype.f=function(){return this.nf};PB.prototype.p=function(){return Ha(this.nf)};PB.prototype.$f=function(a){a:{var b=this.nf;a=a.nf;for(var c=b.length,d=a.length,f=cc)return Zj.prototype.yb.call(a,a.L-1|0),Mk().Xf;b.cp(65535&f);d=1+d|0}else{d=a.re;if(null===d)throw SB();if(a.ff())throw new TB;f=a.Xe;if(-1===f)throw SB();if(a.ff())throw new TB;var k=a.L+f|0;h=k+h|0;var m=b.Ze;if(null===m)throw SB();if(b.ff())throw new TB;var n=b.rg;if(-1===n)throw SB();if(b.ff())throw new TB; +for(var q=b.L+n|0;k!==h;){var u=255&d.a[k];if(u>c)return Zj.prototype.yb.call(a,k-f|0),Zj.prototype.yb.call(b,q-n|0),Mk().Xf;m.a[q]=65535&u;k=1+k|0;q=1+q|0}Zj.prototype.yb.call(a,k-f|0);Zj.prototype.yb.call(b,q-n|0)}return g?Mk().Oj:Mk().sg};z(RB,"java.nio.charset.ISO_8859_1_And_US_ASCII_Common$Decoder",{iY:1,JM:1}); +function UB(a,b){var c=a.re;if(null===c)throw SB();if(a.ff())throw new TB;var d=a.Xe;if(-1===d)throw SB();if(a.ff())throw new TB;var f=a.L+d|0,g=a.aa+d|0,h=b.Ze;if(null===h)throw SB();if(b.ff())throw new TB;var k=b.rg;if(-1===k)throw SB();if(b.ff())throw new TB;for(var m=b.aa+k|0,n=b.L+k|0;;){if(f===g)return c=Mk().sg,Zj.prototype.yb.call(a,f-d|0),Zj.prototype.yb.call(b,n-k|0),c;var q=c.a[f];if(0<=q){if(n===m)return c=Mk().Oj,Zj.prototype.yb.call(a,f-d|0),Zj.prototype.yb.call(b,n-k|0),c;h.a[n]=65535& +q;n=1+n|0;f=1+f|0}else{var u=Xx().oA.a[127&q];if(-1===u)return c=Mk().Xf,Zj.prototype.yb.call(a,f-d|0),Zj.prototype.yb.call(b,n-k|0),c;if((1+f|0)>=g){q=Mk().sg;var v=0,w=0}else if(v=c.a[1+f|0],128!==(192&v))q=Mk().Xf,w=v=0;else if(2===u)v=(31&q)<<6|63&v,128>v?(q=Mk().Xf,v=0):(q=null,v&=65535),w=0;else if((2+f|0)>=g)q=Mk().sg,w=v=0;else if(w=c.a[2+f|0],128!==(192&w))q=Mk().tx,w=v=0;else if(3===u)v=(15&q)<<12|(63&v)<<6|63&w,2048>v?(q=Mk().Xf,v=0):2047>=(v-55296|0)>>>0?(q=Mk().co,v=0):(q=null,v&=65535), +w=0;else if((3+f|0)>=g)q=Mk().sg,w=v=0;else{var A=c.a[3+f|0];128!==(192&A)?(q=Mk().co,w=v=0):(q=(7&q)<<18|(63&v)<<12|(63&w)<<6|63&A,1048575<(q-65536|0)>>>0?(q=Mk().Xf,w=v=0):(w=q-65536|0,q=null,v=65535&(55296|w>>10),w=65535&(56320|1023&w)))}if(null!==q)return c=q,Zj.prototype.yb.call(a,f-d|0),Zj.prototype.yb.call(b,n-k|0),c;if(0===w){if(n===m)return c=Mk().Oj,Zj.prototype.yb.call(a,f-d|0),Zj.prototype.yb.call(b,n-k|0),c;h.a[n]=v;n=1+n|0;f=f+u|0}else{if((2+n|0)>m)return c=Mk().Oj,Zj.prototype.yb.call(a, +f-d|0),Zj.prototype.yb.call(b,n-k|0),c;h.a[n]=v;h.a[1+n|0]=w;n=2+n|0;f=f+u|0}}}}function VB(){this.qx=0;this.sx=this.rx=this.qq=null;this.Yk=0;yk(this,Xx())}VB.prototype=new Bk;VB.prototype.constructor=VB; +VB.prototype.oH=function(a,b){if(null===a.re||a.ff()||null===b.Ze||b.ff())for(;;){var c=a.L;if(a.L===a.aa)return Mk().sg;var d=a.Zj();if(0<=d){if(b.L===b.aa)return b=Mk().Oj,Zj.prototype.yb.call(a,c),b;b.cp(65535&d)}else{var f=Xx().oA.a[127&d];if(-1===f)return b=Mk().Xf,Zj.prototype.yb.call(a,c),b;if(a.L!==a.aa){var g=a.Zj();if(128!==(192&g)){d=Mk().Xf;var h=g=0}else 2===f?(g=(31&d)<<6|63&g,128>g?(d=Mk().Xf,g=0):(d=null,g&=65535),h=0):a.L!==a.aa?(h=a.Zj(),128!==(192&h)?(d=Mk().tx,h=g=0):3===f?(g= +(15&d)<<12|(63&g)<<6|63&h,2048>g?(d=Mk().Xf,g=0):2047>=(g-55296|0)>>>0?(d=Mk().co,g=0):(d=null,g&=65535),h=0):a.L!==a.aa?(f=a.Zj(),128!==(192&f)?(d=Mk().co,h=g=0):(d=(7&d)<<18|(63&g)<<12|(63&h)<<6|63&f,1048575<(d-65536|0)>>>0?(d=Mk().Xf,h=g=0):(h=d-65536|0,d=null,g=65535&(55296|h>>10),h=65535&(56320|1023&h)))):(d=Mk().sg,h=g=0)):(d=Mk().sg,h=g=0)}else d=Mk().sg,h=g=0;if(null!==d)return b=d,Zj.prototype.yb.call(a,c),b;if(0===h){if(b.L===b.aa)return b=Mk().Oj,Zj.prototype.yb.call(a,c),b;b.cp(g)}else{if(2> +(b.aa-b.L|0))return b=Mk().Oj,Zj.prototype.yb.call(a,c),b;b.cp(g);b.cp(h)}}}else return UB(a,b)};z(VB,"java.nio.charset.UTF_8$Decoder",{mY:1,JM:1});function WB(){}WB.prototype=new t;WB.prototype.constructor=WB;WB.prototype.I=function(a,b){return za(a,b)};z(WB,"java.util.Arrays$NaturalComparator$",{f2:1,Fd:1});var XB;function dl(){XB||(XB=new WB);return XB}function YB(){}YB.prototype=new Il;YB.prototype.constructor=YB;z(YB,"java.util.Formatter$RootLocaleInfo$",{p2:1,o2:1});var ZB; +function SA(){ZB||(ZB=new YB);return ZB}function $B(){}$B.prototype=new t;$B.prototype.constructor=$B;$B.prototype.Fi=function(a,b,c){a.a[b]=c};$B.prototype.ef=function(a,b){return a.a[b]};z($B,"java.util.internal.GenericArrayOps$ReusableAnyRefArrayOps$",{H2:1,Uv:1});var aC;function el(){aC||(aC=new $B);return aC}function bC(a){if(null===a.WB)throw Ws("No match available");return a.WB}function gr(a,b){this.fO=a;this.eO=b;this.gO=0;this.TH=this.eO;this.UH=0;this.WB=null}gr.prototype=new t; +gr.prototype.constructor=gr;function fr(a){var b=a.fO;var c=a.TH;var d=b.cC;d.lastIndex=a.UH;c=d.exec(c);b=b.cC.lastIndex|0;a.UH=null!==c?b===(c.index|0)?1+b|0:b:1+a.TH.length|0;a.WB=c;return null!==c}function cC(a){return(bC(a).index|0)+a.gO|0}function dC(a){return cC(a)+bC(a)[0].length|0}z(gr,"java.util.regex.Matcher",{L2:1,K2:1});function um(a,b,c,d,f){this.cC=null;this.dI=a;this.kO=d;this.lO=f;this.cC=new RegExp(c,this.kO+(this.lO?"gy":"g"));new RegExp("^(?:"+c+")$",d)}um.prototype=new t; +um.prototype.constructor=um;um.prototype.f=function(){return this.dI};z(um,"java.util.regex.Pattern",{M2:1,b:1});function tn(){}tn.prototype=new t;tn.prototype.constructor=tn;tn.prototype.$o=function(a){return new eC(a)};z(tn,"pink.cozydev.lucille.QueryParserHelpers$$anon$1",{VY:1,Yp:1});function eC(a){this.tA=null;this.uA=a;this.tA=new Jh}eC.prototype=new t;eC.prototype.constructor=eC;eC.prototype.kl=function(a){this.tA.Ia(this.uA);this.uA=a;return this}; +eC.prototype.Hm=function(){return new C(this.tA.Sa(),this.uA)};z(eC,"pink.cozydev.lucille.QueryParserHelpers$$anon$2",{WY:1,$p:1});function yn(){}yn.prototype=new t;yn.prototype.constructor=yn;yn.prototype.$o=function(a){return new fC(a)};z(yn,"pink.cozydev.lucille.QueryParserHelpers$$anon$3",{XY:1,Yp:1});function fC(a){this.Ex=this.HF=null;ic();oc();this.HF=kc(a).Y;var b=this.Ex=new Jh,c=b.gc;ic();oc();a=kc(a).V;c.call(b,a.i())}fC.prototype=new t;fC.prototype.constructor=fC; +fC.prototype.GB=function(){oi();return new Cm(this.HF,this.Ex.Sa())};fC.prototype.kl=function(a){this.Ex.gc((ic(),oc().PB(a)));return this};fC.prototype.Hm=function(){return this.GB()};z(fC,"pink.cozydev.lucille.QueryParserHelpers$$anon$4",{YY:1,$p:1});function gC(){this.jo=this.km=null;this.XF=this.Kx=0}gC.prototype=new t;gC.prototype.constructor=gC;function hC(){}e=hC.prototype=gC.prototype;e.fC=function(){return this.Kx};e.f=function(){return"FrequencyIndex("+this.XF+" terms, "+this.Kx+" docs)"}; +e.tH=function(a,b){var c=this.km;a=yq(c,a,c.Nq);return 0>a?new iC:new jC(b,this.jo.a[a])}; +e.qH=function(a,b){a=jr(this.km,a);if(0===a.a.length)return new iC;var c=new (B(Zp).P)(a.a.length),d=new er(0),f=n=>{c.a[d.lc]=new jC(b,this.jo.a[n|0]);d.lc=1+d.lc|0},g=a.a.length,h=0;if(a instanceof x)for(;h=d,g=d-a|0;b=d-1|0;d=new (B(Zp).P)(f?0:0{c.a[d.lc]=new jC(b,this.jo.a[n|0]);d.lc=1+d.lc|0},g=a.a.length,h=0;if(a instanceof x)for(;ha?new iC:new Aq(b,this.lm.a[a])}; +e.qH=function(a,b){a=jr(this.al,a);if(0===a.a.length)return new iC;var c=new (B(Zp).P)(a.a.length),d=new er(0),f=n=>{c.a[d.lc]=new Aq(b,this.lm.a[n|0]);d.lc=1+d.lc|0},g=a.a.length,h=0;if(a instanceof x)for(;h=d,g=d-a|0;b=d-1|0;d=new (B(Zp).P)(f?0:0{c.a[d.lc]=new Aq(b,this.lm.a[n|0]);d.lc=1+d.lc|0},g=a.a.length,h=0;if(a instanceof x)for(;h>>31|0)|0)>>1;this.Pj=0;this.mm=-2}jC.prototype=new Gq;jC.prototype.constructor=jC;jC.prototype.Cd=function(){return this.Pj};jC.prototype.Ag=function(){return this.XM.cy(this.IA.Kq.a[1+this.mm|0],this.oG)}; +jC.prototype.wd=function(a){if(-1===this.Pj)return-1;if(a<=this.Pj)return this.Pj;for(;a>this.Pj&&((2+this.mm|0)this.$M||-1===this.Qj)return-1;if(aa)return this.Qj;b=1+b|0}return this.Qj===this.dl?this.wd(1+a|0):this.Qj};z(Tq,"pink.cozydev.protosearch.internal.NotQueryIterator",{TZ:1,aj:1});function mq(a,b){this.Lq=a;this.aN=b;this.Rj=0}mq.prototype=new Gq;mq.prototype.constructor=mq;mq.prototype.Cd=function(){return this.Rj}; +mq.prototype.Ag=function(){for(var a=0,b=0;bthis.Rj&&(b=b=this.aN?this.Rj:2147483647===b?this.Rj=-1:this.wd(b)};z(mq,"pink.cozydev.protosearch.internal.OrQueryIterator",{UZ:1,aj:1}); +function Cq(a){this.Mq=a;this.of=0}Cq.prototype=new Gq;Cq.prototype.constructor=Cq;Cq.prototype.Cd=function(){return this.of};Cq.prototype.Ag=function(){return 1}; +Cq.prototype.wd=function(a){if(-1===this.of)a=-1;else if(a<=this.of)a=this.of;else a:for(;;){for(this.of=a;-1!==mC(this,this.of);){a=this.Mq.a[mC(this,this.of)].wd(this.of);if(aa.Mq.a.length)return-1;jq||(jq=new iq);var b=a.Mq,c=b.a.length;a=new y(c);if(0a.a[c]?b:c,b=1+b|0;f=c;b=f-1|0;for(d=c=-1;0<=b&&-1===c;)d=a.a[1+b|0]-a.a[b]|0,1!==d?c=b:b=b-1|0;b=1+f|0;for(g=f=-1;bg?f:c} +z(Cq,"pink.cozydev.protosearch.internal.PhraseIterator",{WZ:1,aj:1});function qC(a){this.cN=a}qC.prototype=new hq;qC.prototype.constructor=qC;function rC(a,b){a=a.cN.cf(b);a instanceof Eg&&(a=a.ib,b=new er(a.wd(1)),a=-1===b.lc?Lv().da:new sC(b,a),a=new Eg(a));return a}z(qC,"pink.cozydev.protosearch.internal.QueryIteratorSearch$$anon$1",{c_:1,QZ:1});function tC(){}tC.prototype=new Gq;tC.prototype.constructor=tC;function uC(){}uC.prototype=tC.prototype; +var Bq=z(0,"pink.cozydev.protosearch.internal.QueryIteratorWithPositions",{eN:1,aj:1});function vC(){this.hI=null;wC=this;this.hI=new xC}vC.prototype=new t;vC.prototype.constructor=vC;z(vC,"scala.$less$colon$less$",{W2:1,b:1});var wC;function yC(){wC||(wC=new vC);return wC}function as(a){a=new (B(ua).P)(a);H();for(var b=a.a.length,c=0;c!==b;)a.a[c]=void 0,c=1+c|0;return a}function zC(){}zC.prototype=new t;zC.prototype.constructor=zC; +function AC(a,b,c){a=b.E();if(-1b)throw new ml;var c=a.a.length;c=bb)throw new ml;c=a.a.length;c=bf?Hu(b,hD(a,b.K,c,d)):0d?Hu(b,kD(a,b.K,c)):0=d.a.length||0>=c||0>f)throw new Rn("Bad offset/length: offset\x3d0 len\x3d"+c+" in.length\x3d"+d.a.length);var g=0;if(0<=f&&43===d.a[0]){if(g=1+g|0,g>31,g=Ln(dh(),g,214748364),k=d-g|0,a.na=k,m=a.na,0!==(k^m|((f-(g>>31)|0)+((~d&g|~(d^g)&k)>>31)|0)^m>>31)))throw new Rn("Scale out of range");if(19>h){h=LA();""===c&&BA(dh(),c);f=0;d=!1;switch(c.charCodeAt(0)){case 43:f=1;break;case 45:f=1,d=!0}g=c.length;if(f>=g)BA(dh(),c),g=void 0;else{h=(h.uy?h.vy:DA(h))[10];for(k=h.JN;fMath.imul(3,k)&&BA(dh(),c);m=1+(((g-f|0)-1|0)%Ka(k)|0)|0;m=f+m|0;var n=EA(f,m,c);if(m===g)g=r(n,0);else{f=h.NH;k=m+k|0;var q= +65535&n;n=n>>>16|0;var u=65535&f,v=f>>>16|0,w=Math.imul(q,u);u=Math.imul(n,u);var A=Math.imul(q,v);q=w+((u+A|0)<<16)|0;w=(w>>>16|0)+A|0;v=(Math.imul(n,v)+(w>>>16|0)|0)+(((65535&w)+u|0)>>>16|0)|0;m=EA(m,k,c);n=q+m|0;m=v+((q&m|(q|m)&~n)>>>31|0)|0;k===g?g=r(n,m):(q=h.LN,h=h.KN,g=EA(k,g,c),(m===h?n>>>0>q>>>0:m>h)&&BA(dh(),c),q=65535&n,h=n>>>16|0,v=65535&f,k=f>>>16|0,n=Math.imul(q,v),v=Math.imul(h,v),w=Math.imul(q,k),q=n+((v+w|0)<<16)|0,n=(n>>>16|0)+w|0,h=((Math.imul(m,f)+Math.imul(h,k)|0)+(n>>>16|0)| +0)+(((65535&n)+v|0)>>>16|0)|0,f=q+g|0,h=h+((q&g|(q|g)&~f)>>>31|0)|0,0===h&&f>>>0>>0&&BA(dh(),c),g=r(f,h))}}f=g.h;g=g.l;d?(d=-f|0,f=(-g|0)+((f|d)>>31)|0,(0===f?0!==d:0g&&BA(dh(),c),c=r(f,g));a.vd=c.h;a.kd=c.l;a.Od=AB(zB(),a.vd,a.kd)}else ED(a,ke(c));FD(a,b);return new RC(a,b)}z(zD,"scala.math.BigDecimal$",{M3:1,b:1});var AD;function GD(){AD||(AD=new zD);return AD}function HD(a,b){var c=b-a.gp|0,d=a.pC.a[c];null===d&&(d=ID(new JD,null,b,b>>31),a.pC.a[c]=d);return d} +function KD(){this.qI=this.qC=null;this.cw=this.gp=0;this.rI=this.pC=null;LD=this;this.qC=Aj(Zi(),0,-2147483648);this.qI=ID(new JD,this.qC,0,-2147483648);this.gp=-1024;this.cw=1024;this.pC=new (B(MD).P)(1+(this.cw-this.gp|0)|0);this.rI=Aj(Zi(),-1,-1)}KD.prototype=new t;KD.prototype.constructor=KD;function ND(a,b){return a.gp<=b&&b<=a.cw?HD(a,b):OD(a,b,b>>31)} +function OD(a,b,c){var d=a.gp,f=d>>31;(f===c?d>>>0<=b>>>0:f>31,d=c===f?b>>>0<=d>>>0:c=Wi(gj(),b)){var c=b.uf(),d=c.h;c=c.l;var f=a.gp,g=f>>31;(g===c?f>>>0<=d>>>0:g>31,f=c===g?d>>>0<=f>>>0:cpE(b,this.kN)))};z(oE,"scodec.Decoder$$anon$1",{t_:1,lb:1});function qE(a,b){this.vG=null;this.lN=a;if(null===b)throw Nr();this.vG=b}qE.prototype=new t;qE.prototype.constructor=qE;qE.prototype.ka=function(a){return this.vG.ka(a).Lv(new G(b=>this.lN.g(b.Zf).ka(b.$g)))};z(qE,"scodec.Decoder$$anon$2",{u_:1,lb:1});function rE(a,b){this.wG=null;this.mN=a;if(null===b)throw Nr();this.wG=b}rE.prototype=new t;rE.prototype.constructor=rE; +rE.prototype.ka=function(a){return this.wG.ka(a).Lv(new G(b=>this.mN.g(b.Zf).jj(new G((c=>d=>new Rx(d,c.$g))(b)))))};z(rE,"scodec.Decoder$$anon$3",{v_:1,lb:1});function sE(a){this.xG=a}sE.prototype=new t;sE.prototype.constructor=sE;sE.prototype.ka=function(a){return this.xG.jj(new G(b=>new Rx(b,a)))};sE.prototype.f=function(){return"constAttempt("+this.xG+")"};z(sE,"scodec.Decoder$$anon$9",{w_:1,lb:1});function tE(){}tE.prototype=new t;tE.prototype.constructor=tE; +function zo(){uE();return new vE(new G(a=>a),new G(a=>a))}function wE(){uE();var a=Gp();return new vE(new G(b=>a.Rc(b)),new G(b=>Zw(bx(),b)))}function xE(){uE();var a=yE();return new vE(new G(b=>{b=$w(bx(),b,Dw());return a.Rc(b)}),new G(b=>Zw(bx(),b).Oo))}z(tE,"scodec.Iso$",{C_:1,D_:1});var zE;function uE(){zE||(zE=new tE)}function vE(a,b){this.oN=a;this.nN=b}vE.prototype=new t;vE.prototype.constructor=vE;z(vE,"scodec.IsoLowPriority$$anon$2",{E_:1,B_:1});function AE(a){this.qN=a}AE.prototype=new iy; +AE.prototype.constructor=AE;z(AE,"scodec.bits.ByteVector$$anon$13",{X_:1,j0:1});function BE(a,b,c){this.BG=null;this.rN=a;this.sN=b;if(null===c)throw Nr();this.BG=c}BE.prototype=new ey;BE.prototype.constructor=BE;BE.prototype.fi=function(a,b){var c=CE(this.BG,a,b);a=CE(this.sN,a,b);return this.rN.qN.Ca(c,a)|0};z(BE,"scodec.bits.ByteVector$$anon$16",{Y_:1,Oq:1});function DE(a){this.tN=a}DE.prototype=new ey;DE.prototype.constructor=DE;DE.prototype.fi=function(a,b){return this.tN.g(r(a,b))|0}; +z(DE,"scodec.bits.ByteVector$$anon$19",{Z_:1,Oq:1});function EE(a){this.uN=a}EE.prototype=new ey;EE.prototype.constructor=EE;EE.prototype.fi=function(){return this.uN};z(EE,"scodec.bits.ByteVector$$anon$21",{$_:1,Oq:1});function FE(a,b,c,d){this.xN=a;this.wN=b;this.vN=c;if(null===d)throw Nr();}FE.prototype=new gy;FE.prototype.constructor=FE;z(FE,"scodec.bits.ByteVector$$anon$8",{a0:1,i0:1});function GE(a){this.Wx=a}GE.prototype=new ey;GE.prototype.constructor=GE;GE.prototype.fi=function(a){return this.Wx.a[a]}; +GE.prototype.ey=function(a,b,c){return this.wB(a,b,c).xB()};GE.prototype.wB=function(a,b,c){var d=this.Wx;d=nk(qk(),d,d.a.length,a,c);return 0===(a|b)&&c===this.Wx.a.length?d:d.zz()};GE.prototype.hy=function(a,b,c,d,f){this.Wx.G(c,a,b,f)};z(GE,"scodec.bits.ByteVector$AtArray",{c0:1,Oq:1});function np(a){this.Xx=a}np.prototype=new ey;np.prototype.constructor=np;np.prototype.fi=function(a){return this.Xx.CH(a)};np.prototype.hy=function(a,b,c,d,f){d=this.Xx.jy();Zj.prototype.yb.call(d,c);d.AH(a,b,f)}; +np.prototype.ey=function(a,b,c){return this.wB(a,b,c).xB()};np.prototype.wB=function(a,b,c){var d=this.Xx.jy();return 0===(a|b)&&0===d.L&&c===(d.aa-d.L|0)?d:(Zj.prototype.yb.call(d,a),Zj.prototype.Zo.call(d,a+c|0),d.zz())};z(np,"scodec.bits.ByteVector$AtByteBuffer",{d0:1,Oq:1});function HE(){}HE.prototype=new ey;HE.prototype.constructor=HE;HE.prototype.fi=function(){throw Qi("empty view");};HE.prototype.ey=function(){ck||(ck=new bk);var a=new eb(0);return nk(qk(),a,a.a.length,0,a.a.length)}; +z(HE,"scodec.bits.ByteVector$AtEmpty$",{e0:1,Oq:1});var IE;function JE(){IE||(IE=new HE);return IE}function KE(){}KE.prototype=new ky;KE.prototype.constructor=KE;function LE(){no();var a=no().jH;return new ME(a,tz(Nv(),ow(Qg(),new (B(qs).P)([]))),new NE)}z(KE,"scodec.codecs.codecs$package$$anon$22",{U0:1,D0:1});function NE(){}NE.prototype=new t;NE.prototype.constructor=NE;NE.prototype.ld=function(a){return pr(this,a)};NE.prototype.f=function(){return"\x3cfunction1\x3e"};NE.prototype.g=function(a){return a}; +z(NE,"scodec.codecs.codecs$package$$anon$23",{V0:1,U:1});function jz(){this.Op=null}jz.prototype=new gz;jz.prototype.constructor=jz;function OE(){}OE.prototype=jz.prototype;jz.prototype.Kh=function(){return PE(this)};function hz(){}hz.prototype=new gz;hz.prototype.constructor=hz;function QE(){}QE.prototype=hz.prototype;hz.prototype.Kh=function(){return PE(this)};function RE(){}RE.prototype=new gz;RE.prototype.constructor=RE;function SE(){}SE.prototype=RE.prototype;function TE(){}TE.prototype=new nz; +TE.prototype.constructor=TE;function UE(){}UE.prototype=TE.prototype;function VE(a){if(null===a)throw Nr();}VE.prototype=new t;VE.prototype.constructor=VE;z(VE,"cats.EvalInstances$$anon$9",{hQ:1,b:1,JJ:1});function ve(a,b){this.Ww=a;this.Xw=b}ve.prototype=new t;ve.prototype.constructor=ve;z(ve,"cats.Traverse$ToTraverseOps$$anon$3",{GQ:1,b:1,EQ:1});function WE(){}WE.prototype=new ZD;WE.prototype.constructor=WE;WE.prototype.g=function(){return this};z(WE,"cats.data.Chain$$anon$2",{cR:1,Az:1,U:1}); +function lc(){}lc.prototype=new rz;lc.prototype.constructor=lc;function XE(){}XE.prototype=lc.prototype;function YE(){}YE.prototype=new Hz;YE.prototype.constructor=YE;function ZE(){}ZE.prototype=YE.prototype;function $E(){}$E.prototype=new Jz;$E.prototype.constructor=$E;function aF(){}aF.prototype=$E.prototype;function bF(){}bF.prototype=new Lz;bF.prototype.constructor=bF;function cF(){}cF.prototype=bF.prototype;function dF(){this.Oz=null}dF.prototype=new Nz;dF.prototype.constructor=dF; +function eF(){}eF.prototype=dF.prototype;function fF(){new gF(this)}fF.prototype=new t;fF.prototype.constructor=fF;z(fF,"cats.instances.FunctionInstancesBinCompat0$$anon$1",{eS:1,b:1,UP:1});function gF(a){this.OD=null;if(null===a)throw Nr();this.OD=a}gF.prototype=new t;gF.prototype.constructor=gF;gF.prototype.f=function(){return"Deferred"};gF.prototype.Rc=function(a){return new hF(this.OD,a.c(0))};z(gF,"cats.instances.FunctionInstancesBinCompat0$$anon$1$Deferred$",{gS:1,Wb:1,pd:1}); +function ni(){this.dE=null;mi=this;this.Kv(new iF);this.dE=new yz}ni.prototype=new t;ni.prototype.constructor=ni;ni.prototype.Kv=function(){};z(ni,"cats.instances.package$string$",{DS:1,xE:1,ZD:1});var mi;function Pc(){}Pc.prototype=new t;Pc.prototype.constructor=Pc;z(Pc,"cats.kernel.Comparison$",{FS:1,Wb:1,ep:1});var Oc;function Cz(){}Cz.prototype=new t;Cz.prototype.constructor=Cz;Cz.prototype.Ud=function(a,b){return V(W(),a,b)};z(Cz,"cats.kernel.Eq$$anon$6",{HS:1,b:1,ee:1});function Rc(){} +Rc.prototype=new Tz;Rc.prototype.constructor=Rc;z(Rc,"cats.kernel.Hash$",{KS:1,MS:1,fE:1});var Qc;function jF(){}jF.prototype=new Vz;jF.prototype.constructor=jF;function kF(){}kF.prototype=jF.prototype;function Gc(){Ic()}Gc.prototype=new t;Gc.prototype.constructor=Gc;z(Gc,"cats.package$$anon$2",{gU:1,b:1,JJ:1});function Hc(){Ic()}Hc.prototype=new t;Hc.prototype.constructor=Hc;z(Hc,"cats.package$$anon$3",{hU:1,b:1,Ki:1});function gi(){}gi.prototype=new t;gi.prototype.constructor=gi; +gi.prototype.$o=function(a){return this.Cy().kl(a)};gi.prototype.Cy=function(){return new aA(new Jh)};z(gi,"cats.parse.Accumulator0$$anon$10",{lU:1,Yp:1,BE:1});function Xc(){}Xc.prototype=new t;Xc.prototype.constructor=Xc;Xc.prototype.$o=function(a){return $z(new Xz,$a(a))};Xc.prototype.Cy=function(){return new Xz};z(Xc,"cats.parse.Accumulator0$$anon$7",{mU:1,Yp:1,BE:1});function Yc(){}Yc.prototype=new t;Yc.prototype.constructor=Yc;Yc.prototype.$o=function(a){return fd().Uz.kl(a)}; +Yc.prototype.Cy=function(){return fd().Uz};z(Yc,"cats.parse.Accumulator0$$anon$9",{nU:1,Yp:1,BE:1});function lF(){this.FE=this.EE=null;mF=this;this.EE=(vd(),new wd(0,0,0));this.FE=new nF;Og(this.FE)}lF.prototype=new t;lF.prototype.constructor=lF;lF.prototype.Rc=function(a){return new wd(a.c(0)|0,a.c(1)|0,a.c(2)|0)};z(lF,"cats.parse.Caret$",{vU:1,Wb:1,pd:1});var mF;function vd(){mF||(mF=new lF);return mF} +function oF(a,b,c){if(null===b)throw Nr();return b.Gk?b.Hk:eE(b,c.g(new Ff(new pg(()=>b.Gk?b.Hk:oF(a,b,c)))))}var qF=function pF(a,b,c,d,f){for(var h=d;;){if(f>=b.a.length||0>f)return oi(),new Cm(new C(p(c),p(h)),N());d=b.a[f];if(d===(1+h|0)||d===h)f=1+f|0,h=d;else return c=new C(p(c),p(h)),a=pF(a,b,d,d,1+f|0),b=c,oi(),new Cm(b,new J(a.mc,a.fc))}}; +function rF(){this.Zz=this.Wf=this.nx=this.YE=null;sF=this;this.YE=new J((I(),new kf(F())),N());this.nx=new te;this.Wf=new kf(void 0);for(var a=Kv().Da(),b=new uq(32,1,126,!1);b.xk;){var c=65535&vq(b);c=Pd(this,new J(p(c),N()));a.Ia(Wd(I(),c))}b=a.Sa();if(0<=b.q())a=new (B(rn).P)(b.q()),b.hc(a,0,2147483647);else{a=[];for(b=b.i();b.n();)c=b.j(),a.push(null===c?null:c);a=new (B(rn).P)(a)}this.Zz=a}rF.prototype=new t;rF.prototype.constructor=rF; +function ci(a,b){return 1===b.length?Xd(a,b.charCodeAt(0)):new Ve(b)}function Mg(a,b){a=qe(He(),b);b=zf(He(),a);if(b instanceof E)return b=b.zc,wh(I(),a,b);if(F()===b)return a;throw new D(b);}function th(a,b){a=Oe(He(),b);b=zf(He(),a);if(b instanceof E)return a.yB(b.zc);if(F()===b)return a;throw new D(b);}function Tn(a,b){b=tF(b.ub());if(N().d(b))return a.nx;if(b instanceof J){var c=b.Z,d=b.Fa;if(N().d(c))return a=ci(a,d),Yd(I(),a)}return new mf(Ng().gi(b,(ue(),Og(ue().Yg))))} +function Sd(a,b,c){return new Ne(new J(new Ef(b,1,2147483647,c),new J(new kf(c.Cy().Hm()),N())))}function Td(a,b,c){return new Ef(b,1,2147483647,c)}function cg(a,b,c){return b instanceof Me?Ud(0,b,c):b instanceof kf?c.Nm(new uF(b.mf)):c instanceof Me?$d(a,b,c):c instanceof kf?b.Nm(new vF(c.mf)):new xf(b,c)}function Ud(a,b,c){return b instanceof te?b:c instanceof kf?(a=new vF(c.mf),le(I(),b,a)):new Cf(b,c)} +function $d(a,b,c){if(b instanceof Me)return Ud(0,b,c);if(b instanceof kf)return a=new uF(b.mf),le(I(),c,a);if(b instanceof Ne){var d=b.Ge,f=d.tf();return wf(He(),f)?Vd($d(a,Le(He(),d.$j()),c),$d(a,f,c)):new Cf(b,c)}return b instanceof nf?(d=b.Yl,c=$d(a,b.Xi,c),a=new wF(d),le(I(),c,a)):b instanceof xf&&(b.Uf instanceof Ne||b.Uf instanceof nf||b.Uf instanceof xf)?(c=$d(a,b.Th,$d(a,b.Uf,c)),a=new xF,le(I(),c,a)):new Cf(b,c)} +function dg(a,b,c){b instanceof Me?c=fA(0,b,c):b instanceof kf?c=c.Nm(new uF(b.mf)):c instanceof Me?b instanceof te?c=b:b instanceof kf?(a=new uF(b.mf),c=le(I(),c,a)):c=new qf(b,c):c=c instanceof kf?b.Nm(new vF(c.mf)):new pf(b,c);return c}function fA(a,b,c){return b instanceof te?b:c instanceof kf?(a=new vF(c.mf),le(I(),b,a)):new qf(b,c)} +function uh(a,b,c){if(b instanceof Me)return le(0,b,c);a=zf(He(),b);if(a instanceof E)return b.yB(c.g(a.zc));if(F()===a)return b instanceof nf?(a=b.Yl,new nf(b.Xi,yF(zF(AF(),a),c))):new nf(b,c);throw new D(a);}function le(a,b,c){a=zf(He(),b);if(a instanceof E)return c=c.g(a.zc),wh(I(),b,c);if(F()===a)return b instanceof te?b:b instanceof of?(a=b.Zl,new of(b.Pk,yF(zF(AF(),a),c))):new of(b,c);throw new D(a);} +function eA(a,b){a=I().nx;var c=zf(He(),b);if(c instanceof E){var d=c.zc;if(d instanceof Eg)return a=d.ib,wh(I(),b,a);if(d instanceof Dg){var f=d.lj;return fi(b,a.Nm(new G(g=>g.g(f))))}}if(F()===c)return b=new Hf(b,a),a=new G(g=>{if(g instanceof Dg){var h=g.lj;if(null!==h)return h.V.g(h.Y)}if(g instanceof Eg)return g.ib;throw new D(g);}),le(I(),b,a);throw new D(c);}function qn(a,b){var c=new dE;return c.Gk?c.Hk:oF(a,c,b)} +function Pd(a,b){if(b.e())return a.nx;if(b instanceof BF){Yg||(Yg=new Xg);var c=1===$a(b.$a)?new E(new C(b.oc,b.Id)):F();if(!c.e()){a=c.oa();b=$a(a.Y);c=$a(a.V);if(0===(b|65535^c))return Te();Ig();var d=1+(c-b|0)|0;a=CF(d);if(0>d)throw O(new P,"toIndex \x3c 0: "+d);if(0>d)throw O(new P,"fromIndex: 0 \x3e toIndex: "+d);if(0!==d){DF(a,1+((d-1|0)>>5)|0);var f=(d-1|0)>>5;d=-1>>>(32-(31&d)|0)|0;if(0===f)f=a.Tb,f.a[0]^=-1&d;else{var g=a.Tb;g.a[0]^=-1;g=a.Tb;g.a[f]^=d;for(d=1;d>5)|0;DF(k,m);k=k.Tb;m=m-1|0;k.a[m]|=1<<(31&h);g=1+g|0}return new Ue(c,d,a)} +function ei(){var a=I(),b=Qd(new Rd(65),p(70));return Pd(a,b.Cb(new G(c=>{c=$a(c);return new J(p(lA(qA(),c)),new J(p(nA(qA(),c)),N()))})))}function Xd(a,b){var c=b-32|0;if(0<=c&&c(1+c.Wi|0))a=new J(f,a),b=Rb(b,c);else{d=c.Vn;var g=c.Vi;c=(ue(),new wi(p(c.Wi),ue().Pz));f=p(f.Wi);c=(Ic(),c.hF).Pe(c.iF,f);a=new J(new GF(d,g,$a(c)),a)}continue}}a=Yb(Ob(),re(a));return jc(Ob(),b,a)}}function HF(){this.NE=this.ME=null;IF=this;this.ME=new JF;this.NE=new KF}HF.prototype=new t;HF.prototype.constructor=HF; +function LF(a,b){return FF(MF(b,new G(c=>p(c.Vi)))).ub()}function NF(a,b,c){for(a=c;;){if(N().d(b))return a;if(b instanceof J)a=new pe(b.Fa,a),b=b.Z;else throw new D(b);}} +function En(a,b){var c=N();ue();var d=Og(OF()),f=null;f=PF(d);for(b=new J(b.mc,b.fc);!b.e();){var g=b.u(),h=new C(g.kj(),g.Fm());var k=f;k=wu(Bu(),k.zb,h,k.Ob);if(k instanceof E)k.zc.Ia(g);else if(F()===k)f=QF(f,h,Kh(new Jh,g));else throw new D(k);b=b.A()}d=new RF(d);for(f=new SF(f.zb,F(),f.Ob);f.n();){b=f.j();if(null!==b)g=b.V,b=b.Y,g=Fn(oi(),g.Sa());else throw new D(b);d.Aj=hD(d,d.Aj,b,g)}d=TF(d);c=rg(c,new td(new SF(d.zb,F(),d.Ob),new G(m=>{if(null!==m){var n=m.Y;if(null!==n){n=n.V;var q=m.V,u= +new Jh,v=new Jh;m=new Jh;var w=new Jh;for(q=new J(q.mc,q.fc);!q.e();){var A;b:for(on(),A=q.u();;){var K=A;if(K instanceof pe)A=K.Gj;else break b}A instanceof GF?Kh(u,A):A instanceof qg?Kh(v,A):A instanceof ig?Kh(m,A):Kh(w,A);q=q.A()}u=LF(on(),u.ub());on();v=v.ub();if(v.e())v=F();else{q=Ng().Xv((ue(),Og(ue().Yg)));for(A=v;!A.e();)q.gc(A.u().Nk),A=A.A();v=new E(new qg(v.u().Wn,q.Sa().ub()))}w=w.ub();for(v=rg(u,v);!w.e();)v=new J(w.u(),v),w=w.A();w=v;v=w.e()?m.ub():w;if(n.e())return v;n=re(n);if(v=== +N())return N();m=v.u();m=NF(on(),n,m);w=m=new J(m,N());for(v=v.A();v!==N();)u=v.u(),u=NF(on(),n,u),u=new J(u,N()),w=w.Z=u,v=v.A();return m}}throw new D(m);})));return Fn(oi(),tF(c).hd((ue(),Og(a.ME))))}z(HF,"cats.parse.Parser$Expectation$",{DU:1,Wb:1,ep:1});var IF;function on(){IF||(IF=new HF);return IF}function qb(a){this.ba=a}qb.prototype=new t;qb.prototype.constructor=qb;qb.prototype.f=function(){return(this.ba.EH?"interface ":this.ba.Qv?"":"class ")+this.ba.name}; +z(qb,"java.lang.Class",{F1:1,b:1,ak:1});function UF(){this.JH=null;this.sy=0}UF.prototype=new t;UF.prototype.constructor=UF;function VF(){}VF.prototype=UF.prototype;UF.prototype.f=function(){return this.JH};UF.prototype.d=function(a){return this===a};UF.prototype.p=function(){return Va(this)};UF.prototype.$f=function(a){var b=this.sy;a=a.sy;return b===a?0:ba.Tb.a.length){H();var c=a.Tb.a.length<<1;a.Tb=ql(0,a.Tb,b>c?b:c)}}function bh(a){for(var b=a.Tb.a.length-1|0;0<=b&&0===a.Tb.a[b];)b=b-1|0;return 1+b|0}function EF(a){if(0>a)throw O(new P,"bitIndex \x3c 0: "+a);}function CF(a){var b=new fG;if(0>a)throw new ml;a=new y((31+a|0)>>5);b.Tb=a;return b} +function fG(){this.Tb=null}fG.prototype=new t;fG.prototype.constructor=fG;function gG(a,b){EF(b);var c=b>>5;return cb)throw O(new P,"fromIndex \x3c 0: "+b);if(b>=a.Tb.a.length<<5)return-1;var c=b>>5;if(0!==a.Tb.a[c])for(b&=31;32>b;){if(0!==(a.Tb.a[c]&1<b;){if(0!==(a.Tb.a[c]&1<>>16|0;var h=65535&f;f=f>>>16|0;var k=Math.imul(g,h);h=Math.imul(d,h);var m=Math.imul(g,f);g=k+((h+m|0)<<16)|0;k=(k>>>16|0)+m|0;d=(Math.imul(d,f)+(k>>>16|0)|0)+(((65535&k)+h|0)>>>16|0)|0;b^=d;a^=g;c=1+c|0}return b^a}; +fG.prototype.d=function(a){var b;if(b=a instanceof fG)a:{var c=this.Tb.a.length,d=a.Tb.a.length;b=c<=d?this:a;var f=c<=d?c:d;a=c>d?this:a;c=c>d?c:d;for(d=0;dg;){var h=g;0!==(this.Tb.a[f]&1<>>0).toString(16),g="00000000".substring(f.length);b=((65535&(a>>>16|0|b<<16))>>>0).toString(16);var h="0000".substring(b.length);a=((65535&a)>>>0).toString(16);var k="0000".substring(a.length),m=((65535&(d>>>16|0))>>>0).toString(16),n="0000".substring(m.length);d=((65535&d)>>>0).toString(16);var q="0000".substring(d.length);c=(c>>>0).toString(16);return""+g+f+"-"+(""+h+b)+"-"+(""+k+a)+"-"+(""+n+m)+"-"+(""+q+d)+(""+"00000000".substring(c.length)+ +c)};hG.prototype.p=function(){return this.Yo^this.Xo^this.Wo^this.Vo};hG.prototype.d=function(a){return a instanceof hG?0===(this.Yo^a.Yo|this.Xo^a.Xo)?0===(this.Wo^a.Wo|this.Vo^a.Vo):!1:!1};hG.prototype.$f=function(a){var b=this.Yo,c=this.Xo,d=a.Yo,f=a.Xo;0!==(b^d|c^f)?a=(c===f?b>>>0>>0:c>>0>>0:cnew Dg(g));jo||(jo=new io);a=vG(a,0,b,c,jo.WF);b=new wG;c=new G(g=>new Eg(g));Eo||(Eo=new Co);a=cy(0,vG(a,1,b,c,Eo.ZF),new G(g=>{gx||(gx=new fx);if(g instanceof Eg)g=g.ib;else if(g instanceof Dg)g=g.lj;else throw new D(g);return g}),new G(g=>g instanceof Yq?new Tp(new Eg(g)):g instanceof gC?new Tp(new Dg(g)):new Vp(cz(new dz,"Index was neither FrequencyIndex or PositionalIndex"))));a=new to(new G(g=>rg(N(),g)),new G(g=>Bp(Cp(), +g)),Fp(no(),no().qf,wo(new yo(no().No,a),zo())));b=lo(mo(),no().qf,Xy(no(),no().qf,no().kH),new ro(l(fa)));b=new to(new G(g=>rg(N(),g)),new G(g=>Bp(Cp(),g)),Fp(no(),no().qf,wo(new yo(no().No,b),zo())));c=new G(g=>new uo(g.ko,g.$k,g.Bq));var d=new G(g=>{if(null!==g){var h=g.bh,k=g.ch;g=g.dh;pp();return new xG(h,k,g)}throw new D(g);}),f=wo;Ep||(Ep=new Dp);this.YF=new to(c,d,f(new xo(a,new yo(Ep.bG,b)),zo()))}sG.prototype=new t;sG.prototype.constructor=sG; +sG.prototype.Rc=function(a){return new xG(a.c(0),a.c(1),a.c(2))};z(sG,"pink.cozydev.protosearch.MultiIndex$",{jZ:1,Wb:1,pd:1});var tG;function pp(){tG||(tG=new sG);return tG}function Io(a,b,c){this.al=a;this.lm=b;this.BA=c}Io.prototype=new lC;Io.prototype.constructor=Io;z(Io,"pink.cozydev.protosearch.PositionalIndex$$anon$1",{oZ:1,mZ:1,PM:1});function Jp(){}Jp.prototype=new t;Jp.prototype.constructor=Jp;Jp.prototype.f=function(){return"\x3cfunction3\x3e"};Jp.prototype.cy=function(){return 0}; +Jp.prototype.dy=function(){};z(Jp,"pink.cozydev.protosearch.ScoreFunction$$anon$1",{uZ:1,tD:1,RM:1});function Kp(){}Kp.prototype=new t;Kp.prototype.constructor=Kp;Kp.prototype.f=function(){return"\x3cfunction3\x3e"};Kp.prototype.cy=function(a,b){return Math.fround(1/b*+Math.log(1+a))};Kp.prototype.dy=function(a,b,c){this.cy(a|0,c|0)};z(Kp,"pink.cozydev.protosearch.ScoreFunction$$anon$2",{vZ:1,tD:1,RM:1});function No(){Mo=this}No.prototype=new t;No.prototype.constructor=No; +No.prototype.Rc=function(a){return new Oo(a.c(0),a.c(1))};z(No,"pink.cozydev.protosearch.Searcher$",{AZ:1,Wb:1,pd:1});var Mo;function yG(){this.jG=null;zG=this;this.jG=wo((no(),new qo("lowerCase",no().Mo)),xE())}yG.prototype=new t;yG.prototype.constructor=yG;yG.prototype.Rc=function(a){return new AG(!!a.c(0))};z(yG,"pink.cozydev.protosearch.analysis.Analyzer$",{CZ:1,Wb:1,pd:1});var zG;function yE(){zG||(zG=new yG);return zG}function zp(){}zp.prototype=new t;zp.prototype.constructor=zp; +zp.prototype.Rc=function(a){return new Ap(a.c(0),a.c(1))};z(zp,"pink.cozydev.protosearch.analysis.QueryAnalyzer$",{EZ:1,Wb:1,pd:1});var yp;function Aq(a,b){this.qG=this.nm=this.Zh=0;this.el=null;this.bN=a;if(null===b)throw Nr();this.el=b;this.Zh=0;this.nm=2;this.qG=b.Sj.a.length/3|0}Aq.prototype=new uC;Aq.prototype.constructor=Aq;e=Aq.prototype;e.Cd=function(){return this.el.Sj.a[this.Zh]};function oC(a){return a.el.Sj.a[1+a.Zh|0]}e.Ag=function(){return this.bN.cy(oC(this),this.qG)}; +function pC(a){return a.el.Sj.a[a.nm]}e.f=function(){var a="PositionalPostingsReader(i\x3d"+this.Zh+", posIndex\x3d"+this.nm+", currentDocId\x3d"+this.Cd()+", currentPosition\x3d"+pC(this)+"\n positions\x3d",b=N();Ah();var c=this.el.Sj;return a+rg(b,null!==c?new BG(c):null)+")"};e.n=function(){return(2+this.Zh|0)b)return 1;var c=a.E();if(0<=c)return c===b?0:cc?-1:c<=b?0:c-b|0;return 0===c?Lv().da:new rH(a,b,c)} +function sH(){this.da=null;tH=this;this.da=new uH}sH.prototype=new t;sH.prototype.constructor=sH;sH.prototype.Da=function(){return new vH};sH.prototype.mb=function(a){return a.i()};z(sH,"scala.collection.Iterator$",{v5:1,Gd:1,b:1});var tH;function Lv(){tH||(tH=new sH);return tH}function wH(a){var b=Cp();a.Vm=b}function xH(){this.Vm=null}xH.prototype=new t;xH.prototype.constructor=xH;function yH(){}yH.prototype=xH.prototype;xH.prototype.mb=function(a){return this.Vm.mb(a)};xH.prototype.Sd=function(){return this.Vm.Sd()}; +xH.prototype.Da=function(){return this.Vm.Da()};function zH(){}zH.prototype=new t;zH.prototype.constructor=zH;function AH(a,b){return b&&b.$classData&&b.$classData.wb.Bc?b:BH(b)?new CH(new Rh((c=>()=>c.i())(b))):DH(new EH,FH(Q(),b))}zH.prototype.Da=function(){return new GH((cr(),new HH),new Zo(a=>AH(IH(),a)))};zH.prototype.mb=function(a){return AH(0,a)};z(zH,"scala.collection.View$",{$5:1,Gd:1,b:1});var JH;function IH(){JH||(JH=new zH);return JH} +function ct(a,b,c,d,f,g){this.Yb=a;this.Cc=b;this.zd=c;this.Mg=d;this.Ae=f;this.gg=g}ct.prototype=new PC;ct.prototype.constructor=ct;e=ct.prototype;e.X=function(){return this.Ae};e.Bb=function(){return this.gg};e.gj=function(a){return this.zd.a[a<<1]};e.ii=function(a){return this.zd.a[1+(a<<1)|0]};e.MB=function(a){return new C(this.zd.a[a<<1],this.zd.a[1+(a<<1)|0])};e.hb=function(a){return this.Mg.a[a]};e.gh=function(a){return this.zd.a[(this.zd.a.length-1|0)-a|0]}; +e.sB=function(a,b,c,d){var f=nt(R(),c,d),g=ot(R(),f);if(0!==(this.Yb&g)){if(b=qt(R(),this.Yb,f,g),V(W(),a,this.gj(b)))return this.ii(b)}else if(0!==(this.Cc&g))return this.gh(qt(R(),this.Cc,f,g)).sB(a,b,c,5+d|0);throw new Cs("key not found: "+a);};e.ky=function(a,b,c,d){var f=nt(R(),c,d),g=ot(R(),f);return 0!==(this.Yb&g)?(b=qt(R(),this.Yb,f,g),V(W(),a,this.gj(b))?new E(this.ii(b)):F()):0!==(this.Cc&g)?this.gh(qt(R(),this.Cc,f,g)).ky(a,b,c,5+d|0):F()}; +e.LB=function(a,b,c,d,f){var g=nt(R(),c,d),h=ot(R(),g);return 0!==(this.Yb&h)?(b=qt(R(),this.Yb,g,h),V(W(),a,this.gj(b))?this.ii(b):f.za()):0!==(this.Cc&h)?this.gh(qt(R(),this.Cc,g,h)).LB(a,b,c,5+d|0,f):f.za()};e.CB=function(a,b,c,d){var f=nt(R(),c,d),g=ot(R(),f);return 0!==(this.Yb&g)?(c=qt(R(),this.Yb,f,g),this.Mg.a[c]===b&&V(W(),a,this.gj(c))):0!==(this.Cc&g)&&this.gh(qt(R(),this.Cc,f,g)).CB(a,b,c,5+d|0)}; +function KH(a,b,c,d,f,g,h){var k=nt(R(),f,g),m=ot(R(),k);if(0!==(a.Yb&m)){var n=qt(R(),a.Yb,k,m);k=a.gj(n);var q=a.hb(n);if(q===d&&V(W(),k,b))return h?(f=a.ii(n),Object.is(k,b)&&Object.is(f,c)||(m=a.bf(m)<<1,b=a.zd,f=new x(b.a.length),b.G(0,f,0,b.a.length),f.a[1+m|0]=c,a=new ct(a.Yb,a.Cc,f,a.Mg,a.Ae,a.gg)),a):a;n=a.ii(n);h=ds(fs(),q);c=LH(a,k,n,q,h,b,c,d,f,5+g|0);f=a.bf(m);d=f<<1;g=(a.zd.a.length-2|0)-a.li(m)|0;k=a.zd;b=new x(k.a.length-1|0);k.G(0,b,0,d);k.G(2+d|0,b,d,g-d|0);b.a[g]=c;k.G(2+g|0,b, +1+g|0,(k.a.length-g|0)-2|0);f=it(a.Mg,f);return new ct(a.Yb^m,a.Cc|m,b,f,(a.Ae-1|0)+c.X()|0,(a.gg-h|0)+c.Bb()|0)}if(0!==(a.Cc&m))return k=qt(R(),a.Cc,k,m),k=a.gh(k),c=k.BJ(b,c,d,f,5+g|0,h),c!==k&&(m=(a.zd.a.length-1|0)-a.li(m)|0,b=a.zd,f=new x(b.a.length),b.G(0,f,0,b.a.length),f.a[m]=c,a=new ct(a.Yb,a.Cc,f,a.Mg,(a.Ae-k.X()|0)+c.X()|0,(a.gg-k.Bb()|0)+c.Bb()|0)),a;g=a.bf(m);k=g<<1;q=a.zd;h=new x(2+q.a.length|0);q.G(0,h,0,k);h.a[k]=b;h.a[1+k|0]=c;q.G(k,h,2+k|0,q.a.length-k|0);c=jt(a.Mg,g,d);return new ct(a.Yb| +m,a.Cc,h,c,1+a.Ae|0,a.gg+f|0)}function LH(a,b,c,d,f,g,h,k,m,n){if(32<=n)return new MH(d,f,tz(Nv(),bD(new VC,[new C(b,c),new C(g,h)])));var q=nt(R(),f,n),u=nt(R(),m,n),v=f+m|0;if(q!==u)return a=ot(R(),q)|ot(R(),u),q>>0).toString(16)};function NH(a){for(var b=a.zd.H(),c=b.a.length,d=ch(dh(),a.Yb)<<1;dd;){d=ot(R(),d);if(0!==(this.$&d))if(0!==(a.$&d))f=V(W(),this.Pa(pt(R(),this.$,d)),a.Pa(pt(R(),a.$,d)));else{var g=pt(R(),this.$,d);f=this.Pa(g);var h=a.ad(pt(R(),a.ma,d));g=this.hb(g);var k=ds(fs(),g);f=h.Wj(f,g,k,5+b|0)}else 0===(a.$&d)?(f=this.ad(pt(R(),this.ma,d)),h=a.ad(pt(R(),a.ma, +d)),f=f.rD(h,5+b|0)):f=!1;c=d^=c;d=32-Math.clz32(~d&(d-1|0))|0}return f}; +function YH(a,b,c){if(0===a.ta)return a;if(1===a.ta)return!!b.g(a.Pa(0))!==c?a:Su().Bn;if(0===a.ma){for(var d=a.$,f=32-Math.clz32(~d&(d-1|0))|0,g=32-Math.clz32(a.$)|0,h=d=0,k=0;f{if(null!==b)return a.Ca(b.Y,b.V);throw new D(b);}))};e.IB=function(a){for(var b=this.dd.i();b.n();){var c=b.j();a.dy(c.Y,c.V,this.UC)}}; +e.d=function(a){if(a instanceof MH){if(this===a)return!0;if(this.gn===a.gn&&this.dd.q()===a.dd.q()){for(var b=this.dd.i();b.n();){var c=b.j();if(null===c)throw new D(c);var d=c.V;c=dI(a,c.Y);if(0>c||!V(W(),d,a.dd.J(c).V))return!1}return!0}}return!1};e.p=function(){throw Vh("Trie nodes do not support hashing.");};e.f=function(){var a=Va(this);return xa(this)+"@"+(a>>>0).toString(16)};e.Bb=function(){return Math.imul(this.dd.q(),this.gn)};e.mH=function(){return new MH(this.UC,this.gn,this.dd)}; +e.Mv=function(a){return this.gh(a)};z(MH,"scala.collection.immutable.HashCollisionMapNode",{y6:1,fP:1,kz:1});function XH(a,b,c){this.hn=a;this.pj=b;this.nc=c;GG(Ah(),2<=this.nc.q())}XH.prototype=new mD;XH.prototype.constructor=XH;e=XH.prototype;e.Wj=function(a,b,c){return this.pj===c&&fI(this.nc,a)};e.Ow=function(a,b,c,d){return this.Wj(a,b,c,d)?this:new XH(b,c,this.nc.we(a))}; +e.Ey=function(a,b,c,d){return this.Wj(a,b,c,d)?(d=gI(this.nc,new Zo(f=>V(W(),f,a)),!0),1===d.q()?new Ru(ot(R(),nt(R(),c,0)),0,new x([d.J(0)]),new y(new Int32Array([b])),1,c):new XH(b,c,d)):this};e.Nv=function(){return!1};e.$v=function(){return 0};e.ad=function(){throw O(new P,"No sub-nodes present in hash-collision leaf node.");};e.Uo=function(){return!0};e.bp=function(){return this.nc.q()};e.Pa=function(a){return this.nc.J(a)};e.hb=function(){return this.hn};e.X=function(){return this.nc.q()}; +e.Oa=function(a){for(var b=this.nc.i();b.n();)a.g(b.j())};e.Bb=function(){return Math.imul(this.nc.q(),this.pj)};e.rD=function(a){if(this===a)return!0;if(a instanceof XH&&this.nc.q()<=a.nc.q()){a=a.nc;for(var b=!0,c=this.nc.i();b&&c.n();)b=fI(a,c.j());return b}return!1};e.FB=function(a,b){a=gI(this.nc,a,b);b=a.q();return 0===b?Su().Bn:1===b?new Ru(ot(R(),nt(R(),this.pj,0)),0,new x([a.u()]),new y(new Int32Array([this.hn])),1,this.pj):a.q()===this.nc.q()?this:new XH(this.hn,this.pj,a)}; +e.pH=function(a,b){return this.FB(new Zo(c=>a.Wj(c,this.hn,this.pj,b)),!0)};e.d=function(a){if(a instanceof XH){if(this===a)return!0;if(this.pj===a.pj&&this.nc.q()===a.nc.q()){a=a.nc;for(var b=!0,c=this.nc.i();b&&c.n();)b=fI(a,c.j());return b}}return!1};e.p=function(){throw Vh("Trie nodes do not support hashing.");}; +e.lH=function(a){if(a instanceof XH){if(a===this)return this;var b=null;for(a=a.nc.i();a.n();){var c=a.j();fI(this.nc,c)||(null===b&&(b=new hI,iI(b,this.nc)),jI(b,c))}return null===b?this:new XH(this.hn,this.pj,b.lh())}if(a instanceof Ru)throw Vh("Cannot concatenate a HashCollisionSetNode with a BitmapIndexedSetNode");throw new D(a);};e.HB=function(a){for(var b=this.nc.i();b.n();){var c=b.j();a.Ca(c,this.hn)}};e.nH=function(){return new XH(this.hn,this.pj,this.nc)};e.Mv=function(a){return this.ad(a)}; +z(XH,"scala.collection.immutable.HashCollisionSetNode",{z6:1,oP:1,kz:1});function kI(){this.sw=null;lI=this;bt||(bt=new at);this.sw=new mI(bt.bJ)}kI.prototype=new t;kI.prototype.constructor=kI;kI.prototype.Da=function(){return new nI};kI.prototype.mb=function(a){return a instanceof mI?a:oI(pI(new nI,a))};kI.prototype.Sd=function(){return this.sw};z(kI,"scala.collection.immutable.HashMap$",{B6:1,Xy:1,b:1});var lI;function Sh(){lI||(lI=new kI);return lI} +function qI(){this.qj=null;rI=this;this.qj=new sI(Su().Bn)}qI.prototype=new t;qI.prototype.constructor=qI;qI.prototype.Da=function(){return new tI};qI.prototype.mb=function(a){return a instanceof sI?a:0===a.E()?this.qj:uI(vI(new tI,a))};z(qI,"scala.collection.immutable.HashSet$",{F6:1,Gd:1,b:1});var rI;function wI(){rI||(rI=new qI);return rI}function xI(){}xI.prototype=new t;xI.prototype.constructor=xI; +function Bp(a,b){return yI(b)&&b.e()?zI():b instanceof mI||b instanceof AI||b instanceof BI||b instanceof CI||b instanceof DI?b:EI(FI(new GI,b))}xI.prototype.Da=function(){return new GI};xI.prototype.mb=function(a){return Bp(0,a)};xI.prototype.Sd=function(){return zI()};z(xI,"scala.collection.immutable.Map$",{V6:1,Xy:1,b:1});var HI;function Cp(){HI||(HI=new xI);return HI}function II(){}II.prototype=new t;II.prototype.constructor=II; +function od(a,b){return 0===b.E()?JI():b instanceof sI?b:b instanceof KI?b:b instanceof LI?b:b instanceof MI?b:b instanceof NI?b:wp(OI(new up,b))}II.prototype.Da=function(){return new up};II.prototype.mb=function(a){return od(0,a)};z(II,"scala.collection.immutable.Set$",{B7:1,Gd:1,b:1});var PI;function pd(){PI||(PI=new II);return PI}function QI(){}QI.prototype=new t;QI.prototype.constructor=QI; +function RI(a,b,c){if(b instanceof SI&&(a=b.Ob,null===c?null===a:c.d(a)))return b;if(b&&b.$classData&&b.$classData.wb.VI&&(a=b.Ob,null===c?null===a:c.d(a))){a=new SI;var d=Bu(),f=new SF(b.zb,F(),b.Ob);b=Nt(Bu(),b.zb);b=tu(d,1,b,f,32-Math.clz32(b)|0);return TI(a,b,c)}a=null;for(b=b.i();b.n();){f=b.j();if(null===f)throw new D(f);d=f.Y;f=f.V;a=yu(Bu(),a,d,f,!0,c)}return TI(new SI,a,c)}z(QI,"scala.collection.immutable.TreeMap$",{Z7:1,P5:1,b:1});var UI;function VI(){UI||(UI=new QI);return UI} +function WI(a){this.bD=a;this.Bw=null}WI.prototype=new aE;WI.prototype.constructor=WI;WI.prototype.tB=function(a,b){this.Bw=hD(this.bD,this.Bw,a,b)};WI.prototype.Ca=function(a,b){this.tB(a,b)};z(WI,"scala.collection.immutable.TreeMap$TreeMapBuilder$adder$",{a8:1,nD:1,Sw:1});function XI(a){this.tP=a;this.cD=a.Ta}XI.prototype=new ZD;XI.prototype.constructor=XI;XI.prototype.g=function(a){var b=Bu();this.cD=xt(au(b,this.cD,a,this.tP.Ba))};z(XI,"scala.collection.immutable.TreeSet$sub$1$",{e8:1,Az:1,U:1}); +function YI(a,b,c){b=b.E();-1!==b&&(c=b+c|0,a.Fc(0>c?0:c))}function ZI(){}ZI.prototype=new t;ZI.prototype.constructor=ZI;ZI.prototype.Da=function(){return new $I(16,.75)};ZI.prototype.mb=function(a){var b=a.E();return aJ(bJ(new cJ,0=a.a.length)throw iw(),Ik(new Jk,"assertion failed");}Xw.prototype=new t;Xw.prototype.constructor=Xw;e=Xw.prototype;e.x=function(){return new Z(this)};e.c=function(a){return this.Gi.a[a]};e.t=function(){return this.Gi.a.length}; +e.v=function(){return"Tuple"};e.f=function(){return Dh(Zq(Ah(),this.Gi),"(",",",")")};e.p=function(){return xh(this)};e.d=function(a){if(a instanceof Xw){if(this.Gi!==a.Gi){if(this.Gi.a.length!==a.Gi.a.length)return!1;for(var b=0;bb.nN.g(c)),new G(c=>b.oN.g(c)),a)}function eq(a,b){yC();return new to(new G(c=>new C(void 0,c)),new G(c=>c.V),new yo(a,b))}function AJ(){}AJ.prototype=new t;AJ.prototype.constructor=AJ;z(AJ,"scodec.Codec$",{j_:1,y_:1,x_:1});var BJ;function CJ(){BJ||(BJ=new AJ);return BJ} +function DJ(){this.qo=null;EJ=this;this.qo=(FJ(),new GJ(0,0,F()))}DJ.prototype=new t;DJ.prototype.constructor=DJ;function HJ(a,b,c){FJ();return new GJ(b,c,new E(r(b,c)))}function IJ(a){FJ();FJ();return new GJ(8,0,new E(r(a,0)))} +function JJ(a,b){return b.e()?HJ(FJ(),0,0):b.rl(new Qb((c,d)=>{FJ();var f=c.te,g=c.se,h=d.te,k=d.se;(g===k?f>>>0>>0:g>>0>d>>>0:k>h)?(d=c,c=k):c=h,d=new E(r(d,c))));return new GJ(f,g,d)}))}DJ.prototype.Rc=function(a){var b=ab(a.c(0));return new GJ(b.h,b.l,a.c(1))};z(DJ,"scodec.SizeBound$",{H_:1,Wb:1,pd:1});var EJ;function FJ(){EJ||(EJ=new DJ);return EJ}function KJ(){} +KJ.prototype=new t;KJ.prototype.constructor=KJ;function LJ(){}LJ.prototype=KJ.prototype;function MJ(){this.Pq=null;NJ=this;this.Pq=new lp((kp(),new mp(JE(),0,0,0,0)))}MJ.prototype=new t;MJ.prototype.constructor=MJ;function Zy(a,b,c){var d=Mh(Nh(),l(Ab),new y(new Int32Array([b.q()]))),f=new er(0);b.Oa(new G(g=>{d.a[f.lc]=c.Nf(g)<<24>>24;f.lc=1+f.lc|0}));return OJ(0,d)}function OJ(a,b){kp();a=b.a.length;return new lp(new mp(new GE(b),0,0,a,a>>31))} +function PJ(a,b,c){return new lp((kp(),new mp(new DE(a),0,0,b,c)))}function QJ(a,b,c){gp();c=Yy().Nf(c)<<24>>24;return new lp((kp(),new mp(new EE(c),0,0,a,b)))}function RJ(a,b,c){if(0===c?2147483647>=b>>>0:0>c)return b;throw Qi("size must be \x3c\x3d Int.MaxValue but is "+da(b,c));}z(MJ,"scodec.bits.ByteVector$",{W_:1,n0:1,m0:1});var NJ;function gp(){NJ||(NJ=new MJ);return NJ}function SJ(){this.$A=null;TJ=this;this.$A=(kp(),new mp(JE(),0,0,0,0))}SJ.prototype=new t;SJ.prototype.constructor=SJ; +SJ.prototype.Rc=function(a){var b=a.c(0),c=ab(a.c(1));a=ab(a.c(2));return new mp(b,c.h,c.l,a.h,a.l)};z(SJ,"scodec.bits.ByteVector$View$",{l0:1,Wb:1,pd:1});var TJ;function kp(){TJ||(TJ=new SJ);return TJ}function UJ(a){this.DJ=a}UJ.prototype=new SE;UJ.prototype.constructor=UJ;UJ.prototype.Kh=function(){return this.DJ.za()};z(UJ,"cats.Always",{OP:1,ED:1,Ll:1,b:1});function VJ(){new VE(this);WJ=this}VJ.prototype=new UE;VJ.prototype.constructor=VJ; +function PE(a){Ke();a:for(var b=new XJ(yC().hI);;){var c=a;if(c instanceof hz){a=c.Mp().za();if(a instanceof hz){c=new YJ(c.sl(),b);b=a.Mp().za();c=new YJ(a.sl(),c);a=b;b=c;continue}if(a instanceof jz){a=a.Op.za();b=new YJ(c.sl(),b);continue}if(a instanceof RE){a=c.sl().g(a.Kh());continue}throw new D(a);}if(c instanceof jz)a=c.Op.za();else{if(c instanceof RE){a=c.Kh();c=b;if(c instanceof YJ){b=c.Uw;a=c.Tw.g(a);continue}if(c instanceof XJ)break a;throw new D(c);}throw new D(c);}}return a} +z(VJ,"cats.Eval$",{XP:1,fQ:1,iQ:1,jQ:1});var WJ;function Ke(){WJ||(WJ=new VJ)}function iz(a,b){this.yD=this.zD=null;this.zD=a.Mp();this.yD=new G(c=>new ZJ(a,c,b,this))}iz.prototype=new QE;iz.prototype.constructor=iz;iz.prototype.Mp=function(){return this.zD};iz.prototype.sl=function(){return this.yD};z(iz,"cats.Eval$$anon$1",{YP:1,Iz:1,Ll:1,b:1});function ZJ(a,b,c,d){this.AD=this.BD=null;if(null===d)throw Nr();this.BD=new pg(()=>a.sl().g(b));this.AD=c}ZJ.prototype=new QE; +ZJ.prototype.constructor=ZJ;ZJ.prototype.Mp=function(){return this.BD};ZJ.prototype.sl=function(){return this.AD};z(ZJ,"cats.Eval$$anon$2",{ZP:1,Iz:1,Ll:1,b:1});function kz(a,b){this.GJ=a.Op;this.FJ=b}kz.prototype=new QE;kz.prototype.constructor=kz;kz.prototype.Mp=function(){return this.GJ};kz.prototype.sl=function(){return this.FJ};z(kz,"cats.Eval$$anon$3",{$P:1,Iz:1,Ll:1,b:1});function lz(a,b){this.CD=this.DD=null;if(null===b)throw Nr();this.DD=new pg(()=>b);this.CD=a}lz.prototype=new QE; +lz.prototype.constructor=lz;lz.prototype.Mp=function(){return this.DD};lz.prototype.sl=function(){return this.CD};z(lz,"cats.Eval$$anon$4",{aQ:1,Iz:1,Ll:1,b:1});function $J(a){this.Op=null;this.Op=(Ke(),a)}$J.prototype=new OE;$J.prototype.constructor=$J;z($J,"cats.Eval$$anon$5",{bQ:1,cQ:1,Ll:1,b:1});function og(a){this.GD=null;this.HD=!1;this.FD=a}og.prototype=new SE;og.prototype.constructor=og;og.prototype.Kh=function(){if(!this.HD){var a=this.FD.za();this.FD=null;this.GD=a;this.HD=!0}return this.GD}; +z(og,"cats.Later",{pQ:1,ED:1,Ll:1,b:1});function aK(){}aK.prototype=new t;aK.prototype.constructor=aK;z(aK,"cats.Parallel$$anon$2",{vQ:1,b:1,qQ:1,uQ:1});function yz(){}yz.prototype=new t;yz.prototype.constructor=yz;yz.prototype.Lw=function(a){return Ja(a)};z(yz,"cats.Show$$anon$5",{zQ:1,b:1,LJ:1,KJ:1});function bK(){cK=this;new dK;new eK}bK.prototype=new t;bK.prototype.constructor=bK;z(bK,"cats.UnorderedFoldable$",{IQ:1,xQ:1,oS:1,MQ:1});var cK;function wq(){cK||(cK=new bK)} +function wz(a){this.Fj=this.Sh=null;this.Rp=a;this.Sh=N();this.Fj=null}wz.prototype=new t;wz.prototype.constructor=wz;e=wz.prototype;e.E=function(){return-1};e.hc=function(a,b,c){return Fd(this,a,b,c)};e.rf=function(a,b,c,d){return ns(this,a,b,c,d)};e.ub=function(){return rg(N(),this)};function fK(a){yC();return Bp(Cp(),a)}e.Wg=function(a){return os(this,a)};e.i=function(){return this};e.cj=function(a){return oH(this,a)};e.Mw=function(a){return this.Sg(0,0{c=new y(new Int32Array([a.I(c.Y,d.Y),b.I(c.V,d.V)]));a:{for(d=0;d""+a.fx+f+a.fx)),"{",", ","}"):c.e()?"??? bug with Expectation.OneOfStr":"must match string: "+a.fx+c.u()+a.fx}if(b instanceof GF)return c=b.Vi,b=b.Wi,c!==b?"must be a char within the range of: ['"+Na(c)+"', '"+Na(b)+"']":"must be char: '"+Na(c)+"'";if(b instanceof FK)return"must start the string";if(b instanceof Cn)return"must end the string";if(b instanceof GK)return"must fail but matched with "+ +b.Tl;if(b instanceof ig)return"must fail";if(b instanceof pe){c=b.Ul;var d=b.Gj;if(null!==c&&null!==d)return"context: "+c+", "+EK(a,d)}throw new D(b);}KF.prototype.Lw=function(a){return EK(this,a)};z(KF,"cats.parse.Parser$Expectation$$anon$3",{FU:1,b:1,LJ:1,KJ:1});class HK extends XF{}function IK(){}IK.prototype=new t;IK.prototype.constructor=IK;function JK(){}JK.prototype=IK.prototype;function Ik(a,b){ft(a,""+b);return a}class Jk extends WF{}z(Jk,"java.lang.AssertionError",{A1:1,KH:1,Xa:1,b:1}); +var ta=z(0,"java.lang.Boolean",{B1:1,b:1,Lb:1,ak:1},a=>"boolean"===typeof a),wa=z(0,"java.lang.Character",{D1:1,b:1,Lb:1,ak:1},a=>a instanceof ba);function ag(a,b){ft(a,b);return a}class bg extends XF{}z(bg,"java.lang.RuntimeException",{Mb:1,xb:1,Xa:1,b:1});function Yz(a){a.F="";return a}function KK(a){var b=new Zz;Yz(b);if(null===a)throw Nr();b.F=a;return b}function Zz(){this.F=null}Zz.prototype=new t;Zz.prototype.constructor=Zz;function LK(a,b){b=PA(zs(),b,0,b.a.length);a.F=""+a.F+b}e=Zz.prototype; +e.f=function(){return this.F};e.q=function(){return this.F.length};e.So=function(a){return this.F.charCodeAt(a)};e.Cz=function(a,b){return this.F.substring(a,b)};e.pB=function(a){this.F=""+this.F+a};z(Zz,"java.lang.StringBuilder",{Z1:1,qy:1,QB:1,b:1}); +function FD(a,b){var c=b.Vk,d=MK(a)-c|0;if(!(NK(a)=d))if(64>a.Od){var f=zB().nq.a,g=d<<1,h=f[g],k=f[g+1|0],m=a.na,n=m-d|0,q=((m>>31)-(d>>31)|0)+((~m&d|~(m^d)&n)>>31)|0,u=a.vd,v=a.kd,w=xm(qj(),u,v,h,k),A=w.h,K=w.l,M=65535&h,S=h>>>16|0,L=65535&A,aa=A>>>16|0,U=Math.imul(M,L),ka=Math.imul(S,L),ra=Math.imul(M,aa),ha=U+((ka+ra|0)<<16)|0,fb=(U>>>16|0)+ra|0,xb=(((Math.imul(h,K)+Math.imul(k,A)|0)+Math.imul(S,aa)|0)+(fb>>>16|0)|0)+(((65535&fb)+ka|0)>>>16|0)|0,pb=u-ha|0,Ra=(v-xb|0)+((~u&ha|~(u^ +ha)&pb)>>31)|0;if(0!==(pb|Ra)){zB();var xc=Ra>>31,ma=pb^xc,Za=ma-xc|0,qd=Za<<1,Sc=Za>>>31|0|((Ra^xc)+((ma&~Za)>>>31|0)|0)<<1,qc=Math.imul(Ra>>31|((-Ra|0)+((pb|-pb|0)>>31)|0)>>>31|0,5+((Sc===k?qd>>>0>h>>>0:Sc>k)?1:(Sc===k?qd>>>0>>0:Sc>31)|0)+((A&qa|(A|qa)&~Eb)>>>31|0)|0,Sb=Fa>>31,Tc=Eb^Sb,fh=Tc-Sb|0;if(+Math.log10(4294967296*((Fa^Sb)+((Tc&~fh)>>>31|0)|0)+(fh>>>0))>=b.Vk)var Kf=n-1|0,ce=(q-1|0)+((n|~Kf)>>>31|0)|0,ye=Fa>>31,Lf=65535&Eb,ug=Eb>>> +16|0,gh=Math.imul(26214,Lf),de=Math.imul(26214,Lf),Gd=Math.imul(26214,ug),yb=gh+((de+Gd|0)<<16)|0,vg=(gh>>>16|0)+Gd|0,ze=(Math.imul(26214,ug)+(vg>>>16|0)|0)+(((65535&vg)+de|0)>>>16|0)|0,Hd=65535&Eb,Ae=Eb>>>16|0,Ye=Math.imul(26215,Hd),Mf=Math.imul(26214,Hd),Ze=Math.imul(26215,Ae),Nf=(Ye>>>16|0)+Ze|0,bd=(Math.imul(26214,Ae)+(Nf>>>16|0)|0)+(((65535&Nf)+Mf|0)>>>16|0)|0,Id=yb+bd|0,Uc=ze+((yb&bd|(yb|bd)&~Id)>>>31|0)|0,Be=65535&Fa,$e=Fa>>>16|0,af=Math.imul(26214,Be),Of=Math.imul(26214,Be),Ce=Math.imul(26214, +$e),rd=af+((Of+Ce|0)<<16)|0,ee=(af>>>16|0)+Ce|0,De=((Math.imul(1717986918,ye)+Math.imul(26214,$e)|0)+(ee>>>16|0)|0)+(((65535&ee)+Of|0)>>>16|0)|0,yc=rd+Uc|0,wg=(De+(Uc>>31)|0)+((rd&Uc|(rd|Uc)&~yc)>>>31|0)|0,bf=65535&Fa,cf=Fa>>>16|0,xg=Math.imul(26215,bf),df=Math.imul(26214,bf),Pf=Math.imul(26215,cf),rc=xg+((df+Pf|0)<<16)|0,sc=(xg>>>16|0)+Pf|0,Jc=(((Math.imul(1717986919,ye)+Math.imul(26214,cf)|0)+(sc>>>16|0)|0)+(((65535&sc)+df|0)>>>16|0)|0)+((Id&rc|(Id|rc)&~(Id+rc|0))>>>31|0)|0,Jd=yc+Jc|0,Kd=(wg+(Jc>> +31)|0)+((yc&Jc|(yc|Jc)&~Jd)>>>31|0)|0,ff=Jd>>>2|0|Kd<<30,Qf=Fa>>>31|0,Rf=ff+Qf|0,fe=r(Kf,ce),gf=r(Rf,(Kd>>2)+((ff&Qf|(ff|Qf)&~Rf)>>>31|0)|0);else fe=r(n,q),gf=r(Eb,Fa)}else fe=r(n,q),gf=r(A,K);var Sf=ab(fe),Tf=Sf.h,zc=Sf.l,sd=ab(gf),Ld=sd.h,ge=sd.l;a.na=GB(zB(),Tf,zc);a.Uk=b.Vk;a.vd=Ld;a.kd=ge;a.Od=AB(zB(),Ld,ge);a.Tk=null}else{var he=Wj(sj(),d,d>>31),yg=rB(a);var Ac=Ti(OK(yg,he));var Fe=a.na,Md=Fe-d|0,ie=((Fe>>31)-(d>>31)|0)+((~Fe&d|~(Fe^d)&Md)>>31)|0;if(0!==Ac.a[1].ca){var cd=kB(Ac.a[1]);if(0=== +cd.ca)var Ge=cd;else{gj();var hf=cd.fa,qi=1+hf|0,hh=new y(qi);cj(0,hh,cd.N,hf);var Sj=aj(cd.ca,qi,hh);bj(Sj);Ge=Sj}var Tj=xA(Ge,he),ri=PK(Ac.a[0],0)?1:0,si=Math.imul(Ac.a[1].ca,5+Tj|0),dd=FB(zB(),ri,si,b.bo);if(0!==dd){var zg=Aj(Zi(),dd,dd>>31);Ac.a[0]=yj(Ej(),Ac.a[0],zg)}var je=new qB;QK(je,Ac.a[0],0);if(MK(je)>c){Ac.a[0]=RK(Ac.a[0],Zi().Lj);var Uf=Md-1|0,Ag=Uf,ih=(ie-1|0)+((Md|~Uf)>>>31|0)|0}else Ag=Md,ih=ie}else Ag=Md,ih=ie;a.na=GB(zB(),Ag,ih);a.Uk=c;ED(a,Ac.a[0])}} +function SK(a){return 0===a.Od?0!==(~a.vd|~a.kd):!1}function TK(a,b){var c=a.na,d=-c|0,f=NK(a),g=d+f|0;c=(((-(c>>31)|0)+((c|d)>>31)|0)+(f>>31)|0)+((d&f|(d|f)&~g)>>>31|0)|0;if(0===c?19>>0:0a.Od&&(b=b.uf(),a.vd=b.h,a.kd=b.l)} +function DD(a){a.cm=null;a.Kj=0;a.Od=0;a.vd=0;a.kd=0;a.na=0;a.Uk=0}function EB(a,b,c,d){DD(a);a.vd=b;a.kd=c;a.na=d;a.Od=AB(zB(),b,c);return a}function xB(a,b){var c=new qB;DD(c);c.vd=a;c.kd=a>>31;c.na=b;zB();a=32-Math.clz32(0>a?~a:a)|0;c.Od=a;return c}function QK(a,b,c){DD(a);if(null===b)throw jw("unscaledVal \x3d\x3d null");a.na=c;ED(a,b);return a}function qB(){this.cm=null;this.Kj=0;this.Tk=null;this.Uk=this.na=this.kd=this.vd=this.Od=0}qB.prototype=new NA;qB.prototype.constructor=qB; +function VK(a){if(64>a.Od){if(0>a.kd)return-1;var b=a.vd;a=a.kd;return(0===a?0!==b:0a.Od){var c=a.vd,d=a.kd;if(0===(c|-2147483648^d))b=19;else{H();b=zB().nq;var f=d>>31,g=c^f;c=g-f|0;b:for(d=(d^f)+((g&~c)>>>31|0)|0,f=0,g=b.a.length>>>1|0;;){if(f===g){b=-1-f|0;break b}var h=(f+g|0)>>>1|0,k=b.a,m=h<<1,n=k[m];k=k[m+1|0];n=d===k?c===n?0:c>>>0>>0?-1:1:dn)g=h;else{if(0===n){b=h;break b}f=1+h|0}}b=0>b?-1-b|0:1+b|0}}else b=1+Ma(.3010299956639812*(a.Od-1|0))|0,c=rB(a),d=sj(),b=0!==RK(c,Wj(d,b,b>>31)).ca?1+b|0:b;a.Uk= +b}return a.Uk}function WK(a){if(SK(a))return zB().mF;var b=sj().Xk.a.length-1|0,c=1,d=rB(a),f=a=a.na;for(a>>=31;;){if(!PK(d,0)){var g=OK(d,sj().Xk.a[c]);if(0===g.pF.ca){d=g.oF;g=f;var h=c;f=g-h|0;a=(a-(h>>31)|0)+((~g&h|~(g^h)&f)>>31)|0;c=ca.Od&&64>b.Od){d=a.vd;var f=a.kd;c=b.vd;var g=b.kd;if(f===g?d>>>0>>0:f>>0>f>>>0:a>b)?1:0}f=a.na;g=b.na;d=f-g|0;f=((f>>31)-(g>>31)|0)+((~f&g|~(f^g)&d)>>31)|0;g=NK(a)-NK(b)|0;var h=g>>31,k=1+d|0,m=f+((d&~k)>>>31|0)|0;if(h===m?g>>>0>k>>>0:h>m)return c;h=g>>31;k=d-1|0;m=(f-1|0)+((d|~k)>>>31|0)|0;if(h===m?g>>>0>>0:hf)c=sj(),g=-d|0,a=Oj(a,Wj(c, +g,(-f|0)+((d|g)>>31)|0));else if(0===f?0!==d:0this.Od?0===(a.vd^this.vd|a.kd^this.kd):this.Tk.d(a.Tk):!1:!1};e.p=function(){if(0===this.Kj)if(64>this.Od){this.Kj=this.vd;var a=this.kd;this.Kj=Math.imul(33,this.Kj)+a|0;this.Kj=Math.imul(17,this.Kj)+this.na|0}else this.Kj=Math.imul(17,this.Tk.p())+this.na|0;return this.Kj}; +e.f=function(){if(null!==this.cm)return this.cm;if(32>this.Od)return this.cm=kj(mj(),this.vd,this.kd,this.na);var a=rB(this),b=jj(mj(),a);if(0===this.na)return b;var c=0>rB(this).ca?2:1,d=b.length,f=this.na,g=-f|0,h=g+d|0;a=h-c|0;f=(((((-(f>>31)|0)+((f|g)>>31)|0)+(d>>31)|0)+((g&d|(g|d)&~h)>>>31|0)|0)-(c>>31)|0)+((~h&c|~(h^c)&a)>>31)|0;0>>0:-1a.na){var b=rB(a),c=sj();a=a.na;var d=-a|0;return Oj(b,Wj(c,d,(-(a>>31)|0)+((a|d)>>31)|0))}b=rB(a);c=sj();a=a.na;return RK(b,Wj(c,a,a>>31))} +function UK(a){if(0===a.na||SK(a))return rB(a);if(0>a.na){var b=rB(a),c=sj();a=a.na;var d=-a|0;return Oj(b,Wj(c,d,(-(a>>31)|0)+((a|d)>>31)|0))}if(a.na>NK(a)||a.na>ZK(rB(a)))throw new La("Rounding necessary");b=rB(a);c=sj();a=a.na;a=Wj(c,a,a>>31);a=Ti(OK(b,a));if(0!==a.a[1].ca)throw new La("Rounding necessary");return a.a[0]}e.uf=function(){return-64>=this.na||this.na>NK(this)?r(0,0):YK(this).uf()};e.Dd=function(){return-32>=this.na||this.na>NK(this)?0:YK(this).Dd()}; +e.Im=function(){return Pn(Qn(),rB(this)+"e"+(-this.na|0))};e.dj=function(){return tA(Ca(),rB(this)+"e"+(-this.na|0))};function rB(a){null===a.Tk&&(a.Tk=Aj(Zi(),a.vd,a.kd));return a.Tk}e.$f=function(a){return XK(this,a)};var BB=z(qB,"java.math.BigDecimal",{BX:1,hj:1,b:1,Lb:1});function $K(a){a.ao=-2;a.dm=0} +function ke(a){var b=new iB;$K(b);Zi();if(null===a)throw Nr();if(""===a)throw new Rn("Zero length BigInteger");if(""===a||"+"===a||"-"===a)throw new Rn("Zero length BigInteger");var c=a.length;if(45===a.charCodeAt(0))var d=-1,f=1,g=c-1|0;else 43===a.charCodeAt(0)?(f=d=1,g=c-1|0):(d=1,f=0,g=c);d|=0;var h=f|0;f=g|0;for(g=h;g>>0)|0);h=Kj(sj(),f,f,n,k);Ej();var u=f,v=n,w=q;for(q=0;0!==w&&q>>31|0;u.a[q]=w;w=A;q=1+q|0}h=h+w|0;f.a[n]=h;n=1+n|0;h=m;m=h+g|0}b.ca=d;b.fa=n;b.N=f;bj(b);return b}function zj(a,b){var c=new iB;$K(c);c.ca=a;c.fa=1;c.N=new y(new Int32Array([b]));return c}function aj(a,b,c){var d=new iB;$K(d);d.ca=a;d.fa=b;d.N=c;return d} +function LB(a,b,c){var d=new iB;$K(d);d.ca=a;0===c?(d.fa=1,d.N=new y(new Int32Array([b]))):(d.fa=2,d.N=new y(new Int32Array([b,c])));return d}function iB(){this.N=null;this.dm=this.ao=this.ca=this.fa=0}iB.prototype=new NA;iB.prototype.constructor=iB;function kB(a){return 0>a.ca?aj(1,a.fa,a.N):a}function xA(a,b){return a.ca>b.ca?1:a.cab.fa?a.ca:a.fa>>0)/(Ka(b.N.a[0])>>>0)|0,d!==c?(b=c=-a|0,a=(a|c)>>31):(b=a,a=0),Aj(Zi(),b,a);var h=f!==g?f>g?1:-1:Bj(Ej(),a.N,b.N,f);if(0===h)return d===c?Zi().$n:Zi().px;if(-1===h)return Zi().Mj;h=1+(f-g|0)|0;var k=new y(h);c=d===c?1:-1;1===g?tj(rj(),k,a.N,f,b.N.a[0]):oj(rj(),k,h,a.N,f,b.N,g);a=aj(c,h,k);bj(a);return a} +function OK(a,b){var c=b.ca;if(0===c)throw new La("BigInteger divide by zero");var d=b.fa;b=b.N;if(1===d){rj();var f=b.a[0],g=a.N;b=a.fa;d=a.ca;if(1===b){g=g.a[0];a=(g>>>0)/(Ka(f)>>>0)|0;b=0;f=(g>>>0)%(Ka(f)>>>0)|0;g=0;if(d!==c){c=a;var h=-c|0;a=h;b=(-b|0)+((c|h)>>31)|0}0>d&&(c=f,f=d=-c|0,g=(-g|0)+((c|d)>>31)|0);c=new Si(Aj(Zi(),a,b),Aj(Zi(),f,g))}else c=d===c?1:-1,a=new y(b),f=tj(0,a,g,b,f),f=new y(new Int32Array([f])),c=aj(c,b,a),d=aj(d,1,f),bj(c),bj(d),c=new Si(c,d);return c}f=a.N;g=a.fa;if(0> +(g!==d?g>d?1:-1:Bj(Ej(),f,b,g)))return new Si(Zi().Mj,a);a=a.ca;h=1+(g-d|0)|0;c=a===c?1:-1;var k=new y(h);b=oj(rj(),k,h,f,g,b,d);c=aj(c,h,k);d=aj(a,d,b);bj(c);bj(d);return new Si(c,d)}e=iB.prototype;e.d=function(a){var b;if(b=a instanceof iB)if(b=this.ca===a.ca&&this.fa===a.fa)a:{for(b=0;b!==this.fa;){if(this.N.a[b]!==a.N.a[b]){b=!1;break a}b=1+b|0}b=!0}return b};function ZK(a){if(0===a.ca)return-1;var b=Xi(a);a=a.N.a[b];return(b<<5)+(32-Math.clz32(~a&(a-1|0))|0)|0} +e.p=function(){if(0===this.dm){for(var a=this.fa,b=0;b>31,f=65535&c,g=c>>>16|0,h=65535&a,k=a>>>16|0,m=Math.imul(f,h);h=Math.imul(g,h);var n=Math.imul(f,k);f=m+((h+n|0)<<16)|0;m=(m>>>16|0)+n|0;a=(((Math.imul(c,b)+Math.imul(d,a)|0)+Math.imul(g,k)|0)+(m>>>16|0)|0)+(((65535&m)+h|0)>>>16|0)|0;return r(f,a)};function Oj(a,b){return 0===b.ca||0===a.ca?Zi().Mj:Qj(sj(),a,b)}function Dj(a){return 0===a.ca?a:aj(-a.ca|0,a.fa,a.N)} +function Xj(a,b){if(0>b)throw new La("Negative exponent");if(0===b)return Zi().$n;if(1===b||a.d(Zi().$n)||a.d(Zi().Mj))return a;if(PK(a,0)){sj();for(var c=Zi().$n,d=a;1>=1;c=a}return Oj(c,d)}for(d=1;!PK(a,d);)d=1+d|0;c=Zi();f=Math.imul(d,b);if(f>5;f&=31; +var g=new y(1+c|0);g.a[c]=1<>5;if(0===b)return 0!==(1&a.N.a[0]);if(0>b)throw new La("Negative bit address");if(c>=a.fa)return 0>a.ca;if(0>a.ca&&ca.ca&&(d=Xi(a)===c?-d|0:~d);return 0!==(d&1<<(31&b))}e.f=function(){return jj(mj(),this)}; +function bj(a){for(;;){if(0a||a>=this.aa)throw kk();return this.re.a[this.Xe+a|0]};e.AH=function(a,b,c){if(0>b||0>c||b>(a.a.length-c|0))throw kk();var d=this.L,f=d+c|0;if(f>this.aa)throw new Fk;this.L=f;this.re.G(this.Xe+d|0,a,b,c)}; +e.NB=function(){var a=this.re,b=this.Xe,c=this.Ye,d=this.L,f=2+d|0;if(f>this.aa)throw new Fk;this.L=f;d=d+b|0;b=a.a[d];a=a.a[1+d|0];return c?(b<<8|255&a)<<16>>16:(a<<8|255&b)<<16>>16};e.KB=function(){var a=this.re,b=this.Xe,c=this.Ye,d=this.L,f=4+d|0;if(f>this.aa)throw new Fk;this.L=f;var g=d+b|0;b=a.a[g];d=a.a[1+g|0];f=a.a[2+g|0];a=a.a[3+g|0];return c?b<<24|(255&d)<<16|(255&f)<<8|255&a:a<<24|(255&f)<<16|(255&d)<<8|255&b}; +e.zH=function(){var a=this.re,b=this.Xe,c=this.Ye,d=this.L,f=8+d|0;if(f>this.aa)throw new Fk;this.L=f;var g=d+b|0;b=a.a[g];d=a.a[1+g|0];f=a.a[2+g|0];var h=a.a[3+g|0],k=a.a[4+g|0],m=a.a[5+g|0],n=a.a[6+g|0];a=a.a[7+g|0];return c?r(k<<24|(255&m)<<16|(255&n)<<8|255&a,b<<24|(255&d)<<16|(255&f)<<8|255&h):r(h<<24|(255&f)<<16|(255&d)<<8|255&b,a<<24|(255&n)<<16|(255&m)<<8|255&k)}; +e.yH=function(){var a=this.re,b=this.Xe,c=this.Ye,d=this.L,f=4+d|0;if(f>this.aa)throw new Fk;this.L=f;var g=d+b|0;b=a.a[g];d=a.a[1+g|0];f=a.a[2+g|0];a=a.a[3+g|0];return Qa(c?b<<24|(255&d)<<16|(255&f)<<8|255&a:a<<24|(255&f)<<16|(255&d)<<8|255&b)}; +e.xH=function(){var a=this.re,b=this.Xe,c=this.Ye,d=this.L,f=8+d|0;if(f>this.aa)throw new Fk;this.L=f;var g=d+b|0;b=a.a[g];d=a.a[1+g|0];f=a.a[2+g|0];var h=a.a[3+g|0],k=a.a[4+g|0],m=a.a[5+g|0],n=a.a[6+g|0];a=a.a[7+g|0];c?(c=k<<24|(255&m)<<16|(255&n)<<8|255&a,a=b<<24|(255&d)<<16|(255&f)<<8|255&h):(c=h<<24|(255&f)<<16|(255&d)<<8|255&b,a=a<<24|(255&n)<<16|(255&m)<<8|255&k);Oa.setInt32(0,c,!0);Oa.setInt32(4,a,!0);return+Oa.getFloat64(0,!0)};e.Ay=function(a){return this.re.a[this.Xe+a|0]}; +z(ok,"java.nio.HeapByteBuffer",{VX:1,EM:1,gA:1,Lb:1});function jp(a,b,c,d){this.Pd=this.L=this.aa=this.He=0;this.Ye=!1;this.BF=null;this.lA=!1;this.Nj=a;this.kA=d;a=a.length|0;this.re=null;this.Xe=-1;Yj(this,a);this.Ye=!0;Zj.prototype.yb.call(this,b);Zj.prototype.Zo.call(this,c)}jp.prototype=new ZF;jp.prototype.constructor=jp;function aL(a){a.lA||a.lA||(a.BF=new DataView(a.Nj.buffer,a.Nj.byteOffset|0,a.He),a.lA=!0);return a.BF}e=jp.prototype;e.ff=function(){return this.kA}; +e.zz=function(){var a=this.Nj.subarray(this.L,this.aa);return new jp(a,0,a.length|0,this.kA)};e.jy=function(){var a=new jp(this.Nj,this.L,this.aa,this.kA);a.Pd=this.Pd;return a};e.xB=function(){var a=new jp(this.Nj,this.L,this.aa,!0);a.Pd=this.Pd;return a};e.Zj=function(){var a=this.L;if(a===this.aa)throw new Fk;this.L=1+a|0;return this.Nj[a]|0};e.CH=function(a){if(0>a||a>=this.aa)throw kk();return this.Nj[a]|0}; +e.AH=function(a,b,c){if(0>b||0>c||b>(a.a.length-c|0))throw kk();var d=this.L,f=d+c|0;if(f>this.aa)throw new Fk;this.L=f;for(c=d+c|0;d!==c;)a.a[b]=this.Nj[d]|0,d=1+d|0,b=1+b|0};e.NB=function(){var a=aL(this),b=this.L,c=2+b|0;if(c>this.aa)throw new Fk;this.L=c;return a.getInt16(b,!this.Ye)|0};e.KB=function(){var a=aL(this),b=this.L,c=4+b|0;if(c>this.aa)throw new Fk;this.L=c;return a.getInt32(b,!this.Ye)|0}; +e.zH=function(){var a=aL(this),b=this.L,c=8+b|0;if(c>this.aa)throw new Fk;this.L=c;var d=!this.Ye;c=a.getInt32(b+(d?4:0)|0,d)|0;a=a.getInt32(b+(d?0:4)|0,d)|0;return r(a,c)};e.yH=function(){var a=aL(this),b=this.L,c=4+b|0;if(c>this.aa)throw new Fk;this.L=c;a=a.getFloat32(b,!this.Ye);return Math.fround(a)};e.xH=function(){var a=aL(this),b=this.L,c=8+b|0;if(c>this.aa)throw new Fk;this.L=c;return+a.getFloat64(b,!this.Ye)};e.Ay=function(a){return this.Nj[a]|0}; +z(jp,"java.nio.TypedArrayByteBuffer",{aY:1,EM:1,gA:1,Lb:1});class bL extends WF{constructor(a){super();ft(this,null===a?null:a.f())}}z(bL,"java.nio.charset.CoderMalfunctionError",{cY:1,KH:1,Xa:1,b:1});function Zx(){this.nf=null;this.nA=0;new (B(fa).P)("cp367 ascii7 ISO646-US 646 csASCII us iso_646.irv:1983 ISO_646.irv:1991 IBM367 ASCII default ANSI_X3.4-1986 ANSI_X3.4-1968 iso-ir-6".split(" "));this.nA=127;this.nf="US-ASCII"}Zx.prototype=new cG;Zx.prototype.constructor=Zx; +z(Zx,"java.nio.charset.US_ASCII$",{kY:1,hY:1,IM:1,Lb:1});var Yx;function UA(a,b){null===a.Mm?a.pl=""+a.pl+b:cL(a,[b])}function dL(a,b,c){null===a.Mm?a.pl=""+a.pl+b+c:cL(a,[b,c])}function eL(a,b,c,d){null===a.Mm?a.pl=a.pl+(""+b+c)+d:cL(a,[b,c,d])}function cL(a,b){try{for(var c=b.length|0,d=0;d!==c;)a.Mm.pB(b[d]),d=1+d|0}catch(f){if(!(f instanceof HK))throw f;}}function XA(a){return void 0!==a?(a=+parseInt(a,10),2147483647>=a?Ma(a):-2):-1} +function fL(a){return(0!==(1&a)?"-":"")+(0!==(2&a)?"#":"")+(0!==(4&a)?"+":"")+(0!==(8&a)?" ":"")+(0!==(16&a)?"0":"")+(0!==(32&a)?",":"")+(0!==(64&a)?"(":"")+(0!==(128&a)?"\x3c":"")}function gL(a,b,c){var d=Gl(a,1+b|0);a=d.ol?"-":"";var f=d.ck,g=f.length-1|0,h=b-g|0;b=f.substring(0,1);f=""+f.substring(1)+Cl(Dl(),h);d=g-d.bk|0;g=d>>31;g=""+((d^g)-g|0);return a+(""!==f||c?b+"."+f:b)+"e"+(0>d?"-":"+")+(1===g.length?"0"+g:g)} +function hL(a,b,c){var d=El(a,(a.ck.length+b|0)-a.bk|0);Dl();if(!("0"===d.ck||d.bk<=b))throw Ik(new Jk,"roundAtPos returned a non-zero value with a scale too large");d="0"===d.ck||d.bk===b?d:new Fl(a.ol,""+d.ck+Cl(Dl(),b-d.bk|0),b);a=d.ol?"-":"";d=d.ck;var f=d.length,g=1+b|0;d=f>=g?d:""+Cl(Dl(),g-f|0)+d;f=d.length-b|0;a+=d.substring(0,f);return 0!==b||c?a+"."+d.substring(f):a}function fB(a,b,c,d,f,g){b=0>f||f>=g.length?g:g.substring(0,f);b=0!==(256&c)?b.toUpperCase():b;cB(a,c,d,b)} +function nB(a,b,c,d){cB(a,b,c,mB(b,d!==d?"NaN":0=c&&0===(110&b))UA(a,mB(b,d));else if(0===(126&b))cB(a,b,c,mB(b,d));else{if(45!==d.charCodeAt(0))var g=0!==(4&b)?"+":0!==(8&b)?" ":"";else 0!==(64&b)?(g="(",d=d.substring(1)+")"):(g="-",d=d.substring(1));f=""+g+f;if(0!==(32&b)){var h=d.length;for(g=0;g!==h&&9>=(d.charCodeAt(g)-48|0)>>>0;)g=1+g|0;g=g-3|0;if(!(0>=g)){for(h=d.substring(g);3=c?UA(a,d):0!==(1&b)?dL(a,d,iL(" ",c-f|0)):dL(a,iL(" ",c-f|0),d)}function lB(a,b,c,d,f,g){b=f.length+g.length|0;b>=d?dL(a,f,g):0!==(16&c)?eL(a,f,iL("0",d-b|0),g):0!==(1&c)?eL(a,f,g,iL(" ",d-b|0)):eL(a,iL(" ",d-b|0),f,g)}function iL(a,b){for(var c="",d=0;d!==b;)c=""+c+a,d=1+d|0;return c}function ZA(a){throw new jL(a);} +function pB(a,b,c,d,f,g){var h=0!==(2&c);d=0<=d?d:6;switch(f){case 101:h=gL(b,d,h);break;case 102:h=hL(b,d,h);break;default:f=0===d?1:d,b=Gl(b,f),d=(b.ck.length-1|0)-b.bk|0,-4<=d&&df?0:f,h)):h=gL(b,f-1|0,h)}jB(a,c,g,h,"")}function RA(){this.pl=this.UN=this.Mm=null;this.TB=!1}RA.prototype=new t;RA.prototype.constructor=RA;RA.prototype.f=function(){if(this.TB)throw new TA;return null===this.Mm?this.pl:this.Mm.f()};function $A(a){throw new kL(fL(a));} +function bB(a,b,c){throw new lL(fL(b&c),a);}function gB(a,b){throw new mL(a,ea(b));}z(RA,"java.util.Formatter",{l2:1,jF:1,FH:1,kF:1});function nL(){}nL.prototype=new t;nL.prototype.constructor=nL;nL.prototype.I=function(a,b){return(a|0)-(b|0)|0};nL.prototype.Fi=function(a,b,c){a.a[b]=c|0};nL.prototype.ef=function(a,b){return a.a[b]};z(nL,"java.util.internal.GenericArrayOps$ByteArrayOps$",{D2:1,Uv:1,xy:1,Fd:1});var oL;function bl(){oL||(oL=new nL);return oL}function pL(){}pL.prototype=new t; +pL.prototype.constructor=pL;pL.prototype.I=function(a,b){return $a(a)-$a(b)|0};pL.prototype.Fi=function(a,b,c){a.a[b]=$a(c)};pL.prototype.ef=function(a,b){return p(a.a[b])};z(pL,"java.util.internal.GenericArrayOps$CharArrayOps$",{E2:1,Uv:1,xy:1,Fd:1});var qL;function $k(){qL||(qL=new pL);return qL}function rL(){}rL.prototype=new t;rL.prototype.constructor=rL;rL.prototype.I=function(a,b){a|=0;b|=0;return a===b?0:a>>0>>0?-1:1:cn=>{if(null!==n){var q=n.Y|0;n=Math.fround(n.V);var u=new GI,v=new GI;f.Em(m).Oa(new G(w=>{var A=a.Eq.Bq.hh(w);if(!A.e()){var K=A.oa().a[q-1|0];f.Hc(w)&&BL(u,w,K);if(m.Hc(w)){A=a.Dq;var M=CL(b.mo).toLowerCase(),S=K.toLowerCase().indexOf(M)|0;if(-1===S)A=DL(A,K);else{var L= +S-A.UM|0,aa=0>L?0:L;if(0===aa||K.lengthnew C(Math.fround(-Math.fround(c.V)),c.Y)));this.Fq=a.$k.dG}Oo.prototype=new t;Oo.prototype.constructor=Oo;e=Oo.prototype;e.x=function(){return new Z(this)};e.p=function(){return xx(this,-1997460225)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Oo){var b=this.Eq,c=a.Eq;if(null===b?null===c:b.d(c))return b=this.Dq,a=a.Dq,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Searcher"};e.c=function(a){if(0===a)return this.Eq;if(1===a)return this.Dq;throw O(new P,""+a);}; +function To(a,b){var c=a.iG.hC(b.mo);c instanceof Eg&&(c=c.ib,c=new Eg(b.Cq?c.md(new G(d=>{Bo||(Bo=new Ao);if(d instanceof ao){d=d.ho;var f=new bo(d);d=new jn(new JL(new ao(d),Pg(Qg(),new (B(xL).P)([])),f))}return d})):c));c instanceof Eg?(c=c.ib,b=zL(a,b),b instanceof Eg?(b=b.ib,c=rC(a.gG,c),a=c instanceof Eg?new Eg(KL(LL(rg(N(),c.ib),a.hG),b)):c):a=b):a=c;if(a instanceof Dg)a=new Uo(a.lj);else if(a instanceof Eg)a=new Yo(a.ib);else throw new D(a);return a} +z(Oo,"pink.cozydev.protosearch.Searcher",{zZ:1,k:1,s:1,b:1});function AG(a){this.Gq=a}AG.prototype=new t;AG.prototype.constructor=AG;e=AG.prototype;e.x=function(){return new Z(this)};e.p=function(){var a=-889275714;a=X().m(a,-959795002);a=X().m(a,this.Gq?1231:1237);return X().T(a,1)};e.d=function(a){return this===a||a instanceof AG&&this.Gq===a.Gq};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Analyzer"}; +e.c=function(a){if(0===a)return this.Gq;throw O(new P,""+a);};function ML(a,b){return a.Gq?rg(N(),Zq(Ah(),Ad(b.toLowerCase(),"\\s+",0))):rg(N(),Zq(Ah(),Ad(b,"\\s+",0)))}z(AG,"pink.cozydev.protosearch.analysis.Analyzer",{BZ:1,k:1,s:1,b:1}); +function NL(a,b){if(b instanceof ao){b=b.ho;a=ML(a.DA,b);if(N().d(a))return new Dg("Error tokenizing Term '"+b+"' during query analysis");if(a instanceof J){var c=a.Fa;b=a.Z;if(N().d(b))return new Eg(new ao(c));if(b instanceof J){var d=b.Z;a=new ao(c);b=new ao(b.Fa);if(d===N())d=N();else{c=d.u();var f=c=new J(new ao(c),N());for(d=d.A();d!==N();){var g=d.u();g=new J(new ao(g),N());f=f.Z=g;d=d.A()}d=c}return new Eg(new kn(a,d,b))}}throw new D(a);}return b instanceof Un?(b=b.rq,a=ML(a.DA,b),N().d(a)? +new Dg("Error tokenizing Phrase '"+b+"' during query analysis"):new Eg(new Un(Dh(a,""," ","")))):new Eg(b)}function Ap(a,b){this.DA=null;this.EA=a;this.CA=b;this.DA=b.g(a)}Ap.prototype=new t;Ap.prototype.constructor=Ap;e=Ap.prototype;e.x=function(){return new Z(this)};e.p=function(){return xx(this,-1623997882)};e.d=function(a){if(this===a)return!0;if(a instanceof Ap&&this.EA===a.EA){var b=this.CA;a=a.CA;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2}; +e.v=function(){return"QueryAnalyzer"};e.c=function(a){if(0===a)return this.EA;if(1===a)return this.CA;throw O(new P,""+a);};e.hC=function(a){Hn||(Hn=new Gn);a=Hn.GF.hC(a);return a instanceof Eg?a.ib.Oc(new G(b=>NL(this,b)),(Nq(),new Qq)):a};z(Ap,"pink.cozydev.protosearch.analysis.QueryAnalyzer",{DZ:1,k:1,s:1,b:1});function Sp(a,b,c){this.FA=a;this.kG=b;this.TM=c}Sp.prototype=new t;Sp.prototype.constructor=Sp; +Sp.prototype.ia=function(){var a=this.kG;if(F()===a)return FJ().qo;if(a instanceof E)return a=a.zc|0,OL(this.FA.ia(),a,a>>31);throw new D(a);};Sp.prototype.ka=function(a){return Qx(this.FA,a,this.kG,new eH(this.TM))};Sp.prototype.f=function(){return"arrayCodec("+this.FA+")"};z(Sp,"pink.cozydev.protosearch.codecs.ArrayCodec",{FZ:1,Rb:1,lb:1,Qb:1});function Ko(a){this.bl=a;a=a.cl;this.UM=(a+(a>>>31|0)|0)>>1}Ko.prototype=new t;Ko.prototype.constructor=Ko;e=Ko.prototype;e.x=function(){return new Z(this)}; +e.p=function(){return xx(this,1892259497)};e.d=function(a){if(this===a)return!0;if(a instanceof Ko){var b=this.bl;a=a.bl;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"FirstMatchHighlighter"};e.c=function(a){if(0===a)return this.bl;throw O(new P,""+a);};function DL(a,b){b=CL(b);return b.length>a.bl.cl?Ds(Cd(),b,a.bl.cl)+"...":b}z(Ko,"pink.cozydev.protosearch.highlight.FirstMatchHighlighter",{HZ:1,k:1,s:1,b:1}); +function Lo(a,b,c){this.cl=a;this.Iq=b;this.Hq=c}Lo.prototype=new t;Lo.prototype.constructor=Lo;e=Lo.prototype;e.x=function(){return new Z(this)};e.p=function(){var a=-889275714;a=X().m(a,-1818245062);a=X().m(a,this.cl);a=X().m(a,zw(X(),this.Iq));a=X().m(a,zw(X(),this.Hq));return X().T(a,3)};e.d=function(a){return this===a||a instanceof Lo&&this.cl===a.cl&&this.Iq===a.Iq&&this.Hq===a.Hq};e.f=function(){return nw(this)};e.t=function(){return 3};e.v=function(){return"FragmentFormatter"}; +e.c=function(a){switch(a){case 0:return this.cl;case 1:return this.Iq;case 2:return this.Hq;default:throw O(new P,""+a);}}; +function EL(a,b,c){if(0===c.X())return b;var d=c.Wg(fq()),f=d.a.length,g=f>>>31|0;if(0!==((1&(f+g|0))-g|0))throw iw(),Ik(new Jk,"assertion failed: even number of offsets required");f=pi();c.X();b=CD(b);try{PL(f,b,0,d.a[0]);var h=d.a[0];for(c=0;cF())}vr.prototype=new t;vr.prototype.constructor=vr;e=vr.prototype;e.af=function(a,b){return KC(this,a,b)};e.f=function(){return"\x3cfunction1\x3e"};e.sf=function(){return!1};e.rB=function(a){throw new D(a);};e.ql=function(){return this.kI};e.ld=function(){return this}; +e.g=function(a){this.rB(a)};z(vr,"scala.PartialFunction$$anon$1",{h3:1,ja:1,U:1,b:1});function JC(a,b){this.lC=a;this.lI=b}JC.prototype=new t;JC.prototype.constructor=JC;e=JC.prototype;e.ql=function(){return new tJ(this)};e.f=function(){return"\x3cfunction1\x3e"};e.sf=function(a){return this.lC.sf(a)};e.g=function(a){return this.lI.g(this.lC.g(a))};e.af=function(a,b){var c=this.lC.af(a,ur().Om);return wr(ur(),c)?b.g(a):this.lI.g(c)};e.ld=function(a){return HC(this,a)}; +z(JC,"scala.PartialFunction$AndThen",{i3:1,ja:1,U:1,b:1});function IC(a,b){this.nC=a;this.mC=b}IC.prototype=new t;IC.prototype.constructor=IC;e=IC.prototype;e.ql=function(){return new tJ(this)};e.f=function(){return"\x3cfunction1\x3e"};e.sf=function(a){a=this.nC.af(a,ur().Om);return!wr(ur(),a)&&this.mC.sf(a)};e.g=function(a){return this.mC.g(this.nC.g(a))};e.af=function(a,b){var c=this.nC.af(a,ur().Om);return wr(ur(),c)?b.g(a):this.mC.af(c,new Zo(()=>b.g(a)))};e.ld=function(a){return HC(this,a)}; +z(IC,"scala.PartialFunction$Combined",{j3:1,ja:1,U:1,b:1});function tJ(a){this.nO=a}tJ.prototype=new ZD;tJ.prototype.constructor=tJ;tJ.prototype.uB=function(a){a=this.nO.af(a,ur().Om);return wr(ur(),a)?F():new E(a)};tJ.prototype.g=function(a){return this.uB(a)};z(tJ,"scala.PartialFunction$Lifted",{k3:1,Az:1,U:1,b:1});function SL(){}SL.prototype=new t;SL.prototype.constructor=SL;function TL(){}e=TL.prototype=SL.prototype;e.i=function(){return this};e.cj=function(a){return oH(this,a)}; +e.Mw=function(a){return this.Sg(0,0WL().NI)}UL.prototype=new yH; +UL.prototype.constructor=UL;z(UL,"scala.collection.Map$",{J5:1,TO:1,Xy:1,b:1});var VL;function WL(){VL||(VL=new UL);return VL}function XL(){this.Cf=null}XL.prototype=new t;XL.prototype.constructor=XL;function YL(){}e=YL.prototype=XL.prototype;e.Jm=function(a){return this.Cf.mb(a)};e.Da=function(){return this.Cf.Da()};e.mb=function(a){return this.Jm(a)};e.Sd=function(){return this.Cf.Sd()};e.wg=function(a){return this.Cf.wg(a)}; +function ZL(a,b){var c=a.Kb(),d=c.mb,f=new $L;f.cn=a;f.op=b;return d.call(c,f)}function tF(a){return a.Rd(new Zo(b=>b))}function aM(a,b){return a.Vd(new bM(a,b))}function cM(a,b){return 0<=b&&0V(W(),b,c)),0)}function fI(a,b){return a.ll(new Zo(c=>V(W(),c,b)))}function LL(a,b){var c=a.q(),d=a.xf();if(1===c)d.Ia(a.u());else if(1()=>{for(var f=new fE(null),g=!1,h=new fE(d.Gc);!g&&pM(h.Gc)!==Q().ua;)f.Gc=c.g(h.Gc.u()).i(),g=f.Gc.n(),g||(h.Gc=qM(h.Gc),d.Gc=h.Gc);return g?(g=f.Gc.j(),h.Gc=qM(h.Gc),d.Gc=h.Gc,Q(),Ps(new Os,g,(Q(),Ns(new Os,new Rh(()=>rM(Q(),f.Gc,new Rh(()=>oM(Q(),h.Gc,c)))))))):Q().ua})(new fE(b))))}function sM(a,b,c){return Ns(new Os,new Rh(((d,f)=>()=>{for(var g=d.Gc,h=f.lc;0tM(Q(),b.i())))}function rM(a,b,c){return b.n()?Ps(new Os,b.j(),Ns(new Os,new Rh(()=>rM(Q(),b,c)))):c.za()}function tM(a,b){return b.n()?Ps(new Os,b.j(),Ns(new Os,new Rh(()=>tM(Q(),b)))):a.ua}mM.prototype.Da=function(){return new uM};mM.prototype.Sd=function(){return this.ua};mM.prototype.mb=function(a){return FH(this,a)};z(mM,"scala.collection.immutable.LazyList$",{M6:1,fg:1,Gd:1,b:1});var nM; +function Q(){nM||(nM=new mM);return nM}function vM(){}vM.prototype=new t;vM.prototype.constructor=vM;vM.prototype.wg=function(a){return wM(0,a)};function wM(a,b){return b instanceof xM?b:yM(0,b.i())}function yM(a,b){return b.n()?new zM(b.j(),new Rh(()=>yM(Mv(),b))):AM()}vM.prototype.Da=function(){return new GH((cr(),new HH),new Zo(a=>wM(Mv(),a)))};vM.prototype.Sd=function(){return AM()};vM.prototype.mb=function(a){return wM(0,a)};z(vM,"scala.collection.immutable.Stream$",{T7:1,fg:1,Gd:1,b:1});var BM; +function Mv(){BM||(BM=new vM);return BM}function CM(){}CM.prototype=new t;CM.prototype.constructor=CM;CM.prototype.gi=function(a,b){return DM(0,a,b)};function DM(a,b,c){if(b instanceof EM&&(a=b.Ba,null===c?null===a:c.d(a)))return b;if(b&&b.$classData&&b.$classData.wb.WI&&(a=b.Ba,null===c?null===a:c.d(a))){a=new EM;var d=Bu(),f=new Tg(b.Ta,F(),b.Ba);b=Nt(Bu(),b.Ta);b=ru(d,1,b,32-Math.clz32(b)|0,f);return FM(a,b,c)}a=null;for(b=b.i();b.n();)a=yu(Bu(),a,b.j(),null,!1,c);return FM(new EM,a,c)} +CM.prototype.Xv=function(a){return new Sg(a)};z(CM,"scala.collection.immutable.TreeSet$",{c8:1,WO:1,zC:1,b:1});var GM;function HM(){GM||(GM=new CM);return GM}function IM(){}IM.prototype=new t;IM.prototype.constructor=IM;function JM(a,b){a=a.Da();YI(a,b,0);a.gc(b);return a.Sa()}IM.prototype.Da=function(){return new GH(pi(),new Zo(a=>new GC(a)))};z(IM,"scala.collection.immutable.WrappedString$",{r8:1,U5:1,FI:1,b:1});var KM;function LM(){KM||(KM=new IM);return KM} +function GH(a,b){this.vz=a;this.wP=b}GH.prototype=new t;GH.prototype.constructor=GH;GH.prototype.Fc=function(a){this.vz.Fc(a)};GH.prototype.Sa=function(){return this.wP.g(this.vz.Sa())};GH.prototype.gc=function(a){this.vz.gc(a);return this};GH.prototype.Ia=function(a){this.vz.Ia(a);return this};z(GH,"scala.collection.mutable.Builder$$anon$1",{O8:1,ae:1,ud:1,td:1});function MM(a,b){a.Eh=b;return a}function NM(){this.Eh=null}NM.prototype=new t;NM.prototype.constructor=NM;function OM(){} +OM.prototype=NM.prototype;NM.prototype.Fc=function(){};NM.prototype.gc=function(a){this.Eh.gc(a);return this};NM.prototype.Ia=function(a){this.Eh.Ia(a);return this};NM.prototype.Sa=function(){return this.Eh};z(NM,"scala.collection.mutable.GrowableBuilder",{wz:1,ae:1,ud:1,td:1});function PM(){this.ip=null;this.ip=cr()}PM.prototype=new hH;PM.prototype.constructor=PM;z(PM,"scala.collection.mutable.Iterable$",{j9:1,KO:1,Gd:1,b:1});var QM;function Ph(){this.Vm=null;this.Vm=eJ()}Ph.prototype=new yH; +Ph.prototype.constructor=Ph;z(Ph,"scala.collection.mutable.Map$",{m9:1,TO:1,Xy:1,b:1});var Oh;function pg(a){this.BP=a}pg.prototype=new vJ;pg.prototype.constructor=pg;pg.prototype.za=function(){return(0,this.BP)()};z(pg,"scala.scalajs.runtime.AnonFunction0.$$Lambda$92a2e254bbb9c06a0a02fc31abab59c51c18ecc1",{B9:1,A9:1,EP:1,Rw:1});function G(a){this.CP=a}G.prototype=new xJ;G.prototype.constructor=G;G.prototype.g=function(a){return(0,this.CP)(a)}; +z(G,"scala.scalajs.runtime.AnonFunction1.$$Lambda$3aa60c34ef08a878abffbf4628007cc68fa3c7ab",{D9:1,C9:1,Az:1,U:1});function Qb(a){this.DP=a}Qb.prototype=new zJ;Qb.prototype.constructor=Qb;Qb.prototype.Ca=function(a,b){return(0,this.DP)(a,b)};z(Qb,"scala.scalajs.runtime.AnonFunction2.$$Lambda$1a8112ad760bd31301975c22c9537bb38341e0c2",{F9:1,E9:1,nD:1,Sw:1});function RM(){}RM.prototype=new t;RM.prototype.constructor=RM;function SM(){}SM.prototype=RM.prototype;function TM(){}TM.prototype=new t; +TM.prototype.constructor=TM;function UM(){}UM.prototype=TM.prototype;TM.prototype.x=function(){return new Z(this)};function by(a,b,c){this.OA=null;this.gN=b;if(null===c)throw Nr();this.OA=c}by.prototype=new t;by.prototype.constructor=by;by.prototype.ia=function(){return this.OA.ia()};by.prototype.ka=function(a){return(new rE(this.gN,this.OA)).ka(a)};z(by,"scodec.Codec$$anon$1",{k_:1,Rb:1,lb:1,Qb:1});function to(a,b,c){this.PA=null;this.hN=b;if(null===c)throw Nr();this.PA=c}to.prototype=new t; +to.prototype.constructor=to;to.prototype.ia=function(){return this.PA.ia()};to.prototype.ka=function(a){return this.PA.ka(a).jj(new G(b=>pE(b,this.hN)))};z(to,"scodec.Codec$$anon$2",{l_:1,Rb:1,lb:1,Qb:1});function Rp(a,b){this.QA=null;this.iN=a;if(null===b)throw Nr();this.QA=b}Rp.prototype=new t;Rp.prototype.constructor=Rp;Rp.prototype.ia=function(){return VM(this.QA.ia())};Rp.prototype.ka=function(a){return(new qE(new G(b=>new oE(new G(c=>new C(b,c)),this.iN.g(b))),this.QA)).ka(a)}; +z(Rp,"scodec.Codec$$anon$3",{m_:1,Rb:1,lb:1,Qb:1});function qo(a,b){this.Rx=null;this.tG=a;if(null===b)throw Nr();this.Rx=b}qo.prototype=new t;qo.prototype.constructor=qo;qo.prototype.ia=function(){return this.Rx.ia()};qo.prototype.ka=function(a){return this.Rx.ka(a).eI(new G(b=>b.Dy(this.tG)))};qo.prototype.f=function(){return this.tG+"("+this.Rx+")"};z(qo,"scodec.Codec$$anon$7",{n_:1,Rb:1,lb:1,Qb:1});function Op(a,b){this.RA=null;this.jN=a;if(null===b)throw Nr();this.RA=b}Op.prototype=new t; +Op.prototype.constructor=Op;Op.prototype.ia=function(){return this.RA.ia()};Op.prototype.ka=function(a){return this.RA.ka(a)};Op.prototype.f=function(){return this.jN.za()};z(Op,"scodec.Codec$$anon$8",{o_:1,Rb:1,lb:1,Qb:1});function xo(a,b){this.SA=a;this.TA=b}xo.prototype=new t;xo.prototype.constructor=xo;xo.prototype.ia=function(){return WM(this.SA.ia(),this.TA.ia())};xo.prototype.ka=function(a){return Sx(CJ(),this.SA,this.TA,a).jj(new G(b=>{var c=b.Zf;return new Rx($w(bx(),c.Y,c.V),b.$g)}))}; +xo.prototype.f=function(){return this.SA+" :: "+this.TA};z(xo,"scodec.Codec$inlineImplementations$ConsCodec",{q_:1,Rb:1,lb:1,Qb:1});function yo(a,b){this.UA=a;this.VA=b}yo.prototype=new t;yo.prototype.constructor=yo;yo.prototype.ia=function(){return WM(this.UA.ia(),this.VA.ia())};yo.prototype.ka=function(a){return Sx(CJ(),this.UA,this.VA,a)};yo.prototype.f=function(){return this.UA+" :: "+this.VA};z(yo,"scodec.Codec$inlineImplementations$PairCodec",{r_:1,Rb:1,lb:1,Qb:1}); +function Rx(a,b){this.Zf=a;this.$g=b}Rx.prototype=new t;Rx.prototype.constructor=Rx;e=Rx.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Rx&&V(W(),this.Zf,a.Zf)){var b=this.$g;a=a.$g;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"DecodeResult"};e.c=function(a){if(0===a)return this.Zf;if(1===a)return this.$g;throw O(new P,""+a);}; +function pE(a,b){return new Rx(b.g(a.Zf),a.$g)}z(Rx,"scodec.DecodeResult",{s_:1,k:1,s:1,b:1});function GJ(a,b,c){this.te=a;this.se=b;this.ah=c;GG(Ah(),0<=b);Ah();var d=ab(c.e()?r(0,0):c.oa());GG(0,0<=d.l);Ah();d=ab(c.e()?r(a,b):c.oa());c=d.h;d=d.l;GG(0,d===b?c>>>0>=a>>>0:d>b)}GJ.prototype=new t;GJ.prototype.constructor=GJ;e=GJ.prototype;e.x=function(){return new Z(this)}; +e.p=function(){var a=-889275714;a=X().m(a,Ha("SizeBound"));a=X().m(a,xw(X(),this.te,this.se));a=X().m(a,zw(X(),this.ah));return X().T(a,2)};e.d=function(a){if(this===a)return!0;if(a instanceof GJ&&0===(this.te^a.te|this.se^a.se)){var b=this.ah;a=a.ah;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"SizeBound"};e.c=function(a){if(0===a)return r(this.te,this.se);if(1===a)return this.ah;throw O(new P,""+a);}; +function Up(a){var b=a.ah;return b instanceof E&&(b=ab(b.zc),0===(a.te^b.h|a.se^b.l))?a.ah:F()}function VM(a){var b=F();return new GJ(a.te,a.se,b)}function WM(a,b){FJ();var c=a.te,d=b.te,f=c+d|0;c=(a.se+b.se|0)+((c&d|(c|d)&~f)>>>31|0)|0;a=a.ah;if(a.e())b=F();else if(a=a.oa(),d=ab(a),a=d.h,d=d.l,b=b.ah,b.e())b=F();else{b=b.oa();b=ab(b);var g=b.h,h=a+g|0;b=new E(r(h,(d+b.l|0)+((a&g|(a|g)&~h)>>>31|0)|0))}return new GJ(f,c,b)} +function OL(a,b,c){FJ();var d=a.te,f=a.se,g=65535&d,h=d>>>16|0,k=65535&b,m=b>>>16|0,n=Math.imul(g,k);k=Math.imul(h,k);var q=Math.imul(g,m);g=n+((k+q|0)<<16)|0;n=(n>>>16|0)+q|0;d=(((Math.imul(d,c)+Math.imul(f,b)|0)+Math.imul(h,m)|0)+(n>>>16|0)|0)+(((65535&n)+k|0)>>>16|0)|0;a=a.ah;if(a.e())b=F();else{a=a.oa();f=ab(a);a=f.h;f=f.l;q=65535&a;h=a>>>16|0;k=65535&b;m=b>>>16|0;n=Math.imul(q,k);k=Math.imul(h,k);var u=Math.imul(q,m);q=n+((k+u|0)<<16)|0;n=(n>>>16|0)+u|0;b=(((Math.imul(a,c)+Math.imul(f,b)|0)+ +Math.imul(h,m)|0)+(n>>>16|0)|0)+(((65535&n)+k|0)>>>16|0)|0;b=new E(r(q,b))}return new GJ(g,d,b)}e.f=function(){var a=this.ah;if(a instanceof E){var b=ab(a.zc);a=b.h;b=b.l;return 0===(this.te^a|this.se^b)?vm(qj(),a,b):"["+da(this.te,this.se)+", "+da(a,b)+"]"}if(F()===a)return"["+da(this.te,this.se)+", \u221e)";throw new D(a);};z(GJ,"scodec.SizeBound",{G_:1,k:1,s:1,b:1});function XM(){this.WA=null;YM=this;this.WA=new db(new Uint16Array([48,49,50,51,52,53,54,55,56,57,97,98,99,100,101,102]))} +XM.prototype=new LJ;XM.prototype.constructor=XM;z(XM,"scodec.bits.Bases$Alphabets$HexLowercase$",{K_:1,L_:1,J_:1,M_:1});var YM;function ZM(){YM||(YM=new XM);return YM}function mp(a,b,c,d,f){this.bi=a;this.di=b;this.ci=c;this.vg=d;this.ug=f}mp.prototype=new t;mp.prototype.constructor=mp;e=mp.prototype;e.x=function(){return new Z(this)}; +e.p=function(){var a=-889275714;a=X().m(a,Ha("View"));a=X().m(a,zw(X(),this.bi));a=X().m(a,xw(X(),this.di,this.ci));a=X().m(a,xw(X(),this.vg,this.ug));return X().T(a,3)};e.d=function(a){if(this===a)return!0;if(a instanceof mp&&0===(this.di^a.di|this.ci^a.ci)&&0===(this.vg^a.vg|this.ug^a.ug)){var b=this.bi;a=a.bi;return null===b?null===a:b===a}return!1};e.f=function(){return nw(this)};e.t=function(){return 3};e.v=function(){return"View"}; +e.c=function(a){switch(a){case 0:return this.bi;case 1:return r(this.di,this.ci);case 2:return r(this.vg,this.ug);default:throw O(new P,""+a);}};e.fi=function(a,b){var c=this.di,d=c+a|0;return this.bi.fi(d,(this.ci+b|0)+((c&a|(c|a)&~d)>>>31|0)|0)};e.iy=function(a,b){this.bi.hy(a,b,this.di,this.ci,RJ(gp(),this.vg,this.ug))};z(mp,"scodec.bits.ByteVector$View",{k0:1,k:1,s:1,b:1}); +function Fy(a,b,c){this.Bo=this.Co=0;this.yN=a;this.CG=b;this.zN=c;if(!(0>31)|0;if(0<=((g^k)&(g^f))){OD(Vv(),m,f);break a}}be(Vv(),Cj(Ej(),bN(c),bN(d)))}b?(b=$M(OD(ae(),1,0),a-1|0),aN(b)?(c=Vv(),d=b.Ac,g=-d|0,OD(c,g,(-b.Xb|0)+((d|g)>>31)|0)):be(Vv(),Dj(bN(b)))):ND(ae(),0);this.Co=a;this.Bo=a>>31} +Fy.prototype=new t;Fy.prototype.constructor=Fy;Fy.prototype.ia=function(){return HJ(FJ(),this.Co,this.Bo)}; +Fy.prototype.ka=function(a){if(cN(a,this.Co,this.Bo)){a:{var b=a.jd(this.Co,this.Bo);for(var c=this.CG,d=this.zN;;){var f=Gy();if(null!==d&&d===f)b=dN(b),d=Cy();else{d=b;f=b.z();b=d;d=f.h;f=c;Ah();c=0+d|0;GG(0,cN(b,c,(d>>31|0)+((0&d|(0|d)&~c)>>>31|0)|0)&&0<=d);if(0===d)b=ND(ae(),0);else{var g=b.ji(0,0);c=f&&g;f=!f&&g?rp().AG:rp().$h;var h=d>>31,k=f.z(),m=k.h;g=d+m|0;h=((h+k.l|0)+((d&m|(d|m)&~g)>>>31|0)|0)>>2>>>29|0;k=7&(g+h|0);g=k-h|0;k=(~k&h|~(k^h)&g)>>31;0===(g|k)?c=rp().$h:(rp(),h=m=8-g|0,g=(-k| +0)+((g|m)>>31)|0,k=eN(0,h,g),c=fN(QJ(k.h,k.l,c?-1:0),h,g));c=f.Dj(c);h=0+d|0;f=c.Dj;g=h;d=(d>>31|0)+((0&d|(0|d)&~h)>>>31|0)|0;b=b.Sb(0,0);h=g-0|0;b=b.jd(h,(d-0|0)+((~g&0|~(g^0)&h)>>31)|0);d=gN(zy(f.call(c,b)));b=ae();c=new iB;$K(c);if(0===d.a.length)throw new Rn("Zero length BigInteger");if(0>d.a[0]){c.ca=-1;g=d.a.length;h=3&g;c.fa=(g>>2)+(0===h?0:1)|0;c.N=new y(c.fa);f=0;for(c.N.a[c.fa-1|0]=-1;;){if(g>h)if(c.N.a[f]=255&d.a[g-1|0]|(255&d.a[g-2|0])<<8|(255&d.a[g-3|0])<<16|d.a[g-4|0]<<24,g=g-4|0,0!== +c.N.a[f])for(c.N.a[f]=-c.N.a[f]|0,c.ao=f,f=1+f|0;g>h;)c.N.a[f]=255&d.a[g-1|0]|(255&d.a[g-2|0])<<8|(255&d.a[g-3|0])<<16|d.a[g-4|0]<<24,g=g-4|0,c.N.a[f]=~c.N.a[f],f=1+f|0;else{f=1+f|0;continue}break}if(0!==h)if(-2!==c.ao){for(h=0;h>2)+(0===h?0:1)|0;c.N=new y(c.fa);for(f=0;g>h;)c.N.a[f]=255&d.a[g-1|0]|(255&d.a[g-2|0])<<8| +(255&d.a[g-3|0])<<16|d.a[g-4|0]<<24,g=g-4|0,f=1+f|0;for(h=0;h>31)}; +Ay.prototype.ka=function(a){var b=this.Qq;b=hN(a,b,b>>31);if(b instanceof Dg){b=this.Qq;var c=b>>31;a=a.z();return new Vp(Wp(b,c,a.h,a.l))}if(b instanceof Eg)return b=iN(b.ib,this.DG),c=this.Qq,new Tp(new Rx(b,a.Sb(c,c>>31)));throw new D(b);};Ay.prototype.f=function(){return this.Qq+"-bit "+(this.DG?"signed":"unsigned")+" byte"};z(Ay,"scodec.codecs.ByteCodec",{q0:1,Rb:1,lb:1,Qb:1});function $y(a,b){this.zm=a;this.AN=b}$y.prototype=new t;$y.prototype.constructor=$y; +$y.prototype.ia=function(){FJ();var a=this.zm.z();return HJ(0,a.h,a.l)};$y.prototype.ka=function(a){var b=this.zm.z();b=hN(a,b.h,b.l);if(b instanceof Dg)return b=this.zm.z(),a=a.z(),new Vp(Wp(b.h,b.l,a.h,a.l));if(b instanceof Eg){b=b.ib;if(this.AN){var c=this.zm;c=null===b?null===c:b.d(c)}else c=!0;return c?(b=this.zm.z(),new Tp(new Rx(void 0,a.Sb(b.h,b.l)))):new Vp(cz(new dz,"expected constant "+this.zm+" but got "+b))}throw new D(b);};$y.prototype.f=function(){return"constant("+this.zm+")"}; +z($y,"scodec.codecs.ConstantCodec",{r0:1,Rb:1,lb:1,Qb:1});function jN(a,b){this.Yx=a;this.Do=b}jN.prototype=new t;jN.prototype.constructor=jN;e=jN.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof jN&&V(W(),this.Yx,a.Yx)){var b=this.Do;a=a.Do;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Case"}; +e.c=function(a){if(0===a)return this.Yx;if(1===a)return this.Do;throw O(new P,""+a);};z(jN,"scodec.codecs.DiscriminatorCodec$Case",{t0:1,k:1,s:1,b:1});function kN(a,b,c){this.bB=a;this.Zx=b;this.Rq=c}kN.prototype=new t;kN.prototype.constructor=kN;e=kN.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof kN){var b=this.bB,c=a.bB;(null===b?null===c:b.d(c))?(b=this.Zx,c=a.Zx,b=null===b?null===c:b.d(c)):b=!1;if(b)return b=this.Rq,a=a.Rq,null===b?null===a:b===a}return!1};e.f=function(){return nw(this)};e.t=function(){return 3};e.v=function(){return"Prism"};e.c=function(a){switch(a){case 0:return this.bB;case 1:return this.Zx;case 2:return this.Rq;default:throw O(new P,""+a);}};z(kN,"scodec.codecs.DiscriminatorCodec$Prism",{u0:1,k:1,s:1,b:1}); +function My(a){this.FG=null;this.FG=a.Jl()}My.prototype=new t;My.prototype.constructor=My;My.prototype.ia=function(){return HJ(FJ(),64,0)};My.prototype.ka=function(a){var b=hN(a,64,0);if(b instanceof Dg)return a=a.z(),new Vp(Wp(64,0,a.h,a.l));if(b instanceof Eg)return b=gN(zy(b.ib)),new Tp(new Rx(aG(nk(qk(),b,b.a.length,0,b.a.length),this.FG).xH(),a.Sb(64,0)));throw new D(b);};My.prototype.f=function(){return"double"};z(My,"scodec.codecs.DoubleCodec",{v0:1,Rb:1,lb:1,Qb:1}); +function lN(a,b,c){this.Fo=a;this.Eo=b;this.GG=c}lN.prototype=new t;lN.prototype.constructor=lN;lN.prototype.ia=function(){return HJ(FJ(),this.Fo,this.Eo)};lN.prototype.ka=function(a){if(cN(a,this.Fo,this.Eo))return this.GG.ka(a.jd(this.Fo,this.Eo)).jj(new G(f=>new Rx(f.Zf,a.Sb(this.Fo,this.Eo))));var b=this.Fo,c=this.Eo,d=a.z();return new Vp(Wp(b,c,d.h,d.l))};lN.prototype.f=function(){return"fixedSizeBits("+da(this.Fo,this.Eo)+", "+this.GG+")"}; +z(lN,"scodec.codecs.FixedSizeCodec",{w0:1,Rb:1,lb:1,Qb:1});function Ly(a){this.HG=null;this.HG=a.Jl()}Ly.prototype=new t;Ly.prototype.constructor=Ly;Ly.prototype.ia=function(){return HJ(FJ(),32,0)};Ly.prototype.ka=function(a){var b=hN(a,32,0);if(b instanceof Dg)return a=a.z(),new Vp(Wp(32,0,a.h,a.l));if(b instanceof Eg)return b=gN(zy(b.ib)),new Tp(new Rx(aG(nk(qk(),b,b.a.length,0,b.a.length),this.HG).yH(),a.Sb(32,0)));throw new D(b);};Ly.prototype.f=function(){return"float"}; +z(Ly,"scodec.codecs.FloatCodec",{x0:1,Rb:1,lb:1,Qb:1});function Dy(a,b,c){this.Go=this.Ho=0;this.BN=a;this.IG=b;this.CN=c;if(!(0>31}Dy.prototype=new t;Dy.prototype.constructor=Dy;Dy.prototype.ia=function(){return HJ(FJ(),this.Ho,this.Go)}; +Dy.prototype.ka=function(a){if(cN(a,this.Ho,this.Go))return new Tp(new Rx(mN(a.jd(this.Ho,this.Go),this.IG,this.CN),a.Sb(this.Ho,this.Go)));var b=this.Ho,c=this.Go;a=a.z();return new Vp(Wp(b,c,a.h,a.l))};Dy.prototype.f=function(){return this.BN+"-bit "+(this.IG?"signed":"unsigned")+" integer"};z(Dy,"scodec.codecs.IntCodec",{y0:1,Rb:1,lb:1,Qb:1});function ez(a,b){this.dB=a;this.JG=b}ez.prototype=new t;ez.prototype.constructor=ez; +ez.prototype.ia=function(){var a=this.JG;if(F()===a)return FJ().qo;if(a instanceof E)return a=a.zc|0,OL(this.dB.ia(),a,a>>31);throw new D(a);};ez.prototype.ka=function(a){return Qx(this.dB,a,this.JG,new iH(Ih()))};ez.prototype.f=function(){return"list("+this.dB+")"};z(ez,"scodec.codecs.ListCodec",{B0:1,Rb:1,lb:1,Qb:1}); +function Ey(a,b,c){this.Ko=this.Lo=0;this.DN=a;this.KG=b;this.EN=c;if(!(0>31}Ey.prototype=new t;Ey.prototype.constructor=Ey;Ey.prototype.ia=function(){return HJ(FJ(),this.Lo,this.Ko)}; +Ey.prototype.ka=function(a){if(cN(a,this.Lo,this.Ko)){var b=nN(a.jd(this.Lo,this.Ko),this.KG,this.EN);return new Tp(new Rx(b,a.Sb(this.Lo,this.Ko)))}b=this.Lo;var c=this.Ko;a=a.z();return new Vp(Wp(b,c,a.h,a.l))};Ey.prototype.f=function(){return this.DN+"-bit "+(this.KG?"signed":"unsigned")+" integer"};z(Ey,"scodec.codecs.LongCodec",{C0:1,Rb:1,lb:1,Qb:1}); +function By(a,b,c){this.Tq=this.Uq=0;this.LG=a;this.MG=b;this.FN=c;if(!(0>31}By.prototype=new t;By.prototype.constructor=By;By.prototype.ia=function(){FJ();var a=this.LG;return HJ(0,a,a>>31)}; +By.prototype.ka=function(a){if(cN(a,this.Uq,this.Tq)){a:for(var b=a.jd(this.Uq,this.Tq),c=this.MG,d=this.FN;;){GG(Ah(),oN(b,16,0));var f=Gy();if(null!==d&&d===f)b=dN(b),d=Cy();else{d=b;f=b.z();b=d;d=f.h;Ah();f=0+d|0;GG(0,cN(b,f,(d>>31|0)+((0&d|(0|d)&~f)>>>31|0)|0)&&0<=d&&16>=d);f=d>>2>>>29|0;var g=(7&(d+f|0))-f|0;f=0;var h=eN(rp(),d,d>>31),k=h.h;h=h.l;for(var m=0;;){var n=m,q=n>>31;if(q===h?n>>>0>>0:q>31|0)+((0&n|(0|n)&~q)>>>31|0)|0),m=1+m|0;else break}0!==g&& +(f=f>>>(8-g|0)|0);c&&16!==d&&0!==(1<<(d-1|0)&f)&&(b=32-d|0,f=f<>b);break a}}return new Tp(new Rx(f<<16>>16,a.Sb(this.Uq,this.Tq)))}b=this.Uq;d=this.Tq;a=a.z();return new Vp(Wp(b,d,a.h,a.l))};By.prototype.f=function(){return this.LG+"-bit "+(this.MG?"signed":"unsigned")+" short"};z(By,"scodec.codecs.ShortCodec",{E0:1,Rb:1,lb:1,Qb:1});function Py(a){this.eB=a}Py.prototype=new t;Py.prototype.constructor=Py;Py.prototype.ia=function(){return FJ().qo}; +Py.prototype.ka=function(a){var b=this.eB.fI();null===b&&hw();try{var c=gN(zy(a)),d=nk(qk(),c,c.a.length,0,c.a.length);b.Yk=1;for(var f=ik(Ma((d.aa-d.L|0)*b.qx)),g;;){a:{c=b;var h=d,k=f;if(4===c.Yk)throw pN();for(c.Yk=3;;){try{var m=c.oH(h,k)}catch(ma){if(ma instanceof Ek)throw new bL(ma);if(ma instanceof Fk)throw new bL(ma);throw ma;}if(0===m.Zi){var n=h.aa-h.L|0;if(0U)throw SB();Zj.prototype.yb.call(h,aa+U|0)}else{var ka=zk().vx;if(null===ka?null===M:ka===M){K=u;break a}var ra=zk().DF;if(null===ra?null===M:ra===M){var ha=h.L,fb=u.ux;if(0>fb)throw SB();Zj.prototype.yb.call(h,ha+fb|0)}else throw Ik(new Jk, +M);}}}if(0!==K.Zi){if(1===K.Zi){f=wk(f);continue}Dk(K);throw Ik(new Jk,"should not get here");}if(d.L!==d.aa){var xb=new Jk;ft(xb,null);throw xb;}g=f;break}for(var pb;;){a:switch(d=b,d.Yk){case 3:var Ra=Mk().sg;0===Ra.Zi&&(d.Yk=4);var xc=Ra;break a;case 4:xc=Mk().sg;break a;default:throw pN();}if(0!==xc.Zi){if(1===xc.Zi){g=wk(g);continue}Dk(xc);throw Ik(new Jk,"should not get here");}pb=g;break}Zj.prototype.vH.call(pb);return new Tp(new Rx(pb.f(),rp().$h))}catch(ma){b=ma instanceof uB?ma:new tB(ma); +a:{if(!(b instanceof Gk||b instanceof Hk))break a;return new Vp(cz(new dz,this.eB.nf+" cannot decode string from '0x"+zy(a).Nw(ZM())+"'"))}throw b instanceof tB?b.Lp:b;}};Py.prototype.f=function(){var a=this.eB.nf;null===a&&hw();return a};z(Py,"scodec.codecs.StringCodec",{F0:1,Rb:1,lb:1,Qb:1});function Uy(){this.OG=this.NG=null;Ty=this;this.NG=new Qb((a,b)=>{a=ab(a);b=ab(b);return new hG(a.h,a.l,b.h,b.l)});this.OG=OL(no().by.ia(),2,0)}Uy.prototype=new t;Uy.prototype.constructor=Uy; +Uy.prototype.ia=function(){return this.OG};Uy.prototype.ka=function(a){return Tx(CJ(),no().by,no().by,a,this.NG)};Uy.prototype.f=function(){return"uuid"};z(Uy,"scodec.codecs.UuidCodec$",{G0:1,Rb:1,lb:1,Qb:1});var Ty;function Hy(a){this.QG=null;a=new Iy(a);var b=new G(c=>ab(c).h);my||(my=new ly);this.QG=new to(my.PG,b,a)}Hy.prototype=new t;Hy.prototype.constructor=Hy;Hy.prototype.ia=function(){return IJ(40)};Hy.prototype.ka=function(a){return this.QG.ka(a)};Hy.prototype.f=function(){return"variable-length integer"}; +z(Hy,"scodec.codecs.VarIntCodec",{H0:1,Rb:1,lb:1,Qb:1});function Jy(a){this.TG=this.fB=null;a=new Hy(a);var b=py().RG;this.fB=new to(py().SG,b,a);this.TG=this.fB.ia()}Jy.prototype=new t;Jy.prototype.constructor=Jy;Jy.prototype.ia=function(){return this.TG};Jy.prototype.ka=function(a){return this.fB.ka(a)};Jy.prototype.f=function(){return"variable-length zig-zag signed integer"};z(Jy,"scodec.codecs.VarIntZigZagCodec",{J0:1,Rb:1,lb:1,Qb:1}); +function Iy(a){this.VG=this.UG=this.XG=this.WG=null;this.WG=a;this.XG=IJ(80);this.UG=new G(b=>{var c;a:for(var d=c=0,f=0,g=sy().Vq<<24>>24;;)if(0!==(g&sy().Vq)){if(b.qe(8,0)){c=b.z();c=new Vp(rN(new sN,8,0,c.h,c.l,N()));break a}var h=g=iN(b.jd(8,0),!1),k=h>>31,m=sy();h&=m.iB;k&=m.hB;m=c;d|=0===(32&m)?h<>>1|0)>>>(31-m|0)|0|k<{a:for(var c=0,d=0,f=sy().Vq<<24>>24;;)if(0!==(f&sy().Vq)){if(b.qe(8, +0)){b=b.z();b=new Vp(rN(new sN,8,0,b.h,b.l,N()));break a}f=iN(b.jd(8,0),!1);var g=c,h=sy().gB;c=0===(32&h)?g<>>1|0)>>>(31-h|0)|0|d<>31;h=sy();c|=k&h.iB;d|=g&h.hB;b=b.Sb(8,0)}else{b=new Tp(new Rx(r(c,d),b));break a}return b})}Iy.prototype=new t;Iy.prototype.constructor=Iy;Iy.prototype.ia=function(){return this.XG};Iy.prototype.ka=function(a){var b=this.WG,c=Cy();return(null!==b&&b===c?this.UG:this.VG).g(a)};Iy.prototype.f=function(){return"variable-length long"}; +z(Iy,"scodec.codecs.VarLongCodec",{L0:1,Rb:1,lb:1,Qb:1});function Ky(a){this.$G=this.jB=null;a=new Iy(a);var b=vy().YG;this.jB=new to(vy().ZG,b,a);this.$G=this.jB.ia()}Ky.prototype=new t;Ky.prototype.constructor=Ky;Ky.prototype.ia=function(){return this.$G};Ky.prototype.ka=function(a){return this.jB.ka(a)};Ky.prototype.f=function(){return"variable-length zig-zag signed long"};z(Ky,"scodec.codecs.VarLongZigZagCodec",{N0:1,Rb:1,lb:1,Qb:1}); +function tN(a,b,c,d){this.aH=this.bH=this.kB=null;this.kB=a;this.bH=b;this.aH=new qE(new G(f=>{var g=ab(f);f=g.h;g=g.l;no();var h=f-c|0;return new lN(h,(g-d|0)+((~f&c|~(f^c)&h)>>31)|0,b)}),a)}tN.prototype=new t;tN.prototype.constructor=tN;tN.prototype.ia=function(){return VM(this.kB.ia())};tN.prototype.ka=function(a){return this.aH.ka(a)};tN.prototype.f=function(){return"variableSizeBits("+this.kB+", "+this.bH+")"};z(tN,"scodec.codecs.VariableSizeCodec",{P0:1,Rb:1,lb:1,Qb:1});function yy(){} +yy.prototype=new t;yy.prototype.constructor=yy;yy.prototype.ia=function(){return FJ().qo};yy.prototype.ka=function(a){return new Tp(new Rx(a,rp().$h))};yy.prototype.f=function(){return"bits"};z(yy,"scodec.codecs.codecs$package$$anon$1",{R0:1,Rb:1,lb:1,Qb:1}); +function bz(a,b,c,d){this.cH=this.mB=this.lB=null;this.lB=a;this.mB=b;no();a=cy(Qp(),a,new G(f=>{f=ab(f);var g=f.h;return r(g<<3,g>>>29|0|f.l<<3)}),new G(f=>{var g=ab(f);f=g.h;g=g.l;no();var h=g>>2>>>29|0,k=7&(f+h|0),m=k-h|0;0===(m|(~k&h|~(k^h)&m)>>31)?(k=g>>2>>>29|0,h=f+k|0,f=g+((f&k|(f|k)&~h)>>>31|0)|0,f=new Tp(r(h>>>3|0|f<<29,f>>3))):f=new Vp(cz(new dz,da(f,g)+" is not evenly divisible by 8"));return f}));this.cH=new tN(a,b,c<<3,c>>>29|0|d<<3)}bz.prototype=new t;bz.prototype.constructor=bz; +bz.prototype.ia=function(){return WM(this.lB.ia(),this.mB.ia())};bz.prototype.ka=function(a){return this.cH.ka(a)};bz.prototype.f=function(){return"variableSizeBytes("+this.lB+", "+this.mB+")"};z(bz,"scodec.codecs.codecs$package$$anon$11",{S0:1,Rb:1,lb:1,Qb:1});function Vy(a,b){this.nB=a;this.dH=b}Vy.prototype=new t;Vy.prototype.constructor=Vy;Vy.prototype.ia=function(){return this.nB.ia()};Vy.prototype.ka=function(a){return this.nB.ka(a).Lv(new G(b=>this.dH.ka(b.Zf).jj(new G((c=>d=>new Rx(d.Zf,d.$g.Dj(c.$g)))(b)))))}; +Vy.prototype.f=function(){return"filtered("+this.dH+", "+this.nB+")"};z(Vy,"scodec.codecs.codecs$package$$anon$20",{T0:1,Rb:1,lb:1,Qb:1});function Wy(a){this.eH=null;this.GN=a;this.eH=rp().zG}Wy.prototype=new t;Wy.prototype.constructor=Wy;Wy.prototype.ia=function(){return FJ().qo}; +Wy.prototype.ka=function(a){var b=zy(a);a:{var c=zy(this.eH),d=0;var f=0;for(b=b.hi(0,0);;){var g=b,h=c,k=h.z();if(uN(g.Hi(k.h,k.l),h)){f=r(d,f);break a}if(b.e()){f=r(-1,-1);break a}b=b.hi(1,0);g=d;d=1+g|0;f=f+((g&~d)>>>31|0)|0}}c=f.h;f=f.l;if(0===(~c|~f))return f=a.z(),d=f.h,c=8+d|0,f=f.l+((d&~c)>>>31|0)|0,a=a.z(),new Vp(rN(new sN,c,f,a.h,a.l,this.GN));d=a.jd(c<<3,c>>>29|0|f<<3);b=c<<3;g=8+b|0;return new Tp(new Rx(d,a.Sb(g,(c>>>29|0|f<<3)+((b&~g)>>>31|0)|0)))}; +z(Wy,"scodec.codecs.codecs$package$$anon$6",{W0:1,Rb:1,lb:1,Qb:1});function XJ(a){this.Jz=a}XJ.prototype=new Ib;XJ.prototype.constructor=XJ;e=XJ.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof XJ){var b=this.Jz;a=a.Jz;return null===b?null===a:b===a}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Ident"};e.c=function(a){if(0===a)return this.Jz;throw O(new P,""+a);}; +z(XJ,"cats.Eval$Ident",{dQ:1,HJ:1,k:1,s:1,b:1});function YJ(a,b){this.Tw=a;this.Uw=b}YJ.prototype=new Ib;YJ.prototype.constructor=YJ;e=YJ.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof YJ){var b=this.Tw,c=a.Tw;if(null===b?null===c:b.d(c))return b=this.Uw,a=a.Uw,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Many"}; +e.c=function(a){if(0===a)return this.Tw;if(1===a)return this.Uw;throw O(new P,""+a);};z(YJ,"cats.Eval$Many",{eQ:1,HJ:1,k:1,s:1,b:1});function vN(){}vN.prototype=new t;vN.prototype.constructor=vN;function wN(){}wN.prototype=vN.prototype;vN.prototype.x=function(){return new Z(this)}; +vN.prototype.g=function(a){a:for(var b=this;;){if(b instanceof xN){a=b.Pf.g(a);break a}if(b instanceof yN){var c=b.Ri,d=b.Si;if(c instanceof xN){a=c.Pf.g(a);b=d;continue}}if(b instanceof yN&&(d=b.Ri,c=b.Si,d instanceof yN)){b:for(b=c;;)if(d instanceof yN)b=new yN(d.Si,b),d=d.Ri;else{b=new yN(d,b);break b}continue}throw new D(b);}return a}; +function yF(a,b){if(b instanceof vN)return zN(AF(),a,b);if(a instanceof xN){var c=a.Pf,d=a.Xg;if(128>d)return new xN(c.ld(b),1+d|0)}if(a instanceof yN){c=a.Ri;var f=a.Si;if(f instanceof xN&&(d=f.Pf,f=f.Xg,128>f))return new yN(c,new xN(d.ld(b),1+f|0))}return new yN(a,new xN(b,0))}vN.prototype.f=function(){return"AndThen$"+Va(this)};vN.prototype.ld=function(a){return yF(this,a)};function AN(){new BN}AN.prototype=new pz;AN.prototype.constructor=AN; +function zF(a,b){return b instanceof vN?b:new xN(b,0)} +function zN(a,b,c){if(b instanceof xN){a=b.Pf;var d=b.Xg;if(c instanceof xN){var f=c.Pf,g=c.Xg;return 128>(d+g|0)?new xN(a.ld(f),1+(d+g|0)|0):new yN(b,c)}if(c instanceof yN){var h=c.Ri;f=c.Si;if(h instanceof xN&&(g=h.Pf,h=h.Xg,128>(d+h|0)))return new yN(new xN(a.ld(g),1+(d+h|0)|0),f)}return new yN(b,c)}if(b instanceof yN&&(a=b.Ri,f=b.Si,f instanceof xN)){d=f.Pf;f=f.Xg;if(c instanceof xN)return g=c.Pf,h=c.Xg,128>(f+h|0)?new yN(a,new xN(d.ld(g),1+(f+h|0)|0)):new yN(b,c);if(c instanceof yN){var k=c.Ri; +g=c.Si;if(k instanceof xN&&(h=k.Pf,k=k.Xg,128>(f+k|0)))return new yN(a,new yN(new xN(d.ld(h),1+(f+k|0)|0),g))}}return new yN(b,c)}z(AN,"cats.data.AndThen$",{VQ:1,YQ:1,$Q:1,Wb:1,ep:1});var CN;function AF(){CN||(CN=new AN);return CN}function uz(a){this.KD=a}uz.prototype=new sJ;uz.prototype.constructor=uz;uz.prototype.sf=function(a){return!!this.KD.g(a)||!1};uz.prototype.af=function(a,b){return this.KD.g(a)?a:b.g(a)};z(uz,"cats.data.Chain$$anon$1",{bR:1,vJ:1,U:1,ja:1,b:1});function DN(){} +DN.prototype=new kK;DN.prototype.constructor=DN;function EN(){}EN.prototype=DN.prototype;function Cm(a,b){this.mc=a;this.fc=b}Cm.prototype=new t;Cm.prototype.constructor=Cm;e=Cm.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Cm&&V(W(),this.mc,a.mc)){var b=this.fc;a=a.fc;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"NonEmptyList"}; +e.c=function(a){if(0===a)return this.mc;if(1===a)return this.fc;throw O(new P,""+a);};e.tf=function(){return this.fc.e()?this.mc:this.fc.tf()};e.X=function(){return 1+this.fc.q()|0};e.Bg=function(a,b){return FN(this.fc,b.Ca(a,this.mc),b)};function Mq(a,b,c){return c.Oe(b.g(a.mc),new UJ(new pg(()=>(wq(),xq().Qz).kf(a.fc,b,c))),new Qb((d,f)=>{oi();return new Cm(d,f)})).Kh()}e.f=function(){return"NonEmpty"+new J(this.mc,this.fc)};z(Cm,"cats.data.NonEmptyList",{NR:1,MR:1,k:1,s:1,b:1}); +function hF(a,b){this.PD=null;this.QD=!1;this.Tp=null;this.Up=b;if(null===a)throw Nr();this.Tp=a}hF.prototype=new t;hF.prototype.constructor=hF;e=hF.prototype;e.f=function(){return"\x3cfunction0\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof hF&&a.Tp===this.Tp){var b=this.Up;a=a.Up;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 1};e.v=function(){return"Deferred"}; +e.c=function(a){if(0===a)return this.Up;throw O(new P,""+a);};e.za=function(){if(!this.QD){a:for(var a=this.Up;;)if(a=a.za(),a instanceof hF&&a.Tp===this.Tp)a=a.Up;else break a;this.PD=a;this.QD=!0}return this.PD.za()};z(hF,"cats.instances.FunctionInstancesBinCompat0$$anon$1$Deferred",{fS:1,Rw:1,k:1,s:1,b:1});function GN(){}GN.prototype=new t;GN.prototype.constructor=GN;z(GN,"cats.instances.package$either$",{yS:1,mE:1,lE:1,kE:1,ND:1});var HN;function Nq(){HN||(HN=new GN)} +function tK(){sK=this;new IN;new rK}tK.prototype=new t;tK.prototype.constructor=tK;z(tK,"cats.instances.package$seq$",{BS:1,kM:1,jM:1,iM:1,WK:1});var sK;function Nc(){}Nc.prototype=new kF;Nc.prototype.constructor=Nc;z(Nc,"cats.kernel.Order$",{QS:1,TS:1,WS:1,fE:1,iE:1});var Mc;function DK(a){this.zL=a}DK.prototype=new t;DK.prototype.constructor=DK;DK.prototype.Pe=function(a,b){return AK(this,a,b)};DK.prototype.Ud=function(a,b){return 0===this.I(a,b)}; +DK.prototype.Le=function(a,b){return 0{var d=a.Yh.substring(b,c);Ob();return new $b(new GK(b,d))})))}a.Wa=b};e.qa=function(a){this.ek(a)}; +z(lf,"cats.parse.Parser$Impl$Not",{dV:1,vb:1,k:1,s:1,b:1});function Ne(a){this.fb=0;this.gb=!1;this.QE=null;this.Ge=a;if(!(0<=a.Qa(2)))throw Qi("requirement failed: expected more than two items, found: "+a.q());if(0<=a.E()){var b=new (B(yh).P)(a.E());Fd(a,b,0,2147483647)}else{b=[];for(a=a.i();a.n();){var c=a.j();b.push(null===c?null:c)}b=new (B(yh).P)(b)}this.QE=b}Ne.prototype=new sh;Ne.prototype.constructor=Ne;e=Ne.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Ne){var b=this.Ge;a=a.Ge;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"OneOf0"};e.c=function(a){if(0===a)return this.Ge;throw O(new P,""+a);};e.qa=function(a){return jg(He(),this.QE,a)};z(Ne,"cats.parse.Parser$Impl$OneOf0",{fV:1,vb:1,k:1,s:1,b:1});function Df(a){this.fb=0;this.gb=!1;this.hx=a}Df.prototype=new sh;Df.prototype.constructor=Df;e=Df.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Df){var b=this.hx;a=a.hx;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Peek"};e.c=function(a){if(0===a)return this.hx;throw O(new P,""+a);};e.ek=function(a){var b=a.Wa;this.hx.qa(a);null===a.bb&&(a.Wa=b)};e.qa=function(a){this.ek(a)};z(Df,"cats.parse.Parser$Impl$Peek",{gV:1,vb:1,k:1,s:1,b:1});function xf(a,b){this.fb=0;this.gb=!1;this.Th=a;this.Uf=b}xf.prototype=new sh; +xf.prototype.constructor=xf;e=xf.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof xf){var b=this.Th,c=a.Th;if(null===b?null===c:b.d(c))return b=this.Uf,a=a.Uf,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Prod0"};e.c=function(a){if(0===a)return this.Th;if(1===a)return this.Uf;throw O(new P,""+a);};e.qa=function(a){return sg(He(),this.Th,this.Uf,a)}; +z(xf,"cats.parse.Parser$Impl$Prod0",{iV:1,vb:1,k:1,s:1,b:1});function kf(a){this.fb=0;this.gb=!1;this.mf=a}kf.prototype=new sh;kf.prototype.constructor=kf;e=kf.prototype;e.x=function(){return new Z(this)};e.d=function(a){return this===a||a instanceof kf&&V(W(),this.mf,a.mf)};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Pure"};e.c=function(a){if(0===a)return this.mf;throw O(new P,""+a);};e.qa=function(){return this.mf}; +z(kf,"cats.parse.Parser$Impl$Pure",{jV:1,vb:1,k:1,s:1,b:1});function xF(){}xF.prototype=new t;xF.prototype.constructor=xF;e=xF.prototype;e.ld=function(a){return pr(this,a)};e.f=function(){return"\x3cfunction1\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof xF||!1};e.t=function(){return 0};e.v=function(){return"RotateRight"};e.c=function(a){throw O(new P,""+a);};e.vB=function(a){return new C(new C(a.Y,a.V.Y),a.V.V)};e.g=function(a){return this.vB(a)}; +z(xF,"cats.parse.Parser$Impl$RotateRight",{lV:1,U:1,k:1,s:1,b:1});function If(a,b){this.fb=0;this.gb=!1;this.fq=a;this.gq=b}If.prototype=new sh;If.prototype.constructor=If;e=If.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof If){var b=this.fq,c=a.fq;if(null===b?null===c:b.d(c))return b=this.gq,a=a.gq,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Select0"}; +e.c=function(a){if(0===a)return this.fq;if(1===a)return this.gq;throw O(new P,""+a);};e.qa=function(a){return Cg(He(),this.fq,this.gq,a)};z(If,"cats.parse.Parser$Impl$Select0",{nV:1,vb:1,k:1,s:1,b:1});function pf(a,b){this.fb=0;this.gb=!1;this.Uh=a;this.Vh=b}pf.prototype=new sh;pf.prototype.constructor=pf;e=pf.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof pf){var b=this.Uh,c=a.Uh;if(null===b?null===c:b.d(c))return b=this.Vh,a=a.Vh,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"SoftProd0"};e.c=function(a){if(0===a)return this.Uh;if(1===a)return this.Vh;throw O(new P,""+a);};e.qa=function(a){return tg(He(),this.Uh,this.Vh,a)};z(pf,"cats.parse.Parser$Impl$SoftProd0",{qV:1,vb:1,k:1,s:1,b:1});function uF(a){this.jq=a}uF.prototype=new t; +uF.prototype.constructor=uF;e=uF.prototype;e.f=function(){return"\x3cfunction1\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof uF&&V(W(),this.jq,a.jq)};e.t=function(){return 1};e.v=function(){return"ToTupleWith1"};e.c=function(a){if(0===a)return this.jq;throw O(new P,""+a);};e.ld=function(a){return a instanceof wF?new uF(a.eq.g(this.jq)):pr(this,a)};e.g=function(a){return new C(this.jq,a)}; +z(uF,"cats.parse.Parser$Impl$ToTupleWith1",{vV:1,U:1,k:1,s:1,b:1});function vF(a){this.kx=a}vF.prototype=new t;vF.prototype.constructor=vF;e=vF.prototype;e.ld=function(a){return pr(this,a)};e.f=function(){return"\x3cfunction1\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof vF&&V(W(),this.kx,a.kx)};e.t=function(){return 1};e.v=function(){return"ToTupleWith2"};e.c=function(a){if(0===a)return this.kx;throw O(new P,""+a);}; +e.g=function(a){return new C(a,this.kx)};z(vF,"cats.parse.Parser$Impl$ToTupleWith2",{wV:1,U:1,k:1,s:1,b:1});function eg(a){this.mx=a}eg.prototype=new t;eg.prototype.constructor=eg;e=eg.prototype;e.f=function(){return"\x3cfunction0\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof eg){var b=this.mx;a=a.mx;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 1};e.v=function(){return"UnmapDefer"}; +e.c=function(a){if(0===a)return this.mx;throw O(new P,""+a);};e.za=function(){return Wf(He(),Gg(He(),this.mx))};z(eg,"cats.parse.Parser$Impl$UnmapDefer",{xV:1,Rw:1,k:1,s:1,b:1});function Yf(a){this.lx=a}Yf.prototype=new t;Yf.prototype.constructor=Yf;e=Yf.prototype;e.f=function(){return"\x3cfunction0\x3e"};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Yf){var b=this.lx;a=a.lx;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 1}; +e.v=function(){return"UnmapDefer0"};e.c=function(a){if(0===a)return this.lx;throw O(new P,""+a);};e.za=function(){return Vf(He(),Fg(He(),this.lx))};z(Yf,"cats.parse.Parser$Impl$UnmapDefer0",{yV:1,Rw:1,k:1,s:1,b:1});function tf(a){this.fb=0;this.gb=!1;this.Wh=a}tf.prototype=new sh;tf.prototype.constructor=tf;e=tf.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof tf){var b=this.Wh;a=a.Wh;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)}; +e.t=function(){return 1};e.v=function(){return"Void0"};e.c=function(a){if(0===a)return this.Wh;throw O(new P,""+a);};e.qa=function(a){He();var b=this.Wh,c=a.We;a.We=!1;b.qa(a);a.We=c};z(tf,"cats.parse.Parser$Impl$Void0",{AV:1,vb:1,k:1,s:1,b:1});function rf(a,b){this.fb=0;this.gb=!1;this.kq=a;this.Xh=b}rf.prototype=new sh;rf.prototype.constructor=rf;e=rf.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof rf&&this.kq===a.kq){var b=this.Xh;a=a.Xh;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"WithContextP0"};e.c=function(a){if(0===a)return this.kq;if(1===a)return this.Xh;throw O(new P,""+a);};e.qa=function(a){var b=this.Xh.qa(a);null!==a.bb&&(a.bb=lg(a.bb,new G(c=>sz(c,new G(d=>new pe(this.kq,d))))));return b};z(rf,"cats.parse.Parser$Impl$WithContextP0",{CV:1,vb:1,k:1,s:1,b:1}); +function LN(){}LN.prototype=new JK;LN.prototype.constructor=LN;function MN(){}MN.prototype=LN.prototype;class La extends bg{constructor(a){super();ft(this,a)}}z(La,"java.lang.ArithmeticException",{y1:1,Mb:1,xb:1,Xa:1,b:1});var ja=z(0,"java.lang.Byte",{C1:1,hj:1,b:1,Lb:1,ak:1},a=>Wa(a));class eI extends bg{constructor(){super();ft(this,null)}}z(eI,"java.lang.ClassCastException",{G1:1,Mb:1,xb:1,Xa:1,b:1});function Qi(a){var b=new NN;ft(b,a);return b}function vb(){var a=new NN;ft(a,null);return a} +class NN extends bg{}z(NN,"java.lang.IllegalArgumentException",{Eg:1,Mb:1,xb:1,Xa:1,b:1});function Ws(a){var b=new ON;ft(b,a);return b}function pN(){var a=new ON;ft(a,null);return a}class ON extends bg{}z(ON,"java.lang.IllegalStateException",{HN:1,Mb:1,xb:1,Xa:1,b:1});function O(a,b){ft(a,b);return a}function kk(){var a=new P;ft(a,null);return a}class P extends bg{}z(P,"java.lang.IndexOutOfBoundsException",{MH:1,Mb:1,xb:1,Xa:1,b:1});function PN(){}PN.prototype=new JK;PN.prototype.constructor=PN; +z(PN,"java.lang.JSConsoleBasedPrintStream$DummyOutputStream",{P1:1,DM:1,jF:1,FH:1,kF:1});class ml extends bg{constructor(){super();ft(this,null)}}z(ml,"java.lang.NegativeArraySizeException",{U1:1,Mb:1,xb:1,Xa:1,b:1});function jw(a){var b=new QN;ft(b,a);return b}function Nr(){var a=new QN;ft(a,null);return a}class QN extends bg{}z(QN,"java.lang.NullPointerException",{V1:1,Mb:1,xb:1,Xa:1,b:1});var la=z(0,"java.lang.Short",{X1:1,hj:1,b:1,Lb:1,ak:1},a=>Xa(a)); +function SB(){var a=new RN;ft(a,null);return a}function Vh(a){var b=new RN;ft(b,a);return b}class RN extends bg{}z(RN,"java.lang.UnsupportedOperationException",{PN:1,Mb:1,xb:1,Xa:1,b:1});class Ek extends bg{constructor(){super();ft(this,null)}}z(Ek,"java.nio.BufferOverflowException",{PX:1,Mb:1,xb:1,Xa:1,b:1});class Fk extends bg{constructor(){super();ft(this,null)}}z(Fk,"java.nio.BufferUnderflowException",{QX:1,Mb:1,xb:1,Xa:1,b:1});class SN extends HK{} +class yv extends bg{constructor(a){super();ft(this,a)}}z(yv,"java.util.ConcurrentModificationException",{h2:1,Mb:1,xb:1,Xa:1,b:1});class Cs extends bg{constructor(a){super();ft(this,a)}}z(Cs,"java.util.NoSuchElementException",{z2:1,Mb:1,xb:1,Xa:1,b:1});function Lq(){this.Yf=null}Lq.prototype=new t;Lq.prototype.constructor=Lq;function TN(){}e=TN.prototype=Lq.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Lq){var b=this.Yf;a=a.Yf;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"And"};e.c=function(a){if(0===a)return this.Yf;throw O(new P,""+a);};e.Oc=function(a,b){var c=Mq(this.Yf,new G(d=>d.Oc(a,b)),b);return b.Vb(c,new G(d=>new UN(d)))};e.md=function(a){return new VN(a,this)};function $m(a,b){this.fm=a;this.em=b}$m.prototype=new t;$m.prototype.constructor=$m;e=$m.prototype; +e.x=function(){return new Z(this)};e.p=function(){var a=-889275714;a=X().m(a,Ha("Boost"));a=X().m(a,zw(X(),this.fm));a=X().m(a,yw(X(),this.em));return X().T(a,2)};e.d=function(a){if(this===a)return!0;if(a instanceof $m&&this.em===a.em){var b=this.fm;a=a.fm;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Boost"};e.c=function(a){if(0===a)return this.fm;if(1===a)return this.em;throw O(new P,""+a);}; +e.Oc=function(a,b){return b.Vb(this.fm.Oc(a,b),new G(c=>new $m(c,this.em)))};e.md=function(){return this};z($m,"pink.cozydev.lucille.Query$Boost",{uY:1,k:1,s:1,b:1,Zc:1});function Wm(a,b){this.eo=a;this.fo=b}Wm.prototype=new t;Wm.prototype.constructor=Wm;e=Wm.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Wm&&this.eo===a.eo){var b=this.fo;a=a.fo;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)}; +e.t=function(){return 2};e.v=function(){return"Field"};e.c=function(a){if(0===a)return this.eo;if(1===a)return this.fo;throw O(new P,""+a);};e.Oc=function(a,b){return b.Vb(this.fo.Oc(a,b),new G(c=>new Wm(this.eo,c)))};e.md=function(a){return new Wm(this.eo,this.fo.md(a))};z(Wm,"pink.cozydev.lucille.Query$Field",{vY:1,k:1,s:1,b:1,Zc:1});function jn(a){this.go=a}jn.prototype=new t;jn.prototype.constructor=jn;e=jn.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof jn){var b=this.go;a=a.go;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Group"};e.c=function(a){if(0===a)return this.go;throw O(new P,""+a);};e.Oc=function(a,b){return b.Vb(this.go.Oc(a,b),new G(c=>new jn(c)))};e.md=function(){return this};z(jn,"pink.cozydev.lucille.Query$Group",{xY:1,k:1,s:1,b:1,Zc:1});function dn(a,b){this.hm=a;this.gm=b}dn.prototype=new t; +dn.prototype.constructor=dn;e=dn.prototype;e.x=function(){return new Z(this)};e.p=function(){var a=-889275714;a=X().m(a,Ha("MinimumMatch"));a=X().m(a,zw(X(),this.hm));a=X().m(a,this.gm);return X().T(a,2)};e.d=function(a){if(this===a)return!0;if(a instanceof dn&&this.gm===a.gm){var b=this.hm;a=a.hm;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"MinimumMatch"}; +e.c=function(a){if(0===a)return this.hm;if(1===a)return this.gm;throw O(new P,""+a);};e.Oc=function(a,b){var c=Mq(this.hm,new G(d=>d.Oc(a,b)),b);return b.Vb(c,new G(d=>new dn(d,this.gm)))};e.md=function(){return this};z(dn,"pink.cozydev.lucille.Query$MinimumMatch",{yY:1,k:1,s:1,b:1,Zc:1});function Qm(a){this.im=a}Qm.prototype=new t;Qm.prototype.constructor=Qm;e=Qm.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Qm){var b=this.im;a=a.im;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Not"};e.c=function(a){if(0===a)return this.im;throw O(new P,""+a);};e.Oc=function(a,b){return b.Vb(this.im.Oc(a,b),new G(c=>new Qm(c)))};e.md=function(a){return new Qm(this.im.md(a))};z(Qm,"pink.cozydev.lucille.Query$Not",{zY:1,k:1,s:1,b:1,Zc:1});function Rq(){this.$e=null}Rq.prototype=new t; +Rq.prototype.constructor=Rq;function WN(){}e=WN.prototype=Rq.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Rq){var b=this.$e;a=a.$e;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Or"};e.c=function(a){if(0===a)return this.$e;throw O(new P,""+a);};e.Oc=function(a,b){var c=Mq(this.$e,new G(d=>d.Oc(a,b)),b);return b.Vb(c,new G(d=>new XN(d)))}; +e.md=function(a){return new YN(a,this)};function Um(a){this.uq=a}Um.prototype=new t;Um.prototype.constructor=Um;e=Um.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Um){var b=this.uq;a=a.uq;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"UnaryMinus"};e.c=function(a){if(0===a)return this.uq;throw O(new P,""+a);}; +e.Oc=function(a,b){return b.Vb(this.uq.Oc(a,b),new G(c=>new Um(c)))};e.md=function(a){return new Um(this.uq.md(a))};z(Um,"pink.cozydev.lucille.Query$UnaryMinus",{MY:1,k:1,s:1,b:1,Zc:1});function Sm(a){this.vq=a}Sm.prototype=new t;Sm.prototype.constructor=Sm;e=Sm.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Sm){var b=this.vq;a=a.vq;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1}; +e.v=function(){return"UnaryPlus"};e.c=function(a){if(0===a)return this.vq;throw O(new P,""+a);};e.Oc=function(a,b){return b.Vb(this.vq.Oc(a,b),new G(c=>new Sm(c)))};e.md=function(a){return new Sm(this.vq.md(a))};z(Sm,"pink.cozydev.lucille.Query$UnaryPlus",{NY:1,k:1,s:1,b:1,Zc:1});function $n(a){this.Dx=a}$n.prototype=new t;$n.prototype.constructor=$n;e=$n.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof $n&&this.Dx===a.Dx}; +e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Str"};e.c=function(a){if(0===a)return this.Dx;throw O(new P,""+a);};z($n,"pink.cozydev.lucille.Query$WildCardOp$Str",{RY:1,k:1,s:1,b:1,FF:1});function uG(){}uG.prototype=new sJ;uG.prototype.constructor=uG;uG.prototype.OB=function(a){return a instanceof Dg||!1};uG.prototype.qB=function(a,b){return a instanceof Dg?a.lj:b.g(a)};uG.prototype.sf=function(a){return this.OB(a)};uG.prototype.af=function(a,b){return this.qB(a,b)}; +z(uG,"pink.cozydev.protosearch.MultiIndex$$anon$1",{kZ:1,vJ:1,U:1,ja:1,b:1});function wG(){}wG.prototype=new sJ;wG.prototype.constructor=wG;wG.prototype.OB=function(a){return a instanceof Eg||!1};wG.prototype.qB=function(a,b){return a instanceof Eg?a.ib:b.g(a)};wG.prototype.sf=function(a){return this.OB(a)};wG.prototype.af=function(a,b){return this.qB(a,b)};z(wG,"pink.cozydev.protosearch.MultiIndex$$anon$2",{lZ:1,vJ:1,U:1,ja:1,b:1});function Uo(a){this.Mx=a}Uo.prototype=new t; +Uo.prototype.constructor=Uo;e=Uo.prototype;e.x=function(){return new Z(this)};e.p=function(){return xx(this,-143510350)};e.d=function(a){return this===a||a instanceof Uo&&this.Mx===a.Mx};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"SearchFailure"};e.c=function(a){if(0===a)return this.Mx;throw O(new P,""+a);};z(Uo,"pink.cozydev.protosearch.SearchFailure",{wZ:1,SM:1,k:1,s:1,b:1});function Yo(a){this.Ox=a}Yo.prototype=new t;Yo.prototype.constructor=Yo;e=Yo.prototype; +e.x=function(){return new Z(this)};e.p=function(){return xx(this,-2116389641)};e.d=function(a){if(this===a)return!0;if(a instanceof Yo){var b=this.Ox;a=a.Ox;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"SearchSuccess"};e.c=function(a){if(0===a)return this.Ox;throw O(new P,""+a);};z(Yo,"pink.cozydev.protosearch.SearchSuccess",{yZ:1,SM:1,k:1,s:1,b:1});function xC(){}xC.prototype=new RL;xC.prototype.constructor=xC; +xC.prototype.g=function(a){return a};xC.prototype.ld=function(a){return a};xC.prototype.f=function(){return"generalized constraint"};z(xC,"scala.$less$colon$less$$anon$1",{X2:1,U2:1,V2:1,U:1,b:1}); +class D extends bg{constructor(a){super();this.jI=null;this.jC=!1;this.kC=a;ft(this,null)}xe(){if(!this.jC&&!this.jC){if(null===this.kC)var a="null";else{var b=ea(this.kC);b=null===b?"of a JS class":"of class "+b.ba.name;try{a=this.kC+" ("+b+")"}catch(c){a="an instance "+b}}this.jI=a;this.jC=!0}return this.jI}}z(D,"scala.MatchError",{c3:1,Mb:1,xb:1,Xa:1,b:1});function ZN(){}ZN.prototype=new t;ZN.prototype.constructor=ZN;function $N(){}$N.prototype=ZN.prototype; +ZN.prototype.e=function(){return this===F()};ZN.prototype.E=function(){return this.e()?0:1};ZN.prototype.i=function(){return this.e()?Lv().da:new vz(this.oa())};function Z(a){this.nI=0;this.oO=a;this.Fy=0;this.nI=a.t()}Z.prototype=new TL;Z.prototype.constructor=Z;Z.prototype.n=function(){return this.Fya.ph)return-1;a=a.ph-b|0;return 0>a?0:a}function rH(a,b,c){this.Um=a;this.ph=c;this.vl=b}rH.prototype=new TL; +rH.prototype.constructor=rH;rH.prototype.E=function(){var a=this.Um.E();if(0>a)return-1;a=a-this.vl|0;a=0>a?0:a;if(0>this.ph)return a;var b=this.ph;return bthis.ph?this.Um.j():Lv().da.j()}; +rH.prototype.Sg=function(a,b){a=0b)var c=sO(this,a);else if(b<=a)c=0;else if(0>this.ph)c=b-a|0;else{var d=sO(this,a);b=b-a|0;c=df)return this.vl=2147483647,this.ph=0,oH(this,new Rh(()=>new rH(this.Um,f-2147483647|0,c)));this.vl=f;this.ph=c;return this};z(rH,"scala.collection.Iterator$SliceIterator",{G5:1,ra:1,la:1,w:1,y:1});function tO(a){this.Vy=null;this.Vy=new ss(this,new Rh(()=>a))}tO.prototype=new TL;tO.prototype.constructor=tO; +tO.prototype.n=function(){return!ts(this.Vy).e()};tO.prototype.j=function(){if(this.n()){var a=ts(this.Vy),b=a.u();this.Vy=new ss(this,new Rh(()=>a.A()));return b}return Lv().da.j()};z(tO,"scala.collection.LinearSeqIterator",{H5:1,ra:1,la:1,w:1,y:1});function uO(a){for(var b=0;!a.e();)b=1+b|0,a=a.A();return b}function vO(a,b){return 0<=b&&0b)throw O(new P,""+b);a=a.Na(b);if(a.e())throw O(new P,""+b);return a.u()} +function xO(a,b){for(;!a.e();){if(b.g(a.u()))return!0;a=a.A()}return!1}function FN(a,b,c){for(;!a.e();)b=c.Ca(b,a.u()),a=a.A();return b}function yO(a,b){if(b&&b.$classData&&b.$classData.wb.iw)a:for(;;){if(a===b){a=!0;break a}if(a.e()||b.e()||!V(W(),a.u(),b.u())){a=a.e()&&b.e();break a}a=a.A();b=b.A()}else a=gM(a,b);return a}function zO(a,b,c){var d=0b.i())))}function DO(){this.hw=null}DO.prototype=new dH;DO.prototype.constructor=DO;function EO(){}EO.prototype=DO.prototype;function Jg(a){this.cz=a}Jg.prototype=new TL;Jg.prototype.constructor=Jg;Jg.prototype.n=function(){return!this.cz.e()}; +Jg.prototype.j=function(){var a=this.cz.u();this.cz=this.cz.A();return a};z(Jg,"scala.collection.StrictOptimizedLinearSeqOps$$anon$1",{V5:1,ra:1,la:1,w:1,y:1});function FO(a,b){this.kw=null;this.lw=a;this.SC=b;this.dn=-1;this.oj=0}FO.prototype=new TL;FO.prototype.constructor=FO;FO.prototype.E=function(){return this.dn}; +FO.prototype.n=function(){if(null===this.kw){var a=this.SC;a=256>a?a:256;var b=new GO;HO(b,new x(1c)throw Fs(Hs(),c,b.Xc-1|0);if(f>b.Xc)throw Fs(Hs(),f-1|0,b.Xc-1|0);b.El=1+b.El|0;b.Ch.a[c]=d;this.lw.n()||(this.dn=0)}else this.dn=this.dn-1|0;this.oj=1+this.oj|0;this.oj===this.SC&&(this.oj=0);return a}return Lv().da.j()};z(FO,"scala.collection.View$DropRightIterator",{d6:1,ra:1,la:1,w:1,y:1}); +function IO(a,b){null===a.sh&&(a.sh=new y(R().yw<<1),a.wl=new (B(kt).P)(R().yw));a.Qe=1+a.Qe|0;var c=a.Qe<<1,d=1+(a.Qe<<1)|0;a.wl.a[a.Qe]=b;a.sh.a[c]=0;a.sh.a[d]=b.$v()}function JO(a,b){a.Dc=0;a.qk=0;a.Qe=-1;b.Nv()&&IO(a,b);b.Uo()&&(a.Df=b,a.Dc=0,a.qk=b.bp())}function KO(){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null}KO.prototype=new TL;KO.prototype.constructor=KO;function LO(){}LO.prototype=KO.prototype; +KO.prototype.n=function(){var a;if(!(a=this.Dch)throw SO();if(h>c.a.length)throw SO();d=new y(1+c.a.length|0);c.G(0,d,0,h);d.a[h]=f;c.G(h,d,1+h|0,c.a.length-h|0);b.Yb|=m;b.zd=a;b.Mg=d;b.Ae=1+b.Ae|0;b.gg=b.gg+g|0}}else if(b instanceof MH)m=dI(b,c),b.dd=0>m?b.dd.we(new C(c,d)):b.dd.Jk(m,new C(c, +d));else throw new D(b);}function oI(a){if(0===a.rk.Ae)return Sh().sw;null===a.tw&&(a.tw=new mI(a.rk));return a.tw}function TO(a,b){QO(a);var c=zw(X(),b.Y),d=ds(fs(),c);RO(a,a.rk,b.Y,b.V,c,d,0);return a}function UO(a,b,c){QO(a);var d=zw(X(),b);RO(a,a.rk,b,c,d,ds(fs(),d),0);return a} +function pI(a,b){QO(a);if(b instanceof mI)new VO(a,b);else if(b instanceof cJ)for(b=WO(b);b.n();){var c=b.j(),d=c.Ck;d^=d>>>16|0;var f=ds(fs(),d);RO(a,a.rk,c.Hn,c.Di,d,f,0)}else if(b&&b.$classData&&b.$classData.wb.kn)b.Cg(new pJ((g,h)=>UO(a,g,h)));else for(b=b.i();b.n();)TO(a,b.j());return a}nI.prototype.gc=function(a){return pI(this,a)};nI.prototype.Ia=function(a){return TO(this,a)};nI.prototype.Sa=function(){return oI(this)}; +z(nI,"scala.collection.immutable.HashMapBuilder",{C6:1,Ei:1,ae:1,ud:1,td:1});function tI(){this.sk=this.jn=null;this.sk=new Ru(0,0,or().iC,or().bw,0,0)}tI.prototype=new t;tI.prototype.constructor=tI;tI.prototype.Fc=function(){}; +function XO(a,b,c,d,f,g){if(b instanceof Ru){var h=nt(R(),f,g),k=ot(R(),h);if(0!==(b.$&k)){h=qt(R(),b.$,h,k);a=b.Pa(h);var m=b.hb(h);m===d&&V(W(),a,c)?(d=b.bf(k),b.Ma.a[d]=a):(h=ds(fs(),m),d=RH(b,a,m,h,c,d,f,5+g|0),UH(b,k,h,d))}else if(0!==(b.ma&k))k=qt(R(),b.ma,h,k),k=b.ad(k),h=k.X(),m=k.Bb(),XO(a,k,c,d,f,5+g|0),b.ta=b.ta+(k.X()-h|0)|0,b.Zb=b.Zb+(k.Bb()-m|0)|0;else{g=b.bf(k);h=b.Ma;a=new x(1+h.a.length|0);h.G(0,a,0,g);a.a[g]=c;h.G(g,a,1+g|0,h.a.length-g|0);c=b.nb;if(0>g)throw SO();if(g>c.a.length)throw SO(); +h=new y(1+c.a.length|0);c.G(0,h,0,g);h.a[g]=d;c.G(g,h,1+g|0,c.a.length-g|0);b.$|=k;b.Ma=a;b.nb=h;b.ta=1+b.ta|0;b.Zb=b.Zb+f|0}}else if(b instanceof XH)d=dM(b.nc,c),b.nc=0>d?b.nc.we(c):b.nc.Jk(d,c);else throw new D(b);}function uI(a){if(0===a.sk.ta)return wI().qj;null===a.jn&&(a.jn=new sI(a.sk));return a.jn}function YO(a,b){null!==a.jn&&(a.sk=bI(a.sk));a.jn=null;var c=zw(X(),b),d=ds(fs(),c);XO(a,a.sk,b,c,d,0);return a} +function vI(a,b){null!==a.jn&&(a.sk=bI(a.sk));a.jn=null;if(b instanceof sI)new ZO(a,b);else for(b=b.i();b.n();)YO(a,b.j());return a}tI.prototype.gc=function(a){return vI(this,a)};tI.prototype.Ia=function(a){return YO(this,a)};tI.prototype.Sa=function(){return uI(this)};z(tI,"scala.collection.immutable.HashSetBuilder",{G6:1,Ei:1,ae:1,ud:1,td:1});function $O(){this.Cf=null;this.Cf=Nv()}$O.prototype=new YL;$O.prototype.constructor=$O;function aP(a,b){return bP(b)?b:XL.prototype.Jm.call(a,b)} +$O.prototype.mb=function(a){return aP(this,a)};$O.prototype.Jm=function(a){return aP(this,a)};z($O,"scala.collection.immutable.IndexedSeq$",{I6:1,HC:1,fg:1,Gd:1,b:1});var cP;function Kv(){cP||(cP=new $O);return cP}function uM(){this.ZI=this.up=null;this.fy()}uM.prototype=new t;uM.prototype.constructor=uM;e=uM.prototype;e.Fc=function(){};e.fy=function(){var a=new Us;this.ZI=(Q(),Ns(new Os,new Rh(()=>Vs(a))));this.up=a};function dP(a){Xs(a.up,new Rh(()=>Q().ua));return a.ZI} +function eP(a,b){var c=new Us;Xs(a.up,new Rh(()=>{Q();return Ps(new Os,b,(Q(),Ns(new Os,new Rh(()=>Vs(c)))))}));a.up=c;return a}function fP(a,b){if(0!==b.E()){var c=new Us;Xs(a.up,new Rh(()=>rM(Q(),b.i(),new Rh(()=>Vs(c)))));a.up=c}return a}e.gc=function(a){return fP(this,a)};e.Ia=function(a){return eP(this,a)};e.Sa=function(){return dP(this)};z(uM,"scala.collection.immutable.LazyList$LazyBuilder",{P6:1,Ei:1,ae:1,ud:1,td:1});function gP(a){this.uw=a}gP.prototype=new TL;gP.prototype.constructor=gP; +gP.prototype.n=function(){return pM(this.uw)!==Q().ua};gP.prototype.j=function(){if(pM(this.uw)===Q().ua)return Lv().da.j();var a=this.uw.u();this.uw=qM(this.uw);return a};z(gP,"scala.collection.immutable.LazyList$LazyIterator",{R6:1,ra:1,la:1,w:1,y:1});function hP(){this.$I=null;iP=this;this.$I=new C(N(),N())}hP.prototype=new t;hP.prototype.constructor=hP;hP.prototype.wg=function(a){return rg(N(),a)};hP.prototype.Da=function(){return new Jh};hP.prototype.Sd=function(){return N()}; +hP.prototype.mb=function(a){return rg(N(),a)};z(hP,"scala.collection.immutable.List$",{U6:1,bn:1,fg:1,Gd:1,b:1});var iP;function Ih(){iP||(iP=new hP);return iP}function jP(){this.ln=0;this.vw=null}jP.prototype=new TL;jP.prototype.constructor=jP;function kP(){}kP.prototype=jP.prototype;jP.prototype.n=function(){return 2>this.ln}; +jP.prototype.j=function(){switch(this.ln){case 0:var a=new C(this.vw.uh,this.vw.tk);break;case 1:a=new C(this.vw.vh,this.vw.uk);break;default:a=Lv().da.j()}this.ln=1+this.ln|0;return a};jP.prototype.df=function(a){this.ln=this.ln+a|0;return this};function lP(){this.nn=0;this.mn=null}lP.prototype=new TL;lP.prototype.constructor=lP;function mP(){}mP.prototype=lP.prototype;lP.prototype.n=function(){return 3>this.nn}; +lP.prototype.j=function(){switch(this.nn){case 0:var a=new C(this.mn.Ng,this.mn.sj);break;case 1:a=new C(this.mn.Og,this.mn.tj);break;case 2:a=new C(this.mn.Pg,this.mn.uj);break;default:a=Lv().da.j()}this.nn=1+this.nn|0;return a};lP.prototype.df=function(a){this.nn=this.nn+a|0;return this};function nP(){this.on=0;this.vk=null}nP.prototype=new TL;nP.prototype.constructor=nP;function oP(){}oP.prototype=nP.prototype;nP.prototype.n=function(){return 4>this.on}; +nP.prototype.j=function(){switch(this.on){case 0:var a=new C(this.vk.Ef,this.vk.wh);break;case 1:a=new C(this.vk.Ff,this.vk.xh);break;case 2:a=new C(this.vk.Gf,this.vk.yh);break;case 3:a=new C(this.vk.Hf,this.vk.zh);break;default:a=Lv().da.j()}this.on=1+this.on|0;return a};nP.prototype.df=function(a){this.on=this.on+a|0;return this};function GI(){this.wk=null;this.ww=!1;this.pn=null;this.wk=zI();this.ww=!1}GI.prototype=new t;GI.prototype.constructor=GI;GI.prototype.Fc=function(){}; +function EI(a){return a.ww?oI(a.pn):a.wk}function BL(a,b,c){if(a.ww)UO(a.pn,b,c);else if(4>a.wk.X())a.wk=a.wk.Kl(b,c);else if(a.wk.Hc(b))a.wk=a.wk.Kl(b,c);else{a.ww=!0;null===a.pn&&(a.pn=new nI);var d=a.wk;UO(UO(UO(UO(a.pn,d.Ef,d.wh),d.Ff,d.xh),d.Gf,d.yh),d.Hf,d.zh);UO(a.pn,b,c)}return a}function FI(a,b){return a.ww?(pI(a.pn,b),a):nD(a,b)}GI.prototype.gc=function(a){return FI(this,a)};GI.prototype.Ia=function(a){return BL(this,a.Y,a.V)};GI.prototype.Sa=function(){return EI(this)}; +z(GI,"scala.collection.immutable.MapBuilderImpl",{g7:1,Ei:1,ae:1,ud:1,td:1});function pP(a,b,c,d){a.Ap=b;a.wn=d;a.Bh=null===b?null:new (B(Nu).P)(((32-Math.clz32(1+(2147483647&b.D)|0)|0)<<1)-2|0);a.pb=0;if(c.e())b=qP(a,b);else if(b=c.oa(),null===a.Ap)b=null;else a:for(c=a.Ap;;){if(null===c){if(0===a.pb){b=null;break a}a.pb=a.pb-1|0;b=a.Bh.a[a.pb];break a}a.wn.Ne(b,c.ga)?(a.Bh.a[a.pb]=c,a.pb=1+a.pb|0,c=c.K):c=c.M}a.Ha=b}function rP(){this.Bh=this.wn=this.Ap=null;this.pb=0;this.Ha=null} +rP.prototype=new TL;rP.prototype.constructor=rP;function sP(){}sP.prototype=rP.prototype;rP.prototype.n=function(){return null!==this.Ha};rP.prototype.j=function(){var a=this.Ha;return null!==a?(this.Ha=qP(this,a.M),this.eC(a)):Lv().da.j()};function qP(a,b){for(;;){if(null===b){if(0===a.pb)return null;a.pb=a.pb-1|0;return a.Bh.a[a.pb]}if(null===b.K)return b;a.Bh.a[a.pb]=b;a.pb=1+a.pb|0;b=b.K}}function tP(){this.Cf=null;this.Cf=Ih()}tP.prototype=new YL;tP.prototype.constructor=tP; +tP.prototype.mb=function(a){return a&&a.$classData&&a.$classData.wb.pc?a:XL.prototype.Jm.call(this,a)};tP.prototype.Jm=function(a){return a&&a.$classData&&a.$classData.wb.pc?a:XL.prototype.Jm.call(this,a)};z(tP,"scala.collection.immutable.Seq$",{A7:1,HC:1,fg:1,Gd:1,b:1});var uP;function vP(){uP||(uP=new tP);return uP}function up(){this.zn=null;this.Aw=!1;this.An=null;this.zn=JI();this.Aw=!1}up.prototype=new t;up.prototype.constructor=up;up.prototype.Fc=function(){}; +function wp(a){return a.Aw?uI(a.An):a.zn}function vp(a,b){if(a.Aw)YO(a.An,b);else if(4>a.zn.X())a.zn=a.zn.ih(b);else if(!a.zn.Hc(b)){a.Aw=!0;null===a.An&&(a.An=new tI);var c=a.zn;a.An.Ia(c.wj).Ia(c.xj).Ia(c.yj).Ia(c.zj);YO(a.An,b)}return a}function OI(a,b){return a.Aw?(vI(a.An,b),a):nD(a,b)}up.prototype.gc=function(a){return OI(this,a)};up.prototype.Ia=function(a){return vp(this,a)};up.prototype.Sa=function(){return wp(this)};z(up,"scala.collection.immutable.SetBuilderImpl",{K7:1,Ei:1,ae:1,ud:1,td:1}); +function wP(){this.hJ=0;this.iJ=null;xP=this;try{var a=Ln(dh(),Gi(Ii(),"scala.collection.immutable.Vector.defaultApplyPreferredMaxLength","250"),214748364)}catch(b){throw b;}this.hJ=a;this.iJ=new yP(bv(),0,0)}wP.prototype=new t;wP.prototype.constructor=wP;wP.prototype.wg=function(a){return tz(0,a)}; +function tz(a,b){if(b instanceof zP)return b;a=b.E();if(0===a)return bv();if(31>=(a-1|0)>>>0){a:{if(b instanceof sw){var c=b.Qc().od();if(null!==c&&c===l(ob)){b=b.ti;break a}}yI(b)?(a=new x(a),b.hc(a,0,2147483647),b=a):(a=new x(a),b.i().hc(a,0,2147483647),b=a)}return new cv(b)}return iI(new hI,b).lh()}wP.prototype.Da=function(){return new hI};wP.prototype.mb=function(a){return tz(0,a)};wP.prototype.Sd=function(){return bv()};z(wP,"scala.collection.immutable.Vector$",{f8:1,bn:1,fg:1,Gd:1,b:1});var xP; +function Nv(){xP||(xP=new wP);return xP}function AP(a,b){var c=b.a.length;if(0>>5|0);c=c>>5|0),c);DP(a,c<<5);0>9>>>22|0;if(0!==((1023&(c+g|0))-g|0)){f=b.a.length;c=0;if(null!==b)for(;c>>10|0);c=c>>10|0),c);DP(a,c<<10);0> +14>>>17|0;if(0!==((32767&(c+g|0))-g|0)){f=b.a.length;c=0;if(null!==b)for(;c>>15|0);c=c>>15|0),c);DP(a,c<<15);0>19>>>12|0;if(0!==((1048575&(c+g|0))-g|0)){f=b.a.length;c=0;if(null!==b)for(;c>>20|0);c=c>>20|0),c);DP(a,c<<20);0>24>>>7|0;if(0!==((33554431&(c+g|0))-g|0)){f=b.a.length;c=0;if(null!==b)for(;c>>25|0;if(64<(c+f|0))throw Qi("exceeding 2^31 elements");b.G(0,a.Md,c,f);DP(a,f<<25);break;default:throw new D(c);}}};function FP(a,b){for(var c=b.Ii(),d=0;d>>31|0)|0)>>1,h=d-g|0,k=h>>31;g=(1+g|0)-((h^k)-k|0)|0;1===g?AP(a,f):32===a.La||0===a.La?EP(a,f,g):ov(T(),g-2|0,f,new Zo(m=>{AP(a,m)}));d=1+d|0}return a} +function BP(a){var b=32+a.va|0,c=b^a.va;a.va=b;a.La=0;GP(a,b,c)}function DP(a,b){if(0=c)throw Qi("advance1("+b+", "+c+"): a1\x3d"+a.Pb+", a2\x3d"+a.Ka+", a3\x3d"+a.db+", a4\x3d"+a.Gb+", a5\x3d"+a.xc+", a6\x3d"+a.Md+", depth\x3d"+a.eb);1024>c?(1>=a.eb&&(a.Ka=new (B(B(ob)).P)(32),a.Ka.a[0]=a.Pb,a.eb=2),a.Pb=new x(32),a.Ka.a[31&(b>>>5|0)]=a.Pb):32768>c?(2>=a.eb&&(a.db=new (B(B(B(ob))).P)(32),a.db.a[0]=a.Ka,a.eb=3),a.Pb=new x(32),a.Ka=new (B(B(ob)).P)(32),a.Ka.a[31&(b>>>5|0)]=a.Pb,a.db.a[31&(b>>>10|0)]=a.Ka):1048576>c?(3>=a.eb&&(a.Gb=new (B(B(B(B(ob)))).P)(32), +a.Gb.a[0]=a.db,a.eb=4),a.Pb=new x(32),a.Ka=new (B(B(ob)).P)(32),a.db=new (B(B(B(ob))).P)(32),a.Ka.a[31&(b>>>5|0)]=a.Pb,a.db.a[31&(b>>>10|0)]=a.Ka,a.Gb.a[31&(b>>>15|0)]=a.db):33554432>c?(4>=a.eb&&(a.xc=new (B(B(B(B(B(ob))))).P)(32),a.xc.a[0]=a.Gb,a.eb=5),a.Pb=new x(32),a.Ka=new (B(B(ob)).P)(32),a.db=new (B(B(B(ob))).P)(32),a.Gb=new (B(B(B(B(ob)))).P)(32),a.Ka.a[31&(b>>>5|0)]=a.Pb,a.db.a[31&(b>>>10|0)]=a.Ka,a.Gb.a[31&(b>>>15|0)]=a.db,a.xc.a[31&(b>>>20|0)]=a.Gb):(5>=a.eb&&(a.Md=new (B(B(B(B(B(B(ob)))))).P)(64), +a.Md.a[0]=a.xc,a.eb=6),a.Pb=new x(32),a.Ka=new (B(B(ob)).P)(32),a.db=new (B(B(B(ob))).P)(32),a.Gb=new (B(B(B(B(ob)))).P)(32),a.xc=new (B(B(B(B(B(ob))))).P)(32),a.Ka.a[31&(b>>>5|0)]=a.Pb,a.db.a[31&(b>>>10|0)]=a.Ka,a.Gb.a[31&(b>>>15|0)]=a.db,a.xc.a[31&(b>>>20|0)]=a.Gb,a.Md.a[b>>>25|0]=a.xc)}function hI(){this.Pb=this.Ka=this.db=this.Gb=this.xc=this.Md=null;this.ab=this.va=this.La=0;this.rz=!1;this.eb=0;this.Pb=new x(32);this.ab=this.va=this.La=0;this.rz=!1;this.eb=1}hI.prototype=new t; +hI.prototype.constructor=hI;e=hI.prototype;e.Fc=function(){};function HP(a,b){a.eb=1;var c=b.a.length;a.La=31&c;a.va=c-a.La|0;a.Pb=32===b.a.length?b:yl(H(),b,0,32);0===a.La&&0>>25|0;0>24>>>7|0,g=(33554431&(d+f|0))-f|0;this.va=this.va-(this.ab-g|0)|0;this.ab=g;0===(this.va>>>25|0)&&(this.eb=5);b=a;a=a.a[0]}if(5<=this.eb){null===a&&(a=this.xc);var h=31&(this.ab>>>20|0);if(5===this.eb){0>19>>>12|0,n=(1048575&(k+m|0))-m|0;this.va=this.va-(this.ab-n|0)|0;this.ab=n;0===(this.va>>>20|0)&&(this.eb=4)}else 0< +h&&(a=yl(H(),a,h,32)),b.a[0]=a;b=a;a=a.a[0]}if(4<=this.eb){null===a&&(a=this.Gb);var q=31&(this.ab>>>15|0);if(4===this.eb){0>14>>>17|0,w=(32767&(u+v|0))-v|0;this.va=this.va-(this.ab-w|0)|0;this.ab=w;0===(this.va>>>15|0)&&(this.eb=3)}else 0>>10|0);if(3===this.eb){0>9>>>22|0,S=(1023&(K+M|0))-M|0;this.va= +this.va-(this.ab-S|0)|0;this.ab=S;0===(this.va>>>10|0)&&(this.eb=2)}else 0>>5|0);if(2===this.eb){0>4>>>27|0,ka=(31&(aa+U|0))-U|0;this.va=this.va-(this.ab-ka|0)|0;this.ab=ka;0===(this.va>>>5|0)&&(this.eb=1)}else 0ha)throw O(new P,"Vector cannot have negative size "+ha);if(32>=ha){var xb=this.Pb;return new cv(xb.a.length===fb?xb:ll(H(),xb,fb))}if(1024>=ha){var pb=31&(ha-1|0),Ra=(ha-1|0)>>>5|0,xc=yl(H(),this.Ka,1,Ra),ma=this.Ka.a[0],Za=this.Ka.a[Ra],qd=1+pb|0,Sc=Za.a.length===qd?Za:ll(H(),Za,qd);return new dv(ma,32-this.ab|0,xc,Sc,fb)}if(32768>= +ha){var qc=31&(ha-1|0),qa=31&((ha-1|0)>>>5|0),Eb=(ha-1|0)>>>10|0,Fa=yl(H(),this.db,1,Eb),Sb=this.db.a[0],Tc=yl(H(),Sb,1,Sb.a.length),fh=this.db.a[0].a[0],Kf=ll(H(),this.db.a[Eb],qa),ce=this.db.a[Eb].a[qa],ye=1+qc|0,Lf=ce.a.length===ye?ce:ll(H(),ce,ye),ug=fh.a.length;return new ev(fh,ug,Tc,ug+(Tc.a.length<<5)|0,Fa,Kf,Lf,fb)}if(1048576>=ha){var gh=31&(ha-1|0),de=31&((ha-1|0)>>>5|0),Gd=31&((ha-1|0)>>>10|0),yb=(ha-1|0)>>>15|0,vg=yl(H(),this.Gb,1,yb),ze=this.Gb.a[0],Hd=yl(H(),ze,1,ze.a.length),Ae=this.Gb.a[0].a[0], +Ye=yl(H(),Ae,1,Ae.a.length),Mf=this.Gb.a[0].a[0].a[0],Ze=ll(H(),this.Gb.a[yb],Gd),Nf=ll(H(),this.Gb.a[yb].a[Gd],de),bd=this.Gb.a[yb].a[Gd].a[de],Id=1+gh|0,Uc=bd.a.length===Id?bd:ll(H(),bd,Id),Be=Mf.a.length,$e=Be+(Ye.a.length<<5)|0;return new fv(Mf,Be,Ye,$e,Hd,$e+(Hd.a.length<<10)|0,vg,Ze,Nf,Uc,fb)}if(33554432>=ha){var af=31&(ha-1|0),Of=31&((ha-1|0)>>>5|0),Ce=31&((ha-1|0)>>>10|0),rd=31&((ha-1|0)>>>15|0),ee=(ha-1|0)>>>20|0,De=yl(H(),this.xc,1,ee),yc=this.xc.a[0],wg=yl(H(),yc,1,yc.a.length),bf=this.xc.a[0].a[0], +cf=yl(H(),bf,1,bf.a.length),xg=this.xc.a[0].a[0].a[0],df=yl(H(),xg,1,xg.a.length),Pf=this.xc.a[0].a[0].a[0].a[0],rc=ll(H(),this.xc.a[ee],rd),sc=ll(H(),this.xc.a[ee].a[rd],Ce),Jc=ll(H(),this.xc.a[ee].a[rd].a[Ce],Of),Jd=this.xc.a[ee].a[rd].a[Ce].a[Of],Kd=1+af|0,ff=Jd.a.length===Kd?Jd:ll(H(),Jd,Kd),Qf=Pf.a.length,Rf=Qf+(df.a.length<<5)|0,fe=Rf+(cf.a.length<<10)|0;return new gv(Pf,Qf,df,Rf,cf,fe,wg,fe+(wg.a.length<<15)|0,De,rc,sc,Jc,ff,fb)}var gf=31&(ha-1|0),Sf=31&((ha-1|0)>>>5|0),Tf=31&((ha-1|0)>>>10| +0),zc=31&((ha-1|0)>>>15|0),sd=31&((ha-1|0)>>>20|0),Ld=(ha-1|0)>>>25|0,ge=yl(H(),this.Md,1,Ld),he=this.Md.a[0],yg=yl(H(),he,1,he.a.length),Ac=this.Md.a[0].a[0],Fe=yl(H(),Ac,1,Ac.a.length),Md=this.Md.a[0].a[0].a[0],ie=yl(H(),Md,1,Md.a.length),cd=this.Md.a[0].a[0].a[0].a[0],Ge=yl(H(),cd,1,cd.a.length),hf=this.Md.a[0].a[0].a[0].a[0].a[0],qi=ll(H(),this.Md.a[Ld],sd),hh=ll(H(),this.Md.a[Ld].a[sd],zc),Sj=ll(H(),this.Md.a[Ld].a[sd].a[zc],Tf),Tj=ll(H(),this.Md.a[Ld].a[sd].a[zc].a[Tf],Sf),ri=this.Md.a[Ld].a[sd].a[zc].a[Tf].a[Sf], +si=1+gf|0,dd=ri.a.length===si?ri:ll(H(),ri,si),zg=hf.a.length,je=zg+(Ge.a.length<<5)|0,Uf=je+(ie.a.length<<10)|0,Ag=Uf+(Fe.a.length<<15)|0;return new hv(hf,zg,Ge,je,ie,Uf,Fe,Ag,yg,Ag+(yg.a.length<<20)|0,ge,qi,hh,Sj,Tj,dd,fb)};e.f=function(){return"VectorBuilder(len1\x3d"+this.La+", lenRest\x3d"+this.va+", offset\x3d"+this.ab+", depth\x3d"+this.eb+")"};e.Sa=function(){return this.lh()};e.gc=function(a){return iI(this,a)};e.Ia=function(a){return jI(this,a)}; +z(hI,"scala.collection.immutable.VectorBuilder",{n8:1,Ei:1,ae:1,ud:1,td:1});function IP(){this.lJ=null;JP=this;this.lJ=new x(0)}IP.prototype=new t;IP.prototype.constructor=IP;IP.prototype.wg=function(a){return KP(this,a)};function KP(a,b){var c=b.E();if(0<=c){a=LP(0,a.lJ,0,c);b=BH(b)?b.hc(a,0,2147483647):b.i().hc(a,0,2147483647);if(b!==c)throw Ws("Copied "+b+" of "+c);return HO(new GO,a,c)}return MP(dr(),b)}IP.prototype.Da=function(){return new HH}; +function LP(a,b,c,d){a=b.a.length;if(0>d)throw ag(new bg,"Overflow while resizing array of array-backed collection. Requested length: "+d+"; current length: "+a+"; increase: "+(d-a|0));if(d<=a)d=-1;else{if(2147483639a?d:a)}if(0>d)return b;d=new x(d);b.G(0,d,0,c);return d}IP.prototype.Sd=function(){return dr()}; +IP.prototype.mb=function(a){return KP(this,a)};z(IP,"scala.collection.mutable.ArrayBuffer$",{v8:1,bn:1,fg:1,Gd:1,b:1});var JP;function cr(){JP||(JP=new IP);return JP}function HH(){this.Eh=null;MM(this,(cr(),dr()))}HH.prototype=new OM;HH.prototype.constructor=HH;HH.prototype.Fc=function(a){this.Eh.Fc(a)};z(HH,"scala.collection.mutable.ArrayBuffer$$anon$1",{w8:1,wz:1,ae:1,ud:1,td:1});function NP(){}NP.prototype=new t;NP.prototype.constructor=NP;NP.prototype.wg=function(a){return OP(a)}; +function OP(a){var b=a.E();if(0<=b){var c=PP(0,b);a=BH(a)?a.hc(c,0,2147483647):a.i().hc(c,0,2147483647);if(a!==b)throw Ws("Copied "+a+" of "+b);return QP(new RP,c,b)}return SP(TP(),a)}NP.prototype.Da=function(){return new UP};function PP(a,b){if(!(0<=b))throw Qi("requirement failed: Non-negative array size required");a=(-2147483648>>>Math.clz32(b)|0)<<1;if(!(0<=a))throw Qi("requirement failed: ArrayDeque too big - cannot allocate ArrayDeque of length "+b);return new x(16((b.Mc-b.Nc|0)&(b.Va.a.length-1|0))&&a>=b.Va.a.length&&XP(b,a)};z(UP,"scala.collection.mutable.ArrayDeque$$anon$1",{B8:1,wz:1,ae:1,ud:1,td:1});function wD(){this.Cf=null;this.Cf=YP()}wD.prototype=new YL; +wD.prototype.constructor=wD;z(wD,"scala.collection.mutable.Buffer$",{N8:1,HC:1,fg:1,Gd:1,b:1});var oD;function $I(a,b){this.Eh=null;MM(this,bJ(new cJ,a,b))}$I.prototype=new OM;$I.prototype.constructor=$I;$I.prototype.Fc=function(a){this.Eh.Fc(a)};z($I,"scala.collection.mutable.HashMap$$anon$6",{Z8:1,wz:1,ae:1,ud:1,td:1});function ZP(a,b){a.Ew=b;a.Gl=0;a.Bk=null;a.Fw=b.kb.a.length}function $P(){this.Gl=0;this.Bk=null;this.Fw=0;this.Ew=null}$P.prototype=new TL;$P.prototype.constructor=$P; +function aQ(){}aQ.prototype=$P.prototype;$P.prototype.n=function(){if(null!==this.Bk)return!0;for(;this.GlbD(new VC,a.Fk)))};zQ.prototype.mb=function(a){return AQ(this,a)};zQ.prototype.Sd=function(){var a=new VC;bD(a,[]);return a};z(zQ,"scala.scalajs.runtime.WrappedVarArgs$",{H9:1,bn:1,fg:1,Gd:1,b:1});var BQ;function CQ(){BQ||(BQ=new zQ);return BQ} +function Dg(a){this.lj=a}Dg.prototype=new SM;Dg.prototype.constructor=Dg;e=Dg.prototype;e.v=function(){return"Left"};e.t=function(){return 1};e.c=function(a){return 0===a?this.lj:Aw(X(),a)};e.x=function(){return new aO(this)};e.p=function(){return xx(this,877209692)};e.f=function(){return nw(this)};e.d=function(a){return this===a||a instanceof Dg&&V(W(),this.lj,a.lj)};z(Dg,"scala.util.Left",{T4:1,zO:1,s:1,k:1,b:1});function Eg(a){this.ib=a}Eg.prototype=new SM;Eg.prototype.constructor=Eg;e=Eg.prototype; +e.v=function(){return"Right"};e.t=function(){return 1};e.c=function(a){return 0===a?this.ib:Aw(X(),a)};e.x=function(){return new aO(this)};e.p=function(){return xx(this,252890491)};e.f=function(){return nw(this)};e.d=function(a){return this===a||a instanceof Eg&&V(W(),this.ib,a.ib)};z(Eg,"scala.util.Right",{V4:1,zO:1,s:1,k:1,b:1});function Vp(a){this.po=a}Vp.prototype=new UM;Vp.prototype.constructor=Vp;e=Vp.prototype;e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Vp){var b=this.po;a=a.po;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Failure"};e.c=function(a){if(0===a)return this.po;throw O(new P,""+a);};e.jj=function(){return this};e.eI=function(a){return new Vp(a.g(this.po))};e.Lv=function(){return this};e.gI=function(){throw Qi(Ux(this.po));};z(Vp,"scodec.Attempt$Failure",{h_:1,fN:1,k:1,s:1,b:1});function Tp(a){this.om=a} +Tp.prototype=new UM;Tp.prototype.constructor=Tp;e=Tp.prototype;e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof Tp&&V(W(),this.om,a.om)};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Successful"};e.c=function(a){if(0===a)return this.om;throw O(new P,""+a);};e.jj=function(a){return new Tp(a.g(this.om))};e.eI=function(){return this};e.Lv=function(a){return a.g(this.om)};e.gI=function(){return this.om}; +z(Tp,"scodec.Attempt$Successful",{i_:1,fN:1,k:1,s:1,b:1});function cz(a,b){var c=N();a.qm=b;a.pm=c;return a}function dz(){this.pm=this.qm=null}dz.prototype=new t;dz.prototype.constructor=dz;e=dz.prototype;e.f=function(){return Ux(this)};e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof dz&&this.qm===a.qm){var b=this.pm;a=a.pm;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"General"}; +e.c=function(a){if(0===a)return this.qm;if(1===a)return this.pm;throw O(new P,""+a);};e.dC=function(){return this.qm};e.Fm=function(){return this.pm};e.Dy=function(a){var b=new dz;a=new J(a,this.pm);b.qm=this.qm;b.pm=a;return b};z(dz,"scodec.Err$General",{z_:1,yG:1,k:1,s:1,b:1});function rN(a,b,c,d,f,g){a.vm=b;a.um=c;a.tm=d;a.sm=f;a.rm=g;return a}function Wp(a,b,c,d){var f=new sN;rN(f,a,b,c,d,N());return f}function sN(){this.sm=this.tm=this.um=this.vm=0;this.rm=null}sN.prototype=new t; +sN.prototype.constructor=sN;e=sN.prototype;e.f=function(){return Ux(this)};e.x=function(){return new Z(this)};e.p=function(){var a=-889275714;a=X().m(a,Ha("InsufficientBits"));a=X().m(a,xw(X(),this.vm,this.um));a=X().m(a,xw(X(),this.tm,this.sm));a=X().m(a,zw(X(),this.rm));return X().T(a,3)};e.d=function(a){if(this===a)return!0;if(a instanceof sN&&0===(this.vm^a.vm|this.um^a.um)&&0===(this.tm^a.tm|this.sm^a.sm)){var b=this.rm;a=a.rm;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 3}; +e.v=function(){return"InsufficientBits"};e.c=function(a){switch(a){case 0:return r(this.vm,this.um);case 1:return r(this.tm,this.sm);case 2:return this.rm;default:throw O(new P,""+a);}};e.Fm=function(){return this.rm};e.dC=function(){return"cannot acquire "+da(this.vm,this.um)+" bits from a vector that contains "+da(this.tm,this.sm)+" bits"};e.Dy=function(a){return rN(new sN,this.vm,this.um,this.tm,this.sm,new J(a,this.rm))};z(sN,"scodec.Err$InsufficientBits",{A_:1,yG:1,k:1,s:1,b:1}); +function DQ(){this.zG=this.AG=this.$h=null;EQ=this;this.$h=fN(gp().Pq,0,0);this.AG=fN(Zy(gp(),dq(Qg(),new y(new Int32Array([0]))),Yy()),1,0);fN(Zy(gp(),dq(Qg(),new y(new Int32Array([255]))),Yy()),1,0);this.zG=fN(QJ(1,0,0),8,0);fN(QJ(1,0,255),8,0)}DQ.prototype=new t;DQ.prototype.constructor=DQ;function qp(a,b){a=b.z();var c=a.h;return fN(b,c<<3,c>>>29|0|a.l<<3)} +function fN(a,b,c){var d=eN(0,b,c),f=d.h;d=d.l;Ah();var g=a.z(),h=g.h;g=g.l;GG(0,d===g?f>>>0<=h>>>0:d>>0>f>>>0:g>d)?a.Hi(f,d):a,b,c)}function GQ(a,b,c){a=c>>2>>>29|0;c=7&(b+a|0);b=c-a|0;a=(~c&a|~(c^a)&b)>>31;return 0===(b|a)?r(8,0):r(b,a)}function HQ(a,b){return-1<<(8-b|0)<<24>>24}function eN(a,b,c){a=7+b|0;c=c+((b&~a)>>>31|0)|0;var d=c>>2>>>29|0;b=a+d|0;a=c+((a&d|(a|d)&~b)>>>31|0)|0;return r(b>>>3|0|a<<29,a>>3)} +function IQ(a,b,c,d){a=GQ(0,b,c).h;if(!d.e()&&8>a){c=d.z();var f=c.h;b=f-1|0;c=(c.l-1|0)+((f|~b)>>>31|0)|0;f=CE(d,b,c);return JQ(d,b,c,(f&HQ(0,a))<<24>>24)}return d}z(DQ,"scodec.bits.BitVector$",{N_:1,T_:1,S_:1,Wb:1,ep:1});var EQ;function rp(){EQ||(EQ=new DQ);return EQ} +function ME(a,b,c){this.EG=this.$x=this.cB=this.Sq=null;this.Sq=a;this.cB=b;this.$x=c;this.EG=new G((d=>f=>{var g=d.hh(f);g.e()?(g=new KQ,LQ(g,this,f,N()),f=new Vp(g)):f=new Tp(g.oa());return f})(fK(b.Vd(new MQ(b)).Ra(new G(d=>new C(d.Yx,d))))))}ME.prototype=new t;ME.prototype.constructor=ME;function vG(a,b,c,d,f){return new ME(a.Sq,a.cB.pa(new jN(b,new kN(c.ql(),d,f))),a.$x)}ME.prototype.ia=function(){return WM(this.Sq.ia(),JJ(FJ(),this.cB.vf(new G(a=>this.$x.g(a.Do.Rq).ia()))))}; +ME.prototype.ka=function(a){return(new qE(new G(b=>new qE(new G(c=>new oE(new G((d=>f=>d.Do.Zx.g(f))(c)),this.$x.g(c.Do.Rq))),new sE(this.EG.g(b)))),this.Sq)).ka(a)};ME.prototype.f=function(){return"discriminated("+this.Sq+")"};z(ME,"scodec.codecs.DiscriminatorCodec",{s0:1,Rb:1,lb:1,Qb:1,z0:1});function LQ(a,b,c,d){a.Jo=c;a.Io=d;if(null===b)throw Nr();a.ay=b;return a}function KQ(){this.ay=this.Io=this.Jo=null}KQ.prototype=new t;KQ.prototype.constructor=KQ;e=KQ.prototype;e.f=function(){return Ux(this)}; +e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof KQ&&a.ay===this.ay&&V(W(),this.Jo,a.Jo)){var b=this.Io;a=a.Io;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"UnknownDiscriminator"};e.c=function(a){if(0===a)return this.Jo;if(1===a)return this.Io;throw O(new P,""+a);};e.Fm=function(){return this.Io};e.dC=function(){return"Unknown discriminator "+this.Jo}; +e.Dy=function(a){return LQ(new KQ,this.ay,this.Jo,new J(a,this.Io))};z(KQ,"scodec.codecs.KnownDiscriminatorType$UnknownDiscriminator",{A0:1,yG:1,k:1,s:1,b:1});function NQ(){}NQ.prototype=new t;NQ.prototype.constructor=NQ;function OQ(){}OQ.prototype=NQ.prototype;function Nb(a){this.Vw=a}Nb.prototype=new SE;Nb.prototype.constructor=Nb;e=Nb.prototype;e.x=function(){return new Z(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof Nb&&V(W(),this.Vw,a.Vw)};e.f=function(){return nw(this)}; +e.t=function(){return 1};e.v=function(){return"Now"};e.c=function(a){if(0===a)return this.Vw;throw O(new P,""+a);};e.Kh=function(){return this.Vw};z(Nb,"cats.Now",{tQ:1,ED:1,Ll:1,b:1,k:1,s:1});function dK(){Ke()}dK.prototype=new t;dK.prototype.constructor=dK;z(dK,"cats.UnorderedFoldable$$anon$1",{JQ:1,b:1,lf:1,Tf:1,Rf:1,qg:1});function eK(){Ke()}eK.prototype=new t;eK.prototype.constructor=eK;z(eK,"cats.UnorderedFoldable$$anon$2",{KQ:1,b:1,lf:1,Tf:1,Rf:1,qg:1}); +function yN(a,b){this.Ri=a;this.Si=b}yN.prototype=new wN;yN.prototype.constructor=yN;e=yN.prototype;e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof yN){var b=this.Ri,c=a.Ri;if(null===b?null===c:b.d(c))return b=this.Si,a=a.Si,null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"Concat"};e.c=function(a){if(0===a)return this.Ri;if(1===a)return this.Si;throw O(new P,""+a);};z(yN,"cats.data.AndThen$Concat",{WQ:1,XJ:1,U:1,k:1,s:1,b:1}); +function xN(a,b){this.Pf=a;this.Xg=b}xN.prototype=new wN;xN.prototype.constructor=xN;e=xN.prototype;e.p=function(){var a=-889275714;a=X().m(a,Ha("Single"));a=X().m(a,zw(X(),this.Pf));a=X().m(a,this.Xg);return X().T(a,2)};e.d=function(a){if(this===a)return!0;if(a instanceof xN&&this.Xg===a.Xg){var b=this.Pf;a=a.Pf;return null===b?null===a:b.d(a)}return!1};e.t=function(){return 2};e.v=function(){return"Single"};e.c=function(a){if(0===a)return this.Pf;if(1===a)return this.Xg;throw O(new P,""+a);}; +z(xN,"cats.data.AndThen$Single",{XQ:1,XJ:1,U:1,k:1,s:1,b:1});function PQ(){}PQ.prototype=new EN;PQ.prototype.constructor=PQ;function QQ(){}QQ.prototype=PQ.prototype;function RQ(){new SQ}RQ.prototype=new mK;RQ.prototype.constructor=RQ;function un(a,b){a=Pg(Qg(),new (B(xL).P)([]));return jc(Ob(),(Ob(),new $b(b)),Yb(Ob(),a))}z(RQ,"cats.data.NonEmptyChainImpl$",{FR:1,GR:1,IR:1,JR:1,KR:1,TR:1});var TQ;function ic(){TQ||(TQ=new RQ);return TQ}function UQ(){this.Oz=null;this.Oz=new VQ}UQ.prototype=new eF; +UQ.prototype.constructor=UQ;function Fn(a,b){if(N().d(b))throw Qi("Cannot create NonEmptyList from empty list");if(b instanceof J)return a=b.Z,b=b.Fa,oi(),new Cm(b,a);throw new D(b);}UQ.prototype.Rc=function(a){return new Cm(a.c(0),a.c(1))};z(UQ,"cats.data.NonEmptyList$",{OR:1,PR:1,RR:1,SR:1,Wb:1,pd:1});var WQ;function oi(){WQ||(WQ=new UQ);return WQ}function XQ(){}XQ.prototype=new t;XQ.prototype.constructor=XQ;z(XQ,"cats.instances.InvariantMonoidalInstances$$anon$1",{iS:1,b:1,pg:1,mg:1,og:1,ng:1}); +function YQ(){}YQ.prototype=new t;YQ.prototype.constructor=YQ;z(YQ,"cats.instances.InvariantMonoidalInstances$$anon$4",{jS:1,b:1,pg:1,mg:1,og:1,ng:1});function ZQ(){this.Qz=null;$Q=this;this.Qz=new aR;new pK}ZQ.prototype=new t;ZQ.prototype.constructor=ZQ;z(ZQ,"cats.instances.package$list$",{zS:1,pE:1,oE:1,nE:1,RD:1,TD:1});var $Q;function xq(){$Q||($Q=new ZQ);return $Q}function wK(){vK=this;new uK}wK.prototype=new t;wK.prototype.constructor=wK; +z(wK,"cats.instances.package$stream$",{CS:1,wE:1,vE:1,uE:1,XD:1,YD:1});var vK;function zK(){yK=this;new bR;new xK}zK.prototype=new t;zK.prototype.constructor=zK;z(zK,"cats.instances.package$vector$",{ES:1,AE:1,zE:1,yE:1,$D:1,bE:1});var yK;function eM(a){this.hE=null;if(null===a)throw Nr();this.hE=a}eM.prototype=new t;eM.prototype.constructor=eM;e=eM.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)}; +e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return this.hE.I(a,b)};z(eM,"cats.kernel.Order$$anon$1",{RS:1,Fd:1,b:1,yf:1,Af:1,zf:1});function cR(){}cR.prototype=new t;cR.prototype.constructor=cR;e=cR.prototype;e.ke=function(a){return a.p()};e.I=function(a,b){return XK(a.Ya,b.Ya)};e.Ud=function(a,b){return Yv(W(),a,b)};e.Le=function(a,b){return 0+b};e.Pe=function(a,b){return+Math.max(+a,+b)};z(dR,"cats.kernel.instances.DoubleOrder",{wT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1});function eR(){}eR.prototype=new t; +eR.prototype.constructor=eR;e=eR.prototype;e.ke=function(a){a=Math.fround(a);var b=a|0;if(b===a&&-Infinity!==1/a)return b;if(a!==a)return 2146959360;Oa.setFloat64(0,a,!0);return(Oa.getInt32(0,!0)|0)^(Oa.getInt32(4,!0)|0)};e.I=function(a,b){return Ba(Ca(),Math.fround(a),Math.fround(b))};e.Ud=function(a,b){a=Math.fround(a);b=Math.fround(b);return a===b};e.Le=function(a,b){a=Math.fround(a);b=Math.fround(b);return a>b}; +e.Pe=function(a,b){a=Math.fround(a);b=Math.fround(b);return Math.fround(+Math.max(a,b))};z(eR,"cats.kernel.instances.FloatOrder",{ET:1,b:1,ee:1,Fe:1,Ue:1,Sf:1});function Se(a){this.fb=0;this.gb=!1;this.Wl=a}Se.prototype=new dA;Se.prototype.constructor=Se;e=Se.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Se){var b=this.Wl;a=a.Wl;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Backtrack"}; +e.c=function(a){if(0===a)return this.Wl;throw O(new P,""+a);};e.qa=function(a){return fg(He(),this.Wl,a)};z(Se,"cats.parse.Parser$Impl$Backtrack",{PU:1,Ve:1,vb:1,k:1,s:1,b:1});function Ue(a,b,c){this.fb=0;this.gb=!1;this.Hj=a;this.Ok=b;this.dq=c}Ue.prototype=new dA;Ue.prototype.constructor=Ue;e=Ue.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Ue){if(this.Hj===a.Hj){var b=this.Ok,c=a.Ok;b=null===b?null===c:b.d(c)}else b=!1;if(b)return b=this.dq,a=a.dq,null===b?null===a:b.d(a)}return!1};e.t=function(){return 3};e.v=function(){return"CharIn"};e.c=function(a){switch(a){case 0:return this.Hj;case 1:return this.Ok;case 2:return this.dq;default:throw O(new P,""+a);}};e.f=function(){return"CharIn("+this.Hj+", bitSet \x3d ..., "+this.dq+")"}; +function fR(a,b){var c=Ob().Qf;a=a.dq;for(a=new J(a.mc,a.fc);!a.e();){var d=a.u();if(null!==d){var f=p($a(d.Y));d=p($a(d.V))}else throw new D(d);c=Rb(c,new GF(b,$a(f),$a(d)));a=a.A()}return c}e.gC=function(a){var b=a.Wa;if(bfR(this,b))));return 0}a.bb=(Ke(),new og(new pg(()=>fR(this,b))));return 0};e.qa=function(a){return p(this.gC(a))}; +z(Ue,"cats.parse.Parser$Impl$CharIn",{RU:1,Ve:1,vb:1,k:1,s:1,b:1});function Ff(a){this.fb=0;this.gb=!1;this.Xl=a;this.PE=null}Ff.prototype=new dA;Ff.prototype.constructor=Ff;e=Ff.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Ff){var b=this.Xl;a=a.Xl;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Defer"};e.c=function(a){if(0===a)return this.Xl;throw O(new P,""+a);}; +e.qa=function(a){var b=this.PE;null===b&&(this.PE=b=Gg(He(),this.Xl));return b.qa(a)};z(Ff,"cats.parse.Parser$Impl$Defer",{UU:1,Ve:1,vb:1,k:1,s:1,b:1});function te(){this.fb=0;this.gb=!1}te.prototype=new dA;te.prototype.constructor=te;e=te.prototype;e.x=function(){return new Z(this)};e.d=function(a){return this===a||a instanceof te||!1};e.f=function(){return nw(this)};e.t=function(){return 0};e.v=function(){return"Fail"};e.c=function(a){throw O(new P,""+a);}; +e.qa=function(a){var b=a.Wa;a.bb=(Ke(),new og(new pg(()=>{Ob();return new $b(new ig(b))})));return null};z(te,"cats.parse.Parser$Impl$Fail",{YU:1,Ve:1,vb:1,k:1,s:1,b:1});function of(a,b){this.fb=0;this.gb=!1;this.Pk=a;this.Zl=b}of.prototype=new dA;of.prototype.constructor=of;e=of.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof of){var b=this.Pk,c=a.Pk;if(null===b?null===c:b.d(c))return b=this.Zl,a=a.Zl,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)}; +e.t=function(){return 2};e.v=function(){return"Map"};e.c=function(a){if(0===a)return this.Pk;if(1===a)return this.Zl;throw O(new P,""+a);};e.qa=function(a){return Bg(He(),this.Pk,this.Zl,a)};z(of,"cats.parse.Parser$Impl$Map",{aV:1,Ve:1,vb:1,k:1,s:1,b:1}); +function se(a){this.fb=0;this.gb=!1;this.RE=null;this.Bd=a;if(!(0<=a.Qa(2)))throw Qi("requirement failed: expected more than two items, found: "+a.q());if(0<=a.E()){var b=new (B(yh).P)(a.E());Fd(a,b,0,2147483647)}else{b=[];for(a=a.i();a.n();){var c=a.j();b.push(null===c?null:c)}b=new (B(yh).P)(b)}this.RE=b}se.prototype=new dA;se.prototype.constructor=se;e=se.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof se){var b=this.Bd;a=a.Bd;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"OneOf"};e.c=function(a){if(0===a)return this.Bd;throw O(new P,""+a);};e.qa=function(a){return jg(He(),this.RE,a)};z(se,"cats.parse.Parser$Impl$OneOf",{eV:1,Ve:1,vb:1,k:1,s:1,b:1});function Cf(a,b){this.fb=0;this.gb=!1;this.$l=a;this.am=b;GG(Ah(),a instanceof Me||b instanceof Me)}Cf.prototype=new dA; +Cf.prototype.constructor=Cf;e=Cf.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Cf){var b=this.$l,c=a.$l;if(null===b?null===c:b.d(c))return b=this.am,a=a.am,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Prod"};e.c=function(a){if(0===a)return this.$l;if(1===a)return this.am;throw O(new P,""+a);};e.qa=function(a){return sg(He(),this.$l,this.am,a)}; +z(Cf,"cats.parse.Parser$Impl$Prod",{hV:1,Ve:1,vb:1,k:1,s:1,b:1});function Ef(a,b,c,d){this.fb=0;this.gb=!1;this.jx=null;this.bm=a;this.Yn=b;this.Qk=c;this.ix=d;if(1>b)throw Qi("expected min \x3e\x3d 1, found: "+b);this.jx=null}Ef.prototype=new dA;Ef.prototype.constructor=Ef;e=Ef.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Ef){if(this.Yn===a.Yn&&this.Qk===a.Qk){var b=this.bm,c=a.bm;b=null===b?null===c:b.d(c)}else b=!1;if(b)return b=this.ix,a=a.ix,null===b?null===a:b===a}return!1};e.f=function(){return nw(this)};e.t=function(){return 4};e.v=function(){return"Rep"};e.c=function(a){switch(a){case 0:return this.bm;case 1:return this.Yn;case 2:return this.Qk;case 3:return this.ix;default:throw O(new P,""+a);}}; +e.qa=function(a){var b=this.bm.qa(a);if(null!==a.bb)return this.jx;if(a.We){b=this.ix.$o(b);a:{He();for(var c=this.bm,d=this.Yn-1|0,f=2147483647===this.Qk?2147483647:this.Qk-1|0,g=a.Wa,h=0;h<=f;){var k=c.qa(a);if(null===a.bb)h=1+h|0,b.kl(k),g=a.Wa;else if(a.Wa===g&&h>=d){a.bb=null;break}else{a=!1;break a}}a=!0}return a?b.Hm():this.jx}He();b=this.bm;c=this.Yn-1|0;d=2147483647===this.Qk?2147483647:this.Qk-1|0;f=a.Wa;for(g=0;g<=d;)if(b.qa(a),null===a.bb)g=1+g|0,f=a.Wa;else{a.Wa===f&&g>=c&&(a.bb=null); +break}return this.jx};z(Ef,"cats.parse.Parser$Impl$Rep",{kV:1,Ve:1,vb:1,k:1,s:1,b:1});function Hf(a,b){this.fb=0;this.gb=!1;this.hq=a;this.iq=b}Hf.prototype=new dA;Hf.prototype.constructor=Hf;e=Hf.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof Hf){var b=this.hq,c=a.hq;if(null===b?null===c:b.d(c))return b=this.iq,a=a.iq,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Select"}; +e.c=function(a){if(0===a)return this.hq;if(1===a)return this.iq;throw O(new P,""+a);};e.qa=function(a){return Cg(He(),this.hq,this.iq,a)};z(Hf,"cats.parse.Parser$Impl$Select",{mV:1,Ve:1,vb:1,k:1,s:1,b:1});function qf(a,b){this.fb=0;this.gb=!1;this.Rk=a;this.Sk=b;GG(Ah(),a instanceof Me||b instanceof Me)}qf.prototype=new dA;qf.prototype.constructor=qf;e=qf.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof qf){var b=this.Rk,c=a.Rk;if(null===b?null===c:b.d(c))return b=this.Sk,a=a.Sk,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"SoftProd"};e.c=function(a){if(0===a)return this.Rk;if(1===a)return this.Sk;throw O(new P,""+a);};e.qa=function(a){return tg(He(),this.Rk,this.Sk,a)};z(qf,"cats.parse.Parser$Impl$SoftProd",{pV:1,Ve:1,vb:1,k:1,s:1,b:1}); +function Ve(a){this.fb=0;this.gb=!1;this.Vf=a;if(""===a)throw Qi("we need a non-empty string to expect a message");}Ve.prototype=new dA;Ve.prototype.constructor=Ve;e=Ve.prototype;e.x=function(){return new Z(this)};e.d=function(a){return this===a||a instanceof Ve&&this.Vf===a.Vf};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Str"};e.c=function(a){if(0===a)return this.Vf;throw O(new P,""+a);}; +e.ek=function(a){var b=a.Wa;Fh(a.Yh,b,this.Vf,this.Vf.length)?a.Wa=a.Wa+this.Vf.length|0:a.bb=(Ke(),new og(new pg(()=>{Ob();var c=N();return new $b(new qg(b,new J(this.Vf,c)))})))};e.qa=function(a){this.ek(a)};z(Ve,"cats.parse.Parser$Impl$Str",{sV:1,Ve:1,vb:1,k:1,s:1,b:1}); +function mf(a){this.fb=0;this.gb=!1;this.SE=null;this.Zg=a;if(!(2<=Nt(Bu(),a.Ta)))throw Qi("requirement failed: expected more than two items, found: "+Nt(Bu(),a.Ta));Bu();if(null!==xu(0,a.Ta,"",a.Ba))throw Qi("requirement failed: empty string is not allowed in alternatives");var b=Uh();a=Fn(oi(),rg(N(),a));this.SE=Xh(b,null,"",tF(new J(a.mc,a.fc)))}mf.prototype=new dA;mf.prototype.constructor=mf;e=mf.prototype;e.x=function(){return new Z(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof mf){var b=this.Zg;a=a.Zg;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"StringIn"};e.c=function(a){if(0===a)return this.Zg;throw O(new P,""+a);};e.qa=function(a){return mg(He(),this.SE,this.Zg,a)};z(mf,"cats.parse.Parser$Impl$StringIn",{tV:1,Ve:1,vb:1,k:1,s:1,b:1});function we(a){this.fb=0;this.gb=!1;this.Yi=a}we.prototype=new dA;we.prototype.constructor=we;e=we.prototype; +e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof we){var b=this.Yi;a=a.Yi;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"StringP"};e.c=function(a){if(0===a)return this.Yi;throw O(new P,""+a);};e.qa=function(a){He();var b=this.Yi,c=a.We;a.We=!1;var d=a.Wa;b.qa(a);a.We=c;return null===a.bb?a.Yh.substring(d,a.Wa):null};z(we,"cats.parse.Parser$Impl$StringP",{uV:1,Ve:1,vb:1,k:1,s:1,b:1}); +function xe(a){this.fb=0;this.gb=!1;this.Yc=a}xe.prototype=new dA;xe.prototype.constructor=xe;e=xe.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof xe){var b=this.Yc;a=a.Yc;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Void"};e.c=function(a){if(0===a)return this.Yc;throw O(new P,""+a);};e.qa=function(a){He();var b=this.Yc,c=a.We;a.We=!1;b.qa(a);a.We=c}; +z(xe,"cats.parse.Parser$Impl$Void",{zV:1,Ve:1,vb:1,k:1,s:1,b:1});function sf(a,b){this.fb=0;this.gb=!1;this.lq=a;this.Jj=b}sf.prototype=new dA;sf.prototype.constructor=sf;e=sf.prototype;e.x=function(){return new Z(this)};e.d=function(a){if(this===a)return!0;if(a instanceof sf&&this.lq===a.lq){var b=this.Jj;a=a.Jj;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"WithContextP"}; +e.c=function(a){if(0===a)return this.lq;if(1===a)return this.Jj;throw O(new P,""+a);};e.qa=function(a){var b=this.Jj.qa(a);null!==a.bb&&(a.bb=lg(a.bb,new G(c=>sz(c,new G(d=>new pe(this.lq,d))))));return b};z(sf,"cats.parse.Parser$Impl$WithContextP",{BV:1,Ve:1,vb:1,k:1,s:1,b:1});function $h(){}$h.prototype=new t;$h.prototype.constructor=$h; +$h.prototype.BB=function(a,b){Uh();var c=a.length;var d=b.length;c=c"number"===typeof a),pa=z(0,"java.lang.Float",{K1:1,hj:1,b:1,Lb:1,ak:1,wy:1},a=>oa(a)),na=z(0,"java.lang.Integer",{M1:1,hj:1,b:1,Lb:1,ak:1,wy:1},a=>ia(a)),va=z(0,"java.lang.Long",{Q1:1,hj:1,b:1,Lb:1,ak:1,wy:1},a=>a instanceof ca); +class Rn extends NN{constructor(a){super();ft(this,a)}}z(Rn,"java.lang.NumberFormatException",{W1:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});function Pl(a,b){return a.codePointAt(b)|0}function Ha(a){for(var b=a.length,c=0,d=0;d!==b;)c=((c<<5)-c|0)+a.charCodeAt(d)|0,d=1+d|0;return c}function Aa(a,b){for(var c=a.length,d=b.length,f=ca.length||0>b||0>b)throw a=new QA,ft(a,"Index out of Bound"),a;d=d-0|0;for(var f=0;fb||d>(a.length-b|0)||d>(c.length-0|0))return!1;if(0>=d)return!0;a=a.substring(b,b+d|0);c=c.substring(0,0+d|0);return a===c} +function Ad(a,b,c){b=sm(Sl(),b);if(""===a)b=new (B(fa).P)([""]);else{var d=0=a.charCodeAt(c);)c=1+c|0;if(c===b)return"";for(var d=b;32>=a.charCodeAt(d-1|0);)d=d-1|0;return 0===(c|d^b)?a:a.substring(c,d)}var fa=z(0,"java.lang.String",{Y0:1,b:1,Lb:1,qy:1,ak:1,wy:1},a=>"string"===typeof a);class QA extends P{}z(QA,"java.lang.StringIndexOutOfBoundsException",{$1:1,MH:1,Mb:1,xb:1,Xa:1,b:1});function hR(){this.Pd=this.L=this.aa=this.He=0;this.Ze=null;this.rg=0}hR.prototype=new ak;hR.prototype.constructor=hR;function iR(){} +iR.prototype=hR.prototype;function xk(a,b){if(b===a)throw vb();if(a.ff())throw new TB;var c=b.aa,d=b.L,f=c-d|0,g=a.L,h=g+f|0;if(h>a.aa)throw new Ek;a.L=h;Zj.prototype.yb.call(b,c);h=b.Ze;if(null!==h)a.yJ(g,h,b.rg+d|0,f);else for(;d!==c;)a.zJ(g,b.Vv(d)),d=1+d|0,g=1+g|0}function qN(a,b,c){jk||(jk=new hk);tk||(tk=new sk);var d="string"===typeof b?b.length:b.q();c=c-0|0;if(0>d||(0+d|0)>("string"===typeof b?b.length:b.q()))throw kk();var f=0+c|0;if(0>c||f>d)throw kk();b=new jR(d,b,0,0,f);xk(a,b)}e=hR.prototype; +e.p=function(){for(var a=this.L,b=this.aa,c=-182887236,d=a;d!==b;)c=Ll().m(c,this.Vv(d)),d=1+d|0;return Ll().T(c,b-a|0)};e.d=function(a){return a instanceof hR&&0===kR(this,a)};function kR(a,b){if(a===b)return 0;for(var c=a.L,d=a.aa-c|0,f=b.L,g=b.aa-f|0,h=da?"":" near index "+a)+"\n"+b;if(0<=a&&null!==b&&aa)throw vb();a=" ".repeat(a);c=c+"\n"+a+"^"}return c}}z(Nl,"java.util.regex.PatternSyntaxException",{R2:1,Eg:1,Mb:1,xb:1,Xa:1,b:1}); +function Hm(a,b){this.Yf=null;this.Yf=(oi(),new Cm(a,b))}Hm.prototype=new TN;Hm.prototype.constructor=Hm;z(Hm,"pink.cozydev.lucille.Query$And$$anon$10",{qY:1,pA:1,k:1,s:1,b:1,Zc:1});function VN(a,b){this.Yf=null;if(null===b)throw Nr();this.Yf=Bm(Em(),b.Yf,a)}VN.prototype=new TN;VN.prototype.constructor=VN;z(VN,"pink.cozydev.lucille.Query$And$$anon$6",{rY:1,pA:1,k:1,s:1,b:1,Zc:1});function UN(a){this.Yf=a}UN.prototype=new TN;UN.prototype.constructor=UN; +z(UN,"pink.cozydev.lucille.Query$And$$anon$7",{sY:1,pA:1,k:1,s:1,b:1,Zc:1});function ln(a,b,c){this.Yf=null;this.Yf=(oi(),new Cm(a,new J(c,b)))}ln.prototype=new TN;ln.prototype.constructor=ln;z(ln,"pink.cozydev.lucille.Query$And$$anon$9",{tY:1,pA:1,k:1,s:1,b:1,Zc:1});function Wn(a,b){this.rA=a;this.qA=b}Wn.prototype=new t;Wn.prototype.constructor=Wn;e=Wn.prototype;e.x=function(){return new Z(this)};e.md=function(){return this};e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)}; +e.d=function(a){if(this===a)return!0;if(a instanceof Wn&&this.rA===a.rA){var b=this.qA;a=a.qA;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Fuzzy"};e.c=function(a){if(0===a)return this.rA;if(1===a)return this.qA;throw O(new P,""+a);};z(Wn,"pink.cozydev.lucille.Query$Fuzzy",{wY:1,k:1,s:1,b:1,Zc:1,Zk:1});function YN(a,b){this.$e=null;if(null===b)throw Nr();this.$e=Bm(Em(),b.$e,a)}YN.prototype=new WN; +YN.prototype.constructor=YN;z(YN,"pink.cozydev.lucille.Query$Or$$anon$1",{BY:1,wx:1,k:1,s:1,b:1,Zc:1});function XN(a){this.$e=a}XN.prototype=new WN;XN.prototype.constructor=XN;z(XN,"pink.cozydev.lucille.Query$Or$$anon$2",{CY:1,wx:1,k:1,s:1,b:1,Zc:1});function JL(a,b,c){this.$e=null;this.$e=(oi(),new Cm(a,new J(c,b.ub())))}JL.prototype=new WN;JL.prototype.constructor=JL;z(JL,"pink.cozydev.lucille.Query$Or$$anon$3",{DY:1,wx:1,k:1,s:1,b:1,Zc:1}); +function kn(a,b,c){this.$e=null;this.$e=(oi(),new Cm(a,new J(c,b)))}kn.prototype=new WN;kn.prototype.constructor=kn;z(kn,"pink.cozydev.lucille.Query$Or$$anon$4",{EY:1,wx:1,k:1,s:1,b:1,Zc:1});function mG(a,b){this.$e=null;this.$e=(oi(),new Cm(a,b))}mG.prototype=new WN;mG.prototype.constructor=mG;z(mG,"pink.cozydev.lucille.Query$Or$$anon$5",{FY:1,wx:1,k:1,s:1,b:1,Zc:1});function Un(a){this.rq=a}Un.prototype=new t;Un.prototype.constructor=Un;e=Un.prototype;e.x=function(){return new Z(this)};e.md=function(){return this}; +e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof Un&&this.rq===a.rq};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Phrase"};e.c=function(a){if(0===a)return this.rq;throw O(new P,""+a);};z(Un,"pink.cozydev.lucille.Query$Phrase",{GY:1,k:1,s:1,b:1,Zc:1,Zk:1});function bo(a){this.xx=a}bo.prototype=new t;bo.prototype.constructor=bo;e=bo.prototype;e.x=function(){return new Z(this)};e.md=function(){return this}; +e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof bo&&this.xx===a.xx};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Prefix"};e.c=function(a){if(0===a)return this.xx;throw O(new P,""+a);};z(bo,"pink.cozydev.lucille.Query$Prefix",{HY:1,k:1,s:1,b:1,Zc:1,Zk:1});function Vn(a,b){this.zx=a;this.yx=b}Vn.prototype=new t;Vn.prototype.constructor=Vn;e=Vn.prototype;e.x=function(){return new Z(this)};e.md=function(){return this}; +e.Oc=function(a){return a.g(this)};e.p=function(){var a=-889275714;a=X().m(a,Ha("Proximity"));a=X().m(a,zw(X(),this.zx));a=X().m(a,this.yx);return X().T(a,2)};e.d=function(a){return this===a||a instanceof Vn&&this.yx===a.yx&&this.zx===a.zx};e.f=function(){return nw(this)};e.t=function(){return 2};e.v=function(){return"Proximity"};e.c=function(a){if(0===a)return this.zx;if(1===a)return this.yx;throw O(new P,""+a);};z(Vn,"pink.cozydev.lucille.Query$Proximity",{IY:1,k:1,s:1,b:1,Zc:1,Zk:1}); +function ao(a){this.ho=a}ao.prototype=new t;ao.prototype.constructor=ao;e=ao.prototype;e.x=function(){return new Z(this)};e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof ao&&this.ho===a.ho};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"Term"};e.c=function(a){if(0===a)return this.ho;throw O(new P,""+a);};e.md=function(a){return a.g(this)};z(ao,"pink.cozydev.lucille.Query$Term",{JY:1,k:1,s:1,b:1,Zc:1,Zk:1}); +function ho(a,b,c,d){this.sq=a;this.tq=b;this.Ax=c;this.Bx=d}ho.prototype=new t;ho.prototype.constructor=ho;e=ho.prototype;e.x=function(){return new Z(this)};e.md=function(){return this};e.Oc=function(a){return a.g(this)};e.p=function(){var a=-889275714;a=X().m(a,Ha("TermRange"));a=X().m(a,zw(X(),this.sq));a=X().m(a,zw(X(),this.tq));a=X().m(a,this.Ax?1231:1237);a=X().m(a,this.Bx?1231:1237);return X().T(a,4)}; +e.d=function(a){if(this===a)return!0;if(a instanceof ho){if(this.Ax===a.Ax&&this.Bx===a.Bx){var b=this.sq,c=a.sq;b=null===b?null===c:b.d(c)}else b=!1;if(b)return b=this.tq,a=a.tq,null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)};e.t=function(){return 4};e.v=function(){return"TermRange"};e.c=function(a){switch(a){case 0:return this.sq;case 1:return this.tq;case 2:return this.Ax;case 3:return this.Bx;default:throw O(new P,""+a);}}; +z(ho,"pink.cozydev.lucille.Query$TermRange",{KY:1,k:1,s:1,b:1,Zc:1,Zk:1});function eo(a){this.Cx=a}eo.prototype=new t;eo.prototype.constructor=eo;e=eo.prototype;e.x=function(){return new Z(this)};e.md=function(){return this};e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)};e.d=function(a){return this===a||a instanceof eo&&this.Cx===a.Cx};e.f=function(){return nw(this)};e.t=function(){return 1};e.v=function(){return"TermRegex"}; +e.c=function(a){if(0===a)return this.Cx;throw O(new P,""+a);};z(eo,"pink.cozydev.lucille.Query$TermRegex",{LY:1,k:1,s:1,b:1,Zc:1,Zk:1});function co(a){this.sA=a}co.prototype=new t;co.prototype.constructor=co;e=co.prototype;e.x=function(){return new Z(this)};e.md=function(){return this};e.Oc=function(a){return a.g(this)};e.p=function(){return xh(this)};e.d=function(a){if(this===a)return!0;if(a instanceof co){var b=this.sA;a=a.sA;return null===b?null===a:b.d(a)}return!1};e.f=function(){return nw(this)}; +e.t=function(){return 1};e.v=function(){return"WildCard"};e.c=function(a){if(0===a)return this.sA;throw O(new P,""+a);};z(co,"pink.cozydev.lucille.Query$WildCard",{OY:1,k:1,s:1,b:1,Zc:1,Zk:1});function mR(){}mR.prototype=new $N;mR.prototype.constructor=mR;e=mR.prototype;e.v=function(){return"None"};e.t=function(){return 0};e.c=function(a){return Aw(X(),a)};e.x=function(){return new aO(this)};e.p=function(){return 2433880};e.f=function(){return"None"};e.oa=function(){throw new Cs("None.get");}; +z(mR,"scala.None$",{d3:1,mO:1,w:1,s:1,k:1,b:1});var nR;function F(){nR||(nR=new mR);return nR}function E(a){this.zc=a}E.prototype=new $N;E.prototype.constructor=E;e=E.prototype;e.oa=function(){return this.zc};e.v=function(){return"Some"};e.t=function(){return 1};e.c=function(a){return 0===a?this.zc:Aw(X(),a)};e.x=function(){return new aO(this)};e.p=function(){return xx(this,1323286827)};e.f=function(){return nw(this)};e.d=function(a){return this===a||a instanceof E&&V(W(),this.zc,a.zc)}; +z(E,"scala.Some",{J3:1,mO:1,w:1,s:1,k:1,b:1});function oR(){}oR.prototype=new t;oR.prototype.constructor=oR;function pR(){}e=pR.prototype=oR.prototype;e.xd=function(){return this.Ib()};e.Xj=function(a){return this.Kb().mb(a)};e.xf=function(){return this.Kb().Da()};e.u=function(){return this.i().j()};e.Na=function(a){return this.Vd(qR(new rR,this,a))};e.ya=function(a){return this.Vd(zR(new AR,this,a))};e.A=function(){return kH(this)};e.$j=function(){if(this.e())throw SB();return this.ya(1)}; +e.Cb=function(a){return lH(this,a)};e.Oa=function(a){gs(this,a)};e.fj=function(a){return hs(this,a)};e.ll=function(a){return is(this,a)};e.Bg=function(a,b){return js(this,a,b)};e.rl=function(a){return ls(this,a)};e.e=function(){a:switch(this.E()){case -1:var a=!this.i().n();break a;case 0:a=!0;break a;default:a=!1}return a};e.X=function(){if(0<=this.E())var a=this.E();else{a=this.i();for(var b=0;a.n();)b=1+b|0,a.j();a=b}return a};e.hc=function(a,b,c){return Fd(this,a,b,c)}; +e.rf=function(a,b,c,d){return ns(this,a,b,c,d)};e.ub=function(){return rg(N(),this)};e.Ln=function(){return tz(Nv(),this)};e.Kn=function(){return aP(Kv(),this)};e.Wg=function(a){return os(this,a)};e.Xd=function(){for(var a=N(),b=this.i();b.n();)a=new J(b.j(),a);return a};e.E=function(){return-1};e.Vd=function(a){return this.Xj(a)};function Cr(a,b){a.Kg=b;a.sa=0;a.eg=Pi(Nh(),a.Kg);return a}function Dr(){this.Kg=null;this.eg=this.sa=0}Dr.prototype=new TL;Dr.prototype.constructor=Dr;function BR(){} +BR.prototype=Dr.prototype;Dr.prototype.E=function(){return this.eg-this.sa|0};Dr.prototype.n=function(){return this.sa=Pi(Nh(),this.Kg)&&Lv().da.j();var a=mw(Qg(),this.Kg,this.sa);this.sa=1+this.sa|0;return a};Dr.prototype.df=function(a){if(0a)a=this.eg;else{var b=this.eg;a=ba?0:a);return this}; +e.Sg=function(a,b){a=0>a?0:a>this.Bf?this.Bf:a;b=(0>b?0:b>this.Bf?this.Bf:b)-a|0;this.Bf=0>b?0:b;this.kk=this.kk+a|0;return this};z(DR,"scala.collection.IndexedSeqView$IndexedSeqViewIterator",{IO:1,ra:1,la:1,w:1,y:1,b:1});function FR(a,b){a.BC=b;a.gf=b.q();a.Rm=a.gf-1|0;return a}function GR(){this.BC=null;this.Rm=this.gf=0}GR.prototype=new TL;GR.prototype.constructor=GR;function HR(){}HR.prototype=GR.prototype;GR.prototype.n=function(){return 0=a?0<=b&&bnew vz(b)));return a}vH.prototype.Ia=function(a){return IR(this,a)};z(vH,"scala.collection.Iterator$$anon$21",{A5:1,h9:1,Ei:1,ae:1,ud:1,td:1});function JR(a,b,c){return a.Yj(b,new Rh(()=>c.g(b)))}function KR(a){throw new Cs("key not found: "+a);}function LR(a,b,c,d,f){return ns(new hd(a.i(),new Zo(g=>{if(null!==g)return g.Y+" -\x3e "+g.V;throw new D(g);})),b,c,d,f)} +function MR(a,b){var c=a.xf(),d=pO();for(a=a.i();a.n();){var f=a.j();qO(d,b.g(f))&&c.Ia(f)}return c.Sa()}function NR(a,b){var c=a.Dg().Da();YI(c,a,1);c.Ia(b);c.gc(a);return c.Sa()}function Lg(a,b){var c=a.Dg().Da();YI(c,a,1);c.gc(a);c.Ia(b);return c.Sa()}function rw(a){a.dz||(a.ez=new sw(new x(0)),a.dz=!0);return a.ez}function OR(){this.TC=this.ez=null;this.dz=!1;PR=this;this.TC=new bO(this)}OR.prototype=new t;OR.prototype.constructor=OR;e=OR.prototype;e.gi=function(a,b){return QR(0,a,b)}; +function QR(a,b,c){return b instanceof RR?b:pw(0,AC($r(),b,c))}e.ap=function(a){return new GH((cr(),new HH),new Zo(b=>pw(qw(),os(b,a))))}; +function pw(a,b){if(null===b)return null;if(b instanceof x)return new sw(b);if(b instanceof y)return new tw(b);if(b instanceof jb)return new SR(b);if(b instanceof hb)return new TR(b);if(b instanceof ib)return new UR(b);if(b instanceof db)return new uw(b);if(b instanceof eb)return new VR(b);if(b instanceof gb)return new WR(b);if(b instanceof cb)return new YR(b);if(Li(b))return new ZR(b);throw new D(b);}e.Xv=function(a){return this.ap(a)};e.JB=function(a,b){return QR(0,a,b)}; +e.uH=function(){return this.dz?this.ez:rw(this)};z(OR,"scala.collection.immutable.ArraySeq$",{k6:1,YO:1,DO:1,CO:1,zC:1,b:1});var PR;function qw(){PR||(PR=new OR);return PR}function VO(a,b){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null;for(JO(this,b.Be);this.n();)b=this.Df.hb(this.Dc),RO(a,a.rk,this.Df.gj(this.Dc),this.Df.ii(this.Dc),b,ds(fs(),b),0),this.Dc=1+this.Dc|0}VO.prototype=new LO;VO.prototype.constructor=VO;VO.prototype.Yv=function(){Lv().da.j();throw new eI;}; +VO.prototype.j=function(){this.Yv()};z(VO,"scala.collection.immutable.HashMapBuilder$$anon$1",{D6:1,fz:1,ra:1,la:1,w:1,y:1});function ZO(a,b){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null;for(JO(this,b.ob);this.n();)b=this.Df.hb(this.Dc),XO(a,a.sk,this.Df.Pa(this.Dc),b,ds(fs(),b),0),this.Dc=1+this.Dc|0}ZO.prototype=new LO;ZO.prototype.constructor=ZO;ZO.prototype.Yv=function(){Lv().da.j();throw new eI;};ZO.prototype.j=function(){this.Yv()}; +z(ZO,"scala.collection.immutable.HashSetBuilder$$anon$1",{H6:1,fz:1,ra:1,la:1,w:1,y:1});function yI(a){return!!(a&&a.$classData&&a.$classData.wb.Ga)}function $R(a){this.vw=a;this.ln=0}$R.prototype=new kP;$R.prototype.constructor=$R;z($R,"scala.collection.immutable.Map$Map2$$anon$1",{Z6:1,$6:1,ra:1,la:1,w:1,y:1});function aS(a){this.mn=a;this.nn=0}aS.prototype=new mP;aS.prototype.constructor=aS;z(aS,"scala.collection.immutable.Map$Map3$$anon$4",{b7:1,c7:1,ra:1,la:1,w:1,y:1}); +function bS(a){this.vk=a;this.on=0}bS.prototype=new oP;bS.prototype.constructor=bS;z(bS,"scala.collection.immutable.Map$Map4$$anon$7",{e7:1,f7:1,ra:1,la:1,w:1,y:1});function cS(a){this.rw=this.qw=this.gz=null;this.XC=0;this.aJ=null;this.th=this.fn=-1;this.qw=new y(1+R().yw|0);this.rw=new (B(kt).P)(1+R().yw|0);MO(this,a);NO(this);this.XC=0}cS.prototype=new PO;cS.prototype.constructor=cS;cS.prototype.p=function(){return wx(Y(),this.XC,zw(X(),this.aJ))}; +cS.prototype.j=function(){this.n()||Lv().da.j();this.XC=this.gz.hb(this.fn);this.aJ=this.gz.ii(this.fn);this.fn=this.fn-1|0;return this};z(cS,"scala.collection.immutable.MapKeyValueTupleHashIterator",{h7:1,x6:1,ra:1,la:1,w:1,y:1});function dS(a){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null;JO(this,a)}dS.prototype=new LO;dS.prototype.constructor=dS;dS.prototype.Zv=function(){this.n()||Lv().da.j();var a=this.Df.MB(this.Dc);this.Dc=1+this.Dc|0;return a};dS.prototype.j=function(){return this.Zv()}; +z(dS,"scala.collection.immutable.MapKeyValueTupleIterator",{i7:1,fz:1,ra:1,la:1,w:1,y:1}); +function eS(a){a.pe<=a.qd&&Lv().da.j();a.un=1+a.un|0;for(var b=a.cJ.Ji(a.un);0===b.a.length;)a.un=1+a.un|0,b=a.cJ.Ji(a.un);a.jz=a.yl;var c=a.gP;c=(c+(c>>>31|0)|0)>>1;var d=a.un-c|0,f=d>>31;a.tn=(1+c|0)-((d^f)-f|0)|0;c=a.tn;switch(c){case 1:a.wi=b;break;case 2:a.qn=b;break;case 3:a.rn=b;break;case 4:a.sn=b;break;case 5:a.wp=b;break;case 6:a.YC=b;break;default:throw new D(c);}a.yl=a.jz+Math.imul(b.a.length,1<a.Ah&&(a.yl=a.Ah);1c?a.wi=a.qn.a[31&(b>>>5|0)]:(32768>c?a.qn=a.rn.a[31&(b>>>10|0)]:(1048576>c?a.rn=a.sn.a[31&(b>>>15|0)]:(33554432>c?a.sn=a.wp.a[31&(b>>>20|0)]:(a.wp=a.YC.a[b>>>25|0],a.sn=a.wp.a[0]),a.rn=a.sn.a[0]),a.qn=a.rn.a[0]),a.wi=a.qn.a[0]);a.xw=b}a.pe=a.pe-a.qd|0;b=a.wi.a.length;c=a.pe;a.vj=bthis.qd};e.j=function(){this.qd===this.vj&&fS(this);var a=this.wi.a[this.qd];this.qd=1+this.qd|0;return a}; +e.df=function(a){if(0=this.yl;)eS(this);b=a-this.jz|0;if(1c||(32768>c||(1048576>c||(33554432>c||(this.wp=this.YC.a[b>>>25|0]),this.sn=this.wp.a[31&(b>>>20|0)]),this.rn=this.sn.a[31&(b>>>15|0)]),this.qn=this.rn.a[31&(b>>>10|0)]);this.wi=this.qn.a[31&(b>>>5|0)];this.xw=b}this.vj=this.wi.a.length;this.qd=31&b;this.pe=this.qd+(this.Ah-a|0)|0;this.vj>this.pe&& +(this.vj=this.pe)}}return this};e.Mw=function(a){a<(this.pe-this.qd|0)&&(a=(this.pe-this.qd|0)-(0>a?0:a)|0,this.Ah=this.Ah-a|0,this.pe=this.pe-a|0,this.peb?d:d-b|0;d=dd?0:d;c=0;for(f=a instanceof x;c>31,d=Math.imul(this.zw,a);a=b+d|0;b=(c+(d>>31)|0)+((b&d|(b|d)&~a)>>>31|0)|0;0>31,this.yp=(d===b?c>>>0>>0:d>31,this.xk=b===d?a>>>0<=c>>>0:bthis.zw&&(c=this.zp,d=c>>31,this.yp=(d===b?c>>>0>a>>>0:d>b)?c:a,c=this.zp,d=c>>31,this.xk=b===d?a>>>0>=c>>>0:b>d)}return this};uq.prototype.j=function(){return vq(this)}; +z(uq,"scala.collection.immutable.RangeIterator",{s7:1,ra:1,la:1,w:1,y:1,b:1});function SF(a,b,c){this.Bh=this.wn=this.Ap=null;this.pb=0;this.Ha=null;pP(this,a,b,c)}SF.prototype=new sP;SF.prototype.constructor=SF;SF.prototype.eC=function(a){return new C(a.ga,a.Aa)};z(SF,"scala.collection.immutable.RedBlackTree$EntriesIterator",{u7:1,eJ:1,ra:1,la:1,w:1,y:1});function hS(a,b){this.Bh=this.wn=this.Ap=null;this.pb=0;this.Ha=null;pP(this,a,F(),b)}hS.prototype=new sP;hS.prototype.constructor=hS; +hS.prototype.eC=function(){throw new Uq;};z(hS,"scala.collection.immutable.RedBlackTree$EqualsIterator",{v7:1,eJ:1,ra:1,la:1,w:1,y:1});function Tg(a,b,c){this.Bh=this.wn=this.Ap=null;this.pb=0;this.Ha=null;pP(this,a,b,c)}Tg.prototype=new sP;Tg.prototype.constructor=Tg;Tg.prototype.eC=function(a){return a.ga};z(Tg,"scala.collection.immutable.RedBlackTree$KeysIterator",{w7:1,eJ:1,ra:1,la:1,w:1,y:1});function iS(){this.yk=this.Cl=0}iS.prototype=new TL;iS.prototype.constructor=iS;function jS(){} +jS.prototype=iS.prototype;iS.prototype.E=function(){return this.yk};iS.prototype.n=function(){return 0a?0:a);return this};function kS(a){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null;this.$C=0;JO(this,a);this.$C=0}kS.prototype=new LO;kS.prototype.constructor=kS; +kS.prototype.p=function(){return this.$C};kS.prototype.j=function(){this.n()||Lv().da.j();this.$C=this.Df.hb(this.Dc);this.Dc=1+this.Dc|0;return this};z(kS,"scala.collection.immutable.SetHashIterator",{L7:1,fz:1,ra:1,la:1,w:1,y:1});function lS(a){this.qk=this.Dc=0;this.Df=null;this.Qe=0;this.wl=this.sh=null;JO(this,a)}lS.prototype=new LO;lS.prototype.constructor=lS;lS.prototype.j=function(){this.n()||Lv().da.j();var a=this.Df.Pa(this.Dc);this.Dc=1+this.Dc|0;return a}; +z(lS,"scala.collection.immutable.SetIterator",{M7:1,fz:1,ra:1,la:1,w:1,y:1});function mS(){this.hw=null;this.hw=HM()}mS.prototype=new EO;mS.prototype.constructor=mS;z(mS,"scala.collection.immutable.SortedSet$",{R7:1,O5:1,n5:1,zC:1,b:1,WO:1});var nS;function Ng(){nS||(nS=new mS);return nS}function oS(){}oS.prototype=new t;oS.prototype.constructor=oS;function pS(){}pS.prototype=oS.prototype;oS.prototype.Fc=function(){}; +function qS(){this.fD=this.gD=null;rS=this;this.gD=new bO(this);this.fD=new FC(new x(0))}qS.prototype=new t;qS.prototype.constructor=qS;e=qS.prototype;e.gi=function(a,b){return Bh(0,AC($r(),a,b))};e.ap=function(a){return new GH(new fH(a.od()),new Zo(b=>Bh(Ch(),b)))}; +function Bh(a,b){if(null===b)return null;if(b instanceof x)return new FC(b);if(b instanceof y)return new BG(b);if(b instanceof jb)return new sS(b);if(b instanceof hb)return new tS(b);if(b instanceof ib)return new uS(b);if(b instanceof db)return new vS(b);if(b instanceof eb)return new wS(b);if(b instanceof gb)return new xS(b);if(b instanceof cb)return new yS(b);if(Li(b))return new zS(b);throw new D(b);}e.Xv=function(a){return this.ap(a)};e.JB=function(a,b){return Bh(0,AC($r(),a,b))};e.uH=function(){return this.fD}; +z(qS,"scala.collection.mutable.ArraySeq$",{C8:1,YO:1,DO:1,CO:1,zC:1,b:1});var rS;function Ch(){rS||(rS=new qS);return rS}function AS(a){this.Gl=0;this.Bk=null;this.Fw=0;this.Ew=null;ZP(this,a)}AS.prototype=new aQ;AS.prototype.constructor=AS;AS.prototype.DB=function(a){return new C(a.Hn,a.Di)};z(AS,"scala.collection.mutable.HashMap$$anon$1",{W8:1,rJ:1,ra:1,la:1,w:1,y:1});function BS(a){this.Gl=0;this.Bk=null;this.Fw=0;this.Ew=null;ZP(this,a)}BS.prototype=new aQ;BS.prototype.constructor=BS; +BS.prototype.DB=function(a){return a};z(BS,"scala.collection.mutable.HashMap$$anon$4",{X8:1,rJ:1,ra:1,la:1,w:1,y:1});function CS(a){this.Gl=0;this.Bk=null;this.Fw=0;this.Ew=null;this.hD=0;ZP(this,a);this.hD=0}CS.prototype=new aQ;CS.prototype.constructor=CS;CS.prototype.p=function(){return this.hD};CS.prototype.DB=function(a){var b=Y(),c=a.Ck;this.hD=iE(b,c^(c>>>16|0),zw(X(),a.Di));return this};z(CS,"scala.collection.mutable.HashMap$$anon$5",{Y8:1,rJ:1,ra:1,la:1,w:1,y:1}); +function DS(a){this.Hl=0;this.Dk=null;this.Iw=0;this.Hw=null;bQ(this,a)}DS.prototype=new dQ;DS.prototype.constructor=DS;DS.prototype.EB=function(a){return a.Bj};z(DS,"scala.collection.mutable.HashSet$$anon$1",{c9:1,sJ:1,ra:1,la:1,w:1,y:1});function ES(a){this.Hl=0;this.Dk=null;this.Iw=0;this.Hw=null;bQ(this,a)}ES.prototype=new dQ;ES.prototype.constructor=ES;ES.prototype.EB=function(a){return a};z(ES,"scala.collection.mutable.HashSet$$anon$2",{d9:1,sJ:1,ra:1,la:1,w:1,y:1}); +function FS(a){this.Hl=0;this.Dk=null;this.Iw=0;this.Hw=null;this.iD=0;bQ(this,a);this.iD=0}FS.prototype=new dQ;FS.prototype.constructor=FS;FS.prototype.p=function(){return this.iD};FS.prototype.EB=function(a){this.iD=GS(a.Gh);return this};z(FS,"scala.collection.mutable.HashSet$$anon$3",{e9:1,sJ:1,ra:1,la:1,w:1,y:1});function HL(a,b){this.rO=a;this.sI=b}HL.prototype=new t;HL.prototype.constructor=HL;e=HL.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return this.rO.I(this.sI.g(a),this.sI.g(b))};z(HL,"scala.math.Ordering$$anon$1",{d4:1,zf:1,Fd:1,Af:1,yf:1,b:1});function tQ(a){this.gk=a}tQ.prototype=new t;tQ.prototype.constructor=tQ;e=tQ.prototype;e.Me=function(a){var b=this.gk;return null===a?null===b:a.d(b)};e.I=function(a,b){return this.gk.I(b,a)};e.Ne=function(a,b){return this.gk.Ne(b,a)};e.ye=function(a,b){return this.gk.ye(b,a)}; +e.Td=function(a,b){return this.gk.Td(b,a)};e.d=function(a){if(null!==a&&this===a)return!0;if(a instanceof tQ){var b=this.gk;a=a.gk;return null===b?null===a:b.d(a)}return!1};e.p=function(){return Math.imul(41,this.gk.p())};z(tQ,"scala.math.Ordering$Reverse",{n4:1,zf:1,Fd:1,Af:1,yf:1,b:1});function IL(a,b){this.My=a;this.Ny=b}IL.prototype=new t;IL.prototype.constructor=IL;e=IL.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.d=function(a){if(null!==a&&this===a)return!0;if(a instanceof IL){var b=this.My,c=a.My;if(null===b?null===c:b.d(c))return b=this.Ny,a=a.Ny,null===b?null===a:b.d(a)}return!1};e.p=function(){for(var a=this.My,b=this.Ny,c=Y(),d=-116390334,f=0;2>f;){X();var g=f;switch(g){case 0:g=a;break;case 1:g=b;break;default:throw O(new P,g+" is out of bounds (min 0, max 1)");}d=c.m(d,zw(0,g));f=1+f|0}return c.T(d,2)}; +e.I=function(a,b){var c=this.My.I(a.Y,b.Y);return 0!==c?c:this.Ny.I(a.V,b.V)};z(IL,"scala.math.Ordering$Tuple2Ordering",{p4:1,zf:1,Fd:1,Af:1,yf:1,b:1});function ro(a){this.Oy=a}ro.prototype=new t;ro.prototype.constructor=ro;e=ro.prototype;e.d=function(a){if(a&&a.$classData&&a.$classData.wb.dg){var b=this.od();a=a.od();b=null===b?null===a:b===a}else b=!1;return b};e.p=function(){return zw(X(),this.Oy)};e.f=function(){return vQ(this.Oy)};e.od=function(){return this.Oy}; +e.le=function(a){return tb(this.Oy.ba,a)};z(ro,"scala.reflect.ClassTag$GenericClassTag",{t4:1,dg:1,Ig:1,Jg:1,b:1,k:1});function HS(a){this.Jn=a}HS.prototype=new t;HS.prototype.constructor=HS;e=HS.prototype;e.zg=function(a){var b=this.Jn;return b===!!a?0:b?1:-1};e.f=function(){return""+this.Kw()};e.$f=function(a){var b=this.Jn;return b===!!a?0:b?1:-1};e.p=function(){return this.Jn?1231:1237};e.d=function(a){bw||(bw=new aw);return a instanceof HS&&this.Jn===a.Jn};e.Kw=function(){return this.Jn}; +z(HS,"scala.runtime.RichBoolean",{V9:1,wJ:1,nh:1,Lb:1,pI:1,oI:1});function IS(a,b,c){Ah();var d=0+b|0;GG(0,cN(a,d,(b>>31|0)+((0&b|(0|b)&~d)>>>31|0)|0)&&0<=b&&32>=b);d=b>>2>>>29|0;var f=(7&(b+d|0))-d|0;d=0;var g=eN(rp(),b,b>>31),h=g.h;g=g.l;for(var k=0;;){var m=k,n=m>>31;if(n===g?m>>>0>>0:n>31|0)+((0&m|(0|m)&~n)>>>31|0)|0),k=1+k|0;else break}0!==f&&(d=d>>>(8-f|0)|0);c&&32!==b&&0!==(1<<(b-1|0)&d)&&(a=32-b|0,d=d<>a);return d} +function JS(a,b,c){Ah();var d=0+b|0;GG(0,cN(a,d,(b>>31|0)+((0&b|(0|b)&~d)>>>31|0)|0)&&0<=b&&64>=b);d=b>>2>>>29|0;d=(7&(b+d|0))-d|0;var f;var g=f=0;var h=eN(rp(),b,b>>31),k=h.h;h=h.l;for(var m=0;;){var n=m,q=n>>31;if(q===h?n>>>0>>0:q>>24|0|g<<8,n=m,q=0+n|0,n=255&a.fh(q,(n>>31|0)+((0&n|(0|n)&~q)>>>31|0)|0),f|=n,m=1+m|0;else break}0!==d&&(a=g,d=8-d|0,f=0===(32&d)?f>>>d|0|a<<1<<(31-d|0):a>>>d|0,g=0===(32&d)?a>>>d|0:0);c&&64!==b?(c=b-1|0,c=0!==((0===(32&c)?1<>>1|0)>>>(31-b|0)|0|g<>>b|0|a<<1<<(31-b|0):a>>b,g=0===(32&b)?a>>b:a>>31);return r(f,g)}function KS(){this.wo=0;this.xo=!1}KS.prototype=new t;KS.prototype.constructor=KS;function LS(){}e=LS.prototype=KS.prototype;e.$f=function(a){return MS(this,a)};function cN(a,b,c){if(0>c)return!0;var d=b-1|0;return!oN(a,d,(c-1|0)+((b|~d)>>>31|0)|0)} +function oN(a,b,c){if(0===(~b|2147483647^c))return!0;var d=1+b|0;return a.qe(d,c+((b&~d)>>>31|0)|0)}e.sD=function(){return this};e.Dj=function(a){return this.qe(1,0)?a:new NS(new OS(this,a))};function hN(a,b,c){if(cN(a,b,c))return new Eg(a.jd(b,c));a=a.z();return new Dg("cannot acquire "+da(b,c)+" bits from a vector that contains "+da(a.h,a.l)+" bits")} +function dN(a){var b=a.z(),c=b.l>>2>>>29|0;b=7&(b.h+c|0);var d=b-c|0;if(0===(d|(~b&c|~(b^c)&d)>>31)){b=a.z();c=b.l>>2>>>29|0;b=7&(b.h+c|0);d=b-c|0;if(0===(d|(~b&c|~(b^c)&d)>>31))rp(),c=PS(QS(a).$c),a=a.z(),a=fN(c,a.h,a.l);else{rp();c=a.z();c=GQ(0,c.h,c.l);b=c.h;d=c.l;c=QS(a.jd(b,d));b=PS(zy(a.Sb(b,d)));rp();a=a.z();d=a.h;var f=c.ge,g=d-f|0;a=fN(b,g,(a.l-c.fe|0)+((~d&f|~(d^f)&g)>>31)|0).Dj(c)}return a}rp();c=a.z();b=GQ(0,c.h,c.l);c=b.h;d=b.l;f=a.z();g=f.h;b=g-c|0;c=(f.l-d|0)+((~g&c|~(g^c)&b)>>31)| +0;c=new C(a.jd(b,c),a.Sb(b,c));if(null!==c)a=c.Y,c=c.V;else throw new D(c);a=PS(zy(a));return c.Dj(qp(rp(),a))} +function QS(a){rp();var b=a.z(),c=eN(0,b.h,b.l);b=c.h;c=c.l;if(0===c?2147483647>>0:0>>0))/8/1E9+" GB");if(a instanceof FQ){b=a.$c;c=a.ge;var d=a.fe,f=RS(b);return f===b?a:new FQ(f,c,d)}if(a instanceof SS)return a=TS(a),b=RS(a.$c),b===a.$c?a:new FQ(b,a.ge,a.fe);rp();a:for(b=new J(a,N()),a=tz(Nv(),ow(Qg(),new (B(qs).P)([])));;){if(b instanceof J){c=b.Fa;b=b.Z;if(c instanceof FQ){a=a.pa(c);continue}if(c instanceof OS){b= +new J(c.yc,new J(c.Qd,b));continue}if(c instanceof SS){a=a.pa(TS(c));continue}if(c instanceof NS){b=new J(c.pf.yc,new J(c.pf.Qd,b));continue}}break a}c=N();d=0;b=a.q();for(f=c;;)if(d!==b){c=1+d|0;var g=d=a.J(d);for(d=new J(new C(d,r(g.ge,g.fe)),f);;){g=d;if(g instanceof J){f=g.Fa;var h=g.Z;if(null!==f){g=f.Y;var k=ab(f.V);f=k.h;k=k.l;if(h instanceof J){var m=h.Fa;h=h.Z;if(null!==m){var n=m.Y,q=ab(m.V);m=q.h;q=q.l;var u=q>>>31|0,v=m+u|0;u=q+((m&u|(m|u)&~v)>>>31|0)|0;v=v>>>1|0|u<<31;u>>=1;if(k===u? +f>>>0>v>>>0:k>u){d=US(n,g);g=m+f|0;d=new J(new C(d,r(g,(q+k|0)+((m&f|(m|f)&~g)>>>31|0)|0)),h);continue}}}}}f=d;break}d=c}else break;c=re(f);if(c===N())a=N();else for(a=c.u(),b=a=new J(a.Y,N()),c=c.A();c!==N();)d=c.u(),d=new J(d.Y,N()),b=b.Z=d,c=c.A();a:{if(Tb(a)&&0>2>>>29|0,d=7&(b.h+c|0);b=d-c|0;c=(~d&c|~(d^c)&b)>>31;if(0!==(b|c)&&(0===c?4>=b>>>0:0>c)){Cd();if(""===a)throw Vh("init of empty String");a=ws(Cd(),a,0,a.length-1|0)}return a}; +function iN(a,b){GG(Ah(),oN(a,8,0));if(a.qe(1,0))return 0;var c=a.z().h;Ah();var d=0+c|0;GG(0,cN(a,d,(c>>31|0)+((0&c|(0|c)&~d)>>>31|0)|0)&&0<=c&&8>=c);a=255&a.fh(0,0);0!==c&&(a=a>>>(8-c|0)|0);b&&8!==c&&0!==(1<<(c-1|0)&a)&&(b=32-c|0,a=a<>b);return a<<24>>24} +function mN(a,b,c){for(var d=c;;){GG(Ah(),oN(a,32,0));var f=a;if(f instanceof FQ){c=a.z().h;if(32===c&&b)return aG(VS(f.$c),d.Jl()).KB();if(16===c)return a=aG(VS(f.$c),d.Jl()).NB(),b?a:65535&a;if(8===c)return a=VS(f.$c).Zj(),b?a:255&a;f=Gy();if(null!==d&&d===f){a=dN(a);d=c=Cy();continue}else return IS(a,c,b)}c=d;d=Gy();if(null!==c&&c===d)a=dN(a),d=c=Cy();else return c=a,a=a.z(),IS(c,a.h,b)}} +function nN(a,b,c){for(var d=c;;){GG(Ah(),oN(a,64,0));var f=a;if(f instanceof FQ){c=a.z().h;if(64===c&&b)return aG(VS(f.$c),d.Jl()).zH();switch(c){case 32:return a=aG(VS(f.$c),d.Jl()).KB(),b?r(a,a>>31):r(a,0);case 16:return a=aG(VS(f.$c),d.Jl()).NB(),b?(b=a,r(b,b>>31)):r(65535&a,0);case 8:return a=VS(f.$c).Zj(),b?(b=a,r(b,b>>31)):r(255&a,0);default:if(f=Gy(),null!==d&&d===f){a=dN(a);d=c=Cy();continue}else return JS(a,c,b)}}c=d;d=Gy();if(null!==c&&c===d)a=dN(a),d=c=Cy();else return c=a,a=a.z(),JS(c, +a.h,b)}}e.d=function(a){var b;if(b=a instanceof KS)if(this===a)b=!0;else{var c=this.z();b=c.h;c=c.l;var d=a.z();if(0===(b^d.h|c^d.l))b:for(b=this;;){if(b.qe(1,0)){a=a.qe(1,0);break b}c=b.jd(524288,0);d=a.jd(524288,0);if(uN(zy(c),zy(d)))b=b.Sb(524288,0),a=a.Sb(524288,0);else{a=!1;break b}}else a=!1;b=a}return b}; +e.p=function(){if(!this.xo){var a;a:{var b=1,c=yx("BitVector");for(a=this;;){if(a.qe(1,0)){a=Y().T(c,b);break a}var d=a.Sb(524288,0);c=Y().m(c,Bx(Y(),gN(zy(a.jd(524288,0)))));b=1+b|0;a=d}}this.wo=a;this.xo=!0}return this.wo};e.f=function(){if(this.qe(1,0))return"BitVector(empty)";if(this.qe(513,0)){var a=this.z();return"BitVector("+da(a.h,a.l)+" bits, 0x"+this.Nw(ZM())+")"}a=this.z();return"BitVector("+da(a.h,a.l)+" bits, #"+this.p()+")"}; +function MS(a,b){if(a===b)return 0;var c=a.z(),d=c.h;c=c.l;var f=b.z(),g=f.h;f=f.l;if(c===f?d>>>0>>0:c>31;if(q===k?n>>>0>>0:q>31));q=m;q=b.ji(q,q>>31);n=n.Jn;n=n===q?0:n?1:-1;if(0!==n)return n;m=1+m|0}else break}return(c===f?d>>>0>>0:c>>0>g>>>0:c>f)?1:0}e.zg=function(a){return MS(this,a)}; +function WS(a,b,c){if(0>c)var d=!0;else{var f=a.z();d=f.h;f=f.l;d=c===f?b>>>0>=d>>>0:c>f}if(d)throw a=a.z(),O(new P,"invalid index: "+da(b,c)+" for size "+da(a.h,a.l));}function XS(a,b,c,d){a.a[b.lc]=c.WA.a[15&d>>4];a.a[1+b.lc|0]=c.WA.a[15&d];b.lc=2+b.lc|0}function YS(){this.zo=0;this.Ao=!1}YS.prototype=new t;YS.prototype.constructor=YS;function ZS(){}e=ZS.prototype=YS.prototype;e.$f=function(a){return $S(this,a)};e.e=function(){var a=this.z();return 0===(a.h|a.l)}; +function CE(a,b,c){WS(a,b,c);return a.ml(b,c)}function JQ(a,b,c,d){WS(a,b,c);d=a.Hi(b,c).Qw(d);var f=1+b|0;return d.Of(a.hi(f,c+((b&~f)>>>31|0)|0))}e.Of=function(a){return this.e()?a:a.e()?this:aT(new bT(new cT(this,a)),64)};e.Qw=function(a){var b=this.Of,c=Zy,d=gp();Qg();a=new eb(new Int8Array([a]));return b.call(this,c(d,null!==a?new VR(a):null,YC()))}; +e.hi=function(a,b){var c=this.z(),d=c.h;c=c.l;(b===c?a>>>0>>0:bd)a=c;else{var f=c.vg,g=c.ug;if(d===g?a>>>0>=f>>>0:d>g)a=kp().$A;else{kp();f=c.di;g=f+a|0;var h=c.vg,k=h-a|0;a=new mp(c.bi,g,(c.ci+d|0)+((f&a|(f|a)&~g)>>>31|0)|0,k,(c.ug-d|0)+((~h&a|~(h^a)&k)>>31)|0)}}for(a=new lp(a);!b.e();)d=b.u(), +a=a.Of(d),b=b.A();b=a.De();break a}if(c instanceof cT){f=c.ai;c=c.bj;g=a;h=d;var m=f.z();k=m.h;m=m.l;(h===m?g>>>0>k>>>0:h>m)?(g=d,f=f.z(),h=f.h,d=a-h|0,f=(g-f.l|0)+((~a&h|~(a^h)&d)>>31)|0,a=d,d=f):(b=new J(c,b),c=f)}else if(c instanceof dT)f=a,g=d,k=c.ue.z(),h=k.h,k=k.l,(g===k?f>>>0>h>>>0:g>k)?(f=eT(c),g=d,c=c.ue.z(),h=c.h,d=a-h|0,g=(g-c.l|0)+((~a&h|~(a^h)&d)>>31)|0,c=f,a=d,d=g):(f=c.ue,b=new J(eT(c),b),c=f);else if(c instanceof bT)c=c.il;else throw new D(c);}return b}; +function fT(a){var b=a.z(),c=b.h,d=c-1|0;return a.Hi(d,(b.l-0|0)+((~c&1|~(c^1)&d)>>31)|0)} +e.Hi=function(a,b){var c=this.z(),d=c.h;c=c.l;(b===c?a>>>0>>0:ba)b=kp().$A;else{f=g.vg;var h=g.ug;(a===h?b>>>0>=f>>>0:a>h)?b=g:(kp(),b=new mp(g.bi,g.di,g.ci,b,a))}b=c.call(d,new lp(b));break a}if(f instanceof cT){g=f.ai;d=f.bj;f=b;h=a;var k=g.z(),m=k.h;k=k.l;(h===k?f>>>0>m>>>0:h>k)? +(c=c.Of(g),f=b,g=g.z(),h=g.h,b=f-h|0,a=(a-g.l|0)+((~f&h|~(f^h)&b)>>31)|0):d=g}else if(f instanceof bT)d=f.il;else if(f instanceof dT)d=f.De();else throw new D(f);}return b};function gT(a,b,c,d,f){a=a.hi(b,c);(0===c?0!==b:0>31)|0)}function PS(a){gp();var b=new G(d=>{var f=ab(d);d=f.h;f=f.l;var g=a.z(),h=g.h,k=h-d|0,m=k-1|0;return CE(a,m,(((g.l-f|0)+((~h&d|~(h^d)&k)>>31)|0)-1|0)+((k|~m)>>>31|0)|0)}),c=a.z();return PJ(b,c.h,c.l)} +function RS(a){return a instanceof lp?a:hT(a)}function hT(a){var b=a.z(),c=b.h;b=b.l;return(0===b?2147483647>=c>>>0:0>b)?(a=gN(a),new lp((kp(),new mp(new GE(a),0,0,c,b)))):hT(a.Hi(2147483647,0)).Of(hT(a.hi(2147483647,0)))}function gN(a){var b=Nh();gp();var c=a.z();b=Mh(b,l(Ab),new y(new Int32Array([RJ(0,c.h,c.l)])));a.iy(b,0);return b} +e.iy=function(a,b){for(var c=new J(this,N());;){var d=c;if(d instanceof J){var f=d.Fa;c=d.Z;if(f instanceof lp){d=f.hl;d.iy(a,b);b=b+RJ(gp(),d.vg,d.ug)|0;continue}if(f instanceof cT){c=new J(f.ai,new J(f.bj,c));continue}if(f instanceof bT){var g=f.il;if(null!==g){c=new J(g.ai,new J(g.bj,c));continue}}if(f instanceof dT){c=new J(f.De(),c);continue}}if(N().d(d))break;throw new D(d);}}; +function aT(a,b){if(a instanceof dT){if(!(a.ve.a.length>=b)){GG(Ah(),b>a.ve.a.length);var c=Mh(Nh(),l(Ab),new y(new Int32Array([b])));Dd();b=a.ve;var d=c.a.length,f=Pi(Nh(),b),g=Pi(Nh(),c);d=dg?0:g;0>>31|0)|0;;)if(n=u,m=h,m===k?n>>>0>>0:m>>31|0)|0;else break;break a}if(f instanceof np){f=f.Xx.jy();Zj.prototype.yb.call(f, +k);for(Zj.prototype.Zo.call(f,k+n|0);f.L!==f.aa;)XS(b,c,a,f.Zj());break a}}f=g;g=new FE(b,c,a,this);for(h=k=0;;)if(u=k,n=h,m=f.vg,q=f.ug,n===q?u>>>0>>0:n>>31|0)|0),XS(g.xN,g.wN,g.vN,u),u=k,k=1+u|0,h=h+((u&~k)>>>31|0)|0;else break}continue}if(g instanceof cT){d=new J(g.ai,new J(g.bj,d));continue}if(g instanceof bT&&(h=g.il,null!==h)){d=new J(h.ai,new J(h.bj,d));continue}if(g instanceof dT){d=new J(g.De(),d);continue}}if(N().d(f))break; +throw new D(f);}return PA(zs(),b,0,b.a.length)};function iT(a,b,c){c=new BE(new AE(c),b,a);kp();var d=a.z();a=d.h;d=d.l;var f=b.z();b=f.h;f=f.l;(d===f?a>>>0>>0:d(c.Ca(d|0,f|0)|0)<<24>>24))}e.p=function(){if(!this.Ao){var a;a:{var b=1,c=yx("ByteVector");for(a=this;;){if(a.e()){a=Y().T(c,b);break a}var d=a.hi(65536,0);c=Y().m(c,Bx(Y(),gN(a.Hi(65536,0))));b=1+b|0;a=d}}this.zo=a;this.Ao=!0}return this.zo}; +function uN(a,b){if(a===b)return!0;var c=a.z(),d=c.h;c=c.l;var f=b.z();if(0===(d^f.h|c^f.l))a:{var g=0;for(f=0;;){var h=g,k=f;if(k===c?h>>>0>>0:k>>31|0)|0;continue}a=!1;break a}a=!0;break a}}else a=!1;return a}e.d=function(a){return a instanceof YS&&uN(this,a)}; +e.f=function(){if(this.e())return"ByteVector(empty)";var a=this.z(),b=a.h;a=a.l;if(0===a?512>b>>>0:0>a)return b=this.z(),"ByteVector("+da(b.h,b.l)+" bytes, 0x"+this.Nw(ZM())+")";b=this.z();return"ByteVector("+da(b.h,b.l)+" bytes, #"+this.p()+")"}; +function $S(a,b){if(a===b)return 0;var c=a.z(),d=c.h;c=c.l;var f=b.z(),g=f.h;f=f.l;if(c===f?d>>>0>>0:c>31;if(q===k?n>>>0>>0:q>31));q=m;q=255&CE(b,q,q>>31);Qr();n=n.Ug;n=n===q?0:n>>0>>0:c>>0>g>>>0:c>f)?1:0}e.zg=function(a){return $S(this,a)};function mc(a,b){this.Zw=a;this.$w=b}mc.prototype=new XE;mc.prototype.constructor=mc; +mc.prototype.x=function(){return new Z(this)};mc.prototype.t=function(){return 2};mc.prototype.v=function(){return"Append"};mc.prototype.c=function(a){if(0===a)return this.Zw;if(1===a)return this.$w;throw O(new P,""+a);};z(mc,"cats.data.Chain$Append",{dR:1,LD:1,Kz:1,Mz:1,k:1,s:1,b:1});function $b(a){this.Nl=a}$b.prototype=new XE;$b.prototype.constructor=$b;$b.prototype.x=function(){return new Z(this)};$b.prototype.t=function(){return 1};$b.prototype.v=function(){return"Singleton"}; +$b.prototype.c=function(a){if(0===a)return this.Nl;throw O(new P,""+a);};z($b,"cats.data.Chain$Singleton",{gR:1,LD:1,Kz:1,Mz:1,k:1,s:1,b:1});function Zb(a){this.Lk=a}Zb.prototype=new XE;Zb.prototype.constructor=Zb;Zb.prototype.x=function(){return new Z(this)};Zb.prototype.t=function(){return 1};Zb.prototype.v=function(){return"Wrap"};Zb.prototype.c=function(a){if(0===a)return this.Lk;throw O(new P,""+a);};z(Zb,"cats.data.Chain$Wrap",{hR:1,LD:1,Kz:1,Mz:1,k:1,s:1,b:1});function lT(){}lT.prototype=new QQ; +lT.prototype.constructor=lT;function mT(){}mT.prototype=lT.prototype;function nT(){this.cE=null;oT=this;this.cE=new pT;new qK}nT.prototype=new t;nT.prototype.constructor=nT;z(nT,"cats.instances.package$option$",{AS:1,tE:1,sE:1,rE:1,qE:1,UD:1,VD:1});var oT;function zq(){oT||(oT=new nT);return oT}function qT(){}qT.prototype=new MN;qT.prototype.constructor=qT;function rT(){}rT.prototype=qT.prototype;qT.prototype.pB=function(a){a=null===a?"null":Ja(a);Xo(this,null===a?"null":a)}; +function lk(a,b,c,d,f,g){this.Pd=this.L=this.aa=this.He=0;this.jA=g;this.Ze=b;this.rg=c;Yj(this,a);Zj.prototype.yb.call(this,d);Zj.prototype.Zo.call(this,f)}lk.prototype=new iR;lk.prototype.constructor=lk;e=lk.prototype;e.ff=function(){return this.jA};e.pD=function(a,b){if(0>a||a>b||b>(this.aa-this.L|0))throw kk();return new lk(this.He,this.Ze,this.rg,this.L+a|0,this.L+b|0,this.jA)}; +e.cp=function(a){if(this.jA)throw new TB;var b=this.L;if(b===this.aa)throw new Ek;this.L=1+b|0;this.Ze.a[this.rg+b|0]=a};e.DH=function(a){if(0>a||a>=this.aa)throw kk();return this.Ze.a[this.rg+a|0]};e.BH=function(a,b,c){if(0>b||0>c||b>(a.a.length-c|0))throw kk();var d=this.L,f=d+c|0;if(f>this.aa)throw new Fk;this.L=f;this.Ze.G(this.rg+d|0,a,b,c)};e.Vv=function(a){return this.Ze.a[this.rg+a|0]};e.zJ=function(a,b){this.Ze.a[this.rg+a|0]=b};e.yJ=function(a,b,c,d){b.G(c,this.Ze,this.rg+a|0,d)}; +e.Cz=function(a,b){return this.pD(a,b)};z(lk,"java.nio.HeapCharBuffer",{XX:1,GM:1,gA:1,Lb:1,qy:1,QB:1,ON:1});function jR(a,b,c,d,f){this.Pd=this.L=this.aa=this.He=0;this.oq=b;this.pq=c;this.Ze=null;this.rg=-1;Yj(this,a);Zj.prototype.yb.call(this,d);Zj.prototype.Zo.call(this,f)}jR.prototype=new iR;jR.prototype.constructor=jR;e=jR.prototype;e.ff=function(){return!0};e.pD=function(a,b){if(0>a||a>b||b>(this.aa-this.L|0))throw kk();return new jR(this.He,this.oq,this.pq,this.L+a|0,this.L+b|0)}; +e.cp=function(){throw new TB;};e.DH=function(a){if(0>a||a>=this.aa)throw kk();return ya(this.oq,this.pq+a|0)};e.BH=function(a,b,c){if(0>b||0>c||b>(a.a.length-c|0))throw kk();var d=this.L,f=d+c|0;if(f>this.aa)throw new Fk;this.L=f;for(c=d+c|0;d!==c;){f=b;var g=ya(this.oq,this.pq+d|0);a.a[f]=g;d=1+d|0;b=1+b|0}};e.f=function(){var a=this.pq;var b=this.oq;var c=this.L+a|0;a=this.aa+a|0;b="string"===typeof b?b.substring(c,a):b.Cz(c,a);return Ja(b)};e.Vv=function(a){return ya(this.oq,this.pq+a|0)}; +e.zJ=function(){throw new TB;};e.yJ=function(){throw new TB;};e.Cz=function(a,b){return this.pD(a,b)};z(jR,"java.nio.StringCharBuffer",{ZX:1,GM:1,gA:1,Lb:1,qy:1,QB:1,ON:1});class WA extends lR{constructor(a){super();this.RN=a;ft(this,null);if(null===a)throw Nr();}xe(){return"Flags \x3d '"+this.RN+"'"}}z(WA,"java.util.DuplicateFormatFlagsException",{i2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1}); +class lL extends lR{constructor(a,b){super();this.TN=a;this.SN=b;ft(this,null);if(null===a)throw Nr();}xe(){return"Conversion \x3d "+Na(this.SN)+", Flags \x3d "+this.TN}}z(lL,"java.util.FormatFlagsConversionMismatchException",{j2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class dB extends lR{constructor(a){super();this.VN=a;ft(this,null)}xe(){return this.VN}}z(dB,"java.util.IllegalFormatArgumentIndexException",{r2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1}); +class hB extends lR{constructor(a){super();this.WN=a;ft(this,null)}xe(){return"Code point \x3d 0x"+(this.WN>>>0).toString(16)}}z(hB,"java.util.IllegalFormatCodePointException",{s2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class mL extends lR{constructor(a,b){super();this.YN=a;this.XN=b;ft(this,null);if(null===b)throw Nr();}xe(){return""+Na(this.YN)+" !\x3d "+this.XN.ba.name}}z(mL,"java.util.IllegalFormatConversionException",{t2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1}); +class kL extends lR{constructor(a){super();this.ZN=a;ft(this,null);if(null===a)throw Nr();}xe(){return"Flags \x3d '"+this.ZN+"'"}}z(kL,"java.util.IllegalFormatFlagsException",{u2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class jL extends lR{constructor(a){super();this.$N=a;ft(this,null)}xe(){return""+this.$N}}z(jL,"java.util.IllegalFormatPrecisionException",{v2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class YA extends lR{constructor(a){super();this.aO=a;ft(this,null)}xe(){return""+this.aO}} +z(YA,"java.util.IllegalFormatWidthException",{w2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class eB extends lR{constructor(a){super();this.bO=a;ft(this,null);if(null===a)throw Nr();}xe(){return"Format specifier '"+this.bO+"'"}}z(eB,"java.util.MissingFormatArgumentException",{x2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});class aB extends lR{constructor(a){super();this.cO=a;ft(this,null);if(null===a)throw Nr();}xe(){return this.cO}}z(aB,"java.util.MissingFormatWidthException",{y2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1}); +class VA extends lR{constructor(a){super();this.dO=a;ft(this,null);if(null===a)throw Nr();}xe(){return"Conversion \x3d '"+this.dO+"'"}}z(VA,"java.util.UnknownFormatConversionException",{B2:1,ij:1,Eg:1,Mb:1,xb:1,Xa:1,b:1});function sT(){}sT.prototype=new t;sT.prototype.constructor=sT;e=sT.prototype;e.x=function(){return new Z(this)};e.p=function(){return 924202651};e.t=function(){return 0};e.v=function(){return"EmptyTuple"};e.c=function(a){throw O(new P,""+a);};e.f=function(){return"()"};e.Rc=function(){return this}; +z(sT,"scala.Tuple$package$EmptyTuple$",{Z0:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var tT;function Dw(){tT||(tT=new sT);return tT}function Jr(a){this.Kg=null;this.eg=this.sa=0;this.xI=a;Cr(this,a)}Jr.prototype=new BR;Jr.prototype.constructor=Jr;Jr.prototype.j=function(){this.sa>=this.xI.a.length&&Lv().da.j();var a=this.xI.a[this.sa];this.sa=1+this.sa|0;return a};z(Jr,"scala.collection.ArrayOps$ArrayIterator$mcB$sp",{c5:1,ik:1,ra:1,la:1,w:1,y:1,b:1}); +function Ir(a){this.Kg=null;this.eg=this.sa=0;this.yI=a;Cr(this,a)}Ir.prototype=new BR;Ir.prototype.constructor=Ir;Ir.prototype.j=function(){this.sa>=this.yI.a.length&&Lv().da.j();var a=this.yI.a[this.sa];this.sa=1+this.sa|0;return p(a)};z(Ir,"scala.collection.ArrayOps$ArrayIterator$mcC$sp",{d5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Fr(a){this.Kg=null;this.eg=this.sa=0;this.zI=a;Cr(this,a)}Fr.prototype=new BR;Fr.prototype.constructor=Fr; +Fr.prototype.j=function(){this.sa>=this.zI.a.length&&Lv().da.j();var a=this.zI.a[this.sa];this.sa=1+this.sa|0;return a};z(Fr,"scala.collection.ArrayOps$ArrayIterator$mcD$sp",{e5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Hr(a){this.Kg=null;this.eg=this.sa=0;this.AI=a;Cr(this,a)}Hr.prototype=new BR;Hr.prototype.constructor=Hr;Hr.prototype.j=function(){this.sa>=this.AI.a.length&&Lv().da.j();var a=this.AI.a[this.sa];this.sa=1+this.sa|0;return a}; +z(Hr,"scala.collection.ArrayOps$ArrayIterator$mcF$sp",{f5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Er(a){this.Kg=null;this.eg=this.sa=0;this.BI=a;Cr(this,a)}Er.prototype=new BR;Er.prototype.constructor=Er;Er.prototype.j=function(){this.sa>=this.BI.a.length&&Lv().da.j();var a=this.BI.a[this.sa];this.sa=1+this.sa|0;return a};z(Er,"scala.collection.ArrayOps$ArrayIterator$mcI$sp",{g5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Gr(a){this.Kg=null;this.eg=this.sa=0;this.CI=a;Cr(this,a)}Gr.prototype=new BR; +Gr.prototype.constructor=Gr;Gr.prototype.j=function(){this.sa>=(this.CI.a.length>>>1|0)&&Lv().da.j();var a=this.CI.a,b=this.sa<<1,c=a[b];a=a[b+1|0];this.sa=1+this.sa|0;return r(c,a)};z(Gr,"scala.collection.ArrayOps$ArrayIterator$mcJ$sp",{h5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Kr(a){this.Kg=null;this.eg=this.sa=0;this.DI=a;Cr(this,a)}Kr.prototype=new BR;Kr.prototype.constructor=Kr;Kr.prototype.j=function(){this.sa>=this.DI.a.length&&Lv().da.j();var a=this.DI.a[this.sa];this.sa=1+this.sa|0;return a}; +z(Kr,"scala.collection.ArrayOps$ArrayIterator$mcS$sp",{i5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Mr(a){this.Kg=null;this.eg=this.sa=0;this.BO=a;Cr(this,a)}Mr.prototype=new BR;Mr.prototype.constructor=Mr;Mr.prototype.j=function(){this.sa>=this.BO.a.length&&Lv().da.j();this.sa=1+this.sa|0};z(Mr,"scala.collection.ArrayOps$ArrayIterator$mcV$sp",{j5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function Lr(a){this.Kg=null;this.eg=this.sa=0;this.EI=a;Cr(this,a)}Lr.prototype=new BR;Lr.prototype.constructor=Lr; +Lr.prototype.j=function(){this.sa>=this.EI.a.length&&Lv().da.j();var a=this.EI.a[this.sa];this.sa=1+this.sa|0;return a};z(Lr,"scala.collection.ArrayOps$ArrayIterator$mcZ$sp",{k5:1,ik:1,ra:1,la:1,w:1,y:1,b:1});function uT(a){return a.xd()+"(\x3cnot computed\x3e)"}function vT(a){this.lP=a;this.Cl=0;this.yk=2}vT.prototype=new jS;vT.prototype.constructor=vT;vT.prototype.J=function(a){a:{var b=this.lP;switch(a){case 0:a=b.xn;break a;case 1:a=b.yn;break a;default:throw new D(a);}}return a}; +z(vT,"scala.collection.immutable.Set$Set2$$anon$1",{F7:1,fJ:1,ra:1,la:1,w:1,y:1,b:1});function wT(a){this.mP=a;this.Cl=0;this.yk=3}wT.prototype=new jS;wT.prototype.constructor=wT;wT.prototype.J=function(a){a:{var b=this.mP;switch(a){case 0:a=b.zl;break a;case 1:a=b.Al;break a;case 2:a=b.Bl;break a;default:throw new D(a);}}return a};z(wT,"scala.collection.immutable.Set$Set3$$anon$2",{H7:1,fJ:1,ra:1,la:1,w:1,y:1,b:1});function xT(a){this.nP=a;this.Cl=0;this.yk=4}xT.prototype=new jS; +xT.prototype.constructor=xT;xT.prototype.J=function(a){return yT(this.nP,a)};z(xT,"scala.collection.immutable.Set$Set4$$anon$3",{J7:1,fJ:1,ra:1,la:1,w:1,y:1,b:1});function RF(a){this.qz=null;this.ig=a;this.Aj=null}RF.prototype=new gD;RF.prototype.constructor=RF;RF.prototype.Fc=function(){};function TF(a){return TI(new SI,Eu(a.Aj),a.ig)}RF.prototype.Sa=function(){return TF(this)}; +RF.prototype.gc=function(a){a:{if(a instanceof SI){var b=a.Ob,c=this.ig;if(null===b?null===c:b.d(c)){this.Aj=null===this.Aj?a.zb:zu(Bu(),Eu(this.Aj),a.zb,this.ig);break a}}a&&a.$classData&&a.$classData.wb.lk?(null===this.qz&&null===this.qz&&(this.qz=new WI(this)),b=this.qz,b.Bw=b.bD.Aj,a.Cg(b),b.bD.Aj=b.Bw,b.Bw=null):nD(this,a)}return this};RF.prototype.Ia=function(a){this.Aj=hD(this,this.Aj,a.Y,a.V);return this}; +z(RF,"scala.collection.immutable.TreeMap$TreeMapBuilder",{$7:1,x7:1,kP:1,Ei:1,ae:1,ud:1,td:1});function Sg(a){this.ig=a;this.zk=null}Sg.prototype=new jD;Sg.prototype.constructor=Sg;Sg.prototype.Fc=function(){};function Ug(a,b){a.zk=kD(a,a.zk,b);return a}function Vg(a){return FM(new EM,Eu(a.zk),a.ig)}Sg.prototype.Sa=function(){return Vg(this)}; +Sg.prototype.gc=function(a){a:{if(a instanceof EM){var b=a.Ba,c=this.ig;if(null===b?null===c:b.d(c)){this.zk=null===this.zk?a.Ta:zu(Bu(),Eu(this.zk),a.Ta,this.ig);break a}}if(a instanceof SI&&(b=a.Ob,c=this.ig,null===b?null===c:b.d(c))){this.zk=null===this.zk?a.zb:zu(Bu(),Eu(this.zk),a.zb,this.ig);break a}nD(this,a)}return this};Sg.prototype.Ia=function(a){return Ug(this,a)};z(Sg,"scala.collection.immutable.TreeSet$TreeSetBuilder",{d8:1,y7:1,kP:1,Ei:1,ae:1,ud:1,td:1}); +function fH(a){this.nJ=!1;this.eD=null;this.Dw=a;this.nJ=a===l(zb);this.eD=[]}fH.prototype=new pS;fH.prototype.constructor=fH;function zT(a,b){a.eD.push(a.nJ?$a(b):null===b?a.Dw.ba.Pw:b);return a}fH.prototype.Sa=function(){return B((this.Dw===l(ub)?l(ua):this.Dw===l(ps)||this.Dw===l(qs)?l(ob):this.Dw).ba).Np(this.eD)};fH.prototype.f=function(){return"ArrayBuilder.generic"};fH.prototype.gc=function(a){for(a=a.i();a.n();)zT(this,a.j());return this};fH.prototype.Ia=function(a){return zT(this,a)}; +z(fH,"scala.collection.mutable.ArrayBuilder$generic",{z8:1,y8:1,Ei:1,ae:1,ud:1,td:1,b:1});function AT(a,b,c,d){var f=1+Pi(Nh(),b)|0;if(0>c||c>=f)throw Fs(Hs(),c,f-1|0);f=(a.Mc-a.Nc|0)&(a.Va.a.length-1|0)|0;var g=Pi(Nh(),b)-c|0;f=f=f)throw Fs(Hs(),0,f-1|0);f=(a.Nc+0|0)&(a.Va.a.length-1|0);g=a.Va.a.length-f|0;g=d=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){a=!!a;return a===!!b?0:a?1:-1}; +z(DT,"scala.math.Ordering$Boolean$",{e4:1,f4:1,zf:1,Fd:1,Af:1,yf:1,b:1});var ET;function Vr(){ET||(ET=new DT);return ET}function FT(){}FT.prototype=new t;FT.prototype.constructor=FT;e=FT.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return(a|0)-(b|0)|0};z(FT,"scala.math.Ordering$Byte$",{g4:1,sO:1,zf:1,Fd:1,Af:1,yf:1,b:1});var GT; +function Tr(){GT||(GT=new FT);return GT}function HT(){}HT.prototype=new t;HT.prototype.constructor=HT;e=HT.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return $a(a)-$a(b)|0};z(HT,"scala.math.Ordering$Char$",{i4:1,tO:1,zf:1,Fd:1,Af:1,yf:1,b:1});var IT;function Sr(){IT||(IT=new HT);return IT}function GL(){}GL.prototype=new t;GL.prototype.constructor=GL;e=GL.prototype; +e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return Ba(Ca(),Math.fround(a),Math.fround(b))};z(GL,"scala.math.Ordering$DeprecatedFloatOrdering$",{j4:1,k4:1,zf:1,Fd:1,Af:1,yf:1,b:1});var FL;function JT(){}JT.prototype=new t;JT.prototype.constructor=JT;e=JT.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){var c=ab(a);a=c.h;c=c.l;var d=ab(b);b=d.h;d=d.l;return c===d?a===b?0:a>>>0>>0?-1:1:c=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.I=function(a,b){return(a|0)-(b|0)|0};z(LT,"scala.math.Ordering$Short$",{o4:1,wO:1,zf:1,Fd:1,Af:1,yf:1,b:1});var MT;function Ur(){MT||(MT=new LT);return MT}function NT(){this.oe=null}NT.prototype=new t;NT.prototype.constructor=NT;function OT(){}OT.prototype=NT.prototype;NT.prototype.f=function(){return this.oe};NT.prototype.d=function(a){return this===a};NT.prototype.p=function(){return Va(this)};function PT(){} +PT.prototype=new t;PT.prototype.constructor=PT;function QT(){}QT.prototype=PT.prototype;class tB extends bg{constructor(a){super();this.Lp=a;ft(this,null)}xe(){return Ja(this.Lp)}v(){return"JavaScriptException"}t(){return 1}c(a){return 0===a?this.Lp:Aw(X(),a)}x(){return new aO(this)}p(){return xx(this,1744042595)}d(a){return this===a||a instanceof tB&&V(W(),this.Lp,a.Lp)}}z(tB,"scala.scalajs.js.JavaScriptException",{w9:1,Mb:1,xb:1,Xa:1,b:1,s:1,k:1}); +function dT(a,b,c,d,f,g){this.zo=0;this.Ao=!1;this.yo=a;this.gl=b;this.fl=c;this.ue=d;this.ve=f;this.tg=g}dT.prototype=new ZS;dT.prototype.constructor=dT;e=dT.prototype;e.z=function(){var a=this.ue.z(),b=a.h,c=this.tg,d=b+c|0;return r(d,(a.l+(c>>31)|0)+((b&c|(b|c)&~d)>>>31|0)|0)};e.Hi=function(a,b){var c=this.ue.z(),d=c.h;c=c.l;if(b===c?a>>>0<=d>>>0:b>31)|0))}; +e.hi=function(a,b){var c=this.ue.z(),d=c.h;c=c.l;if(b===c?a>>>0<=d>>>0:b>>0>>0:b>>31|0)|0)&&this.tg>>31|0)|0,this.ue.De(),c,d)}b=new iG(0,0);c=this.De();d=Mh(Nh(),l(Ab),new y(new Int32Array([this.ve.a.length])));return(new dT(b,0,0,c.De(),d,0)).Qw(a)}; +e.Of=function(a){if(a.e())return this;if(this.e())return a;var b=this.gl,c=1+b|0;if(jG(this.yo,this.gl,this.fl,c,this.fl+((b&~c)>>>31|0)|0)){b=this.ve.a.length-this.tg|0;c=b>>31;var d=a.z(),f=d.h;d=d.l;b=c===d?b>>>0>f>>>0:c>d}else b=!1;if(b){a.iy(this.ve,this.tg);b=this.yo;f=this.gl;c=1+f|0;f=this.fl+((f&~c)>>>31|0)|0;d=this.ue;var g=this.ve,h=this.tg;a=a.z();a=h+a.h|0;return new dT(b,c,f,d.De(),g,a)}if(0===this.tg)return b=this.yo,c=this.gl,f=this.fl,a=this.ue.Of(a).De(),d=this.ve,g=this.tg,new dT(b, +c,f,a.De(),d,g);b=new iG(0,0);c=this.De();f=Mh(Nh(),l(Ab),new y(new Int32Array([this.ve.a.length])));return(new dT(b,0,0,c.De(),f,0)).Of(a)};function eT(a){var b=OJ(gp(),a.ve);a=a.tg;return b.Hi(a,a>>31)}e.De=function(){return this.ue.Of(this.tg<<1c.Vb(d.g(f.J(h-1|0)),new G(w=>new J(w,N()))))),n=h-2|0;g<=n;){var q=f.J(n);Ke();m=new $J(new pg(((w,A,K,M)=>()=>w.Oe(A.g(K),M,new Qb((S,L)=>new J(S,L))))(c,d,q,m)));n=n-1|0}return lg(m,new G(w=>c.Vb(w,new G(A=>Yb(Ob(),A)))))}var u=(h-g|0)/Ka(b)|0;Ke();n=new $J(new pg(()=>RT(a,b,c,d,f,g,g+u|0)));q=g+u|0;for(m=q+u|0;qK=>w.Oe(K,A,new Qb((M,S)=>jc(Ob(),M,S))))(c, +v)));q=q+u|0;m=m+u|0}return n};function TT(){this.Qf=this.Lz=this.ax=null;this.ax=new UT;VT=this;this.Lz=new WE;WT||(WT=new XT);this.Qf=WT}TT.prototype=new hK;TT.prototype.constructor=TT;function jc(a,b,c){return b instanceof lc?c instanceof lc?new mc(b,c):b:c}function YT(a,b,c,d){return b.e()?d.ne(Ob().Qf):ST(a,128,d,c,b,0,b.q()).Kh()}z(TT,"cats.data.Chain$",{aR:1,jR:1,lR:1,mR:1,nR:1,iR:1,Wb:1,ep:1});var VT;function Ob(){VT||(VT=new TT);return VT}function ZT(){}ZT.prototype=new mT; +ZT.prototype.constructor=ZT;function $T(){}$T.prototype=ZT.prototype;function aU(){var a=Uv();0===(1&a.tl)<<24>>24&&0===(1&a.tl)<<24>>24&&(a.uI=GD(),a.tl=(1|a.tl)<<24>>24);var b=a.uI;var c=a=b.fp;if((null===a?null===c:a.d(c))&&0>=b.Iy&&0<=b.oC){c=0-b.Iy|0;var d=(b.Gy?b.Hy:xD(b)).a[c];null===d&&(d=new RC(DB(zB(),0,0),a),(b.Gy?b.Hy:xD(b)).a[c]=d)}else b=new qB,EB(b,0,0,0),FD(b,a),new RC(b,a)}aU.prototype=new t;aU.prototype.constructor=aU; +z(aU,"cats.kernel.instances.BigDecimalGroup",{gT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function bU(){ND(ae(),0)}bU.prototype=new t;bU.prototype.constructor=bU;z(bU,"cats.kernel.instances.BigIntGroup",{iT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function cU(){}cU.prototype=new t;cU.prototype.constructor=cU;z(cU,"cats.kernel.instances.ByteGroup",{pT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function dU(){}dU.prototype=new t;dU.prototype.constructor=dU; +z(dU,"cats.kernel.instances.DoubleGroup",{vT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function eU(){}eU.prototype=new t;eU.prototype.constructor=eU;z(eU,"cats.kernel.instances.DurationGroup",{yT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function fU(){}fU.prototype=new t;fU.prototype.constructor=fU;z(fU,"cats.kernel.instances.FiniteDurationGroup",{BT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function gU(){}gU.prototype=new t;gU.prototype.constructor=gU; +z(gU,"cats.kernel.instances.FloatGroup",{DT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function hU(){}hU.prototype=new t;hU.prototype.constructor=hU;z(hU,"cats.kernel.instances.IntGroup",{HT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function iU(){}iU.prototype=new t;iU.prototype.constructor=iU;z(iU,"cats.kernel.instances.LongGroup",{MT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function jU(){}jU.prototype=new t;jU.prototype.constructor=jU; +z(jU,"cats.kernel.instances.ShortGroup",{QT:1,b:1,lf:1,Tf:1,Ui:1,Rf:1,qg:1,Ti:1});function iF(){}iF.prototype=new t;iF.prototype.constructor=iF;e=iF.prototype;e.Pe=function(a,b){return AK(this,a,b)};e.Le=function(a,b){return 0{Ob();return new $b(new Cn(b,a.Yh.length))}))))};e.qa=function(a){this.ek(a)};e.Rc=function(){return this};z(kU,"cats.parse.Parser$Impl$EndParser$",{XU:1,vb:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var lU; +function Xe(){lU||(lU=new kU);return lU}function mU(){this.fb=0;this.gb=!1}mU.prototype=new sh;mU.prototype.constructor=mU;e=mU.prototype;e.x=function(){return new Z(this)};e.f=function(){return"GetCaret"};e.t=function(){return 0};e.v=function(){return"GetCaret"};e.c=function(a){throw O(new P,""+a);};e.qa=function(a){a.XE||(a.WE=new zd(a.Yh),a.XE=!0);var b=a.WE;a=a.Wa;if(0<=a&&a<=b.dx.length)b=yd(b,a);else throw Qi("offset \x3d "+a+" exceeds "+b.dx.length);return b};e.Rc=function(){return this}; +z(mU,"cats.parse.Parser$Impl$GetCaret$",{ZU:1,vb:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var nU;function jf(){nU||(nU=new mU);return nU}function oU(){this.fb=0;this.gb=!1}oU.prototype=new sh;oU.prototype.constructor=oU;e=oU.prototype;e.x=function(){return new Z(this)};e.f=function(){return"Index"};e.t=function(){return 0};e.v=function(){return"Index"};e.c=function(a){throw O(new P,""+a);};e.qa=function(a){return a.Wa};e.Rc=function(){return this}; +z(oU,"cats.parse.Parser$Impl$Index$",{$U:1,vb:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var pU;function ef(){pU||(pU=new oU);return pU}function qU(){this.fb=0;this.gb=!1}qU.prototype=new sh;qU.prototype.constructor=qU;e=qU.prototype;e.x=function(){return new Z(this)};e.f=function(){return"StartParser"};e.t=function(){return 0};e.v=function(){return"StartParser"};e.c=function(a){throw O(new P,""+a);};e.ek=function(a){var b=a.Wa;0!==b&&(a.bb=(Ke(),new og(new pg(()=>{Ob();return new $b(new FK(b))}))))};e.qa=function(a){this.ek(a)}; +e.Rc=function(){return this};z(qU,"cats.parse.Parser$Impl$StartParser$",{rV:1,vb:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var rU;function We(){rU||(rU=new qU);return rU}function Di(a){this.IN=a;this.ty=""}Di.prototype=new rT;Di.prototype.constructor=Di;function Xo(a,b){for(;""!==b;){var c=b.indexOf("\n")|0;if(0>c)a.ty=""+a.ty+b,b="";else{var d=""+a.ty+b.substring(0,c);"undefined"!==typeof console&&(a.IN&&console.error?console.error(d):console.log(d));a.ty="";b=b.substring(1+c|0)}}} +z(Di,"java.lang.JSConsoleBasedPrintStream",{O1:1,AX:1,zX:1,DM:1,jF:1,FH:1,kF:1,QB:1});function sU(){}sU.prototype=new t;sU.prototype.constructor=sU;e=sU.prototype;e.x=function(){return new Z(this)};e.p=function(){return 209005173};e.f=function(){return"ManyChar"};e.t=function(){return 0};e.v=function(){return"ManyChar"};e.c=function(a){throw O(new P,""+a);};e.Rc=function(){return this};z(sU,"pink.cozydev.lucille.Query$WildCardOp$ManyChar$",{PY:1,k:1,s:1,b:1,FF:1,Wb:1,pd:1,mh:1});var tU; +function Zn(){tU||(tU=new sU);return tU}function Yn(){}Yn.prototype=new t;Yn.prototype.constructor=Yn;e=Yn.prototype;e.x=function(){return new Z(this)};e.p=function(){return-1388379170};e.f=function(){return"SingleChar"};e.t=function(){return 0};e.v=function(){return"SingleChar"};e.c=function(a){throw O(new P,""+a);};e.Rc=function(){return this};z(Yn,"pink.cozydev.lucille.Query$WildCardOp$SingleChar$",{QY:1,k:1,s:1,b:1,FF:1,Wb:1,pd:1,mh:1});var Xn;function uU(){}uU.prototype=new t; +uU.prototype.constructor=uU;e=uU.prototype;e.x=function(){return new Z(this)};e.p=function(){return 64951};e.f=function(){return"AND"};e.t=function(){return 0};e.v=function(){return"AND"};e.c=function(a){throw O(new P,""+a);};e.Rc=function(){return this};z(uU,"pink.cozydev.lucille.internal.Op$AND$",{$Y:1,k:1,s:1,b:1,OM:1,Wb:1,pd:1,mh:1});var vU;function go(){vU||(vU=new uU);return vU}function wU(){}wU.prototype=new t;wU.prototype.constructor=wU;e=wU.prototype;e.x=function(){return new Z(this)}; +e.p=function(){return 2531};e.f=function(){return"OR"};e.t=function(){return 0};e.v=function(){return"OR"};e.c=function(a){throw O(new P,""+a);};e.Rc=function(){return this};z(wU,"pink.cozydev.lucille.internal.Op$OR$",{aZ:1,k:1,s:1,b:1,OM:1,Wb:1,pd:1,mh:1});var xU;function fo(){xU||(xU=new wU);return xU}function yU(a,b){if(0>=a.Qa(1))return a;for(var c=a.xf(),d=pO(),f=a.i(),g=!1;f.n();){var h=f.j();qO(d,b.g(h))?c.Ia(h):g=!0}return g?c.Sa():a} +function RC(a,b){this.Jy=0;this.Ya=a;this.pO=b;if(null===a)throw Qi("null value for BigDecimal");if(null===b)throw Qi("null MathContext for BigDecimal");this.Jy=1565550863}RC.prototype=new oJ;RC.prototype.constructor=RC;e=RC.prototype;e.$f=function(a){return XK(this.Ya,a.Ya)}; +e.p=function(){if(1565550863===this.Jy){if(this.ki()&&4934>(MK(this.Ya)-this.Ya.na|0))var a=zU(new JD,YK(this.Ya)).p();else{a=this.Ya.dj();if(Infinity!==a&&-Infinity!==a){var b=GD();a=AU(this,BD(a,b.fp))}else a=!1;if(a)a=yw(X(),this.Ya.dj());else{a=WK(this.Ya);b=Y();var c=b.cg,d=a.na,f=a.na,g=f-d|0;d=((f>>31)-(d>>31)|0)+((~f&d|~(f^d)&g)>>31)|0;64>a.Od?0===(a.vd|a.kd)?(f=zB(),g=0===(g^g|d^g>>31)?CB(f,0,0,g):0<=d?xB(0,2147483647):xB(0,-2147483648)):g=CB(zB(),a.vd,a.kd,GB(zB(),g,d)):g=QK(new qB,rB(a), +GB(zB(),g,d));a=c.call(b,YK(g).p(),a.na)}}this.Jy=a}return this.Jy}; +e.d=function(a){if(a instanceof RC)return AU(this,a);if(a instanceof JD){if(BU(a)>3.3219280948873626*((MK(this.Ya)-this.Ya.na|0)-2|0)){if(this.ki())try{var b=new E(zU(new JD,UK(this.Ya)))}catch(c){if(c instanceof La)b=F();else throw c;}else b=F();if(b.e())return!1;b=b.oa();return CU(a,b)}return!1}return"number"===typeof a?(b=+a,Infinity!==b&&-Infinity!==b&&(a=this.Ya.dj(),Infinity!==a&&-Infinity!==a&&a===b)?(b=GD(),AU(this,BD(a,b.fp))):!1):oa(a)?(b=Math.fround(a),Infinity!==b&&-Infinity!==b&&(a=this.Ya.Im(), +Infinity!==a&&-Infinity!==a&&a===b)?(b=GD(),AU(this,BD(a,b.fp))):!1):this.Sv()&&Fv(this,a)};e.ny=function(){try{return TK(this.Ya,8),!0}catch(a){if(a instanceof La)return!1;throw a;}};e.py=function(){try{return TK(this.Ya,16),!0}catch(a){if(a instanceof La)return!1;throw a;}};e.oy=function(){return this.Rv()&&0<=TK(this.Ya,32).h&&65535>=TK(this.Ya,32).h};e.Rv=function(){try{return TK(this.Ya,32),!0}catch(a){if(a instanceof La)return!1;throw a;}}; +e.Sv=function(){try{return TK(this.Ya,64),!0}catch(a){if(a instanceof La)return!1;throw a;}};e.ki=function(){return 0>=this.Ya.na||0>=WK(this.Ya).na};function AU(a,b){return 0===XK(a.Ya,b.Ya)}e.Ro=function(){return this.Ya.Dd()<<24>>24};e.Kp=function(){return this.Ya.Dd()<<16>>16};e.Dd=function(){return this.Ya.Dd()};e.uf=function(){return this.Ya.uf()};e.Im=function(){return this.Ya.Im()};e.dj=function(){return this.Ya.dj()};e.f=function(){return this.Ya.f()};e.zg=function(a){return XK(this.Ya,a.Ya)}; +e.AJ=function(){return this.Ya};var yD=z(RC,"scala.math.BigDecimal",{L3:1,xO:1,hj:1,b:1,yO:1,rC:1,nh:1,Lb:1});function aN(a){return 0!==(a.Ac|-2147483648^a.Xb)}function DU(a){a=Rj(bN(a),2147483647);return 0!==a.ca&&!a.d(Vv().rI)}function ID(a,b,c,d){a.Hg=b;a.Ac=c;a.Xb=d;return a}function zU(a,b){if(63>=Wi(gj(),b)){var c=b.uf(),d=c.h;c=c.l}else d=0,c=-2147483648;ID(a,b,d,c);return a}function JD(){this.Hg=null;this.Xb=this.Ac=0}JD.prototype=new oJ;JD.prototype.constructor=JD;e=JD.prototype; +e.$f=function(a){return EU(this,a)};function bN(a){var b=a.Hg;if(null!==b)return b;b=a.Ac;var c=a.Xb;b=Aj(Zi(),b,c);return a.Hg=b}e.p=function(){if(this.Sv()){var a=this.uf();var b=a.h;a=a.l;b=(-1===a?2147483648<=b>>>0:-1=b>>>0:0>a)?b:xw(X(),b,a)}else b=zw(X(),bN(this));return b}; +e.d=function(a){if(a instanceof JD)return CU(this,a);if(a instanceof RC)return a.d(this);if("number"===typeof a){a=+a;var b=BU(this);if(53>=b)b=!0;else{var c=FU(this);b=1024>=b&&c>=(b-53|0)&&1024>c}return(b?!DU(this):!1)&&this.dj()===a}return oa(a)?(a=Math.fround(a),b=BU(this),24>=b?b=!0:(c=FU(this),b=128>=b&&c>=(b-24|0)&&128>c),b&&!DU(this)?(b=bN(this),Pn(Qn(),jj(mj(),b))===a):!1):this.Sv()&&Fv(this,a)}; +e.ny=function(){var a=this.Ac,b=this.Xb;return(-1===b?4294967168<=a>>>0:-1=a>>>0:0>b):!1};e.py=function(){var a=this.Ac,b=this.Xb;return(-1===b?4294934528<=a>>>0:-1=a>>>0:0>b):!1};e.oy=function(){if(0<=this.Xb){var a=this.Ac,b=this.Xb;return 0===b?65535>=a>>>0:0>b}return!1};e.Rv=function(){var a=this.Ac,b=this.Xb;return(-1===b?2147483648<=a>>>0:-1=a>>>0:0>b):!1}; +e.Sv=function(){return aN(this)||Yv(W(),this.Hg,Vv().qC)};e.ki=function(){return!0};function CU(a,b){return aN(a)?aN(b)?0===(a.Ac^b.Ac|a.Xb^b.Xb):!1:!aN(b)&&Yv(W(),a.Hg,b.Hg)}function EU(a,b){if(aN(a)){if(aN(b)){var c=a.Ac;a=a.Xb;var d=b.Ac;b=b.Xb;return a===b?c===d?0:c>>>0>>0?-1:1:a=b)if(b=-b|0,aN(a)&&0<=b)if(64>b){var c=Vv(),d=a.Ac;a=a.Xb;a=OD(c,0===(32&b)?d>>>b|0|a<<1<<(31-b|0):a>>b,0===(32&b)?a>>b:a>>31)}else a=0>a.Xb?ND(Vv(),-1):ND(Vv(),0);else a=be(Vv(),Rj(bN(a),b));else a=be(Vv(),Vj(bN(a),b));return a}function FU(a){if(aN(a)){if(0===(a.Ac|a.Xb))return-1;var b=a.Ac;a=a.Xb;return 0!==b?32-Math.clz32(~b&(b-1|0))|0:64-Math.clz32(~a&(a-1|0))|0}return ZK(bN(a))} +function BU(a){if(aN(a)){if(0>a.Xb){var b=a.Ac,c=1+b|0,d=-c|0;a=(-(a.Xb+((b&~c)>>>31|0)|0)|0)+((c|d)>>31)|0;return 64-(0!==a?Math.clz32(a):32+Math.clz32(d)|0)|0}d=a.Ac;a=a.Xb;return 64-(0!==a?Math.clz32(a):32+Math.clz32(d)|0)|0}return Wi(gj(),a.Hg)}e.Ro=function(){return this.Dd()<<24>>24};e.Kp=function(){return this.Dd()<<16>>16};e.Dd=function(){return aN(this)?this.Ac:bN(this).Dd()};e.uf=function(){return aN(this)?r(this.Ac,this.Xb):this.Hg.uf()}; +e.Im=function(){var a=bN(this);return Pn(Qn(),jj(mj(),a))};e.dj=function(){if(this.Sv())if(-2097152<=this.Xb){var a=this.Ac,b=this.Xb;a=2097152===b?0===a:2097152>b}else a=!1;else a=!1;if(a)return 4294967296*this.Xb+(this.Ac>>>0);a=bN(this);return tA(Ca(),jj(mj(),a))};e.f=function(){if(aN(this)){var a=this.Ac,b=this.Xb;return vm(qj(),a,b)}a=this.Hg;return jj(mj(),a)};e.zg=function(a){return EU(this,a)};e.AJ=function(){return bN(this)}; +var MD=z(JD,"scala.math.BigInt",{N3:1,xO:1,hj:1,b:1,yO:1,rC:1,nh:1,Lb:1});function GU(){this.tI=null;HU=this;this.tI=new tQ(this)}GU.prototype=new t;GU.prototype.constructor=GU;e=GU.prototype;e.Me=function(a){return a===this.tI};e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.I=function(a,b){a|=0;b|=0;return a===b?0:aa.Vb(c,new G(g=>d.Ca(f,g)))))}function fV(a,b,c,d){Ke();return new Nb(a.ag(b,new G(f=>a.Vb(c.Kh(),new G(g=>d.Ca(f,g))))))}function XT(){}XT.prototype=new rz;XT.prototype.constructor=XT;e=XT.prototype;e.x=function(){return new Z(this)};e.t=function(){return 0};e.v=function(){return"Empty"}; +e.c=function(a){throw O(new P,""+a);};e.Rc=function(){return this};z(XT,"cats.data.Chain$Empty$",{fR:1,Kz:1,Mz:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var WT;function gV(){}gV.prototype=new $T;gV.prototype.constructor=gV;function hV(){}hV.prototype=gV.prototype;function iV(){}iV.prototype=new t;iV.prototype.constructor=iV;iV.prototype.BB=function(a,b){return a.MP(b)};z(iV,"cats.kernel.instances.BitSetSemilattice",{mT:1,b:1,lf:1,eE:1,Rf:1,jE:1,Tf:1,qg:1,xL:1});function jV(){}jV.prototype=new t; +jV.prototype.constructor=jV;e=jV.prototype;e.ke=function(a){return a.p()};e.I=function(a,b){return a.zg(b)};e.Ud=function(a,b){return null===a?null===b:a.d(b)};e.Le=function(a,b){return a.CJ(b)};e.Pe=function(a,b){return a.S2(b)};z(jV,"cats.kernel.instances.DurationOrder",{zT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,xT:1});function kV(){}kV.prototype=new t;kV.prototype.constructor=kV;e=kV.prototype;e.ke=function(a){return a.p()};e.I=function(a,b){return a.v1(b)}; +e.Ud=function(a,b){return null===a?null===b:a.d(b)};e.Le=function(a,b){return a.CJ(b)};e.Pe=function(a,b){return a.T2(b)};z(kV,"cats.kernel.instances.FiniteDurationOrder",{CT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,AT:1});function lV(){this.fb=0;this.gb=!1}lV.prototype=new dA;lV.prototype.constructor=lV;e=lV.prototype;e.x=function(){return new Z(this)};e.f=function(){return"AnyChar"};e.t=function(){return 0};e.v=function(){return"AnyChar"};e.c=function(a){throw O(new P,""+a);}; +e.gC=function(a){var b=a.Wa;if(b{Ob();return new $b(new GF(b,0,65535))})));return 0};e.qa=function(a){return p(this.gC(a))};e.Rc=function(){return this};z(lV,"cats.parse.Parser$Impl$AnyChar$",{OU:1,Ve:1,vb:1,k:1,s:1,b:1,Wb:1,pd:1,mh:1});var mV;function Te(){mV||(mV=new lV);return mV}function nV(){}nV.prototype=new pR;nV.prototype.constructor=nV;function oV(){}oV.prototype=nV.prototype;nV.prototype.Kb=function(){return IH()}; +nV.prototype.f=function(){return uT(this)};nV.prototype.Ib=function(){return"View"};function pV(a,b){if(a===b)return!0;if(b&&b.$classData&&b.$classData.wb.mk)if(a.X()===b.X())try{return a.qD(b)}catch(c){if(c instanceof eI)return!1;throw c;}else return!1;else return!1}function qV(){this.ew="Any"}qV.prototype=new XU;qV.prototype.constructor=qV;qV.prototype.od=function(){return l(ob)};qV.prototype.le=function(a){return new x(a)}; +z(qV,"scala.reflect.ManifestFactory$AnyManifest$",{u4:1,tC:1,sC:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var rV;function TD(){rV||(rV=new qV);return rV}function sV(){this.oe="Boolean"}sV.prototype=new JU;sV.prototype.constructor=sV;z(sV,"scala.reflect.ManifestFactory$BooleanManifest$",{w4:1,v4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var tV;function rx(){tV||(tV=new sV);return tV}function uV(){this.oe="Byte"}uV.prototype=new LU;uV.prototype.constructor=uV; +z(uV,"scala.reflect.ManifestFactory$ByteManifest$",{y4:1,x4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var vV;function px(){vV||(vV=new uV);return vV}function wV(){this.oe="Char"}wV.prototype=new NU;wV.prototype.constructor=wV;z(wV,"scala.reflect.ManifestFactory$CharManifest$",{A4:1,z4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var xV;function ox(){xV||(xV=new wV);return xV}function yV(){this.oe="Double"}yV.prototype=new PU;yV.prototype.constructor=yV; +z(yV,"scala.reflect.ManifestFactory$DoubleManifest$",{C4:1,B4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var zV;function lx(){zV||(zV=new yV);return zV}function AV(){this.oe="Float"}AV.prototype=new RU;AV.prototype.constructor=AV;z(AV,"scala.reflect.ManifestFactory$FloatManifest$",{E4:1,D4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var BV;function nx(){BV||(BV=new AV);return BV}function CV(){this.oe="Int"}CV.prototype=new TU;CV.prototype.constructor=CV; +z(CV,"scala.reflect.ManifestFactory$IntManifest$",{G4:1,F4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var DV;function fq(){DV||(DV=new CV);return DV}function EV(){this.oe="Long"}EV.prototype=new VU;EV.prototype.constructor=EV;z(EV,"scala.reflect.ManifestFactory$LongManifest$",{I4:1,H4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var FV;function mx(){FV||(FV=new EV);return FV}function GV(){this.ew="Nothing"}GV.prototype=new XU;GV.prototype.constructor=GV;GV.prototype.od=function(){return l(qs)}; +GV.prototype.le=function(a){return new x(a)};z(GV,"scala.reflect.ManifestFactory$NothingManifest$",{J4:1,tC:1,sC:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var HV;function UD(){HV||(HV=new GV);return HV}function IV(){this.ew="Null"}IV.prototype=new XU;IV.prototype.constructor=IV;IV.prototype.od=function(){return l(ps)};IV.prototype.le=function(a){return new x(a)};z(IV,"scala.reflect.ManifestFactory$NullManifest$",{K4:1,tC:1,sC:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var JV;function VD(){JV||(JV=new IV);return JV} +function KV(){this.ew="Object"}KV.prototype=new XU;KV.prototype.constructor=KV;KV.prototype.od=function(){return l(ob)};KV.prototype.le=function(a){return new x(a)};z(KV,"scala.reflect.ManifestFactory$ObjectManifest$",{L4:1,tC:1,sC:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var LV;function Bs(){LV||(LV=new KV);return LV}function MV(){this.oe="Short"}MV.prototype=new ZU;MV.prototype.constructor=MV;z(MV,"scala.reflect.ManifestFactory$ShortManifest$",{N4:1,M4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var NV; +function qx(){NV||(NV=new MV);return NV}function OV(){this.oe="Unit"}OV.prototype=new aV;OV.prototype.constructor=OV;z(OV,"scala.reflect.ManifestFactory$UnitManifest$",{P4:1,O4:1,ul:1,oh:1,dg:1,Ig:1,Jg:1,b:1,k:1});var PV;function SD(){PV||(PV=new OV);return PV}function kT(a){this.Ug=a}kT.prototype=new t;kT.prototype.constructor=kT;e=kT.prototype;e.zg=function(a){Qr();var b=this.Ug;a|=0;return b===a?0:b=this.Dd()};e.dj=function(){return this.Ug};e.Im=function(){return Math.fround(this.Ug)};e.uf=function(){var a=this.Ug;return r(a,a>>31)};e.Dd=function(){return this.Ug};e.Ro=function(){return this.Ug<<24>>24};e.Kp=function(){return this.Ug<<16>>16};e.ki=function(){return!0};e.Rv=function(){return!0}; +e.p=function(){return this.Ug};e.d=function(a){fw||(fw=new ew);return a instanceof kT&&this.Ug===a.Ug};e.Kw=function(){return this.Ug};z(kT,"scala.runtime.RichInt",{Z9:1,KP:1,rC:1,pI:1,oI:1,wJ:1,nh:1,Lb:1,JP:1});function OS(a,b){this.wo=0;this.xo=!1;this.to=this.uo=this.ro=this.so=0;this.yc=a;this.Qd=b;var c=a.z();var d=c.h;c=c.l;var f=b.z(),g=f.h;b=d+g|0;d=(c+f.l|0)+((d&g|(d|g)&~b)>>>31|0)|0;this.so=b;this.ro=d;a=a.z();this.uo=a.h;this.to=a.l}OS.prototype=new LS;OS.prototype.constructor=OS;e=OS.prototype; +e.x=function(){return new Z(this)};e.t=function(){return 2};e.v=function(){return"Append"};e.c=function(a){if(0===a)return this.yc;if(1===a)return this.Qd;throw O(new P,""+a);};e.ji=function(a,b){var c=this.yc.z(),d=c.h;c=c.l;if(b===c?a>>>0>>0:b>31)|0)}; +e.fh=function(a,b){var c=this.yc.z(),d=c.h,f=c.l,g=f>>2>>>29|0;c=d+g|0;d=f+((d&g|(d|g)&~c)>>>31|0)|0;f=d>>3;if(b===f?a>>>0<(c>>>3|0|d<<29)>>>0:b>2>>>29|0;d=7&(d.h+c|0);f=d-c|0;0===(f|(~d&c|~(d^c)&f)>>31)?(c=this.yc.z(),d=c.h,f=c.l,g=f>>2>>>29|0,c=d+g|0,d=f+((d&g|(d|g)&~c)>>>31|0)|0,f=d>>3,c=b===f?a>>>0>(c>>>3|0|d<<29)>>>0:b>f):c=!1;if(c){c=this.Qd;f=this.yc.z();d=f.h;g=f.l;var h=g>>2>>>29|0;f=d+h|0;d=g+((d&h|(d|h)&~f)>>>31|0)|0;f=f>>>3|0|d<<29;g=a-f|0; +return c.fh(g,(b-(d>>3)|0)+((~a&f|~(a^f)&g)>>31)|0)}return this.Sb(a<<3,a>>>29|0|b<<3).jd(8,0).ei().fh(0,0)};e.ei=function(){return US(this.yc.ei(),this.Qd.ei())}; +e.z=function(){if(0!==(~this.so|~this.ro))return r(this.so,this.ro);var a;a:for(var b=a=0,c=new J(this.yc,new J(this.Qd,N()));;){if(N().d(c)){a=r(a,b);break a}if(c instanceof J){var d=c.Fa;c=c.Z;if(d instanceof OS)c=new J(d.yc,new J(d.Qd,c));else{if(d instanceof NS){var f=d.pf;if(null!==f){c=new J(f.yc,new J(f.Qd,c));continue}}f=b;d=d.z();var g=d.h;b=a+g|0;d=(f+d.l|0)+((a&g|(a|g)&~b)>>>31|0)|0;a=b;b=d}}else throw new D(c);}c=a.h;a=a.l;this.so=c;this.ro=a;return r(c,a)}; +e.jd=function(a,b){var c=0>b?a=0:b;if(0===(a|c))return rp().$h;var d=this.yc.z();b=d.h;d=d.l;if(c===d?a>>>0<=b>>>0:c>31)|0,a=f;;)if(g=a,g instanceof OS){f=g.yc;a=g.Qd;g=b;h=c;var m=f.z();k=m.h;m=m.l;if(h===m?g>>>0<=k>>>0:h>31)|0}else{b=d.Dj(a.jd(b,c));break a}return b}; +e.Sb=function(a,b){var c=0>b?a=0:b;if(0===(a|c))return this;var d=this.yc.z();b=d.h;d=d.l;if(c===d?a>>>0>=b>>>0:c>d){d=this.Qd;var f=this.yc.z(),g=f.h,h=a-g|0;a:for(b=h,c=(c-f.l|0)+((~a&g|~(a^g)&h)>>31)|0,a=d;;){f=a;if(f instanceof OS){d=f.yc;a=f.Qd;f=b;g=c;var k=d.z();h=k.h;k=k.l;if(g===k?f>>>0>=h>>>0:g>k){f=c;d=d.z();g=d.h;c=b-g|0;d=(f-d.l|0)+((~b&g|~(b^g)&c)>>31)|0;b=c;c=d;continue}else{b=new OS(d.Sb(b,c),a);break a}}b=a.Sb(b,c);break a}return b}return new OS(this.yc.Sb(a,c),this.Qd)}; +e.qe=function(a,b){if(0!==(~this.so|~this.ro)){var c=this.so,d=this.ro;return d===b?c>>>0>>0:d>>0>=a>>>0:d>b)a=!1;else a:{var f=0;c=0;for(d=this;;){var g=d;if(g instanceof OS){var h=g.yc;d=g.Qd;var k=h.z();g=k.h;k=k.l;var m=a,n=b;if(k===n?g>>>0>=m>>>0:k>n){b=f;d=h.z();f=d.h;a=b+f|0;b=(c+d.l|0)+((b&f|(b|f)&~a)>>>31|0)|0;d=this.uo;c=this.to;(b===c?a>>>0>d>>>0:b>c)||(a=d,b=c);this.uo=a;this.to=b;a=!1;break a}g=a;k=h.z();m=k.h;a=g-m|0;b=(b-k.l|0)+((~g&m|~(g^m)&a)>> +31)|0;g=c;h=h.z();k=h.h;c=f+k|0;h=(g+h.l|0)+((f&k|(f|k)&~c)>>>31|0)|0;f=c;c=h}else{g=this.uo;h=this.to;(c===h?f>>>0>g>>>0:c>h)||(f=g,c=h);this.uo=f;this.to=c;d=d.z();c=d.h;d=d.l;a=d===b?c>>>0>>0:d>31)|0)}function FQ(a,b,c){this.wo=0;this.xo=!1;this.$c=a;this.ge=b;this.fe=c}FQ.prototype=new LS; +FQ.prototype.constructor=FQ;e=FQ.prototype;e.x=function(){return new Z(this)};e.t=function(){return 2};e.v=function(){return"Bytes"};e.c=function(a){if(0===a)return this.$c;if(1===a)return r(this.ge,this.fe);throw O(new P,""+a);};e.z=function(){return r(this.ge,this.fe)};e.ei=function(){return this};e.qe=function(a,b){var c=this.ge,d=this.fe;return d===b?c>>>0>>0:d>>0>>0:ac&&(c=b=0);return fN(d,b,c)} +e.Sb=function(a,b){var c=this.ge,d=this.fe;if(b===d?a>>>0>=c>>>0:b>d)return rp().$h;if(0===b?0===a:0>b)return this;c=b>>2>>>29|0;d=7&(a+c|0);var f=d-c|0;0===(f|(~d&c|~(d^c)&f)>>31)?(d=b>>2>>>29|0,c=a+d|0,d=b+((a&d|(a|d)&~c)>>>31|0)|0,c=this.$c.hi(c>>>3|0|d<<29,d>>3),d=this.ge,f=d-a|0,a=new FQ(c,f,(this.fe-b|0)+((~d&a|~(d^a)&f)>>31)|0)):a=new SS(this,a,b);return a}; +e.ji=function(a,b){if(!(0>b)&&oN(this,a,b)){var c=this.z();throw new Cs("invalid index: "+da(a,b)+" of "+da(c.h,c.l));}rp();var d=b>>2>>>29|0;c=a+d|0;d=b+((a&d|(a|d)&~c)>>>31|0)|0;c=CE(this.$c,c>>>3|0|d<<29,d>>3);b=b>>2>>>29|0;return 0!==(128>>((7&(a+b|0))-b|0)&c)};e.fh=function(a,b){var c=this.$c.z(),d=c.h,f=d-1|0;c=(c.l-1|0)+((d|~f)>>>31|0)|0;if(b===c?a>>>0>>0:b>24}; +function US(a,b){var c=QV(a),d=c.h;c=c.l;var f=b.$c;if(a.qe(1,0))return b;if(f.e())return a;if(0===(d|c)){rp();d=a.$c.Of(f);c=a.ge;var g=b.ge,h=c+g|0;return fN(d,h,(a.fe+b.fe|0)+((c&g|(c|g)&~h)>>>31|0)|0)}g=IQ(rp(),a.ge,a.fe,a.$c);h=g.z();var k=h.h,m=k-1|0;h=CE(g,m,(h.l-1|0)+((k|~m)>>>31|0)|0);f=CE(f,0,0);k=HQ(rp(),d);m=GQ(rp(),a.ge,a.fe);f=((255&f&k)>>>m.h|0)<<24>>24;k=g.z();m=k.h;var n=m-1|0;g=JQ(g,n,(k.l-1|0)+((m|~n)>>>31|0)|0,(h|f)<<24>>24);d=zy(b.Sb(d,c));rp();d=g.Of(d);c=a.ge;g=b.ge;h=c+g|0; +return fN(d,h,(a.fe+b.fe|0)+((c&g|(c|g)&~h)>>>31|0)|0)}e.jd=function(a,b){return RV(this,a,b)};z(FQ,"scodec.bits.BitVector$Bytes",{P_:1,XA:1,ym:1,YA:1,Lb:1,nh:1,b:1,k:1,s:1});function NS(a){this.wo=0;this.xo=!1;this.pf=a}NS.prototype=new LS;NS.prototype.constructor=NS;e=NS.prototype;e.x=function(){return new Z(this)};e.t=function(){return 1};e.v=function(){return"Chunks"};e.c=function(a){if(0===a)return this.pf;throw O(new P,""+a);};e.sD=function(){return new OS(this.pf.yc,this.pf.Qd.sD())}; +e.ei=function(){return this.pf.ei()};e.jd=function(a,b){return this.pf.jd(a,b)};e.Sb=function(a,b){return this.pf.Sb(a,b)}; +e.Dj=function(a){if(a.qe(1,0))a=this;else if(!this.pf.qe(1,0))a:{var b=a.sD();for(a=this.pf;;){var c=b.z(),d=c.h,f=c.l,g=a.z();c=g.h;g=g.l;if(f===g?d>>>0>=c>>>0:f>g)c=!0;else{c=d<<1;g=d>>>31|0|f<<1;var h=a.Qd.z(),k=h.h;h=h.l;c=g===h?c>>>0<=k>>>0:g>2>>>29|0;d=7&(d+f|0);h=d-f|0;var m=k>>2>>>29|0,n=7&(g+m|0),q=n-m|0,u=h+q|0;d=0===(u|(((~d&f|~(d^f)&h)>>31)+((~n&m|~(n^m)&q)>>31)|0)+((h&q|(h|q)&~u)>>>31|0)|0);(0===k?256>=g>>>0:0>k)&&d?(b=US(a.Qd.ei(), +b.ei()),a=c):(b=new OS(a.Qd,b),a=c);continue}a=new NS(new OS(a,b));break a}}return a};e.z=function(){return this.pf.z()};e.qe=function(a,b){return this.pf.qe(a,b)};e.ji=function(a,b){return this.pf.ji(a,b)};e.fh=function(a,b){return this.pf.fh(a,b)};z(NS,"scodec.bits.BitVector$Chunks",{Q_:1,XA:1,ym:1,YA:1,Lb:1,nh:1,b:1,k:1,s:1});function SS(a,b,c){this.wo=0;this.xo=!1;this.vo=a;this.xm=b;this.wm=c}SS.prototype=new LS;SS.prototype.constructor=SS;e=SS.prototype;e.x=function(){return new Z(this)}; +e.t=function(){return 2};e.v=function(){return"Drop"};e.c=function(a){if(0===a)return this.vo;if(1===a)return r(this.xm,this.wm);throw O(new P,""+a);};e.z=function(){var a=this.vo,b=a.ge,c=this.xm,d=b-c|0;a=(a.fe-this.wm|0)+((~b&c|~(b^c)&d)>>31)|0;return 0>a?r(0,0):r(d,a)};e.qe=function(a,b){var c=this.z(),d=c.h;c=c.l;return c===b?d>>>0>>0:c>>0>=d>>>0:b>c)return rp().$h;if(0===b?0===a:0>b)return this;c=this.xm;d=a+c|0;b=(b+this.wm|0)+((a&c|(a|c)&~d)>>>31|0)|0;a=new SS(this.vo,d,b);(0===b?32768>>0:0>2>>>29|0,d=7&(d+b|0),c=d-b|0,b=0===(c|(~d&b|~(d^b)&c)>>31)):b=!1;return b?TS(a):a}; +e.jd=function(a,b){var c=this.z(),d=c.h;c=c.l;if(b===c?a>>>0>=d>>>0:b>c)return this;if(0===b?0===a:0>b)return rp().$h;d=this.xm;c=d+a|0;return RV(this.vo,c,(this.wm+b|0)+((d&a|(d|a)&~c)>>>31|0)|0).Sb(this.xm,this.wm)};e.ji=function(a,b){var c=this.xm,d=c+a|0;return this.vo.ji(d,(this.wm+b|0)+((c&a|(c|a)&~d)>>>31|0)|0)};e.fh=function(a,b){return this.Sb(a<<3,a>>>29|0|b<<3).jd(8,0).ei().fh(0,0)}; +function TS(a){var b=a.xm,c=a.wm;if(0===c?0!==b:0>2>>>29|0,h=d+g|0;g=f+((d&g|(d|g)&~h)>>>31|0)|0;h=h>>>3|0|g<<29;g>>=3;a=a.vo.$c;var k=eN(rp(),c,b),m=k.h,n=h+m|0,q=1+n|0;a=gT(a,h,g,q,((g+k.l|0)+((h&m|(h|m)&~n)>>>31|0)|0)+((n&~q)>>>31|0)|0);f=f>>2>>>29|0;var u=(7&(d+f|0))-f|0;d=0===u?a:jT(a,a.hi(1,0).Qw(0),new Qb((v,w)=>(v|0)<>>(8-u|0)|0));rp();a=d.z();h=a.h;f=h-1|0;a=f>>>29|0|((a.l-1|0)+ +((h|~f)>>>31|0)|0)<<3;return fN((b===a?c>>>0<=f<<3>>>0:b>>31|0)|0;this.Vx=b;this.Ux=a}cT.prototype=new ZS;cT.prototype.constructor=cT;e=cT.prototype;e.x=function(){return new Z(this)};e.t=function(){return 2};e.v=function(){return"Append"}; +e.c=function(a){if(0===a)return this.ai;if(1===a)return this.bj;throw O(new P,""+a);};e.z=function(){return r(this.Vx,this.Ux)};e.ml=function(a,b){var c=this.ai.z(),d=c.h;c=c.l;if(b===c?a>>>0>>0:b>31)|0)};z(cT,"scodec.bits.ByteVector$Append",{b0:1,ZA:1,aB:1,ym:1,Lb:1,nh:1,b:1,k:1,s:1});function lp(a){this.zo=0;this.Ao=!1;this.hl=a}lp.prototype=new ZS;lp.prototype.constructor=lp;e=lp.prototype; +e.x=function(){return new Z(this)};e.t=function(){return 1};e.v=function(){return"Chunk"};e.c=function(a){if(0===a)return this.hl;throw O(new P,""+a);};e.z=function(){var a=this.hl;return r(a.vg,a.ug)};e.ml=function(a,b){return this.hl.fi(a,b)};z(lp,"scodec.bits.ByteVector$Chunk",{g0:1,ZA:1,aB:1,ym:1,Lb:1,nh:1,b:1,k:1,s:1});function bT(a){this.zo=0;this.Ao=!1;this.il=a}bT.prototype=new ZS;bT.prototype.constructor=bT;e=bT.prototype;e.x=function(){return new Z(this)};e.t=function(){return 1};e.v=function(){return"Chunks"}; +e.c=function(a){if(0===a)return this.il;throw O(new P,""+a);};e.z=function(){var a=this.il;return r(a.Vx,a.Ux)};e.ml=function(a,b){return this.il.ml(a,b)};e.Of=function(a){if(a.e())a=this;else if(!this.e())a:{var b=a.De();for(a=this.il;;){var c=b.z(),d=c.h,f=c.l;c=a.Vx;var g=a.Ux;(f===g?d>>>0>=c>>>0:f>g)?c=!0:(c=d<<1,d=d>>>31|0|f<<1,g=a.bj.z(),f=g.h,g=g.l,c=d===g?c>>>0<=f>>>0:dbW(this).i()))};e.E=function(){return this.qh}; +e.e=function(){return 0===this.qh};e.mD=function(a){var b=this.mp;return(null===a?null===b:a.d(b))?this:a.Me(this.mp)?new aW(this):YV(new $V,cW(this),this.qh,a)};e.Vd=function(a){return AH(IH(),a)};e.ya=function(a){return dW(new eW,this,a)};e.Na=function(a){return fW(new gW,this,a)};e.pa=function(a){return hW(new iW,this,a)};e.hd=function(a){return this.mD(a)};z($V,"scala.collection.SeqView$Sorted",{M5:1,ze:1,ea:1,R:1,w:1,y:1,Bc:1,O:1,Q:1,b:1}); +function jW(a){if(!a.bz){var b=new kW,c=bW(a.oi);b.$m=c;a.az=b;a.bz=!0}return a.az}function aW(a){this.az=null;this.bz=!1;this.oi=a}aW.prototype=new t;aW.prototype.constructor=aW;e=aW.prototype;e.Kb=function(){return IH()};e.f=function(){return uT(this)};e.xd=function(){return"SeqView"};e.xf=function(){return IH().Da()};e.X=function(){return this.oi.qh};e.Rd=function(a){return aM(this,a)};e.Nb=function(){return this.oi.i()};e.bg=function(a,b){return nH(this.i(),a,b)}; +e.Qa=function(a){return jH(this,a)};e.u=function(){return this.i().j()};e.Cb=function(a){return lH(this,a)};e.ll=function(a){return is(this,a)};e.rl=function(a){return ls(this,a)};e.hc=function(a,b,c){return Fd(this,a,b,c)};e.rf=function(a,b,c,d){return ns(this,a,b,c,d)};e.ub=function(){return rg(N(),this)};e.Wg=function(a){return os(this,a)};e.J=function(a){return(this.bz?this.az:jW(this)).J(a)};e.q=function(){return this.oi.qh};e.i=function(){return Lv().da.cj(new Rh(()=>(this.bz?this.az:jW(this)).i()))}; +e.E=function(){return this.oi.qh};e.e=function(){return 0===this.oi.qh};e.mD=function(a){var b=this.oi.mp;return(null===a?null===b:a.d(b))?this.oi:a.Me(this.oi.mp)?this:YV(new $V,cW(this.oi),this.oi.qh,a)};e.Vd=function(a){return AH(IH(),a)};e.ya=function(a){return dW(new eW,this,a)};e.Na=function(a){return fW(new gW,this,a)};e.pa=function(a){return hW(new iW,this,a)};e.hd=function(a){return this.mD(a)}; +z(aW,"scala.collection.SeqView$Sorted$ReverseSorted",{N5:1,ze:1,ea:1,R:1,w:1,y:1,Bc:1,O:1,Q:1,b:1});function CH(a){this.aP=a}CH.prototype=new oV;CH.prototype.constructor=CH;CH.prototype.i=function(){return this.aP.za()};z(CH,"scala.collection.View$$anon$1",{a6:1,bd:1,S:1,O:1,w:1,R:1,y:1,Q:1,Bc:1,b:1});function $L(){this.op=this.cn=null}$L.prototype=new oV;$L.prototype.constructor=$L;function lW(){}lW.prototype=$L.prototype;$L.prototype.i=function(){return(new CO(this.cn,new mW(this.op))).i()}; +$L.prototype.E=function(){var a=this.cn.E();return 0<=a?1+a|0:-1};$L.prototype.e=function(){return!1};z($L,"scala.collection.View$Appended",{MC:1,bd:1,S:1,O:1,w:1,R:1,y:1,Q:1,Bc:1,b:1});function CO(a,b){this.NC=a;this.OC=b}CO.prototype=new oV;CO.prototype.constructor=CO;CO.prototype.i=function(){return this.NC.i().cj(new Rh(()=>this.OC.i()))};CO.prototype.E=function(){var a=this.NC.E();if(0<=a){var b=this.OC.E();return 0<=b?a+b|0:-1}return-1};CO.prototype.e=function(){return this.NC.e()&&this.OC.e()}; +z(CO,"scala.collection.View$Concat",{b6:1,bd:1,S:1,O:1,w:1,R:1,y:1,Q:1,Bc:1,b:1});function bM(a,b){this.PC=a;this.bP=b}bM.prototype=new oV;bM.prototype.constructor=bM;bM.prototype.i=function(){return new oO(this.PC.i(),this.bP)};bM.prototype.E=function(){return 0===this.PC.E()?0:-1};bM.prototype.e=function(){return this.PC.e()};z(bM,"scala.collection.View$DistinctBy",{c6:1,bd:1,S:1,O:1,w:1,R:1,y:1,Q:1,Bc:1,b:1});function qR(a,b,c){a.qp=b;a.ow=c;a.en=0=b)){var c=a.E();a=0<=c?a.Mw(c-b|0):new FO(a,b)}return a};AR.prototype.E=function(){var a=this.pp.E();return 0<=a?(a=a-this.nw|0,0=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.Ek=function(a){a|=0;return(a>>31|(-a|0)>>>31|0)<<24>>24};e.Nf=function(a){return a|0};e.Jb=function(a){return a<<24>>24};e.Wv=function(a){return(-(a|0)|0)<<24>>24};e.dp=function(a,b){return((a|0)%Ka(b|0)|0)<<24>>24};e.fk=function(a,b){return((a|0)/Ka(b|0)|0)<<24>>24};e.Ih=function(a,b){return Math.imul(a|0,b|0)<<24>>24};e.wf=function(a,b){return((a|0)-(b|0)|0)<<24>>24};e.me=function(a,b){return((a|0)+(b|0)|0)<<24>>24}; +e.I=function(a,b){return(a|0)-(b|0)|0};z(tW,"scala.math.Numeric$ByteIsIntegral$",{T3:1,S3:1,Ky:1,Ly:1,zf:1,Fd:1,Af:1,yf:1,b:1,sO:1});var uW;function YC(){uW||(uW=new tW);return uW}function vW(){}vW.prototype=new t;vW.prototype.constructor=vW;e=vW.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.Ek=function(a){a=$a(a);return p(65535&(a>>31|(-a|0)>>>31|0))};e.Nf=function(a){return $a(a)}; +e.Jb=function(a){return p(65535&a)};e.Wv=function(a){return p(65535&(-$a(a)|0))};e.dp=function(a,b){return p(65535&($a(a)%Ka($a(b))|0))};e.fk=function(a,b){return p(65535&($a(a)/Ka($a(b))|0))};e.Ih=function(a,b){return p(65535&Math.imul($a(a),$a(b)))};e.wf=function(a,b){return p(65535&($a(a)-$a(b)|0))};e.me=function(a,b){return p(65535&($a(a)+$a(b)|0))};e.I=function(a,b){return $a(a)-$a(b)|0};z(vW,"scala.math.Numeric$CharIsIntegral$",{V3:1,U3:1,Ky:1,Ly:1,zf:1,Fd:1,Af:1,yf:1,b:1,tO:1});var wW; +function ZC(){wW||(wW=new vW);return wW}function xW(){}xW.prototype=new t;xW.prototype.constructor=xW;e=xW.prototype;e.Ne=function(a,b){return 0>=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)};e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.Ek=function(a){a|=0;return a>>31|(-a|0)>>>31|0};e.Nf=function(a){return a|0};e.Jb=function(a){return a};e.Wv=function(a){return-(a|0)|0};e.dp=function(a,b){return(a|0)%Ka(b|0)|0};e.fk=function(a,b){return(a|0)/Ka(b|0)|0}; +e.Ih=function(a,b){return Math.imul(a|0,b|0)};e.wf=function(a,b){return(a|0)-(b|0)|0};e.me=function(a,b){return(a|0)+(b|0)|0};e.I=function(a,b){a|=0;b|=0;return a===b?0:a=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.Ek=function(a){Hv||(Hv=new Gv);var b=ab(a);a=b.h;b=b.l;a=b>>31|((-b|0)+((a|-a|0)>>31)|0)>>>31|0;return r(a,a>>31)};e.Nf=function(a){return ab(a).h};e.Jb=function(a){return r(a,a>>31)};e.Wv=function(a){a=ab(a);var b=a.h,c=-b|0;return r(c,(-a.l|0)+((b|c)>>31)|0)}; +e.dp=function(a,b){a=ab(a);var c=ab(b),d=a.h;a=a.l;b=c.h;var f=c.l;qj();c=a>>31;var g=d^c;d=g-c|0;c=(a^c)+((g&~d)>>>31|0)|0;g=f>>31;var h=b^g;b=h-g|0;f=(f^g)+((h&~b)>>>31|0)|0;if(0===(f|-2097152&b))c=(4294967296*((c>>>0)%(Ka(b)>>>0)|0)+(d>>>0))/b|0,d=d-Math.imul(b,c)|0,b=0;else if(0===(-1073741824&f)){h=(4294967296*(c>>>0)+(d>>>0))/(4294967296*(f>>>0)+(b>>>0));g=h|0;h=h/4294967296|0;var k=65535&b,m=b>>>16|0,n=65535&g,q=g>>>16|0,u=Math.imul(k,n);n=Math.imul(m,n);var v=Math.imul(k,q);k=u+((n+v|0)<< +16)|0;u=(u>>>16|0)+v|0;h=(((Math.imul(b,h)+Math.imul(f,g)|0)+Math.imul(m,q)|0)+(u>>>16|0)|0)+(((65535&u)+n|0)>>>16|0)|0;g=d-k|0;c=(c-h|0)+((~d&k|~(d^k)&g)>>31)|0;0>c?(d=h=g+b|0,b=(c+f|0)+((g&b|(g|b)&~h)>>>31|0)|0):(c===f?g>>>0>=b>>>0:c>>>0>f>>>0)?(d=h=g-b|0,b=(c-f|0)+((~g&b|~(g^b)&h)>>31)|0):(d=g,b=c)}else b=ym(d,c,b,f,!1),d=b.h,b=b.l;0>a?(a=-d|0,a=r(a,(-b|0)+((d|a)>>31)|0)):a=r(d,b);return a};e.fk=function(a,b){var c=ab(a);a=ab(b);b=c.h;c=c.l;var d=a.h;a=a.l;return xm(qj(),b,c,d,a)}; +e.Ih=function(a,b){a=ab(a);var c=ab(b);b=a.h;a=a.l;var d=c.h;c=c.l;var f=65535&b,g=b>>>16|0,h=65535&d,k=d>>>16|0,m=Math.imul(f,h);h=Math.imul(g,h);var n=Math.imul(f,k);f=m+((h+n|0)<<16)|0;m=(m>>>16|0)+n|0;b=(((Math.imul(b,c)+Math.imul(a,d)|0)+Math.imul(g,k)|0)+(m>>>16|0)|0)+(((65535&m)+h|0)>>>16|0)|0;return r(f,b)};e.wf=function(a,b){a=ab(a);b=ab(b);var c=a.h,d=b.h,f=c-d|0;return r(f,(a.l-b.l|0)+((~c&d|~(c^d)&f)>>31)|0)}; +e.me=function(a,b){a=ab(a);b=ab(b);var c=a.h,d=b.h,f=c+d|0;return r(f,(a.l+b.l|0)+((c&d|(c|d)&~f)>>>31|0)|0)};e.I=function(a,b){var c=ab(a);a=c.h;c=c.l;var d=ab(b);b=d.h;d=d.l;return c===d?a===b?0:a>>>0>>0?-1:1:c=this.I(a,b)};e.ye=function(a,b){return qQ(this,a,b)}; +e.Td=function(a,b){return rQ(this,a,b)};e.Me=function(a){return sQ(this,a)};e.Ek=function(a){a|=0;return(a>>31|(-a|0)>>>31|0)<<16>>16};e.Nf=function(a){return a|0};e.Jb=function(a){return a<<16>>16};e.Wv=function(a){return(-(a|0)|0)<<16>>16};e.dp=function(a,b){return((a|0)%Ka(b|0)|0)<<16>>16};e.fk=function(a,b){return((a|0)/Ka(b|0)|0)<<16>>16};e.Ih=function(a,b){return Math.imul(a|0,b|0)<<16>>16};e.wf=function(a,b){return((a|0)-(b|0)|0)<<16>>16};e.me=function(a,b){return((a|0)+(b|0)|0)<<16>>16}; +e.I=function(a,b){return(a|0)-(b|0)|0};z(XC,"scala.math.Numeric$ShortIsIntegral$",{b4:1,a4:1,Ky:1,Ly:1,zf:1,Fd:1,Af:1,yf:1,b:1,wO:1});var WC;function Qd(a,b){cD();return new BF(p(a.Tg),b,p(1),ZC())}function zW(){}zW.prototype=new VV;zW.prototype.constructor=zW;function AW(){}AW.prototype=zW.prototype;function BW(){}BW.prototype=new t;BW.prototype.constructor=BW;BW.prototype.BB=function(){};z(BW,"cats.kernel.instances.UnitAlgebra",{aU:1,b:1,lf:1,eE:1,Rf:1,jE:1,Tf:1,qg:1,xL:1,Ui:1,Ti:1}); +function CW(){}CW.prototype=new pR;CW.prototype.constructor=CW;function DW(){}e=DW.prototype=CW.prototype;e.d=function(a){return pV(this,a)};e.p=function(){var a=Y();return zx(a,this,a.Qy)};e.Ib=function(){return"Set"};e.f=function(){return nO(this)};e.qD=function(a){return this.fj(a)};e.Em=function(a){return BO(this,a)};e.ld=function(a){return pr(this,a)};e.g=function(a){return this.Hc(a)}; +function EW(a,b){if(a===b)return!0;if(b&&b.$classData&&b.$classData.wb.lk)if(a.X()===b.X())try{return a.fj(new Zo((c=>d=>V(W(),c.Yj(d.Y,WL().OI),d.V))(b)))}catch(c){if(c instanceof eI)return!1;throw c;}else return!1;else return!1}function Rd(a){this.Tg=a}Rd.prototype=new t;Rd.prototype.constructor=Rd;e=Rd.prototype;e.ki=function(){return!0};e.zg=function(a){return this.Tg-$a(a)|0};e.$f=function(a){return this.Tg-$a(a)|0};e.f=function(){return""+this.Kw()}; +e.ny=function(){return this.ki()&&this.Dd()===this.Ro()};e.py=function(){return this.ki()&&this.Dd()===this.Kp()};e.Rv=function(){if(this.ki()){var a=this.uf();var b=a.h;a=a.l;var c=this.Dd();b=0===(b^c|a^c>>31)}else b=!1;return b};e.dj=function(){return this.Tg};e.Im=function(){return Math.fround(this.Tg)};e.uf=function(){var a=this.Tg;return r(a,a>>31)};e.Dd=function(){return this.Tg};e.Ro=function(){return this.Tg<<24>>24};e.Kp=function(){return this.Tg<<16>>16};e.oy=function(){return!0};e.p=function(){return this.Tg}; +e.d=function(a){dw||(dw=new cw);return a instanceof Rd&&this.Tg===a.Tg};e.Kw=function(){return p(this.Tg)};z(Rd,"scala.runtime.RichChar",{X9:1,Q9:1,d$:1,KP:1,rC:1,pI:1,oI:1,wJ:1,nh:1,Lb:1,JP:1});function FW(){}FW.prototype=new AW;FW.prototype.constructor=FW;function GW(){}GW.prototype=FW.prototype;function HW(){}HW.prototype=new t;HW.prototype.constructor=HW;e=HW.prototype;e.ke=function(a){return a.p()};e.I=function(a,b){return EU(a,b)};e.Ud=function(a,b){return Yv(W(),a,b)}; +e.Le=function(a,b){return 0b?1:0};e.Ud=function(a,b){return(a|0)===(b|0)}; +e.Le=function(a,b){return(a|0)>(b|0)};e.Pe=function(a,b){a|=0;b|=0;return(a>b?a:b)<<24>>24};z(ZW,"cats.kernel.instances.ByteOrder",{qT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,nT:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,oT:1});function $W(){}$W.prototype=new t;$W.prototype.constructor=$W;e=$W.prototype;e.Pe=function(a,b){return AK(this,a,b)};e.ke=function(a){return $a(a)};e.I=function(a,b){a=$a(a);b=$a(b);return ab?1:0};e.Ud=function(a,b){return $a(a)===$a(b)};e.Le=function(a,b){return $a(a)>$a(b)}; +z($W,"cats.kernel.instances.CharOrder",{tT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,rT:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,sT:1});function aX(){}aX.prototype=new t;aX.prototype.constructor=aX;e=aX.prototype;e.ke=function(a){return a|0};e.I=function(a,b){a|=0;b|=0;return ab?1:0};e.Ud=function(a,b){return(a|0)===(b|0)};e.Le=function(a,b){return(a|0)>(b|0)};e.Pe=function(a,b){a|=0;b|=0;return a>b?a:b}; +z(aX,"cats.kernel.instances.IntOrder",{IT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,FT:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,GT:1});function bX(){}bX.prototype=new t;bX.prototype.constructor=bX;e=bX.prototype;e.ke=function(a){a=ab(a);return a.h^a.l};e.I=function(a,b){var c=ab(a);a=ab(b);b=c.h;c=c.l;var d=a.h;a=a.l;return(c===a?b>>>0>>0:c>>0>d>>>0:c>a)?1:0};e.Ud=function(a,b){a=ab(a);b=ab(b);return 0===(a.h^b.h|a.l^b.l)}; +e.Le=function(a,b){a=ab(a);b=ab(b);var c=a.l,d=b.l;return c===d?a.h>>>0>b.h>>>0:c>d};e.Pe=function(a,b){var c=ab(a);a=c.h;c=c.l;var d=ab(b);b=d.h;d=d.l;return(c===d?a>>>0>b>>>0:c>d)?r(a,c):r(b,d)};z(bX,"cats.kernel.instances.LongOrder",{NT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Ol:1,Mk:1,KT:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,LT:1});function cX(){}cX.prototype=new t;cX.prototype.constructor=cX;e=cX.prototype;e.ke=function(a){return a|0};e.I=function(a,b){a|=0;b|=0;return ab?1:0}; +e.Ud=function(a,b){return(a|0)===(b|0)};e.Le=function(a,b){return(a|0)>(b|0)};e.Pe=function(a,b){a|=0;b|=0;return(a>b?a:b)<<16>>16};z(cX,"cats.kernel.instances.ShortOrder",{RT:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,OT:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,PT:1});function dX(){}dX.prototype=new t;dX.prototype.constructor=dX;e=dX.prototype;e.I=function(){return 0};e.ke=function(){return 0};e.Ud=function(){return!0};e.Le=function(){return!1};e.Pe=function(){}; +z(dX,"cats.kernel.instances.UnitOrder",{dU:1,b:1,ee:1,Fe:1,Ue:1,Sf:1,Mk:1,Ol:1,bU:1,Qn:1,Pn:1,Xp:1,Wp:1,Vp:1,cU:1});function eX(){}eX.prototype=new LW;eX.prototype.constructor=eX;function fX(){}e=fX.prototype=eX.prototype;e.i=function(){return CR(new DR,this)};e.Nb=function(){return FR(new GR,this)};e.he=function(a){return gX(new hX,this,a)};e.je=function(a){return fO(new gO,this,a)};e.ie=function(a){return iO(new jO,this,a)};e.Ib=function(){return"IndexedSeqView"};e.Xd=function(){return new MQ(this)}; +e.u=function(){return mO(this)};e.Qa=function(a){var b=this.q();return b===a?0:b>31;var k=g>>>31|0|g>>31<<1;for(g=(h===k?c>>>0>g<<1>>>0:h>k)?g:c;ff instanceof Eg?new Eg(c.Ca(d,f.ib)):f))}throw new D(a);} +function KX(a,b,c){if(a instanceof Dg)return c.ne(a);if(a instanceof Eg)return c.Vb(b.g(a.ib),new G(d=>new Eg(d)));throw new D(a);}e=Qq.prototype;e.ne=function(a){return new Eg(a)};e.ag=function(a,b){return a instanceof Eg?b.g(a.ib):a};e.Vb=function(a,b){return a instanceof Eg?new Eg(b.g(a.ib)):a};e.Oe=function(a,b,c){return JX(a,b,c)};e.kf=function(a,b,c){return KX(a,b,c)};e.eh=function(a,b,c){if(a instanceof Dg)a=b;else if(a instanceof Eg)a=c.Ca(b,a.ib);else throw new D(a);return a}; +z(Qq,"cats.instances.EitherInstances$$anon$2",{bS:1,b:1,mg:1,Qh:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,EJ:1,Ph:1,Oh:1,Rh:1,IJ:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,Ki:1});function LX(a,b){return a.ob===b?a:new sI(b)}function MX(a,b){b=b.i();for(var c=a.ob;b.n();){var d=b.j(),f=zw(X(),d),g=ds(fs(),f);c=VH(c,d,f,g,0);if(c!==a.ob){if(0===c.ta)return wI().qj;for(;b.n();)if(a=b.j(),d=zw(X(),a),f=ds(fs(),d),WH(c,a,d,f),0===c.ta)return wI().qj;return new sI(c)}}return a}function sI(a){this.ob=a}sI.prototype=new kX; +sI.prototype.constructor=sI;e=sI.prototype;e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.Kb=function(){return wI()};e.E=function(){return this.ob.ta};e.X=function(){return this.ob.ta};e.e=function(){return 0===this.ob.ta};e.i=function(){return this.e()?Lv().da:new lS(this.ob)};e.Hc=function(a){var b=zw(X(),a),c=ds(fs(),b);return this.ob.Wj(a,b,c,0)};function DX(a,b){var c=zw(X(),b),d=ds(fs(),c);return LX(a,QH(a.ob,b,c,d,0))} +function NX(a,b){if(b instanceof sI){if(a.e())return b;var c=cI(a.ob,b.ob,0);return c===b.ob?b:LX(a,c)}if(b instanceof mJ)for(b=new ES(b),c=a.ob;b.n();){var d=b.j(),f=GS(d.Gh),g=ds(fs(),f);c=QH(c,d.Bj,f,g,0);if(c!==a.ob){for(a=ot(R(),nt(R(),g,0));b.n();)d=b.j(),f=GS(d.Gh),g=ds(fs(),f),a=TH(c,d.Bj,f,g,0,a);return new sI(c)}}else for(b=b.i(),c=a.ob;b.n();)if(d=b.j(),f=zw(X(),d),g=ds(fs(),f),c=QH(c,d,f,g,0),c!==a.ob){for(a=ot(R(),nt(R(),g,0));b.n();)d=b.j(),f=zw(X(),d),g=ds(fs(),f),a=TH(c,d,f,g,0,a); +return new sI(c)}return a}e.Oa=function(a){this.ob.Oa(a)};e.qD=function(a){return this.e()||!a.e()&&(a instanceof sI?this.ob.rD(a.ob,0):hs(this,a))};e.d=function(a){if(a instanceof sI){if(this===a)return!0;var b=this.ob;a=a.ob;return null===b?null===a:b.d(a)}return pV(this,a)};e.xd=function(){return"HashSet"};e.p=function(){var a=new kS(this.ob);return zx(Y(),a,Y().Qy)}; +function OX(a,b){if(a.e())return a;if(b instanceof sI)return b.e()?a:0===aI(a.ob,b.ob,0).ta?wI().qj:LX(a,aI(a.ob,b.ob,0));if(b instanceof mJ){for(var c=new ES(b),d=a.ob;c.n();){var f=c.j(),g=GS(f.Gh),h=ds(fs(),g);d=VH(d,f.Bj,g,h,0);if(d!==a.ob){if(0===d.ta)return wI().qj;for(;c.n();)if(a=c.j(),f=GS(a.Gh),g=ds(fs(),f),WH(d,a.Bj,f,g),0===d.ta)return wI().qj;return new sI(d)}}return a}c=b.E();return 0===c?a:c<=a.ob.ta?MX(a,b):PX(a,new Zo(k=>b.Hc(k)))} +function PX(a,b){b=YH(a.ob,b,!0);return b===a.ob?a:0===b.ta?wI().qj:new sI(b)}e.ya=function(a){return jM(this,a)};e.Na=function(a){return this.Vd(qR(new rR,this,a))};e.aw=function(a){return a&&a.$classData&&a.$classData.wb.mk?OX(this,a):MX(this,a)};e.Em=function(a){return NX(this,a)};e.ej=function(a){var b=zw(X(),a),c=ds(fs(),b);return LX(this,VH(this.ob,a,b,c,0))};e.ih=function(a){return DX(this,a)}; +z(sI,"scala.collection.immutable.HashSet",{E6:1,sp:1,Qm:1,S:1,O:1,w:1,R:1,y:1,Q:1,mk:1,an:1,U:1,k:1,Bp:1,Ga:1,Dp:1,sP:1,$O:1,wa:1,hf:1,b:1});function QX(){}QX.prototype=new DW;QX.prototype.constructor=QX;function RX(){}RX.prototype=QX.prototype;QX.prototype.Sa=function(){return this}; +var ZX=function YX(a,b){Q();return Ns(new Os,new Rh(()=>pM(a)===Q().ua?Q().ua:(Q(),Ps(new Os,b.g(a.u()),YX(qM(a),b)))))},aY=function $X(a,b){return pM(b)===Q().ua?Q().ua:(Q(),Ps(new Os,a.u(),(Q(),Ns(new Os,new Rh(()=>$X(qM(a),qM(b)))))))}; +function bY(a,b,c,d,f){b.F=""+b.F+c;if(a.Yd===NC())b.F+="\x3cnot computed\x3e";else if(pM(a)!==Q().ua){c=a.u();b.F=""+b.F+c;var g=a;c=qM(a);if(g!==c){var h=g=c;if(h.Yd!==NC()&&pM(h)!==Q().ua)for(c=qM(c);;)if(g!==c?(h=c,h=h.Yd!==NC()&&pM(h)!==Q().ua):h=!1,h)h=g,b.F=""+b.F+d,h=h.u(),b.F=""+b.F+h,g=qM(g),h=c=qM(c),h.Yd!==NC()&&pM(h)!==Q().ua&&(c=qM(c));else break}h=c;if(h.Yd===NC()||pM(h)===Q().ua){for(;g!==c;)a=g,b.F=""+b.F+d,a=a.u(),b.F=""+b.F+a,g=qM(g);g.Yd===NC()&&(b.F=""+b.F+d,b.F+="\x3cnot computed\x3e")}else{if(g!== +a){for(;a!==c;)a=qM(a),c=qM(c);for(;a=qM(g),a!==c&&(b.F=""+b.F+d,g=g.u(),b.F=""+b.F+g),g=a,g!==c;);}b.F=""+b.F+d;b.F+="\x3ccycle\x3e"}}b.F=""+b.F+f;return b}function Ns(a,b){a.Yd=b===Ts()?null:NC();a.rj=b===Ts()?null:b;return a}function Ps(a,b,c){Ns(a,Ts());a.Yd=b;a.rj=c;return a}function Os(){this.rj=this.Yd=null}Os.prototype=new qX;Os.prototype.constructor=Os;e=Os.prototype;e.Ib=function(){return"LinearSeq"};e.q=function(){return uO(this)};e.Qa=function(a){return 0>a?1:AO(this,a)}; +e.my=function(a){return vO(this,a)};e.J=function(a){return wO(this,a)};e.ll=function(a){return xO(this,a)};e.mj=function(a){return yO(this,a)};e.bg=function(a,b){return zO(this,a,b)};function pM(a){for(;;){if(a.Yd!==NC())return null===a.rj?Q().ua:a;var b=a;if(b.Yd===NC()){if(b.rj===$s())throw ag(new bg,"LazyList evaluation depends on its own result (self-reference); see docs for more info");var c=b.rj;b.rj=$s();try{var d=pM(c.za())}finally{b.rj=c}b.rj=d.rj;b.Yd=d.Yd}}} +e.e=function(){return pM(this)===Q().ua};e.E=function(){return this.Yd!==NC()&&pM(this)===Q().ua?0:-1};e.u=function(){if(pM(this)===Q().ua)throw new Cs("head of empty lazy list");return this.Yd};function qM(a){if(pM(a)===Q().ua)throw Vh("tail of empty lazy list");return a.rj}e.i=function(){return this.Yd!==NC()&&pM(this)===Q().ua?Lv().da:new gP(this)};e.Oa=function(a){for(var b=this;;)if(pM(b)!==Q().ua)a.g(b.u()),b=qM(b);else break}; +e.Bg=function(a,b){for(var c=this;;){if(pM(c)===Q().ua)return a;var d=qM(c);a=b.Ca(a,c.u());c=d}};e.xd=function(){return"LazyList"};function cY(a,b){Q();return Ns(new Os,new Rh(()=>{if(pM(a)===Q().ua){var c=b.za();return c instanceof Os?c:0===c.E()?Q().ua:tM(Q(),c.i())}Q();return Ps(new Os,a.u(),cY(qM(a),b))}))}function dY(a,b){return a.Yd!==NC()&&pM(a)===Q().ua?(Q(),Ps(new Os,b,Q().ua)):cY(a,new Rh(()=>new vz(b)))} +e.rl=function(a){if(pM(this)===Q().ua)throw Vh("empty.reduceLeft");for(var b=this.u(),c=qM(this);pM(c)!==Q().ua;)b=a.Ca(b,c.u()),c=qM(c);return b};function eY(a,b){return a.Yd!==NC()&&pM(a)===Q().ua?Q().ua:ZX(a,b)}function fY(a,b){return a.Yd!==NC()&&pM(a)===Q().ua?Q().ua:oM(Q(),a,b)}function gY(a,b){return 0>=b?a:a.Yd!==NC()&&pM(a)===Q().ua?Q().ua:(Q(),Ns(new Os,new Rh(()=>{for(var c=a,d=b;0=a?this:this.Yd!==NC()&&pM(this)===Q().ua?Q().ua:sM(Q(),this,a)};e.Cb=function(a){return fY(this,a)}; +e.Ra=function(a){return eY(this,a)};e.nd=function(a){Q();return Ps(new Os,a,this)};e.pa=function(a){return dY(this,a)};e.A=function(){return qM(this)};e.Kb=function(){return Q()};z(Os,"scala.collection.immutable.LazyList",{L6:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,hz:1,iw:1,Wy:1,iz:1,b:1}); +function hY(a,b,c,d,f){b.F=""+b.F+c;if(!a.e()){c=a.u();b.F=""+b.F+c;c=a;if(a.Ik()){var g=a.A();if(c!==g&&(c=g,g.Ik()))for(g=g.A();c!==g&&g.Ik();){b.F=""+b.F+d;var h=c.u();b.F=""+b.F+h;c=c.A();g=g.A();g.Ik()&&(g=g.A())}if(g.Ik()){for(h=0;a!==g;)a=a.A(),g=g.A(),h=1+h|0;c===g&&0a?1:AO(this,a)};e.my=function(a){return vO(this,a)};e.J=function(a){return wO(this,a)};e.ll=function(a){return xO(this,a)}; +e.mj=function(a){return yO(this,a)};e.bg=function(a,b){return zO(this,a,b)};e.xd=function(){return"Stream"};e.Oa=function(a){for(var b=this;!b.e();)a.g(b.u()),b=b.A()};e.Bg=function(a,b){for(var c=this;;){if(c.e())return a;var d=c.A();a=b.Ca(a,c.u());c=d}};function jY(a,b){return a.e()?wM(Mv(),b.za()):new zM(a.u(),new Rh(()=>jY(a.A(),b)))}e.rl=function(a){if(this.e())throw Vh("empty.reduceLeft");for(var b=this.u(),c=this.A();!c.e();)b=a.Ca(b,c.u()),c=c.A();return b}; +function kY(a,b){return new zM(b,new Rh(()=>a))}function lY(a,b){return a.e()?AM():new zM(b.g(a.u()),new Rh(()=>lY(a.A(),b)))}function mY(a,b){if(a.e())return AM();var c=new fE(a);for(a=wM(Mv(),b.g(c.Gc.u()));!c.Gc.e()&&a.e();)c.Gc=c.Gc.A(),c.Gc.e()||(a=wM(Mv(),b.g(c.Gc.u())));return c.Gc.e()?AM():jY(a,new Rh(()=>mY(c.Gc.A(),b)))}e.rf=function(a,b,c,d){this.wH();hY(this,a.Hb,b,c,d);return a};e.f=function(){return hY(this,KK("Stream"),"(",", ",")").F};e.g=function(a){return wO(this,a|0)}; +e.sf=function(a){return vO(this,a|0)};e.Cb=function(a){return mY(this,a)};e.Ra=function(a){return lY(this,a)};e.nd=function(a){return kY(this,a)};e.Kb=function(){return Mv()};function GC(a){this.Jf=a}GC.prototype=new qX;GC.prototype.constructor=GC;e=GC.prototype;e.Kn=function(){return this};e.Dm=function(a){return xX(this,a)};e.Ib=function(){return"IndexedSeq"};e.i=function(){return CR(new DR,new CX(this.Jf))};e.Nb=function(){return FR(new GR,new CX(this.Jf))};e.Xd=function(){return new MQ(this)}; +e.nd=function(a){return cO(this,a)};e.Na=function(a){return eO(this,a)};e.ya=function(a){return hO(this,a)};e.Ra=function(a){return kO(this,a)};e.u=function(){return mO(this)};e.Qa=function(a){var b=this.Jf.length;return b===a?0:bb?f:f-b|0;f=ff?0:f;gR(this.Jf,f,a,b);return f}return Fd(this,a,b,c)};e.mj=function(a){return a instanceof GC?this.Jf===a.Jf:yX(this,a)};e.xd=function(){return"WrappedString"};e.Bm=function(){return 2147483647};e.d=function(a){return a instanceof GC?this.Jf===a.Jf:XV(this,a)};e.Kb=function(){return Kv()};e.Vd=function(a){return JM(LM(),a)};e.Xj=function(a){return JM(LM(),a)}; +e.g=function(a){return p(this.Jf.charCodeAt(a|0))};e.J=function(a){return p(this.Jf.charCodeAt(a))};z(GC,"scala.collection.immutable.WrappedString",{q8:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,b:1});function bD(a,b){a.lD=b;return a}function VC(){this.lD=null}VC.prototype=new t;VC.prototype.constructor=VC;e=VC.prototype;e.Rd=function(a){return yU(this,a)};e.hd=function(a){return LL(this,a)};e.nd=function(a){return NR(this,a)}; +e.pa=function(a){return Lg(this,a)};e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.Kn=function(){return this};e.Dm=function(a){return xX(this,a)};e.mj=function(a){return yX(this,a)};e.Bm=function(){return Ks().VC};e.i=function(){return CR(new DR,new oX(this))};e.Nb=function(){return FR(new GR,new oX(this))};e.Na=function(a){return eO(this,a)};e.u=function(){return mO(this)};e.Qa=function(a){var b=this.q();return b===a?0:bk=>g.Ca(h,k))(c,f)));d=jc(Ob(),d,f)}return d} +function uY(a,b,c){return a.e()?(Ke(),new Nb(Ob().Qf)):lg(b,new G(d=>tY(a,d,c)))}e=UT.prototype;e.eh=function(a,b,c){return a.Bg(b,c)};e.Vb=function(a,b){return sz(a,b)};e.kf=function(a,b,c){if(a.e())a=c.ne(Ob().Qf);else if(MW(c))a=Mb(Vb(),a.i(),b,c);else{var d=Ob(),f=KP(cr(),ow(Qg(),new x([])));MP(f,a.i());a=YT(d,new BX(f),b,c)}return a};e.ne=function(a){Ob();return new $b(a)};e.ag=function(a,b){var c=Ob().Qf;for(a=a.i();a.n();){var d=b.g(a.j());c=jc(Ob(),c,d)}return c}; +e.Oe=function(a,b,c){return uY(a,b,c)};z(UT,"cats.data.ChainInstances$$anon$3",{kR:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,Ej:1,Kk:1,Ml:1,Nn:1,Ph:1,Oh:1,Rh:1,Li:1,Ki:1});function vY(){}vY.prototype=new t;vY.prototype.constructor=vY;function wY(a,b,c){return pM(b)===Q().ua?Q().ua:fY(a,new G(d=>eY(b,new G(f=>c.Ca(d,f)))))}function xY(a,b,c){return pM(a)===Q().ua?(Ke(),new Nb(Q().ua)):lg(b,new G(d=>wY(a,d,c)))} +function yY(a,b,c,d){return kg(new Nb(b),new G(f=>pM(f)===Q().ua?c:d.Ca(f.u(),(Ke(),new $J(new pg((g=>()=>yY(a,qM(g),c,d))(f)))))))}function zY(a,b,c,d){return yY(a,b,new UJ(new pg(()=>d.ne(Q().ua))),new Qb((f,g)=>d.Oe(c.g(f),g,new Qb((h,k)=>{Q();Qs||(Qs=new Ls);return Ms(new pg((m=>()=>m)(k)),new pg(()=>h))})))).Kh()}e=vY.prototype;e.ne=function(a){return FH(Q(),ow(Qg(),new x([a])))};e.Vb=function(a,b){return eY(a,b)};e.ag=function(a,b){return fY(a,b)};e.Oe=function(a,b,c){return xY(a,b,c)}; +e.eh=function(a,b,c){return a.Bg(b,c)};e.kf=function(a,b,c){return zY(this,a,b,c)};z(vY,"cats.instances.LazyListInstances$$anon$1",{kS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,Ej:1,Kk:1,Ml:1,Nn:1,Ph:1,Oh:1,Rh:1,Li:1,Ki:1});function aR(){this.SD=null;this.SD=(Ke(),new Nb(N()));N()}aR.prototype=new t;aR.prototype.constructor=aR; +function AY(a,b,c){if(b.e())return N();for(var d=null,f=null;a!==N();){var g=a.u();g=((q,u)=>v=>q.Ca(u,v))(c,g);if(b===N())g=N();else{for(var h=b.u(),k=h=new J(g(h),N()),m=b.A();m!==N();){var n=m.u();n=new J(g(n),N());k=k.Z=n;m=m.A()}g=h}for(g=g.i();g.n();)h=new J(g.j(),N()),null===f?d=h:f.Z=h,f=h;a=a.A()}return null===d?N():d}function BY(a,b,c,d){return b.e()?a.SD:lg(c,new G(f=>AY(b,f,d)))} +function CY(a,b,c){if(a.e())return c.ne(N());if(MW(c))return lg(Mb(Vb(),a,b,c),new G(g=>g.ub()));var d=Ob(),f=KP(cr(),ow(Qg(),new x([])));MP(f,a);return c.Vb(YT(d,new BX(f),b,c),new G(g=>g.ub()))}e=aR.prototype;e.ne=function(a){return new J(a,N())};e.Vb=function(a,b){return KL(a,b)};e.ag=function(a,b){return DY(a,b)};e.Oe=function(a,b,c){return BY(this,a,b,c)};e.eh=function(a,b,c){return FN(a,b,c)};e.kf=function(a,b,c){return CY(a,b,c)}; +z(aR,"cats.instances.ListInstances$$anon$1",{mS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,Ej:1,Kk:1,Ml:1,Nn:1,Ph:1,Oh:1,Rh:1,Li:1,Ki:1});function IN(){this.WD=null;this.WD=(Ke(),new Nb(vP().Cf.Sd()))}IN.prototype=new t;IN.prototype.constructor=IN;function EY(a,b,c){return b.e()?vP().Cf.Sd():a.Cb(new G(d=>b.Ra(new G(f=>c.Ca(d,f)))))}function FY(a,b,c,d){return b.e()?a.WD:lg(c,new G(f=>EY(b,f,d)))} +function GY(a,b,c){return MW(c)?lg(Mb(Vb(),a,b,c),new G(d=>d.ub())):c.Vb(YT(Ob(),a.Kn(),b,c),new G(d=>d.ub()))}e=IN.prototype;e.ne=function(a){var b=vP();a=ow(Qg(),new x([a]));return b.Cf.wg(a)};e.Vb=function(a,b){return a.Ra(b)};e.ag=function(a,b){return a.Cb(b)};e.Oe=function(a,b,c){return FY(this,a,b,c)};e.eh=function(a,b,c){return a.Bg(b,c)};e.kf=function(a,b,c){return GY(a,b,c)}; +z(IN,"cats.instances.SeqInstances$$anon$1",{rS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,Ph:1,Oh:1,ng:1,Lh:1,Rh:1,Ej:1,Kk:1,Ml:1,Nn:1,Li:1,Ki:1});function HY(){}HY.prototype=new t;HY.prototype.constructor=HY;function IY(a,b,c){return b.e()?AM():mY(a,new G(d=>lY(b,new G(f=>c.Ca(d,f)))))}function JY(a,b,c){return a.e()?(Ke(),new Nb(AM())):lg(b,new G(d=>IY(a,d,c)))} +function KY(a,b,c,d){return kg(new Nb(b),new G(f=>f.e()?c:d.Ca(f.u(),(Ke(),new $J(new pg((g=>()=>KY(a,g.Dz(),c,d))(f)))))))}function LY(a,b,c,d){return KY(a,b,new UJ(new pg(()=>d.ne(AM()))),new Qb((f,g)=>d.Oe(c.g(f),g,new Qb((h,k)=>new zM(h,new pg((m=>()=>m)(k))))))).Kh()}e=HY.prototype;e.ne=function(a){return wM(Mv(),ow(Qg(),new x([a])))};e.Vb=function(a,b){return lY(a,b)};e.ag=function(a,b){return mY(a,b)};e.Oe=function(a,b,c){return JY(a,b,c)};e.eh=function(a,b,c){return a.Bg(b,c)}; +e.kf=function(a,b,c){return LY(this,a,b,c)};z(HY,"cats.instances.StreamInstances$$anon$1",{tS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,Ej:1,Kk:1,Ml:1,Nn:1,Ph:1,Oh:1,Rh:1,Li:1,Ki:1});function bR(){this.aE=null;this.aE=(Ke(),new Nb((Nv(),bv())))}bR.prototype=new t;bR.prototype.constructor=bR;function MY(a,b,c){if(fM(b))return Nv(),bv();Nv();var d=new hI;for(a=a.i();a.n();){var f=a.j();iI(d,b.vf(new G(((g,h)=>k=>g.Ca(h,k))(c,f))))}return d.lh()} +function NY(a,b,c,d){return fM(b)?a.aE:lg(c,new G(f=>MY(b,f,d)))}function OY(a,b,c){return MW(c)?lg(Mb(Vb(),a,b,c),new G(d=>d.Ln())):c.Vb(YT(Ob(),a,b,c),new G(d=>d.Ln()))}e=bR.prototype;e.ne=function(a){return tz(Nv(),ow(Qg(),new x([a])))};e.Vb=function(a,b){return a.vf(b)};e.ag=function(a,b){return iM(a,b)};e.Oe=function(a,b,c){return NY(this,a,b,c)};e.eh=function(a,b,c){return js(a,b,c)};e.kf=function(a,b,c){return OY(a,b,c)}; +z(bR,"cats.instances.VectorInstances$$anon$1",{vS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,Ph:1,Oh:1,ng:1,Lh:1,Rh:1,Ej:1,Kk:1,Ml:1,Nn:1,Li:1,Ki:1});function PY(a,b,c){var d=c&(a.Nd.a.length-1|0),f=a.Nd.a[d];if(null===f)a.Nd.a[d]=new uv(b,c,null);else{for(var g=null,h=f;null!==h&&h.Gh<=c;){if(h.Gh===c&&V(W(),b,h.Bj))return!1;g=h;h=h.ce}null===g?a.Nd.a[d]=new uv(b,c,f):g.ce=new uv(b,c,g.ce)}a.Il=1+a.Il|0;return!0} +function QY(a,b){var c=a.Nd.a.length;a.jD=Ma(b*a.yz);if(0===a.Il)a.Nd=new (B(vv).P)(b);else{a.Nd=ll(H(),a.Nd,b);for(var d=new uv(null,0,null),f=new uv(null,0,null);c>Math.clz32(a)&a)<<1;return 1073741824>a?a:1073741824}function lJ(a,b,c){a.yz=c;a.Nd=new (B(vv).P)(RY(b));a.jD=Ma(a.Nd.a.length*a.yz);a.Il=0;return a}function pO(){var a=new mJ;lJ(a,16,.75);return a}function mJ(){this.yz=0;this.Nd=null;this.Il=this.jD=0}mJ.prototype=new RX;mJ.prototype.constructor=mJ;e=mJ.prototype;e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.X=function(){return this.Il}; +function GS(a){return a^(a>>>16|0)}e.Hc=function(a){var b=GS(zw(X(),a)),c=this.Nd.a[b&(this.Nd.a.length-1|0)];if(null===c)a=null;else a:for(;;){if(b===c.Gh&&V(W(),a,c.Bj)){a=c;break a}if(null===c.ce||c.Gh>b){a=null;break a}c=c.ce}return null!==a};e.Fc=function(a){a=RY(Ma((1+a|0)/this.yz));a>this.Nd.a.length&&QY(this,a)};function qO(a,b){(1+a.Il|0)>=a.jD&&QY(a,a.Nd.a.length<<1);return PY(a,b,GS(zw(X(),b)))} +function kJ(a,b){YI(a,b,0);if(b instanceof sI)return b.ob.HB(new pJ((d,f)=>{PY(a,d,GS(f|0))})),a;if(b instanceof mJ){for(b=new ES(b);b.n();){var c=b.j();PY(a,c.Bj,c.Gh)}return a}return nD(a,b)}e.i=function(){return new DS(this)};e.Kb=function(){nJ||(nJ=new fJ);return nJ};e.E=function(){return this.Il};e.e=function(){return 0===this.Il};e.Oa=function(a){for(var b=this.Nd.a.length,c=0;c>24&&0===(1&this.hg)<<24>>24){a:{cD();var a=this.oc,b=this.Id,c=this.$a,d=this.vn;var f=this.W;var g=f.Jb(0),h=0>f.I(a,b),k=0n:m>31;d=1+(((((n-m|0)^d)-d|0)>>>0)/(Ka((q^d)-d|0)>>>0)|0)|0;f=f?0:0=n:m<=n;if(0===q)throw Qi("step cannot be 0.");g=q>>31;d=((n-m|0)^g)-g|0;g=(q^g)-g|0;c=(d>>>0)/(Ka(g)>>>0)|0;d=Math.imul(g,c)!==d?1+c|0:c;f=f?0:0f.I(q,a)||!k&&0f.I(a,b)!==h?d=d&&V(W(),a,b)?f.me(q,f.Jb(2)):f.me(q,n):(h=SC(f.fk(f.wf(b,a),c),f,m),g=V(W(),h,g)?a:f.me(a,f.Ih(h,c)),d=f.me(q,f.me(h,!d&&V(W(),g,b)?n:f.Jb(2)))),d=SC(d,f,m));f=f.Nf(d)}}this.pz=f;this.hg=(1|this.hg)<<24>>24}return this.pz}; +e.e=function(){0===(2&this.hg)<<24>>24&&0===(2&this.hg)<<24>>24&&(this.oz=0this.W.I(this.oc,this.Id)&&0>this.W.I(this.$a,this.W.Jb(0))||rQ(this.W,this.oc,this.Id)&&!this.vn,this.hg=(2|this.hg)<<24>>24);return this.oz};e.tf=function(){return this.e()?N().Ov():UY(this,this.q()-1|0)};e.u=function(){return this.e()?N().Ov():this.oc}; +e.J=function(a){if(0>a||a>=this.q())throw O(new P,a+" is out of bounds (min 0, max "+(this.q()-1|0)+")");return UY(this,a)};e.Oa=function(a){for(var b=0,c=this.oc;b>24&&0===(4&this.hg)<<24>>24&&(this.nz=jE(this),this.hg=(4|this.hg)<<24>>24);return this.nz};e.Bm=function(){return 2147483647}; +e.d=function(a){return a instanceof XY?xX(a,this)&&this.q()===a.q()&&(this.e()||V(W(),this.oc,a.oc)&&V(W(),this.tf(),a.tf())):XV(this,a)};e.f=function(){var a=this.e()?"empty ":"",b=this.vn?"to":"until",c=V(W(),this.$a,1)?"":" by "+this.$a;return a+"NumericRange "+this.oc+" "+b+" "+this.Id+c};e.xd=function(){return"NumericRange"};e.Kb=function(){return Kv()}; +e.Na=function(a){if(0>=a||this.e())a=this;else{var b=0this.W.I(this.Id,this.oc)&&V(W(),this.W.Ek(this.$a),Ev(new PD(this.W,this.W.Jb(1))));if(rQ(this.W,this.oc,this.Id)||0>=a||!b)b=1<=a;else if(rQ(this.W,this.W.Ek(this.oc),this.W.Ek(this.Id)))b=VY(this,this),b=WY(this,b)?a>=this.W.Nf(b):qQ(this.W,this.W.Jb(a),b);else{var c=this.W.dp(this.oc,this.$a);c=(b=rQ(this.W,c,this.W.Jb(0)))?Ev(new PD(this.W,this.$a)):c;if(0>this.W.I(this.oc,this.W.Jb(0)))if(b){b= +this.W.me(c,this.W.Ih(this.$a,this.W.Jb(2)));var d=new uo((cD(),new ZY(this.oc,c,this.$a,this.W)),this.To(b,this.Id,this.$a),2)}else d=new uo((cD(),new ZY(this.oc,c,this.$a,this.W)),this.To(this.W.me(c,this.$a),this.Id,this.$a),1);else d=b?new uo(this.To(this.W.Ih(this.$a,this.W.Jb(2)),this.Id,this.$a),(cD(),new BF(this.oc,Ev(new PD(this.W,this.$a)),this.$a,this.W)),2):new uo(this.To(this.W.me(c,this.W.Ih(this.$a,this.W.Jb(2))),this.Id,this.$a),(cD(),new BF(this.oc,c,this.$a,this.W)),2);c=d.bh;b= +d.ch;var f=d.dh|0;if(null===c||null===b)throw new D(d);d=f|0;c=VY(this,c);b=VY(this,b);b=WY(this,c)&&WY(this,b)?((a-this.W.Nf(c)|0)-d|0)>=this.W.Nf(b):qQ(this.W,this.W.wf(this.W.wf(this.W.Jb(a),c),this.W.Jb(d)),b)}b?(a=this.Id,cD(),a=new ZY(a,a,this.$a,this.W)):a=this.To(UY(this,a),this.Id,this.$a)}return a};e.g=function(a){return this.J(a|0)}; +e.A=function(){return this.e()?$Y(N()):this.vn?new BF(Dv(new PD(this.W,this.oc),this.$a),this.Id,this.$a,this.W):new ZY(Dv(new PD(this.W,this.oc),this.$a),this.Id,this.$a,this.W)};function RR(){}RR.prototype=new qX;RR.prototype.constructor=RR;function aZ(){}e=aZ.prototype=RR.prototype;e.Xj=function(a){return QR(qw(),a,this.Qc())};e.xf=function(){return qw().ap(this.Qc())};e.Rd=function(a){return yU(this,a)};e.Cb=function(a){return iM(this,a)};e.Kn=function(){return this}; +e.Dm=function(a){return xX(this,a)};e.mj=function(a){return yX(this,a)};e.Ib=function(){return"IndexedSeq"};e.Nb=function(){return FR(new GR,new oX(this))};e.Xd=function(){return new MQ(this)};e.u=function(){return mO(this)};e.Qa=function(a){var b=this.q();return b===a?0:bb?f:f-b|0;f=ff?0:f;0=Pi(Nh(),this.Ee()))return this;var b=this.Ee(),c=this.q();rb(ob,sb(ea(b).ba).ba)?b=nl(H(),b,c,l(B(ob))):(c=new x(c),Zr($r(),b,0,c,0,Pi(Nh(),b)),b=c);cl(H(),b,a);return new sw(b)};e.Vd=function(a){return QR(qw(),a,this.Qc())};e.hd=function(a){return this.de(a)};e.A=function(){qw();Dd();var a=this.Ee();if(0===Pi(Nh(),a))throw Vh("tail of empty array");a=zr(Dd(),a,1,Pi(Nh(),a));return pw(0,a)}; +e.ya=function(a){if(0>=a)a=this;else{qw();Dd();var b=this.Ee();a=zr(Dd(),b,0,Pi(Nh(),b)-(0=a)a=this;else{qw();Dd();var b=this.Ee();a=zr(Dd(),b,a,Pi(Nh(),b));a=pw(0,a)}return a};e.pa=function(a){return this.Pc(a)};e.nd=function(a){return this.Tc(a)};e.Ra=function(a){for(var b=new x(this.q()),c=0;cv=>!!n.g(v)!==q?jI(u,v):void 0)(b,c,h)));return h.lh()}if(0===f)return bv();h=new x(f);a.r.G(0,h,0,d);for(k=1+d|0;d!==f;)0!==(1<!!b.g(n)!==c?jI(m,n):void 0));return m.lh()}return a}e.xd=function(){return"Vector"};e.hc=function(a,b,c){return this.i().hc(a,b,c)};e.Ln=function(){return this};e.Bm=function(){return Nv().hJ};e.yd=function(a){return Fs(Hs(),a,this.q()-1|0)};e.u=function(){if(0===this.r.a.length)throw new Cs("empty.head");return this.r.a[0]}; +e.Oa=function(a){for(var b=this.Ii(),c=0;c>>31|0)|0)>>1,g=c-f|0,h=g>>31;ov(d,((1+f|0)-((g^h)-h|0)|0)-1|0,this.Ji(c),a);c=1+c|0}};e.ya=function(a){return eZ(this,0,this.q()-(0b?f:f-b|0;f=ff?0:f;0=this.pi.a.length?this:a===Vr()?(a=this.pi.H(),Xr(Yr(),a,a.a.length,Vr()),new YR(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Lr(this.pi)}; +e.Pc=function(a){if("boolean"===typeof a){a=!!a;var b=this.pi;$r();var c=1+b.a.length|0;rb(wb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new cb(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new YR(c)}return RR.prototype.Pc.call(this,a)};e.Tc=function(a){if("boolean"===typeof a){a=!!a;var b=this.pi,c=new cb(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new YR(c)}return RR.prototype.Tc.call(this,a)};e.Gv=function(a){return this.pi.a[a]};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)}; +e.hd=function(a){return this.de(a)};e.g=function(a){return this.Gv(a|0)};e.J=function(a){return this.Gv(a)};e.Qc=function(){return rx()};e.Ee=function(){return this.pi};z(YR,"scala.collection.immutable.ArraySeq$ofBoolean",{l6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function VR(a){this.qi=a}VR.prototype=new aZ;VR.prototype.constructor=VR;e=VR.prototype;e.q=function(){return this.qi.a.length};e.Hv=function(a){return this.qi.a[a]}; +e.p=function(){var a=Y();return Dx(a,this.qi,a.Uc)};e.d=function(a){return a instanceof VR?jl(H(),this.qi,a.qi):XV(this,a)};e.de=function(a){return 1>=this.qi.a.length?this:a===Tr()?(a=this.qi.H(),al(H(),a),new VR(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Jr(this.qi)}; +e.Pc=function(a){if(Wa(a)){a|=0;var b=this.qi;$r();var c=1+b.a.length|0;rb(Ab,sb(ea(b).ba).ba)?c=bs(b,c):(c=new eb(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new VR(c)}return RR.prototype.Pc.call(this,a)};e.Tc=function(a){if(Wa(a)){a|=0;var b=this.qi,c=new eb(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new VR(c)}return RR.prototype.Tc.call(this,a)};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)};e.hd=function(a){return this.de(a)}; +e.g=function(a){return this.Hv(a|0)};e.J=function(a){return this.Hv(a)};e.Qc=function(){return px()};e.Ee=function(){return this.qi};z(VR,"scala.collection.immutable.ArraySeq$ofByte",{m6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function uw(a){this.rh=a}uw.prototype=new aZ;uw.prototype.constructor=uw;e=uw.prototype;e.q=function(){return this.rh.a.length};e.Iv=function(a){return this.rh.a[a]}; +e.p=function(){var a=Y();return Ex(a,this.rh,a.Uc)};e.d=function(a){return a instanceof uw?il(H(),this.rh,a.rh):XV(this,a)};e.de=function(a){return 1>=this.rh.a.length?this:a===Sr()?(a=this.rh.H(),Zk(H(),a),new uw(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Ir(this.rh)}; +e.Pc=function(a){if(a instanceof ba){a=$a(a);var b=this.rh;$r();var c=1+b.a.length|0;rb(zb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new db(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,p(a));return new uw(c)}return RR.prototype.Pc.call(this,a)};e.Tc=function(a){if(a instanceof ba){a=$a(a);var b=this.rh,c=new db(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new uw(c)}return RR.prototype.Tc.call(this,a)};e.rf=function(a,b,c,d){return(new vS(this.rh)).rf(a,b,c,d)};e.nd=function(a){return this.Tc(a)}; +e.pa=function(a){return this.Pc(a)};e.hd=function(a){return this.de(a)};e.g=function(a){return p(this.Iv(a|0))};e.J=function(a){return p(this.Iv(a))};e.Qc=function(){return ox()};e.Ee=function(){return this.rh};z(uw,"scala.collection.immutable.ArraySeq$ofChar",{n6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function SR(a){this.ok=a}SR.prototype=new aZ;SR.prototype.constructor=SR;e=SR.prototype;e.q=function(){return this.ok.a.length}; +e.p=function(){var a=Y();return Kx(a,this.ok,a.Uc)};e.d=function(a){if(a instanceof SR){var b=this.ok;a=a.ok;if(b===a)return!0;if(b.a.length===a.a.length){for(var c=0;c=b.a.length}return!1}return XV(this,a)};e.i=function(){return new Fr(this.ok)}; +e.Pc=function(a){if("number"===typeof a){a=+a;var b=this.ok;$r();var c=1+b.a.length|0;rb(Gb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new jb(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new SR(c)}return RR.prototype.Pc.call(this,a)};e.Tc=function(a){if("number"===typeof a){a=+a;var b=this.ok,c=new jb(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new SR(c)}return RR.prototype.Tc.call(this,a)};e.Cv=function(a){return this.ok.a[a]};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)}; +e.g=function(a){return this.Cv(a|0)};e.J=function(a){return this.Cv(a)};e.Qc=function(){return lx()};e.Ee=function(){return this.ok};z(SR,"scala.collection.immutable.ArraySeq$ofDouble",{o6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function UR(a){this.pk=a}UR.prototype=new aZ;UR.prototype.constructor=UR;e=UR.prototype;e.q=function(){return this.pk.a.length};e.p=function(){var a=Y();return Lx(a,this.pk,a.Uc)}; +e.d=function(a){if(a instanceof UR){var b=this.pk;a=a.pk;if(b===a)return!0;if(b.a.length===a.a.length){for(var c=0;c=b.a.length}return!1}return XV(this,a)};e.i=function(){return new Hr(this.pk)};e.Pc=function(a){if(oa(a)){a=Math.fround(a);var b=this.pk;$r();var c=1+b.a.length|0;rb(Fb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new ib(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new UR(c)}return RR.prototype.Pc.call(this,a)}; +e.Tc=function(a){if(oa(a)){a=Math.fround(a);var b=this.pk,c=new ib(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new UR(c)}return RR.prototype.Tc.call(this,a)};e.Dv=function(a){return this.pk.a[a]};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)};e.g=function(a){return this.Dv(a|0)};e.J=function(a){return this.Dv(a)};e.Qc=function(){return nx()};e.Ee=function(){return this.pk}; +z(UR,"scala.collection.immutable.ArraySeq$ofFloat",{p6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function tw(a){this.ri=a}tw.prototype=new aZ;tw.prototype.constructor=tw;e=tw.prototype;e.q=function(){return this.ri.a.length};e.p=function(){var a=Y();return Mx(a,this.ri,a.Uc)};e.d=function(a){return a instanceof tw?gl(H(),this.ri,a.ri):XV(this,a)}; +e.de=function(a){return 1>=this.ri.a.length?this:a===Qr()?(a=this.ri.H(),Rk(H(),a),new tw(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Er(this.ri)};e.Pc=function(a){if(ia(a)){a|=0;var b=this.ri;$r();var c=1+b.a.length|0;rb(Cb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new y(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new tw(c)}return RR.prototype.Pc.call(this,a)}; +e.Tc=function(a){if(ia(a)){a|=0;var b=this.ri,c=new y(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new tw(c)}return RR.prototype.Tc.call(this,a)};e.Ev=function(a){return this.ri.a[a]};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)};e.hd=function(a){return this.de(a)};e.g=function(a){return this.Ev(a|0)};e.J=function(a){return this.Ev(a)};e.Qc=function(){return fq()};e.Ee=function(){return this.ri}; +z(tw,"scala.collection.immutable.ArraySeq$ofInt",{q6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function TR(a){this.si=a}TR.prototype=new aZ;TR.prototype.constructor=TR;e=TR.prototype;e.q=function(){return this.si.a.length>>>1|0};e.p=function(){var a=Y();return Nx(a,this.si,a.Uc)};e.d=function(a){return a instanceof TR?fl(H(),this.si,a.si):XV(this,a)}; +e.de=function(a){return 1>=(this.si.a.length>>>1|0)?this:a===Rr()?(a=this.si.H(),Vk(H(),a),new TR(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Gr(this.si)};e.Pc=function(a){if(a instanceof ca){var b=ab(a);a=b.h;b=b.l;var c=this.si;$r();var d=1+(c.a.length>>>1|0)|0;rb(Db,sb(ea(c).ba).ba)?d=bs(c,d):(d=new hb(d),Zr($r(),c,0,d,0,c.a.length>>>1|0));ms(Qg(),d,c.a.length>>>1|0,r(a,b));return new TR(d)}return RR.prototype.Pc.call(this,a)}; +e.Tc=function(a){if(a instanceof ca){var b=ab(a);a=b.h;b=b.l;var c=this.si,d=new hb(1+(c.a.length>>>1|0)|0),f=d.a;f[0]=a;f[1]=b;Zr($r(),c,0,d,1,c.a.length>>>1|0);return new TR(d)}return RR.prototype.Tc.call(this,a)};e.Fv=function(a){var b=this.si.a;a<<=1;return r(b[a],b[a+1|0])};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)};e.hd=function(a){return this.de(a)};e.g=function(a){return this.Fv(a|0)};e.J=function(a){return this.Fv(a)};e.Qc=function(){return mx()};e.Ee=function(){return this.si}; +z(TR,"scala.collection.immutable.ArraySeq$ofLong",{r6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function sw(a){this.ti=a}sw.prototype=new aZ;sw.prototype.constructor=sw;e=sw.prototype;e.Qc=function(){return Ar(Br(),sb(ea(this.ti).ba))};e.q=function(){return this.ti.a.length};e.J=function(a){return this.ti.a[a]};e.p=function(){var a=Y();return Ax(a,this.ti,a.Uc)}; +e.d=function(a){return a instanceof sw?BC($r(),this.ti,a.ti):XV(this,a)};function iZ(a,b){if(1>=a.ti.a.length)return a;a=a.ti.H();cl(H(),a,b);return new sw(a)}e.i=function(){return Cr(new Dr,this.ti)};e.hd=function(a){return iZ(this,a)};e.de=function(a){return iZ(this,a)};e.g=function(a){return this.J(a|0)};e.Ee=function(){return this.ti}; +z(sw,"scala.collection.immutable.ArraySeq$ofRef",{s6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function WR(a){this.ui=a}WR.prototype=new aZ;WR.prototype.constructor=WR;e=WR.prototype;e.q=function(){return this.ui.a.length};e.Jv=function(a){return this.ui.a[a]};e.p=function(){var a=Y();return Ox(a,this.ui,a.Uc)};e.d=function(a){return a instanceof WR?hl(H(),this.ui,a.ui):XV(this,a)}; +e.de=function(a){return 1>=this.ui.a.length?this:a===Ur()?(a=this.ui.H(),Xk(H(),a),new WR(a)):RR.prototype.de.call(this,a)};e.i=function(){return new Kr(this.ui)};e.Pc=function(a){if(Xa(a)){a|=0;var b=this.ui;$r();var c=1+b.a.length|0;rb(Bb,sb(ea(b).ba).ba)?c=bs(b,c):(c=new gb(c),Zr($r(),b,0,c,0,b.a.length));ms(Qg(),c,b.a.length,a);return new WR(c)}return RR.prototype.Pc.call(this,a)}; +e.Tc=function(a){if(Xa(a)){a|=0;var b=this.ui,c=new gb(1+b.a.length|0);c.a[0]=a;Zr($r(),b,0,c,1,b.a.length);return new WR(c)}return RR.prototype.Tc.call(this,a)};e.nd=function(a){return this.Tc(a)};e.pa=function(a){return this.Pc(a)};e.hd=function(a){return this.de(a)};e.g=function(a){return this.Jv(a|0)};e.J=function(a){return this.Jv(a)};e.Qc=function(){return qx()};e.Ee=function(){return this.ui}; +z(WR,"scala.collection.immutable.ArraySeq$ofShort",{t6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1});function ZR(a){this.tp=a}ZR.prototype=new aZ;ZR.prototype.constructor=ZR;e=ZR.prototype;e.q=function(){return this.tp.a.length};e.p=function(){var a=Y();return Px(a,this.tp,a.Uc)};e.d=function(a){return a instanceof ZR?this.tp.a.length===a.tp.a.length:XV(this,a)};e.i=function(){return new Mr(this.tp)};e.g=function(){}; +e.J=function(){};e.Qc=function(){return SD()};e.Ee=function(){return this.tp};z(ZR,"scala.collection.immutable.ArraySeq$ofUnit",{u6:1,nk:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,jk:1,b:1}); +function AL(a,b){a:for(;;){if(a.e()){b=N();break a}var c=a.u(),d=a.A();if(!0!==!!b.g(c)){b:for(;;){if(d.e()){b=a;break b}c=d.u();if(!0!==!!b.g(c))d=d.A();else{var f=a;c=d;a=new J(f.u(),N());f=f.A();for(d=a;f!==c;){var g=new J(f.u(),N());d=d.Z=g;f=f.A()}for(f=c=c.A();!c.e();){g=c.u();if(!0===!!b.g(g)){for(;f!==c;)g=new J(f.u(),N()),d=d.Z=g,f=f.A();f=c.A()}c=c.A()}f.e()||(d.Z=f);b=a;break b}}break a}a=d}return b}function kE(){}kE.prototype=new qX;kE.prototype.constructor=kE;function jZ(){} +e=jZ.prototype=kE.prototype;e.Rd=function(a){return yU(this,a)};e.hd=function(a){return LL(this,a)};e.i=function(){return new Jg(this)};e.pa=function(a){return Lg(this,a)};e.ya=function(a){return jM(this,a)};e.Ib=function(){return"LinearSeq"};e.my=function(a){return vO(this,a)};e.J=function(a){return wO(this,a)};e.Bg=function(a,b){return FN(this,a,b)};e.mj=function(a){return yO(this,a)};e.bg=function(a,b){return zO(this,a,b)};e.Dg=function(){return Ih()}; +function Kg(a,b){if(a.e())return b;if(b.e())return a;var c=new J(b.u(),a),d=c;for(b=b.A();!b.e();){var f=new J(b.u(),a);d=d.Z=f;b=b.A()}return c}e.e=function(){return this===N()};function rg(a,b){if(b instanceof kE)return Kg(a,b);if(0===b.E())return a;if(b instanceof Jh&&a.e())return b.ub();b=b.i();if(b.n()){for(var c=new J(b.j(),a),d=c;b.n();){var f=new J(b.j(),a);d=d.Z=f}return c}return a} +function KL(a,b){if(a===N())return N();var c=new J(b.g(a.u()),N()),d=c;for(a=a.A();a!==N();){var f=new J(b.g(a.u()),N());d=d.Z=f;a=a.A()}return c}function DY(a,b){for(var c=null,d=null;a!==N();){for(var f=b.g(a.u()).i();f.n();){var g=new J(f.j(),N());null===d?c=g:d.Z=g;d=g}a=a.A()}return null===c?N():c}e.Oa=function(a){for(var b=this;!b.e();)a.g(b.u()),b=b.A()};function re(a){for(var b=N();!a.e();)b=new J(a.u(),b),a=a.A();return b}e.q=function(){for(var a=this,b=0;!a.e();)b=1+b|0,a=a.A();return b}; +e.Qa=function(a){if(0>a)a=1;else a:for(var b=this,c=0;;){if(c===a){a=b.e()?0:1;break a}if(b.e()){a=-1;break a}c=1+c|0;b=b.A()}return a};e.ll=function(a){for(var b=this;!b.e();){if(a.g(b.u()))return!0;b=b.A()}return!1};e.tf=function(){if(this.e())throw new Cs("List.last");for(var a=this,b=this.A();!b.e();)a=b,b=b.A();return a.u()};e.xd=function(){return"List"};e.ub=function(){return this}; +e.d=function(a){var b;if(a instanceof kE)a:for(b=this;;){if(b===a){b=!0;break a}var c=b.e(),d=a.e();if(c||d||!V(W(),b.u(),a.u())){b=c&&d;break a}b=b.A();a=a.A()}else b=XV(this,a);return b};e.g=function(a){return wO(this,a|0)};e.sf=function(a){return vO(this,a|0)};e.Na=function(a){a:for(var b=this;;){if(0>=a||b.e())break a;a=a-1|0;b=b.A()}return b};e.Cb=function(a){return DY(this,a)};e.Ra=function(a){return KL(this,a)};e.nd=function(a){return new J(a,this)};e.Kb=function(){return Ih()}; +function kZ(){this.r=null}kZ.prototype=new bZ;kZ.prototype.constructor=kZ;function lZ(){}lZ.prototype=kZ.prototype;function eZ(a,b,c){b=0=this.yi.a.length}return!1}return fZ.prototype.d.call(this,a)};e.i=function(){return new Fr(this.yi)};e.Cv=function(a){return this.yi.a[a]};e.g=function(a){return this.Cv(a|0)};e.J=function(a){return this.Cv(a)};e.Qc=function(){return lx()};e.xg=function(){return this.yi}; +z(sS,"scala.collection.mutable.ArraySeq$ofDouble",{G8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1});function uS(a){this.zi=a}uS.prototype=new gZ;uS.prototype.constructor=uS;e=uS.prototype;e.q=function(){return this.zi.a.length};e.p=function(){var a=Y();return Lx(a,this.zi,a.Uc)}; +e.d=function(a){if(a instanceof uS){a=a.zi;if(this.zi===a)return!0;if(this.zi.a.length===a.a.length){for(var b=0;b=this.zi.a.length}return!1}return fZ.prototype.d.call(this,a)};e.i=function(){return new Hr(this.zi)};e.Dv=function(a){return this.zi.a[a]};e.g=function(a){return this.Dv(a|0)};e.J=function(a){return this.Dv(a)};e.Qc=function(){return nx()};e.xg=function(){return this.zi}; +z(uS,"scala.collection.mutable.ArraySeq$ofFloat",{H8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1});function BG(a){this.En=a}BG.prototype=new gZ;BG.prototype.constructor=BG;e=BG.prototype;e.q=function(){return this.En.a.length};e.p=function(){var a=Y();return Mx(a,this.En,a.Uc)};e.d=function(a){return a instanceof BG?gl(H(),this.En,a.En):fZ.prototype.d.call(this,a)};e.i=function(){return new Er(this.En)};e.Ev=function(a){return this.En.a[a]}; +e.g=function(a){return this.Ev(a|0)};e.J=function(a){return this.Ev(a)};e.Qc=function(){return fq()};e.xg=function(){return this.En};z(BG,"scala.collection.mutable.ArraySeq$ofInt",{I8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1});function tS(a){this.Fn=a}tS.prototype=new gZ;tS.prototype.constructor=tS;e=tS.prototype;e.q=function(){return this.Fn.a.length>>>1|0};e.p=function(){var a=Y();return Nx(a,this.Fn,a.Uc)}; +e.d=function(a){return a instanceof tS?fl(H(),this.Fn,a.Fn):fZ.prototype.d.call(this,a)};e.i=function(){return new Gr(this.Fn)};e.Fv=function(a){var b=this.Fn.a;a<<=1;return r(b[a],b[a+1|0])};e.g=function(a){return this.Fv(a|0)};e.J=function(a){return this.Fv(a)};e.Qc=function(){return mx()};e.xg=function(){return this.Fn};z(tS,"scala.collection.mutable.ArraySeq$ofLong",{J8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1}); +function FC(a){this.Fl=a}FC.prototype=new gZ;FC.prototype.constructor=FC;e=FC.prototype;e.Qc=function(){return Ar(Br(),sb(ea(this.Fl).ba))};e.q=function(){return this.Fl.a.length};e.J=function(a){return this.Fl.a[a]};e.p=function(){var a=Y();return Ax(a,this.Fl,a.Uc)};e.d=function(a){return a instanceof FC?BC($r(),this.Fl,a.Fl):fZ.prototype.d.call(this,a)};e.i=function(){return Cr(new Dr,this.Fl)};e.g=function(a){return this.J(a|0)};e.xg=function(){return this.Fl}; +z(FC,"scala.collection.mutable.ArraySeq$ofRef",{K8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1});function xS(a){this.Gn=a}xS.prototype=new gZ;xS.prototype.constructor=xS;e=xS.prototype;e.q=function(){return this.Gn.a.length};e.Jv=function(a){return this.Gn.a[a]};e.p=function(){var a=Y();return Ox(a,this.Gn,a.Uc)};e.d=function(a){return a instanceof xS?hl(H(),this.Gn,a.Gn):fZ.prototype.d.call(this,a)};e.i=function(){return new Kr(this.Gn)}; +e.g=function(a){return this.Jv(a|0)};e.J=function(a){return this.Jv(a)};e.Qc=function(){return qx()};e.xg=function(){return this.Gn};z(xS,"scala.collection.mutable.ArraySeq$ofShort",{L8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1});function zS(a){this.Hp=a}zS.prototype=new gZ;zS.prototype.constructor=zS;e=zS.prototype;e.q=function(){return this.Hp.a.length};e.p=function(){var a=Y();return Px(a,this.Hp,a.Uc)}; +e.d=function(a){return a instanceof zS?this.Hp.a.length===a.Hp.a.length:fZ.prototype.d.call(this,a)};e.i=function(){return new Mr(this.Hp)};e.g=function(){};e.J=function(){};e.Qc=function(){return SD()};e.xg=function(){return this.Hp};z(zS,"scala.collection.mutable.ArraySeq$ofUnit",{M8:1,Ak:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,b:1}); +function mZ(a,b,c,d){(1+a.Fh|0)>=a.Gw&&nZ(a,a.kb.a.length<<1);return oZ(a,b,c,d,d&(a.kb.a.length-1|0))}function pZ(a,b,c){(1+a.Fh|0)>=a.Gw&&nZ(a,a.kb.a.length<<1);var d=zw(X(),b);d^=d>>>16|0;oZ(a,b,c,d,d&(a.kb.a.length-1|0))}function oZ(a,b,c,d,f){var g=a.kb.a[f];if(null===g)a.kb.a[f]=new rv(b,d,c,null);else{for(var h=null,k=g;null!==k&&k.Ck<=d;){if(k.Ck===d&&V(W(),b,k.Hn))return k.Di=c,null;h=k;k=k.be}null===h?a.kb.a[f]=new rv(b,d,c,g):h.be=new rv(b,d,c,h.be)}a.Fh=1+a.Fh|0;return null} +function nZ(a,b){if(0>b)throw ag(new bg,"new HashMap table size "+b+" exceeds maximum");var c=a.kb.a.length;a.Gw=Ma(b*a.xz);if(0===a.Fh)a.kb=new (B(tv).P)(b);else{a.kb=ll(H(),a.kb,b);for(var d=new rv(null,0,null,null),f=new rv(null,0,null,null);c>Math.clz32(a)&a)<<1;return 1073741824>a?a:1073741824}function bJ(a,b,c){a.xz=c;a.kb=new (B(tv).P)(qZ(b));a.Gw=Ma(a.kb.a.length*a.xz);a.Fh=0;return a}function cJ(){this.xz=0;this.kb=null;this.Fh=this.Gw=0}cJ.prototype=new sY;cJ.prototype.constructor=cJ;e=cJ.prototype;e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.X=function(){return this.Fh}; +e.Hc=function(a){var b=zw(X(),a);b^=b>>>16|0;var c=this.kb.a[b&(this.kb.a.length-1|0)];return null!==(null===c?null:sv(c,a,b))};e.Fc=function(a){a=qZ(Ma((1+a|0)/this.xz));a>this.kb.a.length&&nZ(this,a)}; +function aJ(a,b){YI(a,b,0);if(b instanceof mI)return b.Be.IB(new qJ((d,f,g)=>{g|=0;mZ(a,d,f,g^(g>>>16|0))})),a;if(b instanceof cJ){for(b=WO(b);b.n();){var c=b.j();mZ(a,c.Hn,c.Di,c.Ck)}return a}return b&&b.$classData&&b.$classData.wb.zP?(b.Cg(new pJ((d,f)=>{var g=zw(X(),d);return mZ(a,d,f,g^(g>>>16|0))})),a):nD(a,b)}e.i=function(){return 0===this.Fh?Lv().da:new AS(this)};function WO(a){return 0===a.Fh?Lv().da:new BS(a)} +e.hh=function(a){var b=zw(X(),a);b^=b>>>16|0;var c=this.kb.a[b&(this.kb.a.length-1|0)];a=null===c?null:sv(c,a,b);return null===a?F():new E(a.Di)};e.g=function(a){var b=zw(X(),a);b^=b>>>16|0;var c=this.kb.a[b&(this.kb.a.length-1|0)];b=null===c?null:sv(c,a,b);return null===b?KR(a):b.Di}; +e.Yj=function(a,b){if(ea(this)!==l(rZ)){a=this.hh(a);if(a instanceof E)b=a.zc;else if(F()===a)b=b.za();else throw new D(a);return b}var c=zw(X(),a);c^=c>>>16|0;var d=this.kb.a[c&(this.kb.a.length-1|0)];a=null===d?null:sv(d,a,c);return null===a?b.za():a.Di}; +function Qh(a,b,c){if(ea(a)!==l(rZ)){var d=a.hh(b);if(d instanceof E)a=d.zc;else if(F()===d)c=c.za(),pZ(a,b,c),a=c;else throw new D(d);return a}d=zw(X(),b);d^=d>>>16|0;var f=d&(a.kb.a.length-1|0),g=a.kb.a[f];g=null===g?null:sv(g,b,d);if(null!==g)return g.Di;g=a.kb;c=c.za();(1+a.Fh|0)>=a.Gw&&nZ(a,a.kb.a.length<<1);oZ(a,b,c,d,g===a.kb?f:d&(a.kb.a.length-1|0));return c}e.E=function(){return this.Fh};e.e=function(){return 0===this.Fh}; +e.Cg=function(a){for(var b=this.kb.a.length,c=0;cc.Ca(a,d)))};e.eh=function(a,b,c){return c.Ca(b,a)}; +z(sZ,"cats.package$$anon$1",{fU:1,b:1,mg:1,Qh:1,pg:1,og:1,Nh:1,Mh:1,Ph:1,Oh:1,ng:1,Lh:1,Rh:1,Li:1,Hz:1,Gz:1,vD:1,wD:1,uD:1,xD:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,JD:1,ID:1,VP:1});function tZ(a,b,c,d){a.B=c;a.C=d;a.r=b}function cZ(){this.B=this.r=null;this.C=0}cZ.prototype=new lZ;cZ.prototype.constructor=cZ;function uZ(){}uZ.prototype=cZ.prototype;function dZ(a,b){for(var c=a.Ii(),d=1;d>>31|0)|0)>>1,h=d-g|0,k=h>>31;ov(f,((1+g|0)-((h^k)-k|0)|0)-1|0,a.Ji(d),b);d=1+d|0}} +function cv(a){this.r=a}cv.prototype=new lZ;cv.prototype.constructor=cv;e=cv.prototype;e.J=function(a){if(0<=a&&athis.r.a.length)return new cv(kv(T(),this.r,a));var b=this.r,c=T().Ua,d=new x(1);d.a[0]=a;return new dv(b,32,c,d,33)}; +e.Gg=function(a){var b=this.r.a.length;if(32>b)return new cv(mv(T(),a,this.r));var c=new x(1);c.a[0]=a;return new dv(c,1,T().Ua,this.r,1+b|0)};e.vf=function(a){return new cv(pv(T(),this.r,a))};e.Hh=function(a,b){return new cv(yl(H(),this.r,a,b))};e.Vg=function(){if(1===this.r.a.length)return bv();var a=this.r;return new cv(yl(H(),a,1,a.a.length))};e.Ii=function(){return 1};e.Ji=function(){return this.r};e.A=function(){return this.Vg()};e.Ra=function(a){return this.vf(a)};e.nd=function(a){return this.Gg(a)}; +e.pa=function(a){return this.we(a)};e.g=function(a){a|=0;if(0<=a&&a=b)a=yZ(this.Ba);else if(b>=Nt(Bu(),this.Ta))a=this;else{a=new EM;var c=Bu();b=xt(Tt(c,this.Ta,b));a=FM(a,b,this.Ba)}return a}; +e.Na=function(a){if(0>=a)var b=this;else if(a>=Nt(Bu(),this.Ta))b=yZ(this.Ba);else{b=new EM;var c=Bu();a=xt(Rt(c,this.Ta,a));b=FM(b,a,this.Ba)}return b};z(EM,"scala.collection.immutable.TreeSet",{b8:1,sp:1,Qm:1,S:1,O:1,w:1,R:1,y:1,Q:1,mk:1,an:1,U:1,k:1,Bp:1,Ga:1,Dp:1,Q7:1,WI:1,T5:1,XO:1,S5:1,S7:1,X7:1,X5:1,$O:1,wa:1,sP:1,hf:1,b:1});function zZ(){this.B=this.r=null;this.C=0;tZ(this,T().dD,T().dD,0)}zZ.prototype=new uZ;zZ.prototype.constructor=zZ;e=zZ.prototype;e.Jk=function(a){throw this.yd(a);}; +e.we=function(a){var b=new x(1);b.a[0]=a;return new cv(b)};e.Gg=function(a){var b=new x(1);b.a[0]=a;return new cv(b)};e.vf=function(){return this};e.Vg=function(){throw Vh("empty.tail");};e.Hh=function(){return this};e.Ii=function(){return 0};e.Ji=function(){return null};e.d=function(a){return this===a||!(a instanceof zP)&&XV(this,a)};e.yd=function(a){return O(new P,a+" is out of bounds (empty vector)")};e.A=function(){return this.Vg()};e.Ra=function(){return this};e.nd=function(a){return this.Gg(a)}; +e.pa=function(a){return this.we(a)};e.g=function(a){throw this.yd(a|0);};e.J=function(a){throw this.yd(a);};z(zZ,"scala.collection.immutable.Vector0$",{g8:1,pw:1,Fp:1,Ep:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,hf:1,b:1});var AZ;function bv(){AZ||(AZ=new zZ);return AZ}function dv(a,b,c,d,f){this.B=this.r=null;this.C=0;this.Ce=b;this.Kd=c;tZ(this,a,d,f)}dv.prototype=new uZ;dv.prototype.constructor=dv;e=dv.prototype; +e.J=function(a){if(0<=a&&a>>5|0,a=this.Ce){var c=a-this.Ce|0;a=c>>>5|0;c&=31;if(athis.B.a.length)return a=kv(T(),this.B,a),new dv(this.r,this.Ce,this.Kd,a,1+this.C|0);if(30>this.Kd.a.length){var b=lv(T(),this.Kd,this.B),c=new x(1);c.a[0]=a;return new dv(this.r,this.Ce,b,c,1+this.C|0)}b=this.r;c=this.Ce;var d=this.Kd,f=this.Ce,g=T().Lc,h=this.B,k=new (B(B(ob)).P)(1);k.a[0]=h;h=new x(1);h.a[0]=a;return new ev(b,c,d,960+f|0,g,k,h,1+this.C|0)}; +e.Gg=function(a){if(32>this.Ce){var b=mv(T(),a,this.r);return new dv(b,1+this.Ce|0,this.Kd,this.B,1+this.C|0)}if(30>this.Kd.a.length)return b=new x(1),b.a[0]=a,a=nv(T(),this.r,this.Kd),new dv(b,1,a,this.B,1+this.C|0);b=new x(1);b.a[0]=a;a=this.r;var c=new (B(B(ob)).P)(1);c.a[0]=a;return new ev(b,1,c,1+this.Ce|0,T().Lc,this.Kd,this.B,1+this.C|0)};e.vf=function(a){var b=pv(T(),this.r,a),c=qv(T(),2,this.Kd,a);a=pv(T(),this.B,a);return new dv(b,this.Ce,c,a,this.C)}; +e.Hh=function(a,b){a=new $u(a,b);av(a,1,this.r);av(a,2,this.Kd);av(a,1,this.B);return a.lh()};e.Vg=function(){if(1>>5|0,b>>10|0;var c=31&(b>>>5|0);b&=31;return a=this.Zd?(b=a-this.Zd|0,this.$d.a[b>>>5|0].a[31&b]):this.r.a[a]}throw this.yd(a);}; +e.Jk=function(a,b){if(0<=a&&a=this.Ld){var c=a-this.Ld|0,d=c>>>10|0;a=31&(c>>>5|0);c&=31;if(d= +this.Zd)return c=a-this.Zd|0,a=c>>>5|0,c&=31,d=this.$d.H(),f=d.a[a].H(),f.a[c]=b,d.a[a]=f,new ev(this.r,this.Zd,d,this.Ld,this.Vc,this.ed,this.B,this.C);c=this.r.H();c.a[a]=b;return new ev(c,this.Zd,this.$d,this.Ld,this.Vc,this.ed,this.B,this.C)}throw this.yd(a);}; +e.we=function(a){if(32>this.B.a.length)return a=kv(T(),this.B,a),new ev(this.r,this.Zd,this.$d,this.Ld,this.Vc,this.ed,a,1+this.C|0);if(31>this.ed.a.length){var b=lv(T(),this.ed,this.B),c=new x(1);c.a[0]=a;return new ev(this.r,this.Zd,this.$d,this.Ld,this.Vc,b,c,1+this.C|0)}if(30>this.Vc.a.length){b=lv(T(),this.Vc,lv(T(),this.ed,this.B));c=T().Ua;var d=new x(1);d.a[0]=a;return new ev(this.r,this.Zd,this.$d,this.Ld,b,c,d,1+this.C|0)}b=this.r;c=this.Zd;d=this.$d;var f=this.Ld,g=this.Vc,h=this.Ld,k= +T().jf,m=lv(T(),this.ed,this.B),n=new (B(B(B(ob))).P)(1);n.a[0]=m;m=T().Ua;var q=new x(1);q.a[0]=a;return new fv(b,c,d,f,g,30720+h|0,k,n,m,q,1+this.C|0)}; +e.Gg=function(a){if(32>this.Zd){var b=mv(T(),a,this.r);return new ev(b,1+this.Zd|0,this.$d,1+this.Ld|0,this.Vc,this.ed,this.B,1+this.C|0)}if(1024>this.Ld)return b=new x(1),b.a[0]=a,a=nv(T(),this.r,this.$d),new ev(b,1,a,1+this.Ld|0,this.Vc,this.ed,this.B,1+this.C|0);if(30>this.Vc.a.length){b=new x(1);b.a[0]=a;a=T().Ua;var c=nv(T(),nv(T(),this.r,this.$d),this.Vc);return new ev(b,1,a,1,c,this.ed,this.B,1+this.C|0)}b=new x(1);b.a[0]=a;a=T().Ua;c=nv(T(),this.r,this.$d);var d=new (B(B(B(ob))).P)(1);d.a[0]= +c;return new fv(b,1,a,1,d,1+this.Ld|0,T().jf,this.Vc,this.ed,this.B,1+this.C|0)};e.vf=function(a){var b=pv(T(),this.r,a),c=qv(T(),2,this.$d,a),d=qv(T(),3,this.Vc,a),f=qv(T(),2,this.ed,a);a=pv(T(),this.B,a);return new ev(b,this.Zd,c,this.Ld,d,f,a,this.C)};e.Hh=function(a,b){a=new $u(a,b);av(a,1,this.r);av(a,2,this.$d);av(a,3,this.Vc);av(a,2,this.ed);av(a,1,this.B);return a.lh()}; +e.Vg=function(){if(1>>10|0;var c=31&(a>>>5|0);a&=31;return b=this.Zd?(a=b-this.Zd|0,this.$d.a[a>>>5|0].a[31&a]):this.r.a[b]}throw this.yd(b);};z(ev,"scala.collection.immutable.Vector3",{j8:1,pw:1,Fp:1,Ep:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,hf:1,b:1}); +function fv(a,b,c,d,f,g,h,k,m,n,q){this.B=this.r=null;this.C=0;this.rd=b;this.fd=c;this.sd=d;this.gd=f;this.Wc=g;this.ic=h;this.rc=k;this.qc=m;tZ(this,a,n,q)}fv.prototype=new uZ;fv.prototype.constructor=fv;e=fv.prototype; +e.J=function(a){if(0<=a&&a>>15|0;var c=31&(b>>>10|0),d=31&(b>>>5|0);b&=31;return a=this.sd?(b=a-this.sd|0,this.gd.a[b>>>10|0].a[31&(b>>>5|0)].a[31&b]):a>=this.rd?(b=a-this.rd|0,this.fd.a[b>>>5|0].a[31&b]):this.r.a[a]}throw this.yd(a);}; +e.Jk=function(a,b){if(0<=a&&a=this.Wc){var c=a-this.Wc|0,d=c>>>15|0,f=31&(c>>>10|0);a=31&(c>>>5|0);c&=31;if(d=this.sd)return f=a-this.sd|0,a=f>>>10|0,c=31&(f>>>5|0),f&=31,d=this.gd.H(),g=d.a[a].H(),h=g.a[c].H(),h.a[f]=b,g.a[c]=h,d.a[a]=g,new fv(this.r,this.rd,this.fd,this.sd,d,this.Wc,this.ic,this.rc,this.qc,this.B,this.C); +if(a>=this.rd)return c=a-this.rd|0,a=c>>>5|0,c&=31,f=this.fd.H(),d=f.a[a].H(),d.a[c]=b,f.a[a]=d,new fv(this.r,this.rd,f,this.sd,this.gd,this.Wc,this.ic,this.rc,this.qc,this.B,this.C);c=this.r.H();c.a[a]=b;return new fv(c,this.rd,this.fd,this.sd,this.gd,this.Wc,this.ic,this.rc,this.qc,this.B,this.C)}throw this.yd(a);}; +e.we=function(a){if(32>this.B.a.length)return a=kv(T(),this.B,a),new fv(this.r,this.rd,this.fd,this.sd,this.gd,this.Wc,this.ic,this.rc,this.qc,a,1+this.C|0);if(31>this.qc.a.length){var b=lv(T(),this.qc,this.B),c=new x(1);c.a[0]=a;return new fv(this.r,this.rd,this.fd,this.sd,this.gd,this.Wc,this.ic,this.rc,b,c,1+this.C|0)}if(31>this.rc.a.length){b=lv(T(),this.rc,lv(T(),this.qc,this.B));c=T().Ua;var d=new x(1);d.a[0]=a;return new fv(this.r,this.rd,this.fd,this.sd,this.gd,this.Wc,this.ic,b,c,d,1+this.C| +0)}if(30>this.ic.a.length){b=lv(T(),this.ic,lv(T(),this.rc,lv(T(),this.qc,this.B)));c=T().Lc;d=T().Ua;var f=new x(1);f.a[0]=a;return new fv(this.r,this.rd,this.fd,this.sd,this.gd,this.Wc,b,c,d,f,1+this.C|0)}b=this.r;c=this.rd;d=this.fd;f=this.sd;var g=this.gd,h=this.Wc,k=this.ic,m=this.Wc,n=T().Dl,q=lv(T(),this.rc,lv(T(),this.qc,this.B)),u=new (B(B(B(B(ob)))).P)(1);u.a[0]=q;q=T().Lc;var v=T().Ua,w=new x(1);w.a[0]=a;return new gv(b,c,d,f,g,h,k,983040+m|0,n,u,q,v,w,1+this.C|0)}; +e.Gg=function(a){if(32>this.rd){var b=mv(T(),a,this.r);return new fv(b,1+this.rd|0,this.fd,1+this.sd|0,this.gd,1+this.Wc|0,this.ic,this.rc,this.qc,this.B,1+this.C|0)}if(1024>this.sd)return b=new x(1),b.a[0]=a,a=nv(T(),this.r,this.fd),new fv(b,1,a,1+this.sd|0,this.gd,1+this.Wc|0,this.ic,this.rc,this.qc,this.B,1+this.C|0);if(32768>this.Wc){b=new x(1);b.a[0]=a;a=T().Ua;var c=nv(T(),nv(T(),this.r,this.fd),this.gd);return new fv(b,1,a,1,c,1+this.Wc|0,this.ic,this.rc,this.qc,this.B,1+this.C|0)}if(30>this.ic.a.length){b= +new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;var d=nv(T(),nv(T(),nv(T(),this.r,this.fd),this.gd),this.ic);return new fv(b,1,a,1,c,1,d,this.rc,this.qc,this.B,1+this.C|0)}b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;d=nv(T(),nv(T(),this.r,this.fd),this.gd);var f=new (B(B(B(B(ob)))).P)(1);f.a[0]=d;return new gv(b,1,a,1,c,1,f,1+this.Wc|0,T().Dl,this.ic,this.rc,this.qc,this.B,1+this.C|0)}; +e.vf=function(a){var b=pv(T(),this.r,a),c=qv(T(),2,this.fd,a),d=qv(T(),3,this.gd,a),f=qv(T(),4,this.ic,a),g=qv(T(),3,this.rc,a),h=qv(T(),2,this.qc,a);a=pv(T(),this.B,a);return new fv(b,this.rd,c,this.sd,d,this.Wc,f,g,h,a,this.C)};e.Hh=function(a,b){a=new $u(a,b);av(a,1,this.r);av(a,2,this.fd);av(a,3,this.gd);av(a,4,this.ic);av(a,3,this.rc);av(a,2,this.qc);av(a,1,this.B);return a.lh()}; +e.Vg=function(){if(1>>15|0;var c=31&(a>>>10|0),d=31&(a>>>5|0);a&=31;return b=this.sd?(a=b-this.sd|0,this.gd.a[a>>>10|0].a[31&(a>>>5|0)].a[31&a]):b>=this.rd?(a=b-this.rd|0,this.fd.a[a>>>5|0].a[31&a]):this.r.a[b]}throw this.yd(b);}; +z(fv,"scala.collection.immutable.Vector4",{k8:1,pw:1,Fp:1,Ep:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,hf:1,b:1});function gv(a,b,c,d,f,g,h,k,m,n,q,u,v,w){this.B=this.r=null;this.C=0;this.Jc=b;this.sc=c;this.Kc=d;this.tc=f;this.Ec=g;this.uc=h;this.jc=k;this.Ab=m;this.Fb=n;this.Eb=q;this.Db=u;tZ(this,a,v,w)}gv.prototype=new uZ;gv.prototype.constructor=gv;e=gv.prototype; +e.J=function(a){if(0<=a&&a>>20|0;var c=31&(b>>>15|0),d=31&(b>>>10|0),f=31&(b>>>5|0);b&=31;return a=this.Ec?(b=a-this.Ec|0,this.uc.a[b>>>15|0].a[31&(b>>>10|0)].a[31&(b>>>5|0)].a[31&b]):a>=this.Kc?(b=a-this.Kc|0,this.tc.a[b>>>10|0].a[31&(b>>>5|0)].a[31&b]):a>=this.Jc? +(b=a-this.Jc|0,this.sc.a[b>>>5|0].a[31&b]):this.r.a[a]}throw this.yd(a);}; +e.Jk=function(a,b){if(0<=a&&a=this.jc){var c=a-this.jc|0,d=c>>>20|0,f=31&(c>>>15|0),g=31&(c>>>10|0);a=31&(c>>>5|0);c&=31;if(d=this.Ec)return f=a-this.Ec|0,a=f>>>15|0,c=31&(f>>>10|0),g=31&(f>>>5|0),f&=31,d=this.uc.H(),h=d.a[a].H(),k=h.a[c].H(),m=k.a[g].H(),m.a[f]=b,k.a[g]=m,h.a[c]=k,d.a[a]=h,new gv(this.r,this.Jc,this.sc,this.Kc,this.tc,this.Ec,d,this.jc,this.Ab,this.Fb,this.Eb,this.Db,this.B,this.C);if(a>=this.Kc)return g=a-this.Kc|0,a=g>>>10|0,c=31&(g>>>5|0),g&=31,f=this.tc.H(), +d=f.a[a].H(),h=d.a[c].H(),h.a[g]=b,d.a[c]=h,f.a[a]=d,new gv(this.r,this.Jc,this.sc,this.Kc,f,this.Ec,this.uc,this.jc,this.Ab,this.Fb,this.Eb,this.Db,this.B,this.C);if(a>=this.Jc)return c=a-this.Jc|0,a=c>>>5|0,c&=31,g=this.sc.H(),f=g.a[a].H(),f.a[c]=b,g.a[a]=f,new gv(this.r,this.Jc,g,this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,this.Fb,this.Eb,this.Db,this.B,this.C);c=this.r.H();c.a[a]=b;return new gv(c,this.Jc,this.sc,this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,this.Fb,this.Eb,this.Db,this.B, +this.C)}throw this.yd(a);}; +e.we=function(a){if(32>this.B.a.length)return a=kv(T(),this.B,a),new gv(this.r,this.Jc,this.sc,this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,this.Fb,this.Eb,this.Db,a,1+this.C|0);if(31>this.Db.a.length){var b=lv(T(),this.Db,this.B),c=new x(1);c.a[0]=a;return new gv(this.r,this.Jc,this.sc,this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,this.Fb,this.Eb,b,c,1+this.C|0)}if(31>this.Eb.a.length){b=lv(T(),this.Eb,lv(T(),this.Db,this.B));c=T().Ua;var d=new x(1);d.a[0]=a;return new gv(this.r,this.Jc,this.sc, +this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,this.Fb,b,c,d,1+this.C|0)}if(31>this.Fb.a.length){b=lv(T(),this.Fb,lv(T(),this.Eb,lv(T(),this.Db,this.B)));c=T().Lc;d=T().Ua;var f=new x(1);f.a[0]=a;return new gv(this.r,this.Jc,this.sc,this.Kc,this.tc,this.Ec,this.uc,this.jc,this.Ab,b,c,d,f,1+this.C|0)}if(30>this.Ab.a.length){b=lv(T(),this.Ab,lv(T(),this.Fb,lv(T(),this.Eb,lv(T(),this.Db,this.B))));c=T().jf;d=T().Lc;f=T().Ua;var g=new x(1);g.a[0]=a;return new gv(this.r,this.Jc,this.sc,this.Kc,this.tc, +this.Ec,this.uc,this.jc,b,c,d,f,g,1+this.C|0)}b=this.r;c=this.Jc;d=this.sc;f=this.Kc;g=this.tc;var h=this.Ec,k=this.uc,m=this.jc,n=this.Ab,q=this.jc,u=T().sz,v=lv(T(),this.Fb,lv(T(),this.Eb,lv(T(),this.Db,this.B))),w=new (B(B(B(B(B(ob))))).P)(1);w.a[0]=v;v=T().jf;var A=T().Lc,K=T().Ua,M=new x(1);M.a[0]=a;return new hv(b,c,d,f,g,h,k,m,n,31457280+q|0,u,w,v,A,K,M,1+this.C|0)}; +e.Gg=function(a){if(32>this.Jc){var b=mv(T(),a,this.r);return new gv(b,1+this.Jc|0,this.sc,1+this.Kc|0,this.tc,1+this.Ec|0,this.uc,1+this.jc|0,this.Ab,this.Fb,this.Eb,this.Db,this.B,1+this.C|0)}if(1024>this.Kc)return b=new x(1),b.a[0]=a,a=nv(T(),this.r,this.sc),new gv(b,1,a,1+this.Kc|0,this.tc,1+this.Ec|0,this.uc,1+this.jc|0,this.Ab,this.Fb,this.Eb,this.Db,this.B,1+this.C|0);if(32768>this.Ec){b=new x(1);b.a[0]=a;a=T().Ua;var c=nv(T(),nv(T(),this.r,this.sc),this.tc);return new gv(b,1,a,1,c,1+this.Ec| +0,this.uc,1+this.jc|0,this.Ab,this.Fb,this.Eb,this.Db,this.B,1+this.C|0)}if(1048576>this.jc){b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;var d=nv(T(),nv(T(),nv(T(),this.r,this.sc),this.tc),this.uc);return new gv(b,1,a,1,c,1,d,1+this.jc|0,this.Ab,this.Fb,this.Eb,this.Db,this.B,1+this.C|0)}if(30>this.Ab.a.length){b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;d=T().jf;var f=nv(T(),nv(T(),nv(T(),nv(T(),this.r,this.sc),this.tc),this.uc),this.Ab);return new gv(b,1,a,1,c,1,d,1,f,this.Fb,this.Eb,this.Db,this.B,1+this.C| +0)}b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;d=T().jf;f=nv(T(),nv(T(),nv(T(),this.r,this.sc),this.tc),this.uc);var g=new (B(B(B(B(B(ob))))).P)(1);g.a[0]=f;return new hv(b,1,a,1,c,1,d,1,g,1+this.jc|0,T().sz,this.Ab,this.Fb,this.Eb,this.Db,this.B,1+this.C|0)}; +e.vf=function(a){var b=pv(T(),this.r,a),c=qv(T(),2,this.sc,a),d=qv(T(),3,this.tc,a),f=qv(T(),4,this.uc,a),g=qv(T(),5,this.Ab,a),h=qv(T(),4,this.Fb,a),k=qv(T(),3,this.Eb,a),m=qv(T(),2,this.Db,a);a=pv(T(),this.B,a);return new gv(b,this.Jc,c,this.Kc,d,this.Ec,f,this.jc,g,h,k,m,a,this.C)};e.Hh=function(a,b){a=new $u(a,b);av(a,1,this.r);av(a,2,this.sc);av(a,3,this.tc);av(a,4,this.uc);av(a,5,this.Ab);av(a,4,this.Fb);av(a,3,this.Eb);av(a,2,this.Db);av(a,1,this.B);return a.lh()}; +e.Vg=function(){if(1>>20|0;var c=31&(a>>>15|0),d=31&(a>>>10|0),f=31&(a>>>5|0);a&=31;return b=this.Ec?(a=b-this.Ec|0,this.uc.a[a>>>15|0].a[31&(a>>>10|0)].a[31&(a>>>5|0)].a[31&a]):b>=this.Kc?(a=b-this.Kc|0,this.tc.a[a>>>10|0].a[31&(a>>>5|0)].a[31&a]):b>= +this.Jc?(a=b-this.Jc|0,this.sc.a[a>>>5|0].a[31&a]):this.r.a[b]}throw this.yd(b);};z(gv,"scala.collection.immutable.Vector5",{l8:1,pw:1,Fp:1,Ep:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,hf:1,b:1});function hv(a,b,c,d,f,g,h,k,m,n,q,u,v,w,A,K,M){this.B=this.r=null;this.C=0;this.vc=b;this.bc=c;this.wc=d;this.cc=f;this.kc=g;this.dc=h;this.$b=k;this.ec=m;this.ac=n;this.jb=q;this.tb=u;this.sb=v;this.rb=w;this.qb=A;tZ(this,a,K,M)} +hv.prototype=new uZ;hv.prototype.constructor=hv;e=hv.prototype; +e.J=function(a){if(0<=a&&a>>25|0;var c=31&(b>>>20|0),d=31&(b>>>15|0),f=31&(b>>>10|0),g=31&(b>>>5|0);b&=31;return a=this.$b?(b=a-this.$b|0,this.ec.a[b>>>20|0].a[31&(b>>>15|0)].a[31&(b>>>10|0)].a[31&(b>>>5|0)].a[31& +b]):a>=this.kc?(b=a-this.kc|0,this.dc.a[b>>>15|0].a[31&(b>>>10|0)].a[31&(b>>>5|0)].a[31&b]):a>=this.wc?(b=a-this.wc|0,this.cc.a[b>>>10|0].a[31&(b>>>5|0)].a[31&b]):a>=this.vc?(b=a-this.vc|0,this.bc.a[b>>>5|0].a[31&b]):this.r.a[a]}throw this.yd(a);}; +e.Jk=function(a,b){if(0<=a&&a=this.ac){var c=a-this.ac|0,d=c>>>25|0,f=31&(c>>>20|0),g=31&(c>>>15|0),h=31&(c>>>10|0);a=31&(c>>>5|0);c&=31;if(d=this.$b)return f=a-this.$b|0,a=f>>>20|0,c=31&(f>>>15|0),h=31&(f>>>10|0),g=31&(f>>>5|0),f&=31,d=this.ec.H(),k=d.a[a].H(),m=k.a[c].H(),n=m.a[h].H(),q=n.a[g].H(),q.a[f]=b,n.a[g]=q,m.a[h]=n,k.a[c]=m,d.a[a]=k,new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,d,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,this.C);if(a>=this.kc)return g=a-this.kc|0,a=g>>>15|0,c=31&(g>>>10|0),h=31&(g>>>5|0),g&=31,f=this.dc.H(), +d=f.a[a].H(),k=d.a[c].H(),m=k.a[h].H(),m.a[g]=b,k.a[h]=m,d.a[c]=k,f.a[a]=d,new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,f,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,this.C);if(a>=this.wc)return h=a-this.wc|0,a=h>>>10|0,c=31&(h>>>5|0),h&=31,g=this.cc.H(),f=g.a[a].H(),d=f.a[c].H(),d.a[h]=b,f.a[c]=d,g.a[a]=f,new hv(this.r,this.vc,this.bc,this.wc,g,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,this.C);if(a>=this.vc)return c= +a-this.vc|0,a=c>>>5|0,c&=31,h=this.bc.H(),g=h.a[a].H(),g.a[c]=b,h.a[a]=g,new hv(this.r,this.vc,h,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,this.C);c=this.r.H();c.a[a]=b;return new hv(c,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,this.C)}throw this.yd(a);}; +e.we=function(a){if(32>this.B.a.length)return a=kv(T(),this.B,a),new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,this.qb,a,1+this.C|0);if(31>this.qb.a.length){var b=lv(T(),this.qb,this.B),c=new x(1);c.a[0]=a;return new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,this.rb,b,c,1+this.C|0)}if(31>this.rb.a.length){b=lv(T(),this.rb,lv(T(),this.qb,this.B));c=T().Ua;var d=new x(1); +d.a[0]=a;return new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,this.sb,b,c,d,1+this.C|0)}if(31>this.sb.a.length){b=lv(T(),this.sb,lv(T(),this.rb,lv(T(),this.qb,this.B)));c=T().Lc;d=T().Ua;var f=new x(1);f.a[0]=a;return new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,this.tb,b,c,d,f,1+this.C|0)}if(31>this.tb.a.length){b=lv(T(),this.tb,lv(T(),this.sb,lv(T(),this.rb,lv(T(),this.qb,this.B))));c=T().jf; +d=T().Lc;f=T().Ua;var g=new x(1);g.a[0]=a;return new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,this.jb,b,c,d,f,g,1+this.C|0)}if(62>this.jb.a.length){b=lv(T(),this.jb,lv(T(),this.tb,lv(T(),this.sb,lv(T(),this.rb,lv(T(),this.qb,this.B)))));c=T().Dl;d=T().jf;f=T().Lc;g=T().Ua;var h=new x(1);h.a[0]=a;return new hv(this.r,this.vc,this.bc,this.wc,this.cc,this.kc,this.dc,this.$b,this.ec,this.ac,b,c,d,f,g,h,1+this.C|0)}throw vb();}; +e.Gg=function(a){if(32>this.vc){var b=mv(T(),a,this.r);return new hv(b,1+this.vc|0,this.bc,1+this.wc|0,this.cc,1+this.kc|0,this.dc,1+this.$b|0,this.ec,1+this.ac|0,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0)}if(1024>this.wc)return b=new x(1),b.a[0]=a,a=nv(T(),this.r,this.bc),new hv(b,1,a,1+this.wc|0,this.cc,1+this.kc|0,this.dc,1+this.$b|0,this.ec,1+this.ac|0,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0);if(32768>this.kc){b=new x(1);b.a[0]=a;a=T().Ua;var c=nv(T(),nv(T(), +this.r,this.bc),this.cc);return new hv(b,1,a,1,c,1+this.kc|0,this.dc,1+this.$b|0,this.ec,1+this.ac|0,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0)}if(1048576>this.$b){b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;var d=nv(T(),nv(T(),nv(T(),this.r,this.bc),this.cc),this.dc);return new hv(b,1,a,1,c,1,d,1+this.$b|0,this.ec,1+this.ac|0,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0)}if(33554432>this.ac){b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;d=T().jf;var f=nv(T(),nv(T(),nv(T(),nv(T(),this.r, +this.bc),this.cc),this.dc),this.ec);return new hv(b,1,a,1,c,1,d,1,f,1+this.ac|0,this.jb,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0)}if(62>this.jb.a.length){b=new x(1);b.a[0]=a;a=T().Ua;c=T().Lc;d=T().jf;f=T().Dl;var g=nv(T(),nv(T(),nv(T(),nv(T(),nv(T(),this.r,this.bc),this.cc),this.dc),this.ec),this.jb);return new hv(b,1,a,1,c,1,d,1,f,1,g,this.tb,this.sb,this.rb,this.qb,this.B,1+this.C|0)}throw vb();}; +e.vf=function(a){var b=pv(T(),this.r,a),c=qv(T(),2,this.bc,a),d=qv(T(),3,this.cc,a),f=qv(T(),4,this.dc,a),g=qv(T(),5,this.ec,a),h=qv(T(),6,this.jb,a),k=qv(T(),5,this.tb,a),m=qv(T(),4,this.sb,a),n=qv(T(),3,this.rb,a),q=qv(T(),2,this.qb,a);a=pv(T(),this.B,a);return new hv(b,this.vc,c,this.wc,d,this.kc,f,this.$b,g,this.ac,h,k,m,n,q,a,this.C)}; +e.Hh=function(a,b){a=new $u(a,b);av(a,1,this.r);av(a,2,this.bc);av(a,3,this.cc);av(a,4,this.dc);av(a,5,this.ec);av(a,6,this.jb);av(a,5,this.tb);av(a,4,this.sb);av(a,3,this.rb);av(a,2,this.qb);av(a,1,this.B);return a.lh()};e.Vg=function(){if(1>>25|0;var c=31&(a>>>20|0),d=31&(a>>>15|0),f=31&(a>>>10|0),g=31&(a>>>5|0);a&=31;return b=this.$b?(a=b-this.$b|0,this.ec.a[a>>>20|0].a[31&(a>>>15|0)].a[31&(a>>>10|0)].a[31&(a>>> +5|0)].a[31&a]):b>=this.kc?(a=b-this.kc|0,this.dc.a[a>>>15|0].a[31&(a>>>10|0)].a[31&(a>>>5|0)].a[31&a]):b>=this.wc?(a=b-this.wc|0,this.cc.a[a>>>10|0].a[31&(a>>>5|0)].a[31&a]):b>=this.vc?(a=b-this.vc|0,this.bc.a[a>>>5|0].a[31&a]):this.r.a[b]}throw this.yd(b);};z(hv,"scala.collection.immutable.Vector6",{m8:1,pw:1,Fp:1,Ep:1,cd:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,pc:1,Ga:1,Ic:1,Ad:1,Za:1,xa:1,Hd:1,Jd:1,cb:1,wa:1,hf:1,b:1});function pi(){var a=new BZ;a.Hb=Yz(new Zz);return a} +function zz(){var a=new BZ,b=KK("Chain(");a.Hb=b;return a}function BZ(){this.Hb=null}BZ.prototype=new FX;BZ.prototype.constructor=BZ;e=BZ.prototype;e.Ib=function(){return"IndexedSeq"};e.i=function(){return CR(new DR,new oX(this))};e.Nb=function(){return FR(new GR,new oX(this))};e.Xd=function(){return new MQ(this)};e.Na=function(a){return eO(this,a)};e.ya=function(a){return hO(this,a)};e.u=function(){return mO(this)};e.Qa=function(a){var b=this.Hb.q();return b===a?0:b{if(f instanceof E)return new E(c.Ca(d,f.zc));if(F()===f)return F();throw new D(f);}))}throw new D(a);}function EZ(a,b,c){if(F()===a)return c.ne(F());if(a instanceof E)return c.Vb(b.g(a.zc),new G(d=>new E(d)));throw new D(a);}e=pT.prototype;e.ne=function(a){return new E(a)};e.Vb=function(a,b){return a.e()?F():new E(b.g(a.oa()))};e.ag=function(a,b){return a.e()?F():b.g(a.oa())}; +e.Oe=function(a,b,c){return DZ(a,b,c)};e.eh=function(a,b,c){if(F()===a)a=b;else if(a instanceof E)a=c.Ca(b,a.zc);else throw new D(a);return a};e.kf=function(a,b,c){return EZ(a,b,c)};z(pT,"cats.instances.OptionInstances$$anon$1",{pS:1,b:1,mg:1,Qh:1,Pi:1,Ni:1,Mi:1,Qi:1,Oi:1,pg:1,og:1,Nh:1,Mh:1,ng:1,Lh:1,EJ:1,Ph:1,Oh:1,Rh:1,IJ:1,Ej:1,Kk:1,Ml:1,Nn:1,vD:1,wD:1,uD:1,xD:1,Li:1,Ki:1});function FZ(a){var b=jQ(new Jh,a);a.Qg=b.Qg;a.Cj=b.Cj;a.Ip=!1} +function Jh(){this.Cj=this.Qg=null;this.Ip=!1;this.In=this.Rg=0;this.Qg=N();this.Cj=null;this.Ip=!1;this.Rg=0}Jh.prototype=new qY;Jh.prototype.constructor=Jh;e=Jh.prototype;e.Fc=function(){};e.Rd=function(a){return MR(this,a)};e.pa=function(a){return Lg(this,a)};e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.i=function(){return new mQ(this.Qg.i(),new Rh(()=>this.In))};e.Dg=function(){return lQ()};e.J=function(a){return wO(this.Qg,a)}; +e.q=function(){return this.Rg};e.E=function(){return this.Rg};e.e=function(){return 0===this.Rg};e.ub=function(){this.Ip=!this.e();return this.Qg};e.fy=function(){this.In=1+this.In|0;this.Qg=N();this.Rg=0;this.Cj=null;this.Ip=!1};function Kh(a,b){a.In=1+a.In|0;a.Ip&&FZ(a);b=new J(b,N());0===a.Rg?a.Qg=b:a.Cj.Z=b;a.Cj=b;a.Rg=1+a.Rg|0;return a}function jQ(a,b){b=b.i();if(b.n()){var c=1,d=new J(b.j(),N());for(a.Qg=d;b.n();){var f=new J(b.j(),N());d=d.Z=f;c=1+c|0}a.Rg=c;a.Cj=d}return a} +function lG(a,b){b=b.i();b.n()&&(b=jQ(new Jh,b),a.In=1+a.In|0,a.Ip&&FZ(a),0===a.Rg?a.Qg=b.Qg:a.Cj.Z=b.Qg,a.Cj=b.Cj,a.Rg=a.Rg+b.Rg|0);return a}e.Ib=function(){return"ListBuffer"};e.gc=function(a){return lG(this,a)};e.Ia=function(a){return Kh(this,a)};e.Sa=function(){return this.ub()};e.g=function(a){return wO(this.Qg,a|0)};e.Kb=function(){return lQ()}; +z(Jh,"scala.collection.mutable.ListBuffer",{k9:1,tz:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,uz:1,ud:1,td:1,Jp:1,cb:1,wa:1,Ei:1,ae:1,hf:1,b:1});function TI(a,b,c){a.zb=b;a.Ob=c;return a}function PF(a){var b=new SI;TI(b,null,a);return b}function SI(){this.Ob=this.zb=null}SI.prototype=new wX;SI.prototype.constructor=SI;e=SI.prototype;e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.Ib=function(){return"SortedMap"};e.xf=function(){return new RF(this.Ob)}; +e.i=function(){return new SF(this.zb,F(),this.Ob)};e.hh=function(a){return wu(Bu(),this.zb,a,this.Ob)};e.Yj=function(a,b){a=xu(Bu(),this.zb,a,this.Ob);return null===a?b.za():a.Aa};e.g=function(a){var b=xu(Bu(),this.zb,a,this.Ob);return null===b?KR(a):b.Aa};e.Hc=function(a){Bu();return null!==xu(0,this.zb,a,this.Ob)};function QF(a,b,c){b=yu(Bu(),a.zb,b,c,!0,a.Ob);return b===a.zb?a:TI(new SI,b,a.Ob)}e.Oa=function(a){var b=Bu(),c=this.zb;null!==c&&st(b,c,a)}; +e.Cg=function(a){var b=Bu(),c=this.zb;null!==c&&wt(b,c,a)};e.X=function(){return Nt(Bu(),this.zb)};e.E=function(){return Nt(Bu(),this.zb)};e.e=function(){return 0===Nt(Bu(),this.zb)}; +e.d=function(a){if(a instanceof SI){var b=this.Ob,c=a.Ob;if(null===b?null===c:b.d(c)){Bu();b=this.zb;a=a.zb;c=this.Ob;var d;if(!(d=b===a)&&(d=null!==b)&&(d=null!==a)&&(d=(2147483647&b.D)===(2147483647&a.D))){b=new hS(b,c);a=new hS(a,c);for(c=!0;c&&null!==b.Ha&&null!==a.Ha;)b.Ha===a.Ha?(0===b.pb?d=null:(b.pb=b.pb-1|0,d=b.Bh.a[b.pb]),b.Ha=d,0===a.pb?d=null:(a.pb=a.pb-1|0,d=a.Bh.a[a.pb]),a.Ha=d):(c=(Object.is(b.Ha.ga,a.Ha.ga)||b.wn.Td(b.Ha.ga,a.Ha.ga))&&V(W(),b.Ha.Aa,a.Ha.Aa),b.Ha=qP(b,b.Ha.M),a.Ha= +qP(a,a.Ha.M));d=c&&null===b.Ha&&null===a.Ha}return d}}return iX(this,a)};e.xd=function(){return"TreeMap"};e.Vd=function(a){return RI(VI(),a,this.Ob)};e.Xj=function(a){return RI(VI(),a,this.Ob)};e.ya=function(a){var b=Nt(Bu(),this.zb)-(0=b)a=PF(this.Ob);else if(b>=Nt(Bu(),this.zb))a=this;else{a=new SI;var c=Bu();b=xt(Tt(c,this.zb,b));a=TI(a,b,this.Ob)}return a}; +e.Na=function(a){if(0>=a)var b=this;else if(a>=Nt(Bu(),this.zb))b=PF(this.Ob);else{b=new SI;var c=Bu();a=xt(Rt(c,this.zb,a));b=TI(b,a,this.Ob)}return b};e.Kl=function(a,b){return QF(this,a,b)};z(SI,"scala.collection.immutable.TreeMap",{Y7:1,rp:1,Pm:1,S:1,O:1,w:1,R:1,y:1,Q:1,lk:1,Xm:1,ja:1,U:1,Wm:1,k:1,kn:1,Ga:1,vp:1,O7:1,VI:1,R5:1,XO:1,Q5:1,P7:1,W7:1,W5:1,XI:1,wa:1,rP:1,hf:1,b:1});function HO(a,b,c){a.El=0;a.Ch=b;a.Xc=c;return a}function dr(){var a=new GO;HO(a,new x(16),0);return a} +function GO(){this.El=0;this.Ch=null;this.Xc=0}GO.prototype=new qY;GO.prototype.constructor=GO;e=GO.prototype;e.Rd=function(a){return MR(this,a)};e.pa=function(a){return Lg(this,a)};e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.i=function(){return ir(this).i()};e.Nb=function(){return ir(this).Nb()};e.Xd=function(){return new MQ(this)};e.Na=function(a){return eO(this,a)};e.u=function(){return mO(this)}; +e.Qa=function(a){var b=this.Xc;return b===a?0:bthis.Xc&&1<=a&&GZ(this,a)};e.J=function(a){var b=1+a|0;if(0>a)throw Fs(Hs(),a,this.Xc-1|0);if(b>this.Xc)throw Fs(Hs(),b-1|0,this.Xc-1|0);return this.Ch.a[a]};e.q=function(){return this.Xc};function ir(a){return new rX(a,new Rh(()=>a.El))}e.Dg=function(){return cr()}; +function hr(a,b){a.El=1+a.El|0;var c=1+a.Xc|0;a.Ch.a.length<=(c-1|0)&&GZ(a,c);a.Xc=c;a.Ch.a[c-1|0]=b;return a}function MP(a,b){if(b instanceof GO){var c=b.Xc;0b?f:f-b|0;f=ff?0:f;0c||c>=f)throw Fs(Hs(),c,f-1|0);f=b.a.length;if(0>d||d>=f)throw Fs(Hs(),d,f-1|0);a.Va=b;a.Nc=c;a.Mc=d} +function QP(a,b,c){a.Va=b;a.Nc=0;a.Mc=c;HZ(a,a.Va,a.Nc,a.Mc);return a}function TP(){var a=new RP;QP(a,PP(WP(),16),0);return a}function RP(){this.Va=null;this.Mc=this.Nc=0}RP.prototype=new qY;RP.prototype.constructor=RP;function IZ(){}e=IZ.prototype=RP.prototype;e.Rd=function(a){return MR(this,a)};e.pa=function(a){return Lg(this,a)};e.Ra=function(a){return hM(this,a)};e.Cb=function(a){return iM(this,a)};e.ya=function(a){return jM(this,a)};e.i=function(){return CR(new DR,new oX(this))}; +e.Nb=function(){return FR(new GR,new oX(this))};e.Xd=function(){return new MQ(this)};e.Na=function(a){return eO(this,a)};e.u=function(){return mO(this)};e.Qa=function(a){var b=(this.Mc-this.Nc|0)&(this.Va.a.length-1|0);return b===a?0:ba||a>=b)throw Fs(Hs(),a,b-1|0);return this.Va.a[(this.Nc+a|0)&(this.Va.a.length-1|0)]}; +function $H(a,b){var c=1+((a.Mc-a.Nc|0)&(a.Va.a.length-1|0))|0;c>((a.Mc-a.Nc|0)&(a.Va.a.length-1|0))&&c>=a.Va.a.length&&XP(a,c);a.Va.a[a.Mc]=b;a.Mc=(1+a.Mc|0)&(a.Va.a.length-1|0);return a}function SP(a,b){var c=b.E();if(0((a.Mc-a.Nc|0)&(a.Va.a.length-1|0))&&c>=a.Va.a.length&&XP(a,c),b=b.i();b.n();)c=b.j(),a.Va.a[a.Mc]=c,a.Mc=(1+a.Mc|0)&(a.Va.a.length-1|0);else for(b=b.i();b.n();)$H(a,b.j());return a} +function PH(a){if(a.e())throw new Cs("empty collection");var b=a.Va.a[a.Nc];a.Va.a[a.Nc]=null;a.Nc=(1+a.Nc|0)&(a.Va.a.length-1|0);return b}e.q=function(){return(this.Mc-this.Nc|0)&(this.Va.a.length-1|0)};e.e=function(){return this.Nc===this.Mc};e.Dg=function(){return WP()};e.hc=function(a,b,c){var d=(this.Mc-this.Nc|0)&(this.Va.a.length-1|0),f=Pi(Nh(),a);d=cb?f:f-b|0;f=ff?0:f;0=a.Va.a.length||16b){var c=(a.Mc-a.Nc|0)&(a.Va.a.length-1|0);HZ(a,AT(a,PP(WP(),b),0,c),0,c)}}e.Ib=function(){return"ArrayDeque"};e.Kb=function(){return this.Dg()};e.gc=function(a){return SP(this,a)};e.Ia=function(a){return $H(this,a)};e.g=function(a){return this.J(a|0)}; +z(RP,"scala.collection.mutable.ArrayDeque",{uP:1,tz:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,uz:1,ud:1,td:1,Jp:1,kD:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,vP:1,hf:1,b:1});function ZH(a){this.Va=null;this.Mc=this.Nc=0;QP(this,PP(WP(),a),0)}ZH.prototype=new IZ;ZH.prototype.constructor=ZH;ZH.prototype.Dg=function(){return pQ()};ZH.prototype.Ib=function(){return"Queue"};ZH.prototype.Kb=function(){return pQ()}; +z(ZH,"scala.collection.mutable.Queue",{q9:1,uP:1,tz:1,Kf:1,Ja:1,S:1,O:1,w:1,R:1,y:1,Q:1,Ea:1,ja:1,U:1,ea:1,k:1,Lf:1,Se:1,Mf:1,Re:1,Ed:1,uz:1,ud:1,td:1,Jp:1,kD:1,jg:1,Za:1,xa:1,kg:1,cb:1,wa:1,vP:1,hf:1,b:1});function JZ(){}JZ.prototype=new t;JZ.prototype.constructor=JZ;function KZ(){}KZ.prototype=JZ.prototype;JZ.prototype.AB=function(){};JZ.prototype.Kv=function(){}; +function TV(){this.AB(new aX);new bU;new aU;new nK(this);new oK(this);new aR;this.Kv(new iF);new bR;new fF;new qK;new pK;new xK;new uK;new IN;new rK}TV.prototype=new KZ;TV.prototype.constructor=TV; +z(TV,"cats.instances.package$all$",{xS:1,XR:1,WL:1,GK:1,IL:1,mK:1,JL:1,nK:1,aM:1,JK:1,nM:1,YK:1,OL:1,vK:1,KL:1,qK:1,HL:1,lK:1,AM:1,wL:1,sL:1,rL:1,qL:1,pL:1,oL:1,yM:1,xM:1,wM:1,vM:1,uL:1,gK:1,DL:1,CL:1,BL:1,hK:1,FL:1,jK:1,EL:1,iK:1,GL:1,kK:1,mE:1,lE:1,kE:1,ND:1,gE:1,ML:1,sK:1,tK:1,UL:1,TL:1,SL:1,RL:1,QL:1,PL:1,xK:1,wK:1,zK:1,yK:1,AK:1,EK:1,DK:1,CK:1,yL:1,VL:1,FK:1,HK:1,ZL:1,YL:1,XL:1,IK:1,pE:1,oE:1,nE:1,RD:1,cM:1,bM:1,LK:1,tE:1,sE:1,rE:1,qE:1,UD:1,iE:1,dM:1,OK:1,PK:1,RK:1,QK:1,AL:1,eM:1,TK:1,UK:1, +hM:1,gM:1,fM:1,VK:1,mM:1,lM:1,XK:1,aL:1,bL:1,$K:1,gL:1,fL:1,ZK:1,wE:1,vE:1,uE:1,XD:1,xE:1,ZD:1,uM:1,jL:1,kL:1,nL:1,mL:1,lL:1,zM:1,vL:1,AE:1,zE:1,yE:1,$D:1,SK:1,ZJ:1,BK:1,tL:1,$J:1,VD:1,TD:1,bE:1,YD:1,MK:1,cL:1,aK:1,LL:1,rK:1,NL:1,uK:1,bK:1,oK:1,pK:1,YJ:1,cK:1,dL:1,NK:1,dK:1,hL:1,eK:1,tM:1,sM:1,KK:1,iL:1,pM:1,qM:1,rM:1,oM:1,eL:1,fK:1,kM:1,jM:1,iM:1,WK:1,YR:1,hS:1,ZR:1,uT:1,aS:1,$R:1});var SV; +function LZ(){this.Yg=this.cx=this.bx=this.Pz=this.MD=null;MZ=this;this.AB(new aX);this.Pz=new $W;new bU;new aU;new nK(this);new oK(this);this.bx=new aR;this.cx=new pT;this.Kv(new iF);new bR;new fF;new qK;new pK;new xK;new uK}LZ.prototype=new t;LZ.prototype.constructor=LZ;LZ.prototype.AB=function(a){this.MD=a};LZ.prototype.Kv=function(a){this.Yg=a}; +z(LZ,"cats.implicits$",{WR:1,UV:1,OW:1,b:1,NP:1,LV:1,XV:1,WV:1,rX:1,BW:1,YV:1,OQ:1,aW:1,PQ:1,$V:1,QP:1,cW:1,PP:1,bW:1,fW:1,eW:1,iX:1,RP:1,iW:1,SP:1,jW:1,RQ:1,kW:1,TP:1,nW:1,WP:1,oW:1,lW:1,mW:1,pW:1,qW:1,tW:1,kQ:1,vW:1,lQ:1,LQ:1,xW:1,mQ:1,DW:1,hX:1,EW:1,FW:1,oQ:1,GW:1,HW:1,IW:1,KW:1,LW:1,MW:1,QW:1,bX:1,SW:1,qX:1,YW:1,SQ:1,cX:1,wQ:1,dX:1,yQ:1,gX:1,BQ:1,kX:1,TQ:1,lX:1,FQ:1,oX:1,sQ:1,PW:1,vX:1,xX:1,yX:1,WW:1,ZW:1,XW:1,MV:1,NQ:1,tX:1,VV:1,pX:1,NV:1,uW:1,QQ:1,hW:1,NW:1,dW:1,VW:1,jX:1,uX:1,fX:1,OV:1,$W:1, +HQ:1,mX:1,nQ:1,CW:1,rW:1,JW:1,wX:1,PV:1,sX:1,AW:1,QV:1,nX:1,ZV:1,TW:1,yW:1,eX:1,zW:1,gW:1,RV:1,UW:1,SV:1,aX:1,TV:1,WL:1,GK:1,IL:1,mK:1,JL:1,nK:1,aM:1,JK:1,nM:1,YK:1,OL:1,vK:1,KL:1,qK:1,HL:1,lK:1,AM:1,wL:1,sL:1,rL:1,qL:1,pL:1,oL:1,yM:1,xM:1,wM:1,vM:1,uL:1,gK:1,DL:1,CL:1,BL:1,hK:1,FL:1,jK:1,EL:1,iK:1,GL:1,kK:1,mE:1,lE:1,kE:1,ND:1,gE:1,ML:1,sK:1,tK:1,UL:1,TL:1,SL:1,RL:1,QL:1,PL:1,xK:1,wK:1,zK:1,yK:1,AK:1,EK:1,DK:1,CK:1,yL:1,VL:1,FK:1,HK:1,ZL:1,YL:1,XL:1,IK:1,pE:1,oE:1,nE:1,RD:1,cM:1,bM:1,LK:1,tE:1,sE:1, +rE:1,qE:1,UD:1,iE:1,dM:1,OK:1,PK:1,RK:1,QK:1,AL:1,eM:1,TK:1,UK:1,hM:1,gM:1,fM:1,VK:1,mM:1,lM:1,XK:1,aL:1,bL:1,$K:1,gL:1,fL:1,ZK:1,wE:1,vE:1,uE:1,XD:1,xE:1,ZD:1,uM:1,jL:1,kL:1,nL:1,mL:1,lL:1,zM:1,vL:1,AE:1,zE:1,yE:1,$D:1,SK:1,ZJ:1,BK:1,tL:1,$J:1,VD:1,TD:1,bE:1,YD:1,MK:1,cL:1,aK:1,LL:1,rK:1,NL:1,uK:1,bK:1,oK:1,pK:1,YJ:1,cK:1,dL:1,NK:1,dK:1,hL:1,eK:1,tM:1,sM:1,KK:1,iL:1,pM:1,qM:1,rM:1,oM:1,eL:1,fK:1});var MZ;function ue(){MZ||(MZ=new LZ);return MZ}QuerierBuilder=fp();Hit=$o();Querier=function(a){return new Jo(a)}; +}).call(this); +//# sourceMappingURL=protosearch-jsinterop-opt.js.map diff --git a/search/search.css b/search/search.css new file mode 100644 index 00000000..5c689f9a --- /dev/null +++ b/search/search.css @@ -0,0 +1,110 @@ +/* Protosearch - Core UI Styles */ + +:root { + --ps-bg: #fafafa; + --ps-border: #ddd; + --ps-text-muted: #666; + --ps-link: #0055aa; + --ps-highlight: #fff3b0; +} + +/* Search page layout */ +.ps-page { + max-width: 48rem; + margin: 0 auto; + padding: 2rem 1rem; +} + +.ps-page-header { + text-align: center; + margin-bottom: 2rem; +} + +.ps-page-header h1 { + margin: 0 0 0.5rem; +} + +.ps-page-header p { + color: var(--ps-text-muted); + margin: 0; +} + +/* Search input */ +.ps-page-search { + margin-bottom: 2rem; +} + +#search-input { + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid var(--ps-border); + border-radius: 4px; + background: var(--ps-bg); +} + +#search-input:focus { + outline: 2px solid var(--ps-link); + outline-offset: 1px; +} + +/* Results container */ +.ps-results { + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* Top bar search (for nav integration) */ +#search-top-bar { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border: 1px solid var(--ps-border); + border-radius: 4px; + background: var(--ps-bg); +} + +.search-row { + margin-left: auto; +} + +/* Modal */ +.search-modal { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; +} + +.search-modal-content { + background: var(--ps-bg); + margin: 2rem auto; + padding: 1rem; + width: 80%; + border-radius: 8px; + max-height: 80vh; + overflow-y: auto; +} + +#search-modal-input { + width: 100%; + padding: 0.5rem; + font-size: 1rem; + border: 1px solid var(--ps-border); + border-radius: 4px; + margin-bottom: 1rem; + box-sizing: border-box; +} + +.search-close { + float: right; + font-size: 1.5rem; + cursor: pointer; + line-height: 1; + padding-bottom: 0.5rem; +} + +.search-close:hover { + color: var(--ps-link); +} diff --git a/search/search.html b/search/search.html new file mode 100644 index 00000000..90bca869 --- /dev/null +++ b/search/search.html @@ -0,0 +1,31 @@ + + + + + + + Search + + + + + +
+
+

Search

+
+ + + + + +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/search/search.js b/search/search.js new file mode 100644 index 00000000..59045a2d --- /dev/null +++ b/search/search.js @@ -0,0 +1,145 @@ +// Protosearch - Core UI +// Renderers register themselves via window.Protosearch.registerRenderer() + +const currentScript = document.currentScript +const baseUrl = new URL("../", currentScript.src) + +// Renderer registry +window.Protosearch = { + renderers: {}, + defaultRenderer: null, + registerRenderer: function(name, fn) { + this.renderers[name] = fn + if (!this.defaultRenderer) this.defaultRenderer = fn + }, + getRenderer: function(name) { + return name ? this.renderers[name] : this.defaultRenderer + } +} + +// Read configuration from script data attributes and URL params +function getConfig() { + const urlParams = new URLSearchParams(location.search) + return { + showScore: currentScript?.dataset.showScore === "true", + showPath: currentScript?.dataset.showPath === "true", + showPreview: currentScript?.dataset.showPreview !== "false", + renderer: urlParams.get("renderer") || currentScript?.dataset.renderer, + query: urlParams.get("q"), + workerParams: buildWorkerParams(urlParams), + baseUrl: baseUrl, + } +} + +function buildWorkerParams(urlParams) { + const workerParams = new URLSearchParams() + const index = urlParams.get("index") + if (index) workerParams.set("index", index) + return workerParams.toString() +} + +function createSearchWorker(config, resultsElement, renderFn) { + const workerFile = config.workerParams ? `worker.js?${config.workerParams}` : "worker.js" + const workerUrl = new URL(workerFile, currentScript.src) + + const worker = new Worker(workerUrl) + worker.onmessage = function(e) { + const markup = e.data.map(hit => renderFn(hit, config)).join("") + resultsElement.innerHTML = markup + } + return worker +} + +function setupModal(config, renderFn) { + const modal = document.getElementById("search-modal") + const modalInput = document.getElementById("search-modal-input") + const modalBody = document.getElementById("search-modal-content-body") + const searchTopBar = document.getElementById("search-top-bar") + + if (!modal || !modalInput || !modalBody || !searchTopBar) return false + + searchTopBar.onclick = function() { + modal.style.display = "block" + modalInput.focus() + } + + const modalClose = document.getElementsByClassName("search-close")[0] + if (modalClose) { + modalClose.onclick = function() { + modal.style.display = "none" + } + } + + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none" + } + } + + // Keyboard shortcuts: `/` to open, `Escape` to close + window.addEventListener("keydown", (event) => { + if (event.defaultPrevented) return + if (event.code == "Slash" && modal.style.display != "block") { + event.preventDefault() + modal.style.display = "block" + modalInput.focus() + } + if (event.code == "Escape" && modal.style.display == "block") { + event.preventDefault() + modal.style.display = "none" + } + }) + + // Send input to worker + const worker = createSearchWorker(config, modalBody, renderFn) + modalInput.addEventListener("input", function() { + worker.postMessage({query: this.value}) + }) + modalInput.addEventListener("keydown", function(event) { + if (event.key === "Enter") worker.postMessage({query: this.value, flush: true}) + }) + + return true +} + +function setupPage(config, renderFn) { + const resultsContainer = document.getElementById("search-results") + const searchBar = document.getElementById("search-input") + + if (!resultsContainer || !searchBar) return false + + // Send input to worker + const worker = createSearchWorker(config, resultsContainer, renderFn) + searchBar.addEventListener("input", function() { + worker.postMessage({query: this.value}) + }) + searchBar.addEventListener("keydown", function(event) { + if (event.key === "Enter") worker.postMessage({query: this.value, flush: true}) + }) + + // If query param `q` is set, use it as initial query + if (config.query) { + searchBar.value = config.query + worker.postMessage({query: config.query, flush: true}) + } + + return true +} + +function main() { + const config = getConfig() + const renderFn = window.Protosearch.getRenderer(config.renderer) + + if (!renderFn) { + console.error("Protosearch: No renderer registered. Include docs.js or scaladoc.js.") + return + } + + if (!setupModal(config, renderFn)) { + setupPage(config, renderFn) + } +} + +window.onload = function() { + main() +} diff --git a/search/searchIndex.idx b/search/searchIndex.idx new file mode 100644 index 00000000..2a49e372 Binary files /dev/null and b/search/searchIndex.idx differ diff --git a/search/worker.js b/search/worker.js new file mode 100644 index 00000000..7c2e91d4 --- /dev/null +++ b/search/worker.js @@ -0,0 +1,62 @@ +importScripts("./protosearch.js") + +async function getQuerier(index) { + let querier = fetch("./" + index + ".idx") + .then(res => res.blob()) + .then(blob => QuerierBuilder.load(blob)) + .catch((error) => console.error("getQuerier error: ", error)); + return await querier +} + +const urlParams = new URLSearchParams(location.search) + +// Handle `index` query param +const maybeIndex = urlParams.get("index") +const index = maybeIndex ? maybeIndex : "searchIndex" + +// Handle `q` query param +const maybeQuery = urlParams.get("q") + +const querierPromise = getQuerier(index) + +async function searchIt(query) { + const querier = await querierPromise + return querier.search(query) +} + +const waitMs = 100 +let timeoutId = null +let lastValue = "" + +function post(value) { + lastValue = value + if (timeoutId) clearTimeout(timeoutId) + timeoutId = setTimeout(async () => { + timeoutId = null + postMessage(await searchIt(lastValue)) + }, waitMs) +} + +async function flush(value) { + if (timeoutId) { + clearTimeout(timeoutId) + timeoutId = null + } + postMessage(await searchIt(value)) +} + +onmessage = function(e) { + const msg = e.data + const query = msg.query || '' + if (msg.flush) { + flush(query) + } else { + post(query) + } +} + +if (maybeQuery == undefined) { + searchIt("warmup") +} +// If it is defined, search.js is going to call us as soon as we return +// So we skip the warmup diff --git a/security.html b/security.html new file mode 100644 index 00000000..5fc7affc --- /dev/null +++ b/security.html @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + Security Policy + + + +
+ + +
+
+
+
+

+ + + + +

+
+
+
+
+ + / to open search   + Esc to close + +
+
+
+ +
+ +
+

Security Policy

+ +

Reporting a Security Issue

+

To report a security issue, please use one of the following methods:

+
    +
  1. Navigate to the "Security and quality" tab at the top of this repository, click the "Report a vulnerability" button, and complete the form as much as possible.
  2. +
  3. Email security@typelevel.org with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
  4. +
+

The Security Team will attempt to respond within 3 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory. This project follows a 90 day disclosure timeline.

+ +

Procedure

+
    +
  1. A GitHub Security Advisory will be created in the appropriate repository.
  2. +
  3. A project member works privately with the reporter to resolve the vulnerability.
  4. +
  5. The project creates a new release of the package the vulnerabilty affects to deliver its fix.
  6. +
  7. The project publicly announces the vulnerability and describes how to apply the fix.
  8. +
+ +

Scala Steward

+

We strongly recommend users of our libraries to use Scala Steward or something similar to + automatically receive updates.

+ +

Typelevel Security Team

+ + + + + + + + + + + + + + + + + + + + + + + + + +
nameemailPGP public key
Ross A. Bakerross@rossabaker.com0x975BE5BC29D92CA5
Arman Bilgearman@typelevel.org0xA335B107E9282548
Brian P. Holtbholt+typelevel-security@planetholt.com +
+
+ +
+ + + + + + diff --git a/src/404.md b/src/404.md deleted file mode 100644 index 311f4b7f..00000000 --- a/src/404.md +++ /dev/null @@ -1,6 +0,0 @@ -# Page Not Found (404) - -Sorry, this page is missing. If you think this is a mistake, please [open an issue] or [email us]. - -[open an issue]: https://github.com/typelevel/typelevel.github.com/issues/new?template=BLANK_ISSUE -[email us]: mailto:webmaster@typelevel.org diff --git a/src/README.md b/src/README.md deleted file mode 100644 index df9e5ad1..00000000 --- a/src/README.md +++ /dev/null @@ -1,6 +0,0 @@ -{% - laika.title = Typelevel - laika.html.template = templates/home.template.html -%} - -This is a placeholder for the landing page. No content from this markdown file is rendered. diff --git a/src/blog/README.md b/src/blog/README.md deleted file mode 100644 index d259099b..00000000 --- a/src/blog/README.md +++ /dev/null @@ -1,5 +0,0 @@ -{% - laika.title: Blog - laika.html.template: blog.template.html - laika.targetFormats: [html] -%} diff --git a/src/blog/algebraic-api-design.md b/src/blog/algebraic-api-design.md deleted file mode 100644 index 834c30bf..00000000 --- a/src/blog/algebraic-api-design.md +++ /dev/null @@ -1,586 +0,0 @@ -{% - author: ${battermann} - date: "2019-02-06" - tags: [technical] - katex: true -%} - -# Algebraic API Design - Types, Functions, Properties - -In this post we are going to explore the concept of *algebraic API design* which is based on types, pure functions, and the relationships between them known as domain rules or properties. We will do this based on a complete, self-contained example using Cats and Cats Effect and walk through the process of designing and implementing the domain of solving complex, deterministic single player games. - -An API in this context describes the types and operations that a module exposes to the user. - -SameGame is a deterministic single player game with perfect information. It has a game-tree complexity of @:math 10^{82} @:@. In other words it is extremely hard to solve. Exhaustive search strategies and traditional path finding algorithms do not perform well. Monte Carlo tree search which is based on random sampling on the other hand is a promising approach. We will go into more details on these concepts below. - -But how can we implement this with functional programming? How can we express algorithms that are based on randomness, mutable state, and side effects in a purely functional way? - -All the code in this post is interpreted with [tut](https://github.com/tpolecat/tut) to make sure everything type-checks so that the reader gets a complete picture rather than simplistic code samples that don't actually work. As a downside some of the code is quite lengthy. Please note that it is not required to read and understand every single line. - -Let's have a quick recap on the definition of algebraic structures and how they relate to programming and domain modeling. - -## Algebraic Structures - -An algebraic structure consists of: - -* One or more *sets* -* A set of *operators* -* A collection of *axioms* (which the operators are required to satisfy) - -A prototypical example of an algebraic structure from mathematics is a group. A concrete example of a group is the set @:math \mathbb{Z} @:@ of integers together with the addition operator denoted as @:math() (\mathbb{Z}, +) @:@ that satisfy the group axioms. - -A group can be defined in an abstract way like this: - -- *Set* and *operator*: @:math() (G, \circ) @:@ -- *Axioms*: - - Closure: @:math \forall a, b \in G:a \circ b \in G @:@ - - Associativity: @:math \forall a, b, c \in G: a \circ (b \circ c) = (a \circ b) \circ c @:@ - - Identity: @:math \exists e \in G: \forall a \in G:e \circ a = a = a \circ e @:@ - - Inverse: @:math \forall a \in G: \exists b \in G:a \circ b = e = b \circ a @:@ - -## Programming - -There is an analogy in programming where: - -- Sets are *types* (specifically algebraic data types) -- Operators are *functions* -- Axioms are *properties* (predicates expressed in terms of the algebra backed up by property-based tests) - -The [type classes provided by Cats](https://typelevel.org/cats/typeclasses.html) follow exactly this pattern. Cats provides representations of many algebraic structures, one of which is `Group[A]`. It is defined by a *type* `A` and an *operator* `combine`: - -```scala -trait Group[A] { - def empty: A - def combine(x: A, y: A): A - def inverse(a: A): A -} -``` - -`Group` additionally has an empty and an inverse element. Closure is already enforced by the types of the operators. The axioms are expressed as properties. - -E.g. for all objects `x`, `y`, `z` of type `A`, the ([associative property](https://github.com/typelevel/cats/blob/master/kernel-laws/src/main/scala/cats/kernel/laws/SemigroupLaws.scala#L8)) must hold: - -```scala -def semigroupAssociative(x: A, y: A, z: A): IsEq[A] = - S.combine(S.combine(x, y), z) <-> S.combine(x, S.combine(y, z)) -``` - -We can create a concrete instance of `Group[A]`, e.g. according to @:math() (\mathbb{Z}, +) @:@: - -```scala -import cats.Group - -implicit val group: Group[Int] = new Group[Int] { - def inverse(a: Int): Int = -a - def empty: Int = 0 - def combine(x: Int, y: Int): Int = x + y -} -``` - -The verification of the `Group` properties for the instance we created can be done with the help of [discipline](https://github.com/typelevel/discipline). - -Please note that, while property-based testing is a very powerful tool, it is not the only way to verify properties. There are other methods like dependant types, formal verification, or example-based testing. However, property-based testing works really well and has a huge return on investment. - -## Domain Modeling - -Algebraic design is not exclusively applicable to modeling algebraic structures from mathematics such as groups from the example above. We can use exactly the same technique to model the API of any domain. - -The elements of algebraic structures can be related to domain driven design as follows: - -- Sets are *types of entities or value objects* -- Operators are *business behavior* -- Axioms are *business rules* - -Here is an overview of how algebraic structures, programming, and domain modeling relate to each other: - -| Algebraic Structure | Programming | Domain Modeling | -| --- | --- | --- | -| Sets | Algebraic data types | Types of entities/value objects | -| Operators | Functions | Business behavior | -| Axioms | Properties | Business rules | - -## Programs - -Once we've defined the algebras that model the API of our domain, we can describe *programs* in terms of one or more of these algebras. Algebras in programming are also sometimes referred to as embedded domain specific languages (EDSLs). Programs that are composed of algebras or EDSLs are parametrically polymorphic and they know nothing about the algebras' concrete implementations other than that they satisfy certain properties. - -Programs are therefore flexible and constrained at the same time. Flexible in the sense that they can be used with any lawful implementation of the given algebra. And constrained because they can only use the operators provided by the algebra to manipulate values of the types that the algebra is expressed with. - -To give a crude, concrete example, a program @:math p @:@ is expressed in terms of the algebra of `Group[A]`. @:math p @:@ can only produce a result by using the operators `combine` and `inverse` on given input parameters of type `A`. Those parameters cannot be manipulated in any other way. Which leaves less room for mistakes and leads to correct programs. The caller of @:math p @:@ decides which concrete implementation of `Group[A]` they want to provide. Which makes @:math p @:@ reusable in multiple different contexts. - -We could define a program as follows: - -> A program is a pure polymorphic function that uses algebras by combining their operators with other input parameters to produce a pure value. - -## Interpreters - -The concrete implementation of an algebra is also know as the *interpreter* of the algebra. In the example from above the value `group` of type `Group[Int]` is an interpreter of the algebra of `Group[A]`. - -This might all sound a bit abstract and theoretical at first. In fact, talking about the concept of algebraic design is one thing, but applying this concept to a real business domain is another. - -Let's look at a concrete and self-contained example. - -## Solving single player games - -@:style(bulma-columns bulma-is-centered) @:image(/img/media/samegame.png) { style: bulma-column bulma-is-half } @:@ - -We will write a program that finds solutions for deterministic single player games with a high game-tree complexity like SameGame. - -SameGame is played on a @:math 15 \times 15 @:@ board initially filled with blocks of 5 colors. The goal of the game is to remove as many blocks from the board as possible while maximising the score. See [https://en.wikipedia.org/wiki/SameGame](https://en.wikipedia.org/wiki/SameGame) for detailed rules. You can play the game at [js-games.de](http://www.js-games.de/eng/games/samegame/lx/play) or [https://samegame.surge.sh](https://samegame.surge.sh). - -SameGame is a game with perfect information that is very difficult to solve. Given an initial starting position, we can construct a complete game-tree for SameGame as follows: - -- The nodes of the tree are board positions -- The edges are moves -- Each position (node) contains all possible moves (edges) -- The root node represents the starting position -- The leafs are terminal game states - -The total number of leafs is the game-tree complexity. The game-tree complexity of Tic-Tac-Toe e.g. is about @:math 10^5 @:@. Tic-Tac-Toe is easy to solve by doing an exhaustive search. Whereas SameGame has a complexity of approximately @:math 10^{82} @:@. This makes it impossible to solve with a brute-force approach or other traditional algorithms in a reasonable amount of time. Smaller SameGame boards are relatively easy to solve. As the size of the board increases we observe a *combinatorial explosion*. The time required to find the best solution increases so rapidly that we hit a solvability limit. - -## Monte Carlo tree search - -When we encounter a combinatorial explosion, *stochastic optimization algorithms* come to the rescue. Instead of exploring the complete search tree these algorithms sample the search space and can find very good solutions. It is very unlikely, however, that they reach a global maximum and find the best solution in a reasonable amount of time. - -A stochastic optimization algorithm that has successfully been employed to game play is known as *Monte Carlo tree search*. The basic idea of Monte Carlo tree search is to determine the most promising move based on random simulations at each node in the game-tree. In a random simulation the game is played out to the very end by selecting uniformly distributed random moves. - -Here is a very simple version of a Monte Carlo tree search: - -1. Choose the root node as the current node @:math n @:@ of the game-tree -2. For the current node @:math n @:@, determine all legal moves @:math ms @:@ - - If no legal moves exist, the algorithm terminates -3. Determine all child nodes @:math cs @:@ of @:math n @:@ by applying each of the moves @:math ms @:@ to the current state @:math n @:@ -4. Perform a random simulation for each of the child nodes @:math cs @:@ -5. From the child nodes @:math cs @:@ choose the node with the best simulation result, and continue with step 2. - -A way to improve on this basic algorithm is to add a nested (lower level) search at step 4. such that a random simulation is performed if the current level equals 1, otherwise a `level - 1` search is performed. - -That's all we need to know so let's implement this in a purely functional way using algebraic API design. - -## Game algebra - -First we define a type that represents the game state: - -```scala -final case class GameState[Move, BoardPosition, Score]( - playedMoves: List[Move], - score: Score, - position: BoardPosition, -) -// defined class GameState -``` - -`GameState` consists of `playedMoves` (the list of moves that have been played), `score` (the current score), and `position` (the current board position). - -With `GameState` we can now express a game algebra like this: - -```scala -trait Game[F[_], Move, BoardPosition, Score] { - type GS = GameState[Move, BoardPosition, Score] - - def applyMove(gameState: GS, move: Move): GS - def legalMoves(gameState: GS): List[Move] - def simulation(gameState: GS): F[GS] -} -// defined trait Game -``` - -The type alias `GS` only serves better readability. - -Note that by parameterizing `Game` with `Move`, `BoardPosition`, and `Score` there are no dependencies. It is completely decoupled from any specific domain models. We can fully focus on the contract of the operations rather than having to deal with the implementation or cumbersome domain models. - -Furthermore, `Game` is defined for any type constructor `F[_]` which we use to model effects. By making this type abstract we can defer the decision of which effects we need to a later point in time, respectively at the entry point of the application. Common effect types that could be used are `IO`, `Task`, `Option`, `State`, `List` etc. or combinations of them. Having an abstract effect type additionally serves better reusability in different contexts like in tests. - -While `applyMove` and `legalMoves` have no effects, `simulation` returns an effect `F`. Even though one could think of implementations without effects, the implementation of `simulation` will use a generator for uniformly distributed random numbers. This can be done by describing a side effect or by using the State Monad. With an abstract effect we are able to choose a specific effect later and make `simulation` referentially transparent. - -## Game properties - -Just as an algebraic structure has certain axioms, we can also define properties for the `Game` algebra that all interpreters have to satisfy. These properties can be generic or they can be driven by the business rules of the domain. - -It is often challenging to come up with meaningful properties for an algebra. However, more and better constraints allow significantly fewer possible implementations of the algebra. Which leads to correct programs because there is less room for errors. - -For the sake of brevity let's consider only two properties: - -- For all game states, a simulation will lead to a terminal board position -- For all game states, given a move `m`, `m` is either illegal or when applied leads to a new game state and it increments the number of played moves - -These rules are expressed as predicates inside the companion object of `Game` like this: - -```scala -object Game { - // let's follow the naming convention used by Cats and call this 'laws' - object laws { - import cats.Functor - import cats.implicits._ - - def simulationIsTerminal[F[_]: Functor, Move, BoardPosition, Score]( - gameState: GameState[Move, BoardPosition, Score])( - implicit ev: Game[F, Move, BoardPosition, Score]): F[Boolean] = - ev.simulation(gameState).map(ev.legalMoves).map(_.isEmpty) - - def legalMoveModifiesGameState[F[_], Move, BoardPosition, Score]( - gameState: GameState[Move, BoardPosition, Score], - move: Move)(implicit ev: Game[F, Move, BoardPosition, Score]): Boolean = { - val legalMoves = ev.legalMoves(gameState) - val nextGameState = ev.applyMove(gameState, move) - !legalMoves.contains(move) || - (nextGameState.position != gameState.position && nextGameState.playedMoves.length == gameState.playedMoves.length + 1) - } - } -} -``` - -There is an additional constraint that we discover while implementing the predicates that define the properties of `Game`. `F` must have a `Functor` instance. `Functor` is the least powerful abstraction that allows us to access the value inside `F` and express the property related to `simulation`. In the next section we will see that the program we are going to write will imply additional constraints for `F`. - -The properties of `Game` are tightly coupled to the algebra as any implementation must satisfy them. This is why they are defined inside the companion object of `Game` as part of the library code. - -Later we will see how to implement property-based tests to verify the properties. - -## Programs - -The `Game` properties are expressed solely in terms of the `Game` algebra. We have no idea how `Game` is implemented or what the types `Move`, `BoardPosition`, or `Score` look like. In a similar fashion we will only use the algebra to implement programs such as the search algorithm described above. - -Before we do this, we will define another algebra that describes logging, simply for convenience. Especially during long running searches it is useful to be able to output intermediate results and search states: - -```scala -import cats.Show - -trait Logger[F[_]] { - def log[T: Show](t: T): F[Unit] -} - -object Logger { - def apply[F[_]]()(implicit ev: Logger[F]): Logger[F] = ev -} -``` - -With the two algebras `Game` and `Logger` we can now implement the nested Monte Carlo tree search. - -```scala -import cats.Monad -// import cats.Monad - -import cats.implicits._ -// import cats.implicits._ - -def nestedSearch[F[_]: Monad: Logger, Move, Position, Score]( - numLevels: Int, - level: Int, - gameState: GameState[Move, Position, Score])( - implicit g: Game[F, Move, Position, Score], - ord: Ordering[Score], - show: Show[GameState[Move, Position, Score]]): F[GameState[Move, Position, Score]] = { - val legalMoves = g.legalMoves(gameState) - for { - _ <- if (level == numLevels) Logger[F].log(gameState) else ().pure[F] - result <- if (legalMoves.isEmpty) - Monad[F].pure(gameState) - else - legalMoves - .traverse { move => - if (level == 1) { - val nextGameState = g.applyMove(gameState, move) - g.simulation(nextGameState).map((move, _)) - } else { - val nextState = g.applyMove(gameState, move) - nestedSearch[F, Move, Position, Score](numLevels, level - 1, nextState).map((move, _)) - } - } - .map(_.maxBy(_._2.score)) - .flatMap { - case (move, _) => - nestedSearch[F, Move, Position, Score](numLevels, level, g.applyMove(gameState, move)) - } - } yield result -} -// nestedSearch: [F[_], Move, Position, Score](numLevels: Int, level: Int, gameState: GameState[Move,Position,Score])(implicit evidence$1: cats.Monad[F], implicit evidence$2: Logger[F], implicit g: Game[F,Move,Position,Score], implicit ord: Ordering[Score], implicit show: cats.Show[GameState[Move,Position,Score]])F[GameState[Move,Position,Score]] -``` - -This program describes the nested Monte Carlo tree search algorithm from above. The biggest difference is that this description is statically type checked by the Scala compiler. The effect of mutating the game state is modelled in a purely functional way with recursion. In fact, the state modifications could be modelled with the State Monad as well, but this makes things a bit more complicated especially when we try to parallelize the search. - -Note that to implement the algorithm we need a `Monad` instance for `F`. Other than that we don't care what `F` exactly is. - -Moreover, the `nestedSearch` function implies additional constraints for `Score` and `GameState`. We need to pass instances of `Ordering[Score]` (because we want to compare scores) and `Show[GameState]` (which we need for logging) as implicit parameters. - -## The Game interpreter - - - - -The game logic itself is defined in the object `SameGame` which is not shown here for the sake of brevity. You can find the implementation in [this Gist](https://gist.github.com/battermann/24d3318ec0cc3de84bfc7e696284aa60). - -With `SameGame` we are able write to an interpreter for `Game` that implements the SameGame rules. For the type constructor `F[_]` we will choose `IO`, so that we can model the side effect of a random number generator in a referentially transparent way. There are other options, like `State` e.g. that we will not discuss here. The point is that we are free to use whatever effect type serves our needs, as long as the implementation is pure and the properties of the `Game` algebra hold. - -```scala -import cats.effect.IO -// import cats.effect.IO - -import SameGame._ -// import SameGame._ - -implicit val game: Game[IO, Position, SameGameState, Int] = - new Game[IO, Position, SameGameState, Int] { - def applyMove(gameState: GameState[Position, SameGameState, Int], - move: Position): GameState[Position, SameGameState, Int] = { - val gs = SameGame.applyMove(move, gameState.position) - GameState(move :: gameState.playedMoves, SameGame.score(gs), gs) - } - - def legalMoves(gameState: GameState[Position, SameGameState, Int]): List[Position] = - SameGame.legalMoves(gameState.position) - - def simulation(gameState: GameState[Position, SameGameState, Int]) - : IO[GameState[Position, SameGameState, Int]] = { - val moves = legalMoves(gameState) - if (moves.isEmpty) - IO.pure(gameState) - else - IO(scala.util.Random.nextInt(moves.length)) - .map(moves) - .map(applyMove(gameState, _)) - .flatMap(simulation) - } - } -// game: Game[cats.effect.IO,SameGame.Position,SameGame.SameGameState,Int] = $anon$1@2fd961b3 -``` - -We must not forget to also implement an interpreter for `Logger`: - -```scala -implicit val logger: Logger[IO] = new Logger[IO] { - def log[T: Show](t: T): IO[Unit] = IO(println(t.show)) -} -// logger: Logger[cats.effect.IO] = $anon$1@32e5f76d -``` - -And some `Show` instances to create nicely formatted outputs in a type-safe way: - -```scala -implicit val showCell: Show[CellState] = Show.show { - case Empty => "-" - case Filled(Green) => "0" - case Filled(Blue) => "1" - case Filled(Red) => "2" - case Filled(Brown) => "3" - case Filled(Gray) => "4" -} - -implicit val showMove: Show[Position] = - Show.show(p => show"(${p.col}, ${p.row})") - -implicit val showList: Show[List[Position]] = - Show.show(_.map(_.show).mkString("[", ", ", "]")) - -implicit val showBoard: Show[Board] = - Show.show( - _.columns - .map(col => col.cells.map(_.show).reverse) - .transpose - .map(_.mkString("[", ",", "]")) - .mkString("\n")) - -implicit val showGame: Show[SameGameState] = Show.show { - case InProgress(board, score) => show"$board\n\nScore: $score (game in progress)" - case Finished(board, score) => show"$board\n\nScore: $score (game finished)" -} - -implicit val showGameState: Show[GameState[Position, SameGameState, Int]] = - Show.show(t => show""" - |${t.position} - |Moves: ${t.playedMoves.reverse} - |""".stripMargin) -``` - -## Verifying the Game properties - -Now that we have defined the interpreter for `Game`, it is time to ensure that `Game` properties are satisfied. We will do this with property-based testing and the library ScalaCheck. ScalaCheck uses a large number of randomly generated test cases to verify that the given properties hold. - -```scala -import org.scalacheck.{Arbitrary, Gen} -import org.scalacheck.Gen._ -import org.scalatest._ -import org.scalatest.prop.PropertyChecks -``` - -To generate test cases, we have to define how to construct the test inputs. ScalaCheck provides numerous combinators that can be composed to create generators for our domain objects. Here are basic implementations of generators for `GameState` and `Postion`: - -```scala -def colEmpty(size: Int): List[CellState] = List.fill(size)(Empty) - -def colNonEmpty(size: Int): Gen[List[CellState]] = - for { - numFilled <- choose(1, size) - filled <- listOfN(numFilled, choose(0, 5).map(c => Filled(Color(c)))) - } yield filled ++ colEmpty(size - filled.length) - -def gameState(min: Int, max: Int): Gen[GameState[Position, SameGameState, Int]] = - for { - size <- choose(min, max) - numFilled <- choose(0, size) - nonEmpty <- listOfN(numFilled, colNonEmpty(size)).map(_.map(Column(_))) - empty <- listOfN(size - numFilled, const(colEmpty(size))).map(_.map(Column(_))) - score <- Arbitrary.arbitrary[Int] - } yield - GameState( - playedMoves = List.empty[Position], - position = SameGame.evaluateGameState(Board(nonEmpty ++ empty), score), - score = score - ) - -def move(boardSize: Int): Gen[Position] = - for { - col <- choose(0, boardSize) - row <- choose(0, boardSize) - } yield Position(col, row) -``` - -Implementing the property checks is now straight forward: - -```scala -class GameTests extends PropSpec with PropertyChecks with Matchers { - property("simulation is terminal") { - forAll(gameState(4, 8)) { gs => - Game.laws - .simulationIsTerminal[IO, Position, SameGameState, Int](gs) - .unsafeRunSync() - } - } - - property("legal move modifies game state") { - forAll(gameState(4, 8), move(8)) { - case (gs, m) => - Game.laws - .legalMoveModifiesGameState[IO, Position, SameGameState, Int](gs, m) - } - } -} -// defined class GameTests -``` - -Finally we run our tests. In the Scala REPL this can be done like this: - -```scala -run(new GameTests) -// GameTests: -// - simulation is terminal -// - legal move modifies game state -``` - -Of course, there are additional test strategies that can be employed. In particular, it is useful to test not only the interpreters, but to test the program, too. However, this goes beyond the scope of this post. For more information on testing in the world of functional programming please, refer to the links in the resource section below. - -## Application - -With `cats.effect.IOApp` we describe a purely functional program that performs a Monte Carlo tree search for a given initial board position. For demonstration purposes we use a smaller board of size @:math 6 \times 6 @:@ to shorten the search time. - -```scala -import cats.effect._ -// import cats.effect._ - -object Main extends IOApp { - def run(args: List[String]): IO[ExitCode] = { - val board6x6 = List( - List(0, 2, 3, 1, 2, 1), - List(3, 0, 0, 3, 3, 2), - List(1, 2, 2, 2, 4, 2), - List(1, 1, 2, 4, 2, 2), - List(0, 0, 1, 3, 1, 4), - List(1, 4, 4, 0, 0, 3) - ) - - val initial = - GameState(playedMoves = List.empty[Position], score = 0, position = SameGame(board6x6)) - - val level = 4 - - nestedSearch[IO, Position, SameGameState, Int](level, level, initial) - .as(ExitCode.Success) - } -} -// defined object Main -``` - -When we look at the code we cannot explicitly see that the instances of `Game`, `Logger`, `Ordering`, and `Show` are passed to the `nestedSearch` function as implicit parameters. They have been defined above and can be successfully resolved by the compiler because of the REPL style code, presented here. In a normal Scala application those instances are imported into the scope in the main method - at the entry point of the application. - -`nestedSearch` returns a value of type `IO[GameState[Position, SameGameState, Int]]`. The return value can be ignored in this case because all the interesting information is logged by the program. `IOApp` takes care of and hides the execution of the `IO`. - -The truncated sample output from the application: - -```text -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,4,4,-,-,-] -[1,0,4,-,-,-] -[0,0,1,-,-,-] - -Score: 16 (game in progress) -Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1)] - - -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[1,0,-,-,-,-] -[0,0,1,-,-,-] - -Score: 17 (game in progress) -Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2)] - - -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[1,1,-,-,-,-] - -Score: 18 (game in progress) -Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2), (0, 0)] - - -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] -[-,-,-,-,-,-] - -Score: 1018 (game finished) -Moves: [(1, 1), (3, 2), (2, 0), (0, 2), (1, 0), (1, 0), (2, 3), (1, 3), (0, 2), (0, 1), (1, 2), (0, 0), (0, 0)] -``` - -## Improving on the results - -@:style(bulma-columns bulma-is-centered) @:image(/img/media/highscore.png) { style: bulma-column bulma-is-half } @:@ - -The Monte Carlo tree search algorithm presented in this post has been intentionally kept simple. There are numerous different strategies of how to guide the tree search based on heuristics to influence the choice of moves which require multiple parameters that have to be fine tuned to maximize the outcomes. - -A way to greatly improve on results of the algorithm presented here is to store best paths that were found in lower level searches which might otherwise be lost. We can apply domain-specific knowledge as well, as opposed to completely relying on random sampling. For SameGame e.g. there is a strategy called Tabu-Color which determines the most frequent color occurring at the start of each simulation. This predominat color is not allowed to be played because it is known to be advantageous to create large groups of blocks. Furthermore, parallelization can significantly shorten the search time. - -Please refer to [this GitHub repository](https://github.com/battermann/mcs) where the techniques mentioned above are applied. While still simple this implementation leads to very promising results. The algorithm was able to discover a solution with a top ten score at [js-games.de](http://www.js-games.de/eng/highscores/samegame/lx). - -Another very promising strategy that can be combined with a Monte Carlo tree search is [simulated annealing](https://en.wikipedia.org/wiki/Simulated_annealing). - -## Conclusion - -We have covered a lot of things. Some of the details might have been challenging depending on your prior knowledge of the topics introduced in this post. The reason for presenting such extensive examples is to provide complete and compiling code and to make a point that we do not only get great benefits from functional programming techniques, but that they are also feasible for real world problems. - -These are the key takeaways: - -- An algebra is defined in terms of sets, operators and axioms -- In functional programming algebras are expressed with types, functions and properties -- This relates nicely to types of entities and value objects, business behavior and business rules in the context of domain driven design -- Algebras are abstract and decoupled from any actual implementation which makes them more flexible and constrained at the same time -- Property-based tests and parametric polymorphism constrain the possible implementations and lead to correct programs -- Functional programming techniques are feasible and beneficial for real world programming - -*The code has been interpreted with tut, using Scala 2.12.7, scalatest 3.0.5, scalacheck 1.14.0, and cats-effect 1.1.0.* - -## Resources - -- [Functional and Reactive Domain Modeling](https://www.manning.com/books/functional-and-reactive-domain-modeling) by Debasish Ghosh -- [Nested Monte-Carlo Search](https://www.lamsade.dauphine.fr/~cazenave/papers/nested.pdf) by Tristan Cazenave -- [Testing in the world of Functional Programming](https://www.youtube.com/watch?v=cW5RY_x0Pbs) by Luka Jacobowitz -- [Sample code on GitHub](https://github.com/battermann/mcs) - -*Thanks to [Jarrod Urban](https://twitter.com/DgtlNmd) who did a very thorough review of this post.* diff --git a/src/blog/announcement-summit.md b/src/blog/announcement-summit.md deleted file mode 100644 index 94fc9144..00000000 --- a/src/blog/announcement-summit.md +++ /dev/null @@ -1,49 +0,0 @@ -{% - author: ${larsrh} - date: "2015-12-11" - tags: [summits] -%} - -# Announcement: Typelevel Summits coming up in 2016 - -We have a big announcement to make. In 2016, there will be not just one, but -two Typelevel Summits. Also, we’ve updated our website to include an up-to-date -list of Typelevel projects. There’s been much work behind the scenes which we -will talk about in a later post, so stay tuned! But first, here are some -details about the Summits. - -### Typelevel Summit US - -The first Typelevel Summit will be co-located with the Northeast Scala -Symposium in Philadelphia. As Brian Clapper already announced on Twitter, NE -Scala is going to happen on 4th and 5th of March with one day of recorded talks -and one day of unconference. Just today, we finalized the booking of the venue -and are happy to report that the Typelevel Summit will have the same format and -take place on 2nd and 3rd of March at the same venue (The Hub’s Cira Centre, -next to 30th Street Station). - -### Typelevel Summit Europe - -The second Typelevel Summit will be co-located with -[flatMap(Oslo)](http://2016.flatmap.no/). We will meet on the 4th of May after -the conference at the same venue (Teknologihuset). More details are to be -announced! - -### Call for Speakers, Attendance, & FAQs - -The planning phase is in full swing and we’ll announce more details soon. -Attendance will probably be limited to about 100. We’re also looking for -sponsors to help pay for the venue and cover other expenses. And we’re also -starting a diversity fund to support people from underrepresented groups, and -to mentor new speakers. If you want to contribute or have any other questions, -please contact us via [info@typelevel.org](mailto:info@typelevel.org). - -The Summits are open to all, not just current contributors to and users of the -Typelevel projects, and we are especially keen to encourage participation from -people who are new to them. Whilst many of the Typelevel projects use somewhat -"advanced" Scala, they are a lot more approachable than many people think, and -a major part of Typelevel's mission is to make the ideas they embody much more -widely accessible. So, if you're interested in types and pure functional -programming, want to make those ideas commonplace and are willing to abide by -the Typelevel code of conduct, then the Summits are for you and we'd love to -see you there. diff --git a/src/blog/blog.template.html b/src/blog/blog.template.html deleted file mode 100644 index 370e844a..00000000 --- a/src/blog/blog.template.html +++ /dev/null @@ -1,46 +0,0 @@ -@:embed(/templates/main.template.html) - -
-
-

- - Blog - - @:svg(fa-square-rss) - - -

-

- Follow our blog for announcements, events, and community-contributed posts. -

-
-
- - - -@:@ diff --git a/src/blog/call-for-code-of-conduct-committee-members.md b/src/blog/call-for-code-of-conduct-committee-members.md deleted file mode 100644 index 979b1879..00000000 --- a/src/blog/call-for-code-of-conduct-committee-members.md +++ /dev/null @@ -1,40 +0,0 @@ -{% - author: ${typelevel} - date: "2024-08-24" - tags: [governance] -%} - -# Call For Code of Conduct Committee Members - -Are you passionate about fostering a positive and inclusive community? -Do you want to help shape how Typelevel works with the community to build a respectful environment for all? -We’re excited to announce this call for new members to the Typelevel Code of Conduct Committee! - -Earlier this year Typelevel adopted a [new Code of Conduct][new-coc] that encompasses both organization and affiliate projects. -The Typelevel Code of Conduct Committee's mission is to enforce that Code of Conduct to help maintain community trust and safety. -We'd like your help in that mission! - - -Responsibilities ----------------- - -- Upholding the Code of Conduct and following through with the procedures outlined in the [Enforcement Policy][enforcement-policy] -- Completing Code of Conduct enforcement training such as that offered by [Otter Technology][otter-tech-training] (paid for by Typelevel) -- Willingness to contribute time and effort to committee activities, such as responding to reports in a timely manner -- Upholding community trust by ensuring transparency and accountability in their actions. - - -Applying --------- - -Send an email to volunteer@typelevel.org to put yourself forward for consideration, please include why you wish to join. -The email is visible only to [Typelevel Steering Committee membership][steering-committee]. -We will thoughtfully consider all serious applications and be discreet in our deliberation. -Accepted candidacies will be public, but we will not disclose the identity of anyone else that applies. - -The call will be open for three weeks, closing on September 14th 2024. - -[new-coc]: code-of-conduct.md -[enforcement-policy]: https://github.com/typelevel/governance/blob/main/ENFORCEMENT-POLICY.md -[otter-tech-training]: https://otter.technology/code-of-conduct-training/ -[steering-committee]: https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md diff --git a/src/blog/call-for-steering-committee-members.md b/src/blog/call-for-steering-committee-members.md deleted file mode 100644 index 0254a327..00000000 --- a/src/blog/call-for-steering-committee-members.md +++ /dev/null @@ -1,66 +0,0 @@ -{% - author: ${typelevel} - date: "2022-04-01" - tags: [governance] -%} - -# Call for Steering Committee Members - -The [Typelevel Steering Committee][committee] is opening a call for -new members as we continue to build a [more transparent and -sustainable community][governing documents]. - -In 2021, co-founders Lars Hupel and Miles Sabin stepped down from -leadership. We are all grateful for what they created and their -several years of service. What remains is a generation of leadership -that has not expanded in a few years. Retirements and additions alike -are healthy, balancing institutional memory with fresh energy. It is -again a time for renewal. - -Historically, leadership has invited prominent technical contributors -to join them. This results in homogenous talent, experience, and -identity. Our hope is that this open call for leaders will be heard -by a more well-rounded and diverse set of candidates to realize -Typelevel's mission. - -## Mission - -Typelevel is an association of projects and individuals united to -foster an inclusive, welcoming, and safe environment around functional -programming in Scala. - -## Responsibilities - -* Together, we are coders, organizers, writers, educators, publicists, - moderators, recruiters, academics, and strategists. You aren't all - of these, but you think you can add something that's scarce. - -* Membership is not a heavy burden, but neither is it an honorific. - You will participate in [governance discussions][governance - discussions], and are excited to reimagine the way we run. - -* The current charter defines no fixed term. You'll try to leave it - better than you found it and when you're ready, hand the baton to - someone who will do the same. - -## Applying - -Send an e-mail to [volunteer@typelevel.org](mailto:volunteer@typelevel.org) -to put yourself forward for consideration, optionally with a brief -case why you wish to serve. The e-mail is visible only to the -[committee membership][committee]. We will thoughtfully consider all -serious applications and be discreet in our deliberation. Accepted -candidacies will be public, but we will not disclose the identity of -anyone else. We hope to hear from some who surprise us, or even yet -had the pleasure of meeting. We are not looking for third-party -nominations, but if there's someone you'd like to see, encourage them -to apply! - -The charter does not prescribe a committee size, but we hope to -welcome two to six new members this cycle, and consider more as -ambitions and capacity warrant. - -[committee]: https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md -[governing documents]: governing-documents.md -[governance discussions]: https://github.com/typelevel/governance/issues -[volunteeer@typelevel.org]: mailto:volunteer@typelevel.org diff --git a/src/blog/cats-1.0-mf.md b/src/blog/cats-1.0-mf.md deleted file mode 100644 index 0ccba340..00000000 --- a/src/blog/cats-1.0-mf.md +++ /dev/null @@ -1,98 +0,0 @@ -{% - author: ${kailuowang} - date: "2017-08-04" - tags: [technical] -%} - -# Announcement: cats 1.0.0-MF - -The [cats](https://github.com/typelevel/cats) maintainer team is proud to announce the **cats 1.0.0-MF** release. - -**MF** stands for *milestone final*, -this will be the last release before cats 1.0.0-RC1 which will be followed by cats 1.0 shortly. - -The main purpose/focus of this release is to offer a relatively stable API to work with prior to the official 1.0. -It can be deemed as a proposal for the final cats 1.0 API. Please help test it and report any improvements/fixes -needed either in the [cats-dev gitter channel](https://gitter.im/typelevel/cats-dev) or as [github issues](https://github.com/typelevel/cats/issues/new). -Post cats 1.0, we will keep API stable and maintain strong binary compatibility. - -Highlights of the major new features include but not limited to: - -* [#1117](https://github.com/typelevel/cats/pull/1117): Stack safe `foldLeftM` without `Free`, by @TomasMikula -* [#1598](https://github.com/typelevel/cats/pull/1598): A `ReaderWriterStateT` data type, by @iravid -* [#1526](https://github.com/typelevel/cats/pull/1526) and [#1596](https://github.com/typelevel/cats/pull/1596): `InjectK` for free programs, by @tpolecat and @andyscott -* [#1602](https://github.com/typelevel/cats/pull/1602): Stack-safe `Coyoneda`, by @edmundnoble -* [#1728](https://github.com/typelevel/cats/pull/1728): `As` class which represents subtyping relationships (`Liskov`), by @stew -* [#1178](https://github.com/typelevel/cats/pull/1178): `Is` constructor for Leibniz equality, by @tel -* [#1748](https://github.com/typelevel/cats/pull/1748): Stack-safe `FreeApplicative`, by @edmundnoble -* [#1611](https://github.com/typelevel/cats/pull/1611): `NonEmptyTraverse`. by @LukaJCB - -Overall 1.0.0-MF has over 120 merged pull requests of API additions, bug fixes, documentation and misc -improvements from 44 contributors. For the complete change list please go to the [release notes][release notes]. - -### Migration -There are more breaking changes in this release - we want to include as many necessary breaking changes as possible in this release -to reach stability. Please follow the [migration guide][migration guide] from 0.9.0 in the [release notes][release notes]. - - - -### What's next - -Although we made many improvements to the documentation in this release, it's still by and large a WIP. -The next release 1.0.0-RC1 will focus documentation and API refinement based on community feedback. -RC1 is scheduled to be released in September. Unless the amount of bug fixes warrants a RC2, it's likely that -we'll release cats 1.0.0 within a couple weeks after RC1. - - -### Credits -Last but not least, many thanks to the contributors that make this release possible: - -* @alexandru -* @andyscott -* @BenFradet -* @Blaisorblade -* @cb372 -* @ceedubs -* @cranst0n -* @DavidGregory084 -* @denisftw -* @DieBauer -* @diesalbla -* @djspiewak -* @durban -* @edmundnoble -* @iravid -* @jtjeferreira -* @julien-truffaut -* @jyane -* @kailuowang -* @larsrh -* @Leammas -* @leandrob13 -* @LukaJCB -* @markus1189 -* @milessabin -* @n4to4 -* @oskoi -* @peterneyens -* @PeterPerhac -* @raulraja -* @RawToast -* @sellout -* @stew -* @sullivan- -* @SystemFw -* @takayuky -* @tel -* @TomasMikula -* @tpolecat -* @wedens -* @xavier-fernandez -* @xuwei-k -* @yilinwei -* @zainab-ali - - - -[migration guide]: https://github.com/typelevel/cats/blob/master/CHANGES.md#to-migrate-from-090 -[release notes]: https://github.com/typelevel/cats/releases/tag/v1.0.0-MF diff --git a/src/blog/cats-1.0.0.md b/src/blog/cats-1.0.0.md deleted file mode 100644 index 0399e2e4..00000000 --- a/src/blog/cats-1.0.0.md +++ /dev/null @@ -1,26 +0,0 @@ -{% - author: ${kailuowang} - date: "2017-12-25" - tags: [technical] -%} - -# Announcement: cats 1.0.0 - -The [cats](https://github.com/typelevel/cats) maintainer team is proud to announce the **cats 1.0.0** release. -Cats has been striving to provide functional programming abstractions that are core, modular, approachable and efficient. -Cats 1.0.0 marks the point where we believe that our API is robust and stable enough to start guarantee backward binary compatibility going forward until Cats 2.0. We expect the Cats 1.x series to be fully backwards compatible for at least one year. This is a major milestone towards -our goal of providing a solid foundation for an ecosystem of pure, typeful functional libraries. - -## Migration - -The vast majority of changes since 1.0.0-RC1 are API compatible, with scalafix scripts ready for those that do not. - [Here is the change list and migration guide](https://github.com/typelevel/cats/blob/master/CHANGES.md). - -## Binary compatibility -After 1.0.0 release, we'll use the *MAJOR.MINOR.PATCH* [Semantic Versioning 2.0.0](https://semver.org/) going forward, which is different from the *EPOCH.MAJOR.MINOR* scheme common among Java and Scala libraries (including the Scala lang). In this semantic versioning, backward breaking change is ONLY allowed between *MAJOR* versions. We will maintain backward binary compatibility between *PATCH* and *MINOR* versions. For example, when we release cats 1.1.0, it will be backward binary compatible with the previous 1.0.x versions. I.E. the new JAR will be a drop-in replacement for the old one. This is critical when your application has a diamond dependency on Cats - depending on two or more libraries that all depend on Cats. If one library upgrades to the new 1.1.0 Cats before the other one does, your application still runs thanks to this backward binary compatibility. - -We will also consider using organization and package name for *MAJOR* versioning with binary breaking changes in the future. But that decision is yet to be made. - -## Community -Cats is built for the FP Scala community by the FP Scala community. We can't thank enough to our [190 (and growing) contributors](https://github.com/typelevel/cats/graphs/contributors) and our users who provided feedbacks and suggestions. -Congratulations to all of us. Let's celebrate this exciting milestone together. diff --git a/src/blog/cats-ecosystem-community-survey-results.md b/src/blog/cats-ecosystem-community-survey-results.md deleted file mode 100644 index 04476c04..00000000 --- a/src/blog/cats-ecosystem-community-survey-results.md +++ /dev/null @@ -1,164 +0,0 @@ -{% - author: ${kailuowang} - date: "2019-01-30" - tags: [technical] -%} - -# Cats Ecosystem Community Survey 2018 Results - -Overall we received 588 responses over the course of 30 days. This feedback is essential for us to make informed decisions on our 2019 plan. Thank you, everyone, who participated. - -As promised, here are the results, as well as some quick reads from us. - -### Q: How long have you been using the Cats ecosystem (including Cats and Cats ecosystem libraries)? -#### Results: -![1](/img/media/2018-survey/5WuSHVP.png) - -#### Our read: -46% of the respondents are relatively new (less than 1 year) users. Welcome! - -### Q: Do you feel welcome in the Cats ecosystem community? - -#### Results: -![2](/img/media/2018-survey/JjK3muU.png) - -#### Our read: -83% of users gave the 4+ ratings on the welcomeness of the community. This is a promising sign. We should aim for having more 5 ratings (currently at 42.5%) and fewer 3- ones. - -### Q: How can we be more welcoming? - -#### Responses summary: -We received 92 responses on this free-form question. The vast majority, 90+%, of them suggested that more documentation, tutorials, and real-world examples would help them feel more welcome. Some of them suggested that the learning curve could be less intimidating to them if there were more introductory resources. - -#### Our read: - -The learning curve for Cats ecosystem libraries could be steep for people new to pure functional programming. This is by far the most impactful area for us to work on to be more inclusive. - -### Q: In what types of projects do you primarily use the Cats ecosystem? - -#### Results: -![3](/img/media/2018-survey/uqUxPWf.png) - -#### Our read: -83% of respondents are using Cats ecosystem libraries in production applications. We are honored to have that trust. In the meantime, it's a great responsibility for us to maintain stability and robustness. - -### Q: In which application domain do you primarily use the Cats ecosystem? - -#### Results: -![4](/img/media/2018-survey/cOhZ8W3.png) - -The free form `other` responses are omitted for the sake of conciseness. - - -### Q: If you use Cats directly, how is your overall experience using Cats? - -#### Results: -![5](/img/media/2018-survey/q2WjZ1d.png) - - -### Q: What would significantly improve your experience using Cats? -#### Results: -![5](/img/media/2018-survey/3ne70PU.png) - -The free form `other` responses can be summarized as -1. better IDE support 10 (1.8%) -2. better imports 7 (1.3%) -3. better integration with other libs 3 (0.6%) - - -#### Our read: -Overall the experience is mostly positive for our users. 73% of them believe there is space for improvements - they gave a rating less than 5. Again, more documentation and training will help most users. 25% of users are looking for more features. - -### Q: If you are contributing to Cats, thank you! How is your overall experience contributing? -#### Results: -![6](/img/media/2018-survey/XpVHZar.png) - -### Q: What would significantly improve your contributing experience? -#### Results: -![6](/img/media/2018-survey/BA1ShtM.png) - -#### Our read: -The contributing experience is good/okay but not really great. There is still a lot of work to be done here. - - - -### Q: For your applications, what would be a good time line for the Cats ecosystem to drop Scala 2.11 support? -#### Results: -![6](/img/media/2018-survey/C5OdxEK.png) - - -#### Our read: -26% of our users still can't migrate to Scala 2.12 before second half of 2019. - - - -### Q: If you are blocked from upgrading to Scala 2.12, by what? -#### Results: -![6](/img/media/2018-survey/C60Td4Q.png) - - - -### Q: What would be a good cadence for Cats to release a new major version (backward incompatible with previous ones)? -#### Results: -![6](/img/media/2018-survey/slzrAHi.png) - -#### Our read: -The community is split on this one. There are slightly more users that prefer a longer cadence (18-36 or in sync with Scala minor version) than those who prefer a shorter one (12-18 months). - -### Q: How are you using cats-laws? -#### Results: -![6](/img/media/2018-survey/mNbUBxW.png) - - -### Q: How would a breaking change in cats-laws affect you? -#### Results: -![6](/img/media/2018-survey/TiDFfzZ.png) - -#### Our Read: -Breaking Cats-laws's backward compatibility might be blockers for roughly 6% of the users. - -### Q: Would you benefit or suffer from cats-laws and cats-testkit being updated to Scalacheck 1.14 which is binary breaking with Scalacheck 1.13? -#### Results: -![6](/img/media/2018-survey/xGjcmj6.png) -#### Our Read: -While most people are neutral on this one, there are significantly more users (14.2%) who would benefit from a Scalacheck 1.14 upgrade than those who would suffer (1%). - - -### Q: Cats and most of its ecosystem libraries are maintained by hobbyists on their spare time. How would you feel about the future of the Cats ecosystem if there were financial backing to allow full-time or part-time maintainers? -#### Results: -![6](/img/media/2018-survey/YDmhHTo.png) - -#### Our Read: -In regards to financially backed full-time or part-time maintainers, it would give more confidence to 69.9% users while reducing it for 3% of the users. - -### Q: If you are against having compensated maintainers, what is your concern? -#### The free form answers can be summarized as -* Vendor favoritism and influence -* Conflicts of interest -* Discouraging non compensated maintainers -* Paid maintainer over contributing unnecessary features - -#### Our Read: -These concerns will be taken into consideration when we, if we decide to, design an institution to support paid maintainership. - - -### Q: If you are in favor of compensated maintainers, which financial source(s) would help boost your confidence in the ecosystem the most? -#### Results: -![6](/img/media/2018-survey/8VbOtFS.png) - -### Q: If you are in favor of corporate contribution, would your employer be interested? -#### Results: -![6](/img/media/2018-survey/evmpmZK.png) - - -### Q: Would you like to participate in Cats ecosystem community surveys going forward? -#### Results: -![6](/img/media/2018-survey/DoZYt4i.png) - -### Q: Please leave any additional feedback/suggestions below. -#### We received many kind words here, we can't list all of them but here are a few examples -* *Gitter support for the cats ecosystem is the best I've had in any language or toolset* -* *Great work, the progress in cats in the last year has had significant positive impact in my work. Thank you.* -* *Thanks for providing amazing libraries that are not only very solid but also a joy to use!* - -Overall we are encouraged by the survey responses from the community, in the meantime, they also showed us many areas to improve. Our 2019 planning will be based on these remarkably valuable feedbacks. We hope to present it to the community soon. diff --git a/src/blog/chain-replacing-the-list-monoid.md b/src/blog/chain-replacing-the-list-monoid.md deleted file mode 100644 index 9b274589..00000000 --- a/src/blog/chain-replacing-the-list-monoid.md +++ /dev/null @@ -1,63 +0,0 @@ -{% - author: ${lukajcb} - date: "2018-09-04" - tags: [technical] -%} - -# Chain – Replacing the List Monoid - -`List` is a great data type, it is very simple and easy to understand. -It has very low overhead for the most important functions such as `fold` and `map` and also supports prepending a single element in constant time. - -Traversing a data structure with something like `Writer[List[Log], A]` or `ValidatedNel[Error, A]` is powerful and allows us to precisely specify what kind of iteration we want to do while remaining succint. -However, in terms of efficiency it's a whole different story unfortunately. -That is because both of these traversals make use of the `List` monoid (or the `NonEmptyList` semigroup), which by the nature of `List` is very inefficient. -If you use `traverse` with a data structure with `n` elements and `Writer` or `Validated` as the `Applicative` type, you will end up with a runtime of `O(n^2)`. -This is because, with `List`, appending a single element requires iterating over the entire data structure and therefore takes linear time. - -So `List` isn't all that great for this use case, so let's use `Vector` or `NonEmptyVector` instead, right? - -Well, `Vector` has its own problems and in this case it's unfortunately not that much faster than `List` at all. You can check [this blog post](http://www.lihaoyi.com/post/BenchmarkingScalaCollections.html#vectors-are-ok) by Li Haoyi for some deeper insight into `Vector`'s issues. - -Because of this, it's now time to welcome a new data structure to Cats. -Meet `Chain` and its non-empty counterpart, `NonEmptyChain`. - -Available in the newest Cats 1.3.1 release, `Chain` evolved from what used to be `fs2.Catenable` and Erik Osheim's [Chain](https://github.com/non/chain ) library. -Similar to `List`, it is also a very simple data structure, but unlike `List` it supports both constant O(1) time `append` and `prepend`. -This makes its `Monoid` instance super performant and a much better fit for usage with `Validated`,`Writer`, `Ior` or `Const`. - -To utilize this, we've added a bunch of `NonEmptyChain` shorthands in Cats 1.3 that mirror those that used `NonEmptyList` in earlier versions. These include type aliases like `ValidatedNec` or `IorNec` as well as helper functions like `groupByNec` or `Validated.invalidNec`. -We hope that these make it easy for you to upgrade to the more efficient data structure and enjoy those benefits as soon as possible. - -To get a good idea of the performance improvements, here are some benchmarks that test monoidal append (higher score is better): - -``` -[info] Benchmark Mode Cnt Score Error Units -[info] CollectionMonoidBench.accumulateChain thrpt 20 51.911 ± 7.453 ops/s -[info] CollectionMonoidBench.accumulateList thrpt 20 6.973 ± 0.781 ops/s -[info] CollectionMonoidBench.accumulateVector thrpt 20 6.304 ± 0.129 ops/s -``` - -As you can see accumulating things with `Chain` is more than 7 times faster than `List` and over 8 times faster than `Vector`. -So appending is a lot more performant than the standard library collections, but what about operations like `map` or `fold`? -Fortunately we've also benchmarked these (again, higher score is better): - -``` -[info] Benchmark Mode Cnt Score Error Units -[info] ChainBench.foldLeftLargeChain thrpt 20 117.267 ± 1.815 ops/s -[info] ChainBench.foldLeftLargeList thrpt 20 135.954 ± 3.340 ops/s -[info] ChainBench.foldLeftLargeVector thrpt 20 61.613 ± 1.326 ops/s -[info] -[info] ChainBench.mapLargeChain thrpt 20 59.379 ± 0.866 ops/s -[info] ChainBench.mapLargeList thrpt 20 66.729 ± 7.165 ops/s -[info] ChainBench.mapLargeVector thrpt 20 61.374 ± 2.004 ops/s -``` - -While not as dominant, `Chain` holds its ground fairly well. -It won't have the random access performance of something like `Vector`, but in a lot of other cases, `Chain` seems to outperform it quite handily. -So if you don't perform a lot of random access on your data structure, then you should be fine using `Chain` extensively instead. - -So next time you write any code that uses `List` or `Vector` as a `Monoid`, be sure to use `Chain` instead! - -The whole code for `Chain` and `NonEmptyChain` can be found [here](https://github.com/typelevel/cats/blob/v1.3.0/core/src/main/scala/cats/data/Chain.scala) and [here](https://github.com/typelevel/cats/blob/v1.3.0/core/src/main/scala/cats/data/NonEmptyChain.scala). -You can also check out the benchmarks [here](https://github.com/typelevel/cats/blob/v1.3.0/bench/src/main/scala/cats/bench). diff --git a/src/blog/change-values.md b/src/blog/change-values.md deleted file mode 100644 index fc9154e5..00000000 --- a/src/blog/change-values.md +++ /dev/null @@ -1,587 +0,0 @@ -{% - author: ${S11001001} - date: "2015-09-21" - tags: [technical] -%} - -# To change types, change values - -*This is the seventh of a series of articles on “Type Parameters and -Type Members”. You may wish to -[start at the beginning](type-members-parameters.md); -more specifically, this post is meant as a followup to -[the previous entry](values-never-change-types.md). -However, in a first for this series, it stands on its own, as -introductory matter.* - -A program is a system for converting data from one format to another, -which we have endowed with the color of magic. In typed programming, -we use a constellation of types to mediate this transformation; a -function’s result can only be passed as another function’s argument to -the extent to which those parts of the functions’ types unify. - -We rely on the richness of our types in these descriptions. So it is -natural to want the types to change as you move to different parts of -the process; each change reflects the reality of what has just -happened. For example, when you parse a string into an AST, your -program’s state has changed types, from `String` to `MyAST`. - -But, as we have just seen, due to decisions we have made to simplify -our lives, -[values cannot change types](values-never-change-types.md), -no matter how important it is to the sanity of our code. At the same -time, we don’t want to give up the richness of using more than one -type to describe our data. - -Fortunately, there is a solution that satisfies these competing -concerns: to change types, change values. You can’t do anything about -the values you have, but you can create new ones of the right type, -and use those instead. - -Type-changing is program organization -------------------------------------- - -In values with complex construction semantics, it is common to write -imperative programs that leave “holes” in the data structures using -the terrible `null` misfeature of Java, Scala, and many other -languages. This looks something like this. - -```scala -class Document(filename: Path) { - // this structure has three parts: - var text: String = null // ← a body of text, - var wordIndex: Map[String, List[Int]] = null - // ↑ an index of words to every - // occurrence in the text, - var mostPopular: List[(String, Int)] = null - // ↑ the most frequently used words - // in the text, and their number of - // occurrences -... -``` - -Now, we must fill in these variables, by computing and assigning to -each in turn. First, we compute the corpus text. - -```scala - initText() -``` - -Then, we compute and fill in the word index. If we didn’t fill in -`text` first, this compiles, but crashes at runtime. - -```scala - initWordIndex() -``` - -Finally, we figure out which words are most popular. If we didn’t -fill in `wordIndex` first, this compiles, but crashes. - -```scala - initMostPopular() -``` - -How do I know that? Well, I have to inspect the definitions of these -three methods. - -```scala - def initText(): Unit = - text = Source.fromFile(filename.toFile).mkString - - def initWordIndex(): Unit = { - val words = """\w+""".r findAllMatchIn text - wordIndex = words.foldLeft(Map[String, List[Int]]()){ - (m, mtch) => - val word = mtch.matched - val idx = mtch.start - m + (word -> (idx :: m.getOrElse(word, Nil))) - } - } - - def initMostPopular(): Unit = - mostPopular = wordIndex.mapValues(_.size).toList - .sortBy(p => 0 - p._2).take(10) -``` - -This method of organizing object initialization is popular because, -among other properties: - -1. it *seems* self-documenting, -2. you don’t have to pass data around, and -3. steps can be customized by subclassing and overriding. - -However! It has the tremendous drawback of preventing the compiler -from helping you get the order of initialization correct. Go, look; -see if you can spot why I said the latter two calls would crash if you -don’t get the order exactly right. Now, I have four questions for -you. - -1. Would you trust yourself to notice these implicit dependencies - every time you look at this code? -2. Suppose you commented on the dependencies. Would you trust these - comments to be updated when the initialization details change? -3. Would you trust subclasses that customize the initialization to - respect the order in which we call these three `init` functions? -4. Could you keep track of this if the initialization was - significantly more complex? (This *is* a toy example for a blog - post, after all.) - -Ironically, as your initialization becomes more complex, the compiler -becomes less able to help you with uninitialized-variable warnings and -the like. But, this is not the natural order of things; it is a -consequence of using imperative variable initialization but not -representing this -[variable refinement](https://github.com/facebook/flow/releases/tag/v0.14.0) -in the type system. By initializing in a different way, we can -recover type safety. - -@:style(bulma-notification) - The implications of refinement, linked above, are much less severe - than those of unrestricted type-changing of a variable. So Flow did - not solve, nor did it aim to solve, those difficulties by - introducing the refinement feature. -@:@ - -The four types of `Document` ----------------------------- - -If we consider `Document` as the simple product of its three state -variables, with some special functions associated with them as -whatever `Document` methods we intend to support, we have a simple -3-tuple. - -```scala -(String, Map[String, List[Int]], - List[(String, Int)]) -``` - -Let us no longer pretend that it is any more complicated than that. - -But this cannot be mutated to fill these in as they are initialized, -you say! Yes, that’s right, we want a *type-changing* transformation. -By *changing values*, this is easy. There are three phases of -initialization, so four states, including uninitialized. - -```scala -Path -String -(String, Map[String, List[Int]]) -(String, Map[String, List[Int]], List[(String, Int)]) -``` - -For interesting phases, such as the final one, we might create a `case -class` to hold its contents, instead. Let us call that class, for -this example, `Doc`. - -```scala -final case class Doc - (text: String, wordIndex: Map[String, List[Int]], - mostPopular: List[(String, Int)]) -``` - -Finally, we can build 3 functions to take us through these steps. -Each begins by taking one as an argument, and produces the next state -as a return type. - -```scala - def initText(filename: Path): String = - Source.fromFile(filename.toFile).mkString - - def initWordIndex(text: String): (String, Map[String, List[Int]]) = { - val words = """\w+""".r findAllMatchIn text - (text, words.foldLeft(Map[String, List[Int]]()){ - (m, mtch) => - val word = mtch.matched - val idx = mtch.start - m + (word -> (idx :: m.getOrElse(word, Nil))) - }) - } - - def initMostPopular(twi: (String, Map[String, List[Int]])): Doc = { - val (text, wordIndex) = twi - Doc(text, wordIndex, - wordIndex.mapValues(_.size).toList - .sortBy(p => 0 - p._2).take(10)) - } -``` - -If we have a `Path`, we can get a `Doc` by `(initText _) andThen -initWordIndex andThen initMostPopular: Path => Doc`. But that hardly -replicates the rich runtime behavior of our imperative version, does -it? That is, we can do reordering of operations in a larger context -with `Document`, but not `Doc`. Let us see what that means. - -Many docs ---------- - -Dealing with one document in isolation is one thing, but suppose we -have a structure of `Document`s. - -```scala -sealed abstract class DocumentTree -final case class SingleDocument(id: Int, doc: Document) - extends DocumentTree -final case class DocumentCategory - (name: String, members: List[DocumentTree]) - extends DocumentTree -``` - -In the imperative mode, we can batch and reorder initialization. Say, -for example, we don’t initialize `Document` when we create it. This -tree then contains `Document`s that contain only `Path`s. We can walk -the tree, doing step 1 for every `Document`. - -```scala - // add this to DocumentTree - def foreach(f: Document => Unit): Unit = - this match { - case SingleDocument(_, d) => f(d) - case DocumentCategory(_, dts) => dts foreach (_ foreach f) - } - -// now we can initialize the text everywhere, -// given some dtree: DocumentTree -dtree foreach (_.initText()) -``` - -The way software does, it got more complex. And we can be ever less -sure that we’re doing things right, under this arrangement. - -The four phases problem, stuck in a tree ----------------------------------------- - -Our tree only supports one type of document. We could choose the -final one, `Doc`, but there is no way to replicate more exotic -document tree initializations like the one above. - -Instead, we want the type of the tree to adapt along with the document -changes. If we have four states, *Foo*, *Bar*, *Baz*, and *Quux*, we -want four different kinds of `DocumentTree` to go along with them. In -a language with type parameters, this is easy: we can model those four -as `DocTree[Foo]`, `DocTree[Bar]`, `DocTree[Baz]`, and -`DocTree[Quux]`, respectively, by adding a type parameter. - -```scala -sealed abstract class DocTree[D] -final case class SingleDoc[D](id: Int, doc: D) - extends DocTree[D] -final case class DocCategory[D] - (name: String, members: List[DocTree[D]]) - extends DocTree[D] -``` - -Now we need a replacement for the `foreach` that we used with the -unparameterized `DocumentTree` to perform each initialization step on -every `Document` therein. Now that `DocTree` is agnostic with respect -to the specific document type, this is a little more abstract, but -quite idiomatic. - -```scala - // add this to DocTree - def map[D2](f: D => D2): DocTree[D2] = - this match { - case SingleDoc(id, d) => SingleDoc(id, f(d)) - case DocCategory(c, dts) => - DocCategory(c, dts map (_ map f)) - } -``` - -It’s worth comparing these side by side. Now we should be able to -step through initialization of `DocTree` with `map`, just as with -`DocumentTree` and `foreach`. - -```scala -scala> val dtp: DocTree[Path] = DocCategory("rt", List(SingleDoc(42, Paths.get("hello.md")))) -dtp: tmtp7.DocTree[java.nio.file.Path] = DocCategory(rt,List(SingleDoc(42,hello.md))) - -scala> dtp map Doc.initText -res3: tmtp7.DocTree[String] = -DocCategory(rt,List(SingleDoc(42,contents of the hello.md file!))) -``` - -You wouldn’t avoid writing functions, would you? ------------------------------------------------- - -There is nothing magical about `DocTree` that makes it especially -amenable to the introduction of a type parameter. This is *not* a -feature whose proper use is limited to highly abstract or -general-purpose data structures; with its `String`s and `Int`s strewn -about, it is *utterly* domain-specific, “business” code. - -In fact, if we were likely to annotate `Doc`s with more data, `Doc` -would be a perfect place to add a type parameter! - -```scala -// suppose we add some "extra" data -final case class Doc[A] - (text: String, wordIndex: Map[String, List[Int]], - mostPopular: List[(String, Int)], - extra: A) -``` - -You can use a type parameter to represent one simple slot in an -otherwise concretely specified structure, as above. You can -[use one to represent 10 slots](https://bitbucket.org/ermine-language/ermine-writers/src/9ec9a98c30bc9924cc49888895f8832e8ce4f8e1/writers/html/src/main/scala/com/clarifi/reporting/writers/HTMLDeps.scala?at=default#HTMLDeps.scala-37). - -Parameterized types are the type system’s version of functions. They -aren’t just for collections, abstract code, or highly general-purpose -libraries: they’re for *your* code! - -Unless you are going to suggest that *functions* are “too academic”. -Or that functions have no place in “business logic”. Or perhaps that, -while it would be nice to define functions to solve this, that, and -sundry, you’ll just do the quick no-defining-functions hack for now -and maybe come back to add some functions later when “paying off -technical debt”. *Then*, I’m not sure what to say. - -The virtuous circle of FP and types ------------------------------------ - -Now we are doing something very close to functional programming. -Moreover, we were led here not by a desire for referential -transparency, nor for purity, but merely for a way to represent the -states of our program in a more well-typed way. - -In this series of posts, I have deliberately avoided discussion of -functional programming until this section; my chosen subject is types, -not functional programming. But the features we have been considering -unavoidably coalesce here into an empirical argument for functional -programming. Type parameters let us elegantly lift transformations -from one part of our program to another; the intractable complexities -of imperative type-changing direct us to program more functionally, by -computing new values instead of changing old ones, if we want access -to these features. This, in turn, encourages ever more of our program -to be written in a functional style, just as the switch to different -`Doc` representations induced a switch to different document tree -representations, `map` instead of `foreach`. - -Paying it Back --------------- - -Likewise, the use of functional programming style feeds back, in the -aforementioned virtuous circle, to encourage the use of stronger -types. - -When we wanted stronger guarantees about the initialization of our -documents, and thereby also of the larger structures incorporating -them, we turned to the most powerful tool we have at our disposal for -describing and ensuring such guarantees: the type system. In so -doing, we induced an explosion of explicit data representations; where -we had two, we now have eight, whose connections to each other are -mediated by the types of functions involved. - -With the increase in the number of explicit concepts in the code comes -a greater need for an automatic method of keeping track of all these -connections. The type system is ideally suited to this role. - -@:style(bulma-notification) - We induced more *explicit* data representation, not more - representations overall. The imperative `Document` has - four stages of initialization, at each of which it exhibits - different behavior. All we have done is expose this fact to the - type system level, at which our usage can be checked. -@:@ - -Don’t miss one! ---------------- - -As it is declared, the type-changing `DocTree#map` has another -wonderful advantage over `DocumentTree#foreach`. - -Let us say that each category should also have a document of its own, -not just a list of subtrees. In refactoring, we adjust the -definitions of `DocumentCategory` or `DocCategory`. - -```scala -// imperative version -final case class DocumentCategory - (name: String, doc: Document, - members: List[DocumentTree]) - extends DocumentTree - -// functional version -final case class DocCategory[D] - (name: String, doc: D, - members: DocTree[D]) - extends DocTree[D] -``` - -So far, so good. Next, neither `foreach` nor `map` compile anymore. - -``` -TmTp7.scala:70: wrong number of arguments for pattern -⤹ tmtp7.DocumentCategory(name: String,doc: tmtp7.Document, -⤹ members: List[tmtp7.DocumentTree]) - case DocumentCategory(_, dts) => - ^ -TmTp7.scala:71: not found: value d - f(d) - ^ -TmTp7.scala:91: wrong number of arguments for pattern -⤹ tmtp7.DocCategory[D](name: String,doc: D,members: List[tmtp7.DocTree[D]]) - case DocCategory(c, dts) => - ^ -TmTp7.scala:92: not enough arguments for method -⤹ apply: (name: String, doc: D, members: List[tmtp7.DocTree[D]] -⤹ )tmtp7.DocCategory[D] in object DocCategory. -Unspecified value parameter members. - DocCategory(c, dts map (_ map f)) - ^ -``` - -So let us fix `foreach` in the simplest way possible. - -```scala - // added ↓ - case DocumentCategory(_, _, dts) => ... -``` - -This compiles. It is wrong, and we can figure out exactly why by -trying the same shortcut with `map`. - -```scala - case DocCategory(c, d, dts) => - DocCategory(c, d, dts map (_ map f)) -``` - -We are treating the `d: D` like the `name: String`, just passing it -through. It is “ignored” in precisely the same way as the `foreach` -ignores the new data. But this version does not compile! - -```scala -TmTp7.scala:90: type mismatch; - found : d.type (with underlying type D) - required: D2 - DocCategory(c, d, dts map (_ map f)) - ^ -``` - -More broadly, `map` must return a `DocTree[D2]`. By implication, the -second argument must be a `D2`, not a `D`. We can fix it by using -`f`. - -```scala - DocCategory(c, f(d), dts map (_ map f)) -``` - -Likewise, we should make a similar fix to `DocumentTree#foreach`. - -```scala - case DocumentCategory(_, d, dts) => - f(d) - dts foreach (_ foreach f) -``` - -But only in the case of `map` did we get help from the compiler. -That’s because `DocumentTree` is not the only thing to gain a type -parameter in this new design. When we made `DocTree` take one, it was -only natural to define `map` with one, too. - -We can see how this works out by looking at both `foreach` and `map` -as the agents of our practical goal: transformation of the tree by -transforming the documents therein. `foreach` works like this. - -``` - document transformer - (Document => Unit) - DocumentTree ~~~~~~~~~~~~~~~~~~~> DocumentTree - ------------ ----------------- -initial state final state - (old tree) (same type, “new” - but same tree) -``` - -The way `map` looks at `DocTree` is very similar, and we give it the -responsibilities that `foreach` had, so it is unsurprising that the -“shape” we imagine for transformation is similar. - -``` - document transformer - (D => D2) - DocTree[D] ~~~~~~~~~~~~~~~~~~~~> DocTree[D2] - ------------- ---------------- - initial state final state - (old tree) (changed type, - changed value!) -``` - -The replacement of `D` with `D2` also means that values of type `D` -cannot occur anywhere in the result, as `D` is abstract, so only -appears as `doc` by virtue of being the type parameter passed to -`DocTree` and its data constructors (er, “subclasses”). - -As our result type is `DocTree[D2]`, we have two options, considering -only the result type: - -1. return a `DocTree` with no `D2`s in its representation, one role of - `None` and `Nil` in `Option` and `List` respectively, or -2. make `D2`s from the `D`s in the `DocTree[D]` we have in hand, by - passing them to the ‘document transformer’ `D => D2`. - -Similarly, no `DocTree[D]` values can appear anywhere in the result. -As with the `D`s, they must all be transformed or dropped, with a -different ‘empty’ `DocTree` chosen. - -The dangers of “simplifying” ----------------------------- - -Suppose we instead defined `map` as follows. - -```scala - def map(f: D => D): DocTree[D] -``` - -If you subscribe to the idea of type parameters being for wonky -academics, this is “simpler”. And it’s fine, I suppose, if you only -have one `D` in mind, one document type in mind. Setting aside that -we have four, there is another problem. Let’s take a look at the -“shape” of this transformation. - -``` - document transformer - (D => D) - DocTree[D] ~~~~~~~~~~~~~~~~~~~~> DocTree[D] - ------------- ---------------- - initial state final state - (old tree) (but no promise, - same type!) -``` - -The problem with a `D => D` transformer is that we can’t make promises -that all our data passed through it. After all, a source `d` has the -same type as `f(d)`. We could even get away with - -```scala - def map(f: D => D): DocTree[D] = this -``` - -`map[D2]` is strictly more general. **Even if we have only one `D` in -mind for `DocTree`, it still pays to type-parameterize it and to add -‘type-changing’ extra type parameters like `D2`.** - -The dangers of missing values ------------------------------ - -Have you ever started getting a new bill, then missed a payment -because you thought you were covered for the month? - -Have you ever gone on vacation and, in your relief at having not left -anything important at home, left something behind when packing for -your return trip? - -This kind of thing cannot be characterized in the manner of “well, I -would just get a runtime error if I didn’t have a type checker, so -it’s fine.” Yet it simply falls out of the system we have chosen; -moreover, we have barely begun to consider the possibilities. - -In this series, I have focused on existential types, which we can in -one sense consider merely abstract types that the compiler checks that -we treat as independent, like `D` and `D2`. Existential types are -only one natural outcome of the system of type abstraction brought to -us by type parameters; there are many more interesting conclusions, -like the ones described above. - -Next, in -[“It’s existential on the inside”](existential-inside.md), -we will see how deeply intertwined universal and existential types -really are. diff --git a/src/blog/charity.md b/src/blog/charity.md deleted file mode 100644 index 61b1d7fe..00000000 --- a/src/blog/charity.md +++ /dev/null @@ -1,55 +0,0 @@ -{% - author: ${foundation} - date: "2026-03-05" - tags: [governance] -%} - -# Typelevel Foundation is a 501(c)(3) public charity - -Last August, [we announced the Typelevel Foundation][incorporation], a nonprofit organization incorporated in California. Today, we are proud to share that the Internal Revenue Service has determined that the [Typelevel Foundation] is a 501(c)(3) tax-exempt public charity. - -The process of applying for charitable status is challenging, and especially so for open source organizations, which [frequently receive denials][denials]. Working together with [our attorneys][oatfield], we prepared an application that explained what Typelevel does and why this work is charitable, citing the unique innovations of our projects, our participation in conferences and mentoring programs, and our commitment to open collaboration. We want to recognize and thank our community for their impressive record of impact that we showcased in our application. - -> The determination that the Typelevel Foundation is a public charity is a testament to the **intellectual merit and educational value of the Typelevel community's contributions** to functional programming and open source for over a decade. - -## Benefits and responsibilities - -Our charitable status expands fundraising opportunities while also reducing our operating costs. - -* We are **exempt from paying tax** on the money that we raise. Furthermore, individuals and companies that pay tax in the US may deduct donations to the Foundation on their annual returns. - -* We are **eligible for free and discounted services** from several companies. For example, GitHub now provides us a Team account without seat limits at no cost, saving us thousands of dollars. - -* We have **internationally recognized institutional credibility**. By partnering with the [Swiss Philanthropy Foundation] and the [Maecenata Foundation], we can now receive tax-efficient charitable donations from across Europe. We are also eligible for grants from other 501(c)(3) organizations, including private foundations such as the [Alfred P. Sloan Foundation][sloan]. - -As a charity, we have legal and fiduciary responsibilities that reinforce our own commitments to transparency and community-driven open source development. Above all, **we are accountable to our mission of advancing research and education in functional programming**, prioritizing public benefit over private interests. To remain compliant, we must file [Form 990] annually with the IRS which details our finances and describes how funds were spent towards our charitable purpose. These documents are made available for public inspection. - -## Migrating our financial infrastructure - -For several years, [Open Source Collective] served as the fiscal host for our funds (not to be confused with [Open Collective], which is the web platform). Earlier this week, they transferred our balance to the Foundation's bank account and closed our account with them. If you had setup a recurring donation on our [old Open Collective page][oc-typelevel], it has been canceled. - -**We now [accept donations][supporting] via several platforms**, including [GitHub Sponsors] and [Every.org] (which can handle stock and crypto donations). We have also set up [a new Open Collective page for the Foundation][oc-foundation], connected to our own bank account, which we will use to provide transparency into our finances and spending. - -## What's next - -The first phase of our work focused on the legal restructuring of Typelevel as a nonprofit Foundation. In this next phase, **we will focus on resourcing the Foundation to support its mission**, by fundraising, grant writing, and recruiting new members to our [committees]. - -[incorporation]: evolving-typelevel.md -[Typelevel Foundation]: /foundation/README.md -[denials]: https://www.stradley.com/business-vantage-point-blog/irs-continues-to-close-open-source-software-out-of-tax-exempt-universe -[oatfield]: https://www.christinaoatfield.com/ - -[sloan]: https://sloan.org/ -[Swiss Philanthropy Foundation]: https://www.swissphilanthropy.ch/en/ -[Maecenata Foundation]: https://www.maecenata.eu/en/ - -[Form 990]: https://en.wikipedia.org/wiki/Form_990 - -[Open Source Collective]: https://oscollective.org/ -[Open Collective]: https://opencollective.com/ -[supporting]: /foundation/README.md#supporting-the-foundation -[GitHub Sponsors]: https://github.com/sponsors/typelevel -[Every.org]: https://www.every.org/typelevel -[oc-typelevel]: https://opencollective.com/typelevel -[oc-foundation]: https://opencollective.com/typelevel-foundation -[committees]: /foundation/people.md diff --git a/src/blog/charter-changes.md b/src/blog/charter-changes.md deleted file mode 100644 index 24f60f06..00000000 --- a/src/blog/charter-changes.md +++ /dev/null @@ -1,15 +0,0 @@ -{% - author: ${typelevel} - date: "2023-11-03" - tags: [governance] -%} - -# Typelevel Governance Update - -This week the [Steering Committee][steering] updated the [Typelevel Charter][charter]. The updates are minimal and intended to clarify the role of `Chair`. First, the role has been renamed to `Secretary`. We believe that this renaming is more aligned with the function of the role, as it is not intended to be a leadership position, but is instead responsible for the administrative running of the committee (notably, calling votes). We have also opted to convert the role to a rotating position, with a term of 12 months so that this administrative work is shared across the committee. For those who are particularly interested, [here is the pull request and associated discussion of the changes][charter-pr]. - -We would like to extend our sincere thanks to Ross Baker for his work as Chair of the Typelevel Steering Committee these last years. For the first rotation, Andrew Valencik has volunteered to take on the mantle of calling votes and determining quorum. Thank you Andrew, we look forward to voting when you ask us to! - -[steering]: https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md -[charter]: https://github.com/typelevel/governance/blob/main/CHARTER.md -[charter-pr]: https://github.com/typelevel/governance/pull/116 diff --git a/src/blog/ciris.md b/src/blog/ciris.md deleted file mode 100644 index 371ae3ae..00000000 --- a/src/blog/ciris.md +++ /dev/null @@ -1,489 +0,0 @@ -{% - author: ${vlovgr} - date: "2017-06-21" - tags: [technical] -%} - -# Validated Configurations with Ciris - -The need for configuration arises in almost every application, as we want to be able to run in different environments -- for example, local, testing, and production environments. Configurations are also used as a way to keep secrets, like passwords and keys, out of source code and version control. By having configurations as untyped structured data in files, we can change and override settings without having to recompile our software. - -In this blog post, we'll take a look at configurations with configuration files, to see how we can make the loading process less error-prone, while overcoming obstacles with boilerplate, testing, and validation. We'll also identify when it's suitable to use Scala as a configuration language for improved compile-time safety, convenience, and flexibility; and more specifically, how [Ciris](https://cir.is) helps out. - -### Configuration Files -Traditionally, configuration files, and libraries like [Typesafe Config](https://github.com/typesafehub/config), have been used to load configurations. This involves writing your configuration file, declaring values and how they're loaded, and then writing very similar Scala code for loading that configuration. That kind of boilerplate code typically looks something along the lines of the following example. - - - - -```scala -import com.typesafe.config.{Config, ConfigFactory} - -// The settings class, wrapping Typesafe Config -final case class Settings(config: Config) { - object http { - def apiKey = config.getString("http.api-key") - def timeoutSeconds = config.getInt("http.timeout-seconds") - def port = config.getInt("http.port") - } -} - -// The configuration file, here represented in code -val config = - ConfigFactory.parseString( - """ - |http { - | api-key = ${?API_KEY} - | timeout-seconds = 10 - | port = 989 - |} - """.stripMargin - ).resolve() - -val settings = Settings(config) -``` - -```scala -show(settings) -// Settings(Config(SimpleConfigObject({"http":{"port":989,"timeout-seconds":10}}))) -``` - -This is a tedious, error-prone process that rarely sees any testing efforts. [PureConfig](https://github.com/pureconfig/pureconfig) (and other libraries, like [Case Classy](https://github.com/47deg/case-classy)) were created to remove that boilerplate. Using macros and conventions, they inspect your configuration model (nested case classes) and generate the necessary configuration loading code. This eliminates a lot of errors typically associated with configuration loading. Following is an example of how you can load that very same configuration with PureConfig. - -```scala -final case class HttpSettings( - apiKey: String, - timeoutSeconds: Int, - port: Int -) - -final case class Settings(http: HttpSettings) - -val settings = pureconfig.loadConfig[Settings](config) -``` - -```scala -show(settings) -// Left(ConfigReaderFailures(KeyNotFound("http.api-key", None, Set()), List())) -``` - -### Encoding Validation -In both previous examples, we do not check whether our configurations are valid to use with our application. In the case of Typesafe Config, we hit a runtime exception if the key is missing or if the type conversion fails, and in PureConfig's case, we will instead get a `ConfigReaderFailures`. But in neither case do we care what values are being loaded, as long as they can be converted to the appropriate types. For example, we might require a key of certain length and that it only contains certain characters, the timeout needs to be positive, and the port must be a non-system port number (value in the inclusive range between 1024 and 65535). - -You could write an additional validation step to ensure the configuration is valid after it has been loaded -- which can be tedious to write and requires testing. One could also argue that the types of the configuration values are too permissive: why use `String` for the key if you do not accept all `String` values? And why use an `Int` for timeout and port, if you only allow a limited subset of values? - -We could write these custom types ourselves, including the validation logic, and tell PureConfig how to load them -- which would be tedious to write for many types and would require testing. Another alternative is to use [refined](https://github.com/fthomas/refined), which allows you to do type-level refinements (apply predicates) to types. I found this approach so useful that I wrote a small integration between PureConfig and refined at the end of last year (see [blog post](https://blog.vlovgr.se/posts/2016-12-24-refined-configuration.html)), so that PureConfig can now load refined's types. - -```scala -import eu.timepit.refined.api.Refined -import eu.timepit.refined.numeric.Interval -import eu.timepit.refined.pureconfig._ -import eu.timepit.refined.string.MatchesRegex -import eu.timepit.refined.types.numeric.PosInt -import eu.timepit.refined.W - -type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T] - -type NonSystemPort = Int Refined Interval.Closed[W.`1024`.T, W.`65535`.T] - -final case class HttpSettings( - apiKey: ApiKey, - timeoutSeconds: PosInt, - port: NonSystemPort -) - -final case class Settings(http: HttpSettings) - -val settings = pureconfig.loadConfig[Settings](config) -``` - -```scala -show(settings) -// Left( -// ConfigReaderFailures( -// KeyNotFound("http.api-key", None, Set()), -// List( -// CannotConvert( -// "989", -// "eu.timepit.refined.api.Refined[Int,eu.timepit.refined.boolean.And[eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Less[Int(1024)]],eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Greater[Int(65535)]]]]", -// "Left predicate of (!(989 < 1024) && !(989 > 65535)) failed: Predicate (989 < 1024) did not fail.", -// None, -// "http.port" -// ) -// ) -// ) -// ) -``` - -As you can see in the example above, refined already contains type aliases for many common refinement types, like `PosInt` (for `Int` values greater than zero). You can also easily define your own predicates, like the one for the key and port. The `W` here is a shorthand for [shapeless](https://github.com/milessabin/shapeless)' `Witness`: a way to encode literal-based singleton types (essentially, values on the type-level). If you're using [Typelevel Scala](https://typelevel.org/scala/) with the `-Yliteral-types` flag, you can write values directly in the type declaration, without having to use `Witness`. - -If you're not convinced configurations need to be validated, I can recommend reading the paper [Early Detection of Configuration Errors to Reduce Failure Damage](https://www.usenix.org/system/files/conference/osdi16/osdi16-xu.pdf), and to read through the slides of Leif Wickland's (one of the authors behind PureConfig) recent presentation [Defusing the Configuration Time Bomb](http://leifwickland.github.io/presentations/configBomb/) on the subject. - -In many ways, think of configurations as user input -- would you happily accept any values provided to your application from its users? Probably not: you would validate the input, and sanitize it if possible. Think about configurations in the same way, except that the user here might happen to be a developer of the application. The key here, as discussed in the paper linked above, is to check that your configuration is valid as soon as possible, ideally at compile-time, or as soon as the application starts. We want to avoid situations where we're running the application and suddenly discover that configuration values are invalid or cannot be loaded -- or worse, continue running with an invalid configuration, not to discover issues until much later on. - -### Improving Compile-time Safety -We've now got a way to encode validation in the types of our configurations, and a boilerplate-free way of loading values of those types from configuration files -- is there still room for improvement? To answer that question, we first need to ask why we are using configuration files in the first place. - -Whether you thought about it or not, the main reason for using configuration files is so that we can change settings without having to recompile the software. In my experience, most developers default to using configuration files, and almost always change values by pushing commits to a version control repository. This is followed by a new release of the software, either manually or via a [continuous integration](https://www.agilealliance.org/glossary/continuous-integration) system. In scenarios like this, and in general when it's easy to change and release software (particularly when employing [continuous deployment](https://www.agilealliance.org/glossary/continuous-deployment/) practices), configuration files are not used for the benefit of being able to change values without recompile. - -In such cases, why are we not writing the configurations directly in source code? Christopher Vogt has written an excellent [blog post](https://medium.com/@cvogt/scala-as-a-configuration-language-f075b058a660) (and given a [presentation](https://www.youtube.com/watch?v=ox4IhIL6ojg)) on the subject. The tricky part here is managing values which need to be dynamic in the environment (like the port to bind) and are secret (like passwords and keys). Depending on your requirements and preferences, you more or less have two alternatives. - -* If you know which environments your application will run in, and what the configuration values will be in those environments, you can just include the configurations in your application code (if it has no secrets), or store, compile, and bundle the configuration separately. If you have a requirement that secrets shouldn't touch persistent storage, this might not be a feasible alternative. You might also appreciate the fact that all code relating to your application is in the same version control repository and gets compiled together, in which case this approach might not be suitable. - -* Alternatively, you can include the configuration in your application, but load secrets and values which need to be dynamic from the environment during runtime. This is necessary when configuration values cannot be determined beforehand -- because you do not know what environment your application will run in, or if you use a vault (like [credstash](https://github.com/fugue/credstash), for example) or a configuration service (like [ZooKeeper](https://zookeeper.apache.org), for example) -- or if you prefer having your configuration together with your application code and in the same version control repository. - -In this post, we'll only focus on the latter case. While it’s possible to not use any libraries in the latter case, loading values from the environment typically means dealing with: different environments and configuration sources, type conversions, error handling, and validation. This is where [Ciris](https://cir.is) comes in: a small library, dependency-free at its core, helping you to deal with all of that more easily. - -### Introducing Ciris -Imagine for the moment that no part of your configuration is secret and that your application only ever runs in one environment. You can then just write your configuration in code. - -```scala -import eu.timepit.refined.auto._ - -final case class Config( - apiKey: ApiKey, - timeoutSeconds: PosInt, - port: NonSystemPort -) - -val config = - Config( - apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX", - timeoutSeconds = 10, - port = 4000 - ) -``` - -You then realize that it's a bad idea to put the key in the source code, because source code can easily get into the wrong hands. You decide that you'll instead read an environment variable for the key. Since you want to make sure that your configuration is valid, you have used refinement types, so you'll have to make sure to check that the key conforms to the predicate. You would also welcome a helpful error message if the key is missing or invalid. This sounds like more work than it should be, so let's see how [Ciris](https://cir.is) can help us. - -Ciris method for loading configurations is `loadConfig` and it works in two steps: first define what to load, and then how to load the configuration. For reading a key from an environment variable, you can use `env[ApiKey]("API_KEY")` which reads the environment variable `API_KEY` as an `ApiKey`. Ciris has a refined integration in a separate module, so you just need to add an appropriate import. Loading the configuration is then just a function accepting the loaded values as arguments. - -```scala -import ciris._ -import ciris.refined._ - -val config = - loadConfig( - env[ApiKey]("API_KEY") - ) { apiKey => - Config( - apiKey = apiKey, - timeoutSeconds = 10, - port = 4000 - ) - } -``` - -```scala -show(config) -// Left(ConfigErrors(MissingKey(API_KEY, Environment))) -``` - -Ciris deals with type conversions, error handling, and error accumulation, so you can focus on your configuration. The `loadConfig` method returns an `Either[ConfigErrors, T]` instance back, where `T` is the result of your configuration loading function. You can retrieve the accumulated error messages by using `messages` on `ConfigErrors`. - -```scala -show { config.left.map(_.messages) } -// Left(Vector("Missing environment variable [API_KEY]")) -``` - -If we decided that the port needs to be dynamic as well, we can simply make that change. In the example below, we are using `prop` to read the `http.port` system property for the port to use. As you can see, you are free to mix configuration sources as you please. While we are reading environment variables and system properties in these examples, you could just as well use sources for some configuration services or vaults. - -```scala -val config = - loadConfig( - env[ApiKey]("API_KEY"), - prop[NonSystemPort]("http.port") - ) { (apiKey, port) => - Config( - apiKey = apiKey, - timeoutSeconds = 10, - port = port - ) - } -``` - -```scala -show { config.left.map(_.messages) } -// Left( -// Vector( -// "Missing environment variable [API_KEY]", -// "Missing system property [http.port]" -// ) -// ) -``` - -You might recognize the similarities between `loadConfig` and `ValidatedNel` with an `Apply` instance from [Cats](https://typelevel.org/cats/). That's because it's more or less how `loadConfig` works behind the scenes, except Ciris has its own custom implementation in order to be dependency-free in the core module. - -#### Multiple Environments -We still have to deal with multiple environments in our configuration, assuming there are differences between configurations, or how they are loaded, in the different environments. There are several ways you can do this with Ciris -- one way is to define an enumeration with [enumeratum](https://github.com/lloydmeta/enumeratum) and load values of that enumeration. Let's say we want to use a default configuration when running the application locally, but want to keep the key and port dynamic in the other environments (testing and production). We start by defining an enumeration of the different environments. - -```scala -import _root_.enumeratum._ - -object environments { - sealed abstract class AppEnvironment extends EnumEntry - object AppEnvironment extends Enum[AppEnvironment] { - case object Local extends AppEnvironment - case object Testing extends AppEnvironment - case object Production extends AppEnvironment - - val values = findValues - } -} -``` - -We can use the `withValue` method to define a requirement on a configuration value in order to be able to load our configuration. It works just like `loadConfig`, except it wraps your `loadConfig` statements (think of it as `flatMap`, while `loadConfig` is `map`). If no environment was specified in the environment variable `APP_ENV` or if it was set to `Local`, we will use a default configuration. We'll load the configuration just like before for any other valid environments (testing and production). - -```scala -import environments._ -import ciris.enumeratum._ - -val config = - withValue(env[Option[AppEnvironment]]("APP_ENV")) { - case Some(AppEnvironment.Local) | None => - loadConfig { - Config( - apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX", - timeoutSeconds = 10, - port = 4000 - ) - } - - case _ => - loadConfig( - env[ApiKey]("API_KEY"), - prop[NonSystemPort]("http.port") - ) { (apiKey, port) => - Config( - apiKey = apiKey, - timeoutSeconds = 10, - port = port - ) - } - } -``` - -```scala -show(config) -// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000)) -``` - -An alternative to the above is to have multiple entrypoints (`main` methods) in your application, each running the application with different configuration loading code (or using a default configuration) for the respective environment. Depending on how packaging and running of your application looks like across different environments, this may or may not be a suitable solution. Note that it's very much possible to mix these approaches, and you should strive to find what works best in your case. - -```scala -// Runs the application with the provided configuration -def runApplication(config: Config): Unit = { /* omitted */ } - -object Local { - def main(args: Array[String]): Unit = - runApplication { - Config( - apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX", - timeoutSeconds = 10, - port = 4000 - ) - } -} - -object TestingOrProduction { - def main(args: Array[String]): Unit = - runApplication { - val config = - loadConfig( - env[ApiKey]("API_KEY"), - prop[NonSystemPort]("http.port") - ) { (apiKey, port) => - Config( - apiKey = apiKey, - timeoutSeconds = 10, - port = port - ) - } - - config.fold( - errors => throw new IllegalArgumentException(s"Unable to load configuration: ${errors.messages}"), - identity - ) - } -} -``` - -#### Testing Configurations -Writing your configurations in Scala means you have the flexibility to work with them as you want. You're no longer limited to what can be done with configuration files. Sharing configurations between your application and tests is also very straightforward -- simply make the configuration loading function (and the default configuration) available for the tests. - -```scala -// This can now be accessed from the tests -val defaultConfig = - Config( - apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX", - timeoutSeconds = 10, - port = 4000 - ) - -// This can now be accessed from the tests -val configWith = - (apiKey: ApiKey, port: NonSystemPort) => - Config( - apiKey = apiKey, - timeoutSeconds = 10, - port = port - ) - -val config = - withValue(env[Option[AppEnvironment]]("APP_ENV")) { - case Some(AppEnvironment.Local) | None => - loadConfig(defaultConfig) - case _ => - loadConfig( - env[ApiKey]("API_KEY"), - prop[NonSystemPort]("http.port") - )(configWith) - } -``` - -If you really want to unit test the configuration loading as well, you can do so with minor rewrites. Currently, we depend on some fixed configuration sources for environment variables and system properties (technically, system properties are mutable), but if we instead pass sources (`ConfigSource`s) as arguments, we can read values from those sources using the `read` method. - -The `read` method normally looks for an implicit `ConfigSource` to read from, which would have been perfect if we only used a single source. But since we have multiple sources here, we instead use `read` to redefine `env` and `prop` to read from the provided sources. `ConfigReader[T]` captures the ability to convert from `String` to `T`, where the `String` value has been read from a `ConfigSource`. - -```scala -def config( - envs: ConfigSource[String], - props: ConfigSource[String] -): Either[ConfigErrors, Config] = { - // Custom env which reads from envs - def env[T: ConfigReader](key: String) = - read[T](key)(envs, ConfigReader[T]) - - // Custom prop which reads from props - def prop[T: ConfigReader](key: String) = - read[T](key)(props, ConfigReader[T]) - - withValue(env[Option[AppEnvironment]]("APP_ENV")) { - case Some(AppEnvironment.Local) | None => - loadConfig(defaultConfig) - case _ => - loadConfig( - env[ApiKey]("API_KEY"), - prop[NonSystemPort]("http.port") - )(configWith) - } -} -``` - -We'll then define a couple of helper methods for creating `ConfigSource`s from key-value pairs. The `ConfigSource` type parameter is the type of keys the source can read, which is `String` for both environment variables and system properties. The `ConfigKeyType` is basically the name of the key that can be read, for example `environment variable`. Below we're using predefined instances in the `ConfigKeyType` companion object. - -```scala -def envs(entries: (String, String)*): ConfigSource[String] = - ConfigSource.fromMap(ConfigKeyType.Environment)(entries.toMap) - -def props(entries: (String, String)*): ConfigSource[String] = - ConfigSource.fromMap(ConfigKeyType.Property)(entries.toMap) -``` - -We can test our `config` method using different combinations of environment variables and system properties. Note that `envs` and `props` have the same type, so if you want to avoid using them interchangeably, you can define custom wrapper types for them. We'll leave that out here for sake of simplicity. I've found that it's not very common to read values from more than one `ConfigSource`, but as it's definitely possible, it can be worth making sure you do not mix them up. - -```scala -show { config(envs(), props()) } -// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000)) - -show { - config( - envs("APP_ENV" -> "Local"), - props() - ) -} -// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000)) - -show { - config( - envs("APP_ENV" -> "QA"), - props() - ).left.map(_.messages) -} -// Left( -// Vector( -// "Environment variable [APP_ENV] with value [QA] cannot be converted to type [$line34.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$environments$AppEnvironment]" -// ) -// ) - -show { - config( - envs("APP_ENV" -> "Production"), - props() - ).left.map(_.messages) -} -// Left( -// Vector( -// "Missing environment variable [API_KEY]", -// "Missing system property [http.port]" -// ) -// ) - -show { - config( - envs( - "APP_ENV" -> "Production", - "API_KEY" -> "changeme" - ), - props() - ).left.map(_.messages) -} -// Left( -// Vector( -// "Environment variable [API_KEY] with value [changeme] cannot be converted to type [eu.timepit.refined.api.Refined[String,eu.timepit.refined.string.MatchesRegex[java.lang.String(\"[a-zA-Z0-9]{25,40}\")]]]: Predicate failed: \"changeme\".matches(\"[a-zA-Z0-9]{25,40}\").", -// "Missing system property [http.port]" -// ) -// ) - -show { - config( - envs( - "APP_ENV" -> "Production", - "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow" - ), - props() - ).left.map(_.messages) -} -// Left(Vector("Missing system property [http.port]")) - -show { - config( - envs( - "APP_ENV" -> "Production", - "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow" - ), - props("http.port" -> "900") - ).left.map(_.messages) -} -// Left( -// Vector( -// "System property [http.port] with value [900] cannot be converted to type [eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Interval.Closed[Int(1024),Int(65535)]]]: Left predicate of (!(900 < 1024) && !(900 > 65535)) failed: Predicate (900 < 1024) did not fail." -// ) -// ) - -show { - config( - envs( - "APP_ENV" -> "Production", - "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow" - ), - props("http.port" -> "4000") - ) -} -// Right(Config(X9aKACPtircCrrFKYhwPr7fXx8srow, 10, 4000)) -``` - -Finally, when running the application, simply provide the actual `ConfigSource`s for environment variables and system properties. - -```scala -show { config(ConfigSource.Environment, ConfigSource.Properties) } -// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000)) -``` - -### Conclusion -In this blog post, we've seen how we can make the configuration loading process, with configuration files, less error-prone, by eliminating the boilerplate code with [PureConfig](https://github.com/pureconfig/pureconfig), and encoding validation with [refined](https://github.com/fthomas/refined) -- seeing how the two libraries can work together seamlessly. - -We've also identified cases where we can use Scala as a configuration language, seeing that it's particularly suitable in cases where it's easy to change and deploy software. We've introduced the challenge of loading configuration values from the environment and seen how [Ciris](https://cir.is) can help you with that, letting you focus on the configuration. We've seen that Scala configurations can provide more compile-time safety and flexibility than traditional configurations with configuration files. - -If you're looking for more information on Ciris, the project's website ([https://cir.is](https://cir.is)) is a good start. -There's also a [usage guide](https://cir.is/docs/basics) and [API documentation](https://cir.is/api) which expands on what's been discussed here. diff --git a/src/blog/code-of-conduct.md b/src/blog/code-of-conduct.md deleted file mode 100644 index 17fdbfb6..00000000 --- a/src/blog/code-of-conduct.md +++ /dev/null @@ -1,34 +0,0 @@ -{% - author: ${typelevel} - date: "2024-03-11" - tags: [governance] -%} - -# New Code of Conduct - -We have [recently adopted a new Code of Conduct and Enforcement Policy](https://github.com/typelevel/governance/pull/129). This change was approved by the Steering committee after a 1 month discussion and voting period. Thank you to everyone who engaged! - -The scope of the Typelevel Code of Conduct and Enforcement Policy encompasses both organization **and** affiliate projects (as described in the [Typelevel Charter](https://github.com/typelevel/governance/blob/main/CHARTER.md)). -While the Typelevel Charter has always specified that affiliate projects must adhere to the Typelevel organization policies, including the Code of Conduct, this has not been enforced in practice. - -Prior to this change the Typelevel Code of Conduct was a fork of the Scala Code of Conduct. We, the Typelevel Steering Committee, are choosing the Python Software Foundation Code of Conduct to fork because it has an accompanying enforcement policy, and there is associated training available. Some Typelevel Steering Committee members engaged in this training through [Otter Technology](https://otter.technology/code-of-conduct-training/) in late 2023. All Code of Conduct Committee members will be encouraged to take this (or equivalent) training going forward. - -We believe our community is already a kind and welcoming place. -However, a Code of Conduct must be enforced to maintain community trust and safety. -Additionally, an enforcement policy is useful to provide transparency and accountability on how the Code of Conduct Committee will work. - - -### What happens next? -Now that the Code of Conduct and Enforcement Policy have been voted in by the Typelevel Steering Committee, we will begin updating the `CODE_OF_CONDUCT` files in organization project repositories. - -### As an affiliate project maintainer, what should I expect? -All affiliate projects are expected to adopt the Typelevel Code of Conduct. -We will open pull requests to update each affiliate project's `CODE_OF_CONDUCT` file. -If there are any concerns, we are available for discussion and we encourage affiliate maintainers to please reach out. -Ultimately, if a project chooses not to adopt the Typelevel Code of Conduct, maintainers can close the PR, and we'll handle removing the project from the affiliate project list. -This is totally fine and we support your choices! -We believe open source developers are free to choose the projects they contribute to and the communities they support ♥ - -### Can an affiliate project maintainer participate in Code of Conduct enforcement? -Affiliate project's can list additional moderators in their `CODE_OF_CONDUCT` file, that the Typelevel Code of Conduct Committee will work with as described in the Enforcement Policy "Affiliate project processes" section. -Additionally, if this work interests you, keep an eye out for future calls for Typelevel Code of Conduct Committee members! diff --git a/src/blog/community-safety.md b/src/blog/community-safety.md deleted file mode 100644 index 6333c439..00000000 --- a/src/blog/community-safety.md +++ /dev/null @@ -1,25 +0,0 @@ -{% - author: ${typelevel} - date: "2021-04-27" - tags: [social] -%} - -# Community Safety - -Effective today, Jon Pretty is barred from participating in Typelevel projects and events. -We make this decision based on well-substantiated reports of predatory behavior at Scala conferences, including conferences at which Typelevel Summits were co-located. - -All conference organizers aspire to create safe spaces for attendees, and succeed to varying degrees. -Our efforts have been insufficient. -We pledge to review our process and practices for event safety, and will provide details on concrete actions in this space before the next Typelevel Summit is held. - -The Typelevel Steering Committee: -* Alexandru Nedelcu -* Christopher Davenport -* Daniel Spiewak -* Kailuo Wang -* Lars Hupel -* Luka Jacobowitz -* Michael Pilquist -* Rob Norris -* Ross A. Baker diff --git a/src/blog/concurrency-in-ce3.md b/src/blog/concurrency-in-ce3.md deleted file mode 100644 index fac0fdc4..00000000 --- a/src/blog/concurrency-in-ce3.md +++ /dev/null @@ -1,615 +0,0 @@ -{% - author: ${rahsan} - date: "2020-10-30" - tags: [technical] -%} - -# Concurrency in Cats Effect 3 - -Cats Effect 3 is just around the corner! The library has seen several major -changes between 2.0 and 3.0, so in an effort to highlight those changes, we -will be releasing a series of blog posts covering a range of topics. If you -would like to see a blog post about a particular subject, don't hesitate to -reach out! You can try out early Cats Effect 3 releases -[here](https://github.com/typelevel/cats-effect/releases). - -### Introduction -In this post, we offer a broad overview of the concurrency model that serves -as the foundation for Cats Effect and its abstractions. We discuss what fibers -are and how to interact with them. We also talk about how to leverage the -concurrency model to build powerful concurrent state machines. Example programs -are written in terms of `cats.effect.IO`. - -Before going any further, let's briefly review what concurrency is, how it's -useful, and why it's tedious to work with. - -### Concurrency -Concurrent programming is about designing programs in which multiple logical -threads of control are executing at the same time. There is a bit to unpack -in this definition: what are logical threads and what does it mean for them to -execute "at the same time?" - -A logical thread is merely a description of a sequence of discrete actions. -An action is one of the most primitive operations that can be expressed in -the host language. - -In programs that are written in traditional high-level languages like Java or -C++, these logical threads are typically represented by native threads that -are managed by the operating system. The actions that comprise these threads -are native processor instructions or VM bytecode instructions. In programs that -are written with Cats Effect, these logical threads are represented by fibers, -and the actions that comprise them are `IO` values. - -What does it mean for logical threads to execute at the same time? Because the -actions of a logical thread are discrete, the actions of many logical threads -can be interleaved into one or more streams of actions. This interleaving is -largely influenced by external factors such as scheduler preemption and I/O -operations, so it is usually nondeterministic. More specifically, in the -absence of synchronization, there is no guarantee that the actions of distinct -logical threads occur in some particular order. - -Another common perspective is that concurrency generates a partial order among -all the actions of all the logical threads in a program. Actions _within_ a -logical thread are ordered consistently with program order. Actions _between_ -multiple logical threads are not ordered in the absence of synchronization. - -#### Concurrency is useful -Concurrency is a tool for designing high-performance applications that are -responsive and resilient. These applications typically involve multiple -interactions or tasks that must happen at the same time. For example, a -computer game needs to listen for keyboard input, play sound effects, and run -game loop logic, all while rendering graphics to the screen. A multiplayer game -must also communicate with a network server to exchange game state information. - -Traditional sequential programming models quickly become inadequate when -applied to building these kinds of responsive programs. The computer game -program must perform all of the tasks described above at the same time; it -wouldn't be a very good game if we couldn't respond to player input while -playing a sound! Concurrency enables us to structure our program so that -each interaction is confined to its own logical thread(s), all of which -execute at the same time. - -Concurrency complements modular program design. Interactions like graphics -rendering and network communication are largely unrelated; it would be tedious -and messy to design a program that constantly switches between graphics and -audio I/O operations. Instead of having one complex thread that performs -every interaction, we can confine each interaction to its own logical thread. -This allows us to think about and code each interaction independently of each -other while having the assurance that they occur at the same time. The -modularity that concurrency affords makes for programs that are much easier to -understand, maintain, and evolve. - -#### Concurrency is hard -Concurrency is notoriously cumbersome to work with. This is no surprise to any -developer that has worked with threads and locks in any mainstream programming -language. Once we start dealing with concurrency, we have to deal with a slew -concerns: deadlocks, starvation, race conditions, thread leaks, thread blocking -and so on. A bug in any one of these concerns can have devastating consequences -in terms of correctness and performance. - -The most popular method for achieving concurrency in Scala is with `Future`, -which enables the evaluation of asynchronous operations. However, it is plainly -insufficient for those of us who practice strict functional programming. -Furthermore, `Future` doesn't expose first-class features like cancellation, -finalization, or synchronization that are crucial for building safe concurrent -applications. - -Akka actors are another common way to achieve concurrency in Scala. For similar -reasons as `Future`, they are also insufficient in strict functional -programming, however, one could certainly build a pure actor library on top of -the Cats Effect's concurrency model. - -### Cats Effect Concurrency -Cats Effect takes the perspective that concurrency is a necessary technique -for building useful applications, but existing tools for achieving concurrency -in Scala are tedious to work with. A goal of Cats Effect is then to provide -library authors and application developers a concurrency model that is safe and -simple to work with. - -Most users should never directly interact with the concurrency model of Cats -Effect because it is a low-level API. This is intentional: low-level -concurrency constructs are inherently unsafe and effectful, so forcing users -to touch it would only be a burden for them. Cats Effect and other libraries -provide higher-level and safer abstractions that users can exploit to achieve -concurrency without having to understand what's happening under the hood. -For example, http4s achieves concurrency on requests by spawning a fiber for -every request it receives; users need only specify the request handling code -that is eventually run inside the fiber. - -That being said, it can be tremendously helpful to learn about Cats Effect's -internals and concurrency model to understand how our applications behave and -how to tune them, so let's jump into it. - -### Fibers -Cats Effect chooses fibers as the foundation for its concurrency model. The -main benefit of fibers is that they are conceptually similar to native threads: -both are types of logical threads that describe a sequence of computations. -Unlike native threads, fibers are not associated with any system resources. -They coexist and are scheduled completely within the userspace process, -independently of the operating system. This mode of concurrency reaps major -benefits for users and performance: - -1. Fibers are incredibly cheap in terms of memory overhead so we can create -hundreds of thousands of them without thrashing the process. This means that we -also don't need to pool fibers; we just create a new one whenever we need it. -2. Context switching between fibers is orders of magnitude faster than context -switching between threads. -3. Blocking a fiber doesn't necessarily block a native thread; this is called -semantic blocking. This is particularly true for nonblocking I/O and other -asynchronous operations. - -Concretely, a fiber is a logical thread that encapsulates the execution of an -`IO[A]` program, which is a sequence of `IO` effects (actions) that are bound -together via `flatMap`. Fibers are logical threads, so they can run -concurrently. - -The active execution of some fiber of an effect `IO[A]` is represented by the -type `FiberIO[A]`. The execution of a `FiberIO[A]` terminates with one of three -possible outcomes, which are encoded by the datatype `OutcomeIO[A]`: - -1. `Succeeded`: indicates success with a value of type `A` -2. `Errored`: indicates failure with a value of type `Throwable` -3. `Canceled`: indicates abnormal termination via cancellation - -Additionally, a fiber may never produce an outcome, in which case it is said to -be nonterminating. - -Cats Effect exposes several functions for interacting with fibers directly. -We'll explore parts of this API in the following sections. Again, fibers are -considered to be an unsafe and low-level feature and must be given more caution -than we offer in the examples. Users are encouraged to use the higher-level -concurrency constructs that Cats Effect provides. - -#### Starting and joining fibers -The most basic action of concurrency in Cats Effect is to start or spawn a new -fiber. This requests to the scheduler to begin the concurrent execution of a -program `IO[A]` inside a new logical thread. The actions of the current fiber -and the spawned fiber are interleaved in a nondeterministic fashion. - -After a fiber is `start`ed, it can be `join`ed which semantically blocks the -joiner until the joinee has terminated, after which `join` will return the -outcome of the joinee. Let's take a look at an example. - -Let's take a look at an example that demonstrates spawning and joining of -fibers, as well as the nondeterministic interleaving of their executions. - -```scala -import cats.effect.{IO, IOApp} -import cats.syntax.all._ - -object ExampleOne extends IOApp.Simple { - def repeat(letter: String): IO[Unit] = - IO.print(letter).replicateA(100).void - - override def run: IO[Unit] = - for { - fa <- (repeat("A") *> repeat("B")).as("foo!").start - fb <- (repeat("C") *> repeat("D")).as("bar!").start - // joinWithNever is a variant of join that asserts - // the fiber has an outcome of Succeeded and returns the - // associated value. - ra <- fa.joinWithNever - rb <- fb.joinWithNever - _ <- IO.println(s"\ndone: a says: $ra, b says: $rb") - } yield () -} -``` - -In this program, the main fiber spawns two fibers, one which prints `A` 100 -times and `B` 100 times, and another which prints `C` 100 times and `D` 100 -times. It then joins on both fibers, awaiting their termination. Here is one -possible output from an execution of the program: - -``` -AAAAAAAAAAAAAAAAAAAAAAACACACACACACACACACACACACACACACACACACACACACACAACACACACACACACACACACACAACACAAACACACACACACACACACACACACACACACACACACACACACACACACACACACCACACACACACACACACACACACACCCCCBBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBCBBBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDBDDBDBDBDBDBDBDBDDDDDDDDDDDDDDDDDDDDDD -done: a says: foo!, b says: bar! -``` - -We can observe that there is no consistent ordering between the effects of -separate fibers; it is completely nondeterministic! However, the effects -_within_ a given fiber are always sequentially consistent, as dictated by -program order; `A` is never printed after `B`, and `C` is never printed -after `D`. This is how `join` imposes an ordering on the execution of the -fibers: the main fiber will only ever print the `done:` message after the -two spawned fibers have completed. - -It is recommended to use the safer `IO.background` instead of `IO.start` for -spawning fibers. - -#### Canceling fibers -A fiber can be canceled after its execution begins with the `FiberIO#cancel` -function. This semantically blocks the current fiber until the target fiber -has finalized and terminated, and then returns. Let's take a look at an -example. - -```scala -import cats.effect.{IO, IOApp} -import cats.syntax.all._ -import scala.concurrent.duration._ - -object ExampleTwo extends IOApp.Simple { - override def run: IO[Unit] = - for { - fiber <- IO.println("hello!").foreverM.start - _ <- IO.sleep(5.seconds) - _ <- fiber.cancel - } yield () -} -``` - -In this program, the main fiber spawns a second fiber that continuously prints -`hello!`. After 5 seconds, the main fiber cancels the second fiber and then the -program exits. - -Cats Effect's concurrency model and cancellation model interact very closely -with each other, however, the latter is out of scope for this post. It will be -discussed in detail in a future post, but in the meantime, visit the Scaladoc -pages for `MonadCancel` and `GenSpawn`. - -#### Racing fibers -Cats Effect exposes several utility functions for racing `IO` actions (or their -fibers) against eachother. Let's take a look at some of them: - -```scala -object IO { - def racePair[A, B](left: IO[A], right: IO[B]): IO[Either[(OutcomeIO[A], FiberIO[B]), (FiberIO[A], OutcomeIO[B])]] - // higher-level functions - def race[A, B](left: IO[A], right: IO[B]): IO[Either[A, B]] - def both[A, B](left: IO[A], right: IO[B]): IO[(A, B)] -} -``` - -`racePair` races two fiber and returns the outcome of the winner along with a -`FiberIO` handle to the loser. `race` races two fibers and returns the -successful outcome of the winner after canceling the loser. `both` races two -fibers and returns the successful outcome of both (in other words, it runs both -fibers concurrently and waits for both of them to complete). - -`racePair` seems a bit hairy to work with, so let's try an example out with -`race`: - -```scala -import cats.effect.{IO, IOApp} -import cats.syntax.all._ - -object ExampleThree extends IOApp.Simple { - def factorial(n: Long): Long = - if (n == 0) 1 else n * factorial(n - 1) - - override def run: IO[Unit] = - for { - res <- IO.race(IO(factorial(20)), IO(factorial(20))) - _ <- res.fold( - a => IO.println(s"Left hand side won: $a"), - b => IO.println(s"Right hand side won: $b") - ) - } yield () -} -``` - -In this program, we're racing two fibers, both of which calculate the 20th -factorial. Running this program with many iterations demonstrates that either -fiber can win. - -### Communication -We have seen how fibers can directly communicate with each other via `start`, -`join`, and `cancel`. These mechanisms enable bidirectional communication, -but only at the beginning and end of a fiber's lifetime. It's natural to ask -if there are other ways in which fibers can communicate, particularly during -their lifetime. - -Shared memory is an alternative means by which fibers can indirectly -communicate and synchronize with each other. Cats Effect exposes two primitive -concurrent data structures that leverage shared memory: `Ref` and `Deferred`. - -#### `Ref` -`Ref` is a concurrent data structure that represents a mutable variable. It -is used to hold state that can be safely accessed and modified by many -contending fibers. Let's take a look at its basic API: - -```scala -trait Ref[A] { - def get: IO[A] - def set(a: A): IO[Unit] - def update(f: A => A): IO[Unit] -} -``` - -`get` reads and returns the current value of the `Ref`. `set` sets the current -value of the `Ref`. `update` atomically reads and sets the current value of the -`Ref`. Let's take a look at an example. - -```scala -import cats.effect.{IO, IOApp} -import cats.syntax.all._ - -object ExampleFour extends IOApp.Simple { - override def run: IO[Unit] = - for { - state <- IO.ref(0) - fibers <- state.update(_ + 1).start.replicateA(100) - _ <- fibers.traverse(_.join).void - value <- state.get - _ <- IO.println(s"The final value is: $value") - } yield () -} -``` - -In this program, the main fiber starts 100 fibers, each of which attempts to -concurrently update the state by atomically incrementing its value. Next, the -main fiber joins on each spawned fiber one after the other, waiting for -their collective completion. Finally, after the spawned fibers are complete, -the main fiber retrieves the final value of the state. The program should -produce the following output: - -``` -The final value is: 100 -``` - -#### `Deferred` -`Deferred` is a concurrent data structure that represents a condition variable. -It is used to semantically block fibers until some arbitrary condition has been -fulfilled. Let's take a look at its basic API: - -```scala -trait Deferred[A] { - def complete(a: A): IO[Unit] - def get: IO[A] -} -``` - -`get` blocks all calling fibers until the `Deferred` has been completed with a -value, after which it will return that value. `complete` completes the -`Deferred`, unblocking all waiters. A `Deferred` can not be completed more than -once. Let's take a look at an example. - -```scala -import cats.effect.{IO, IOApp} -import cats.effect.kernel.Deferred -import cats.syntax.all._ -import scala.concurrent.duration._ - -object ExampleFive extends IOApp.Simple { - def countdown(n: Int, pause: Int, waiter: Deferred[IO, Unit]): IO[Unit] = - IO.println(n) *> IO.defer { - if (n == 0) IO.unit - else if (n == pause) IO.println("paused...") *> waiter.get *> countdown(n - 1, pause, waiter) - else countdown(n - 1, pause, waiter) - } - - override def run: IO[Unit] = - for { - waiter <- IO.deferred[Unit] - f <- countdown(10, 5, waiter).start - _ <- IO.sleep(5.seconds) - _ <- waiter.complete(()) - _ <- f.join - _ <- IO.println("blast off!") - } yield () -} -``` - -In this program, the main fiber spawns a fiber that initiates a countdown. When -the countdown reaches 5, it waits on a `Deferred` which is completed 5 seconds -later by the main fiber. The main fiber then waits for the countdown to -complete before exiting. The program should produce the following output: - -``` -10 -9 -8 -7 -6 -5 -paused... -4 -3 -2 -1 -0 -blast off! -``` - -#### Building a concurrent state machine -`Ref` and `Deferred` are often composed together to build more powerful and -more complex concurrent data structures. Most of the concurrent data types in -the `std` module in Cats Effect are implemented in terms of `Ref` and/or -`Deferred`. Example include `Semaphore`, `Queue`, and `Hotswap`. - -In our next example, we create simple concurrent data structure called `Latch` -that is blocks a waiter until a certain number of internal latches have been -released. Here is the interface for `Latch`: - -```scala -trait Latch { - def release: IO[Unit] - def await: IO[Unit] -} -``` - -Next, we need to define the state machine for `Latch`. We can be in two -possible states. The first state reflects that the `Latch` is still active -and is waiting for more releases. We need to track how many latches are still -remaining, as well as a `Deferred` that is used to block new waiters. The -second state reflects that the `Latch` has been completely released and will no -longer block waiters. - -```scala -sealed trait State -final case class Awaiting(latches: Int, waiter: Deferred[IO, Unit]) extends State -case object Done extends State -``` - -We can implement the `Latch` interface now. We create a `Ref` that holds our -state machine, and then a `Deferred` that block waiters. The initial state of -a `Latch` is the `Awaiting` state with a user-specified number of latches -remaining. - -The `release` method atomically modifies the state based on its current value: -if the current state is `Awaiting` and there is more than one latch remaining, -then subtract by one, but if there is only one latch left, then transition to -the `Done` state and unblock all the waiters. If the current state is `Done`, -then do nothing. - -The `await` method inspects the current state; if it is `Done`, then allow the -current fiber to pass through, otherwise, block the current fiber with the -`waiter`. - -```scala -object Latch { - def apply(latches: Int): IO[Latch] = - for { - waiter <- IO.deferred[Unit] - state <- IO.ref[State](Awaiting(latches, waiter)) - } yield new Latch { - override def release: IO[Unit] = - state.modify { - case Awaiting(n, waiter) => - if (n > 1) - (Awaiting(n - 1, waiter), IO.unit) - else - (Done, waiter.complete(())) - case Done => (Done, IO.unit) - }.flatten.void - override def await: IO[Unit] = - state.get.flatMap { - case Done => IO.unit - case Awaiting(_, waiter) => waiter.get - } - } -} -``` - -Finally, we can use our new concurrent data type in a runnable example: - -```scala -object ExampleSix extends IOApp.Simple { - override def run: IO[Unit] = - for { - latch <- Latch(10) - _ <- (1 to 10).toList.traverse { idx => - (IO.println(s"$idx counting down") *> latch.release).start - } - _ <- latch.await - _ <- IO.println("Got past the latch") - } yield () -} -``` - -This program creates a `Latch` with 10 internal latches and spawns 10 fibers, -each of which releases one internal latch. The main fiber awaits against the -`Latch`. Once all 10 fibers have released a latch, the main fiber is unblocked -and can proceed. The output of the program should resemble the following: - -``` -1 counting down -3 counting down -6 counting down -2 counting down -8 counting down -9 counting down -10 counting down -4 counting down -5 counting down -7 counting down -Got past the latch -``` - -Notice how the latch serves as a form of synchronization that influences the -ordering of effects among the fibers; the main fiber will never proceed until -after the `Latch` is completely released. - -### Scheduling -We've talked about fibers as a conceptual model with which Cats Effect -implements concurrency. One aspect of this that we haven't addressed is: how do -fibers actually execute? Our Scala applications ultimately run on the JVM (or a -JavaScript runtime if we're using Scala.js), so fibers must ultimately be -mapped to native threads in order for them to actually run. The scheduler is -responsible for determining how this mapping takes place. - -On the JVM, Cats Effect uses an M:N scheduling model to map fibers to threads. -In practice, this means that a large number of fibers is multiplexed onto -much smaller pools of native threads. A typical application will reserve a -fixed-size pool for CPU-bound tasks, an unbounded pool for blocking tasks, -and several event handler pools for asynchronous tasks. Throughout its -lifetime, a fiber will migrate between these pools to accomplish its various -tasks. In JavaScript runtimes, a M:1 scheduling model is employed, where all -active fibers are scheduled onto a single native thread for execution. - -Another aspect of scheduling is how context switching of fibers takes place, -which has many implications around the fairness and throughput properties of a -concurrent application. Like many other lightweight thread runtimes, Cats -Effect's default scheduler exhibits cooperative multitasking in which fibers -can explicitly "yield" back to the scheduler, allowing a waiting fiber to run. -The yielded fiber will be scheduled to resume execution at some later time. -This is achieved with the `IO.cede` function. - -Cats Effect's default scheduler also supports autoyielding which is a form of -preemptive multitasking. As we mentioned earlier, logical threads are composed -of a possibly infinite sequence of actions. Autoyielding "preempts" or forcibly -yields a fiber after it runs a certain number of actions, enabling waiting -fibers to proceed. This is particularly important on runtimes that run with 1 -or 2 native threads; a fiber that runs forever but never `cede`s will starve -other fibers of compute time. - -### Parallelism -One aspect of multithreaded programming that we have neglected to mention so -far is parallelism. In theory, parallelism is completely independent of -concurrency; parallelism is about simultaneous execution whereas concurrency -is about interleaved execution. - -Parallelism is typically achieved by exploiting multiple CPU cores or even -multiple, independent machines to run a set of tasks much faster than they -would run on a single CPU or machine. Parallelism is also not necessarily -nondeterministic; we can run a set of deterministic tasks in parallel multiple -times and expect to get back the same result every time. For our purposes, -parallelism can be paired with concurrency to speed up the execution of many -logical threads. This is exactly what JVMs already do: multiple native threads -run simultaneously, resulting in higher throughput of tasks. - -An obscure but crucial point here is that concurrency can be achieved without -parallelism; this is called single-threaded concurrency. We've already seen an -example of this: JavaScript runtimes run on a single compute thread, so the -execution of all fibers must take place on that thread as well! - -### Exercises - -1. Why is the low-level fiber API designated as unsafe? Hint: consider how the -fiber API interacts with cancellation. -2. Implement `timeout` in terms of `IO.race`. `timeout` runs some action for up -to a specified duration, after which it throws an errors. -```scala -def timeout[A](io: IO[A], duration: FiniteDuration): IO[A] -``` -2. Implement `parTraverse` in terms of `IO.both`. `parTraverse` is the same as -`traverse` except all `IO[B]` are run in parallel. -```scala -def parTraverse[A](as: List[A])(f: A => IO[B]): IO[List[B]] -``` -3. Implement `Semaphore` in terms of `Ref` and `Deferred`. -```scala -trait Semaphore { - def acquire: IO[Unit] - def release: IO[Unit] -} -object Semaphore { - def apply(permits: Int): IO[Semaphore] -} -``` -4. Implement `Queue` in terms of `Ref` and `Deferred`. -```scala -trait Queue[A] { - def put(a: A): IO[Unit] - def tryPut(a: A): IO[Boolean] - def take: IO[A] - def tryTake: IO[Option[A]] - def peek: IO[Option[A]] -} -object Queue { - def apply[A](length: Int): IO[Queue[A]] -} -``` -5. `Stateful` is a typeclass in Cats MTL that characterizes a monad's ability -to access and manipulate state. This is typically used in monad transformer -stacks in conjunction with the `StateT` transformer. Is it possible to create -a `Stateful` instance given a `Ref`? diff --git a/src/blog/conf-cadiz-2016-09-30.md b/src/blog/conf-cadiz-2016-09-30.md deleted file mode 100644 index 353deb94..00000000 --- a/src/blog/conf-cadiz-2016-09-30.md +++ /dev/null @@ -1,43 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-09-30" - event-date: "September 30, 2016" - event-location: "Palacio de Congresos de Cádiz" - tags: [events] -%} - -# Typelevel Community Conference Cádiz - -@:include(/img/places/cadiz.md) - -## About the Conference - -A day of Typelevel sessions, co-located with [Lambda World](http://www.lambda.world). - -On Friday, September 30th, [Lambda World](http://www.lambda.world) hosted a Typelevel Unconference during the morning and a few workshops in the afternoon. The day concluded with a community dinner where attendees can discuss functional concepts while enjoying Flamenco music. - -## Venue - -This event took place at the Palacio de Congresos de Cádiz. - - - -## Sponsors - -We'd like to thank all our sponsors who helped to make the conference happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png) { alt: 47 Degrees, title: 47 Degrees, style: legacy-event-sponsor }](http://www.47deg.com/)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(http://www.lambda.world/img/partners/logo-TheWorkshop.png) { alt: The Workshop, title: The Workshop, style: legacy-event-sponsor }](http://www.theworkshop.com/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(http://www.lambda.world/img/partners/logo-Ciklum.png) { alt: Ciklum, title: Ciklum, style: legacy-event-sponsor }](https://www.ciklum.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(http://www.lambda.world/img/partners/logo-workday.svg) { alt: Workday, title: Workday, style: legacy-event-sponsor }](https://www.workday.com/)@:@ -@:@ diff --git a/src/blog/conf-cadiz-2017-10-26.md b/src/blog/conf-cadiz-2017-10-26.md deleted file mode 100644 index b8900ac8..00000000 --- a/src/blog/conf-cadiz-2017-10-26.md +++ /dev/null @@ -1,34 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2017-10-26" - event-date: "October 26th, 2017" - event-location: "Palacio de Congresos de Cádiz" - tags: [events] -%} - -# Typelevel UnConference - Lambda World Cadiz 2017 - -@:include(/img/places/cadiz.md) - -## About the Conference - -A day of Typelevel sessions, co-located with [Lambda World](http://www.lambda.world). - -At Lambda.World, we planned the first day to be an introduction to kick things off, with different workshops and events. Included in your Lambda World ticket price is the ability enjoy round table discussions and workshops to see, in practice, how the functional programming paradigm works. - -We’ll have a Typelevel Unconference during the morning and introductory workshops and a Scala Spree in the afternoon where we’re going to talk and to work on different Open Source projects from the Scala community. - -## Venue - -This event will take place at the Palacio de Congresos de Cádiz. - - - -## Sponsors - -We'd like to thank all our sponsors who are helping to make the conference happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png) { alt: 47 Degrees, title: 47 Degrees, style: legacy-event-sponsor }](http://www.47deg.com/)@:@ -@:@ diff --git a/src/blog/confronting-racism.md b/src/blog/confronting-racism.md deleted file mode 100644 index 69a34efe..00000000 --- a/src/blog/confronting-racism.md +++ /dev/null @@ -1,146 +0,0 @@ -{% - author: ${mtomko} - date: "2020-06-17" - tags: [social] -%} - -# Confronting Racism - -In the wake of unrest in the United States and elsewhere following the -deaths of George Floyd, Ahmaud Arbery, Breonna Taylor, and countless -others, it is important for us to consider the impact of racism in -every sector of our lives. This includes taking a long, hard look at -race and racism in our community - -In this blog post, I want to discuss the problem briefly and then -begin to look at ways that individuals and organizations can learn -about racism and to make changes to improve diversity, equity, and -inclusion. - -### The Problem -The United States Equal Employment Opportunity Commission (EEOC) -produced a -[report](https://www.eeoc.gov/special-report/diversity-high-tech) -showing some sobering statistics about the employment of African -Americans in the technology sector. In the private industry overall, -African Americans represented 14.4 percent of the workforce, but in -the technology sector that number was just 7.4 percent. For -comparison, according to [recent census -data](https://www.census.gov/quickfacts/fact/table/US/RHI225218), -black or African Americans make up approximately 13.4 percent of the -U.S. population. The problem becomes even worse at the executive -level: African Americans fill between 2 and 5.3 percent of executive -roles in technology firms. - -A [2016 commissioned -paper](https://www.nap.edu/read/24926/chapter/14#183) produced by the -U.S. National Academies of Science found that African American and -Hispanic people were not underrepresented in terms of computer science -degrees (about 9.2%), but that they were underrepresented in the labor -force (only 5.9%). That study did not address the cause of the -disparity - we are left to speculate if they encountered bias in -hiring and interviewing, hostile work environments, or something else. - -Also in 2016, the Harvard Business Review [reported on a -study](https://hbr.org/2016/04/if-theres-only-one-woman-in-your-candidate-pool-theres-statistically-no-chance-shell-be-hired) -on bias in hiring. Their findings showed that when choosing between -three candidates for a position, people tended to choose a black -candidate only if the candidate pool had at least 2 black candidates. -It is worth noting that the effect was the same for gender - a -candidate pool of two men and one women virtually always resulted in -committees recommending a man. - -If we are honest with ourselves, these statistics, while sobering, -should not really surprise us. Looking around at our fellow employees, -open source collaborators, and conference attendees, we know that most -are white men. With that, the question becomes, how do we begin to -confront the racism that appears endemic in our industry? - -### What Can We Do? - -#### Workplace culture -In May, 2020, the Harvard Business Review issued a -[report](https://www.umass.edu/employmentequity/what-works-evidence-based-ideas-increase-diversity-equity-and-inclusion-workplace) -titled "What Works? Evidence-Based Ideas to Increase Diversity, -Equity, and Inclusion in the Workplace". One of the authors, David -Pedulla, also contributed a -[summary](https://hbr.org/2020/05/diversity-and-inclusion-efforts-that-really-work) -which included five strategies for employers to help increase -diversity. These recommendations can help companies attract, and -retain diverse talent. - -#### Codes of Conduct -Typelevel has a [code of -conduct](https://typelevel.org/code-of-conduct.html) that emphasizes -the goal of making the community "friendly, safe and welcoming" for -everyone. It prohibits harassment based on (among other things) race -or ethnicity. If you are a member of an underrepresented minority and -feel that anyone in the community is making you feel unwelcome, report -it. - -Familiarize yourself with the codes of conduct in whatever -organizations, meetings, or events you participate in. Taking time to -digest these in advance will help guide your behavior but also to -recognize when others may be out of line, and help you understand what -steps you can take. If your professional or avocational circles don't -have a code of conduct, you can begin discussions to find one that -will suit the community. - -#### Intervention -However, it must not fall to people of color to recognize and report -harassment or unwelcome behavior. As members of a positive and -welcoming community, we must not be passive bystanders. Learning to -intervene constructively may take some practice. - -Robin DiAngelo has a list of [Silence Breakers for Whites in -Cross-racial -Discussions](https://robindiangelo.com/resources/attachment/silence-breakers-for-whites/) -containing 18 different phrases you can use to intervene directly in -an ongoing situation. These phrases can help diffuse a difficult -situation calmly and allow people to save face and, ideally, replace a -harmful discussion with a constructive one. - -The Southern Poverty Law Center has also produced some useful -[suggestions](https://www.splcenter.org/20171005/splc-campus-guide-bystander-intervention) -for bystander intervention. Although their document was intended for -use on college campuses, the suggestions may be more broadly -applicable. For instance, the SPLC points out that even if you do not -intervene in the moment, you can document harassing behavior and -provide it to moderators or authorities. In addition, they suggest -that you can provide support to victims even after harassment has -taken place. - -When you think about intervening, consider the feelings of the people -you are hoping to protect. You may wish to discretely ask if they -would like help in a situation, if there is time or a private channel -available, especially if you intend to notify a moderator or -supervisor. - -#### Education -Those of us who are in the (white) majority owe it to ourselves to -learn about race, racism, and the dark history behind them. We should -also take time to reflect on our role in perpetuating racist power -structures, and how we benefit from the repression of others. Today -there is no shortage of books and resources to help us. Here are a few -commonly-cited resources: - -* [Stamped from the Beginning](https://www.ibramxkendi.com/stamped) by [Ibrahim - Kendi](https://www.ibramxkendi.com/) -* [How to Be an Anti-Racist](https://web.archive.org/web/20201001105306/https://www.ibramxkendi.com/how-to-be-an-antiracist-1) by Ibrahim Kendi -* [So You Want to Talk About Race](https://www.hachettebookgroup.com/titles/ijeoma-oluo/so-you-want-to-talk-about-race/9781580056779/) by [Ijeoma - Oluo](http://www.ijeomaoluo.com/) -* [White Fragility](https://robindiangelo.com/publications/) by [Robin DiAngelo](https://robindiangelo.com/) -* [Seeing White](https://www.sceneonradio.org/seeing-white/) (podcast) - -In addition, we need to look around our own communities for -opportunities to volunteer or contribute. If you cannot march, -consider donating to organizations supporting the Black Lives Matter -movement or to bail funds. - - -### Black Lives Matter. - - -### Acknowledgments -This blog post incorporates suggestions and resources provided by Lina -Dahlberg and Maria Dahlberg. diff --git a/src/blog/custom-error-types.md b/src/blog/custom-error-types.md deleted file mode 100644 index fa057f2f..00000000 --- a/src/blog/custom-error-types.md +++ /dev/null @@ -1,162 +0,0 @@ -{% - author: ${djspiewak} - date: "2025-09-02" - tags: [technical] -%} - -# Custom Error Types Using Cats Effect and MTL - -**tl;dr** Cats MTL 1.6.0 introduces a brand new lightweight syntax for managing user-defined error types in the Cats ecosystem without requiring complex monad transformers. - -One of the most famous and longstanding limitations of the Cats Effect `IO` type (and the Cats generic typeclasses) is the fact that the only available error channel is `Throwable`. This stands in contrast to bifunctor or polyfunctor techniques, which add a typed error channel within the monad itself. You can see this easily in type signatures: `IO[String]` indicates an `IO` which returns a `String` or may produce a `Throwable` error (`Future[String]` is directly analogous). Something like `BIO[ParseError, String]` would represent a `BIO` that produces a `String` *or* raises a `ParseError`. The latter type signature is more general than `Throwable`, since it allows for user-specified error types, and it's somewhat more explicit about where errors can and cannot occur. - -In a meaningful sense, this type of bifunctor error encoding is analogous to *checked* exceptions in Java, whereas monofunctor error encoding (like Cats Effect's `IO`) is analogous to *unchecked* exceptions. Both are valid design decisions for an effect type, but they come with different benefits and tradeoffs. - -Cats has long been quite prescriptive about monofunctor effects, in part because this considerably simplifies the compositional integration space. Libraries like Fs2, Http4s, Calico, and so many more are able to build on top of parametric effects (the famous `F[_]`) with a consistent understanding of what error channels are available and how they're going to behave. This has *very* subtle interactions with concurrent logic and resource handling, and by insisting on a monofunctor calculus, the Cats ecosystem is able to maintain very strong properties with relatively simple implementations in these areas. - -However, the core problem of custom error types doesn't *really* go away. Parsing is a great example of this. For example, Circe has a `ParsingFailure` type which carries a specific JSON parse error message as well as some associated traceback context. While this type does happen to extend `Exception`, and thus can be raised within an `IO`, it's not necessarily *right* for it to do so. This is common, but arguably it's only common because of the prevalence of monofunctors. - -A standard solution to this problem, if you *don't* want to extend `Exception` with your error types, is to simply return `Either` everywhere. Unfortunately, that results in a lot of type signatures which look like this: - -```scala -def parse(input: String): IO[Either[Failure, Result]] = ??? -``` - -And then of course, everything you do with that result must be explicitly `flatMap`ped into the `Either`, and higher-order control flow libraries like Fs2 will often need some extra coaxing in order to make everything work the way you want it to. This gets old in a hurry, which often results in reaching for alternatives like `EitherT`. That way lies frustration and woe. - -## Capabilities - -The good news is that we now have a better answer here, and one which composes very nicely with the existing (and future) ecosystem, maintains all relevant concurrency properties, and which type-infers extremely well, particularly in Scala 3. The answer has been to double down on the relatively little-used implicit capabilities library for Cats, known under the very misleading name of Cats MTL. - -The name "Cats MTL" comes from Haskell's MTL package, which in turn was pretty aptly named: "Monad Transformer Library". Haskell's MTL is entirely oriented around making it easier and more ergonomic to manipulate monad transformer *stacks*, which is to say, multiple layers of datatypes like `EitherT`, `Kleisli`, and so on. Monad transformer stacks are extremely difficult to work with, both in Scala and in Haskell, and so over time people progressively evolved techniques involving typeclasses in Haskell and implicits in Scala to more ergonomically manipulate composable effect types. Cats MTL was rooted in an adaptation of some of these ideas. - -Over time though, we've learned that monad transformer datatypes *themselves* are often too clunky and even unnecessary. They work well in a few contexts, most notably local scopes (i.e. within the body of a single method), but they're generally the wrong solution for the problem. Quite notably, while the Cats Effect concurrent typeclasses do *work* on monad transformer stacks and derive lawful results, the practical outcomes can be very unintuitive. For that reason, it's generally not advisable to use types like `EitherT` or `IorT` composed together with libraries like Fs2 or similar. - -However, the basic idea of MTL itself, divorced from the *datatypes* (like `EitherT`), is actually a very good one. At its core, MTL is just about expressing capabilities available within a given scope using implicit evidence. Capabilities can be things like parallelism, resource safety, error handling, dependency injection, sequential composition, or similar. When done correctly, this can be a very powerful and lightweight way of expressing compositional effects with a high degree of granularity and type safety. It's not a coincidence that this is exactly the route being explored by many of the researchers working on Scala academically! - -## Scoped Error Capabilities - -The problem has been to find a way to blend all of these constructs together in a way that practically *works* with the ecosystem, is syntactically lightweight, has pleasant type inference and errors, and doesn't confuse the heck out of anyone who touches it. That is a problem we feel we have now solved, at least with errors. - -```scala -import cats.Monad -import cats.effect.IO -import cats.mtl.syntax.all.* -import cats.mtl.{Handle, Raise} -import cats.syntax.all.* - -// define a domain error type -enum ParseError: - case UnclosedBracket - case MissingSemicolon - case Other(msg: String) - -// use that error type in some function -def parse[F[_]](input: String)(using Raise[F, ParseError], Monad[F]): F[Result] = - // do some hardcore parsing - if missingBracket then - UnclosedBracket.raise[F, Result] - else if missingSemicolon then - MissingSemicolon.raise // we can rely on type inference and omit extra typings - else - result.pure[F] - -// use allow/rescue like try/catch to create scoped error handling -val program: IO[Unit] = Handle.allow[ParseError]: - for - x <- parse[IO](inputX) - y <- parse(inputY) - _ <- IO.println(s"successfully parsed $x and $y") - yield () -.rescue: - case ParseError.UnclosedBracket => - IO.println("you didn't close your brackets") - case ParseError.MissingSemicolon => - IO.println("you missed your semicolons very much") - case ParseError.Other(msg) => - IO.println(s"error: $msg") -``` - -There's a lot to unpack here! At the very beginning we define a custom error type, `ParseError`. This is just a domain error like any other, and you'll note that it *doesn't* extend `Exception` or `Throwable` or similar. Without Cats MTL, we would generally have to wrap this error up in `Either` in all our function's result types, if we wanted to use it (similar to what Circe does). In this case though, instead of adding the error to the result type, we added a `using` parameter to our `parse` function! - -Specifically, what we're doing here when we say `using Raise[F, ParseError]` is that the `parse` method requires the ability to raise (but not handle!) errors of type `ParseError`. This is a bit like saying `throws ParseError` in Java, except it isn't an exception! - -Later on, in the body of `parse`, we use this `Raise` capability to call the `raise` method, producing errors in failure cases. This is a bit like the `throw` keyword, but again with our own custom domain error type. Btw, if we had expanded our `Monad[F]` using into something like `MonadError[F, Throwable]` or, more aggressively, `Async[F]`, we would have *also* had the ability to raise any error of type `Throwable` using the same syntax! In this case though, `parse` is only able to raise domain errors. - -As an aside, the `F[_]` here could be instantiated with many different monadic types. While we're using `IO` in production, perhaps we would want to test this function using `Either[ParseError, A]` as our type. This is very much supported! And in fact, if you did this, the `Raise` would have been implicitly materialized by Cats MTL, since `Either` has an obvious implementation of that function. - -Finally, at the end of the snippet above, we define `program` using the brand new syntax: `allow`/`rescue`. This is where things get *very* fancy. What we're doing here is we're introducing a new lexical scope (indented after the `allow[ParseError]:`) in which it is valid to `raise` an error of type `ParseError`. You should think of this as being very similar to `try`/`catch`, except it works with effect types like `IO` and any error type you define (not just `Throwable`). Within this scope, we write code as usual, and we're allowed to call the `parse` function. Note that if we had tried to call `parse` *outside* of this scope, it would have been a compile error informing us that we're missing the `Raise` capability. - -At the end of the `allow` scope, we call `.rescue`, and this requires us to pass a function which *handles* any errors which could have been raised by the body of the `allow`. This works exactly like `catch`, except with your own domain error types. In this case, we are apparently just logging the existence of the errors and moving on with our life, because we do some printing and away we go, but you could imagine perhaps returning a custom HTTP error code, or triggering some fallback behavior, or really any other error handling logic. - -### Scala 2 - -Oh, and just in case you were wondering, this syntax *does* work on Scala 2 as well, it's just a bit less fancy! Here's the same snippet from above, but with 100% more braces and a lot more explicit types: - -```scala -import cats.Monad -import cats.effect.IO -import cats.mtl.syntax.all._ -import cats.mtl.{Handle, Raise} -import cats.syntax.all._ - -// define a domain error type -sealed trait ParseError extends Product with Serializable - -object ParseError { - case object UnclosedBracket extends ParseError - case object MissingSemicolon extends ParseError - case class Other(msg: String) extends ParseError -} - -// use that error type in some function -def parse[F[_]](input: String)(implicit r: Raise[F, ParseError], m: Monad[F]): F[Result] = { - // do some hardcore parsing - if (missingBracket) - UnclosedBracket.raise[F] - else if (missingSemicolon) - MissingSemicolon.raise[F] - else - result.pure[F] -} - -// use allow/rescue like try/catch to create scoped error handling -val program: IO[Unit] = Handle.allowF[IO, ParseError] { implicit h => - for { - x <- parse[IO](inputX) - y <- parse[IO](inputY) - _ <- IO.println(s"successfully parsed $x and $y") - } yield () -} rescue { - case ParseError.UnclosedBracket => - IO.println("you didn't close your brackets") - case ParseError.MissingSemicolon => - IO.println("you missed your semicolons very much") - case ParseError.Other(msg) => - IO.println(s"error: $msg") -} -``` - -We need to do a lot more hand-holding for the compiler by using the `allowF` function instead of `allow`, but in general this is very much the same idea! - -## Under the Hood - -Behind the scenes, this functionality is doing two very creative things. First, as the Scala 2 snippet hints, we're introducing a new implicit within the local scope of the function passed to `allow`/`allowF`. This is one of Scala's more unique features and we're leveraging it quite heavily. In Scala 3, we're able to hide this syntax *entirely* by using context functions (the `A ?=> B` syntax), but in Scala 2 we need to use the `implicit x =>` lambda syntax in order to make this work. - -That implicit is introduced targeting the effect type we passed to `allowF`, or in Scala 3's case, the type which was inferred from the return. In this case, that type is `IO`! In other words, you don't need to be using parametric effects (`F[_]`) in order to make all this work! `Raise[IO, ParseError]` is a totally valid `Raise` instance, and it's exactly what we have in scope here. Or rather, we actually have `Handle[IO, ParseError]` (which extends `Raise`), which gives us the ability to both raise *and* handle errors. - -Once the scope is closed, syntactically, we force the user to supply an error handler to ensure that any errors which were raised and unhandled within the body are correctly managed. This is a pretty logical way of setting up your error handling, and precisely mirrors the way that you would do this same thing with a more imperative direct syntax like `try`/`catch`/`throw`/`throws`. - -In the way way deep underdark of the implementation, this whole thing works at runtime by creating what we call a "submarine error". Specifically, we have a local traceless exception type called `Submarine` inside of the `allow` implementation which extends `RuntimeException`. When you `raise` a custom domain error (`ParseError` in this case), we use `Submarine` to "submerge" your error within the `Throwable` error channel of the enclosing effect – in this case, `IO`. Since we catch this error at the boundary, this whole process is entirely invisible to you *unless* you write something like `handleErrorWith` and catch all `Throwable`-typed errors within the scope, in which case you might see something of type `Submarine`. The correct thing to do with this error type, should you see it, depends considerably on exactly *why* you're writing `handleErrorWith`, and as it turns out this is exactly the whole point! - -By implementing this functionality without extending the number of actual error channels within the effect type (either with a bifunctor or something like `EitherT`), we ensure that everything continues to compose correctly around all resource handling, structured and unstructured concurrency, and otherwise-oblivious generic library code which has no idea what your domain errors are or how they might behave. Even in the case of an explicit `handleErrorWith`, you might be adding that type of error handler because you're writing some logic which must make *certain* that there is no possible way to short-circuit without passing through your handler (e.g. perhaps you're trying to make sure that some critical resource is cleaned up), or alternatively you may just be trying to observe `Throwable` errors to log and re-raise them, or any number of other things you *might* be doing with the error channel that we don't have any insight into. - -Rather than trying to impose a particular multi-channel composition semantic on your code, we simply stick with a single error channel with known and well-understood supremacy semantics, and everything else follows from there. - -## Conclusion - -Hopefully you find this technique helpful! This has been in the works for a *surprisingly* long time (I think it was first suggested in the Typelevel Discord about two or three years ago), and it was Thanh Le ([@lenguyenthanh](https://github.com/lenguyenthanh)) who ultimately pushed it over the line. Huge shoutout! He has already begun leveraging this functionality in Lichess, one of the larger production Scala projects: [lichess-org/lila#17944](https://github.com/lichess-org/lila/pull/17944) - -Even more excitingly, this is a bit of a taste of the next phase of the effect type ecosystem. Scala is continuing to move heavily in the direction of implicit capabilities for these types of behaviors, and while efforts such as Caprese are still a long way from bearing real-world fruit, much of the work that is being done in that direction also creates the primitives needed to encode a compositional capabilities ecosystem for our existing production effect types, such as Cats Effect `IO`! - -Cats MTL will continue to evolve in this area, with an eye towards advancing the capabilities and improving syntax and ergonomics of this type of functionality both now and in the future. diff --git a/src/blog/default.template.html b/src/blog/default.template.html deleted file mode 100644 index b61e29e6..00000000 --- a/src/blog/default.template.html +++ /dev/null @@ -1,32 +0,0 @@ -@:embed(/templates/main.template.html) -
-
-

${cursor.currentDocument.title}

- - @:for(tags) - ${_} - @:@ -
-
-
-
- ${cursor.currentDocument.content} -
-
-
-
- @:for(author) -
- @:include(/templates/bio.template.html) { render-bio = true } -
- @:@ -
-
- -@:@ diff --git a/src/blog/default.template.rss b/src/blog/default.template.rss deleted file mode 100644 index 1759da89..00000000 --- a/src/blog/default.template.rss +++ /dev/null @@ -1,10 +0,0 @@ - - ${cursor.currentDocument.rawTitle} - @:date(date, RFC_1123_DATE_TIME) - @:for(author) - ${_.name} - @:@ - @:target(cursor.currentDocument.sourcePath) - @:target(cursor.currentDocument.sourcePath) - - diff --git a/src/blog/deriving-instances-1.md b/src/blog/deriving-instances-1.md deleted file mode 100644 index 5d9917ed..00000000 --- a/src/blog/deriving-instances-1.md +++ /dev/null @@ -1,304 +0,0 @@ -{% - author: ${larsrh} - date: "2013-06-24" - tags: [technical] - katex: true -%} - -# Deriving Type Class Instances - -## Motivating example - -Assume that you have a `case class` representing vectors in three-dimensional space: - -```scala -case class Vector3D(x: Int, y: Int, z: Int) -``` - -Now you want to implement addition on this class. -Currently, you have to do that manually: - -```scala -def +(that: Vector3D): Vector3D = - Vector3D(this.x + that.x, this.y + that.y, this.z + that.z) -``` - -If you are writing some code involving three-dimensional vectors, chances are that you also have to deal with two-dimensional ones: - -```scala -case class Vector2D(x: Int, y: Int) { - def +(that: Vector2D): Vector2D = - Vector2D(this.x + that.x, this.y + that.y) -} -``` - -Observe that the hand-written implementation of `+` is quite repetitive. -We want to avoid that sort of boilerplate code as much as possible. - -In this post, we will introduce an abstraction over the *addition* operation, namely *semigroups*, -and introduce a macro-based facility which allows you to get the implementation of `+` for free. -In the end, the only thing you will have to write is this: - -```scala -implicit val vector2DSemigroup = TypeClass[Semigroup, Vector2D] -implicit val vector3DSemigroup = TypeClass[Semigroup, Vector3D] -``` - -That is still a little bit of boilerplate, right? How about: - -```scala -import Semigroup.auto._ -``` - -This will give you `Semigroup` instances for *all* of your data types – with zero boilerplate! - -But first, let us introduce all the related concepts properly. - -## Abstracting all the things - -@:style(bulma-notification) - If you are already familiar with type classes in general and algebraic structures in particular, you can safely skip this and the next section. - Keep in mind though that we are dealing with classes for types of kind @:math * @:@ only. Type classes for @:math * \rightarrow * @:@ are different and not supported. -@:@ - -Type classes are an incredibly useful abstraction mechanism, originally introduced in Haskell. -If you have been using some of the typelevel.scala libraries already, you probably know how type classes and their instances are represented in Scala: as traits and implicits. -In the following section, we will get started with an example type class from abstract algebra, which is implemented in *spire*. - -## Group theory - -Group theory is a very important field of research in mathematics and has a very broad range of applications, especially in computer science. -One of the most fundamental structures is a *semigroup*, which consists of a set of elements equipped with one operation (often called *append*, *mplus*, or similarly; in textbooks you will often find @:math \circ @:@ or @:math \oplus @:@). -Additionally, the operation has to obey the *law of associativity*, meaning that for any three values @:math s_1, s_2, @:@ and @:math s_3 @:@, it does not matter if you append @:math s_1 @:@ and @:math s_2 @:@ first and then append @:math s_3 @:@, or append @:math s_2 @:@ and @:math s_3 @:@ first and then append @:math s_1 @:@ and the result of that. -In other words, the precise order in which the steps of a larger operation are executed does not matter. -A good analogy here is when flattening a list: -On the surface, you just do not care if it proceeds by splitting the list recursively or if the concatenation is done sequentially by folding. - -@:style(bulma-notification) - In fact, some list operations actually require associativity. From the Scaladoc of the `fold` method on `Seq`: - - > Folds the elements of this collection or iterator using the specified associative binary operator. - > The order in which operations are performed on elements is unspecified and may be nondeterministic. - - This allows a particular collection implementation to use whichever order is most efficient. -@:@ - -Lists are already a good example for a semigroup: Any `List[T]` is a semigroup, with the semigroup operation being list concatenation! -A `Map[K, V]` is a semigroup too, given that `V` is a semigroup. -The operation is just "merging" two maps, and if you have two duplicate keys, you can use the semigroup operation for `V`. - -Enough examples. We can represent the concept of a semigroup in Scala using a trait: - -```scala -trait Semigroup[S] { - def append(s1: S, s2: S): S -} -``` - -Obviously, we can also implement a semigroup for base types like `Int`. An instance could look like this: - -```scala -implicit val intInstance = new Semigroup[Int] { - def append(s1: Int, s2: Int) = s1 + s2 -} -``` - -In other words, we just use the built-in addition function. - -@:style(bulma-notification) -If you want to know more about applications of abstract algebra in programming, especially in *spire*, head over to YouTube and watch [an introduction by Tom Switzer](http://www.youtube.com/watch?v=xO9AoZNSOH4). -@:@ - -## Composing instances - -Now suppose you are working with three-dimensional images. -Most likely, you will encounter a data structure for vectors (or points), which we recall from above: - -```scala -case class Vector3D(x: Int, y: Int, z: Int) -``` - -And since you know your maths, you also know that vectors can be added, and that vector addition forms a semigroup! -Hence, a semigroup instance for `Vector3D` is the next logical step. - -```scala -implicit val vectorInstance = new Semigroup[Vector3D] { - def append(u: Vector3D, v: Vector3D) = - Vector3D(u.x + v.x, u.y + v.y, u.z + v.z) -} -``` - -Now, that was a bit tedious, right? We would love to have a way the compiler could write that instance for us. -(I mean, it already generates reasonable defaults for `equals`, `hashCode` and `toString`, so why not for that?) - -In any case, you can see a pattern here: Each element of the case class is added separately. -Here, we could have even delegated the addition to our `intInstance` from above. - -In essence, what we need is a way to combine smaller instances (e.g. for `Int`) into larger instances (e.g. for `Vector3D` consisting of three `Int`s). -Luckily, this is completely mechanic. As an exercise, try writing the following instance: - -```scala -implicit def tupleInstance[A, B](implicit A: Semigroup[A], B: Semigroup[B]) = - new Semigroup[(A, B)] { - def append(t1: (A, B), t2: (A, B)): (A, B) = ??? - } -``` - -## Representing data types - -Once we know how to produce an instance for a pair, we can apply that two times and obtain an instance for a triple. -However, there are still two problems here: - -1. We would like an instance for `Vector3D`, but we have an instance for `(Int, Int, Int)`. -2. This is still a lie. We actually have an instance for `(Int, (Int, Int))`. - -Let us address these problems now. The following sections assume familiarity with `HList`s, as implemented in *shapeless*. - -@:style(bulma-notification) - If you are not familiar with `HList`s yet, - watch Miles Sabin's [talk about *shapeless*](http://www.youtube.com/watch?v=GDbNxL8bqkY) at the Northeast Scala Symposium 2012. - There's also a [blog series](http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/) exploring type-level programming in general by Mark Harrah. -@:@ - -Now, we want to generate an instance for `Vector3D` and countless other data types. -That means that we cannot just special-case for every possible data type, but we have to abstract over them. -The trick is actually quite simple: -For the purposes of automatic instance derivation, we temporarily convert data types into a canonical *representation* using `HList`s, where each case class parameter corresponds to an element in the `HList`. - -In our example, that representation is `Int :: Int :: Int :: HNil`. -Yes, that type is completely equivalent to `Vector3D`, and you can implement the conversion functions straightforwardly: - -```scala -def to(vec: Vector3D): Int :: Int :: Int :: HNil = - vec.x :: vec.y :: vec.z :: HNil - -def from(hlist: Int :: Int :: Int :: HNil) = - ??? // fun exercise! -``` - -Because we are lazy, we let a macro automatically generate the `to` and `from` methods. -We will see in the second part of the series how that works. -For now, just assume that you can invoke some method, magic happens, and you get the conversions out. - -## Using the representation - -At this point, we have a canonical representation for arbitrary case classes. -We will also assume that there are `Semigroup` instances for each of its elements. -Now we would like to combine those base instances into an instance for the representation. -We need two implicits for that: - -```scala -implicit val nilInstance = - new Semigroup[HNil] { - def append(x: HNil, y: HNil) = HNil - } - -implicit def consInstance[H, T <: HList](implicit val H: Semigroup[H], T: Semigroup[T]) = - new Semigroup[H :: T] { - // actual implementation doesn't matter that much - def append(x: H :: T, y: H :: T) = ??? - } -``` - -The key insight is that the compiler can come up with an instance for `Int :: Int :: Int :: HNil`, just because these two implicits are in scope. - -Now we just need a way to get an instance for `Vector3D`. - -```scala -def subst[A, B](to: A => B, from: B => A, instance: Semigroup[B]) = - new Semigroup[A] { - def append(a1: A, a2: A) = - from(instance.append(to(a1), to(a2)) - } -``` - -Easy enough, right? -To get our much-wanted `Semigroup[Vector3D]`, we ask the compiler to make an instance its `HList` representation, conjure the conversion functions and plug all that stuff into the `subst` machine. Voilà, done! -Add some teaspoons of macros, and we are able to write - -```scala -Semigroup.derive[Vector3D] -``` - -Are we done yet? No. We can go even further. - -## Abstracting over type classes - -`Semigroup` is not the only type class around. For example, there is a whole tower of classes from group theory for varying use cases. Then there are some type classes from _scalaz_: - -* `Show` provides a way to convert a value to a `String` -* `Equal` for type-safe equality -* `Order` provides total ordering on values - -... and many more! - -Another key insight is that _all_ of those classes are able to deal with `HLists` and also support the `subst` operation. -Hence, one could be tempted to write: - -```scala -Show.derive[Vector3D] -Equal.derive[Vector3D] -// and more -``` - -I hate duplication, though. I do not want to implement the `derive` macro over and over again. -Now, if only there was a way to abstract over common functionality of types ... - -## A type class called "TypeClass" - -"What," I hear you saying, "the `TypeClass` type class? You can't be serious!" - -I am serious. - -We use type classes to abstract over types. -`Semigroup` abstracts over types which offer some sort of addition functionality. - -However, type classes are themselves just types in Scala. -Thus, we can use type classes to abstract over type classes. -We are defining a type class which abstracts over type classes whose instances can be combined to form larger instances. - -So, without further ado: - -```scala -trait TypeClass[C[_]] { - def nil: C[HNil] - def cons[H, T <: HList](H: C[H], T: C[T]): C[H :: T] - def subst[A, B](to: A => B, from: B => A, instance: C[B]): C[A] -} -``` - -This should actually be not too surprising. We already know exactly how to implement `TypeClass[Semigroup]`. -If we put this implementation into the companion object of `Semigroup`, it will be available for the macro to use. - -## Wrapping it up - -How can this actually be used? -The work can be roughly divided between three roles: - -1. The macro author, who has to implement all the nitty-gritty details of the derivation process. - - That is already done and implemented in _shapeless_. - The upcoming 2.0.0 release will contain all the necessary bits and pieces, but requires at least Scala 2.10.2 (it will not work for 2.10.1 or earlier). - If you are brave, try the latest snapshot version which is available on Sonatype. -2. The library author, who defines type classes, fundamental instances thereof, and of course the necessary `TypeClass` instances. - - These are usually contained in the libraries you use, but the last part will additionally require a bridge library. - But fear not, those bridge libraries already exist, at least for the typelevel.scala libraries, and can be readily added as dependency for your build. - Head over to the [GitHub project](https://github.com/typelevel/shapeless-contrib#readme), we will keep you posted for when a new version comes out. - We also plan to put a compatibility chart on this site. -3. The library user, who defines data types and wants to get instances without all the boilerplate. - - This is the simplest task of all: All you have to do is to put - - ```scala - implicit val myInstance = TypeClass[Semigroup, Vector3D] - // or - import Semigroup.auto._ - ``` - - somewhere into your scope, and you are done! - - Providing "explicit" implicit declarations for each type class instance provides the tightest control over your implicit scope and ensures you only have the instances that you want. - Importing `auto` reduces the boilerplate to the absolute minimum, which is often desirable, but might result in more instances being materialized than you expect. - Which to choose is partly a matter of taste and partly a function of the size and complexity of the scopes you are importing in to: - large or complex scopes might favour explicit declarations; tighter, simpler scopes might favour `auto`. diff --git a/src/blog/directory.conf b/src/blog/directory.conf deleted file mode 100644 index ca84f719..00000000 --- a/src/blog/directory.conf +++ /dev/null @@ -1,568 +0,0 @@ -# authors - -typelevel { - name: Typelevel Steering Committee - avatar: "https://github.com/typelevel-bot.png" - github: typelevel - bluesky: typelevel.org - mastodon: "https://fosstodon.org/@typelevel" - bio: "The Typelevel Steering Committee was the former governing body of Typelevel, until the Foundation was established in 2025." -} - -foundation { - name: Typelevel Foundation - avatar: "https://github.com/typelevel.png" - url: "/foundation" - github: typelevel - bluesky: typelevel.org - mastodon: "https://fosstodon.org/@typelevel" - linkedin: "https://linkedin.com/company/typelevel-foundation" - bio: "The Typelevel Foundation is a nonprofit 501(c)(3) public charity. Our mission is to maintain Typelevel projects, advance research and education in functional programming, and grow our community." -} - -InTheNow { - name: "Alistair Johnson" - bio: "Alistair has been programming for far too long, for far too many companies, but is still passionate about promoting the integration of maths, science and engineering into everyday programming life." -} - -S11001001 { - name: "Stephen Compall" - avatar: "https://github.com/S11001001.png" - github: S11001001 - twitter: S11001001 -} - -TomasMikula { - name: "Tomas Mikula" - avatar: "https://github.com/TomasMikula.png" - github: TomasMikula - twitter: tomas_mikula -} - -aaronmblevin { - name: "Aaron Levin" - bio: "Aaron Levin is a mathematician who fell in love with programming and now manages Data Science teams at SoundCloud." -} - -adamrosien { - name: "Adam Rosien" - bio: "Adam Rosien is a Principal at Inner Product, focused on building systems using functional programming. He previously helped various startups in many domains develop back-end systems and implement continuous deployment practices, and also spent five years as a developer at Xerox PARC." -} - -adelbertc { - name: "Adelbert Chang" - avatar: "https://github.com/adelbertc.png" - github: adelbertc - twitter: adelbertchang - bio: "Adelbert is an engineer at Box where he attempts to reliably copy bytes from one machine to another. He enjoys writing pure functional programs, teaching functional programming, and learning more about computing." -} - -aleksander { - name: "Aleksander Boruch-Gruszecki" - bio: "I’m Martin Odersky’s freshest PhD student, interested in GADTs, typelevel programming and effect systems. Before coming to EPFL, I spent four years working in the industry on back- and front-ends of web applications and dealing with the abomination known as TeamSite. Also, I drink frankly absurd amounts of coffee." -} - -alexandru { - name: "Alexandru Nedelcu" - avatar: "https://github.com/alexandru.png" - github: alexandru - bio: "Alexandru is a software developer living in Bucharest, Romania. A startup guy, he's dividing his time between work, family and his personal projects, fueled by his work on the Monix project and his increased contributions to Typelevel Cats. He's also a proud father, husband, has a very unhealthy sleep schedule and appreciates talking about programming over coffee. Sometimes he blogs at: https://alexn.org" -} - -annettebieniusa { - name: "Annette Bieniusa" - bio: "Annette is a lecturer and senior researcher at the Technische Universität Kaiserslautern. Her research interests include semantics of concurrent and distributed programming, with a focus on replication, synchronization, and how they are reflected on programming language level. Annette was involved in several national and international research projects, most recently the in the EU-Projects “SyncFree: Large-scale Computation without Synchronization” and “Lightkone: Lightweight computation for networks at the edge“." -} - -battermann { - name: "Leif Battermann" - avatar: "https://github.com/battermann.png" - github: battermann - twitter: leifbattermann -} - -buggymcbugfix { - name: "Vilem-Benjamin Liepelt" - bio: "I believe in static types & thoughtfully crafted APIs, good communication & documentation, and open-mindedness & fearless prototyping" -} - -cameronjoannidis { - name: "Cameron Joannidis" - bio: "Machine Learning / Big Data Engineer working with Scala and Functional Programming. Currently working at Simple Machines, an Australian consultancy specialising in Big Data/Machine Learning/Scala/Functional Programming." -} - -ceedubs { - name: "Cody Allen" - avatar: "https://github.com/ceedubs.png" - github: ceedubs - twitter: FouriersTrick -} - -channingwalton { - name: "Channing Walton" - avatar: "https://github.com/channingwalton.png" - github: channingwalton - twitter: channingwalton -} - -cheng { - name: "Dr Eugenia Cheng" - twitter: DrEugeniaCheng - bio: "Eugenia Cheng is a Senior Lecturer (Associate Professor) of Pure Mathematics in the School of Mathematics and Statistics, University of Sheffield, UK." -} - -chingles { - name: "Ching Hian Chew" - avatar: "https://github.com/Chingles2404.png" - github: Chingles2404 -} - -chrisokasaki { - name: "Chris Okasaki" - avatar: "https://github.com/chrisokasaki.png" - github: chrisokasaki -} - -clhodapp { - name: "Chris Hodapp" - avatar: "https://github.com/clhodapp.png" - github: clhodapp - twitter: clhodapp - bio: "Several-time Scala GSOC student and eventually mentor, author of the ill-fated Comprehensive Comprehensions project. He's hoping to see tooling and techniques from the FP/Typelevel community improve the leverage of the average developer. Based in the SF Bay Area." -} - -cvogt { - name: "Chris Vogt" - avatar: "https://github.com/cvogt.png" - github: cvogt - twitter: cvogt - bio: "Slick co-author, Compossible records author, frequent Scala conference/user group speaker, former member of Martin's team at LAMP/EPFL, based in NYC, Senior Software Engineer at x.ai" -} - -cwmyers { - name: "Chris Myers" - bio: "Chris is an experienced FP/Scala dev working at REA Group, Australia's largest Property website. He uses Scala daily in building the next generation APIs for our business. He also curates http://functionaltalks.org and is the creator of Monet.js (http://cwmyers.github.io/monet.js/), a powerful monad library for JS." -} - -danielasfregola { - name: "Daniela Sfregola" - avatar: "https://github.com/DanielaSfregola.png" - github: DanielaSfregola - twitter: DanielaSfregola - bio: "Daniela Sfregola is a Software Consultant based in London, UK. She is an active contributor to the Scala Community and a passionate blogger at danielasfregola.com." -} - -data_fly { - name: "Zhenhao Li" - bio: "Zhenhao Li is a data engineer and data scientist at Connecterra, a data science and IoT startup. He is responsible for making scalable data processing jobs and pipelines and making sure data science insights are generated and delivered in real time. Before joining Connecterra, Zhenhao worked for Accenture in the area of big data and IoT technology consulting, helping major clients to adapt new technologies such as Kafka and Flink, the immutable data paradigm, and functional programming to gain business value faster. He holds a bachelor degree in software engineering and a master degree in logic. He was doing a PhD in mathematical logic at the University of Amsterdam when decided to change his career path to big data and data science. He loves functional programming, and Scala is his primary language for engineering work." -} - -davegurnell { - name: "Dave Gurnell" - avatar: "https://github.com/davegurnell.png" - github: davegurnell - twitter: davegurnell - bio: "Dave Gurnell is a Scala consultant and developer working for Underscore in London, UK. He has been a Scala developer since 2010 and a functional programmer for nearly a decade." -} - -davenpcm { - name: "Christopher Davenport" - bio: "Chris is a Senior Software Engineer at Banno. He is a firm advocate of Functional Programming. He maintains http4s and cats-effect libraries and contributes regularly to the Open Source Community." -} - -denisrosset { - name: "Denis Rosset" - bio: "I’m a researcher in quantum physics with a strong interest in convex optimization and numerical/symbolical computing. I’ve been using Scala&Play since I wanted to build a database of research results, and Play seemed to be a reasonable solution. I fell in love with the Scala language since then, and am contributing to its open source ecosystem (Spire + personal libraries for mathematical computations)." -} - -dialelo { - name: "Alejandro Gómez" - bio: "Alejandro is a functional programing enthusiast with a lot of experience with dynamic languages, specially Clojure. He's the author of the Clojure cats library (https://github.com/funcool/cats) which predates Scala's cats, and has been trying to map pure FP concepts to Clojure as an experiment for quite some time. He recently started working for 47degrees writing Scala and has started the Fetch project, similar to Facebook's Haxl project (Haskell, open source) and Twitter's Stitch (Scala, not open sourced)." -} - -diesalbla { - name: "Diego E. Alonso" - bio: "" -} - -dordogh { - name: "Dorothy Ordogh" - bio: "I’m a member of the Build team at Twitter, meaning it is my full-time job to contribute to Pants. I’ve been working at Twitter for 1.5 years, the first 11 months I spent on a team building integration test frameworks, and then switched to the Build team after realizing how interesting it was. My favorite part is learning how things work under the surface! I broke into computer science in my early twenties after earning a degree in psychology. Chocolate makes the world a better place." -} - -dreadedsoftware { - name: "Marcus Henry, Jr." - avatar: "https://github.com/dreadedsoftware.png" - github: dreadedsoftware - twitter: dreadedsoftware - bio: "Marcus Henry, Jr. is a Software Developer for Integrichain, a company which provides actionable data insights for the life sciences. He develops mostly in functional Scala to deliver responsive, multi threaded solutions using Akka, FS2 and shapeless." -} - -dscleaver { - name: "Dave Cleaver" - avatar: "https://github.com/dscleaver.png" - github: dscleaver - twitter: dscleaver - bio: "Dave Cleaver is a Senior Principal Engineer at Comcast designing and implementing scalable Web Services and Platforms. He has spent the last two years developing and championing solutions in Scala. His interests include AI planning, distributed systems, programming languages, and type systems." -} - -dwijnand { - name: "Dale Wijnand" - bio: "Dale is an active OSS contributor, typically in Scala, and an sbt maintainer." -} - -edmundnoble { - name: "Edmund Noble" - avatar: "https://github.com/edmundnoble.png" - github: edmundnoble - twitter: edmund_noble - bio: "Edmund loves Scala and code in general, and he is intimately interested in how people code. Purely functional programming is his passion; he is particularly interested in new ways to constrain and abstract in programs. He contributes to a couple of the libraries under the typelevel umbrella, including cats and eff, the last of which he maintains." -} - -etorreborre { - name: "Eric Torreborre" - avatar: "https://github.com/etorreborre.png" - github: etorreborre - twitter: etorreborre -} - -fabio { - name: "Fabio Labella" - bio: "I'm a Principal Software Engineer at Ovo Energy in London, specialised in distributed systems and purely functional programming. I'm also an Open Source author and speaker as SystemFw: I'm one of the maintainers of fs2, cats-effect and http4s, and a contributor to cats, shapeless and several other libraries in the Scala FP ecosystem. Passionate about learning and teaching." -} - -felixmulder { - name: "Felix Mulder" - bio: "Former Scala 3 Compiler Engineer at LAMP, EPFL. Currently writes code for Snoop Dogg, building the next generation of banking at the Swedish payments company Klarna." -} - -fthomas { - name: "Frank Thomas" - avatar: "https://github.com/fthomas.png" - github: fthomas - bio: "Frank is a physicist by education and a programmer by profession, currently working in a telecommunications company. He started programming in Scala in 2011 and is a contributor to scalaz-stream and cats. Most of his Scala work is done in his free time." -} - -guillaumebort { - name: "Guillaume Bort" - bio: "Creator of @playframework - Previously @Inria, @zengularity, @lightbend, @prismicio - Now working on the petabytes of analytics data at @Criteo" -} - -gvolpe { - name: "Gabriel Volpe" - avatar: "https://github.com/gvolpe.png" - github: gvolpe - twitter: volpegabriel87 -} - -harrylaou { - name: "Harry Laoulakos" - bio: "Functional programmer, enjoying programming in Scala, Play framework, Akka, Typelevel stack: cats, shapeless, etc" -} - -igstan { - name: "Ionuț G. Stan" - avatar: "https://github.com/igstan.png" - github: igstan - twitter: igstan - bio: "Ionuț G. Stan is a software developer at Eloquentix, where he works on backend services using Scala. His current interests revolve around functional programming techniques, programming languages and compilers." -} - -itrvd { - name: "Itamar Ravid" - bio: "Itamar is a freelance software engineer and has been working with Scala and functional programming for the last few years. He's been mentoring and helping teams move to functional programming in Scala, and loves finding cool use cases for functional abstractions." -} - -jefersonossa { - name: "Jeferson David Ossa" - bio: "I am a software engineer living in Medellin, Colombia. Scala and distributed systems enthusiast interested in FP, software architecture and infrastructure. Scuba diver wannabe." -} - -jmerritt { - name: "Jonathan Merritt" - bio: "Jonathan Merritt is a software engineer in the finance sector in Australia, writing code in Haskell and Scala to support data scientists to develop large-scale predictive models. Before joining the tech industry 2 years ago, he completed a PhD on equine biomechanics followed by 10 years on the post-doc treadmill; dissecting human cadavers, building photogrammetrically-guided robots, testing horseshoes that had an in-built inertial navigation system, and doing motion capture in horses." -} - -jozic { - name: "Eugene Platonov" - bio: "I’m a Scala Dev at eBay Inc, working commercially with Scala as my main programming language since early 2011, Java Dev in my past life. Occasional contributor to variety of Scala related OS projects. Scala evangelist who successfully converted individuals and teams to Scala from Java." -} - -jqno { - name: "Jan Ouwens" - bio: "Jan Ouwens is a Scala developer at Codestar and has worked and experimented with Scala and Akka for the past five years. He has worked on a wide variety of projects over the years in the fields of service management, electron microscopes, banking, and the operation of trains. He has a polyglot mindset, having worked with a wide variety of languages such as Java, C#, Jess/Clips and yes, even VBscript." -} - -julienrf { - name: "Julien Richard-Foy" - twitter: julienrf - bio: "Julien Richard-Foy likes writing programs. In particular, he likes leveraging programming language features to solve engineering problems. He is fascinated by languages that make it easy to turn ideas into programs that are executable by machines and easy to reason about by humans. He writes tools and MOOCs for the good of the community, at Scala Center." -} - -kailuowang { - name: "Kailuo Wang" - avatar: "https://github.com/kailuowang.png" - github: kailuowang - twitter: kailuowang -} - -kathifisler { - name: "Kathi Fisler" - bio: "Kathi Fisler is a Research Professor in Computer Science at Brown University, and co-director of Bootstrap, a national-scale K-12 project that integrates introductory CS into existing middle- and high-school classes. She spent many years doing software and security verification research before deciding that people were harder (and more interesting) to model than systems. She is currently on a mission (with partner-in-crime Shriram Krishnamurthi) to explore how classical CSEd studies might have turned out differently had they considered functional programming. She's teaching with Scala for the first time this semester." -} - -keikonakata { - name: "Keiko Nakata" - bio: "Keiko Nakata works at SAP Innovation Center Network as a Scala programmer. She holds a PhD in computer science from Kyoto University, Japan. She has served on numerous program committees for international conferences and workshops on programming languages, and currently chairs a working group “Types for Verification” at an EU COST Action EUTypes. She loves topology and intuitionistic logics and their application to programming languages." -} - -kenbot { - name: "Ken Scambler" - twitter: KenScambler - bio: "I'm an FP enthusiast based in Melbourne, Australia, with 15-odd years of programming under the belt, including 7 of Scala. I help out with the YOW Lambda Jam and Compose :: Melbourne FP conferences, and the Melbourne Scala User Group. I work at REA Group, where I was one of the hands hoisting the Scala flag 4 years ago, Iwo Jima-style. My job is mostly to prevent people from writing more software." -} - -kristinasojakova { - name: "Kristina Sojakova" - bio: "Kristina Sojakova is a postdoctoral researcher at Cornell University working with Greg Morrisett on the verification of cryptographic protocols. She received her PhD in 2016 from Carnegie Mellon University, where she worked on homotopy type theory, developing a universal mapping characterization of higher inductive types." -} - -larsrh { - name: "Lars Hupel" - avatar: "https://github.com/larsrh.png" - github: larsrh - twitter: larsr_h -} - -longcao { - name: "Long Cao" - avatar: "https://github.com/longcao.png" - github: longcao - twitter: oacgnol - bio: "Long Cao is a software engineer focusing on Scala, Spark, and data engineering and has been in New York for the last 5 years. He cares deeply about showing newcomers the benefits of Scala and functional programming. On his off time likes to enjoy climbing, Rocket League, music, sports, and coffee." -} - -lucabelli { - name: "Luca Belli" - bio: "Luca Belli is a Senior Software Engineer at Twitter Cortex, the centralized deep learning hub within the company. Previously he was a Senior Scientist at Conversant Media where he helped bootstrapping image classification using deep learning. His first job was at Wolfram Alpha in Boston. He got his Ph.D. in Mathematics from Tor Vergata University in Rome." -} - -marina { - name: "Marina Sigaeva" - twitter: besseifunction - bio: "I'm a physicist. And I'm in love with fashion, ballet and beauty." -} - -martinodersky { - name: "Martin Odersky" - bio: "Martin Odersky is professor at EPFL, coordinating the LAMP group." -} - -matthicks { - name: "Matt Hicks" - avatar: "https://github.com/darkfrog26.png" - github: darkfrog26 - twitter: darkfrog26 -} - -milessabin { - name: "Miles Sabin" - avatar: "https://github.com/milessabin.png" - github: milessabin - twitter: milessabin -} - -mpilquist { - name: "Michael Pilquist" - avatar: "https://github.com/mpilquist.png" - github: mpilquist - twitter: mpilquist - bio: "Michael Pilquist is the author of Scodec, a suite of open source Scala libraries for working with binary data, and Simulacrum, a library that simplifies working with type classes. He is also a committer on a number of other projects in the Scala ecosystem, including Cats and FS2. He is also the chief software architect at Combined Conditional Access Development (CCAD), a joint venture between Comcast and ARRIS, Inc., where he is responsible for the design and development of control systems that manage tens of millions of cable system devices, including set-top boxes and head-end equipment." -} - -mtomko { - name: "Mark Tomko" - avatar: "https://github.com/mtomko.png" - github: mtomko - twitter: oxbsharp - bio: "Mark is a senior software engineer at the Broad Institute of MIT and Harvard. He lives in Bellingham, Washington." -} - -nikivazou { - name: "Niki Vazou" - avatar: "https://github.com/nikivazou.png" - github: nikivazou - twitter: nikivazou - bio: "Niki Vazou is a postdoctoral fellow at University of Maryland. She recently got her Ph.D. at University of California, San Diego, supervised by Ranjit Jhala. She works in the area of programming languages, with the goal of building usable program verifiers that will naturally integrate formal verification techniques into the mainstream software development chain. Niki Vazou received the Microsoft Research Ph.D. fellowship in 2014 and her BS from National Technical University of Athens, Greece in 2011." -} - -noelwelsh { - name: "Noel Welsh" - avatar: "https://github.com/noelwelsh.png" - github: noelwelsh - bio: "Noel Welsh is a partner at Underscore, a consultancy that specializes in Scala. He’s been using Scala for 6 years in all sorts of applications. He’s the author of Advanced Scala, which is in the process of being rewritten to use Cats." -} - -non { - name: "Erik Osheim" - avatar: "https://github.com/non.png" - github: non - twitter: d6 - bio: "Erik Osheim is one of the founders of Typelevel, and maintains several Scala libraries including Cats, Spire, and others. He hacks Scala for a living at Stripe, and is committed to having his cake and eating it too when it comes to functional programming. Besides programming he spends time playing music, drinking tea, and cycling around Providence, Rhode Island." -} - -oronport { - name: "Oron Port" - bio: "I am a third year Electrical Engineering Ph.D. student at Technion – Israel Institute of Technology. My research topic is “DFiant: A Dataflow Hardware Description Language”, a Scala-based DSL. I’m involved in and contribute to the Scala ecosystem and especially to the singleton-ops library." -} - -oweinreese { - name: "Owein Reese" - avatar: "https://github.com/wheaties.png" - github: wheaties - twitter: oweinreese - bio: "Owein is the Director of Creatives Engineering at MediaMath, an adtech company. His teams have built systems in Scala which handle over 1M req/s with under 10ms latency daily. Originally starting out as a mathematical programmer working in infrared countermeasures, he moved on to become a full fledged software developer involved first with NASA satellite systems and then with hedge fund analytics. Since discovering the joys of functional programming, he’s looked for ways to incorporate higher powered abstractions in all the code he writes, when he gets to write code." -} - -pheymann { - name: "Paul Heymann" - avatar: "https://github.com/pheymann.png" - github: pheymann - bio: "Paul entered the realm of functional and type-level programming three years ago when he was caught by a Scala meetup. After that, he started doing Scala professionally as a Data Engineer for the social network XING. There he works on recommender systems and the ontology infrastructure which are serving requests of millions of users every day." -} - -propensive { - name: "Jon Pretty" - bio: "Jon has been having fun riding the bleeding edge of Scala for over a decade, and he's not finished yet. While he's not travelling the world attending Scala conferences, or organizing Scala World, Jon spends his time working on a variety of open-source Scala libraries, and providing professional Scala training services." -} - -puffnfresh { - name: "Brian McKenna" - avatar: "https://github.com/puffnfresh.png" - github: puffnfresh - twitter: puffnfresh -} - -rahsan { - name: "Raas Ahsan" - avatar: "https://github.com/RaasAhsan.png" - github: RaasAhsan - twitter: RaasAhsan -} - -ratan { - name: "Ratan Sebastian" - avatar: "https://github.com/rjsvaljean.png" - github: rjsvaljean - twitter: ratansebastian - bio: "Ratan is a software developer at x.ai where they’re building a meeting scheduling personal assistant. He’s been programming in Scala for about 5 years and is interested in learning more about pure functional programming and type systems through Scala." -} - -raulraja { - name: "Raúl Raja Martínez" - avatar: "https://github.com/raulraja.png" - github: raulraja - twitter: raulraja - bio: "Raul Raja is a functional programming enthusiast, CTO and Co-founder at 47 Degrees, a functional programming consultancy specialized in Scala." -} - -romac { - name: "Romain Ruetschi" - bio: "I earned a MSc degree in Computer Science from EPFL in February 2018, and I have since been working at the Laboratory for Automated Reasoning and Analysis (LARA) at EPFL, under the supervision of Prof. Viktor Kunčak. I discovered Scala directly from Prof. Martin Ordersky during my Bachelor at EPFL a few years ago, and have never stopped learning more of it, alongside other languages such as Haskell, Rust or Idris. I am mainly interested in pure functional programming, type systems and formal methods." -} - -rossabaker { - name: "Ross Baker" - bio: "Ross is a Senior Software Engineer at Takt. He began his open source journey on the Scalatra project in 2009, and has gotten purer, more functional, and more typeful with each passing year. He now contributes to http4s and cats among others, and is a member of Typelevel. He is a co-organizer of IndyScala." -} - -roundcrisis { - name: "Andrea Magnorsky" -} - -ryanwilliams { - name: "Ryan Williams" - bio: "Ryan develops software for analyzing genome- and single-cell-sequencing data at the Icahn School of Medicine at Mount Sinai Hospital in NYC. He has been pushing a snowball of increasingly portable, typelevel, and FP Scala OSS libraries for several years, from dependency-management and testing DSLs to collections algorithms for Spark RDDs and genomic-analysis tools." -} - -sasharomijn { - name: "Sasha Romijn" - bio: "Sasha is the co-founder and CTO of a small Django development company in Amsterdam. Sasha is deeply involved in the community around Django, a popular Python web framework, being a Django team member, chair of the Dutch Django Association and co-organiser of various conferences. She cares about building communities and conferences in which everyone feels welcome, valued and at home, regardless of their background. Sasha has a specific interest in well-being and ethical issues around communities and development. Some of her side projects are the Less Obvious Conference Checklist, with many less obvious suggestions for event organisers, and Happiness Packets, to spread more gratitude and kindness in open source communities." -} - -sellout { - name: "Greg Pfeil" - avatar: "https://github.com/sellout.png" - github: sellout - twitter: sellout - bio: "Greg Pfeil is a compiler writer and programming language designer. At this point they have written four recursion scheme libraries in three languages, with the goal of getting to half a recursion scheme library for all languages. Greg works at Formation, writing Haskell, using only the purest artisanal FP." -} - -smarter { - name: "Guillaume Martres" - bio: "Compiler Engineer at EPFL working on Dotty. He's currently working on incremental compilation support using sbt." -} - -sofiacole { - name: "Sofia Cole" - bio: "Sofia Cole is a Scala Developer at ITV and associate at Underscore consulting. She is also a keen contributor to the Scala and tech communities, mostly focusing on making things more approachable and accessible. One of her goals in 2017 is to visit more schools introducing the wonders of programming, especially helping young girls realise their potential. Her favourite things at the moment are reading about dystopian futures, eating pancakes and watching House for the second time through." -} - -stefanschneider { - name: "Stefan Schneider" -} - -stephaniebalzer { - name: "Stephanie Balzer" - bio: "Stephanie Balzer is a research faculty in the Principles of Programming group in the Computer Science Department at Carnegie Mellon University. Stephanie obtained her PhD from ETH Zurich under the supervision of Thomas R. Gross. In her PhD work, Stephanie developed the object-based programming language Rumer, which uses the abstraction of a relationship to make explicit the collaborations between objects, rather than representing them implicitly in terms of references. Stephanie demonstrated the benefits of relationships for program verification, by developing an invariant-based, visible-state semantics verification technique for Rumer. During her postdoc, Stephanie enriched her expertise with a more theoretical approach to programming language research based on type theory and logic, which resulted in her work on manifest sharing and manifest deadlock freedom." -} - -sweirich { - name: "Stephanie Weirich" - avatar: "https://github.com/sweirich.png" - github: sweirich - twitter: fancytypes - bio: "Stephanie Weirich is a Professor at the University of Pennsylvania. Her research centers on programming languages, type theory and machine-assisted reasoning. In particular, she studies generic programming, metaprogramming, dependent type systems, and type inference in the context of functional programming languages. She is currently an Editor of the Journal of Functional Programming and served as the program chair for ICFP in 2010 and the Haskell Symposium in 2009." -} - -tixxit { - name: "Tom Switzer" - avatar: "https://github.com/tixxit.png" - github: tixxit - twitter: tixxit -} - -umasrinivasan { - name: "Uma Srinivasan" - bio: "Uma is a Staff Software Engineer in the Advanced Scala Tools team at Twitter. She brings with her multiple decades of experience and expertise in the area of compilers, code generation and related hardware/software co-design. Prior to joining Twitter she worked at Intel and Hewlett Packard. She has a Bachelor’s degree in Electrical Engineering and a Master’s in CS. She holds several patents and technical publications in her field of expertise." -} - -valencik { - name: "Andrew Valencik" - avatar: "https://github.com/valencik.png" - github: valencik -} - -vlovgr { - name: "Viktor Lövgren" - avatar: "https://github.com/vlovgr.png" - github: vlovgr - twitter: vlovgr - bio: "Viktor is a Software engineer at Ovo Energy in London, working on the platform powering energy meter readings and consumption data. He’s an advocate of strongly typed functional programming, and Scala in particular, which has been his professional focus the past three years." -} - -yifanxing { - name: "Yifan Xing" - bio: "Yifan is a software engineer, ScalaBridge organizer, and open-source contributor. Her work involves many distributed systems related topics, including network protocols, consensus, network security, etc. Yifan contributed to the message queue systems and asynchronous APIs for a Scala open source project Shared Health Research Information Network (SHRINE) at Harvard Medical School. The system uses concepts of parallel processing/multi-threading, non-blocking asynchronous, distributed systems, etc." -} - -zainabali { - name: "Zainab Ali" - bio: "Zainab is a functional programmer who converted from object oriented design. A physicist at heart, she was excited to find an application of dimensional analysis and dependent types to real world problems. She is the author of Libra and a contributor to many typelevel libraries, such as cats and fs2." -} - -zetashift { - name: "Rishad Sewnarain" - avatar: "https://github.com/zetashift.png" - github: zetashift -} diff --git a/src/blog/discipline.md b/src/blog/discipline.md deleted file mode 100644 index e4bf3037..00000000 --- a/src/blog/discipline.md +++ /dev/null @@ -1,182 +0,0 @@ -{% - author: ${larsrh} - date: "2013-11-17" - tags: [technical] -%} - -# Law Enforcement using Discipline - -Some nine or ten months ago, [Spire](http://github.com/non/spire)'s project structure underwent a major reorganization. -Simultaneously, the [Scalacheck](http://www.scalacheck.org/) bindings were refactored, completely overhauling the law-checking infrastructure. - -Requirements ------------- - -The main goal was to make it easy to check that instances of Spire's type classes adhere to the set of algebraic laws of the respective type classes. -[Scalaz](https://github.com/scalaz/scalaz) also has such an infrastructure, so why not take that one? -The problem is that in Spire, the hierarchy of type classes is a little bit more complex: - -On the one hand, there is a "generic" tower of type classes including `Semigroup`, `Monoid` and the like, where each successive type extends its predecessor. -On the other hand, this tower is replicated *twice* for their "additive" and "multiplicative" counterparts. -These classes are isomorphic, up to the semantics, and hence naming of their operations. - -This distinction is quite useful, because now one can write: - -```scala -trait Semiring[A] extends AdditiveMonoid[A] with MultiplicativeSemigroup[A] { - // ... -} -``` - -without clashes between the additive and multiplicative binary operations. -Also, a semiring can now be quite naturally treated as an additive monoid and a multiplicative semigroup (but not as a generic semigroup, which would be ambiguous). -(One could consider this the *third* hierarchy of algebraic type classes in spire.) - -When checking laws, we do not want to repeat the same laws over and over again. -Hence, we need some way to express that certain type classes share laws with others which are not necessarily in the same type hierarchy. - - -Interface ---------- - -The implementation fundamentally depends on Scalacheck. -To be more specific, it uses `Prop` as the elementary unit of testing. - -Now, a set of named `Prop`s do not quite suffice as the "law" of a type class. -First, to avoid ambiguous naming, let us call the complete law of a type class (including dependencies), a "rule set". - -To satisfy our requirement of having dependencies from (potentially) different hierarchies, we will distinguish *parents* and *bases*. -A *parent* is a rule set of a type class in the same hierachy, whereas a *base* can come from everywhere. -This distinction is expressed with the use of path-dependent types: - -```scala -trait Laws { - - trait RuleSet { - def name: String - def bases: Seq[(String, Laws#RuleSet)] = Seq() - def parents: Seq[RuleSet] = Seq() - def props: Seq[(String, Prop)] = Seq() - - // ... - } - -} -``` - -As we can see, `parents` uses type `RuleSet`, which constrains parents to the same outer `Laws` instance. -In contrast, `bases` uses the type `Laws#RuleSet` which means that bases can come from other instances of `Laws`. - -When you define type classes, the general idea is to define one instance of `Laws` for each *hierarchy* of type classes. -Coming back to the Spire example, that could look like this: - -```scala -trait GroupLaws[A] { - def semigroup(implicit A: Semigroup[A]): RuleSet = new RuleSet { - def name = "semigroup" - def props = // ... - } - - def monoid(implicit A: Monoid[A]): RuleSet = new RuleSet { - def name = "monoid" - def parents = Seq(semigroup) - def props = // ... - } -} - -trait AdditiveLaws[A] { - def groupLaws: GroupLaws[A] - - def semigroup(implicit A: AdditiveSemigroup[A]): RuleSet = new RuleSet { - def name = "additive semigroup" - - // `.additive` converts an additive X to a generic X - def bases = Seq("additive" → groupLaws.semigroup(A.additive)) - } - - def monoid(implicit A: AdditiveMonoid[A]): RuleSet = new RuleSet { - def name = "additive monoid" - - def bases = Seq("additive" → groupLaws.monoid(A.additive)) - def parent = Seq(semigroup) - } -} -``` - -This now clearly expresses the intention: - -* A monoid is a semigroup. -* An additive semigroup should satisfy the laws of a semigroup. -* An additive monoid is an additive semigroup and should satisfy the laws of a monoid. - -Note that in the definitions inside `AdditiveLaws`, no properties have been restated. -The system will automatically take care that all the properties of the parents and the bases are being checked. - -Obviously, this is not very interesting yet, because so far it could have been achieved by other means. -If you are interested in more complex examples, check the sources of Spire: -There are a couple of examples where the additive and multiplicative versions have extra checks which are not covered by the generic version. - - -Implementation --------------- - -Now, the question is how to compute the set of all properties which need to be checked. -A naïve algorithm would just recursively traverse all bases and parents, and check the union of all the property sets. - -However, this leads to unnecessary work. -Consider the rule set of an additive monoid. -There, the properties of semigroup would be included twice: -once via the semigroup base of the additive semigroup parent, and once via the semigroup parent of the monoid base. - -While checking properties twice certainly does no damage, we still do not want to pay for that overhead. -Hence, a slightly smarter algorithm is used. -We compute the set of all properties of a certain class by taking the union of these sets: - -* the properties of the class itself -* recursively, the properties of all its parents (ignoring their bases) -* recursively, the set of all properties of its bases - -In order to present the user a more transparent output, the names of the properties are hierarchical. -When a base is pulled in as dependency, their properties are additionally prefixed with the name of the base. -This should make it very easy to see where exactly a property came from. - -There is a slight complication, though. -Recall the definition of a semiring in spire, which is given above. -A semiring actually consists of two different semigroups of which we must check the laws separately. -At this point, it is not immediately clear what would happen with the presented algorithm. -With just a minor clarification it turns out that this is not actually a problem: -The rule set of a semiring specifies two bases (one for the additive component and one for the multiplicative component), and we only need to make sure that they have different names. -Laws pulled in via different bases are considered different, and are hence not conflated. - -Usage ------ - -Previously, this new law checking infrastructure was tailored to be used just in Spire. -Since it is useful outside of Spire, too, it has recently been generalized and pulled out into a separate project: [Discipline](https://github.com/typelevel/discipline). - -In there, you can find a stripped-down example of the Spire use case. - -Furthermore, there is integration with Specs2 and ScalaTest. -You just have to extend the `specs2.Discipline` (or `scalatest.Discipline`, respectively) trait, and write - -```scala -checkAll("Int", RingLaws[Int].ring /* put your own `RuleSet` here */) -``` - -and rule sets are expanded and turned into individual tests automatically. -For a Specs2-based tests, this will result in the following output (similar for ScalaTest): - -``` -[info] ring laws must hold for Int -[info] -[info] + ring.additive:group.base:group.associative -[info] + ring.additive:group.base:group.identity -[info] + ring.additive:group.base:group.inverse -[info] + ring.multiplicative:monoid.base:monoid.associative -[info] + ring.multiplicative:monoid.base:monoid.identity -[info] + ring.distributive -``` - -Observe that the associativity law for semigroups shows up twice (additive and multiplicative), but not four times (as would have happened with the naïve algorithm). - -In the future, we will investigate whether Scalaz can also be migrated towards Discipline, for a more unified approach to law checking. diff --git a/src/blog/discord-migration.md b/src/blog/discord-migration.md deleted file mode 100644 index 52de76a3..00000000 --- a/src/blog/discord-migration.md +++ /dev/null @@ -1,31 +0,0 @@ -{% - author: ${typelevel} - date: "2021-05-05" - tags: [social] -%} - -# Discord Migration - -Hello Community! - -We have a new Typelevel discord server. - -There is a large and growing community of Scala developers on Discord. - -Gitter has struggled as a platform for Typelevel for many reasons. We have received feedback that the platform is hard to approach, -and that the sense of community is fractured by being in separate rooms, and difficult for beginners since you have to know where to get started. -Additionally, mobile support has completely deteriorated throughout the years. -The only users who remain satisfied unconditionally are perhaps those who are using bridges. - -Discord is a modern platform with solid support across all devices, has -best-in-class moderation tools both personally and administratively, and builds the Typelevel community as a whole rather than per repo silos. -It additionally can support new types of engagement like pairing, voice chat, presentations, and casting. -We hope Discord will help allow us to build even better spaces. - -Discord has some disadvantages compared to Gitter: it's not indexed by Google, and you must have an account to read. -We hope the advantages outweigh the disadvantages, -and we also hope that other tools like Github Discussions and FAQ's/project docs can be a home for permanent documentation. - -We encourage Typelevel projects to create channels there and to give it a try for a few months and see how it goes. - -### [We hope you'll join us over on the new Discord - https://discord.gg/dXWPjcKv2A](https://discord.gg/dXWPjcKv2A) diff --git a/src/blog/edsls-part-1.md b/src/blog/edsls-part-1.md deleted file mode 100644 index 7dcfc3b8..00000000 --- a/src/blog/edsls-part-1.md +++ /dev/null @@ -1,81 +0,0 @@ -{% - author: ${adelbertc} - date: "2016-09-21" - tags: [technical] -%} - -# It's programs all the way down - -*This is the first of a series of articles on "Monadic EDSLs in Scala."* - -Embedded domain specific languages (EDSLs) are a powerful tool for -abstracting complexities such as effects and business logic from our -programs. Instead of mixing ad-hoc error handling, database access, and web -calls through our code, we isolate each domain into a little language. These -little languages can then be used to write "mini-programs" describing, for -example, how to create a web page for a user. - -Our program then becomes a composition of mini-programs, and running our -program becomes interpreting these mini-programs into actions. This is -analogous to running an interpreter, itself a program, which turns code -into actions. - -The following illustrates what an EDSL might look like in Scala. - -```scala -// An embedded program for fetching data for a user -def process(id: UserId): Program[Page] = for { - bio <- getBio(id) - feed <- getFeed(id) - page <- createPage(bio, feed) -} yield page - -def interpretProgram[A](page: Program[A]): IO[A] = page.interpret { - case GetBio(id) => ... - case GetFeed(id) => ... - case CreatePage(bio, feed) => ... -} -``` - -Here `process` defines a program in our embedded language. -No action has actually been performed yet, that happens when it gets -interpreted by `interpretProgram` and run at runtime. - -In many situations a program in one EDSL is translated into another EDSL, -much like a compiler (again another program). - -```scala -// Translate each term of the program into a database call -def compile[A](program: Program[A]): Database[A] = program.interpret { - case GetBio(id) => ... - case GetFeed(id) => ... - case CreatePage(bio, feed) => ... -} - -def interpretDatabase(db: Database[A]): IO[A] = db.interpret { ... } -``` - -Sometimes you can even optimize programs in an EDSL, much like an optimizing -compiler. In the above example, `interpretDatabase` could deduplicate identical -requests and batch requests to the same table. - -In this series of articles we will explore a couple approaches to embedding -such DSLs in Scala. These techniques will be evaluated against the following -axes: - -* Abstraction: Separation of **structure** from **interpretation**. Programs - describe only the structure of a computation, to be interpreted later on. - A common use case is to have a live interpreter that queries databases and - API endpoints and a test interpreter that works with in-memory stores. - -* Composition: Given two or more EDSLs, how simple is it to compose them? - Given EDSLs for database access and RPC, can we query for data and send - it over the wire while maintaining the abstraction requirement? - -* Performance: At the end of the day we must run our programs and therefore - interpret our mini-programs. How EDSLs are encoded will affect - how they perform and therefore affect any downstream consumers of our - programs, be it other programs or end users. - -In the [next post](edsls-part-2.md) we'll take a look -at the first of these approaches. diff --git a/src/blog/edsls-part-2.md b/src/blog/edsls-part-2.md deleted file mode 100644 index 5eddc550..00000000 --- a/src/blog/edsls-part-2.md +++ /dev/null @@ -1,447 +0,0 @@ -{% - author: ${adelbertc} - date: "2016-10-26" - tags: [technical] -%} - -# EDSLs as functions - -*This is the second of a series of articles on "Monadic EDSLs in Scala."* - -Perhaps the most direct way to start writing an EDSL is to start writing -functions. Let's say we want a language for talking about sets of integers. - -```scala -trait SetLang { - def add(i: Int, set: Set[Int]): Set[Int] - def remove(i: Int, set: Set[Int]): Set[Int] - def exists(i: Int, set: Set[Int]): Boolean -} -``` - -This works... to the extent that we want only to work with -`scala.collection.Set`s. As it stands we cannot talk about -other sets such as bloom filters or sets controlled by other threads. -Our language isn't *abstract* enough, so let's remove -all traces of `Set`. - -```scala -trait SetLang[F[_]] { - def add(i: Int, set: F[Int]): F[Int] - def remove(i: Int, set: F[Int]): F[Int] - def exists(i: Int, set: F[Int]): Boolean - - // Given unknown F we no longer know how to create an empty set - // so we add the capability to our language - def empty: F[Int] -} -``` - -We've parameterized our language with a [higher-kinded type][hkt] which -represents the context of our set. A similar parameterization could be -done with a *-kinded type (e.g. `SetLang[A]`) but since this series -focuses on **monadic** EDSLs, the choice is made for us. - -Now we can write mini-programs which talk about some abstract set -yet to be determined. - -```scala -def program[F[_]](lang: SetLang[F]): Boolean = { - import lang._ - exists(10, remove(5, add(10, add(5, empty)))) -} -``` - -Interpretation of our program is done by implementing `SetLang` and -passing an instance into `program`. - -However, our language is still not abstract enough. Replacing `Set` -with `F` allows us to swap in implementations of sets, but doesn't -allow us to talk about the context. Consider the behavior of `exists` if `F` -represents some remote set. Since `exists` returns a `Boolean`, -checking membership must be a synchronous operation despite the set living -on another node. - -It's also tedious to thread the set through each method manually. - -We can solve both problems by generalizing the use of `F` to some -context that is able to read and write to some set -(think `Set[Int] => (Set[Int], A)`). - -```scala -trait SetLang[F[_]] { - def add(i: Int): F[Unit] - def remove(i: Int): F[Unit] - def exists(i: Int): F[Boolean] - - // No longer need `empty` since the "context" has it already -} -``` - -`SetLang` can now talk about the **effects** around interpretation, such as -asynchronity. - -```scala -import scala.concurrent.Future - -type AsyncSet[A] = Set[Int] => Future[(Set[Int], A)] - -object AsyncSet extends SetLang[AsyncSet] { - def add(i: Int): Set[Int] => Future[(Set[Int], Unit)] = ??? - - def remove(i: Int): Set[Int] => Future[(Set[Int], Unit)] = ??? - - def exists(i: Int): Set[Int] => Future[(Set[Int], Boolean)] = ??? -} -``` - -This new encoding introduces a new but important problem: how do we -combine the results of multiple calls to `SetLang` methods? In the previous -encoding we could add and remove by threading the set from one call to -the next. With this change to represent a context, it's not clear how to do -that. - -Fortunately we are now in a position to leverage a powerful tool: -[monads][monads]. By extending our set language to be monadic -we recover composition in an elegant way. The [Cats][cats] library is used -for demonstration purposes, but the discussion applies equally to -[Scalaz][scalaz]. - -```scala -import cats.Monad -import cats.implicits._ - -trait SetLang[F[_]] { - // See: subtype-typeclasses.md - // for why the `Monad` instance is defined as a member as opposed to inherited - def monad: Monad[F] - - def add(i: Int): F[Unit] - def remove(i: Int): F[Unit] - def exists(i: Int): F[Boolean] -} - -def program[F[_]](lang: SetLang[F]): F[Boolean] = { - import lang._ - implicit val monadInstance = monad - for { - _ <- add(5) - _ <- add(10) - _ <- remove(5) - b <- exists(10) - } yield b -} -``` - -Defining an interpreter starts by identifying a target context. Since the context -computes values while updating state, this suggests the state monad. - -```scala -import cats.data.State - -object ScalaSet extends SetLang[State[Set[Int], ?]] { - val monad = Monad[State[Set[Int], ?]] - - def add(i: Int): State[Set[Int], Unit] = - State.modify(_ + i) - - def remove(i: Int): State[Set[Int], Unit] = - State.modify(_ - i) - - def exists(i: Int): State[Set[Int], Boolean] = - State.inspect(_(i)) -} -``` - -```scala -val state = program[State[Set[Int], ?]](ScalaSet) -// state: cats.data.StateT[cats.Eval,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@ce9f626 - -state.run(Set.empty).value -// res5: (scala.collection.immutable.Set[Int], Boolean) = (Set(10),true) -``` - -Note that calling `program` did not require any context-specific knowledge - -we could define another interpreter, perhaps one that talks to a set -concurrently. - -```scala -import cats.data.StateT -import scala.concurrent.{ExecutionContext, Future} - -// Asynchronous state -def AsyncSet(implicit ec: ExecutionContext): SetLang[StateT[Future, Set[Int], ?]] = - new SetLang[StateT[Future, Set[Int], ?]] { - val monad = Monad[StateT[Future, Set[Int], ?]] - - def add(i: Int): StateT[Future, Set[Int], Unit] = - StateT.modify(_ + i) - - def remove(i: Int): StateT[Future, Set[Int], Unit] = - StateT.modify(_ - i) - - def exists(i: Int): StateT[Future, Set[Int], Boolean] = - StateT.inspect(_(i)) - } -``` - -```scala -// No changes to `program` required -val result = program(AsyncSet(ExecutionContext.global)) -// result: cats.data.StateT[scala.concurrent.Future,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@1c029382 -``` - -`SetLang` captures the *structure* of a computation, but leaves open -its *interpretation*. - -# Monad transformers and classes - -As it turns out, `SetLang` is an example of an encoding often referred to as -[MTL-style][mtl]. - -## Monads in monads - -Among the motivations for monad classes is to remove the need to specify -monad transformer stacks. The following example is adapted from -[Functional Programming with Overloading and Higher-Order Polymorphism][mtlPaper] -by Professor Mark P. Jones. - -Consider a program that is open to failure and computes with some state. This -suggests a combinator of `Either` and `State`, both of which have -monad transformers. All that is left is to decide which transformer to use. - -```scala -type App1[A] = EitherT[State[S, ?], Error, A] - // State[S, Either[Error, A]] - // S => (S, Either[Error, A]) - -type App2[A] = StateT[Either[Error, ?], S, A] - // S => Either[Error, (S, A)] -``` - -While `App1` and `App2` are both valid compositions, the -semantics of the compositions differ. `App1` describes a program where -the computation of a *value* at each transition may fail - but any changes -are preserved - whereas `App2` describes a program where the *entire* -transition may fail. - -We can abstract away the difference by creating a type class which provides -the relevant operations we need. - -```scala -trait MonadError[F[_], E] { - def monad: Monad[F] - - def raiseError[A](e: E): F[A] - def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] -} - -trait MonadState[F[_], S] { - def monad: Monad[F] - - def get: F[S] - def set(s: S): F[Unit] -} -``` - -Similar type classes exist for the `Reader` and `Writer` data types. -These type classes are provided in both [Cats][cats] and [Scalaz][scalaz], -[with some caveats][typeClassSubType]. - -With these type classes in place we can write functions against these as -opposed to specific transformer stacks. Furthermore our functions can specify -exactly what operations they need which helps correctness and -[parametricity][parametricity]. - -```scala -import cats.{MonadError, MonadState} -import cats.data.{EitherT, State, StateT} - -def program[F[_]](implicit F0: MonadError[F, String], - F1: MonadState[F, Int]): F[Int] = - F0.flatMap(F1.get) { i => - F0.raiseError[Int]("fail") - } -``` - -Our program can then be instantiated with either transformer stack. - -```scala -import cats.implicits._ - -// At the time of this writing Cats does not have these instances -// so they are defined here. -// -// Additionally, both Cats and Scalaz 7 have encoding issues -// with these MTL type classes which requires us to redefine Monad when -// defining MonadState instances, despite there already being one. -implicit def eitherTMonadState[F[_], E, S](implicit F: MonadState[F, S]): MonadState[EitherT[F, E, ?], S] = - new MonadState[EitherT[F, E, ?], S] { - def get: EitherT[F, E, S] = - EitherT(F.get.map(Right(_))) - - def set(s: S): EitherT[F, E, Unit] = - EitherT(F.set(s).map(Right(_))) - - def flatMap[A, B](fa: EitherT[F, E, A]) - (f: A => EitherT[F, E, B]): EitherT[F, E, B] = - fa.flatMap(f) - - def pure[A](x: A): EitherT[F, E, A] = - EitherT.pure(x) - - def tailRecM[A, B](a: A)(f: A => EitherT[F, E, Either[A, B]]): EitherT[F, E, B] = - EitherT.catsDataMonadErrorForEitherT[F, E].tailRecM(a)(f) - } - -implicit def stateTMonadError[F[_], E, S](implicit F: MonadError[F, E]): MonadError[StateT[F, S, ?], E] = - new MonadError[StateT[F, S, ?], E] { - def handleErrorWith[A](fa: StateT[F, S, A])(f: E => StateT[F, S, A]): StateT[F, S, A] = - StateT[F, S, A] { (s: S) => - val state: F[(S, A)] = fa.run(s) - F.handleErrorWith(state)(e => f(e).run(s)) - } - - def raiseError[A](e: E): StateT[F, S, A] = - StateT.lift(F.raiseError(e)) - - def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] = - fa.flatMap(f) - - def pure[A](x: A): StateT[F, S, A] = StateT.pure(x) - - def tailRecM[A, B](a: A)(f: A => StateT[F, S, Either[A, B]]): StateT[F, S, B] = - StateT.catsDataMonadStateForStateT[F, S].tailRecM(a)(f) - } - -type App1[A] = EitherT[State[Int, ?], String, A] - -type App2[A] = StateT[Either[String, ?], Int, A] -``` - -```scala -val app1 = program[App1] -// app1: App1[Int] = EitherT(cats.data.StateT@5fdc056d) - -val app2 = program[App2] -// app2: App2[Int] = cats.data.StateT@72493a33 -``` - -# Composing languages - -From one angle we can view our set language, or more generally any EDSL -in MTL-style, as an effect like `MonadError` and `MonadState`. From another -angle we can view `MonadError` and `MonadState` as EDSLs that talk about errors -and stateful computations. We can eliminate the distinctions by renaming -`SetLang` to `MonadSet` and treating it as a type class. - -```scala -import cats.Monad -import cats.implicits._ - -trait MonadSet[F[_]] { - def monad: Monad[F] - - def add(i: Int): F[Unit] - def remove(i: Int): F[Unit] - def exists(i: Int): F[Boolean] -} -``` - -Composing multiple languages then becomes adding constraints to functions, and -interpretation becomes instantiating type parameters that satisfy the -constraints. - -```scala -trait MonadCalc[F[_]] { - def monad: Monad[F] - - def lit(i: Int): F[Int] - def plus(l: F[Int], r: F[Int]): F[Int] -} - -def setProgram[F[_]: MonadSet](i: Int): F[Boolean] = - implicitly[MonadSet[F]].exists(i) - -def calcProgram[F[_]: MonadCalc]: F[Int] = { - val calc = implicitly[MonadCalc[F]] - calc.plus(calc.lit(1), calc.lit(2)) -} - -def composedProgram[F[_]: MonadCalc: MonadSet]: F[Boolean] = { - implicit val monad: Monad[F] = implicitly[MonadCalc[F]].monad - for { - i <- calcProgram[F] - b <- setProgram(i) - } yield b -} - -// Instance - -// Instances are defined together but nothing is stopping us from defining -// these separately, perhaps one in the MonadSet object and another in the -// SetState object. -implicit val stateInstance: MonadSet[State[Set[Int], ?]] with MonadCalc[State[Set[Int], ?]] = - new MonadSet[State[Set[Int], ?]] with MonadCalc[State[Set[Int], ?]] { - val monad = Monad[State[Set[Int], ?]] - - def add(i: Int): State[Set[Int], Unit] = State.modify(_ + i) - - def remove(i: Int): State[Set[Int], Unit] = State.modify(_ - i) - - def exists(i: Int): State[Set[Int], Boolean] = State.inspect(_(i)) - - def lit(i: Int): State[Set[Int], Int] = State.pure(i) - def plus(l: State[Set[Int], Int], r: State[Set[Int], Int]): State[Set[Int], Int] = - (l |@| r).map(_ + _) - } -``` - -```scala -val result = composedProgram[State[Set[Int], ?]].run(Set.empty[Int]).value -// result: (scala.collection.immutable.Set[Int], Boolean) = (Set(),false) -``` - -As before, `composedProgram`, `calcProgram`, and `setProgram` are defined -independent of interpretation, so alternative interpretations simply require -defining appropriate instances. - -# A note about laws - -Type classes should come with laws - this lets us give meaning to their use. -The `Monoid` type class requires data types to have an **associative** binary -operation and a corresponding identity element. These laws allow us to -parallelize batch operations, such as partitioning a `List[A]` into -multiple chunks to be scattered across threads or machines and gathered -back. - -Since our EDSLs are type classes, we should think about what laws we expect -to hold. Below are some possible candidates for laws: - -``` -// MonadSet -set *> add(i) *> remove(i) = set -set *> remove(i) *> exists(i) = false -set *> add(i) *> exists(i) = true - -// MonadCalc - these are just the Monoid laws -plus(lit(0), x) = plus(x, lit(0)) = x -plus(x, plus(y, z)) = plus(plus(x, y), z) -``` - -Next up we'll take a look at some pitfalls of this approach, and a modified -encoding that solves some of them. - -*This article was tested with Scala 2.11.8, Cats 0.7.2, kind-projector 0.9.0, -and si2712fix-plugin 1.2.0 using [tut][tut].* - -[cats]: https://github.com/typelevel/cats "Typelevel Cats" -[hkt]: hkts-moving-forward.md "Higher-kinded types: the difference between giving up, and moving forward" -[mtl]: https://hackage.haskell.org/package/mtl "Monad classes" -[mtlPaper]: http://web.cecs.pdx.edu/~mpj/pubs/springschool.html "Functional Programming with Overloading and Higher-Order Polymorphism" -[monads]: http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf "Monads for functional programming" -[parametricity]: https://www.mpi-sws.org/~dreyer/tor/papers/wadler.pdf "Theorems for free!" -[scalaz]: https://github.com/scalaz/scalaz/tree/series/7.3.x "Scalaz 7" -[tagless]: http://okmij.org/ftp/tagless-final/ "Typed final (tagless-final) style" -[tut]: https://github.com/tpolecat/tut "tut: doc/tutorial generator for scala" -[typeClassSubType]: subtype-typeclasses.md "Subtype type classes don't work" diff --git a/src/blog/equivalence-vs-equality.md b/src/blog/equivalence-vs-equality.md deleted file mode 100644 index 7e09679c..00000000 --- a/src/blog/equivalence-vs-equality.md +++ /dev/null @@ -1,273 +0,0 @@ -{% - author: ${TomasMikula} - date: "2017-04-02" - tags: [technical] - katex: true -%} - -# Equivalence versus Equality - -_This is a guest post by Tomas Mikula. It was initially published as a [document](https://github.com/TomasMikula/hasheq/blob/017f289caac398723501b194cd2b36c4584df638/Equivalence-Equality.md) in the [hasheq](https://github.com/TomasMikula/hasheq). It has been slightly edited and is being republished here with the permission of the original author._ - -This article describes what we mean when we say that the data structures in this library are _equivalence-aware_ in a _type-safe_ fashion. - -## Equivalence - -_Set_ is a data structure that doesn't contain _duplicate_ elements. An implementation of _Set_ must therefore have a way to compare elements for _"sameness"_. -A useful notion of sameness is _equivalence_, i.e. a binary relation that is _reflexive_, _symmetric_ and _transitive_. -Any reasonable implementation of _Set_ is equipped with _some_ equivalence relation on its element type. - -Here's the catch: For any type with more than one inhabitant there are _multiple_ valid equivalence relations. -We cannot (in general) pick one that is suitable in all contexts. -For example, are these two binary trees _same_? - -``` - + + - / \ / \ -1 + + 3 - / \ / \ - 2 3 1 2 -``` - -It depends on the context. They clearly have different structure, but they are both binary search trees containing the same elements. -For a balancing algorithm, they are different trees, but as an implementation of _Set_, they represent the same set of integers. - -## Equality - -Despite the non-uniqueness, there is one equivalence relation that stands out: _equality_. -Two objects are considered _equal_ when they are _indistinguishable_ to an observer. -Formally, equality is required to have the _substitution property:_ - -@:math -\forall a,b \in A, \forall f \in (A \to B): a=_A b \implies f(a)=_B f(b) -@:@ - -(Here, @:math =_A @:@ denotes equality on @:math A @:@, @:math =_B @:@ denotes equality on @:math B @:@.) - -Equality is the finest equivalence: whenever two elements are _equal_, they are necessarily _equivalent_ with respect to every equivalence. - -## Choices in libraries - -Popular Scala libraries take one of these two approaches when dealing with comparing elements for _"sameness"_. - -The current approach of [cats](https://github.com/typelevel/cats/) is _equality_. -Instances of the `cats.Eq[A]` typeclass are required to have all the properties of equality, including the substitution property above. -The problem with this approach is that for some types, such as `Set[Int]`, equality is too strict to be useful: -Are values `Set(1, 2)` and `Set(2, 1)` _equal_? -For that to be true, they have to be indistinguishable by any function. -Let's try `(_.toList)`: - -```scala -scala> Set(1, 2).toList == Set(2, 1).toList -res0: Boolean = false -``` - -So, `Set(1, 2)` and `Set(2, 1)` are clearly _not_ equal. -As a result, we cannot use `Set[Int]` in a context where equality is required (without cheating). - -On the other hand, [scalaz](https://github.com/scalaz/scalaz/) uses unspecified _equivalence_. -Although the name `scalaz.Equal[A]` might suggest _equality_, instances of this typeclass are only tested for properties of _equivalence_. -As mentioned above, there are multiple _valid_ equivalence relations for virtually any type. -When there are also multiple _useful_ equivalences for a type, we are at risk of mixing them up (and the fact that they are usually resolved as implicit arguments only makes things worse). - -## Equivalence-aware sets (a.k.a. setoids) - -Let's look at how _we_ deal with this issue. We define typeclass `Equiv` with an extra type parameter that serves as a _"tag"_ identifying the meaning of the equivalence. - -```scala -trait Equiv[A, Eq] { - def equiv(a: A, b: A): Boolean -} -// defined trait Equiv -``` - -For the compiler, the "tag" is an opaque type. It only has specific meaning for humans. The only meaning it has for the compiler is that different tags represent (intensionally) different equivalence relations. - -An _equivalence-aware_ data structure then carries in its _type_ the tag of the equivalence it uses. - -```scala -import hasheq._ -// import hasheq._ - -import hasheq.immutable._ -// import hasheq.immutable._ - -import hasheq.std.int._ -// import hasheq.std.int._ -``` - -```scala -scala> HashSet(1, 2, 3, 4, 5) -res0: hasheq.immutable.HashSet[Int] = HashSetoid(5, 1, 2, 3, 4) -``` - -What on earth is `HashSetoid`? -A [_setoid_](https://en.wikipedia.org/wiki/Setoid) is an _equivalence-aware set_. -`HashSetoid` is then just a setoid implementated using hash-table. -Let's look at the definition of `HashSet`: - -```scala -type HashSet[A] = HashSetoid[A, Equality.type] -``` - -So `HashSet` is just a `HashSetoid` whose equivalence is _equality_. -To create an instance of `HashSet[Int]` above, we needed to have an implicit instance of `Equiv[Int, Equality.type]` in scope. - -```scala -implicitly[Equiv[Int, Equality.type]] -``` - -For the compiler, `Equality` is just a rather arbitrary singleton object. -It only has the meaning of mathematical _equality_ for us, humans. - -There is a convenient type alias provided for _equality_ relation: - -```scala -type Equal[A] = Equiv[A, Equality.type] -``` - -```scala -implicitly[Equal[Int]] -``` - -So how do we deal with the problem of set equality mentioned above, i.e. that `HashSet(1, 2)` and `HashSet(2, 1)` are not truly _equal_? -We just don't provide a definition of equality for `HashSet[Int]`. - -```scala -scala> implicitly[Equal[HashSet[Int]]] -:22: error: could not find implicit value for parameter e: hasheq.Equal[hasheq.immutable.HashSet[Int]] - implicitly[Equal[HashSet[Int]]] - ^ -``` - -But that means we cannot have a `HashSet[HashSet[Int]]`! -(Remember, for a `HashSet[A]`, we need an instance of `Equal[A]`, and we just showed we don't have an instance of `Equal[HashSet[Int]]`.) - -```scala -scala> HashSet(HashSet(1, 2, 3, 4, 5)) -:22: error: could not find implicit value for parameter A: hasheq.Hash[hasheq.immutable.HashSet[Int]] - HashSet(HashSet(1, 2, 3, 4, 5)) - ^ -``` - -But we can have a `HashSetoid[HashSet[Int], E]`, where `E` is _some_ equivalence on `HashSet[Int]`. - -```scala -scala> HashSet.of(HashSet(1, 2, 3, 4, 5)) -res5: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4)) -``` - -`HashSet.of(elems)` is like `HashSet(elems)`, except it tries to infer the equivalence on the element type, instead of requiring it to be equality. - -Notice the _equivalence tag_: `Setoid.ContentEquiv[Int, Equality.type]`. -Its meaning is (again, for humans only) that two setoids are equivalent when they contain the same elements (here, of type `Int`), as compared by the given equivalence of elements (here, `Equality`). - -The remaining question is: How does this work in the presence of _multiple useful equivalences?_ - -Let's define another equivalence on `Int` (in addition to the provided equality). - -```scala -// Our "tag" for equivalence modulo 10. -// This trait will never be instantiated. -sealed trait Mod10 - -// Provide equivalence tagged by Mod10. -implicit object EqMod10 extends Equiv[Int, Mod10] { - def mod10(i: Int): Int = { - val r = i % 10 - if (r < 0) r + 10 - else r - } - def equiv(a: Int, b: Int): Boolean = mod10(a) == mod10(b) -} - -// Provide hash function compatible with equivalence modulo 10. -// Note that the HashEq typeclass is also tagged by Mod10. -implicit object HashMod10 extends HashEq[Int, Mod10] { - def hash(a: Int): Int = EqMod10.mod10(a) -} -``` - -Now let's create a "setoid of sets of integers", as before. - -```scala -scala> HashSet.of(HashSet(1, 2, 3, 4, 5)) -res13: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4)) -``` - -This still works, because `HashSet` requires an _equality_ on `Int`, and there is only one in the implicit scope (the newly defined equivalence `EqMod10` is _not_ equality). -Let's try to create a "setoid of setoids of integers": - -```scala -scala> HashSet.of(HashSet.of(1, 2, 3, 4, 5)) -:24: error: ambiguous implicit values: - both method hashInstance in object int of type => hasheq.Hash[Int] - and object HashMod10 of type HashMod10.type - match expected type hasheq.HashEq[Int,Eq] - HashSet.of(HashSet.of(1, 2, 3, 4, 5)) - ^ -``` - -This fails, because there are now more equivalences on `Int` in scope. -(There are now also multiple hash functions, which is what the error message actually says.) -We need to be more specific: - -```scala -scala> HashSet.of(HashSet.of[Int, Mod10](1, 2, 3, 4, 5)) -res15: hasheq.immutable.HashSetoid[hasheq.immutable.HashSetoid[Int,Mod10],hasheq.immutable.Setoid.ContentEquiv[Int,Mod10]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4)) -``` - -Finally, does it **prevent mixing up equivalences**? Let's see: - -```scala -scala> val s1 = HashSet(1, 2, 3, 11, 12, 13 ) -s1: hasheq.immutable.HashSet[Int] = HashSetoid(1, 13, 2, 12, 3, 11) - -scala> val s2 = HashSet( 2, 3, 4, 5, 13, 14) -s2: hasheq.immutable.HashSet[Int] = HashSetoid(5, 14, 13, 2, 3, 4) - -scala> val t1 = HashSet.of[Int, Mod10](1, 2, 3, 11, 12, 13 ) -t1: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(1, 2, 3) - -scala> val t2 = HashSet.of[Int, Mod10]( 2, 3, 4, 5, 13, 14) -t2: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 2, 3, 4) -``` - -Combining compatible setoids: - -```scala -scala> s1 union s2 -res16: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type] = HashSetoid(5, 14, 1, 13, 2, 12, 3, 11, 4) - -scala> t1 union t2 -res17: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 1, 2, 3, 4) -``` - -Combining incompatible setoids: - -```scala -scala> s1 union t2 -:26: error: type mismatch; - found : hasheq.immutable.HashSetoid[Int,Mod10] - required: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type] - s1 union t2 - ^ - -scala> t1 union s2 -:26: error: type mismatch; - found : hasheq.immutable.HashSet[Int] - (which expands to) hasheq.immutable.HashSetoid[Int,hasheq.Equality.type] - required: hasheq.immutable.HashSetoid[Int,Mod10] - t1 union s2 - ^ -``` - - -## Conclusion - -We went one step further in the direction of type-safe equivalence in Scala compared to what is typically seen out in the wild today. -There is nothing very sophisticated about this encoding. -I think the major win is that we can design APIs so that the extra type parameter (the "equivalence tag") stays unnoticed by the user of the API as long as they only deal with _equalities_. -As soon as the equivalence tag starts requesting our attention (via an ambiguous implicit or a type error), it is likely that the attention is justified. - -*This article was tested with Scala 2.11.8 and hasheq version 0.3.* diff --git a/src/blog/error-handling.md b/src/blog/error-handling.md deleted file mode 100644 index 26c93b51..00000000 --- a/src/blog/error-handling.md +++ /dev/null @@ -1,255 +0,0 @@ -{% - author: ${adelbertc} - date: "2014-02-21" - tags: [technical] -%} - -# How do I error handle thee? - -Scala has several ways to deal with error handling, and often times people -get confused as to when to use what. This post hopes to address that. - -_Let me count the ways._ - -## `Option` - -People coming to Scala from Java-like languages are often told `Option` is -a replacement for `null` or exception throwing. Say we have a function that -creates some sort of interval, but only allows intervals where the lower bound -comes first. - -```scala -class Interval(val low: Int, val high: Int) { - if (low > high) - throw new Exception("Lower bound must be smaller than upper bound!") -} -``` - -Here we want to create an `Interval`, but we want to ensure that the lower bound -is smaller than the upper bound. If it isn't, we throw an exception. The idea here -is to have some sort of "guarantee" that if at any point I'm given an `Interval`, -the lower bound is smaller than the upper bound (otherwise an exception would have -been thrown). - -However, throwing exceptions breaks our ability to reason about a function/program. -Control is handed off to the call site, and we hope the call site catches it – if not, -it propagates further up until at some point something catches it, or our program -crashes. We'd like something a bit cleaner than that. - -Enter `Option` – given our `Interval` constructor, construction may or may not succeed. -Put another way, after we enter the constructor, we may or may not have a valid -`Interval`. `Option` is a type that represents a value that may or may not be there; -it can either be `Some` or `None`. Let's use what's called a _smart constructor_. - -```scala -final class Interval private(val low: Int, val high: Int) - -object Interval { - def apply(low: Int, high: Int): Option[Interval] = - if (low <= high) Some(new Interval(low, high)) - else None -} -``` - -We make our class `final` so nothing can inherit from it, and we make our constructor -private so nobody can create an instance of `Interval` without going through our own -smart constructor function, `Interval.apply`. Our `apply` function takes some relevant -parameters, and returns an `Option[Interval]` that may or may not contain our constructed -`Interval`. Our function does not arbitrarily kick control back to the call site due -to an exception and we can reason about it much more easily. - -## `Either` and `scalaz.\/` - -So, `Option` gives us `Some` or `None`, which is all we need if there is only one thing -that could go wrong. For instance, the standard library's `Map[K, V]` has a function `get` -that given a key of type `K`, returns `Option[V]` – clearly if the key exists, the associated -value is returned (wrapped in a `Some`). If the key does not exist, it returns a `None`. - -But sometimes one of several things can go wrong. Let's say we have some wonky type that -wants a string that is exactly of length 5 and another string that is a palindrome. - -```scala -final class Wonky private(five: String, palindrome: String) - -object Wonky { - def validate(five: String, palindrome: String): Option[Wonky] = - if (five.size != 5) None - else if (palindrome != palindrome.reverse) None - else Some(new Wonky(five, palindrome)) -} - -/* Somewhere else.. */ -val w = Wonky.validate(x, y) // say this returns None -``` - -Clearly something went wrong here, but we don't know what. If the strings were sent over -from some front end via JSON or something, when we send an error back hopefully we have -something more descriptive than "Something went wrong." What we want is instead of `None`, -we want something more descriptive. We can look into `Either` for this, where we use -`Left` to hold some sort of error value (similar to `None`), and `Right` to hold a successful -one (similar to `Some`). - -To manipulate such values that may or may not exist (presumably obtained from functions that may or may not -fail), we use monadic functions such as `flatMap`, often in the form of monad comprehensions, or -for comprehensions as Scala calls them. - -```scala -val x = ... -val y = ... - -for { - a <- foo(x) - b <- bar(a) - c <- baz(y) - d <- quux(b, c) -} yield d -``` - -In the case of `Option`, if any of `foo/bar/baz/quux` returns a `None`, that `None` simply -gets threaded through the rest of the computation – no `try/catch` statements marching off -the right side of the screen! - -For comprehensions in Scala require the type we're working with to have `flatMap` and -`map`. `flatMap`, along with `pure` and some laws, are the requisite functions needed -to form a monad – `map` can be defined in terms of `flatMap` and `pure`. -With `scala.util.Either` however, we don't have those – we have -to use an explicit conversion via `Either#right` or `Either#left` to get a -`RightProjection` or `LeftProjection` (respectively), which specifies in what direction we bias -the `map` and `flatMap` calls. The convention however, is that the right side is the "correct" -(or "right", if you will) side and the left represents the failure case, but it is tedious to -continously call `Either#right` on values of type `Either` to achieve this. - -Thankfully, we have an alternative in the Scalaz library via -`scalaz.\/` (I just pronounce this "either" – some say disjoint union or just "or"), a right-biased -version of `scala.util.Either` – that is, calling `\/#map` maps over the value if it's in -a "right" (`scalaz.\/-`), otherwise if it's "left" (`scalaz.-\/`) it just threads it through -without touching it, much like how `Option` behaves. We can therefore alter the earlier function: - -```scala -sealed abstract class WonkyError -case class MustHaveLengthFive(s: String) extends WonkyError -case class MustBePalindromic(s: String) extends WonkyError - -final class Wonky private(five: String, palindrome: String) - -object Wonky { - def validate(five: String, palindrome: String): WonkyError \/ Wonky = - if (five.size != 5) -\/(MustHaveLengthFive(five)) - else if (palindrome != palindrome.reverse) -\/(MustBePalindromic(palindrome)) - else \/-(new Wonky(five, palindrome)) -} - -/* Somewhere else.. */ -val w = Wonky.validate(x, y) -``` - -`scalaz.\/` also has several useful methods not found on `Either`. - -## `Try` - -As of Scala 2.10, we have `scala.util.Try` which is essentially an either, with the left type -fixed as `Throwable`. There are two problems (that I can think of at this moment) with this: - -1. We want to avoid exceptions where we can. -2. It violates the monad laws. - -A big factor in our ability to deal with all these error handling types nicely -is using their monadic properties in for comprehensions. - -For an explanation of the monad laws, there is a nice post -[here](http://eed3si9n.com/learning-scalaz/Monad+laws.html) describing them (using Scala). `Try` -violates the left identity. - -```scala -def foo[A, B](a: A): Try[B] = throw new Exception("oops") - -foo(1) // exception is thrown - -Try(1).flatMap(foo) // scala.util.Failure -``` - -This can cause unexpected behavior when used, perhaps in a monad/for comprehension. Furthermore, -`Try` encourages the use of `Throwable`s which breaks control flow and parametricity. -While it certainly may be convenient to be able to wrap an arbitrarily code block with the `Try` constructor -and let it catch any exception that may be thrown, we still recommend using an algebraic data type -describing the errors and using `YourErrorType \/ YourReturnType`. - -## `scalaz.Validation` - -Going back to our previous example with validating wonky strings, we see an improvement that -could be made. - -```scala -sealed abstract class WonkyError -case class MustHaveLengthFive(s: String) extends WonkyError -case class MustBePalindromic(s: String) extends WonkyError - -final class Wonky private(five: String, palindrome: String) - -object Wonky { - def validate(five: String, palindrome: String): WonkyError \/ Wonky = - if (five.size != 5) -\/(MustHaveLengthFive(five)) - else if (palindrome != palindrome.reverse) -\/(MustBePalindromic(palindrome)) - else \/-(new Wonky(five, palindrome)) -} - -/* Somewhere else.. */ -val w = Wonky.validate("foo", "bar") // -\/(MustHaveLengthFive("foo")) -``` - -The fact that one string must have a length of 5 can be checked and reported separately from the other -being palindromic. Note that in the above example `"foo"` does not satisfy the length requirement, -and `"bar"` does not satisfy the palindromic requirement, yet only `"foo"`'s error is reported -due to how `\/` works. What if we want to report any and all errors that could be reported -("foo" does not have a length of 5 and "bar" is not palindromic)? - -If we want to validate several properties at once, and return any and all validation errors, -we can turn to `scalaz.Validation`. The modified function would look something like: - -```scala -sealed abstract class WonkyError -case class MustHaveLengthFive(s: String) extends WonkyError -case class MustBePalindromic(s: String) extends WonkyError - -final class Wonky private(five: String, palindrome: String) - -object Wonky { - def checkFive(five: String): ValidationNel[WonkyError, String] = - if (five.size != 5) MustHaveLengthFive(five).failNel - else five.success - - def checkPalindrome(p: String): ValidationNel[WonkyError, String] = - if (p != p.reverse) MustBePalindromic(p).failNel - else p.success - - def validate(five: String, palindrome: String): ValidationNel[WonkyError, Wonky] = - (checkFive(five) |@| checkPalindrome(palindrome)) { (f, p) => new Wonky(f, p) } -} - -/* Somewhere else.. */ -// Failure(NonEmptyList(MustHaveLengthFive("foo"), MustBePalindromic("bar"))) -Wonky.validate("foo", "bar") - -// Failure(NonEmptyList(MustBePalindromic("bar"))) -Wonky.validate("monad", "bar") - -// Success(Wonky("monad", "radar")) -Wonky.validate("monad", "radar") -``` - -Awesome! However, there is one caveat – we cannot in good conscience use -`scalaz.Validation` in a for comprehension. Why? Because there is no valid -monad for it. `Validation`'s accumulative nature works via its `Applicative` -instance, but due to how the instance works, there is no consistent monad -(every monad is an applicative functor, where monadic bind is consistent with -applicative apply). However, you can use the `Validation#disjunction` function to -convert it to a `scalaz.\/`, which can then be used in a for comprehension. - -One more thing to note: in the above code snippet I used -`ValidationNel`, which is just a type alias. -`ValidationNel[E, A]` stands for for -`Validation[NonEmptyList[E], A]` – the actual `Validation` will take -anything on the left side that is a `Semigroup`, and `ValidationNel` is -provided as a convenience as often times you may want a non-empty -list of errors describing the various errors that happened in a function. -However, you can do several interesting things with other semigroups. diff --git a/src/blog/event.template.html b/src/blog/event.template.html deleted file mode 100644 index 574f5de9..00000000 --- a/src/blog/event.template.html +++ /dev/null @@ -1,18 +0,0 @@ -@:embed(/templates/main.template.html) -
-
-

${cursor.currentDocument.title}

- - @:for(tags) - ${_} - @:@ -
-
-
-
- ${cursor.currentDocument.content} -
-
-@:@ diff --git a/src/blog/evolving-typelevel.md b/src/blog/evolving-typelevel.md deleted file mode 100644 index 2ae55872..00000000 --- a/src/blog/evolving-typelevel.md +++ /dev/null @@ -1,44 +0,0 @@ -{% - author: [${typelevel}, ${foundation}] - date: "2025-08-19" - tags: [governance] -%} - -# Evolving Typelevel - -In recent years, Typelevel has existed in a somewhat grey area legally. We have long managed a good deal of intellectual -property (the Organization Libraries) and raised funds. In 2022, [we adopted a Charter](governing-documents.md) establishing our governance. -However, we have not had a well-defined legal status. - -We have decided it is time to become a legally-recognized organization. To that end, we have formed the Typelevel -Foundation, a nonprofit organization incorporated in the United States, in the state of California. Additionally, we are applying for -501(c)(3) tax-exempt status from the IRS (the US tax agency), but this process will take several months. - -The initial Board of Directors for the Foundation will be: - -* Arman Bilge -* Daniel Spiewak -* Andrew Valencik -* Mark Waks (known in the community as Justin du Coeur) - -Arman will also be acting as our interim, part-time Executive Director. In the coming months, we will refine the -mission of the Foundation and define the roles that will carry out its work. - -The Foundation Board will assume the responsibilities of governance, particularly legal and financial matters. -Meanwhile, the Steering Committee will become the Technical Steering Committee and focus on the technical work of -overseeing the Typelevel ecosystem. (The Code of Conduct Committee and Security Team will also continue their essential -work.) Our intent is that, by separating the governance and technical responsibilities, everyone can better focus on -what they enjoy and are good at. - -### Updates to the Technical Steering Committee - -Besides the new name and refined scope of the Technical Steering Committee, we are also announcing changes to its -membership. Zach McCoy and Jasna Rodulfa-Blemberg have completed their terms on the Committee; we thank them for their -service! - -### Next steps - -Our core mission remains the same: we will continue to steward the code and culture that are at the heart of Typelevel. -This transition supports our ongoing effort to build a more transparent and sustainable organization that serves our -growing community. Please stay tuned for upcoming announcements of new initiatives, a roadmap for the Foundation, and -opportunities to become involved. diff --git a/src/blog/existential-inside.md b/src/blog/existential-inside.md deleted file mode 100644 index 1a7b7de2..00000000 --- a/src/blog/existential-inside.md +++ /dev/null @@ -1,162 +0,0 @@ -{% - author: ${S11001001} - date: "2016-01-28" - tags: [technical] - katex: true -%} - -# It’s existential on the inside - -*This is the eighth of a series of articles on “Type Parameters and -Type Members”. You may wish to -[check out the beginning](type-members-parameters.md), -which introduces the `PList` type we refer to throughout this article -without further ado.* - -When you start working with type parameters, nothing makes it -immediately apparent that you are working with universal and -existential types at the same time. It is literally a matter of -perspective. - -I will momentarily set aside a lengthy explanation of what this means, -in favor of some diagrams. - -## Universal outside, existential inside - -```scala -def fizzle[A]: PList[A] => PList[A] = { - def rec(pl: PList[A], tl: PList[A]): PList[A] = tl match { - case PNil() => pl - case PCons(x, xs) => rec(PCons(x, pl), xs) - } - xs => rec(PNil(), xs) -} -``` - -![Universal outside existential inside](/img/media/ieoti-fizzle.png) - -The caller can select any `A`, but the implementation must work with -whatever `A` the caller chooses. So `fizzle` is universal in `A` from -the outside, but existential in `A` from the inside. - -So what happens when the caller and callee ‘trade places’? - -## Existential outside, universal inside - -```scala -def wazzle: Int => PList[_] = - n => if (n <= 0) PCons(42, PNil()) - else PCons("hi", PNil()) -``` - -![Existential outside universal inside](/img/media/ieoti-wazzle.png) - -Now the implementation gets to choose an `A`, and the caller must work -with whatever `A` the implementation chooses. So `wazzle` is universal -in `A` from the inside, but existential in `A` from the outside. - -A good way to think about these two, `fizzle` and `wazzle`, is that -`fizzle` takes a type *argument* from the caller, but `wazzle` -*returns* a type (alongside the list) to the caller. - -## Universal (!) outside, existential inside - -```scala -def duzzle[A]: PList[A] => Int = { - case PNil() => 0 - case PCons(_, xs) => 1 + duzzle(xs) -} - -def duzzle2: PList[_] => Int = { - case PNil() => 0 - case PCons(_, xs) => 1 + duzzle2(xs) -} -``` - -![Universal outside existential inside](/img/media/ieoti-duzzle.png) - -`wazzle` “returns” a type, alongside the list, because the existential -appears as part of the return type. However, `duzzle2` places the -existential in argument position. So, as with all type-parameterized -cases, `duzzle` among them, this is one where the caller determines -the type. - -We’ve [discussed](method-equiv.md) how you can -prove that `duzzle` @:math \equiv_{m} @:@ `duzzle2`, in a -previous post. Now, it’s time to see why. - -## Type parameters are parameters - -The caller chooses the value of a type parameter. It also chooses the -value of normal parameters. So, it makes sense to treat them the same. - -Let’s try to look at `fizzle`’s type this way. - -```scala -[A] => PList[A] => PList[A] -``` - -## Existential types are pairs - -If `wazzle` returns a type and a value, it makes sense to treat them -as a returned pair. - -Let’s look at `wazzle`’s type this way. - -```scala -Int => ([A], PList[A]) -``` - -This corresponds exactly to the `forSome` scope -[we have explored previously](nested-existentials.md). -So we can interpret `PList[PList[_]]` as follows. - -```scala -PList[PList[A] forSome {type A}] // explicitly scoped -PList[([A], PList[A])] // “paired” -``` - -## The `duzzle`s are currying - -With these two models, we can finally get to the bottom of -`duzzle` @:math \equiv_{m} @:@ `duzzle2`. Here are their -types, rewritten in the forms we’ve just seen. - -```scala -[A] => List[A] => Int -([A], List[A]) => Int -``` - -Recognize that? They’re just the curried and uncurried forms of the -same function type. - -You can also see why the same type change will not work for `wazzle`. - -```scala -Int => ([A], List[A]) -[A] => Int => List[A] -``` - -We’ve moved part of the return type into an argument, which is…not the -same. - -## The future of types? - -This formulation of universal and existential types is due to -dependently-typed systems, in which they are “dependent functions” and -“dependent pairs”, respectively, though with significantly more -expressive power than we’re working with here. They come by way of the -description of the Quest programming language in -[“Typeful Programming” by Luca Cardelli](http://www.lucacardelli.name/Papers/TypefulProg.pdf), -which shows in a clear, syntactic way that the dependent view of -universal and existential types is perfectly cromulent to -non-dependent type systems like Scala’s. - -It is also the root of my frustration that Scala doesn’t support a -`forAll`, like `forSome` but for universally-quantified types. After -all, you can’t work with one without the other. - -Now we have enough groundwork for -[“Making internal state functional”](internal-state.md), -the next part of this series. I suspect it will be a little prosaic at -this point, though. diff --git a/src/blog/fabric.md b/src/blog/fabric.md deleted file mode 100644 index 940d8125..00000000 --- a/src/blog/fabric.md +++ /dev/null @@ -1,21 +0,0 @@ -{% - author: ${matthicks} - date: "2022-11-10" - tags: [governance] -%} - -# Fabric: A New JSON Library - -I know what you're thinking! "A new JSON library? Why? Don't we have plenty of those?" Well, the short answer is a -resounding yes, but the idea of this library is simplicity and convenience. This library benefited a great deal from the things I liked -about the existing libraries, but hopefully offers some convenience features and simplifications that are entirely new. - -Once I wrote the library I wanted to do some sanity-checking to make sure the performance wasn't substantially worse -than the existing solutions. Much to my surprise, the initial performance was generally faster than Circe or uPickle. -After some additional tuning, [Fabric][fabric] is outperforming the alternatives: [Benchmarks][benchmarks] ([Source][benchmarks-source]) - -[Check it out][fabric] and please give feedback if there's anything we can do to improve it. - -[fabric]: https://github.com/typelevel/fabric -[benchmarks]: https://jmh.morethan.io/?source=https://raw.githubusercontent.com/typelevel/fabric/master/bench/results/benchmarks-1.7.0.json -[benchmarks-source]: https://github.com/typelevel/fabric/tree/master/bench/src/main/scala/bench diff --git a/src/blog/fibers-fast-mkay.md b/src/blog/fibers-fast-mkay.md deleted file mode 100644 index 499ca429..00000000 --- a/src/blog/fibers-fast-mkay.md +++ /dev/null @@ -1,130 +0,0 @@ -{% - author: ${djspiewak} - date: "2021-02-21" - tags: [technical] -%} - -# Why Are Fibers Fast? - -With Cats Effect 3.0 right around the corner, we've been publishing a lot of numbers and scenarios which demonstrate disorientingly high performance in a lot of realistic setups. At first glance, this seems to defy intuition. After all, `IO` is quite a heavyweight abstraction; how is it that it can be competitive with hand-written and hand-tuned code for the same use-case? - -There are a lot of ways to answer this question, and they get both complex *and* depend heavily on exactly what use-case you're talking about, but we can get a general idea of what's going on here by zooming out a bit on the problem space. - -## A Small Strawman - -The main thing to realize is that fibers are *not* fast compared to a single, hot, straight-line piece of code that runs sequentially. For example: - -```scala -val data: Array[Int] = // ... -var i = 0 -while (i < data.length) { - data(i) *= 2 - i += 1 -} -``` - -Consider the above when compared to the tortured purely functional encoding of the same idea: - -```scala -val data: List[Int] = // ... -data.traverse(i => IO.pure(i * 2)) -``` - -Okay the functional encoding is shorter, and that's even accounting for the fact that I just artificially threw `IO` in there for no reason. However, it is also *several orders of magnitude* slower than the imperative version, and more importantly, offers no advantages whatsoever. This code is not run concurrently. It is not asynchronous. It's just doing a bit of stuff with some data. - -Functional programming is not compelling "in the small". It is *never* compelling in the small, because the small is pretty easy regardless of how you do it, and in many cases the "small" is the most performance-sensitive part of your application. Where functional programming is truly compelling is *in the large*, and this is what brings us to exactly why it is that Cats Effect is a shockingly fast runtime system. - -## The Large - -Cats Effect specifically, and fibers *as an idea*, are meant to solve a very particular problem: how do you architect the control flow of a highly-asynchronous, highly-concurrent application without losing your mind or sacrificing performance? That's the *primary* thing they are designed to do. This is why their benefits in terms of code quality and maintainability are usually only felt within realistic examples, and not in scenarios which fit into slides or blog posts. - -More relevantly to our original point though, this is *also* why the performance question is less obvious than it initially seems. In "the large" of system design, micro-optimized performance questions such as how fast you can loop over a `List` are just not relevant. Instead, what's really relevant is how efficiently you optimize the utilization of system resources such as CPU pipelining and page faults, what your inefficiencies are around contention and scheduling, whether or not you have pathological outcomes under pressure, and so on. - -This is where Cats Effect truly shines. By providing a prescriptive (yet pleasant!) framework for defining your application's control flow, Cats Effect is able to deeply understand what it means to schedule and optimize most programs written in `IO`, and thus take advantage of this knowledge using algorithms and techniques which would be impossible (or at the very least, highly impractical) in a hand-written application. This is very much like how modern concurrent garbage collectors are usually much faster than hand-tuned `malloc`/`free` in practical applications, simply because the GC is able to perform some very complicated and non-local logic which would be essentially impossible if managing memory by hand. - -## The Alternative - -To understand this more concretely, we need to think about what we *could* be doing *instead* of using Cats Effect. Imagine a typical HTTP service at high scale. We need to be able to accept requests as they come in, perform some basic data translation, make network calls to downstream services, and then formulate a response and serialize it to bytes. All of this must be done very very quickly and highly concurrently. - -The most direct and naive way to approach this is to allocate one thread per connection. When a client connection comes in, we allocate a thread and hand off the connection for handling, and that thread will live until the response is fully generated. When we make network calls to downstream services, this thread will block on the responses, giving us a nice linear application control flow. - -### Unbounded Threads - -![loads of threads diagram](/img/media/fibers/many-threads.png) - -Implementation-wise, this is very easy to reason about. Your code will all take on a highly imperative structure, with *A* followed by *B* followed by *C*, etc, and it will behave entirely reasonably at small scales! Unfortunately, the problem here is that threads are not particularly cheap. The reasons for this are relatively complex, but they manifest in two places: the OS kernel scheduler, and the JVM itself. - -The JVM is a bit easier to understand here. Whenever you create a new thread, the garbage collector must establish an awareness of that thread and its current call stack so that it can accurately determine what parts of the shared memory space are reachable and what parts are not (literally, figuring out what memory needs to be freed). For this reason, threads are considered *GC roots*, which basically means that when the garbage collector scans the heap for active/inactive memory, it will do so by starting from each thread individually and will traverse its referenced object graph starting from the call stack and working upwards. Modern garbage collectors are extremely clever, but this bit of juggling between the GC and the actively-running threads is still a source of meaningful overhead, even today. The more threads you have, and the larger their respective object graphs, the more time the GC has to take to perform this scan, and the slower your whole system becomes. - -In practice, on most *practical* hardware, you really can't have more than a few hundred threads before the JVM starts to behave poorly. Keeping things bounded to around a few *dozen* threads is generally considered best practice. - -This is a huge problem, and we run face-first into it in architectures like the above where every request is given its own thread. How can we possibly serve tens of thousands of requests simultaneously when each request implies the allocation and maintenance of such an expensive resource? We need some way of handling multiple requests on the *same* thread, or at the very least *bounding* the number of threads we have floating around. Enter thread pools and the famous "disruptor pattern". - -### Bounded Threads - -![thread pool diagram](/img/media/fibers/few-threads.png) - -In this kind of architecture, incoming requests are handed off to a scheduler (usually a shared lock-free work queue) which then hands them off to a fixed set of worker threads for processing. This is the kind of thing you'll see in almost every JVM application written in the past decade or so. - -The problem is that it *still* doesn't quite resolve the issue. Notice how we're still making that network call to **downstream**, which presumably doesn't return instantaneously. We have to block our *carrier thread* (the worker responsible for handling our specific request) while waiting for that downstream to respond. This creates a situation generally known as "starvation", where every thread in the pool is wasted on blocking operations, waiting for the downstream to respond, while new requests are continuing to flood in at the top waiting for worker to pick them up. - -This is extremely wasteful, because we have a scarce resource (threads) which *could* be utilized to make some progress on our ever-filling request queue, but instead is just sitting there in `BLOCKED` state waiting for **downstream** to respond and just generally being a nuisance. The classic solution to this problem is to evolve to an *asynchronous* model for the network connections, allowing the downstream to take as long as it needs to get back to us, and only using the thread when we actually have real work to do. - -### Improved Thread Utilization - -![async pool diagram](/img/media/fibers/async.png) - -This is much more efficient! It's also incredibly confusing, and it gets exponentially worse the more complexity you have in your control flow. In practice most systems like this one have *multiple* downstreams that they need to talk to, often in parallel, which makes this whole thing get crazy in a hurry. It also doesn't get any easier when you add in the fact that just talking to a downstream (like a database) often involves some form of resource management which has to be correctly threaded across these asynchronous boundaries and carried between threads, not to mention problems like timeouts and fallback races and such. It's a mess. - -However, this is essentially what modern systems look like *without* Cats Effect. It's incredibly complicated to reason about it, you pretty much always get it wrong in some way, and it's actually *still* very wasteful and naive in terms of resource efficiency! For example, imagine if any one of these pipelines takes a particularly long time to execute. While it's executing on a thread, everything else that might be waiting for that thread has to sit there in the queue, not receiving a response. This is another form of starvation and it leads to the problem space of *fairness* and related questions. When this happens in your system, you could see extremely high CPU utilization (since all the worker threads are trying very hard to answer requests as quickly as possible) and very *very* good p50 response times, but your p99 and maybe even p90 response times climb into the stratosphere since a subset of requests under load end up sitting in the work queue for a long time, just waiting for a thread. - -All in all, this is very bad, and it starts to hint at *why* it is that Cats Effect is able to do so much better. The above architecture is already insane and effectively impossible to get right if you're doing it by hand. Cats Effect raises your program up to a higher abstraction layer and allows you to think about things in terms of a simpler model: fibers. - -### Many Fibers, Fewer Threads, One Scheduler - -![fiber diagram](/img/media/fibers/fibers.png) - -This diagram looks a lot like the first one! In here, we're just allocating a new fiber for each request that comes in, much like how we *tried* to allocate a new thread per request. Each fiber is a self-contained, sequential unit which *semantically* runs from start to finish and we don't really need to think about what's going on under the surface. Once the response has been produced to the client, the fiber goes away and we never have to think about it again. - -Of course, you can't map fibers 1-to-1 with threads, otherwise we end up recreating the first architecture but with more extra overhead in all directions. The solution here is to implement a **Scheduler** which understands the nature of fibers and figures out how to optimally map them onto some tightly controlled set of underlying worker threads. In Cats Effect 2, as in almost all asynchronous runtimes on the JVM, this was done using an `Executor` which simply maintained an internal queue of fibers. Each time a worker thread finished with a previous fiber, the queue would dictate which fiber it needs to work on *next*. - -Critically, fibers are free to *suspend* at any time. In our example, fibers suspend whenever they talk to the **downstream**, since they're semantically waiting for a response and cannot make any forward progress within their sequential flow. Fiber suspension is safe and efficient though, because the scheduler responds to this by simply removing the fiber from the work queue until such time as the **downstream** responds, at which point the scheduler seamlessly re-enqueues the fiber and a new thread is able to pick it up. This ensures that the threads are always kept busy and any suspended fibers are truly free: the only resource they consume is memory. - -Raising the level of abstraction here isn't just good for users who write programs (you!) in this model, it's also good for the runtime itself. The scheduler is able to solve problems such as fairness in a more general way – for example, by preventing a fiber from hogging its worker thread for too long and artificially suspending that fiber and sending it to the back of the work queue. The system is also able to contemplate other, vastly more complex problems such as resource management and interruption (for timeouts), simply because the scheduler is taking care of all of these low-level details related to keeping the workers busy. - -This is already a significant improvement over what we can do by hand, not only cognitively but also in terms of runtime performance. The scheduler is able to solve a very complicated problem space in a nice, general fashion given global knowledge of the fiber space, and this results in dramatically improved efficiency in resource utilization throughout the system. We're not done though. - -The above diagram is basically how Cats Effect 2, Vert.x, Project Loom, and almost every other asynchronous framework on the JVM behaves. There are some refinements that you can apply by trying to make the scheduler a bit smarter and such, but the state of the art on the JVM is basically what you see above. - -That is, until Cats Effect 3. - -### Many Fibers, Fewer Threads, Many Schedulers - -![work-stealing diagram](/img/media/fibers/work-stealing.png) - -Cats Effect 3 has a *much* smarter and more efficient scheduler than any other asynchronous framework on the JVM. It was heavily inspired by the [Tokio](https://tokio.rs) Rust framework, which is fairly close to Cats Effect's problem space. As you might infer from the diagram, the scheduler is no longer a central clearing house for work, and instead is dispersed among the worker threads. This *immediately* results in some massive efficiency wins, but the real magic is still to come. - -Work-stealing schedulers are not a new idea, and in fact, they are the default when you use Monix, Scala's `Future`, or even the upcoming Project Loom. The whole idea behind a work-stealing scheduler is that the overhead of the scheduler itself can be dispersed and amortized across the worker pool. This has a number of nice benefits, but the most substantial is that it removes the single point of contention for all of the worker threads. - -In a conventional implementation of the disruptor pattern (which is what a fixed thread pool *is*), all workers must `poll` from a single work queue each time they iterate, fetching their next task. The queue must solve the relatively complex synchronization problem of ensuring that every task is handed to *exactly* one worker, and as it turns out, this synchronization is extremely expensive. To make matters worse, the cost of this synchronization increases *quadratically* with the amount of contention! As you increase the number of working threads, the number of synchronization points between threads increases with the square of the threads. This is actually conceptually obvious, since each thread will create contention faults with every other thread, and only one thread will actually "win" and get the task. This overhead is somewhat imperceptible if you have a small number of workers, but with a larger number of workers it begins to dominate very quickly. And note, the ideal number of workers is exactly equal to the number of CPUs backing your JVM, meaning that scaling up is almost impossible with this kind of design. - -Work-stealing, for contrast, allows the individual worker threads to manage their own *local* queue, and when that queue runs out, they simply take work from each other on a one-to-one basis. Thus, the only contention that exists is between the stealer and the "stealee", entirely avoiding the quadratic growth problem. In fact, contention becomes *less* frequent as the number of workers and the load on the pool increases. You can conceptualize this with the following extremely silly plot (higher numbers are *bad* because they represent overhead): - -![plot of work stealing overhead vs standard disruptor pattern](/img/media/fibers/overhead.png) - -Work-stealing is simply very, very, very good. But we can do even better. - -Most work-stealing implementations on the JVM delegate to `ForkJoinThreadPool`. There are a number of problems with this thread pool implementation, not the least of which is that it tries to be a little too clever around thread blocking. In essence, the work-stealing implementation in the JDK has to render itself through a highly generic interface – literally just `execute(Runnable): Unit`! – and cannot take advantage of the deep system knowledge that we have within the Cats Effect framework. Specifically, we know some very important things: - -- **Hard-blocking worker threads are very rare, if they exist at all.** Users of Cats Effect tend to be quite good about leveraging `blocking` and other tools to avoid hard-blocking the main compute pool, meaning that we can optimize *assuming* that this case is exceptionally rare and we don't need to try to detect blocked threads and speculatively grow the pool. This keeps our worker thread count exactly where it should be: pinned to the number of physical CPUs. -- **Fibers represent *sequential* workflows that maintain working memory sets across suspension boundaries.** This is a more complicated thing to conceptualize, but it makes a lot of intuitive sense. When our request/response fiber in our running example hears back from **downstream**, it's going to pick up exactly where it left off *with the same data* that it was touching prior to its suspension. This is a *huge* thing to optimize for, because memory working sets are very expensive, and we can leverage this knowledge to reduce page faults and improve CPU cache utilization. - -The way in which we optimize for fibers specifically is by ensuring that fiber suspension and wake-up is kept pinned to a single worker thread per fiber *as much as possible*. Obviously, if one thread is completely overloaded by work and the other threads are idle, then the fiber is going to move over to those other threads and the result is going to be a page fault, but in the *common* case this simply doesn't happen. Each thread is responsible for some number of fibers, and those fibers remain on that thread as long as possible. This allows the operating system thread scheduler in turn to live within its happy-path assumptions about memory utilization, keeping fiber memory state resident within the L3 and L2 caches for longer, even across asynchronous boundaries! This has a massive impact on application performance, because it means that fibers are able to avoid round-trips to system main memory in the common case. - -This is in contrast to the single shared queue implementation that is all-too common in this space. With a single shared work queue, once a fiber suspends, there is no guarantee that it will be reallocated back to the thread it *was* on prior to suspension. In fact, the odds of this drop to almost nothing as you increase the number of workers, and so again the general pattern gets much *worse* as you scale up the number of CPUs. Remember that any time semantically-sequential work is moved from one thread to another, that work is *usually* also going to be moving from one *CPU* to another, meaning that all of the nice caching benefits that we normally get from the memory cache hierarchy are blown away as we have to laboriously re-build our working set from main memory (a page fault). A fiber-aware work-stealing scheduler avoids this problem almost all of the time. - -This also means that the fairness problem can be solved *effectively for free*, since a fiber is able to yield control back to its worker thread without incurring the penalty of a page fault or even a memory barrier! In the event that the worker thread has other fibers waiting for time, those fibers will take over at the yield and the starvation is averted (keeping your p99s low!). If the worker thread does *not* have other fibers waiting for time, then the yielding fiber picks up right where it left off with almost exactly zero cost, meaning that we're able to solve fairness with no cost to throughput. - -## Conclusion - -There's a lot more going on in this layer than just what I've described, but hopefully this gives a rough intuition as to *why* Cats Effect 3's scheduler is able to achieve the crazy performance numbers we see in practice, and also why it is that fibers are not only an easier model for writing highly scalable modern applications, they are also a *faster* and more efficient model for running those applications at scale. diff --git a/src/blog/fix.md b/src/blog/fix.md deleted file mode 100644 index 70656917..00000000 --- a/src/blog/fix.md +++ /dev/null @@ -1,328 +0,0 @@ -{% - author: ${S11001001} - date: "2014-04-14" - tags: [technical] -%} - -# Primitive recursion with fix and Mu - -Consider the simple cons-list datatype. - -```scala -import scalaz.Equal, scalaz.std.option._, scalaz.syntax.std.option._, - scalaz.std.anyVal._, scalaz.std.function._, - scala.reflect.runtime.universe.reify - -sealed abstract class XList[A] -final case class XNil[A]() extends XList[A] -final case class XCons[A](head: A, tail: XList[A]) extends XList[A] -``` - -And a simple function over this structure. Say, a simple summing -function. - -```scala -def sum(xs: XList[Int]): Int = xs match { - case XNil() => 0 - case XCons(x, xs) => x + sum(xs) -} -``` - -And that seems to work out alright. - -```scala -scala> val nums = XCons(2, XCons(3, XCons(4, XCons(42, XNil())))) -nums: XCons[Int] = XCons(2,XCons(3,XCons(4,XCons(42,XNil())))) - -scala> sum(nums) -res0: Int = 51 -``` - -Has it ever struck you as curious that, though its own value was -required to construct a value like `sum`, the system has no problem -with that? - -Oh, well, that's just a recursive function, you say. Well, what's so -special about recursive functions? Why do they get special treatment -so that they can define themselves with themselves? - -Induction and termination -------------------------- - -First, let's be clear: there's a limit to how much of `sum` can be -used in its own definition. - -Let us consider the moral equivalent of the statement “this function -gives the sum of a list of integers because it is the function that -gives the sum of a list of integers.” - -```scala -def sum2(xs: XList[Int]): Int = sum2(xs) -``` - -scalac will compile this definition; it is well-typed. However, it -will be nonsensical at runtime, because it is nonsense; it will either -throw some exception or loop forever. - -Let us consider a similar case: the infinite list of 42s. - -```scala -scala> val fortyTwos: Stream[Int] = 42 #:: fortyTwos -fortyTwos: Stream[Int] = Stream(42, ?) - -scala> fortyTwos take 5 toList -res0: List[Int] = List(42, 42, 42, 42, 42) -``` - -The definition of `fortyTwos` is like that of `sum`; it uses its own -value while constructing said value. A similar definition to `sum2` -is, likewise, nonsense, though scalac can catch this particular case: - -```scala -scala> val fortyTwos2: Stream[Int] = fortyTwos2 -:7: error: value fortyTwos2 does nothing other than call itself recursively - val fortyTwos2: Stream[Int] = fortyTwos2 - ^ -``` - -Obviously, functions *aren't* special; non-function values, like -functions, can be defined using their own values. But how can we -characterize the difference between the good, terminating definitions, -and the bad, nonterminating definitions? - -Proof systems like Coq and Agda perform a strong check on recursive -definitions; for definitions like `sum`, they require the recursion -match the structure of the data type, just as ours does, so that each -recursive call is known to operate over smaller data. For definitions -like `fortyTwos`, they apply other strategies. In Scala, we have to -make do with informality. - -I like to think of it this way: **a recursive definition must always -perform at least one inductive step**. `sum` does so because, in the -recursive case, it gives “supposing I have the sum of `tail`, the sum -is the `head` plus that.” `fortyTwos` does because it says “the value -`fortyTwos` is `42` consed onto the value `fortyTwos`.” It is, at -least, the start of a systematic way of thinking about terminating -recursive definitions. - -Abstracting the recursion -------------------------- - -Now that we have a framework for thinking about what is required in a -recursive definition, we can start abstracting over it. - -The above recursive definitions were accomplished with special -language support: the right-hand side of any term definition, `val` or -`def`, can refer to the thing being so defined. Scalaz provides -[the `fix` function](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/scalaz/std/function$.html), -which, if it were provided intrinsically, would eliminate the need for -this language support. - -```scala -def fix[A](f: (=> A) => A): A = { - lazy val a: A = f(a) - a -} -``` - -In this definition, the value returned by `f` *is* the value given to -it as an argument. It's a by-name argument because that's how we -enforce the requirement: `f` must perform at least one inductive step -in the definition of its result, though it can refer to that result by -its argument, which we enforce by requiring it to return a value -*before* evaluating that argument. - -Let's redefine `sum` with `fix`, after importing it from -`scalaz.std.function`. - -```scala -val sum3: XList[Int] => Int = fix[XList[Int] => Int](rec => { - case XNil() => 0 - case XCons(x, xs) => x + rec.apply(xs) -}) -``` - -And Scala thinks that's alright. - -```scala -scala> sum3(nums) -res1: Int = 51 -``` - -The interesting thing here is that `sum3`'s definition doesn't refer -to the name `sum3`; the recursion is entirely inside the `fix` -argument. So one advantage of `fix` is that it's easy to write -recursive values as expressions without giving them a name. - -For example, there's the definition of `fortyTwos`: - -```scala -scala> fix[Stream[Int]](42 #:: _) -res2: Stream[Int] = Stream(42, ?) - -scala> res2 take 5 toList -res3: List[Int] = List(42, 42, 42, 42, 42) -``` - -For special data structures ---------------------------- - -It can be inconvenient to avoid evaluating the argument when providing -an induction step. Fortunately, the requirement that `f` be nonstrict -in its argument is too strong to characterize the space of values that -can be defined with `fix`-style recursion. - -For a given data type, there's often a way to abstract out the -nonstrictness. For example, here's an `Equal` instance combinator -that is fully evaluated, but doesn't force the argument until after -the (equivalent) result has been produced. - -```scala -def lazyEqual[A](A: => Equal[A]): Equal[A] = new Equal[A] { - def equal(l: A, r: A): Boolean = A equal (l, r) - override def equalIsNatural = A.equalIsNatural -} -``` - -Given that, we can produce a `fix` variant for `Equal` that passes the -`Equal` argument strictly. You're simply not allowed to invoke any of -the typeclass's methods. - -```scala -def fixEq[A](f: Equal[A] => Equal[A]): Equal[A] = - fix[Equal[A]](A => f(lazyEqual(A))) -``` - -And now, we have the machinery to build a fully derived `Equal` -instance for `XList`, without function recursion, by defining the base -case and inductive step! - -```scala -def `list equal`[A: Equal]: Equal[XList[A]] = - fixEq[XList[A]](implicit rec => - Equal.equalBy[XList[A], Option[(A, XList[A])]]{ - case XNil() => None - case XCons(x, xs) => Some((x, xs)) - }) -``` - -That works out to interesting compiled output. Note especially the -last line, and its (strict) use of `rec` towards the end. - -```scala -scala> reify(fixEq[XList[Int]](implicit rec => - | Equal.equalBy[XList[Int], Option[(Int, XList[Int])]]{ - | case XNil() => None - | case XCons(x, xs) => Some((x, xs)) - | })) -res10: reflect.runtime.universe.Expr[scalaz.Equal[XList[Int]]] = -Expr[scalaz.Equal[XList[Int]]]($read.fixEq[$read.XList[Int]](((implicit rec) => - Equal.equalBy[$read.XList[Int], Option[Tuple2[Int, $read.XList[Int]]]] - (((x0$1) => x0$1 match { - case $read.XNil() => None - case $read.XCons((x @ _), (xs @ _)) => Some.apply(Tuple2.apply(x, xs)) -}))(option.optionEqual(tuple.tuple2Equal(anyVal.intInstance, rec)))))) -``` - -f0, a binary serialization library, -[uses a similar technique](https://github.com/joshcough/f0/blob/v1.1.1/src/main/scala/f0/Readers.scala#L216-L222) -to help define codecs on recursive data structures. - -What about `XList`? -------------------- - -If we can abstract out the idea of recursive value definitions, what -about recursive type definitions? Well, thanks to higher kinds, sure! -Scalaz doesn't provide it, but it is commonly called `Mu`. - -```scala -final case class Mu[F[_]](value: F[Mu[F]]) -``` - -We have to put a class in the middle of it so that we don't have an -infinite type; Haskell has a similar restriction. But the principle -is the same as with `fix`: feed one datatype induction step `F` to the -higher-order type `Mu` and it will feed `F`'s result back to itself. - -For example, here is the equivalent definition of `XList` with `Mu`. - -```scala -type XList2Step[A] = {type λ[α] = Option[(A, α)]} -type XList2[A] = Mu[XList2Step[A]#λ] -``` - -Note the typelambda's similarity to the second type argument to -`#equalBy` above. And for demonstration, the isomorphism with -`XList`. - -```scala -def onetotwo[A](xs: XList[A]): XList2[A] = xs match { - case XNil() => Mu[XList2Step[A]#λ](None) - case XCons(x, xs) => Mu[XList2Step[A]#λ](Some((x, onetotwo(xs)))) -} - -def twotoone[A](xs: XList2[A]): XList[A] = - xs.value cata ({case (x, xs) => XCons(x, twotoone(xs))}, XNil()) -``` - -Of course, `fix` lends itself to both of these definitions; I have -left its use off here. But let's check those functions: - -```scala -scala> onetotwo(nums) -res11: XList2[Int] = Mu(Some((2,Mu(Some((3,Mu(Some((4,Mu(Some((42,Mu(None))))))))))))) - -scala> twotoone(res11) -res12: XList[Int] = XCons(2,XCons(3,XCons(4,XCons(42,XNil())))) -``` - -`fix` over `Mu` ---------------- - -And, finally, the associated general `Equal` definition for `Mu`. The -`contramap` step is just noise to deal with the fact that the `Mu` -structure has to actually exist; you can ignore it for the most part. - -```scala -def equalMu[F[_]](fa: Equal[Mu[F]] => Equal[F[Mu[F]]]): Equal[Mu[F]] = - fixEq[Mu[F]](emf => fa(emf) contramap (_.value)) -``` - -The evidence we really want is `forall a. Equal[a] => Equal[F[a]]`, -but that's too hard to express in Scala, so this does it in a pinch. -All we're interested in is that we can derive `F`'s equality given the -equality of any type argument given to it. Let's prove that we have -such an `Equal`-lifter: - -```scala -// redefined because Tuple2Equal scalaz is strict on equalIsNatural -class Tuple2Equal[A1, A2](_1: Equal[A1], _2: Equal[A2]) - extends Equal[(A1, A2)] { - def equal(f1: (A1, A2), f2: (A1, A2)) = - _1.equal(f1._1, f2._1) && _2.equal(f1._2, f2._2) - override def equalIsNatural: Boolean = _1.equalIsNatural && _2.equalIsNatural -} -implicit def tup2eq[A1: Equal, A2: Equal] = - new Tuple2Equal[A1, A2](implicitly, implicitly) - -abstract class Blah // just a placeholder - -scala> {implicit X: Equal[Blah] => implicitly[Equal[XList2Step[Int]#λ[Blah]]]} -res4: scalaz.Equal[Blah] => scalaz.Equal[Option[(Int, Blah)]] = -``` - -And now that we have `F` equality, we're done, because `Mu` is `F`s -all the way down. - -```scala -scala> equalMu[XList2Step[Int]#λ](implicit fa => implicitly) -res5: scalaz.Equal[Mu[[α]Option[(Int, α)]]] = scalaz.Equal$$anon$2@de52bcf - -scala> res5 equal (onetotwo(nums), onetotwo(nums)) -res6: Boolean = true - -scala> res5 equal (onetotwo(nums), onetotwo(XCons(3,nums))) -res7: Boolean = false -``` - -*This article was tested with Scala 2.10.4 & Scalaz 7.0.6.* diff --git a/src/blog/forget-refinement-aux.md b/src/blog/forget-refinement-aux.md deleted file mode 100644 index 85aafe2a..00000000 --- a/src/blog/forget-refinement-aux.md +++ /dev/null @@ -1,158 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-19" - tags: [technical] -%} - -# What happens when I forget a refinement? - -*This is the third of a series of articles on “Type Parameters and -Type Members”. If you haven’t yet, you should -[start at the beginning](type-members-parameters.md), -which introduces code we refer to throughout this article without -further ado.* - -As I mentioned -[in the previous article](method-equiv.md#when-are-two-methods-less-alike), -the error of the `mdropFirstE` signature, taking `MList` and returning -merely `MList`, was to fail to relate the input element type to the -output element type. This mistake is an easy one to make when failure -is the default behavior. - -By contrast, **when we try this with `PList`, the compiler helpfully -points out our error**. - -```scala -def pdropFirst(xs: PList): PList = ??? - -TmTp3.scala:6: class PList takes type parameters - def pdropFirst(xs: PList): PList = ??? - ^ -TmTp3.scala:6: class PList takes type parameters - def pdropFirst(xs: PList): PList = ??? - ^ -``` - -What happens when I misspell a refinement? ------------------------------------------- - -There is another mistake that type members open you up to. I have been -using the very odd type parameter—and member—name `T`. -Java developers will find this choice very ordinary, but the name of -choice for the discerning Scala programmer is `A`. So suppose I -attempted to correct `mdropFirstE`’s type as follows: - -```scala -def mdropFirstE2[T0](xs: MList {type A = T0}) = - xs.uncons match { - case None => MNil() - case Some(c) => c.tail - } -``` - -This method compiles, but I cannot invoke it! - -```scala -> mdropFirstE2(MNil[Int]()) -:20: error: type mismatch; - found : tmtp.MNil{type T = Int} - required: tmtp.MList{type A = ?} - mdropFirstE2(MNil[Int]()) - ^ - -> mdropFirstE2(MCons[Int](42, MNil[Int]())) -:20: error: type mismatch; - found : tmtp.MCons{type T = Int} - required: tmtp.MList{type A = ?} - mdropFirstE2(MCons[Int](42, MNil[Int]())) - ^ -``` - -That’s because `MList {type A = T0}` is a perfectly reasonable -intersection type: values of this type have *both* the type `MList` in -their supertype tree somewhere, *and* a type member named `A`, which -is bound to `T0`. In terms of subtyping relationships: - -```scala -MList {type A = T0} <: MList -// and unrelatedly, -MList {type A = T0} <: {type A = T0} -``` - -That `MList` has no such type member `A` is irrelevant to the -intersection and refinement of types in Scala. This type means “an -instance of the trait `MList`, with a type member named `A` set -to `T0`”. This type member `A` could come from another trait mixed -with `MList` or an inline subclass. Whether such a thing is -impossible to instantiate—due to `sealed`, `final`, or anything -else—is also irrelevant; **types with no values are meaningful and -useful in both Java and Scala**. - -Why `T0`? What’s `Aux`? ------------------------- - -A few of the methods on `MList` we have seen so far take a type -parameter `T0` instead of `T`. This is just a mnemonic trick; I’m -saying “I would write `T` here if `scalac` would let me”, which I have -borrowed from -[`scalaz.Unapply`](https://github.com/scalaz/scalaz/blob/v7.1.3/core/src/main/scala/scalaz/Unapply.scala#L217). -Let’s try to implement `def MNil` taking a `T` instead. - -```scala -def MNil[T](): MNil {type T = T} = - new MNil { - type T = T - } - -// Scala complains, though: -TmTp3.scala:15: illegal cyclic reference involving type T - def MNil[T](): MNil {type T = T} = - ^ -``` - -This is a scoping problem; the refinement type makes the member `T` -shadow our method type parameter `T`. We dealt with the problem in -`MList#uncons` and `MCons#tail` as well, way back in section “Two -lists, all alike” of -[the first part](type-members-parameters.md#two-lists-all-alike), -in those cases by outer-scoping the `T` as -`self.T` instead. - -**When defining a type with members, you should define an `Aux` type -in your companion that converts the member to a type parameter.** The -name `Aux` is a convention I have borrowed from -[Shapeless ops](https://github.com/milessabin/shapeless/blob/shapeless-2.2.4/core/src/main/scala/shapeless/ops/hlists.scala#L1501). -This is pretty much boilerplate; in this case, add this to -`object MList`: - -```scala -type Aux[T0] = MList {type T = T0} -``` - -Now you can write `MList.Aux[Int]` instead of `MList {type T = Int}`. -Here’s `mdropFirstT`’s signature rewritten in this style. - -```scala -def mdropFirstT2[T](xs: MList.Aux[T]): MList.Aux[T] = ??? -``` - -Furthermore, because the member `T` is not in scope for `Aux`’s type -parameter position, you can take method type parameters named `T` and -sensibly write `MList.Aux[T]` without the above error. You can see -this in the immediately preceding example. But, stepping back a bit, -this should be considered an advantage for type parameters more -generally; `PList` doesn’t have this problem in the first place. - -**Using `Aux` also helps you avoid the errors of forgetting to specify -or misspelling a type member**, as described at the beginning of this -article. With `Aux`, as with ordinary parameterized types, a missing -argument is caught by the compiler, and misspelling the parameter name -is impossible. - -In -[the next part, “Type projection isn’t that specific”](type-projection.md), -we’ll see why something that, at first glance, seems -like a workable alternative to either refinement or the `Aux` trick, -doesn’t work out as well as people wish it would. - -*This article was tested with Scala 2.11.7.* diff --git a/src/blog/four-ways-to-escape-a-cake.md b/src/blog/four-ways-to-escape-a-cake.md deleted file mode 100644 index 8fee27c5..00000000 --- a/src/blog/four-ways-to-escape-a-cake.md +++ /dev/null @@ -1,586 +0,0 @@ -{% - author: ${S11001001} - date: "2017-03-01" - tags: [technical] -%} - -# Four ways to escape a cake - -The mixin style of importing in which classes and traits are defined -within traits, as seen in `scala.reflect.Universe`, ScalaTest, and -other Scala styles, seems to be infectious. By that, I mean once you -define something in a trait to be mixed in, to produce another -reusable module that calls that thing, you must define *another* -trait, and so must anyone using *your* module, and so on and so forth. -You effectively become “trapped in the cake”. - -However, we can use type parameters that represent **singleton types** -to write functions that are polymorphic over these “cakes”, without -being defined as part of them or mixed in themselves. For example, you -can use this to write functions that operate on elements of a -reflection universe, without necessarily passing that universe around -all over the place. - -Well, for the most part. Let’s see how far this goes. - -## Our little universe - -Let’s set aside the heavyweight real-world examples I mentioned above -in favor of a small example. Then, we should be able to explore the -possibilities in this simpler space. - -```scala -final case class LittleUniverse() { - val haystack: Haystack = Haystack() - - final case class Haystack() { - def init: Needle = Needle() - def iter(n: Needle): Needle = n - } - - final case class Needle() -} -``` - -*For brevity, I’ve defined member `class`es, but this article equally -applies if you are using abstract `type`s instead, as any Functional -programmer of pure, virtuous heart ought to!* - -Suppose we have a universe. - -```scala -scala> val lu: LittleUniverse = LittleUniverse() -lu: LittleUniverse = LittleUniverse() -``` - -The thing that Scala does for us is not let `Haystack`s and `Needle` s -from one universe be confused with those from another. - -```scala -val anotherU = LittleUniverse() - -scala> lu.haystack.iter(anotherU.haystack.init) -:14: error: type mismatch; - found : anotherU.Needle - required: lu.Needle - lu.haystack.iter(anotherU.haystack.init) - ^ -``` - -The meaning of this error is “you can’t use one universe’s `Haystack` -to `iter` a `Needle` from another universe”. - -This doesn’t look very important given the above code, but it’s a -*real* boon to more complex scenarios. You can set up a lot of -interdependent abstract invariants, verify them all, and have the -whole set represented with the “index” formed by the singleton type, -here `lu.type` or `anotherU.type`. - -## Working with a universe on hand - -Refactoring in macro-writing style seems to be based upon passing the -universe around everywhere. We can do that. - -```scala -def twoInits(u: LittleUniverse): (u.Needle, u.Needle) = - (u.haystack.init, u.haystack.init) - -def stepTwice(u: LittleUniverse)(n: u.Needle): u.Needle = - u.haystack.iter(u.haystack.iter(n)) -``` - -The most important feature we’re reaching for with these fancy -dependent method types, and the one that we have to *keep* reaching -for if we want to write sane functions outside the cake, is -**preserving the singleton type index**. - -```scala -scala> twoInits(lu) -res3: (lu.Needle, lu.Needle) = (Needle(),Needle()) - -scala> stepTwice(anotherU)(anotherU.haystack.init) -res4: anotherU.Needle = Needle() -``` - -These values are ready for continued `iter`ing, or whatever else -you’ve come up with, in the confines of their respective -universes. That’s because they’ve “remembered” where they came from. - -By contrast, consider a simple replacement of the path-dependencies -with a type projection. - -```scala -def brokenTwoInits(u: LittleUniverse) - : (LittleUniverse#Needle, LittleUniverse#Needle) = - (u.haystack.init, u.haystack.init) - -scala> val bti = brokenTwoInits(lu) -bti: (LittleUniverse#Needle, LittleUniverse#Needle) = (Needle(),Needle()) -``` - -That seems to be okay, until it’s time to actually use the result. - -```scala -scala> lu.haystack.iter(bti._1) -:14: error: type mismatch; - found : LittleUniverse#Needle - required: lu.Needle - lu.haystack.iter(bti._1) - ^ -``` - -The return type of `brokenTwoInits` “forgot” the index, `lu.type`. - -## Getting two needles without a universe - -When we pass a `LittleUniverse` to the above functions, we’re also -kind of passing in a constraint on the singleton type created by the -argument variable. That’s how we know that the returned `u.Needle` is -a perfectly acceptable `lu.Needle` in the caller scope, when we pass -`lu` as the universe. - -However, as the contents of a universe become more complex, there are -many more interactions that need not involve a universe at all, at -least not directly. - -```scala -def twoInitsFromAHaystack[U <: LittleUniverse]( - h: U#Haystack): (U#Needle, U#Needle) = - (h.init, h.init) - -scala> val tifah = twoInitsFromAHaystack[lu.type](lu.haystack) -tifah: (lu.Needle, lu.Needle) = (Needle(),Needle()) -``` - -Since we didn’t pass in `lu`, how did it know that the returned -`Needle`s were `lu.Needle`s? - -1. The type of `lu.haystack` is `lu.Haystack`. -2. That type is shorthand for `lu.type#Haystack`. -3. We passed in `U = lu.type`, and our argument meets the resulting - requirement for a `lu.type#Haystack` (after expanding `U`). -4. The type of the expression `h.init` is - `u.Needle forSome {val u: U}`. We use an existential because the - relevant variable (and its singleton type) is not in scope. -5. This type *widens* to `U#Needle`, satisfying the expected return - type. - -This seems like a more complicated way of doing things, but it’s very -freeing: by not being forced to *necessarily* pass the universe around -everywhere, you’ve managed to escape the cake’s clutches much more -thoroughly. You can also write syntax enrichments on various members -of the universe that don’t need to talk about the universe’s value, -just its singleton type. - -Unless, you know, the index appears in contravariant position. - -## Syntactic `stepTwice` - -One test of how well we’ve managed to escape the cake is to be able to -write enrichments that deal with the universe. This is a little -tricky, but quite doable if you have the universe’s value. - -With the advent of `implicit class`, this became a little easier to do -wrongly, but it’s a good start. - -```scala -implicit class NonWorkingStepTwice(val u: LittleUniverse) { - def stepTwiceOops(n: u.Needle): u.Needle = - u.haystack.iter(u.haystack.iter(n)) -} -``` - -That compiles okay, but seemingly can’t actually be used! - -```scala -scala> lu stepTwiceOops lu.haystack.init -:15: error: type mismatch; - found : lu.Needle - required: _1.u.Needle where val _1: NonWorkingStepTwice - lu stepTwiceOops lu.haystack.init - ^ -``` - -There’s a hint in that we had to write `val u`, not `u`, nor `private -val u`, in order for the `implicit class` itself to compile. This -signature tells us that there’s an *argument* of type -`LittleUniverse`, and a *member* `u: LittleUniverse`. However, whereas -with the function examples above, we (and the compiler) could trust -that they’re one and the same, we have no such guarantee here. So we -don’t know that an `lu.Needle` is a `u.Needle`. We didn’t get far -enough, but we don’t know that a `u.Needle` is an `lu.Needle`, either. - -## Relatable variables - -Instead, we have to expand a little bit, and take advantage of a very -interesting, if obscure, element of the type equivalence rules in the -Scala language. - -```scala -class WorkingStepTwice[U <: LittleUniverse](val u: U) { - def stepTwice(n: u.Needle): u.Needle = - u.haystack.iter(u.haystack.iter(n)) -} - -implicit def WorkingStepTwice[U <: LittleUniverse](u: U) - : WorkingStepTwice[u.type] = - new WorkingStepTwice(u) -``` - -*Unfortunately, the ritual of expanding the `implicit class` shorthand -is absolutely necessary; the `implicit class` won’t generate the -dependent-method-typed implicit conversion we need.* - -Now we can get the proof we need. - -```scala -scala> lu stepTwice lu.haystack.init -res7: _1.u.Needle forSome { val _1: WorkingStepTwice[lu.type] } = Needle() - -// that's a little weird, but reduces to what we need -scala> res7: lu.Needle -res8: lu.Needle = Needle() -``` - -How does this work? - -1. Implicitly convert `lu`, giving us a `conv: - WorkingStepTwice[lu.type]`. -2. This means that `conv.u: lu.type`, by expansion of `U`. -3. This in turn means that `conv.u.type <: lu.type`. - -The next part is worth taking in two parts. It may be worth -having -[§3.5.2 “Conformance”](http://www.scala-lang.org/files/archive/spec/2.12/03-types.html#conformance) of -the language spec open for reference. First, let’s consider the return -type (a covariant position), which is simpler. - -1. The return type expands to `conv.u.type#Needle`. -2. The ninth conformance bullet point tells us that the left side of a - `#` projection is covariant, so because `conv.u.type <: lu.type` - (see above), the return type *widens* to `lu.type#Needle`. -3. For this, `lu.Needle` is a shorthand. - -It was far longer until I realized how the argument type works. You’ll -want to scroll up on the SLS a bit, to the “Equivalence” section. Keep -in mind that we are trying to widen `lu.Needle` to `conv.u.Needle`, -which is the reverse of what we did for the return type. - -1. Our argument’s type expands to `lu.type#Needle`. -2. The second bullet point under “Equivalence” says that “If a path - *p* has a singleton type *q*`.type`, then *p*`.type` ≡ *q*`.type`.” - From this, we can derive that `conv.u.type = lu.type`. This is a - stronger conclusion than we reached above! -3. We substitute the left side of the `#` using the equivalence, - giving us `conv.u.type#Needle`. - -I cannot characterize this feature of the type system as anything -other than “really freaky” when you first encounter it. It seems like -an odd corner case. Normally, when you write `val x: T`, then `x.type` -is a *strict* subtype of `T`, and you can count on that, but this -carves out an exception to that rule. It is sound, though, and an -absolutely essential feature! - -```scala -val sameLu: lu.type = lu - -scala> sameLu.haystack.iter(lu.haystack.init) -res9: sameLu.Needle = Needle() -``` - -Without this rule, even though we have given it the most specific type -possible, `sameLu` couldn’t be a *true* substitute for `lu` in all -scenarios. That means that in order to make use of singleton type -indices, we would be forever beholden to the *variable* we initially -stored the value in. I think this would be *extremely inconvenient*, -structurally, in almost all useful programs. - -With the rule in place, we can fully relate the `lu` and `conv.u` -variables, to let us reorganize how we talk about universes and values -indexed by their singleton types in many ways. - -## A pointless argument - -Let’s try to hide the universe. We don’t need it, after all. We can’t -refer to `u` in the method signature anymore, so let’s try the same -conversion we used with `twoInitsFromAHaystack`. We already have the -`U` type parameter, after all. - -```scala -class CleanerStepTwice[U <: LittleUniverse](private val u: U) { - def stepTwiceLively(n: U#Needle): U#Needle = - ??? -} - -implicit def CleanerStepTwice[U <: LittleUniverse](u: U) - : CleanerStepTwice[u.type] = - new CleanerStepTwice(u) -``` - -This has the proper signature, and it’s cleaner, since we don’t expose -the unused-at-runtime `u` variable anymore. We could refine a little -further, and replace it with a `U#Haystack`, just as with -`twoInitsFromAHaystack`. - -This gives us the same interface, with all the index preservation we -need. Even better, it infers a nicer return type. - -```scala -scala> def trial = lu stepTwiceLively lu.haystack.init -trial: lu.Needle -``` - -Now, let’s turn to implementation. - -```scala -class OnceMoreStepTwice[U <: LittleUniverse](u: U) { - def stepTwiceFinally(n: U#Needle): U#Needle = - u.haystack.iter(u.haystack.iter(n)) -} - -:18: error: type mismatch; - found : U#Needle - required: OnceMoreStepTwice.this.u.Needle - u.haystack.iter(u.haystack.iter(n)) - ^ -``` - -This is the last part of the escape! If this worked, we could *fully -erase* the `LittleUniverse` from most code, relying on the pure -type-level index to prove enough of its existence! So it’s a little -frustrating that it doesn’t quite work. - -Let’s break it down. First, the return type is fine. - -1. Since `u: U`, `u.type <: U`. (This is true, and useful, in the - scope of `u`, which is now invisible to the caller.) -2. `iter` returns a `u.type#Needle`. - - Note: since `u` is not in scope for the caller, if we returned - this as is, it would effectively widen to the existentially - bound `u.type#Needle forSome {val u: U}`. But the same logic in - the next step would apply to that type. -3. By the `#` left side covariance, `u.type#Needle` widens to - `U#Needle`. - -Pretty simple, by the standards of what we’ve seen so far. - -## Contravariance is the root of all… - -But things break down when we try to call `iter(n)`. Keep in mind that -`n: U#Needle` and the expected type is `u.Needle`. Specifically: since -we don’t know in the implementation that `U` is a singleton type, we -can’t use the “singleton type equivalence” rule on it! But suppose -that we *could*; that is, **suppose that we could constrain `U` to be -a singleton type**. - -1. The argument type is `U#Needle`. -2. By singleton equivalence, since `u: U` and `u` is stable, so - `u.type = U`. -3. By substituting the left-hand side of the `#`, we get - `u.type#Needle`. -4. This shortens to `u.Needle`. - -If we are unable to constrain `U` in this way, though, we are -restricted to places where `U` occurs in covariant position when using -cake-extracted APIs. We can invoke functions like `init`, because -they only have the singleton index occurring in covariant position. - -Invoking functions like `iter`, where the index occurs in -contravariant or invariant position, requires being able to add this -constraint, so that we can use singleton equivalence directly on the -type variable `U`. This is quite a bit trickier. - -## Extracting more types - -We have the same problem with the function version. - -```scala -def stepTwiceHaystack[U <: LittleUniverse]( - h: U#Haystack, n: U#Needle): U#Needle = - h.iter(h.iter(n)) - -:18: error: type mismatch; - found : U#Needle - required: _1.Needle where val _1: U - h.iter(h.iter(n)) - ^ -``` - -Let’s walk through it one more time. - -1. `n: U#Needle`. -2. `h.iter` expects a `u.type#Needle` for all `val u: U`. -3. **Suppose that we constrain `U` to be a singleton type**: - 1. (The existential) `u.type = U`, by singleton equivalence. - 2. By `#` left side equivalence, `h.iter` expects a `U#Needle`. - -The existential variable complicates things, but the rule is sound. - -As a workaround, it is commonly suggested to extract the member types -in question into separate type variables. This works in some cases, -but let’s see how it goes in this one. - -```scala -def stepTwiceExUnim[N, U <: LittleUniverse{type Needle = N}]( - h: U#Haystack, n: N): N = ??? -``` - -This looks a lot weirder, but should be able to return the right type. - -```scala -scala> def trial2 = stepTwiceExUnim[lu.Needle, lu.type](lu.haystack, lu.haystack.init) -trial2: lu.Needle -``` - -But this situation is complex enough for the technique to not work. - -```scala -def stepTwiceEx[N, U <: LittleUniverse{type Needle = N}]( - h: U#Haystack, n: N): N = - h.iter(h.iter(n)) - -:18: error: type mismatch; - found : N - required: _1.Needle where val _1: U - h.iter(h.iter(n)) - ^ -``` - -Instead, we need to index `Haystack` *directly* with the `Needle` -type, that is, add a type parameter to `Haystack` so that its `Needle` -arguments can be talked about completely independently of the -`LittleUniverse`, and then to write `h: U#Haystack[N]` -above. Essentially, this means that any time a type talks about -another type in a `Universe`, you need another type parameter to -redeclare a little bit of the relationships between types in the -universe. - -The problem with this is that we already declared those relationships -by declaring the universe! All of the non-redundant information is -represented in the singleton type index. So even where the above -type-refinement technique works (and it does in many cases), it’s -*still* redeclaring things that ought to be derivable from the “mere” -fact that `U` is a singleton type. - -## The fact that it’s a singleton type - -*(The following is based on enlightening commentary by Daniel Urban on -an earlier draft.)* - -Let’s examine the underlying error in `stepTwiceEx` more directly. - -```scala -scala> def fetchIter[U <: LittleUniverse]( - h: U#Haystack): U#Needle => U#Needle = h.iter -:14: error: type mismatch; - found : _1.type(in method fetchIter)#Needle - where type _1.type(in method fetchIter) <: U with Singleton - required: _1.type(in value $anonfun)#Needle - where type _1.type(in value $anonfun) <: U with Singleton - h: U#Haystack): U#Needle => U#Needle = h.iter - ^ -``` - -It’s a good thing that this doesn’t compile. If it did, we could do - -```scala -fetchIter[LittleUniverse](lu.haystack)(anotherU.haystack.init) -``` - -Which is unsound. - -[§3.2.1 “Singleton Types”](http://www.scala-lang.org/files/archive/spec/2.12/03-types.html#singleton-types) of -the specification mentions this `Singleton`, which is in a way related -to singleton types. - -> A *stable type* is either a singleton type or a type which is -> declared to be a subtype of trait `scala.Singleton`. - -Adding `with Singleton` to the upper bound on `U` causes `fetchIter` -to compile! This is sound, because we are protected from the above -problem with the original `fetchIter`. - -```scala -def fetchIter[U <: LittleUniverse with Singleton]( - h: U#Haystack): U#Needle => U#Needle = h.iter - -scala> fetchIter[lu.type](lu.haystack) -res3: lu.Needle => lu.Needle = $$Lambda$1397/1159581520@683e7892 - -scala> fetchIter[LittleUniverse](lu.haystack) -:16: error: type arguments [LittleUniverse] do not conform - to method fetchIter's type parameter bounds - [U <: LittleUniverse with Singleton] - fetchIter[LittleUniverse](lu.haystack) - ^ -``` - -Let’s walk through the logic for `fetchIter`. The expression `h.iter` -has type `u.Needle => u.Needle` for some `val u: U`, and our goal type -is `U#Needle => U#Needle`. So we have two subgoals: prove -`u.Needle <: U#Needle` for the covariant position (after `=>`), and -`U#Needle <: u.Needle` for the contravariant position (before `=>`). - -First, covariant: - -1. Since `u: U`, `u.type <: U`. -2. Since the left side of `#` is covariant, #1 implies - `u.type#Needle <: U#Needle`. -3. This re-sugars to `u.Needle <: U#Needle`, which is the goal. - -Secondly, contravariant. We’re going to have to make a best guess -here, because it’s not entirely clear to me what’s going on. - -1. Since (existential) path `u` has a singleton type `U` (if we define - “has a singleton type” as “having a type *X* such that - *X*` <: Singleton`”), so `u.type = U` by the singleton equivalence. -2. Since equivalence implies conformance, according to the first - bullet under “Conformance”, #1 implies `U <: u.type`. -3. Since the left side of `#` is covariant, #2 implies that - `U#Needle <: u.type#Needle`. -4. This resugars to `U#Needle <: u.Needle`, which is the goal. - -I don’t quite understand this, because `U` doesn’t *seem* to meet the -requirements for “singleton type”, according to the definition of -singleton types. However, I’m *fairly* sure it’s sound, since type -stability seems to be the property that lets us avoid the -universe-mixing unsoundness. Unfortunately, it only seems to work with -*existential* `val`s; we seem to be out of luck with `val`s that the -compiler can still see. - -```scala -// works fine! -def stepTwiceSingly[U <: LittleUniverse with Singleton]( - h: U#Haystack, n: U#Needle): U#Needle = { - h.iter(h.iter(n)) -} - -// but alas, this form doesn't -class StepTwiceSingly[U <: LittleUniverse with Singleton](u: U) { - def stepTwiceSingly(n: U#Needle): U#Needle = - u.haystack.iter(u.haystack.iter(n)) -} - -:15: error: type mismatch; - found : U#Needle - required: StepTwiceSingly.this.u.Needle - u.haystack.iter(u.haystack.iter(n)) - ^ -``` - -We can work around this by having the second form invoke the first -with the `Haystack`, thus “existentializing” the universe. I imagine -that *most*, albeit not all, cakes can successfully follow this -strategy. - -So, finally, we’re almost out of the cake. - -1. Escape covariant positions with universe variable: complete. -2. Escape contravariant/invariant positions with universe variable: - complete. -3. Escape covariant positions with universe *singleton type*: - complete! -4. Escape contravariant/invariant positions with universe singleton - type: 90% there! - -*This article was tested with Scala 2.12.1.* diff --git a/src/blog/generic-numeric-programming.md b/src/blog/generic-numeric-programming.md deleted file mode 100644 index 979d97ed..00000000 --- a/src/blog/generic-numeric-programming.md +++ /dev/null @@ -1,385 +0,0 @@ -{% - author: ${tixxit} - date: "2013-07-07" - tags: [technical] -%} - -# An Intro to Generic Numeric Programming with Spire - -In this post I'd like to introduce you to what I have been calling *generic -numeric programming*. - -What is Generic Numeric Programming? ------------------------------------- - -What do we mean by generic numeric programming? Let's take a simple example; we -want to add 2 numbers together. However, we don't want to restrict ourselves to -a particular type, like `Int` or `Double`, instead we just want to work with -some *generic* type `A` that can be added. For instance: - -```scala -def add[A](x: A, y: A): A = x + y -``` - -Of course, this won't compile since `A` has no method `+`. What we are really -saying is that we want `A` to be some type that *behaves like a number*. The -usual OO way to achieve this is by creating an interface that defines our -desired behaviour. This is less than ideal, but if we were to go this route, -our `add` function might look like this: - -```scala -trait Addable[A] { self: A => - def +(that: A): A -} - -def add[A <: Addable[A]](x: A, y: A): A = x + y -``` - -We've created an interface that defines our `+` method, and then bound our type -parameter `A` to subsume this interface. The main problem with this is that we -can't directly use types out of our control, like those that come in the -standard library (ie. `Int`, `Long`, `Double`, `BigInt`, etc). The only option -would be to wrap these types, which means extra allocations and either explicit -or implicit conversions, neither of which are good options. - -A better approach is to use type classes. A discussion on type classes is out -of the scope of this post, but they let us express that the type `A` must have -some desired behaviour, without inheritence. Using the type class pattern, we -could write something like this: - -```scala -trait Addable[A] { - // Both arguments must be provided. Addable works with the type A, but - // does not extend it. - def plus(x: A, y: A): A -} - -// This class adds the + operator to any type A that is Addable, -// by delegating to that Addable's `plus` method. -implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) { - def +(rhs: A): A = ev.plus(lhs, rhs) -} - -// We use a context bound to require that A has an Addable instance. -def add[A: Addable](x: A, y: A): A = x + y -``` - -We can then easily add implementations for any numeric type, regardless if we -control it or not, or even if it is a primitive type: - -```scala -implicit object IntIsAddable extends Addable[Int] { - def plus(x: Int, y: Int): Int = x + y -} - -add(5, 4) -``` - -This is, more or less, the approach Spire takes. - -Why be Generic? ---------------- - -Why be generic? The flippant answer I could give is: why not? I do hope that -after reading this, that is an acceptable answer to you, but I know that's not -what you came here for. - -The first reason is the obvious one; sometimes you want to run the same -algorithm, but with different number types. Euclid's GCD algorithm is the same -whether you are using `Byte`, `Short`, `Int`, `Long`, or `BigInt`. Why implement -it only for 1, when you could do it for all 5? Worse; why implement it 5 -times, when you need only implement it once? - -Another reason is that you want to push certain trade-offs, such as speed vs -precision to the user of your library, rather than making the decision for -them. `Double` is fast, but has a fixed precision. `BigDecimal` is slow, but -can have much higher precision. Which one do you use? When in doubt, let -someone else figure it out! - -A last great reason is that it let's you write less tests and can make -testing much less hairy. - -### One Algorithm, Many Types - -So, what does a generic version of Euclid's GCD algorithm look like? Spire -strives to make generic numeric code look more or less like what you'd write -for a direct implementation. So, let's let you compare; first up, the direct -implementation: - -```scala -def euclidGcd(x: Int, y: Int): Int = - if (y == 0) x - else euclidGcd(y, x % y) -``` - -With Spire, we can use the `spire.math.Integral` type class to rewrite this as: - -```scala -import spire.math.Integral -import spire.implicits._ - -def euclidGcd[A: Integral](x: A, y: A): A = - if (y == 0) x - else euclidGcd(y, x % y) -``` - -The 2 methods are almost identical, save the `Integral` context bound. -`Integral` gives us many methods we expect integers to have, like addition, -multiplication, and euclidean division (quotient + remainder). - -Because Spire provides default implicit instances of `Integral` for all of the -integral types that come in the Scala standard library, we can immediately use -`euclidGcd` to find the GCD of many integer types: - -```scala -euclidGcd(42, 96) -euclidGcd(42L, 96L) -euclidGcd(BigInt(42), BigInt(96)) -``` - -This is much better than writing 5 different versions of the same algorithm! -With Spire, you can actually do away with `euclidGcd` altogether, as `gcd` -comes with `Integral` anyways: - -```scala -spire.math.gcd(BigInt(1), BigInt(2)) -``` - -### Performance vs Precision - -Another benefit of generic numeric programming, is that you can push the choice -of numeric type off to someone else. Rather than hardcode a method or data -structure using `Double`, you can simple require some `Fractional` type. - -I actually first found a need for generic numeric programming after I had -implemented a swath of algorithms with double precision floating point -arithmetic, only to find out that the minor precision errors were causing -serious correctness issues. The obvious fix was to just to use an exact type, -like `spire.math.Rational`, which would've worked for many of my purposes. -However, many of the algorithms actually worked fine with doubles or even -integers, where as others required exact n-roots (provided by a number type -like `spire.math.Real`). Being more precise meant everything got slower, even -when it didn't need to be. Being less precise meant some algorithms would -occasionally return wrong answers. Abstracting out the actual number type -used meant I didn't have to worry about these issues. I could make the choice -later, when I knew a bit more about my data, performance and precision -requirements. - -We can illustrate this using a simple algorithm to compute the mean of some -numbers. - -```scala -import spire.math._ -import spire.implicits._ - -// Note: It is generally better to use an incremental mean. -def mean[A: Fractional](xs: A*): A = xs.reduceLeft(_ + _) / xs.size -``` - -Here, we don't care what type `A` is, as long as it can be summed and divided. -If we're working with approximate measurements, perhaps finding the mean of a -list of `Double`s is good enough: - -```scala -mean(0.5, 1.5, 0.0) -// = 0.6666666666666666 -``` - -Or perhaps we'd like an exact answer back instead: - -```scala -import spire.math.Rational - -mean(Rational(1, 2), Rational(3, 2), Rational(0)) -// = Rational(2, 3) -``` - -The main thing here is that as a user of the `mean` function, I get to choose -whether I'd prefer the speed of `Double` or the precision or `Rational`. The -algorithm itself looks no different, so why not give the user the choice? - -### Better Testing - -One of the best things is that if you write test code that abstracts over the -number type, then you can re-use your tests for many different types. Spire -makes great use of this, to ensure instances of our type classes obey the rules -of algebra and that the number types in Spire (Rational, Complex, UInt, etc) -are fundamentally correct. - -There is another benefit though -- you can ignore the subtleties of floating -point arithmetic in your tests if you want! If your code works with any number -type, then you can test with an exact type such as `spire.math.Rational` or -`spire.math.Real`. No more epsilons and NaNs. You shouldn't let this excuse you -from writing numerically stable code, but it may save you many false negatives -in your build system, while also making you more confident that the fundamentals -are correct. - -This is a big topic, deserving of its own blog post (you know who you are), so -I'll leave this here. - -What Abstractions Exist? ------------------------- - -We've already seen `Integral`, which can be used wherever you need something -that acts like an integer. We also saw the modulus operator, `x % y`, but not -integer division. Spire differentiates between *integer division* and *exact -division*. You perform integer division with `x /~ y`. To see it in action, -let's use an overly complicated function to negate an integer: - -```scala -import spire.math._ -import spire.implicits._ - -def negate[A: Integral](x: A) = -(42 * (x /~ 42) + x % 42) -``` - -Instances of `Integral` exist for `Byte`, `Short`, `Int`, `Long` and `BigInt`. - -Another type class Spire provides is `Fractional[A]`, which is used for things -that have "exact" division. "Exact" is in quotes, since `Double` or -`BigDecimal` division isn't really exact, but it's close enough that we give -them a pass. `Fractional` also provides `x.sqrt` and `x nroot k` for taking the -roots of a number. - -```scala -def distance[A: Fractional](x: A, y: A): A = (x ** 2 + y ** 2).sqrt -``` - -Note that `Fractional[A] <: Integral[A]`, so anything you can do with -`Integral`, you can do with `Fractional[A]` too. Here, we can use `distance` -to calculate the length of the hypotenuse with `Double`s, `Float`s, -`BigDecimal`s, or some of Spire's number types like `Real` or `Rational`. - -Lastly, you often have cases where you just don't care if `/` means exact or -integer division, or whether you are taking the square root of an `Int` or a -`Double`. For this kind of catch-all work Spire provides `Numeric[A]`. - -Why Spire? ----------- - -If you've already hit the types of problems solved by generic numeric -programming, then you may have seen that `scala.math` also provides `Numeric`, -`Integral`, and `Fractional`, so why use Spire? Well, we originally created -Spire largely due to the problems with the type classes as they exist in Scala. - -To start, Scala's versions aren't specialized, so they only worked with boxed -versions of primitive types. The operators in Scala also required boxing, which -means you need to trade-off performance for readability. They also aren't very -useful for a lot of numeric programming; what about nroots, trig functions, -unsigned types, etc? - -Spire also provides many more useful (and specialized) type classes. Some are -ones you'd expect, like `Eq` and `Order`, while others define more basic -algebras than `Numeric` and friends, like `Ring`, `Semigroup`, `VectorSpace`, -etc. - -There are many useful number types that are missing from Scala in Spire, such -as `Rational`, `Complex`, `UInt`, etc. - -Spire was written by people who actually use it. I somewhat feel like Scala's -Numeric and friends weren't really used much after they were created, other -than for Scala's NumericRange support (ie. `1.2 to 2.4 by 0.1`). They miss -many little creature comforts whose need becomes apparent after using Scala's -type classes for a bit. - -### Spire is Fast - -One of Spire's goals is that the performance of generic code shouldn't suffer. -Ideally, the generic code should be as fast as the direct implementation. Using -the GCD implementation above as an example, we can compare Spire vs. Scala vs. -a direct implementation. I've put the -[benchmarking code up as a Gist](https://gist.github.com/tixxit/5695365). - - gcdDirect: 29.981 1.00 x gcdDirect - gcdSpire: 30.094 1.00 x gcdDirect - gcdSpireNonSpec: 36.903 1.23 x gcdDirect - gcdScala: 38.989 1.30 x gcdDirect - -For another example, we can look at the code to -[find the mean of an array](https://gist.github.com/tixxit/5695365). - - meanDirect: 10.592 **1.00 x gcdDirect** - meanSpire: 10.638 **1.00 x gcdDirect** - meanSpireNonSpec: 13.434 **1.26 x gcdDirect** - meanScala: 19.388 **1.83 x gcdDirect** - -Spire achieves these goals fairly simply. All our type classes are -`@specialized`, so when using primitives types you can avoid boxing. We then use macros to remove -the boxing normally required for the operators by the implicit conversions. - -Using `@specialized`, both `gcdSpire` and `meanSpire` aren't noticably slower -than the direct implementation. We can see the slow down caused by dropping -`@specialized` in `gcdSpireNonSpec` and `meanSpireNonSpec`. The difference -between `gcdSpireNonSpec` and `gcdScala` is because Spire doesn't allocate an -object for the `%` operator (using macros to remove the allocation). The -difference is even more pronounced between `meanSpireNonSpec` and `meanScala`. - -### More than just `Numeric`, `Integral`, and `Fractional` - -The 3 type classes highlighted in this post are just the tip of the iceberg. -Spire provides a whole slew of type classes in `spire.algebra`. This package -contains type classes representing a wide variety of algebraic structures, -such as `Monoid`, `Group`, `Ring`, `Field`, `VectorSpace`, and more. The 3 type -classes discussed above provide a good starting point, but if you use Spire in -your project, you will probably find yourself using `spire.algebra` more and -more often. If you'd like to learn more, you can [watch my talk on abstract -algebra in Scala](http://www.youtube.com/watch?v=xO9AoZNSOH4). - -As an example of using the algebra package, `spire.math.Integral` is simply -defined as: - -```scala -import spire.algebra.{ EuclideanRing, IsReal } - -trait Integral[A] extends EuclideanRing[A] - with IsReal[A] // Includes Order[A] with Signed[A]. - with ConvertableFrom[A] - with ConvertableTo[A] -``` - -Whereas `spire.math.Fractional` is just: - -```scala -import spire.algebra.{ Field, NRoot } - -trait Fractional[A] extends Integral[A] with Field[A] with NRoot[A] -``` - -### Many New Number Types - -Spire also adds many new useful number types. Here's an incomplete list: - -- `spire.math.Rational` is a fast, exact number type for working with - rational numbers, -- `spire.math.Complex[A]` is a parametric number type for complex numbers, -- `spire.math.Number` is a boxed number type that strives for flexibility - of use, -- `spire.math.Interval` is a number type for interval arithmetic, -- `spire.math.Real` is a number type for exact geometric computation that - provides exact n-roots, as well as exact division, -- `spire.math.{UByte,UShort,UInt,ULong}` unsigned integer types, and -- `spire.math.Natural` an arbitrary precision unsigned integer type. - -### Better Readability - -Spire also provides better operator integration with `Int` and `Double`. For -instance, `2 * x` or `x * 2` will just work for any `x` whose type has an -`Ring`. On the other hand, Scala requires something like -`implicitly[Numeric[A]].fromInt(2) * x` which is much less readable. This -also goes for working with fractions; `x * 0.5` will just work, if `x` has a -`Field`. - -Try It Out! ------------ - -Spire has a basic algebra that let's us work generically with numeric types. It -does this without sacrificing readability or performance. It also provides many -more useful abstractions and concrete number types. This means you can write -less code, write less tests, and worry less about concerns like performance vs. -precision. If this appeals to you, then you should try it out! - -There is some basic information on getting up-and-running with Spire in SBT on -[Spire's project page](https://github.com/non/spire). If you have any further -questions, comments, suggestions, criticism or witticisms you can say what you -want to say on the [Spire mailing list](https://groups.google.com/forum/#!forum/spire-math) -or on IRC on Freenode in `#spire-math`. diff --git a/src/blog/github-seats.md b/src/blog/github-seats.md deleted file mode 100644 index 5416ba1d..00000000 --- a/src/blog/github-seats.md +++ /dev/null @@ -1,16 +0,0 @@ -{% - author: ${valencik} - date: "2024-03-10" - tags: [technical] -%} - -# GitHub Seats - -As we continue to grow our community on GitHub, we've encountered a challenge with our seat limit for maintainers. -It's important to note that each maintainer seat incurs a fee. -By managing our maintainers more efficiently, we can allocate resources effectively and sustain the growth of our community. - -In order to be able to add new members, we've recently conducted a review and removed 25 accounts that have not been active on the organization since January 1st, 2023. - -We understand that circumstances may vary, and if you believe your account was removed in error, we want to hear from you! -Please don't hesitate to reach out to us, and we'll happily re-add you as a maintainer. diff --git a/src/blog/governing-documents.md b/src/blog/governing-documents.md deleted file mode 100644 index a4ec6c17..00000000 --- a/src/blog/governing-documents.md +++ /dev/null @@ -1,25 +0,0 @@ -{% - author: ${typelevel} - date: "2022-01-19" - tags: [governance] -%} - -# Governing Documents - -As a first step in our effort to increase transparency in the Typelevel organization, the [Steering Commitee](https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md) have approved and released an initial set of [Governing Documents](https://github.com/typelevel/governance). - -The most important document is the [Charter](https://github.com/typelevel/governance/blob/main/CHARTER.md), which specifies the role of the Steering Committee and criteria for member projects. The most notable change is that Typelevel no longer distinguishes "full" and "incubator" projects, but instead recognizes: - -- **Affiliate Projects**. These projects agree to use an approved license and adhere to organization policies, including the Code of Conduct. - -- **Organization Projects**. In addition to the requirements for Affiliate membership, these projects agree to be hosted by the Typelevel organization, and to abide by the guidance and direction of the Steering Commitee (which may specify policies for binary compatibility, documentation, contributor base, and so on). It is our hope that these changes will help clarify maintenance expectations for users of foundational libraries like Cats and Cats-Effect. - -Most projects that are now hosted by Typelevel will become Organization Projects, and most that are hosted elsewhere will become Affiliate Projects. Final designations will be reviewed with maintainers and cataloged on the Typelevel website. - -### Next Steps - -First, the Governing Documents provide high-level structure, but do not specify fine-grained policies and procedures for Organization Projects, events, moderation, and so on. These will be discussed and added to the Governance repository in the coming months, under the voting procedures specified by the Charter. Notable policy changes will be accompanied by an announcement here. - -Second, the [Typelevel website](https://typelevel.org) does not yet reflect the Governing Documents or membership policy changes. The current website will be updated minimally, until the redesigned website (in progress) becomes available sometime in the next few months. - -Finally, we recognize that the Steering Committee does not currently represent the diversity of the Typelevel community. We expect to appoint many new faces and see the retirement of many longstanding members over the next year. diff --git a/src/blog/gsoc-2023.md b/src/blog/gsoc-2023.md deleted file mode 100644 index 0a69ee58..00000000 --- a/src/blog/gsoc-2023.md +++ /dev/null @@ -1,32 +0,0 @@ -{% - author: ${armanbilge} - date: "2023-02-23" - tags: [technical] -%} - -# Typelevel Summer of Code - -We are happy to announce that Typelevel will be participating in [Google Summer of Code 2023][GSoC], under the auspice of the [Scala Center]! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. - -We are proposing the following three projects: - -- [io_uring backend for Cats Effect + FS2 JVM][io_uring] for efficient, non-blocking network I/O. This will be the flagship of the Cats Effect [I/O Integrated Runtime Concept][io-integrated]. - -- [http4s Ember WebSocket client][websocket], which opens doors to implement protocols used by Kubernetes and GraphQL while cross-building for JVM, Node.js, and Native. - -- [Pure Scala Open Telemetry implementation][telemetry] for [otel4s], an exciting new offering that promises to integrate observability into the major Typelevel libraries. - -Each of these projects is an important enhancement for the Typelevel ecosystem. Taken together, they are a major milestone towards offering a full-featured stack for developing services and deploying on high-performance runtimes available for JVM, Node.js, and Native. - -Moreover, we are excited to welcome you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. [Applications open on March 20!][apply] Until then, please feel free to reach out or join us on [Discord]; we look forward to getting to know you :) - -[Scala Center]: https://scala.epfl.ch/ -[GSoC]: https://summerofcode.withgoogle.com/ -[io_uring]: https://github.com/scalacenter/GoogleSummerOfCode#io_uring-backend-for-cats-effect--fs2-jvm -[io-integrated]: https://github.com/typelevel/cats-effect/discussions/3070 -[websocket]: https://github.com/scalacenter/GoogleSummerOfCode#http4s-ember-websocket-client -[Ember]: https://http4s.org/v0.23/docs/integrations.html#ember -[telemetry]: https://github.com/scalacenter/GoogleSummerOfCode#pure-scala-open-telemetry-implementation -[otel4s]: https://typelevel.org/otel4s/ -[apply]: https://summerofcode.withgoogle.com/get-started -[Discord]: https://discord.gg/XF3CXcMzqD diff --git a/src/blog/gsoc-2024.md b/src/blog/gsoc-2024.md deleted file mode 100644 index f5d3b88c..00000000 --- a/src/blog/gsoc-2024.md +++ /dev/null @@ -1,52 +0,0 @@ -{% - author: ${armanbilge} - date: "2024-03-02" - tags: [technical] -%} - -# Typelevel Summer of Code 2024 - -We are excited to share that Typelevel will be participating in [Google Summer of Code 2024][GSoC], thanks to the gracious support of the [Scala Center]! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. - -This year we have [several project ideas and mentors lined up][ideas] spanning AI/ML, serverless, data streaming, observability, systems programming, and more. We will continue adding ideas, so please keep checking back! Also, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.” - -We look forward to welcoming you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. [Applications open on March 18!][apply] Until then, please join us on [Discord] or email us at [gsoc@typelevel.org]; we look forward to getting to know you :) - -## 2023 recap - -We are proud of our GSoC contributors' accomplishments last year: - -* [Hieu] implemented a pure FP Scala [WebSocket client] in [http4s Ember] that supports JVM, Node.js, and Scala Native. His work is being adopted across the ecosystem, such as by the [Kubernetes client]. -* [Sherrie] carefully reviewed [otel4s] and [designed new APIs] that better follow the [Open Telemetry] specification. Her enhancements shipped as part of the [v0.3.0 release]. -* [Antonio] demonstrated the awesome potential of [io_uring] when [http4s Ember] performed [more than 3x faster in benchmarks] thanks to his [I/O integrated runtime]. He is returning this year as a mentor on multiple follow-up projects. - -To learn more about their projects, check out the [lightning talks] they gave at the [2023 Northeast Scala Symposium]. Their success would not have been possible without the support and mentorship of our community, especially Diego Alonso, Ross Baker, Chris Davenport, Brian Holt, Maksym Ochenashko, and Daniel Spiewak. Thank you for your enthusiasm and generosity; it made an impression on our students, and it made an impression on me :) - -Lastly, we were surprised by the breadth of the response we got to our announcement last year: there are many people eager to contribute. Even if you are not eligible to participate in GSoC, you are *always* welcome to join the Typelevel community and contribute to our projects! - -[Scala Center]: https://scala.epfl.ch/ -[GSoC]: https://summerofcode.withgoogle.com/ -[ideas]: /gsoc/ideas.md - -[apply]: https://summerofcode.withgoogle.com/get-started -[Discord]: https://discord.gg/hAKabfGjUw -[gsoc@typelevel.org]: mailto:gsoc@typelevel.org - -[Hieu]: https://github.com/danghieutrung -[websocket client]: https://github.com/http4s/http4s/pull/7261 -[Kubernetes client]: https://github.com/joan38/kubernetes-client/pull/239 - -[Sherrie]: https://github.com/sherriesyt -[otel4s]: https://github.com/typelevel/otel4s -[Open Telemetry]: https://opentelemetry.io/ -[designed new APIs]: https://github.com/typelevel/otel4s/pull/236 -[v0.3.0 release]: https://github.com/typelevel/otel4s/releases/tag/v0.3.0 - -[Antonio]: https://github.com/antoniojimeneznieto -[io_uring]: https://en.wikipedia.org/wiki/Io_uring -[http4s Ember]: https://http4s.org/v0.23/docs/integrations.html#ember -[more than 3x faster in benchmarks]: https://github.com/typelevel/cats-effect/issues/3692#issuecomment-1697974751 -[I/O integrated runtime]: https://github.com/armanbilge/fs2-io_uring/pull/78 - -[lightning talks]: https://youtu.be/3HAStrljVwY -[2023 Northeast Scala Symposium]: https://nescalas.github.io/ diff --git a/src/blog/gsoc-2025.md b/src/blog/gsoc-2025.md deleted file mode 100644 index ef31735f..00000000 --- a/src/blog/gsoc-2025.md +++ /dev/null @@ -1,45 +0,0 @@ -{% - author: ${armanbilge} - date: "2025-02-27" - tags: [technical] -%} - -# Typelevel Summer of Code 2025 - -We are proud to announce that Typelevel is a Mentoring Organization for [Google Summer of Code 2025][GSoC]! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. You can learn more about what the experience is like in [this blog post][feral-blog] by our 2024 contributor Ching Hian Chew. - -Please check out [our project ideas and mentors][ideas] spanning AI/ML, serverless, build tooling, frontend/UIs, systems programming, web assembly, and more. Furthermore, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.” - -We look forward to welcoming you to the Typelevel community and we hope that this program will be only the beginning of your open source journey. [Applications open on March 24!][apply] Until then, please join us on [Discord] or email us at [gsoc@typelevel.org]; we are excited to get to know you :) - -## 2024 recap - -Congratulations to our GSoC contributors last year: - -* [Ching] expanded Feral’s serverless integrations, in particular adding support for Google Cloud Run HTTP Functions. Her work landed in the [v0.3.1][feral-0.3.1] release. [Read her blog post!][feral-blog] -* [Gaby] designed and implemented [Catscript], a new library for beginner-friendly scripting with Cats Effect, complete with tutorials and documentation. It will become an essential part of the [Typelevel Toolkit]. -* [Abhi] integrated [Protosearch with Scaladoc][protosearch], enabling a unified search experience across written and API documentation. It will roll out to all Typelevel libraries in a forthcoming sbt-typelevel release. - -Our program culminated in a [virtual meetup][meetup] where each of them gave a lightning talk about their project. Thank you to everyone in our community who showed up for our contributors, especially our mentors Tonio Gela, Thanh Le, Michael Pilquist, Rishad Sewnarain, and Andrew Valencik! - -Finally, we are making a broad call for any and all new contributors. Even if you are not eligible to participate in GSoC, you are *always* welcome to join the Typelevel community and contribute to our projects! - -[GSoC]: https://summerofcode.withgoogle.com/ -[feral-blog]: gsoc24-going-feral-on-the-cloud.md -[ideas]: /gsoc/ideas.md - -[apply]: https://summerofcode.withgoogle.com/get-started -[Discord]: https://discord.gg/hAKabfGjUw -[gsoc@typelevel.org]: mailto:gsoc@typelevel.org - -[Ching]: https://github.com/Chingles2404 -[feral-0.3.1]: https://github.com/typelevel/feral/releases/tag/v0.3.1 - -[Gaby]: https://github.com/Hombre-x -[Catscript]: https://github.com/typelevel/governance/issues/149 -[Typelevel Toolkit]: https://typelevel.org/toolkit/ - -[Abhi]: https://github.com/golden-skipper03 -[protosearch]: https://github.com/cozydev-pink/protosearch/pull/241 - -[meetup]: https://github.com/orgs/typelevel/discussions/151 diff --git a/src/blog/gsoc-2026.md b/src/blog/gsoc-2026.md deleted file mode 100644 index 6f70de2d..00000000 --- a/src/blog/gsoc-2026.md +++ /dev/null @@ -1,46 +0,0 @@ -{% - author: [${armanbilge}, ${antoniojimeneznieto}, ${valencik}] - date: "2026-03-02" - tags: [technical] -%} - -# Typelevel Summer of Code 2026 - -@:style(bulma-notification bulma-is-danger bulma-is-light) - Due to overwhelming participation in GSoC 2026, we are only able to consider proposals from applicants who **complete our [onboarding process][onboarding] by Monday, March 16th**. - If you missed this deadline, we appreciate your interest and hope you will apply next year! -@:@ - -We are pleased to announce that Typelevel is a Mentoring Organization for [Google Summer of Code 2026][GSoC]! If you are a student, this is a wonderful opportunity to spend your summer working on Scala open source projects with mentorship from Typelevel maintainers, while earning a stipend. You can learn more about what the experience is like in [this blog post][feral-blog] by our 2024 contributor Ching Hian Chew. - -Please check out [our project ideas and mentors][ideas] spanning serverless, build tooling, frontend/UIs, systems programming, web assembly, and more. Furthermore, if you have an idea of your own, we would love to hear it: Typelevel members have a wide range of interests and a special fondness for building “at the cutting edge of some incredibly bizarre stuff.” - -We look forward to welcoming you to the Typelevel community and we hope that participating in this program will be the beginning of your open source journey. [Applications open on March 16!][apply] Until then, [get started] by [watching our intro presentation][intro] and [making your first contribution][onboarding]. - -[GSoC]: https://summerofcode.withgoogle.com/ -[feral-blog]: gsoc24-going-feral-on-the-cloud.md -[ideas]: /gsoc/ideas.md -[apply]: https://summerofcode.withgoogle.com/get-started -[get started]: /gsoc/README.md#getting-started -[intro]: https://www.youtube.com/watch?v=oOk9-HQZhRk -[onboarding]: https://github.com/typelevel/gsoc-onboarding - -## 2025 recap - -Congratulations to our GSoC contributors last year: - -* [Rahul] upgraded several FS2 APIs to use non-blocking, polling-based I/O, including datagram sockets and native processes on both the JVM and native platforms. This improves their performance and semantics. -* [Shrey] worked on an ambitious project to implement a machine learning inference runtime with Cats Effect on Scala Native. This makes it possible to serve ML models alongside web services without compromising latency. - -They were joined by a contributor outside of the official GSoC program: - -* [Jay] developed a new API for log4cats based on [a proposal by Olivier Mélois][log4cats proposal]. It encodes the interface as a single abstract method to enhance usability and extensibility while maintaining compatibility with the original API. - -Our summer culminated in a virtual meetup where each of them gave a lightning talk about their project. Thank you to everyone in our community who showed up for our contributors, especially our mentors Arman Bilge, Antonio Jimenez, Morgen Peschke, Michael Pilquist, and Andrew Valencik. - -Finally, we are making a broad call for any and all new contributors. Even if you are not eligible to participate in GSoC, you are *always* welcome to join the Typelevel community and contribute to our projects! - -[Rahul]: https://gist.github.com/rahulrangers/afd8f99c365305843c6c40a0f8b15e92 -[Shrey]: https://gist.github.com/pantShrey/70e289b9025e49d65289b1195bad5083 -[Jay]: https://github.com/typelevel/log4cats/pull/920 -[log4cats proposal]: https://github.com/typelevel/log4cats/discussions/815 diff --git a/src/blog/gsoc24-going-feral-on-the-cloud.md b/src/blog/gsoc24-going-feral-on-the-cloud.md deleted file mode 100644 index a818c5f7..00000000 --- a/src/blog/gsoc24-going-feral-on-the-cloud.md +++ /dev/null @@ -1,34 +0,0 @@ -{% - author: ${chingles} - date: "2024-12-22" - tags: [technical] -%} - -# Google Summer of Code 2024 - Going Feral on The Cloud - -This project was proposed by the Typelevel community in collaboration with the Scala Center, and carried out under Google Summer of Code (GSoC) 2024. Feral is a library in the Typelevel ecosystem that provides a framework for Scala developers to write and deploy serverless functions. As Feral was only supporting AWS Lambda, the goal of the project was to extend Feral to support other serverless providers, specifically Vercel and Google Cloud. - -The vision for Feral is to enable Scala developers to easily switch between one cloud provider to another that better suits their needs, without the need for major refactoring of their codebases. Such convenience would give developers greater freedom as they do not need to be tied down to one platform. Furthermore, as Feral is part of the Typelevel ecosystem, developers who are currently using the Http4s Typelevel library for non-serverless web services may also effortlessly make the switch to serverless. - -With these goals in mind, it is imperative to provide robust support for the common serverless providers, which is what the project aimed to work towards. - -## What I Did - -| Pull Request (PR) | Status | Comments | -| --- | --- | --- | -| [Add `ApiGatewayV2WebSocketEvent`](https://github.com/typelevel/feral/pull/476) | Merged | The addition of this AWS Lambda event provided an introduction to serverless functions for me, while enhancing the support that Feral has for AWS Lambda. | -| [Created support for Vercel using Node.js runtime](https://github.com/typelevel/feral/pull/492) | Not Merged | Vercel's implementation of routes conflicts with the way Http4s handles routes. Due to this incompatibility between Feral and Vercel, this PR was not merged. Through working on this, there is a better understanding of how Vercel and Http4s work, which could pave the way for future work. | -| [Created support for Google Cloud HTTP functions](https://github.com/typelevel/feral/pull/498) | Merged | This PR enabled support for both JVM and Node.js runtimes. There is a minor error logged in the JVM runtime implementation that we minimized to a bug unrelated to Feral or any of the Typelevel libraries. The error does not seem to impact the functionality of the resulting web application, but it would be good to further investigate the cause of it. | - -## Challenges and Lessons Learnt -It was challenging to support various serverless platforms, particularly Vercel and Google Cloud, which I created the initial implementation for. While I learnt along the way that the general procedure for supporting each platform was generally the same, where one would have to do things such as converting between types and using a dispatcher, there were still differences in certain details. For example, while referencing the pre-existing Feral implementation to support AWS Lambda event functions in order to support Google Cloud HTTP functions for the JVM runtime, I learnt that HTTP functions are a subset of AWS Lambda event functions, while they were separate in Google Cloud. - -My lack of familiarity with Scala and functional programming also posed a hindrance. While I had previously done some functional programming with Scala prior to GSoC, it was still something rather new to me. As such, it took me a longer time to write code and debug than the time I would probably have taken if I was more familiar. However, this GSoC project gave me the opportunity to improve myself in these areas. I became more familiar with previously-learnt concepts such as monads, and learnt new things such as for-yield statements and how they can be desugared. - -Through GSoC, I have learnt many new things while reinforcing what I already know. I am grateful that the Typelevel organization held a session that taught the concept of programs-as-values as it was something that I never knew existed. I also learnt to appreciate what I have learnt in school better, by experiencing first-hand how I can apply such knowledge in real-world situations. For example, this project utilized the concept of resource allocation which was something I had previously learnt about. - -## Future Work -I plan to continue contributing to enhancing Feral after GSoC 2024 ends. Some ways I could do this is to create support for Google Cloud Event functions and create SBT plug-ins to test Google Cloud functions locally as well as deploy the functions. If possible, investigation could also be done on how, if possible, Vercel can be integrated into Feral without impeding developers from using Http4s with it. - -## Acknowledgements -I would first like to thank my mentors, [Arman](https://github.com/armanbilge) and [Antonio](https://github.com/toniogela) for their constant guidance during GSoC. In particular, I would like to thank Arman for taking the time to set up weekly pair programming sessions, which has enhanced my learning experience greatly. I would also like to thank the Scala Center and the Typelevel community for proposing and supporting this project. Lastly, I would like to thank Google for hosting GSoC 2024 and providing me with the opportunity to learn about open source projects. diff --git a/src/blog/hackday-2016-06-11.md b/src/blog/hackday-2016-06-11.md deleted file mode 100644 index ea289779..00000000 --- a/src/blog/hackday-2016-06-11.md +++ /dev/null @@ -1,36 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-06-11" - event-date: "June 11, 2016" - event-location: "Salesforce Tower, Bishopsgate, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the second Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -At the [May Typelevel hack day](http://www.meetup.com/london-scala/events/230514810/) we had people working on the Scala compiler (we submitted a pull request against 2.12.x which as been accepted and merged!), people exploring Cats and shapeless, people working on Ensime and FreeSlick and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise. - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](http://www.meetup.com/london-scala/events/231404561/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](http://www.meetup.com/london-scala/events/231404561/)@:@ -@:@ - -## Venue - -This event will take place at the Salesforce Tower in London. - - diff --git a/src/blog/hackday-2016-07-16.md b/src/blog/hackday-2016-07-16.md deleted file mode 100644 index cfd9231a..00000000 --- a/src/blog/hackday-2016-07-16.md +++ /dev/null @@ -1,36 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-07-16" - event-date: "July 16, 2016" - event-location: "Hydrogen Group, Eastcheap, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the third Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -At the [May Typelevel hack day](http://www.meetup.com/london-scala/events/230514810/) we had people working on the Scala compiler (we submitted a pull request against 2.12.x which as been accepted and merged!), people exploring Cats and shapeless, people working on Ensime and FreeSlick and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise. - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](http://www.meetup.com/london-scala/events/232075459/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](http://www.meetup.com/london-scala/events/232075459/)@:@ -@:@ - -## Venue - -Note the changed location: This event will take place at Hydrogen Group, 30 Eastcheap, London. - - diff --git a/src/blog/hackday-2016-08-13.md b/src/blog/hackday-2016-08-13.md deleted file mode 100644 index d536b621..00000000 --- a/src/blog/hackday-2016-08-13.md +++ /dev/null @@ -1,36 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-08-13" - event-date: "August 13, 2016" - event-location: "Hydrogen Group, Eastcheap, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the fourth Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -At the [July Typelevel hack day](http://www.meetup.com/london-scala/events/232075459/) we had people working on the Scala compiler, a Monocle workshop, people working on Ensime and people working through introductory Scala problems and exercises ... something for everyone whatever their level of expertise. - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](http://www.meetup.com/london-scala/events/232890867/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](http://www.meetup.com/london-scala/events/232890867/)@:@ -@:@ - -## Venue - -Note the changed location: This event will take place at Hydrogen Group, 30 Eastcheap, London. - - diff --git a/src/blog/hackday-2016-09-17.md b/src/blog/hackday-2016-09-17.md deleted file mode 100644 index 05483b5e..00000000 --- a/src/blog/hackday-2016-09-17.md +++ /dev/null @@ -1,36 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-09-17" - event-date: "September 17, 2016" - event-location: "Salesforce Tower, Bishopsgate, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the fifth Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -At the [August Typelevel hack day](http://www.meetup.com/london-scala/events/232890867/) we had people working on the Scala compiler, SBT and Ensime, and experimenting with using Scala.Meta to preprocess Scala source code! - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](http://www.meetup.com/london-scala/events/233994868/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](http://www.meetup.com/london-scala/events/233994868/)@:@ -@:@ - -## Venue - -Note the changed location: This event will again take place at the Salesforce Tower. - - diff --git a/src/blog/hackday-2016-10-15.md b/src/blog/hackday-2016-10-15.md deleted file mode 100644 index 01080158..00000000 --- a/src/blog/hackday-2016-10-15.md +++ /dev/null @@ -1,36 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-10-15" - event-date: "October 15, 2016" - event-location: "Salesforce Tower, Bishopsgate, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the sixth Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -At the [August Typelevel hack day](http://www.meetup.com/london-scala/events/232890867/) we had people working on the Scala compiler, SBT and Ensime, and experimenting with using Scala.Meta to preprocess Scala source code! - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](https://www.meetup.com/london-scala/events/234417089/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](http://www.meetup.com/london-scala/events/234417089/)@:@ -@:@ - -## Venue - -This event will take place at the Salesforce Tower. - - diff --git a/src/blog/hackday-2016-11-12.md b/src/blog/hackday-2016-11-12.md deleted file mode 100644 index 985929d7..00000000 --- a/src/blog/hackday-2016-11-12.md +++ /dev/null @@ -1,34 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-11-12" - event-date: "November 12, 2016" - event-location: "Salesforce Tower, Bishopsgate, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the seventh Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](https://www.meetup.com/london-scala/events/235200788/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](https://www.meetup.com/london-scala/events/235200788/)@:@ -@:@ - -## Venue - -This event will take place at the Salesforce Tower. - - diff --git a/src/blog/hackday-2017-01-21.md b/src/blog/hackday-2017-01-21.md deleted file mode 100644 index 2976a71b..00000000 --- a/src/blog/hackday-2017-01-21.md +++ /dev/null @@ -1,34 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2017-01-21" - event-date: "January 21, 2017" - event-location: "Salesforce Tower, Bishopsgate, London" - tags: [events] -%} - -# Hack the Tower – Typelevel & Scala hack day - -![Hack the Tower – Typelevel & Scala hack day](/img/media/hackday.jpg) - -## About the Hackday - -This is the ninth Typelevel hack day in conjunction with the [London Scala User Group](http://www.meetup.com/london-scala/) and [Hack The Tower](http://hackthetower.co.uk/). - -Join users of and contributors to [Typelevel projects](/projects/README.md) and [Typelevel Scala](https://github.com/typelevel/scala) and learn how to put them to good use in your own projects and how to make them better for the whole Scala community! - -Please come and join us, and if there's anything you'd like to chat about beforehand head over to the [Gitter channel](https://gitter.im/typelevel/hack-the-tower). - -## Schedule - -More details, including the schedule, can be found on the [Meetup page](https://www.meetup.com/london-scala/events/236634381/). -It is important that you sign up there. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Sign up](https://www.meetup.com/london-scala/events/236634381/)@:@ -@:@ - -## Venue - -This event will take place at the Salesforce Tower. - - diff --git a/src/blog/heaps.md b/src/blog/heaps.md deleted file mode 100644 index 3aed36e1..00000000 --- a/src/blog/heaps.md +++ /dev/null @@ -1,339 +0,0 @@ -{% - author: ${chrisokasaki} - date: "2016-11-17" - tags: [technical] -%} - -# API Design for Heaps (aka Priority Queues) - -_This is a guest post by Chris Okasaki. It was initially published as the [design document](https://github.com/chrisokasaki/scads/blob/e78233ac6a787b7c66b44cd6139392418b214eb9/design/heaps.md) behind [scads](https://github.com/chrisokasaki/scads). It is being republished here with the permission of the original author._ - -A heap (or priority queue) is a collection of elements ordered by some `Ordering`, optimized for retrieving the first element according to that ordering. -Duplicate elements are allowed. -Applications vary in whether they need the first element to be the smallest or the biggest element according to the ordering, so both variations should be easy to use. -(However, any given heap is expected to offer easy access to either the smallest element only or the biggest element only, not both at the same time.) -I will consider immutable heaps in this document, but the core issues discussed below apply to both immutable heaps and mutable heaps. - -Even if an element type has a natural ordering, that ordering may not be the one we want to use, so we must allow the user to specify the ordering. - -## Problem 1: Don't mix two different orderings in the same heap - -Here is a strawman design for a very simple heap API: - -```scala -trait Heap[Elem] { // WARNING: THIS IS BROKEN!!! - def isEmpty(implicit ord: Ordering[Elem]): Boolean - def add(elem: Elem)(implicit ord: Ordering[Elem]): Heap[Elem] - def first(implicit ord: Ordering[Elem]): Elem - def rest(implicit ord: Ordering[Elem]): Heap[Elem] -} - -// factory method, probably in some companion object -def empty[Elem](implicit ord: Ordering[Elem]): Heap[Elem] = ??? -``` - -You may think it strange that all of these methods are taking an `ord` parameter. -From a Scala point of view, that doesn't make much sense. -But you can find variations of this design in many implementations of heaps on GitHub, including in the well-respected Scalaz library. -Why? As far as I can tell, the answer is _because Haskell does it that way_. -Here's the equivalent design in Haskell: - -```haskell -type Heap a -empty :: Ord a => Heap a -isEmpty :: Ord a => Heap a -> Bool -add :: Ord a => a -> Heap a -> Heap a -first :: Ord a => Heap a -> a -rest :: Ord a => Heap a -> Heap a -``` - -In Haskell, this makes perfect sense. -Behind the scenes, each method takes an `Ord` dictionary as a hidden parameter. -But there's one critical difference between Haskell and Scala: in Haskell, there can only be a _single_ ordering for an element type, but in Scala, there can be _many_ orderings for the same element type. - -For example, consider this Scala code: - -```scala -val ord1 = Ordering.Int -val ord2 = ord1.reverse -val heap1 = empty[Int](ord1).add(5)(ord1).add(7)(ord1) -val heap2 = heap1.add(4)(ord2) -println(heap2.first) -``` - -What should this print? -Of course, that depends on the details of the implementation, but you would expect it to print either 4 (the smallest element) or 7 (the biggest element). -However, because one ordering was used for two of the `add`s and the opposite ordering was used for the third `add`, there's an excellent chance that the actual result will be 5, which is the wrong answer for both orderings. - -The magic of implicit parameters is that you usually don't need to pass them explicitly. -But (A) there's nothing to stop you from doing so, and (B) there's nothing to prevent you from calling methods in different scopes with different orderings. -No, if you're anything like me, the possibility that this could happen by accident is making your skin crawl. -Surely, the API should prevent this from happening! - -Fortunately, this problem is very easy to fix. -_Only the `empty` method should take an ordering._ -Once that initial heap has been created, all future heaps derived from that heap via any sequence of `add`s or `rest`s should use the same ordering. -With this change, the API becomes: - -```scala -trait Heap[Elem] { - def isEmpty: Boolean - def add(elem: Elem): Heap[Elem] - def first: Elem - def rest: Heap[Elem] -} - -// factory method, probably in some companion object -def empty[Elem](implicit ord: Ordering[Elem]): Heap[Elem] = ??? -``` - -Yay! That is both simpler and safer. - -## Problem 2: `merge` - -Another operation supported by many kinds of heaps is `merge`, which combines two heaps into a single heap. -Examples of heaps supporting merge include leftist heaps, skew heaps, binomial heaps (aka binomial queues), Fibonacci heaps, etc. - -We can easily add `merge` to the existing `Heap[Elem]` trait. - -```scala -trait Heap[Elem] { - ... - def merge(other: Heap[Elem]): Heap[Elem] -} -``` - -However, there are at last two problems with this. -First, traits allow for subclassing, so we might have several different implementations, such as leftist heaps and binomial heaps. -But we only want to merge leftist heaps with leftist heaps and binomial heaps with binomial heaps—we do _not_ want to merge leftist heaps with binomial heaps. - -There are several ways to address this problem. -For example, leftist heaps and binomial heaps could just use completely separate trait hierarchies, and each could use a `sealed trait` to prevent this unwanted mixing of types. - -But the code duplication this would entail is unsatisfying. -It would also make it more difficult to share code (such as a testing harness) between different implementations. - -Alternatively, we can control the types more precisely by adding a second type parameter for the specific representation being used, as in: - -```scala -trait MHeap[Elem, Heap] { - // MHeap is "Mergeable Heap" - // Heap is the specific Heap representation being used - def isEmpty: Boolean - def add(elem: Elem): Heap - def first: Elem - def rest: Heap - def merge(other: Heap): Heap -} - -sealed trait LeftistHeap[Elem] extends MHeap[Elem, LeftistHeap[Elem]] - -sealed trait BinomialHeap[Elem] extends MHeap[Elem, BinomialHeap[Elem]] -``` - -Because of the extra type parameter, a leftist heap and binomial heap are incompatible and cannot be merged. - -## Problem 3: `merge` (continued) - -There's a second problem with `merge`. A particular implementation, such as leftist heaps, would provide a factory method for creating a new heap. - -```scala -object LeftistHeap { // companion object - def empty[Elem](implicit ord: Ordering[Elem]): LeftistHeap[Elem] = ??? -} -``` - -Because of the `MHeap` definition, we can't `merge` a leftist heap with a binomial heap. -But now we've re-introduced the problem of incompatible orderings! - -```scala -val ord1 = Ordering.Int -val ord2 = ord1.reverse -val heap1 = empty[Int](ord1).add(5).add(7) -val heap2 = empty[Int](ord2).add(6).add(4) -var heap3 = heap1.merge(heap2) -while (!heap3.isEmpty) { - println(heap3.first) - heap3 = heap3.rest -} -``` - -Notice that `heap1` and `heap2` were created with opposite orderings. -What happens if we merge them? Nothing good! -The exact results depend on details of the implementation, but a likely result is that loop will print the elements in the order 5,6,4,7—or maybe 5,7,6,4—when it _should_ print them in sorted order! - -We would really like to make this sort of situation impossible! -Maybe we could test the orderings for object equality at runtime, and throw an exception if they're different? -That could actually work for simple types like integers with a built-in ordering object. -But for more complicated types, such as tuples, the orderings are generated on demand from the orderings of their constituent parts. -And this generation is not memoized, so if we demand an ordering for, say, `(Int,String)` twice, we'll get two separate ordering objects, which will cause a false negative for our hypothetical dynamic equality check. - -No, we would really like to make merging two heaps with different orderings a type error. -We can achieve this by making the notion of a factory explicit. -The idea is that heaps can only be merged with other heaps from the same factory. -Attempting to merge heaps from different factories will cause a type error. - -In code, we might express this as follows: - -```scala -trait HeapFactory { - type Elem - type Heap <: MHeap[Elem,Heap] - - def empty: Heap - // plus other factory methods -} - -object LeftistHeap { // companion object - def factory[E](implicit ord: Ordering[E]): HeapFactory { type Elem = E } = ??? -} -``` - -Now we can say: - -```scala -val minHeaps = LeftistHeap.factory[Int](Ordering.Int) -val maxHeaps = LeftistHeap.factory[Int](Ordering.Int.reverse) - -val heap1 = minHeaps.empty.add(5).add(7) -val heap2 = minHeaps.empty.add(6).add(4) -val heap3 = heap1.merge(heap2) // this typechecks - -val heap4 = maxHeaps.empty.add(6).add(4) -val heap5 = heap1.merge(heap4) // !!!type error!!! -``` - -Notice that `heap1`, `heap2`, and `heap3` have type `minHeaps.Heap` but `heap4` has type `maxHeaps.Heap`. -According to Scala's notion of _path-dependent types_, these types are incompatible so attempting to merge `heap1` and `heap4` causes a type error, as desired. - -## A question - -Clearly, if I create two factories with incompatible element types, then a heap from one factory should not be mergeable with a heap from the other factory. -Similarly, if I create two factories with the same element type but incompatible orderings, then again a heap from one factory should not be mergeable with a heap from the other factory. - -But what if I create two separate factories with the same element type and the same ordering? -Should a heap created from one of these factories be mergeable with a heap created from the other factory? -It's not clear. -If this duplication of factories was deliberate, then the answer is probably “no”. -This often happens with units of measure. -For example, maybe one of the factories is using integers to represent inches and the other is using integers to represent grams. -Even if the factories are using the same ordering, we probably don't want to merge a heap of inches with a heap of grams! - -On the other hand, the duplication of factories could be accidental, perhaps the result of two chunks of code being written separately and then brought together later. -In that case, we might very well want to be able to merge a heap from one factory with a heap from another accidentally-separate-but-equivalent factory. - -Regardless of where you come down on what _should_ happen, what _will_ happen in the above design is that attempting to merge heaps from distinct factories will cause a type error, even if the factories were made for the same element type and ordering. - -## Problem 4: Usability in the simple case - -Most applications of priority queues do not need the `merge` method. -Trying to make `merge` typesafe has made the API more complicated and harder to use because of the need to instantiate a factory before creating actual heaps. -Can we hide these complications from a user until and unless they actually need to use `merge`? Yes. - -I'll re-introduce the interface without `merge`, but now called `SHeap` for “Simple Heap”: - -```scala -trait SHeap[Elem] { - def isEmpty: Boolean - def add(elem: Elem): SHeap[Elem] - def first: Elem - def rest: SHeap[Elem] -} -``` - -Then `MHeap` should be a subtype of `SHeap`: - -```scala -trait MHeap[Elem, Heap <: SHeap[Elem]] extends SHeap[Elem] { - // inherits isEmpty and first from SHeap[Elem] - def add(elem: Elem): Heap // more specific return type - def rest: Heap // more specific return type - def merge(other: Heap): Heap -} -``` - -The `HeapFactory` definition is unchanged: - -```scala -trait HeapFactory { - type Elem - type Heap <: MHeap[Elem,Heap] - - def empty: Heap - // plus other factory methods -} -``` - -The last part is that the companion object should supply simple factory methods in terms of `SHeap`: - -```scala -object LeftistHeap { // companion object - def empty[E](implicit ord: Ordering[E]): SHeap[E] = ??? - // plus other ordinary factory methods, similar to other Scala collections - - // the big bad - def factory[E](implicit ord: Ordering[E]): HeapFactory { type Elem = E } = ??? -} -``` - -Now the user can proceed in blissful ignorance of `factory` or `MHeap`, treating this essentially just like any other Scala collection, until they need `merge`. -Of course, `empty` will probably be defined as - -```scala - def empty[E](implicit ord: Ordering[E]): SHeap[E] = factory[E](ord).empty -``` - -(and similarly for the other ordinary factory methods), but the user doesn't need to know that. - -## Problem 5: `min` vs `max` - -Should a heap favor smaller elements or bigger elements? -There's no obvious answer—applications abound for both. -Therefore, an interface should easily support both flavors. -Right now, the ordering parameter allows us to say - -```scala -val minHeaps = LeftistHeap.factory[Int](Ordering.Int) -val maxHeaps = LeftistHeap.factory[Int](Ordering.Int.reverse) -``` - -But how did I know that `Ordering.Int` was the right ordering for min-heaps and `Ordering.Int.reverse` was the right ordering for max-heaps? -The opposite could just as easily have been true. -Sure, this detail would probably be documented in the API, but it was fundamentally a flip-a-coin arbitrary decision. -And arbitrary decisions with no logic favoring one choice over the other are the hardest to remember. - -In an easier-to-use interface, the user might write: - -```scala -val minHeaps = LeftistHeap.minFactory[Int](Ordering.Int) -val maxHeaps = LeftistHeap.maxFactory[Int](Ordering.Int) -``` - -Now, the user doesn't need to worry whether to use `Ordering.Int` or `Ordering.Int.reverse`. -Instead, if they want min-oriented heaps, they call `minFactory(Ordering.Int)` and if they want max-oriented heaps, they call `maxFactory(Ordering.Int)`. -In fact, it's even better than that. -The whole point of implicit parameters is that you usually don't need to write them down explicitly. -In reality, the user would probably only write: - -```scala -val minHeaps = LeftistHeap.minFactory[Int] -val maxHeaps = LeftistHeap.maxFactory[Int] -``` - -Actually, in the current version of scads, this is now - -```scala -val minHeaps = LeftistHeap.Min.factory[Int] -val maxHeaps = LeftistHeap.Max.factory[Int] -``` - -where `LeftistHeap.Min` and `LeftistHeap.Max` both support other simpler methods for creating `SHeap`s for users who don't need `merge`. -For example, a user could write: - -```scala -val h1 = LeftistHeap.Min.empty[Int] // an empty min-heap of integers -val h2 = LeftistHeap.Max(1,2,3) // a max-heap containing 1, 2, and 3 -``` - -Of course, there's lots more needed to flesh the whole design out into an industrial-strength API, and even more to integrate it with the current Scala collections. -I'll continue to work on this, and I welcome discussion on these issues. diff --git a/src/blog/higher-leibniz.md b/src/blog/higher-leibniz.md deleted file mode 100644 index ac979b0a..00000000 --- a/src/blog/higher-leibniz.md +++ /dev/null @@ -1,361 +0,0 @@ -{% - author: ${S11001001} - date: "2014-09-20" - tags: [technical] -%} - -# Higher Leibniz - -We’ve previously seen -[the basic implementation and motivation for `scalaz.Leibniz`](type-equality-to-leibniz.md). -But there’s still quite a bit more to this traditionally esoteric -member of the Scalaz collection of well-typed stuff. - -Strictly necessarily strict ---------------------------- - -The word “witness” implies that `Leibniz` is a passive bystander in -your function; sitting back and telling you that some type is equal to -another type, otherwise content to let the *real* code do the real -work. The fact that `Leibniz` lifts into functions (which are a -member of the *everything* set, you’ll agree) might reinforce the -notion that `Leibniz` is spooky action at a distance. - -But one of the nice things about `Leibniz` is that there’s really no -cheating: the value with its shiny new type is dependent on the -`Leibniz` actually existing, and its `subst`, however much a glorified -identity function it might be, completing successfully. - -To see this in action, let’s check in with the bastion of not -evaluating stuff, Haskell. - -The Haskell implementation --------------------------- - -```haskell -{-# LANGUAGE RankNTypes, PolyKinds #-} -module Leib - ( Leib() - , subst - , lift - , symm - , compose - ) where - -import Data.Functor - -data Leib a b = Leib { - subst :: forall f. f a -> f b -} - -refl :: Leib a a -refl = Leib id - -lift :: Leib a b -> Leib (f a) (f b) -lift ab = runOn . subst ab . On $ refl - -newtype On c f a b = On { - runOn :: c (f a) (f b) -} - -symm :: Leib a b -> Leib b a -symm ab = runDual . subst ab . Dual $ refl - -newtype Dual c a b = Dual { - runDual :: c b a -} - -compose :: Leib b c -> Leib a b -> Leib a c -compose = subst -``` - -We use [newtypes](http://www.haskell.org/haskellwiki/Newtype) in place -of type lambdas, and a value instead of a method, but the -implementation is otherwise identical. - -It’s really there ------------------ - -OK. Let’s try to make a fake `Leib`. - -```haskell -badForce :: Leib a b -badForce = Leib $ \_ -> error "sorry for fibbing" -``` - -The following code will signal an error only if forcing the *head -cons* of the `subst`ed list signals such an error. We never give -Haskell the chance to force anything else. - -```haskell -λ> subst (badForce :: Leib Int String) [42] `seq` 33 -*** Exception: sorry for fibbing -``` - -Oh well, let’s try to bury it behind combinators. - -```haskell -λ> subst (symm . symm $ badForce :: Leib Int String) [42] `seq` 33 -*** Exception: sorry for fibbing -λ> subst (compose refl $ badForce :: Leib Int String) [42] `seq` 33 -*** Exception: sorry for fibbing -``` - -Hmm. We have two properties: - -1. The `id` from `refl`? The type-substituted data actually goes - through that function. The same goes for the `subst` method in - Scala. -2. When using `Leibniz` combinators, the strictness forms a chain to - all underlying `Leibniz` evidence. If there are any missing - values, the transform will also fail. - -Higher kinded `Leibniz` ------------------------ - -Let’s try a variant on `Leib`. - -```scala -sealed abstract class LeibF[G[_], H[_]] { - def subst[F[_[_]]](fa: F[G]): F[H] -} -``` - -This reads “`LeibF[G, H]` can replace `G` with `H` in **any** type -function”. But, whereas the -[kind](https://blogs.atlassian.com/2013/09/scala-types-of-a-higher-kind/) -of the types that Leib discusses is `*`, for `LeibF` it’s `*->*`. So, -`LeibF[List, List]` exhibits that the *type constructors* `List` and -`List` are equal. - -```scala -implicit def refl[G[_]]: LeibF[G, G] = new LeibF[G, G] { - override def subst[F[_[_]]](fa: F[G]): F[G] = fa -} -``` - -Interestingly, except for the kinds of type parameters, these -definitions are exactly the same as for `Leib`. Does that hold for -lift? - -```scala -def lift[F[_[_], _], A[_] , B[_]](ab: LeibF[A, B]): LeibF[F[A, ?], F[B, ?]] = - ab.subst[Lambda[x[_] => LeibF[F[A, ?], F[x, ?]]]](LeibF.refl[F[A, ?]]) -``` - -Despite that we are positively buried in type lambdas (yet moderated -by [Kind Projector](https://github.com/non/kind-projector)) now, -absolutely! - -As an exercise, adapt your `symm` and `compose` methods from the last -part for `LeibF`, by only changing type parameters and switching any -`refl` references. - -```scala -def symm[A[_], B[_]](ab: LeibF[A, B]): LeibF[B, A] -def compose[A[_], B[_], C[_]](ab: LeibF[A, B], bc: LeibF[B, C]): LeibF[A, C] -``` - -You can write a `Leibniz` and associated combinators for types of -*any* kind; the principles and implementation techniques outlined -above for types of kind `*->*` apply to all kinds. - -Whence `PolyKinds`? -------------------- - -You have to define a new `Leib` variant and set of combinators for -each kind you wish to support. There is no need to do this in -Haskell, though. - -```haskell -λ> :k Leib [] -Leib [] :: (* -> *) -> * -λ> :t refl :: Leib [] [] -refl :: Leib [] [] :: Leib [] [] -λ> :t lift (refl :: Leib [] []) -lift (refl :: Leib [] []) :: Leib (f []) (f []) -λ> :t compose (refl :: Leib [] []) -compose (refl :: Leib [] []) :: Leib a [] -> Leib a [] -``` - -In Haskell, we can take advantage of the fact that the actual -implementations are kind-agnostic, by having those definitions be -applicable to all kinds via -[the `PolyKinds` language extension](http://www.haskell.org/ghc/docs/7.8.3/html/users_guide/kind-polymorphism.html), -mentioned at the top of the Haskell code above. No such luck in -Scala. - -Better GADTs ------------- - -[In a post from a couple months ago](http://d.hatena.ne.jp/xuwei/20140706/1404612620), -Kenji Yoshida outlines an interesting way to simulate the missing -type-evidence features of Scala’s GADT support with `Leibniz`. This -works in Haskell, too, in case you are comfortable with turning on -[`RankNTypes`](http://www.haskell.org/ghc/docs/7.8.3/html/users_guide/other-type-extensions.html#universal-quantification) -but not -[`GADTs`](http://www.haskell.org/ghc/docs/7.8.3/html/users_guide/data-type-extensions.html#gadt) -somehow. - -Let’s examine Kenji’s GADT. - -```scala -sealed abstract class Foo[A, B] -final case class X[A]() extends Foo[A, A] -final case class Y[A, B](a: A, b: B) extends Foo[A, B] -``` - -For completeness, let’s also see the Haskell version, including the -function that demands so much hoop-jumping in Scala, but just works in -Haskell. - -```haskell -{-# LANGUAGE GADTs #-} -module FooXY where - -data Foo a b where - X :: Foo a a - Y :: a -> b -> Foo a b - -hoge :: Foo a b -> f a c -> f b c -hoge X bar = bar -``` - -Note that the Haskell type system understands that when `hoge`’s first -argument’s data constructor is `X`, the type variables `a` and `b` -must be the same type, and therefore by implication the argument of -type `f a c` must also be of type `f b c`. This is what we’re trying -to get Scala to understand. - -```scala -def hoge1[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] = - foo match { - case X() => bar - } -``` - -This transliteration of the above Haskell `hoge` function fails to -compile, as Kenji notes, with the following: - -```scala -…/LeibnizArticle.scala:39: type mismatch; - found : bar.type (with underlying type F[A,C]) - required: F[B,C] - case X() => bar - ^ -``` - -The overridden `cata` method ----------------------------- - -Kenji introduces a `cata` method on `Foo` to constrain use of the -`Leibniz.force` hack, while still providing external code with usable -`Leibniz` evidence that can be lifted to implement `hoge`. However, -by implementing the method in a slightly different way, we can use -`refl` instead. - -```scala -sealed abstract class Foo[A, B] { - def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z): Z -} - -final case class X[A]() extends Foo[A, A] { - def cata[Z](x: (A Leib A) => Z, y: (A, A) => Z) = - x(Leib.refl) -} - -final case class Y[A, B](a: A, b: B) extends Foo[A, B] { - def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z) = - y(a, b) -} -``` - -Now we can replace the pattern match (and all other such pattern -matches) with an equivalent `cata` invocation. - -```scala -def hoge2[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] = - foo.cata(x => x.subst[F[?, C]](bar), - (_, _) => sys error "nonexhaustive") -``` - -So why can we get away with `Leib.refl`, whereas the function version -Kenji presents cannot? Compare the `cata` signature in `Foo` versus -`X`: - -```scala - def cata[Z](x: (A Leib B) => Z, y: (A, B) => Z): Z - def cata[Z](x: (A Leib A) => Z, y: (A, A) => Z): Z -``` - -We supplied `A` for both the `A` and `B` type parameters in our -`extends` clause, so that substitution also applies in all methods -from `Foo` that we’re implementing, including `cata`. At that point -it’s obvious to the compiler that `refl` implements the requested -`Leib`. - -Incidentally, a similar style of substitution underlies the definition -of `refl`. - -The `Leib` member ------------------ - -What if we don’t want to write or maintain an overriding-style `cata`? -After all, that’s an n² commitment. Instead, we can incorporate a -`Leib` value in the GADT. First, let’s see what the equivalent -Haskell is, without the `GADTs` extension: - -```haskell -data Foo a b = X (Leib a b) | Y a b - -hoge :: Foo a b -> f a c -> f b c -hoge (X leib) bar = runDual . subst leib . Dual $ bar -``` - -We needed `RankNTypes` to implement `Leib`, of course, but perhaps -that’s acceptable. It’s useful in -[Ermine](https://ermine-language.github.io/), which supports rank-N -types but not GADTs as of this writing. - -The above is simple enough to port to Scala, though. - -```scala -sealed abstract class Foo[A, B] -final case class X[A, B](leib: Leib[A, B]) extends Foo[A, B] -final case class Y[A, B](a: A, b: B) extends Foo[A, B] - -def hoge3[F[_, _], A, B, C](foo: Foo[A, B], bar: F[A, C]): F[B, C] = - foo match { - case X(leib) => leib.subst[F[?, C]](bar) - } -``` - -It feels a little weird that `X` now must retain `Foo`’s -type-system-level separation of the two type parameters. But this -style may more naturally integrate in your ADTs, and it is much closer -to the original non-working `hoge1` implementation. - -It also feels a little weird that you have to waste a slot carting -around this evidence of type equality. As demonstrated in section -“It’s really there” above, though, *it matters that the instance -exists*. - -You can play games with this definition to make it easier to supply -the wholly mechanical `leib` argument to `X`, e.g. adding it as an -`implicit val` in the second parameter list so it can be imported and -implicitly supplied on `X` construction. The basic technique is -exactly the same as above, though. - -`Leibniz` mastery ------------------ - -This time we talked about - -* Why it matters that `subst` always executes to use a type equality, -* the Haskell implementation, -* higher-kinded type equalities and their `Leibniz`es, -* simulating GADTs with `Leibniz` members of data constructors. - -*This article was tested with Scala 2.11.2, -[Kind Projector](https://github.com/non/kind-projector) 0.5.2, and -[GHC](http://www.haskell.org/platform/) 7.8.3.* diff --git a/src/blog/hkts-moving-forward.md b/src/blog/hkts-moving-forward.md deleted file mode 100644 index 4c69ff2b..00000000 --- a/src/blog/hkts-moving-forward.md +++ /dev/null @@ -1,331 +0,0 @@ -{% - author: ${S11001001} - date: "2016-08-21" - tags: [technical] -%} - -# Higher-kinded types: the difference between giving up, and moving forward - -As its opening sentence reminds the reader—a point often missed by -many reviewers—the book -[*Functional Programming in Scala*](https://www.manning.com/books/functional-programming-in-scala) -is not a book about Scala. This (wise) choice occasionally manifests -in peculiar ways. - -For example, you can go quite far into the book implementing its -exercises in languages with simpler type systems. Chapters 1–8 and 10 -port quite readily to -[Java 8](https://github.com/sbordet/fpinscala-jdk8) and C#. So -*Functional Programming in Scala* can be a very fine resource for -learning some typed functional programming, even if such languages are -all you have to work with. Within these chapters, you can remain -blissfully unaware of the limitations imposed on you by these -languages’ type systems. - -However, there is a point of inflection in the book at chapter 11. You -can pass through with a language such as [OCaml](https://ocaml.org/), -Scala, Haskell, [PureScript](http://www.purescript.org/), or one of a -few others. However, users of Java, C#, F#, -[Elm](http://elm-lang.org/), and many others may proceed no further, -and must turn back here. - -![Various languages' chapter 11 support](/img/media/hkt-inflection.png) - -Here is where abstracting over type constructors, or “higher-kinded -types”, comes into play. At this point in the book, you can give up, -or proceed with a sufficiently powerful language. Let’s see how this -happens. - -## Functional combinators - -The bread and butter of everyday functional programming, the -“patterns” if you like, is the implementation of standard functional -combinators for your datatypes, and more importantly the comfortable, -confident use of these combinators in your program. - -For example, confidence with `bind`, also known as `>>=` or `flatMap`, -is very important. The best way to acquire this comfort is to -reimplement it a bunch of times, so *Functional Programming in Scala* -has you do just that. - -```scala -def flatMap[B](f: A => List[B]): List[B] // in List[A] -def flatMap[B](f: A => Option[B]): Option[B] // in Option[A] -def flatMap[B](f: A => Either[E, B]): Either[E, B] // in Either[E, A] -def flatMap[B](f: A => State[S, B]): State[S, B] // in State[S, A] -``` - -## All `flatMap`s are the same - -The similarity between these functions’ types is the most obvious -surfacing of their ‘sameness’. (Unless you wish to count their names, -which I do not.) That sameness is congruent: when you write functions -using `flatMap`, in any of the varieties above, these functions -inherit a sort of sameness from the underlying `flatMap` combinator. - -For example, supposing we have `map` and `flatMap` for a type, we can -‘tuple’ the values within. - -```scala -def tuple[A, B](as: List[A], bs: List[B]): List[(A, B)] = - as.flatMap{a => - bs.map((a, _))} - -def tuple[A, B](as: Option[A], bs: Option[B]): Option[(A, B)] = - as.flatMap{a => - bs.map((a, _))} - -def tuple[E, A, B](as: Either[E, A], bs: Either[E, B]): Either[E, (A, B)] = - as.flatMap{a => - bs.map((a, _))} - -def tuple[S, A, B](as: State[S, A], bs: State[S, B]): State[S, (A, B)] = - as.flatMap{a => - bs.map((a, _))} -``` - -*Functional Programming in Scala* contains several such functions, -such as `sequence`. These are each implemented for several types, each -time with potentially the same code, if you remember to look back and -try copying and pasting a previous solution. - -## To parameterize, or not to parameterize - -In programming, when we encounter such great sameness—not merely -similar code, but *identical* code—we would like the opportunity to -*parameterize*: extract the parts that are different to arguments, and -recycle the common code for all situations. - -In `tuple`’s case, what is different are - -1. the `flatMap` and `map` implementations, and -2. the **type constructor**: `List`, `Option`, `State[S, ...]`, what - have you. - -We have a way to pass in implementations; that’s just higher-order -functions, or ‘functions as arguments’. For the type constructor, we -need ‘type-level functions as arguments’. - -```scala -def tuplef[F[_], A, B](fa: F[A], fb: F[B]): F[(A, B)] = ??? -``` - -We’ve handled ‘type constructor as argument’, and will add the -`flatMap` and `map` implementations in a moment. First, let’s learn -how to read this. - -## Reading a higher-kinded type - -Confronted with a type like this, it’s helpful to sit back and muse on -the nature of a function for a moment. - -Functions are given meaning by substitution of their arguments. - -```scala -def double(x: Int) = x + x -``` - -`double` remains “an abstraction” until we *substitute for x*; in -other words, pass an argument. - -``` -double(2) double(5) -2 + 2 5 + 5 -4 10 -``` - -But this isn’t enough to tell us *what `double` is*; all we see from -these tests is that `double` sometimes returns 4, sometimes 10, -sometimes maybe other things. We must imagine what `double` does in -common *for all possible arguments*. - -Likewise, we give meaning to type-parameterized definitions like -`tuplef` by substitution. The parameter declaration `F[_]` means that -`F` may not be a simple type, like `Int` or `String`, but instead a -one-argument type constructor, like `List` or `Option`. Performing -these substitutions for `tuplef`, we get - -```scala -// original, as above -def tuplef[F[_], A, B](fa: F[A], fb: F[B]): F[(A, B)] - -// F = List -def tupleList[A, B](fa: List[A], fb: List[B]): List[(A, B)] - -// F = Option -def tupleOpt[A, B](fa: Option[A], fb: Option[B]): Option[(A, B)] -``` - -More complicated and powerful cases are available with other kinds of -type constructors, such as by partially applying. That’s how we can -fit `State`, `Either`, and other such types with two or more -parameters into the `F` parameter. - -```scala -// F = Either[E, ...] -def tupleEither[E, A, B](fa: Either[E, A], fb: Either[E, B]) - : Either[E, (A, B)] - -// F = State[S, ...] -def tupleState[S, A, B](fa: State[S, A], fb: State[S, B]) - : State[S, (A, B)] -``` - -Just as with `double`, though this isn’t the whole story of `tuplef`, -its true meaning arises from the common way in which it treats *all -possible* `F` arguments. That is where higher kinds start to get -interesting. - -## Implementing functions with higher-kinded type - -The type of `tuplef` expresses precisely our intent—the idea of -“multiplying” two `F`s, tupling the values within—but cannot be -implemented as written. That’s because we don’t have functions that -operate on `F`-constructed values, like `fa: F[A]` and `fb: F[B]`. As -with any value of an ordinary type parameter, these are opaque. - -In Scala, there are a few ways to pass in the necessary functions. One -option is to implement a `trait` or `abstract class` that itself uses -a higher-kinded type parameter or abstract type constructor. Here are -a couple possibilities. - -```scala -trait Bindable[F[_], +A] { - def map[B](f: A => B): F[B] - def flatMap[B](f: A => F[B]): F[B] -} - -trait BindableTM[+A] { - type F[X] - def map[B](f: A => B): F[B] - def flatMap[B](f: A => F[B]): F[B] -} -``` - -Note that we must use higher-kinded trait type signatures to support -our higher-kinded method types; otherwise, we can’t write the return -types for `map` and `flatMap`. - -```scala -trait BindableBad[F] { - def map[B](f: A => B): F ??? - // where is the B supposed to go? -``` - -Now we make every type we’d like to support either inherit from or -implicitly convert to `Bindable`, such as `List[+A] extends -Bindable[List, A]`, and write `tuplef` as follows. - -```scala -def tupleBindable[F[_], A, B](fa: Bindable[F, A], fb: Bindable[F, B]) - : F[(A, B)] = - fa.flatMap{a => - fb.map((a, _))} -``` - -## Escaping two bad choices - -There are two major problems with `Bindable`’s representation of `map` -and `flatMap`, ensuring its wild unpopularity in the Scala functional -community, though it still appears in some places, such as -[in Ermine](https://github.com/ermine-language/ermine-parser/blob/cc77bf6e150a16129744d18d69022f7b5902814f/src/main/scala/scalaparsers/Monadic.scala). - -1. The choices of inheritance and implicit conversion are both bad in - different ways. Implicit conversion propagates very poorly—it - doesn’t compose, after all, and fails as soon as we do something - innocent like put the value-to-be-converted into a tuple. - Inheritance leaves its own mess: modifying a type to add new, - nonessential operations, and the weird way that `F` is declared in - the method type parameters above. -2. The knowledge required to work out the new type signature above is - excessively magical. There are rules about when implicit conversion - happens, how much duplication of the reference to `Bindable` is - required to have the `F` parameter infer correctly, and even how - many calls to `Bindable` methods are performed. For example, we’d - have to declare the `F` parameter as `F[X] <: Bindable[F, X]` if we - did one more trailing `map` call. But then we wouldn’t support - implicit conversion cases anymore, so we’d have to do something - else, too. - -As a result of all this magic, generic functions over higher kinds -with OO-style operations tend to be ugly; note how much `tuplef` -looked like the `List`-specific type, and how little `tupleBindable` -looks like either of them. - -But we still really, really want to be able to write this kind of -generic function. Luckily, we have a Wadler-made alternative. - -## Typeclasses constrain higher-kinded types elegantly - -To constrain `F` to types with the `flatMap` and `map` we need, we use -typeclasses instead. For `tuplef`, that means we leave `F` abstract, -and leave the types of `fa` and `fb` as well as the return type -unchanged, but add an implicit argument, the “typeclass instance”, -which is a first-class representation of the `map` and `flatMap` -operations. - -```scala -trait Bind[F[_]] { - // note the new ↓ fa argument - def map[A, B](fa: F[A])(f: A => B): F[B] - def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] -} -``` - -Then we define instances for the types we’d like to have this on: -`Bind[List]`, `Bind[Option]`, and so on, as seen in chapter 11 of -*Functional Programming in Scala*. - -Now we just add the argument to `tuplef`. - -```scala -def tupleTC[F[_], A, B](fa: F[A], fb: F[B]) - (implicit F: Bind[F]): F[(A, B)] = - F.flatMap(fa){a => - F.map(fb)((a, _))} -``` - -We typically mirror the typeclass operations back to methods with an -implicit conversion—unlike with `Bindable`, this has no effect on -exposed APIs, so is benign. Then, we can remove the `implicit F` -argument, replacing it by writing `F[_]: Bind` in the type argument -list, and write the method body as it has been written before, with -`flatMap` and `map` methods. - -There’s another major reason to prefer typeclasses, but let’s get back -to *Functional Programming in Scala*. - -## Getting stuck - -I’ve just described many of the practical mechanics of writing useful -functions that abstract over type constructors, but *all this is moot -if you cannot abstract over type constructors*. The fact that Java -provides no such capability is not an indicator that they have -sufficient abstractions to replace this missing feature: it is simply -an abstraction that they do not provide you. - -**Oh, you would like to factor this common code? Sorry, you are -stuck. You will have to switch languages if you wish to proceed.** - -## Don’t get stuck on the second order - -`map` functions are obvious candidates for essential parts of a usable -library for functional programming. This is the first-order -abstraction—it eliminates the concrete loops, recursive functions, -or `State` lambda specifications, you would need to write otherwise. - -When we note a commonality in patterns and define an abstraction over -that commonality, we move “one order up”. When we stopped simply -defining functions, and started taking functions as arguments, we -moved from the first order to the second order. - -It is not enough for a modern general-purpose functional library in -Scala to simply have a bunch of `map` functions. It must also provide -the second-order feature: the ability to *abstract over* `map` -functions, as well as many, many other functions numerous type -constructors have in common. Let’s not give up; let’s move forward. - -*This article was tested with Scala 2.11.7 and -[fpinscala](https://github.com/fpinscala/fpinscala) 5b0115a answers, -with the addition of the method variants of `List#map` and -`List#flatMap`.* diff --git a/src/blog/http4s-error-handling-mtl-2.md b/src/blog/http4s-error-handling-mtl-2.md deleted file mode 100644 index 0cf722a4..00000000 --- a/src/blog/http4s-error-handling-mtl-2.md +++ /dev/null @@ -1,431 +0,0 @@ -{% - author: ${gvolpe} - date: "2018-11-28" - tags: [technical] -%} - -# Error handling in Http4s with classy optics – Part 2 - -This is a continuation of my [previous blog post](http4s-error-handling-mtl.md). Make sure you have read that one before continuing here. - -I recently gave a 20 minutes talk on `classy optics` at the unconference of [Scale by the Bay](http://scale.bythebay.io/) where I also talked about this error handling technique and on my way back home I was still thinking of different ways of doing this. So, after some exploratory work, I came up with a few different alternatives. - -### Issues with first approach - -Something that made me cringe and that a few of my colleagues at work were not happy with was that the algebras had no association with the error type defined in `HttpErrorHandler[F, E]` so the type-safety was down to the programmer's discipline and in this case the compiler was not able to do much. - -When working with `EitherT[F, E, A]` or a bifunctor `IO[E, A]` we have a clear error type whereas by just relying on a single `F[A]` with a `MonadError[F, Throwable]` instance we lose this property. There are a few issues with the first though: - -- It has a "double error channel", meaning that can report errors via `Left` or via its effect type `IO`. -- As any other Monad Transformer in Scala, introduces a performance overhead due to the extra `flatMap` calls and extra allocations. -- Code becomes cumbersome as we need to lift effects and pure `Either` values into the transformer stack. - -The `IO[E, A]` model is naturally a better approach but I found out polymorphic code is more cumbersome than working with `F[A]`. Although this might change once [Cats Effect 2.0](https://github.com/typelevel/cats-effect/issues/321) is out, it'll take a while until we get there. - -#### Errors vs Failures - -What I like about the `IO[E, A]` model is that we can distinguish between "business errors" and "unexpected failures" such as a database connection failure (learn more about `zio`'s error model [here](https://scalaz.github.io/scalaz-zio/)). Eg: when working on a REST API, most of the time we only care about mapping a few business errors into the appropriate http responses. The unexpected failures should be handled by someone else. In this case `http4s` will convert any failure into a response with code 500 (internal server error). - -And this is exactly what we want to achieve here. Writing polymorphic code using `cats-effect` while trying to keep it as simple as possible. Here's an encoding I would like to explore further: - -### Error Channel - -In the previous blog post we defined the algebras as a single trait. In this case we are going to try a different encoding but first we need to introduce an `ErrorChannel[F, E]` typeclass where the error type is a subtype of `Throwable` to be compatible with the error type of the `cats-effect` typeclasses: - -```scala -trait ErrorChannel[F[_], E <: Throwable] { - def raise[A](e: E): F[A] -} -``` - -An instance can be derived for any `ApplicativeError[F, Throwable]` so we don't need to write it manually for every error type. - -```scala -import cats.ApplicativeError - -object ErrorChannel { - def apply[F[_], E <: Throwable](implicit ev: ErrorChannel[F, E]) = ev - - implicit def instance[F[_], E <: Throwable](implicit F: ApplicativeError[F, Throwable]): ErrorChannel[F, E] = - new ErrorChannel[F, E] { - override def raise[A](e: E) = F.raiseError(e) - } - - object syntax { - implicit class ErrorChannelOps[F[_]: ErrorChannel[?[_], E], E <: Throwable](e: E) { - def raise[A]: F[A] = ErrorChannel[F, E].raise[A](e) - } - } -} -``` - -### User Algebra - -Our `UserAlg` will now be defined as an `abstract class` instead in order to be able to add typeclass constraint. - -```scala -case class User(username: String, age: Int) -case class UserUpdateAge(age: Int) - -abstract class UserAlg[F[_]: ErrorChannel[?[_], E], E <: Throwable] { - def find(username: String): F[Option[User]] - def save(user: User): F[Unit] - def updateAge(username: String, age: Int): F[Unit] -} -``` - -And here's the ADT of the possible errors that may arise (notice the `extends Exception` part): - -```scala -sealed trait UserError extends Exception -case class UserAlreadyExists(username: String) extends UserError -case class UserNotFound(username: String) extends UserError -case class InvalidUserAge(age: Int) extends UserError -``` - -We want to make sure our ADT is a subtype of `Throwable` and indeed `Exception <: Throwable`. - -### User Interpreter - -Here's a similar `UserAlg` interpreter to the one presented in the previous post. Note that in a real-life project an interpreter will more likely connect to a database instead of using an in-memory representation based on `Ref`. - -The interesting part is that in order to construct a `UserAlg[F, UserError]` we now need an `ErrorChannel[F, UserError]` instance in scope. This will be the chosen strategy to report errors in the context of `F`. - -```scala -import cats.effect.{ Concurrent, Sync } -import cats.effect.concurrent.Ref -import cats.syntax.all._ - -object UserInterpreter { - - def mkUserAlg[F[_]: Sync](implicit error: ErrorChannel[F, UserError]): F[UserAlg[F, UserError]] = - Ref.of[F, Map[String, User]](Map.empty).map { state => - new UserAlg[F, UserError] { - private def validateAge(age: Int): F[Unit] = - if (age <= 0) error.raise(InvalidUserAge(age)) else ().pure[F] - - override def find(username: String): F[Option[User]] = - state.get.map(_.get(username)) - - override def save(user: User): F[Unit] = - validateAge(user.age) *> - find(user.username).flatMap { - case Some(_) => - error.raise(UserAlreadyExists(user.username)) -// error.raise(new Exception("asd")) // Does not compile -// Sync[F].raiseError(new Exception("")) // Should be considered an unrecoverable failure - case None => - state.update(_.updated(user.username, user)) - } - - override def updateAge(username: String, age: Int): F[Unit] = - validateAge(age) *> - find(username).flatMap { - case Some(user) => - state.update(_.updated(username, user.copy(age = age))) - case None => - error.raise(UserNotFound(username)) - } - } - } - -} -``` - -Notice that we could still call `Sync[F].raiseError(new Exception("boom"))` and it will still compile. However, if we choose to use `ErrorChannel` to signal business errors we will have the compiler on our side and it'll warn us when we try to raise an error that is not part of the ADT we have declared. So signaling error in a different way should just be considered unrecoverable. These are the same semantics you get when working with `EitherT[IO, Throwable, ?]` as shown in the comparison table at the beginning. - -### Http Error Handler - -Here's the same `HttpErrorHandler` defined in the previous blog post: - -```scala -import cats.{ ApplicativeError, MonadError } -import cats.data.{ Kleisli, OptionT } -import org.http4s._ - -trait HttpErrorHandler[F[_], E <: Throwable] { - def handle(routes: HttpRoutes[F]): HttpRoutes[F] -} - -object RoutesHttpErrorHandler { - def apply[F[_]: ApplicativeError[?[_], E], E <: Throwable]( - routes: HttpRoutes[F] - )(handler: E => F[Response[F]]): HttpRoutes[F] = - Kleisli { req => - OptionT { - routes.run(req).value.handleErrorWith(e => handler(e).map(Option(_))) - } - } -} - -object HttpErrorHandler { - def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev - - def mkInstance[F[_]: ApplicativeError[?[_], E], E <: Throwable]( - handler: E => F[Response[F]] - ): HttpErrorHandler[F, E] = - (routes: HttpRoutes[F]) => RoutesHttpErrorHandler(routes)(handler) -} -``` - -### Http Routes with error handling - -Now let's look at the new implementation of `UserRoutes` using the error-type algebra: - -```scala -import cats.effect.Sync -import cats.syntax.all._ -import io.circe.generic.auto._ -import io.circe.syntax._ -import org.http4s._ -import org.http4s.circe.CirceEntityDecoder._ -import org.http4s.circe._ -import org.http4s.dsl.Http4sDsl - -class PreUserRoutesMTL[F[_]: Sync](users: UserAlg[F, UserError]) extends Http4sDsl[F] { - - private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] { - - case GET -> Root / "users" / username => - users.find(username).flatMap { - case Some(user) => Ok(user.asJson) - case None => NotFound(username.asJson) - } - - case req @ POST -> Root / "users" => - req.as[User].flatMap { user => - users.save(user) *> Created(user.username.asJson) - } - - case req @ PUT -> Root / "users" / username => - req.as[UserUpdateAge].flatMap { userUpdate => - users.updateAge(username, userUpdate.age) *> Created(username.asJson) - } - } - - def routes(implicit H: HttpErrorHandler[F, UserError]): HttpRoutes[F] = - H.handle(httpRoutes) - -} -``` - -Notice that in contrast to the example shown in the previous blog post there is now a relationship between `UserAlg` and `HttpErrorHandler`: the error type is the same. However, this is not enforced by the compiler. *Can we be more strict about it?* - -We could define a generic `Routes[F, E]`: - -```scala -abstract class Routes[F[_], E <: Throwable](implicit H: HttpErrorHandler[F, E]) extends Http4sDsl[F] { - protected def httpRoutes: HttpRoutes[F] - val routes: HttpRoutes[F] = H.handle(httpRoutes) -} -``` - -But we'll also need something else to connect the error types of the algebra and the http error handler: - -```scala -abstract class UserRoutes[F[_]: HttpErrorHandler[?[_], E], E <: Throwable]( - users: UserAlg[F, E] -) extends Routes[F, E] -``` - -That's it! We are now enforcing this relationship at compile time. Let's see how the `HttpRoutes` looks like: - -```scala -class UserRoutesAlt[F[_]: HttpErrorHandler[?[_], UserError]: Sync]( - users: UserAlg[F, UserError] -) extends UserRoutes(users) { - - protected val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] { - - case GET -> Root / "users" / username => - users.find(username).flatMap { - case Some(user) => Ok(user.asJson) - case None => NotFound(username.asJson) - } - - case req @ POST -> Root / "users" => - req - .as[User] - .flatMap { user => - users.save(user) *> Created(user.username.asJson) - } - - case req @ PUT -> Root / "users" / username => - req - .as[UserUpdateAge] - .flatMap { userUpdate => - users.updateAge(username, userUpdate.age) *> Ok(username.asJson) - } - } - -} -``` - -Neat! Right? If we try to change the error type of `UserAlg` it wouldn't compile! - -### More than one algebra per Http Route - -In most of my programs I tend to specify an `HttpRoute` per algebra. But what if we wanted to just define a single `HttpRoute` that uses multiple algebras? There are a couple of options. - -Let's first define a new ADT of errors and a new algebra to illustrate the problem: - -#### Catalog Error - -```scala -sealed trait CatalogError extends Exception -case class ItemAlreadyExists(item: String) extends CatalogError -case class CatalogNotFound(id: Long) extends CatalogError -``` - -#### CatalogAlg - -```scala -case class Item(name: String) extends AnyVal - -abstract class CatalogAlg[F[_]: ErrorChannel[?[_], E], E <: Throwable] { - def find(id: Long): F[List[Item]] - def save(id: Long, item: Item): F[Unit] -} -``` - -#### HttpRoutes with multiple algebras - -Here we have an `HttpRoutes` that makes use of two algebras with different error types: - -```scala -class UserRoutesMTL[F[_]: Sync]( - users: UserAlg[F, UserError], - catalog: CatalogAlg[F, CatalogError] -) extends Http4sDsl[F] { - - private val httpRoutes: HttpRoutes[F] = ??? - - def routes( - implicit H1: HttpErrorHandler[F, UserError], - H2: HttpErrorHandler[F, CatalogError] - ): HttpRoutes[F] = - H2.handle(H1.handle(httpRoutes)) - -} -``` - -It works! But it's not as elegant as we would like it to be and if we add more algebras this would quicky get out of control. - -*Can we generalize this pattern?* - -### Shapeless Coproduct - -We can define our error type as a coproduct of different errors, in our case `UserError` and `CatalogError`. For example: - -```scala -import shapeless._ - -def routes[F[_]](implicit H: HttpErrorHandler[F, UserError :+: CatalogError :+: CNil]) = ??? -``` - -However, this doesn't compile because the error type is no longer a subtype of `Throwable`. It is now a `Coproduct`. - -But we might be able to derive an instance for a coproduct of errors if we have an instance of `HttpErrorHandler[F, E]` for each error type. Let's give it a try! We need to define a new typeclass `CoHttpErrorHandler`: - -```scala -import shapeless._ - -trait CoHttpErrorHandler[F[_], Err <: Coproduct] { - def handle(routes: HttpRoutes[F]): HttpRoutes[F] -} - -object CoHttpErrorHandler { - def apply[F[_], Err <: Coproduct](implicit ev: CoHttpErrorHandler[F, Err]) = ev - - implicit def cNilInstance[F[_]]: CoHttpErrorHandler[F, CNil] = - (routes: HttpRoutes[F]) => routes - - implicit def consInstance[F[_], E <: Throwable, T <: Coproduct]( - implicit H: HttpErrorHandler[F, E], - CH: CoHttpErrorHandler[F, T] - ): CoHttpErrorHandler[F, E :+: T] = - (routes: HttpRoutes[F]) => CH.handle(H.handle(routes)) -} -``` - -Voilà! We introduced a `CoHttpErrorHandler` where the error type is a coproduct and the instance can only be derived if each type is a subtype of `Throwable` making it impossible to define an invalid coproduct. So it compiles! But how do we use it? - -### HttpRoutes for a coproduct of errors - -```scala -class CoUserRoutesMTL[F[_]: Sync]( - users: UserAlg[F, UserError], - catalog: CatalogAlg[F, CatalogError] -) extends Http4sDsl[F] { - - private val httpRoutes: HttpRoutes[F] = ??? - - def routes(implicit CH: CoHttpErrorHandler[F, UserError :+: CatalogError :+: CNil]): HttpRoutes[F] = - CH.handle(httpRoutes) - -} -``` - -Yay!!! Now this is more elegant and generic so we can re-use the same pattern in different routes. But now again we have lost the relationship between the error types of the algebras and the error type of `CoHttpErrorHandler`. So maybe we could do something similar to what we have done previously? - -It's possible but in the case of coproducts we need to introduce some boilerplate... - -#### CoRoutes - -```scala -abstract class CoRoutes[F[_], E <: Coproduct](implicit CH: CoHttpErrorHandler[F, E]) extends Http4sDsl[F] { - protected def httpRoutes: HttpRoutes[F] - val routes: HttpRoutes[F] = CH.handle(httpRoutes) -} -``` - -This one is pretty basic and similar to `Routes` defined before. - -#### CoUserRoutes - -```scala -abstract class CoUserRoutes[ - F[_]: CoHttpErrorHandler[?[_], E], - A <: Throwable, - B <: Throwable, - E <: Coproduct: =:=[?, A :+: B :+: CNil] -]( - users: UserAlg[F, A], - catalog: CatalogAlg[F, B] -) extends CoRoutes[F, E] - -type CustomError = UserError :+: CatalogError :+: CNil -``` - -Here we have a couple of constraints: - -- `F[_]` needs to have an instance of `CoHttpErrorHandler[F, E]`. -- `A` and `B` are the error types of the two algebras. -- `E` needs to be a `Coproduct` of type `A :+: B :+: CNil`. - -#### HttpRoutes with multiple algebras - Strict version - -```scala -class CoUserRoutesMTL[F[_]: CoHttpErrorHandler[?[_], CustomError]: Sync]( - users: UserAlg[F, UserError], - catalog: CatalogAlg[F, CatalogError] -) extends CoUserRoutes(users, catalog) { - - protected val httpRoutes: HttpRoutes[F] = ??? - -} -``` - -Now we are saying that the error type of our `CoHttpErrorHandler` is a coproduct of each error type of the algebras. And we wouldn't be able to change the error type of any of them without getting a compiler error. - -### Source code - -You can see all the compiling examples [here](https://github.com/gvolpe/classy-optics). Make sure you check out all the different branches. - -### Conclusion - -The last approach is probably too much but we have demonstrated that it's possible to push the boundaries to make our application very type-safe. However, we also need to consider the trade-offs of writing more boilerplate. - -Personally, I settle for the previous approach where the error type of the algebra matches the error type of the `HttpErrorHandler` even if it requires a bit more of discipline. The choice is yours! Just make sure you understand the trade-offs of every mechanism. - -I hope you have enjoyed this post and please do let me know if you have other ideas to keep broadening my understanding! diff --git a/src/blog/http4s-error-handling-mtl.md b/src/blog/http4s-error-handling-mtl.md deleted file mode 100644 index 2dfb4782..00000000 --- a/src/blog/http4s-error-handling-mtl.md +++ /dev/null @@ -1,309 +0,0 @@ -{% - author: ${gvolpe} - date: "2018-08-25" - tags: [technical] -%} - -# Error handling in Http4s with classy optics - -As a longtime `http4s` user I keep on learning new things and I'm always trying to come up with the best practices for writing http applications. This time I want to talk about my latest achievements in error handling within the context of an http application where it basically means mapping each business error to the appropiate [http response](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). - -So let's get started by putting up an example of an http application with three different endpoints that interacts with a `UserAlgebra` that may or may not fail with some specific errors. - -If you are one of those who don't like to read and prefer to jump straight into the code please find it [here](https://gist.github.com/gvolpe/3fa32dd1b6abce2a5466efbf0eca9e94) :) - -### User Algebra - -We have a simple `UserAlgebra` that let us perform some actions such as finding and persisting users. - -```scala -case class User(username: String, age: Int) -case class UserUpdateAge(age: Int) - -trait UserAlgebra[F[_]] { - def find(username: String): F[Option[User]] - def save(user: User): F[Unit] - def updateAge(username: String, age: Int): F[Unit] -} -``` - -And also an ADT of the possible errors that may arise. I'll explain later in this post why it extends `Exception`. - -```scala -sealed trait UserError extends Exception -case class UserAlreadyExists(username: String) extends UserError -case class UserNotFound(username: String) extends UserError -case class InvalidUserAge(age: Int) extends UserError -``` - -### User Interpreter - -And here we have a simple interpreter for our `UserAlgebra` for demonstration purposes so you can have an idea on how the logic would look like. In a real-life project an interpreter will more likely connect to a database instead of using an in-memory representaion based on `Ref`. - -```scala -import cats.effect.Sync -import cats.effect.concurrent.Ref -import cats.syntax.all._ - -object UserInterpreter { - - def create[F[_]](implicit F: Sync[F]): F[UserAlgebra[F]] = - Ref.of[F, Map[String, User]](Map.empty).map { state => - new UserAlgebra[F] { - private def validateAge(age: Int): F[Unit] = - if (age <= 0) F.raiseError(InvalidUserAge(age)) else F.unit - - override def find(username: String): F[Option[User]] = - state.get.map(_.get(username)) - - override def save(user: User): F[Unit] = - validateAge(user.age) *> - find(user.username).flatMap { - case Some(_) => - F.raiseError(UserAlreadyExists(user.username)) - case None => - state.update(_.updated(user.username, user)) - } - - override def updateAge(username: String, age: Int): F[Unit] = - validateAge(age) *> - find(username).flatMap { - case Some(user) => - state.update(_.updated(username, user.copy(age = age))) - case None => - F.raiseError(UserNotFound(username)) - } - } - } - -} -``` - -### Http Routes - -The following implementation of `UserRoutes` applies the tagless final encoding and the concept of "abstracting over the effect type" where we do not commit to a particular effect until the edge of our application. - -```scala -import io.circe.generic.auto._ -import io.circe.syntax._ -import org.http4s._ -import org.http4s.circe._ -import org.http4s.circe.CirceEntityDecoder._ -import org.http4s.dsl.Http4sDsl - -class UserRoutes[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { - - val routes: HttpRoutes[F] = HttpRoutes.of[F] { - - case GET -> Root / "users" / username => - userAlgebra.find(username).flatMap { - case Some(user) => Ok(user.asJson) - case None => NotFound(username.asJson) - } - - case req @ POST -> Root / "users" => - req.as[User].flatMap { user => - userAlgebra.save(user) *> Created(user.username.asJson) - } - - case req @ PUT -> Root / "users" / username => - req.as[UserUpdateAge].flatMap { userUpdate => - userAlgebra.updateAge(username, userUpdate.age) *> Ok(username) - } - } - -} -``` - -Now this particular implementation is missing a very important part: error handling. If we use the `UserAlgebra`'s interpreter previously defined we will clearly miss the three errors defined by the `UserError` ADT. - -***NOTE: If you are not familiar with these concepts make sure you check out [my talk at Scala Matsuri](https://youtu.be/pGfj_l-h3M8?t=887) early this year where I also talk about error handling in http applications using the Http4s library.*** - -### Http Error Handling - -Okay let's just go ahead and add some error handling to our http route by taking advantange of the `MonadError` instance defined by our constraint `Sync[F]` and making use of the syntax provided by `cats`: - -```scala -class UserRoutesAlt[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { - - val routes: HttpRoutes[F] = HttpRoutes.of[F] { - - case GET -> Root / "users" / username => - userAlgebra.find(username).flatMap { - case Some(user) => Ok(user.asJson) - case None => NotFound(username.asJson) - } - - case req @ POST -> Root / "users" => - req.as[User].flatMap { user => - userAlgebra.save(user) *> Created(user.username.asJson) - }.handleErrorWith { - case UserAlreadyExists(username) => Conflict(username.asJson) - } - - case req @ PUT -> Root / "users" / username => - req.as[UserUpdateAge].flatMap { userUpdate => - userAlgebra.updateAge(username, userUpdate.age) *> Ok(username.asJson) - }.handleErrorWith { - case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson) - } - } - -} -``` - -Now we can say this implementation is quite elegant! We are handling and mapping business errors to the according http response and our code compiles without any warning whatsoever. But wait... We are not handling the `UserNotFound` error and the compiler didn't tell us about it! That's not cool and we as functional programmers believe in types because we can know what a function might do just by looking at the types but here it seems we hit the wall. - -The problem is that our constraint of type `Sync` from `cats-effect` has a `MonadError` instance with its type error fixed as `Throwable`. So the compiler can't help us here since this type is too generic. And we can't add a constraint for `MonadError[F, UserError]` because we would get an "ambigous implicits" error with two instances of `MonadError` in scope. - -So, what can we do about it? - -### Next level MTL: Optics - -I heard sometime ago about Classy Optics (Lenses, Prisms, etc) when I was learning Haskell and watched [this amazing talk](https://www.youtube.com/watch?v=GZPup5Iuaqw) by George Wilson but I never got to use this concept in Scala until now! - -Well first, let me give you a quick definition of `Lens`es and `Prism`s. In a few words we can define: - -- `Lens`es as getters and setters that compose making the accessing of nested data structure's fields quite easy. -- `Prism`s as first-class pattern matching that let us access branches of an ADT and that also compose. - -And `Classy Optics` as the idea of "associate with each type a typeclass full of optics for that type". - -***So what am I talking about and how can these concepts help us solving the http error handling problem?*** - -Remember that I defined the `UserError` ADT by extending `Exception`? - -```scala -sealed trait UserError extends Exception -case class UserAlreadyExists(username: String) extends UserError -case class UserNotFound(username: String) extends UserError -case class InvalidUserAge(age: Int) extends UserError -``` - -Well there's a reason! By making `UserError` a subtype of `Exception` (and by default of `Throwable`) we can take advantage of `Prisms` by going back and forth in the types. See what I'm going yet? - -`UserRoute` has a `Sync[F]` constraint, meaning that we have available a `MonadError[F, Throwable]` instance, but we would like to have `MonadError[F, UserError]` instead to leverage the Scala compiler. The caveat is that the error types need to be of the same family so we can derive a `Prism` that can navigate the errors types in one direction or another. But how do we derive it? - -#### Cats Meow MTL - -Fortunately our friend [Oleg Pyzhcov](https://twitter.com/oleg_pyzhcov) has created this great library named [meow-mtl](https://github.com/oleg-py/meow-mtl) that makes heavy use of [Shapeless](https://github.com/milessabin/shapeless) in order to derive `Lenses` and `Prisms` and it provides instances for some `cats-effect` compatible datatypes. - -And two of the supported typeclasses are `ApplicativeError` and `MonadError` as long as the error type is a subtype of `Throwable` to make it compatible with `cats-effect`. So we can do something like this: - -```scala -import cats.MonadError -import cats.effect.IO -import com.olegpy.meow.hierarchy._ // All you need is this import! -import scala.util.Random - -case class CustomError(msg: String) extends Throwable - -def customHandle[F[_], A](f: F[A], fallback: F[A])(implicit ev: MonadError[F, CustomError]): F[A] = - f.handleErrorWith(_ => fallback) - -val io: IO[Int] = IO(Random.nextInt(2)).flatMap { case 1 => IO.raiseError(new Exception("boom")) } -customHandle(io, IO.pure(123)) -``` - -#### Generalizing Http Error Handling - -Now back to our use case. We can't have a `MonadError[F, UserError]` constraint because there's already a `MonadError[F, Throwable]` in scope given our `Sync[F]` constraint. But it turns out we can make this work if we also abstract over the error handling by introducing an `HttpErrorHandler` algebra where the error type is a subtype of `Throwable`. - -```scala -trait HttpErrorHandler[F[_], E <: Throwable] { - def handle(routes: HttpRoutes[F]): HttpRoutes[F] -} - -object HttpErrorHandler { - def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev -} -``` - -`UserRoutes` can now have an additional constraint of type `HttpErrorHandler[F, UserError]` so we clearly know what kind of errors we are dealing with and can have the Scala compiler on our side. - -```scala -class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F])(implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] { - - private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] { - - case GET -> Root / "users" / username => - userAlgebra.find(username).flatMap { - case Some(user) => Ok(user.asJson) - case None => NotFound(username.asJson) - } - - case req @ POST -> Root / "users" => - req.as[User].flatMap { user => - userAlgebra.save(user) *> Created(user.username.asJson) - } - - case req @ PUT -> Root / "users" / username => - req.as[UserUpdateAge].flatMap { userUpdate => - userAlgebra.updateAge(username, userUpdate.age) *> Created(username.asJson) - } - } - - val routes: HttpRoutes[F] = H.handle(httpRoutes) - -} -``` - -We are basically delegating the error handling (AKA mapping business errors to appropiate http responses) to a specific algebra. - -We also need an implementation for this algebra in order to handle errors of type `UserError` but first we can introduce a `RoutesHttpErrorHandler` object that encapsulates the repetitive task of handling errors given an `HttpRoutes[F]`: - -```scala -import cats.ApplicativeError -import cats.data.{Kleisli, OptionT} - -object RoutesHttpErrorHandler { - def apply[F[_], E <: Throwable](routes: HttpRoutes[F])(handler: E => F[Response[F]])(implicit ev: ApplicativeError[F, E]): HttpRoutes[F] = - Kleisli { req: Request[F] => - OptionT { - routes.run(req).value.handleErrorWith { e => handler(e).map(Option(_)) } - } - } -} -``` - -And our implementation: - -```scala -class UserHttpErrorHandler[F[_]](implicit M: MonadError[F, UserError]) extends HttpErrorHandler[F, UserError] with Http4sDsl[F] { - private val handler: UserError => F[Response[F]] = { - case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson) - case UserAlreadyExists(username) => Conflict(username.asJson) - case UserNotFound(username) => NotFound(username.asJson) - } - - override def handle(routes: HttpRoutes[F]): HttpRoutes[F] = - RoutesHttpErrorHandler(routes)(handler) - } -``` - -If we forget to handle some errors the compiler will shout at us ***"match may not be exhaustive!"*** That's fantastic :) - -#### Wiring all the components - -And the last part will be the wiring of all these components where we need to include the `meow-mtl` import to figure out the derivation of the instances we need in order to make this work. It'll look something like this if using `cats.effect.IO`: - -```scala -import com.olegpy.meow.hierarchy._ - -implicit val userHttpErrorHandler: HttpErrorHandler[IO, UserError] = new UserHttpErrorHandler[IO] - -UserInterpreter.create[IO].flatMap { UserAlgebra => - val routes = new UserRoutesMTL[IO](UserAlgebra) - IO.unit // pretend this is the rest of your program -} -``` - -### Final thoughts - -This is such an exciting time to be writing pure functional programming in Scala! The Typelevel ecosystem is getting richer and more mature, having an amazing set of libraries to solve business problems in an elegant and purely functional way. - -I hope you have enjoyed this post and please do let me know if you know of better ways to solve this problem in the comments! - -And last but not least I would like to thank all the friendly folks I hang out with in the `cats-effect`, `cats`, `fs2` and `http4s` Gitter channels for all the time and effort they put (*for free*) into making this community an amazing space. - -**UPDATE:** See the new article [Error handling in Http4s with classy optics – Part 2](http4s-error-handling-mtl-2.md). diff --git a/src/blog/implicitly-existential.md b/src/blog/implicitly-existential.md deleted file mode 100644 index f4b7a04d..00000000 --- a/src/blog/implicitly-existential.md +++ /dev/null @@ -1,233 +0,0 @@ -{% - author: ${S11001001} - date: "2014-01-18" - tags: [technical] -%} - -# When implicitly isn't specific enough - -When working with implicit-encoded dependent function types, such as -`scalaz.Unapply` and numerous Shapeless operations, you'd frequently -like to acquire instances of those functions to see what types get -calculated for them. - -For example, `++` on Shapeless `HList`s is driven by `Prepend`: - -```scala -def ++[S <: HList](suffix : S)(implicit prepend : Prepend[L, S]) - : prepend.Out = prepend(l, suffix) -``` - -So given some `HList`s, we can expect to be able to combine them in a -couple ways. First, by using the syntax function above, and then by -acquiring a value of `prepend`'s type directly and invoking it, just -as in the body of the above function. - -```scala -import shapeless._, ops.hlist._ -import scalaz._, std.string._, std.tuple._, syntax.applicative._ - -scala> val ohi = 1 :: "hi" :: HNil -ohi: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] - = 1 :: hi :: HNil - -scala> ohi ++ ohi -res0: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]] = 1 :: hi :: 1 :: hi :: HNil - -scala> val ohipohi = implicitly[Prepend[String :: Int :: HNil, String :: Int :: HNil]] -ohipohi: shapeless.ops.hlist.Prepend[ - shapeless.::[String,shapeless.::[Int,shapeless.HNil]], - shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] - = shapeless.ops.hlist$Prepend$$anon$58@13399e98 - -scala> ohipohi(ohi, ohi) -res3: ohipohi.Out = 1 :: hi :: 1 :: hi :: HNil -``` - -Back over in Scalaz, for purposes of an `Applicative` instance, -`(String, Int)` selects its second type parameter. Just as the -`To*OpsUnapply` functions acquire `Unapply` instances to do their -work: - -```scala -implicit def ToApplicativeOpsUnapply[FA](v: FA)(implicit F0: Unapply[Applicative, FA]) = - new ApplicativeOps[F0.M,F0.A](F0(v))(F0.TC) -``` - -We can acquire an instance and use it. - -```scala -scala> val t2ap = implicitly[Unapply[Applicative, (String, Int)]] -t2ap: scalaz.Unapply[scalaz.Applicative,(String, Int)] = -scalaz.Unapply_0$$anon$13@18214797 - -scala> t2ap.TC.point(42) -res5: t2ap.M[Int] = ("",42) -``` - -The mysterious result ---------------------- - -Now let's get that first element out of that tuple we got by calling -`point`. - -```scala -scala> res5._1 -:31: error: value _1 is not a member of t2ap.M[Int] - res5._1 - ^ -``` - -Uh, huh? Let's try adding the `HList`s we got from `ohipohi` before. - -```scala -cala> res3 ++ res3 -:32: error: could not find implicit value for parameter - prepend: shapeless.ops.hlist.Prepend[ohipohi.Out,ohipohi.Out] - res3 ++ res3 - ^ -``` - -The clue is in the type report in the above: path-dependent type -members of `t2ap` and `ohipohi` appear. That wouldn't be a problem, -normally, as we know what they are, but **they're existential** to -Scala. - -```scala -scala> implicitly[t2ap.M[Int] =:= (String, Int)] -:30: error: Cannot prove that t2ap.M[Int] =:= (String, Int). - implicitly[t2ap.M[Int] =:= (String, Int)] - ^ -``` - -`implicitly` only gives what you ask for ----------------------------------------- - -The explanation lies with the `implicitly` calls we made to acquire -the specific dependent functions we wanted to use. Let's look at the -definition of `implicitly` and see if it can enlighten: - -```scala -def implicitly[T](implicit e: T): T -``` - -In other words, `implicitly` returns exactly what you asked for, -type-wise. Recall the inferred type of `ohipohi` when it was defined: - -```scala -ohipohi: shapeless.ops.hlist.Prepend[ - shapeless.::[String,shapeless.::[Int,shapeless.HNil]], - shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] -``` - -Not coincidentally, *this is the exact type we gave as a type -parameter to `implicitly`*. What's important is that `Out`, the type -member of `Prepend` that determines its result type, is existential in -both cases. - -In other words, the rule of `implicitly` is “you asked for it, you got -it”. - -A more specific `implicitly` ----------------------------- - -The answer here is to simulate the weird way in which dependent method -types, like `++` and `ToApplicativeOpsUnapply`, can pass through extra -type information about their implicit parameters that would otherwise -be lost. We do this by reinventing `implicitly`. - -The first try is obvious: follow the comment in the `Predef.scala` -source and give `implicitly` a singleton type result. - -```scala -def implicitly2[T <: AnyRef](implicit e: T): T with e.type = e - -scala> val ohipohi2 = implicitly2[Prepend[Int :: String :: HNil, Int :: String :: HNil]] -ohipohi2: shapeless.ops.hlist.Prepend[ - shapeless.::[Int,shapeless.::[String,shapeless.HNil]], - shapeless.::[Int,shapeless.::[String,shapeless.HNil]]] - with e.type = shapeless.ops.hlist$Prepend$$anon$58@4abe65da - -scala> ohipohi2(ohi, ohi) -res9: ohipohi2.Out = 1 :: hi :: 1 :: hi :: HNil - -scala> res9 ++ res9 -:33: error: could not find implicit value for parameter - prepend: shapeless.ops.hlist.Prepend[ohipohi2.Out,ohipohi2.Out] - res9 ++ res9 - ^ -``` - -Not quite good enough. - -An even more, albeit less, specific `implicitly` ------------------------------------------------- - -I think it's strange that the above doesn't work, but we can deal with -it by being a little more specific. - -```scala -def implicitlyDepFn[T <: DepFn2[_,_]](implicit e: T) - : T {type Out = e.Out} = e - -scala> val ohipohi3 = implicitlyDepFn[Prepend[Int :: String :: HNil, Int :: String :: HNil]] -ohipohi3: shapeless.ops.hlist.Prepend[ - shapeless.::[Int,shapeless.::[String,shapeless.HNil]], - shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]{ - type Out = shapeless.::[Int,shapeless.::[String, - shapeless.::[Int,shapeless.::[String,shapeless.HNil]]]] - } = shapeless.ops.hlist$Prepend$$anon$58@7306572f - -scala> ohipohi3(ohi, ohi) -res11: ohipohi3.Out = 1 :: hi :: 1 :: hi :: HNil - -scala> res11 ++ res11 -res12: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String, - shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.::[String, - shapeless.HNil]]]]]]]] - = 1 :: hi :: 1 :: hi :: 1 :: hi :: 1 :: hi :: HNil -``` - -Now that's more like it. The trick is in the return type of -`implicitlyDepFn`, which includes the structural refinement `{type Out -= e.Out}`. - -Again, it's weird that this structural refinement isn't subsumed by -the return type `e.type` from `implicitly2`'s definition, but I'm not -sure it's wrong, either, given the ephemeral nature of type stability. - -Thankfully, most of the evidence for dependent function types in -Shapeless extends from the `DepFn*` traits, so you only need one of -these special `implicitly` variants for each, rather than one for each -individual dependent function type you wish to acquire instances of in -this way. - -And likewise with `Unapply` ---------------------------- - -We can similarly acquire instances of `scalaz.Unapply` conveniently. -I believe this function will be supplied with Scalaz 7.0.6, and it is -[already included in the 7.1 development branch](https://github.com/scalaz/scalaz/pull/621), -so you will be able to write `Unapply[TC, type]` to get instances as -with plain typeclass lookup in Scalaz, but it's easy enough to define -yourself. - -```scala -def unap[TC[_[_]], MA](implicit U: Unapply[TC, MA]): U.type { - type M[A] = U.M[A] - type A = U.A -} = U - -scala> val t2ap2 = unap[Applicative, (String, Int)] -t2ap2: U.type{type M[A] = (String, A); type A = Int} - = scalaz.Unapply_0$$anon$13@3adb9933 - -scala> t2ap2.TC.point(42) -res13: (String, Int) = ("",42) - -scala> res13._1 -res14: String = "" -``` - -*This article was tested with Scala 2.10.3, Scalaz 7.0.5, and -Shapeless 2.0.0-M1.* diff --git a/src/blog/inauguration.md b/src/blog/inauguration.md deleted file mode 100644 index cbc65502..00000000 --- a/src/blog/inauguration.md +++ /dev/null @@ -1,42 +0,0 @@ -{% - author: ${larsrh} - date: "2013-04-04" - tags: [governance] -%} - -# Inaugurating the typelevel.scala blog - -This Twitter conversation happened today: - -> [@d6] do you have a blog on numerics, marcos, and performance? -> @:style(nowrap)— eugene yokota (@eed3si9n) [April 4, 2013](https://twitter.com/eed3si9n/status/319711014620897280)@:@ - -> [@eed3si9n] Not currently. Maybe I should start one? -> @:style(nowrap)— Eiríkr Åsheim (@d6) [April 4, 2013](https://twitter.com/d6/status/319804225607585794)@:@ - -> [@d6] [@eed3si9n] \*cough\* [typelevel.org/blog/](/blog/README.md) \*cough\* -> @:style(nowrap)— Tom Switzer (@tixxit) [April 4, 2013](https://web.archive.org/web/20131123034904/https://twitter.com/tixxit/status/319805809586495489)@:@ - -[@d6]: https://twitter.com/d6 -[@eed3si9n]: https://twitter.com/eed3si9n - -So, here it is, the typelevel.scala blog! - -What is it about? ------------------ - -As you might already know, typelevel.scala is a collection of libraries which provide a great amount of abstraction. -Here, we would like to show how to use them in your code, provide examples, collect learning resources, and explore implementation details. - -Who writes here? ----------------- - -Everyone who would like to! Contributions are welcome. -If you want to share something about Scalaz, Shapeless, Spire, or Scala topics in general (e.g. type classes), case studies, examples, or other related content, please do not hesitate to contact us. -This blog (and in fact, the whole web site) is built using Jekyll on GitHub pages, so you can just fork the [repository](https://github.com/typelevel/typelevel.github.com), add a post, and create a pull request. - -Stay tuned! ------------ - -We hope that this blog will be filled with content soon. -To make sure that you don't miss anything, follow [@typelevel](https://twitter.com/typelevel) on Twitter or subscribe to the [RSS feed](/blog/feed.rss). diff --git a/src/blog/information-hiding.md b/src/blog/information-hiding.md deleted file mode 100644 index f9dd2c79..00000000 --- a/src/blog/information-hiding.md +++ /dev/null @@ -1,320 +0,0 @@ -{% - author: ${adelbertc} - date: "2016-03-13" - tags: [technical] -%} - -# Information hiding, enforced - -Code should be reusable. An expression traversing a data structure -shouldn't be written multiple times, it should be pulled out into a -generic traversal function. At a larger scale, a random number generator -shouldn't be written multiple times, but rather pulled out into a -module that can be used by others. - -It is important that such abstractions must be done carefully. -Often times a type is visible to the caller, and if the type -is not handled carefully the abstraction can leak. - -For example, a set with fast random indexing (useful for random -walks on a graph) can be implemented with a sorted `Vector`. -However, if the `Vector` type is -leaked, the user can use this knowledge to violate the invariant. - -```scala -import scala.annotation.tailrec - -/** (i in repr, position of i in repr) */ -def binarySearch(i: Int, repr: Vector[Int]): (Boolean, Int) = /* elided */ - -object IntSet { - type Repr = Vector[Int] - - def empty: Repr = Vector.empty - - def add(i: Int, repr: Repr): Repr = { - val (isMember, indexOf) = binarySearch(i, repr) - if (isMember) repr - else { - val (prefix, suffix) = repr.splitAt(indexOf) - prefix ++ Vector(i) ++ suffix - } - } - - def contains(i: Int, repr: Repr): Boolean = - binarySearch(i, repr)._1 -} -``` - -```scala -import IntSet._ -// import IntSet._ - -val good = add(1, add(10, add(5, empty))) -// good: IntSet.Repr = Vector(1, 5, 10) - -val goodResult = contains(10, good) -// goodResult: Boolean = true - -val bad = good.reverse // We know it's a Vector! -// bad: scala.collection.immutable.Vector[Int] = Vector(10, 5, 1) - -val badResult = contains(10, bad) -// badResult: Boolean = false - -val bad2 = Vector(10, 5, 1) // Alternatively.. -// bad2: scala.collection.immutable.Vector[Int] = Vector(10, 5, 1) - -val badResult2 = contains(10, bad2) -// badResult2: Boolean = false -``` - -The issue here is the user knows more about the representation than they -should. The function `add` enforces the sorted invariant on each insert, -and the function `contains` leverages this to do an efficient look-up. -Because the `Vector` definition of `Repr` is exposed, the user is -free to create any `Vector` they wish which may violate the invariant, -thus breaking `contains`. - -In general, the **name** of the representation type is needed but the -**definition** is not. If the definition is hidden, the user is only able to -work with the type to the extent the module allows. This is precisely -the notion of information hiding. If this can be enforced by the type -system, modules can be swapped in and out without worrying about breaking -client code. - -# Quantification -It turns out there is a [well understood principle][understandingTypes] -behind this idea called *existential quantification*. Contrast with -universal quantification which says "for all", existential quantification -says "there is a." - -Below is an encoding of universal quantification via parametric polymorphism. - -```scala -trait Universal { - def apply[A]: A => A -} -``` - -Here `Universal#apply` says *for all* choices of `A`, a function `A => A` can be -written. In the [Curry-Howard Isomorphism][propositionsAsTypes], a profound -relationship between logic and computation, this translates to "for all propositions -`A`, `A` implies `A`." It is therefore acceptable to write the following, which picks -`A` to be `Int`. - -```scala -def intInstantiatedU(u: Universal): Int => Int = - (i: Int) => u.apply(i) -// intInstantiatedU: (u: Universal)Int => Int -``` - -Existential quantification can also be written in Scala. - -```scala -trait Existential { - type A - - def apply: A => A -} -``` - -Note that this is just one way of encoding existentials - for a deeper -discussion, refer to the excellent [Type Parameters and Type Members][typeParamsMembers] -blog series. - -The type parameter on `apply` has been moved up to a type member of the trait. -Practically, this means every instance of `Existential` must pick **one** choice of -`A`, whereas in `Universal` the `A` was parameterized and therefore free. In the -language of logic, `Existential#apply` says "there is a" or "there exists some `A` such that -`A` implies `A`." This "there is a" is the crux of the error when trying -to write a corresponding `intExistential` function. - -```scala -def intInstantiatedE(e: Existential): Int => Int = - (i: Int) => e.apply(i) -// :19: error: type mismatch; -// found : i.type (with underlying type Int) -// required: e.A -// (i: Int) => e.apply(i) -// ^ -``` - -In code, the type in `Existential` is chosen per-instance, so there is no way -of knowing what the actual type chosen is. In logical terms, the only guarantee is -that there exists some proposition that satisfies the implication, but it is not -necessarily the case (and often is not) it holds for all propositions. - -# Abstract types -In the ML family of languages (e.g. Standard ML, OCaml), existential quantification -and thus information hiding, is achieved through [type members][abstractExistential]. -Programs are organized into [modules][ocamlModules] which are what contain these -types. - -In Scala, this translates to organizing code with the object system, using the same -type member feature to hide representation. The earlier example of `IntSet` can then -be written: - -```scala -/** Abstract signature */ -trait IntSet { - type Repr - - def empty: Repr - def add(i: Int, repr: Repr): Repr - def contains(i: Int, repr: Repr): Boolean -} - -/** Concrete implementation */ -object VectorIntSet extends IntSet { - type Repr = Vector[Int] - - def empty: Repr = Vector.empty - - def add(i: Int, repr: Repr): Repr = { - val (isMember, indexOf) = binarySearch(i, repr) - if (isMember) repr - else { - val (prefix, suffix) = repr.splitAt(indexOf) - prefix ++ Vector(i) ++ suffix - } - } - - def contains(i: Int, repr: Repr): Boolean = - binarySearch(i, repr)._1 -} -``` - -As long as client code is written against the signature, the -representation cannot be leaked. - - -```scala -def goodUsage(set: IntSet) = { - import set._ - val s = add(1, add(10, add(5, empty))) - contains(5, s) -} -// goodUsage: (set: IntSet)Boolean -``` - -If the user tries to assert the representation type, the type -checker prevents it **at compile time**. - -```scala -def badUsage(set: IntSet) = { - import set._ - val s = add(10, add(1, empty)) - - // Maybe it's a Vector - s.reverse - contains(10, Vector(10, 5, 1)) -} -// :23: error: value reverse is not a member of set.Repr -// s.reverse -// ^ -// :24: error: type mismatch; -// found : scala.collection.immutable.Vector[Int] -// required: set.Repr -// contains(10, Vector(10, 5, 1)) -// ^ -``` - -# Parametricity -Abstract types enforce information hiding at the definition site (the definition -of `IntSet` is what hides `Repr`). There is another mechanism that enforces information -hiding, which pushes the constraint to the use site. - -Consider implementing the following function. - -```scala -def foo[A](a: A): A = ??? -``` - -Given nothing is known about `a`, the only possible thing `foo` can do is return `a`. If -instead of a type parameter the function was given more information.. - -```scala -def bar(a: String): String = "not even going to use `a`" -``` - -..that information can be leveraged to do unexpected things. This is similar to -the first `IntSet` example when knowledge of the underlying `Vector` allowed unintended -behavior to occur. - -From the outside looking in, `foo` is universally quantified - the caller gets to -pick any `A` they want. From the inside looking out, it is -[existentially quantified][existentialInside] - the implementation knows only as much -about `A` as there are constraints on `A` (in this case, nothing). - -Consider another function `listReplace`. - -```scala -def listReplace[A, B](as: List[A], b: B): List[B] = ??? -``` - -Given the type parameters, `listReplace` looks fairly constrained. The name and signature -suggests it takes each element of `as` and replaces it with `b`, returning a new list. -However, even knowledge of `List` can lead to type checking implementations with strange behavior. - -```scala -// Completely ignores the input parameters -def listReplace[A, B](as: List[A], b: B): List[B] = List.empty[B] -``` - -Here, knowledge of `List` allows the implementation -to create a list out of thin air and use that in the implementation. If instead `listReplace` -only knew about some `F[_]` where `F` is a `Functor`, the implementation becomes much more -constrained. - -```scala -trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] -} - -implicit val listFunctor: Functor[List] = - new Functor[List] { - def map[A, B](fa: List[A])(f: A => B): List[B] = - fa.map(f) - } - -def replace[F[_]: Functor, A, B](fa: F[A], b: B): F[B] = - implicitly[Functor[F]].map(fa)(_ => b) -``` - -```scala -replace(List(1, 2, 3), "typelevel") -// res8: List[String] = List(typelevel, typelevel, typelevel) -``` - -Absent any knowledge of `F` other than the ability to `map` over it, `replace` is -forced to do the correct thing. Put differently, irrelevant information about `F` is hidden. - -The fundamental idea behind this is known as parametricity, made popular by Philip Wadler's -seminal [Theorems for free!][theoremsForFree] paper. The technique is best summarized by the -following excerpt from the paper: - -> Write down the definition of a polymorphic function on a piece of paper. Tell me its type, -> but be careful not to let me see the function's definition. I will tell you a theorem that -> the function satisfies. - -# Why types matter -Information hiding is a core tenet of good program design, and it is important to make -sure it is enforced. Underlying information hiding is existential quantification, -which can manifest itself in computation through abstract types and -parametricity. Few languages support defining abstract type members, and fewer -yet support higher-kinded types used in the `replace` example. It is therefore -to the extent that a language's type system is expressive that -[abstraction can be enforced][tapl]. - -*This blog post was tested with Scala 2.11.7 using [tut][tut].* - -[abstractExistential]: http://dl.acm.org/citation.cfm?id=45065 "Abstract types have existential type" -[existentialInside]: existential-inside.md "It’s existential on the inside" -[ocamlModules]: https://realworldocaml.org/v1/en/html/files-modules-and-programs.html "Real World OCaml: Files, Modules, and Programs" -[propositionsAsTypes]: http://homepages.inf.ed.ac.uk/wadler/topics/history.html#propositions-as-types "Propositions as Types - Philip Wadler" -[tapl]: https://www.cis.upenn.edu/~bcpierce/tapl/ "Types and Programming Languages - Benjamin C. Pierce" -[theoremsForFree]: http://dl.acm.org/citation.cfm?id=99404 "Theorems for free!" -[typeParamsMembers]: type-members-parameters.md "Type members are [almost] type parameters" -[tut]: https://github.com/tpolecat/tut "tut: doc/tutorial generator for scala" -[understandingTypes]: http://dl.acm.org/citation.cfm?id=6042 "On understanding types, data abstraction, and polymorphism - Luca Cardelli, Peter Wegner" diff --git a/src/blog/internal-state.md b/src/blog/internal-state.md deleted file mode 100644 index 2afa9d4a..00000000 --- a/src/blog/internal-state.md +++ /dev/null @@ -1,362 +0,0 @@ -{% - author: ${S11001001} - date: "2016-05-10" - tags: [technical] -%} - -# Making internal state functional - -*This is the ninth of a series of articles on “Type Parameters and -Type Members”.* - -Scala’s -[`CanBuildFrom` API](http://www.scala-lang.org/api/2.11.8/scala/collection/generic/CanBuildFrom.html) -is relatively well-founded and flexible; -[in combination with GADTs, it can provide that flexibility in a fully type-safe way](https://bitbucket.org/S11001001/record-map#markdown-header-using-gadts-to-find-fast-paths-safely), -if users choose not to circumvent it with typecasting. - -However, it is designed in a purely mutable way; you cannot write a -useful `CanBuildFrom` that does not rely on mutation, and you cannot -use the API externally in a functional way. - -Let’s design an alternative to `CanBuildFrom` that makes sense in a -purely functional context, allowing both implementers and users to -avoid unsightly mutation. - -Spoiler warning! Our first pass will have one glaring inelegance. We -will use concepts from previous articles in *Type Parameters and Type -Members* to “invert the abstraction”, which will greatly simplify the -design. Once you’re comfortable with the “inversion”, you can skip the -intermediate step and use this technique directly in your own designs. - -## Disallowing functional approaches - -The pattern of use of `CanBuildFrom` is - -1. `apply` the CBF to produce a - [`Builder`](http://www.scala-lang.org/api/2.11.8/scala/collection/mutable/Builder.html). -2. Call `+=` and `++=` methods to “fill up” the `Builder`. -3. Call `result` to “finalize” or “commit” to the final structure. - -```scala -import collection.generic.CanBuildFrom - -val cbf = implicitly[CanBuildFrom[Nothing, Int, List[Int]]] -val b = cbf() -b += 3 -b ++= Seq(4, 5) -b.result() - -res0: List[Int] = List(3, 4, 5) -``` - -Let’s set aside that this is only suited to eager collections, not -lazy ones like -[`Stream`](http://www.scala-lang.org/api/2.11.8/scala/collection/immutable/Stream.html). You -can tell the problem by types: `+=` and `++=` have the return type -`this.type`. Effectively, this means that if their implementations are -purely functional, all they can do is return `this`: - -```scala - def +=(elem: Elem) = this - def ++=(elems: TraversableOnce[Elem]) = this -``` - -Aside from the informal contract of `Builder`, which suggests that -calls to these methods perform a side effect, the types enforce that -they *must* perform any useful work by means of side effects. - -Returning `this.type` permits these methods to be called in a -superficially functional style: - -```scala -b.+=(3) - .++=(Seq(4, 5)) - .result() - -res1: List[Int] = List(3, 4, 5) -``` - -This retouch is only skin-deep, and can’t repair the defect making -`CanBuildFrom` unsuitable for functional programs, but it implies that -a functional alternative lurks nearby. Let’s go looking for it. - -## Step 1: explicit `Builder` state - -First, we need to take the essential mutation out of `Builder`. That -means it needs to provide an initial state, and the other methods must -use it as a parameter and return value. - -1. We’ll add a new method to return the initial state. -2. `+=` and `++=` will take that state as an argument, returning the - new state instead of `this.type`. -3. `result` will take the final state as an argument, still producing - the result collection. - -While the intermediate state *might* be the same as the final state, -we don’t want to require that. So `Builder` also gains a type -parameter to represent the type of state, `S`. - -```scala -trait FunBuilder[S, -Elem, +To] { - /** Produce the initial state. */ - def init: S - - // note everywhere 'S' was added - def +=(s: S, elem: Elem): S - def ++=(s: S, elems: TraversableOnce[Elem]): S - def result(s: S): To -} -``` - -## A sample `FunBuilder` - -We can incrementally build a -[`Vector`](http://www.scala-lang.org/api/2.11.8/scala/collection/immutable/Vector.html), -but it may not be the most efficient way. Instead, let’s try to -accumulate a -[`List`](http://www.scala-lang.org/api/2.11.8/scala/collection/immutable/List.html), -then construct the `Vector` once we’re done. - -```scala -class VectorBuilderList[A] - extends FunBuilder[List[A], A, Vector[A]] { - - def init = List() - - def +=(s: List[A], elem: A) = elem :: s - - def ++=(s: List[A], elems: TraversableOnce[A]) = - elems.toList reverse_::: s - - def result(s: List[A]) = - s.toVector.reverse -} - -val vbl = new VectorBuilderList[Int] -vbl.result(vbl.++=(vbl.+=(vbl.init, 2), Seq(3, 4))) - -res0: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4) -``` - -(There’s a problem with `CanBuildFrom` now, but we’ll hold off fixing -it.) - -## A slightly different Builder - -Maybe it would be better to optimize for the `++=` “bulk add” method, -though. - -```scala -class VectorBuilderListList[A] - extends FunBuilder[List[Traversable[A]], A, Vector[A]] { - def init = List() - - def +=(s: List[Traversable[A]], elem: A) = - Traversable(elem) :: s - - def ++=(s: List[Traversable[A]], elems: TraversableOnce[A]) = - elems.toTraversable :: s - - def result(s: List[Traversable[A]]) = - s.foldLeft(Vector[A]()){(z, as) => as ++: z} -} - -val vbll = new VectorBuilderListList[Int] -vbll.result(vbll.++=(vbll.+=(vbll.init, 2), Seq(3, 4))) - -res0: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4) -``` - -## Hide your state - -The type of these builders are different, even though their usage is -the same. This design also exposes what was originally *internal* -state as part of the API. Luckily, `CanBuildFrom` makes a point of -this when we try to integrate `FunBuilder` into our own CBF version; -there’s nowhere to put the `S` type parameter. - -```scala -trait FunCanBuildFrom[-From, -Elem, +To] { - def apply(): FunBuilder[S, Elem, To] -} - -…/FCBF.scala:42: not found: type S - def apply(): FunBuilder[S, Elem, To] - ^ -``` - -We can hide the state by forcing the caller to deal with the builder -in a state-generic context. One way to do this is with a generic -continuation. - -```scala -trait BuilderCont[+Elem, -To, +Z] { - def continue[S](builder: FunBuilder[S, Elem, To]): Z -} - -// in FunCanBuildFrom... - def apply[Z](cont: BuilderCont[Elem, To, Z]): Z -``` - -Now we can implement a `FunCanBuildFrom` that can use either of the -`FunBuilder`s we’ve defined. - -```scala -class VectorCBF[A](bulkOptimized: Boolean) - extends FunCanBuildFrom[Any, A, Vector[A]] { - def apply[Z](cont: BuilderCont[A, Vector[A], Z]) = - if (bulkOptimized) - cont continue (new VectorBuilderListList) - else - cont continue (new VectorBuilderList) -} -``` - -Take a look at the type flow. The caller of `apply` is the one who -decides the `Z` type. But the `apply` implementation chooses the `S` -to pass to `continue`, which cannot know any more about what that -state type is. (It can even choose different types based on runtime -decisions.) Information hiding is restored. - -```scala -val cbf = new VectorCBF[Int](true) -cbf{new BuilderCont[Int, Vector[Int], Vector[Int]] { - def continue[S](vbl: FunBuilder[S, Int, Vector[Int]]) = - vbl.result(vbl.++=(vbl.+=(vbl.init, 2), Seq(3, 4))) -}} - -res1: Vector[Int] = Vector(2, 3, 4) -``` - -Now the code using the `FunBuilder` can’t fiddle with the -`FunBuilder`’s state values; it can only rewind to previously seen -states, a norm to be expected in functional programming with -persistent state values. - -## Existential types are abstraction inversion - -This is rather a lot of inconvenient ceremony, though. Instead of -passing a continuation that receives the `S` type as an argument along -with the `FunBuilder`, let’s just have `apply` return the type along -with the `FunBuilder`. We have a tool for returning a pair of type and -value using that type. - -```scala - def apply(): FunBuilder[_, Elem, To] -``` - -Remember that existential types are pairs. - -Having collapsed callee-of-callee back to caller perspective, let’s -apply the rule of thumb from -[the first post in this series](type-members-parameters.md). - -> A type parameter is usually more convenient and harder to screw up, but if you intend to use it existentially in most cases, changing it to a member is probably better. - -The usual case will be from the perspective of a CBF user, so the -usual use of the `S` parameter is existential. So let’s turn it into -the equivalent type member. - -```scala -// rewrite the heading of FunBuilder as -trait FunBuilder[-Elem, +To] { - type S - -// and FunCanBuildFrom#apply as - def apply(): FunBuilder[Elem, To] - -// and the parameter S moves to a member -// for all implementations so far; -// fix until compile or see appendix -``` - -And we can see the information stays hidden. - -```scala -scala> val cbf = new VectorCBF[Int](true) -cbf: fcbf.VectorCBF[Int] = fcbf.VectorCBF@4363e2ba - -scala> val vb = cbf() -vb: fcbf.FunBuilder[Int,Vector[Int]] = fcbf.VectorBuilderListList@527c222e - -scala> val with1 = vb.+=(vb.init, 2) -with1: vb.S = List(List(2)) - -scala> val with2 = vb.++=(with1, Seq(2, 3)) -with2: vb.S = List(List(2, 3), List(2)) - -scala> vb.result(with2) -res0: Vector[Int] = Vector(2, 2, 3) -``` - -As in -[“Values never change types”](values-never-change-types.md#naming-the-existential), -`vb.S` is abstract, existential, irreducible. - -## Last minute adjustments - -`Builder` had to be separate from `CanBuildFrom` because the latter -had to be stateless, with `Builder` needing to be stateful. Now that -both are stateless, the `FunBuilder` API can probably be collapsed -into `FunCanBuildFrom`. - -This leaves the question, what about the mutable-state `Builder`s? -They can mutate the `S`, returning the input state from `+=` and -`++=`. You can’t use `S` values to rewind such a `FunBuilder`, but you -couldn’t before, anyway. - -In the next part, “Avoiding refinement with dependent method types”, -we’ll look at the meaning of Scala’s “dependent method types” feature, -using it to replace some more type parameters with type members in -non-existential use cases. - -*This article was tested with Scala 2.11.8.* - -## Appendix: final `FunBuilder` examples - -The rewrite from `S` type parameter to member in the `FunBuilder` -implementations is a boring, mechanical transform, but I’ve included -it here for easy reference. - -```scala -class VectorBuilderList[A] - extends FunBuilder[A, Vector[A]] { - type S = List[A] - - def init = List() - - def +=(s: S, elem: A) = elem :: s - - def ++=(s: S, elems: TraversableOnce[A]) = - elems.toList reverse_::: s - - def result(s: S) = - s.toVector.reverse -} - -class VectorBuilderListList[A] - extends FunBuilder[A, Vector[A]] { - type S = List[Traversable[A]] - - def init = List() - - def +=(s: S, elem: A) = - Traversable(elem) :: s - - def ++=(s: S, elems: TraversableOnce[A]) = - elems.toTraversable :: s - - def result(s: S) = - s.foldLeft(Vector[A]()){(z, as) => as ++: z} -} - -class VectorCBF[A](bulkOptimized: Boolean) - extends FunCanBuildFrom[Any, A, Vector[A]] { - def apply() = - if (bulkOptimized) - new VectorBuilderListList - else - new VectorBuilderList -} -``` diff --git a/src/blog/intro-to-mtl.md b/src/blog/intro-to-mtl.md deleted file mode 100644 index a21f1e63..00000000 --- a/src/blog/intro-to-mtl.md +++ /dev/null @@ -1,491 +0,0 @@ -{% - author: ${lukajcb} - date: "2018-10-06" - tags: [technical] -%} - -# A comprehensive introduction to Cats-mtl - -MTL is a library for composing monad transformers and making it easier to work with nested monad transformer stacks. -It originates from the land of Haskell, but has made it into Scala a long time ago. -For the longest time however, it was barely usable, because of a bunch of different Scala quirks coming together. -With all this, I feel many have the impression that mtl is something scary, abstract or too complicated. -In this blog post, I'll try my best to disprove this notion and demonstrate the simplicity and elegance of Cats-mtl. After reading this, I hope you'll agree that one should prefer `mtl` whenever one needs to compose more than one monad transformer nested inside of each other. - -## What is mtl? - -Mtl is an acronym and stands for *Monad Transformer Library*. Its main purpose it make it easier to work with nested monad transformers. It achieves this by encoding the effects of most common monad transformers as type classes. -To understand what this means we'll first have to look at some of the common monad transformers. - -I'll go over some of the lesser known transformers `StateT` and `ReaderT` next, so feel free to skip the next section if you already know about `StateT` and `ReaderT`. - - -### ReaderT - -`ReaderT` allows us to *read* from an environment and create other values that depend on the environment. -This can be especially useful for e.g. reading from some external configuration. -Some like to describe this as the functional programming equivalent of dependency injection. - -As an example, let's imagine we want to make a call to a service, but to make that call we need to pass some configuration. - -First, some imports and some declarations: - -```scala -import cats._ -import cats.data._ -import cats.implicits._ -import cats.effect._ - -// These are just String for simplicity -type Config = String -type Result = String - -``` - -Now let's say we have these two functions for the service we want to call and the configuration we want to read from. -```scala - -def getConfig: IO[Config] = ??? -// getConfig: cats.effect.IO[Config] - -def serviceCall(c: Config): IO[Result] = ??? -// serviceCall: (c: Config)cats.effect.IO[Result] -``` - -The easiest thing would be to just pass down the configuration from the very top of your application. -However that can be pretty tedious, so what we do instead is use `ReaderT`. -`ReaderT` gives us the `ask` function, which gives us access to a read-only environment value of type `E`: - -```scala -def ask[F[_]: Applicative, E]: ReaderT[F, E, E] -``` - -We can then use `flatMap`, `map` or for-comprehensions to actually use that value and do things with it: - -```scala -def readerProgram: ReaderT[IO, Config, Result] = for { - config <- ReaderT.ask[IO, Config] - result <- ReaderT.liftF(serviceCall(config)) -} yield result -// readerProgram: cats.data.ReaderT[cats.effect.IO,Config,Result] -``` - -Now that we have a value of `ReaderT` that gives us back our result, the next step is to actually "inject" the dependency. -For this purpose, `ReaderT[F, E, A]` gives us a `run` function that expects us to give it a value of `E` and will then return an `F[A]`, so in our case an `IO` of `Result`: - -```scala -def run(e: E): F[A] -``` - -Combined with our `getConfig` function we can now write the entry point to our program: - -```scala -def main: IO[Result] = getConfig.flatMap(readerProgram.run) -// main: cats.effect.IO[Result] -``` - -And that is how we can do functional dependency injection in Scala. -However, I believe this pattern isn't used very often, because it forces you to wrap all of your steps in `ReaderT`. -If you continue reading on, we'll go through how this problem can be mitigated using MTL. - - -### StateT - -Like `ReaderT`, `StateT` also allows us to read from an environment. -However, unlike `ReaderT`, it also allows us to write to that environment, making it capable of holding state, hence the name. -With `StateT` over `IO`, we can deliberately create programs that can access the outside world and also maintain mutable state. -This is very powerful and, when used without care, can give rise to similar problems as can be found in imperative programs that abuse global mutable state and unlimited side effects. -Use `StateT` with care however, and it can be a really great tool for parts of your application that require some notion of mutable state. - -An example use case that comes up very often is the ability to send some requests to an external services and after each of those requests, use the resulting value to modify an environment with which you'll create the next request. -This environment could be used for something simple like a cache, or something more complex like dynamically changing the parameters of each request, depening on what state the environment currently holds. -Let's look at an abstract example, that showcases this ability. - -First, we'll define a function that calls our external service which will take the environment into account. - -```scala -// Again we use String here for simplicity, in real code this would be something else -type Env = String -type Request = String -type Response = String - -def initialEnv: Env = ??? - -def request(r: Request, env: Env): IO[Response] = ??? -``` - -Next, we'll also need a function that given a response and an old environment will return a new updated environment. - -```scala -def updateEnv(r: Response, env: Env): Env = ??? - -// We also need some fake requests -def req1: Request = ??? -def req2: Request = ??? -def req3: Request = ??? -def req4: Request = ??? -``` - -Now we can get started with `StateT`. -To do so, we'll create a new request function that will make the request with the current environment and update it after we've received the response: - -```scala -def requestWithState(r: Request): StateT[IO, Env, Response] = for { - env <- StateT.get[IO, Env] - resp <- StateT.liftF(request(r, env)) - _ <- StateT.modify[IO, Env](updateEnv(resp, _)) -} yield resp -``` - -This demonstrates the power of `StateT`. -We can get the current state by using `StateT.get` (which returns a `StateT[IO, Env, Env]` similar to `ReaderT.ask`) and we can also modify it using `StateT.modify` (which takes a function `Env => Env` and returns a `StateT[IO, Env, Unit]`). - -Now, if we wanted to make those different requests, we could just reuse that `requestWithState` function N number of times: - -```scala -def stateProgram: StateT[IO, Env, Response] = for { - resp1 <- requestWithState(req1) - resp2 <- requestWithState(req2) - resp3 <- requestWithState(req3) - resp4 <- requestWithState(req4) -} yield resp4 -``` - -And now we have a fully fledged program exactly as we wanted. -But what can we actually do with the `StateT` value? -To run the full program, we need an `IO`. -Of course, just like `ReaderT`, we can turn `StateT` into `IO` by using the `run` method and supplying an initial value for our environment. -Let's try that out! - - -```scala -def main: IO[(Env, Response)] = stateProgram.run(initialEnv) -// main: cats.effect.IO[(Env, Response)] -``` - -And that gives us a fully working stateful application. Cool. -Next, we'll look at how we can combine different transformers and what monad transformers actually represent. - - -## Monad Transformers encode some notion of *effect* - -`EitherT` encodes the effect of short-circuiting errors. -`ReaderT` encodes the effect of reading a value from the environment. -`StateT` encodes the effect of pure local mutable state. - -All of these monad transformers encode their effects as data structures, but there's another way to achieve the same result: Type classes! - -For example we've looked extensively at the `ReaderT.ask` function, what would it look like if we used a type class here instead? -Well, Cats-mtl has an answer and it's called `ApplicativeAsk`. -You can think of it as `ReaderT` encoded as a type class: - -```scala -trait ApplicativeAsk[F[_], E] { - val applicative: Applicative[F] - - def ask: F[E] -} -``` - -At it's core `ApplicativeAsk` just encodes the fact that we can ask for a value from the environment, exactly like `ReaderT` does. -Exactly like `ReaderT`, it also includes another type parameter `E`, that represents that environment. - -If you're wondering why `ApplicativeAsk` has an `Applicative` field instead of just extending from `Applicative`, that is to avoid implicit ambiguities that arise from having multiple subclasses of a given type (here `Applicative`) in scope implicitly. -So in this case we favor composition over inheritance as otherwise, we could not e.g. use `Monad` together with `ApplicativeAsk`. -You can read more about this issue in this excellent [blog post by Adelbert Chang](subtype-typeclasses.md). - -### Effect type classes - -`ApplicativeAsk` is an example for what is at the core of Cats-mtl. -Cats-mtl provides type classes for most common effects which let you choose what kind of effects you need without committing to a specific monad transformer stack. - -Ideally, you'd write all your code using only an abstract type constructor `F[_]` with different type class constraints and then at the end run that code with a specific data type that is able to fulfill those constraints. - -So without further ado, let's try to convert our `Reader` program from earlier into mtl-style. -First, I'll include the original program again: - -```scala -def getConfig: IO[Config] = ??? -// getConfig: cats.effect.IO[Config] - -def serviceCall(c: Config): IO[Result] = ??? -// serviceCall: (c: Config)cats.effect.IO[Result] - -def readerProgram: ReaderT[IO, Config, Result] = for { - config <- ReaderT.ask[IO, Config] - result <- ReaderT.liftF(serviceCall(config)) -} yield result -// readerProgram: cats.data.ReaderT[cats.effect.IO,Config,Result] - -def main: IO[Result] = getConfig.flatMap(readerProgram.run) -// main: cats.effect.IO[Result] -``` - -Now we should just replace that `ReaderT` with an `F` and add an `ApplicativeAsk[F, Config]` constraint, right? -We have one small problem though, how can we lift our `serviceCall` which is an `IO` value, into our abstract `F` context? -Fortunately `cats-effect` already defines a typeclass designed to help us out here called `LiftIO`. -It defines a single function `liftIO` that does exactly what you'd expect: - -```scala -@typeclass trait LiftIO[F[_]] { - def liftIO[A](io: IO[A]): F[A] -} -``` - -If there's an instance for `LiftIO[F]` we can lift any `IO[A]` into an `F[A]`. -Furthermore `IO` defines a method `to` which makes use of this type class to provide some nicer looking syntax. - -With this in mind, we can now define our `readerProgram` fully using MTL: - -```scala -import cats.mtl._ -import cats.mtl.instances.all._ - -def readerProgram[F[_]: Monad: LiftIO](implicit A: ApplicativeAsk[F, Config]): F[Result] = for { - config <- A.ask - result <- serviceCall(config).to[F] -} yield result -``` - -We replaced our call to `ReaderT.ask` with a call to `ask` provided by `ApplicativeAsk` and instead of using `ReaderT.liftF` to lift an `IO` into `ReaderT`, we can simply use the `to` function on `IO`, pretty neat if you ask me. - -Now to run it, all we need to do is specify the target `F` to run in, in our case `ReaderT[IO, Config, Result]` fits perfectly: - -```scala -val materializedProgram = readerProgram[ReaderT[IO, Config, ?]] - -def main: IO[Result] = getConfig.flatMap(materializedProgram.run) -``` - -This process of turning a program defined by an abstract type constructor with additional type class constraints into an actual concrete data type is sometimes called *interpreting* or *materializing* a program. - -Another thing we can do is define a type alias for `ApplicativeAsk[F, Config]` so that we can more easily use it with the context bound syntax: - -```scala -type ApplicativeConfig[F[_]] = ApplicativeAsk[F, Config] - -def readerProgram[F[_]: Monad: LiftIO: ApplicativeConfig]: F[Result] = ??? -``` - -So far so good, but this doesn't seem to be any better than what we had before. -I've teased at the beginning that MTL really shines once you use more than one monad transformer. -So let's say our program now also needs to be able to handle errors (which I think is a very reasonable requirement). - -To do so, we'll use `MonadError`, which can be found in cats-core instead of mtl, but in its essence, it encodes the short circuting effect that's shared with `EitherT`. - -To keep things simple for now, we want to raise an error if the configuration we got was invalid somehow. -For this purpose we'll have this simple function that will simply return if a `Config` is valid or not: - -```scala -def validConfig(c: Config): Boolean = ??? -``` - -Then we'll also want to define an error ADT for our app: - -```scala -sealed trait AppError -case object InvalidConfig extends AppError -``` - -Now we can go and extend our program from earlier. -We'll add a `MonadError[F, AppError]` type alias, `MonadAppError` and then add a constraint for it in our program. - -```scala -type MonadAppError[F[_]] = MonadError[F, AppError] - -def program[F[_]: MonadAppError: ApplicativeConfig: LiftIO]: F[Result] = ??? -``` - -Now we want so ensure somehow that our config is valid and raise an `InvalidConfig` error if it's not. -To do so, we'll simply use the `ensure` function provided by `MonadError`. -It looks like this: - -```scala -def ensure(error: => E)(predicate: A => Boolean): F[A] -``` - -And it fills our need exactly. It will raise the passed `error`, if the `predicate` function returns `false`. -Let's go and try it out: - -```scala -def program[F[_]: MonadAppError: ApplicativeConfig: LiftIO]: F[Result] = for { - config <- ApplicativeAsk[F, Config].ask - .ensure(InvalidConfig)(validConfig) - result <- serviceCall(config).to[F] -} yield result -// program: [F[_]](implicit evidence$1: MonadAppError[F], implicit evidence$2: ApplicativeConfig[F], implicit evidence$3: cats.effect.LiftIO[F])F[Result] -``` - -Pretty simple, now let's materialize it! -To do so, we'll use a monad stack of `ReaderT`, `EitherT` and `IO`. -Unwrapped it should look like this `IO[Either[AppError, Reader[Config, A]]]`. - -We'll create some type aliases to get a better overview: - - -```scala -type EitherApp[A] = EitherT[IO, AppError, A] -type Stack[A] = ReaderT[EitherApp, Config, A] - -val materializedProgram: Stack[Result] = program[Stack] - -def main: IO[Either[AppError, Result]] = - EitherT.liftF(getConfig).flatMap(materializedProgram.run).value -``` - - -This is the magic of mtl, it is able to give you type class instances for every single monad transformer in the stack. -This means that when you stack `EitherT`, `ReaderT` and `StateT`, you'll be able to get instances for `MonadError`, `ApplicativeAsk` and `MonadState`, which is really useful! - -If you're wondering how this works, well let's just have a quick look at how the `MonadError` instance for `ReaderT` - -```scala -def monadErrorForReaderT[F[_], E, R](implicit F: MonadError[F, E]): MonadError[ReaderT[F, R, ?], E] = - new MonadError[ReaderT[F, R, ?], E] { - def raiseError[A](e: E): ReaderT[F, R, A] = - ReaderT.liftF(F.raiseError(e)) - - def handleErrorWith[A](fa: ReaderT[F, R, A])(f: E => ReaderT[F, R, A]): ReaderT[F, R, A] = - ReaderT.ask[F, R].flatMap { r => - ReaderT.liftF(fa.run(r).handleErrorWith(e => f(e).run(r))) - } - } -``` - -To get an instance of `MonadError` for `ReaderT[F, R, ?]`, we need to have a `MonadError` for `F`. -Then we can easily use that underlying instance to handle and raise the errors instead. -Again, this means that if some part of transformer stack is capable of raising and handling errors, now your whole stack is. -So if it includes `EitherT` somewhere, you can "lift" that capability. - -There are different strategies for lifting these capabilities throughout your monad stack, but they'd be out of scope for this article. - -What this means for us, is that we never have to think about lifting individual monads through transformer stacks. -The implicit search used by the type class mechanic takes care of it. -Pretty neat, I think. -Now contrast this lack of lifting, with the same program written without mtl: - -```scala -type EitherApp[A] = EitherT[IO, AppError, A] -// defined type alias EitherApp - -type Stack[A] = ReaderT[EitherApp, Config, A] -// defined type alias Stack - -def program: Stack[Result] = for { - config <- ReaderT.ask[EitherApp, Config] - _ <- if (validConfig(config)) ().pure[Stack] - else ReaderT.liftF[EitherApp, Config, Unit](EitherT.leftT(InvalidConfig)) - result <- ReaderT.liftF(EitherT.liftF[IO, AppError, Result](serviceCall(config))) -} yield result -// program: Stack[Result] -``` - -It's the same program, but now we have to add type annotations and `liftF`s everywhere. -If you try to take away one of those type annotations the program will fail to compile, so this is the minimum amount of boilerplate you need. - - -### Adding State - -For the next step, let's imagine we want to send multiple requests and after each, use information we retrieved from the response for the next request, similar to how we did earlier in the `StateT` example. - -Instead of using `StateT`, we'll use the `MonadState` type class: - -```scala -trait MonadState[F[_], S] { - val monad: Monad[F] - - def get: F[S] - - def set(s: S): F[Unit] - - def modify(f: S => S): F[Unit] = get.flatMap(s => set(f(s))) -} -``` - -Let's imagine we have a list of requests, where we want to update the environment after each request, and we also want to use the environment to create the next request. -At the very end we want to return the list of all the responses we got: - -```scala -type Result = List[Response] - -def updateEnv(r: Response, env: Env): Env = ??? - -def requests: List[Request] = ??? - -def newServiceCall(c: Config, req: Request, e: Env): IO[Response] = ??? -``` - -So far, so good, next we'll use `MonadState` to create a new function that will wrap `newServiceCall` with the addition of modifying the environment using `updateEnv`. -To do so, we'll create a new type alias for `MonadState[F, Env]`: - -```scala -type MonadStateEnv[F[_]] = MonadState[F, Env] -// defined type alias MonadStateEnv - -def requestWithState[F[_]: Monad: MonadStateEnv: LiftIO](c: Config, req: Request): F[Response] = for { - env <- MonadState[F, Env].get - response <- newServiceCall(c, req, env).to[F] - _ <- MonadState[F, Env].modify(updateEnv(response, _)) -} yield response -// requestWithState: [F[_]](c: Config, req: Request)(implicit evidence$1: cats.Monad[F], implicit evidence$2: MonadStateEnv[F], implicit evidence$3: cats.effect.LiftIO[F])F[Response] -``` - -Here, we use `get` to retrieve the current state of the environment, then we use `newServiceCall` and lift it into `F` and use the response to modify the environment with `updateEnv`. - -Now, we can use `requestWithState` on our list of requests and embed this new part into our program. -The best way to do that, is of course `traverse`, as we want to go from a `List[Request]` and a function `Request => F[Response]` to an `F[List[Response]]`. -So without further ado, this is our final program, using all three different mtl type classes we learned about in this article: - -```scala -def program[F[_]: MonadAppError: MonadStateEnv: ApplicativeConfig: LiftIO]: F[Result] = for { - config <- ApplicativeAsk[F, Config].ask - .ensure(InvalidConfig)(validConfig) - responses <- requests.traverse(req => requestWithState[F](config, req)) -} yield responses -// program: [F[_]](implicit evidence$1: MonadAppError[F], implicit evidence$2: MonadStateEnv[F], implicit evidence$3: ApplicativeConfig[F], implicit evidence$4: cats.effect.LiftIO[F])F[Result] -``` - -And that is it! -Of course, we still have to run it, so let's materialize our `F` into an appropriate data type. -We'll be using a stack of `EitherT`, `StateT` and `ReaderT`, with `IO` as our base to satisfy `LiftIO`: - -```scala -def materializedProgram = program[StateT[EitherT[ReaderT[IO, Config, ?], AppError, ?], Env, ?]] -``` - -And now we have a fully applied transformer stack. - -The only thing left is to turn that stack back into an `IO` by running the individual layers. - -```scala -def main: IO[Either[AppError, (Env, Result)]] = - getConfig.flatMap(conf => - materializedProgram.run(initialEnv) //Run the StateT layer - .value //Run the EitherT layer - .run(conf) //Run the ReaderT layer - ) -``` - -If we were to get that same value using just transformers and no mtl, the amount of boilerplate would be excruciating. We would need multiple `liftF`s for every monad transformer and dozens of type annotations, leaving the actual code hidden under layers and layers of boilerplate. - -With Cats-mtl, dealing with different effects is simple and free of boilerplate. -We can describe our application as functions dealing with an abstract context `F[_]` that must be able to provide certain effect constraints. -These constraints are provided by the different MTL type classes in Cats-mtl and their instances can be lifted up to the highest layer with Cats-mtl's underlying machinery. - -In summary Cats-mtl provides two things: -MTL type classes representing effects and a way to lift instances of these classes through transformer stacks. -If you'd like to learn more about Cats-mtl, [check out its new website!](https://typelevel.org/cats-mtl/) - -### Other mtl class instances - -Now I said that `ApplicativeAsk` is the type class encoding of `ReaderT`, but it's by no means the only one that can form an `ApplicativeAsk` instance. -Monad transformer stacks are known to be quite unperformant, especially so on the JVM, so there are some alternate solutions. For example, one could use [the Arrows library](https://github.com/traneio/arrows), which provides effect types with an input type in addition to its output type `Arrow[A, B]`. If you squint a bit, it's practically equivalent to a function `A => IO[B]` or `ReaderT[IO, A, B]`. At the same time, however, it can be substantially more performant. - -Other examples include using something like `cats-effect`' `Ref` for `MonadState` (a working instance [can be found here](https://github.com/oleg-py/meow-mtl)), or using a bifunctor `IO` that includes an extra type parameter for the error type, i.e. `BIO[E, A]` instead of using `EitherT[IO, E, A]` (a WIP for cats-effect [can be found here](https://github.com/LukaJCB/cats-bio)). - -In general, we can think up more performant solutions to our effect type class instances by using more specialized data structures. Monad Transformers are extremely general, which makes them very flexible, but that flexibility may come at a price. One of the great things about `mtl` is that we don't have to choose up front, but only at the very end when our program is run. -For example, we might choose to use only monad transformers at the begining when developing our application. Then, when we want to scale up, we can move to more performant instances simply by changing a few lines when materializing our programs. - -In the long term, I'd like to provide a submodule of `cats-mtl` that has very specialized and performant data types for every combination of effect type classes. -For this purpose, I've created [the cats-mtl-special library](https://github.com/LukaJCB/cats-mtl-special) some time ago, but it still remains very much a work in progress. -Shoutout also to Jamie Pullar who has been using cats-mtl extensively in production and has also built some more performant instances along with some benchmarks which you can find [as part of his talk here](https://www.slideshare.net/RyanAdams12/jamie-pullar-cats-mtl-in-action/39). diff --git a/src/blog/io-monad-for-cats.md b/src/blog/io-monad-for-cats.md deleted file mode 100644 index 6f3ad5cc..00000000 --- a/src/blog/io-monad-for-cats.md +++ /dev/null @@ -1,402 +0,0 @@ -{% - author: ${djspiewak} - date: "2017-05-02" - tags: [technical] -%} - -# An IO monad for cats - -Haskell is a pure language. Every Haskell expression is *referentially transparent*, meaning that you can substitute that expression with its evaluated result without changing the program. Or, put into code: - -```haskell --- this program -f expr expr -- apply function f to arguments expr, expr - --- is equivalent to this one, which factors out `expr` -let - x = expr -- introduce a new variable `x` with the value of `expr` -in - f x x -``` - -And this is true for *all* expressions `e`, and all functions `f`. These could be complex expressions which describe ways of manipulating network channels or window buffers, or something trivial like a numeric literal. You can *always* substitute the expression with its value. - -This is not true in Scala, simply because Scala allows *unrestricted* side-effects. Unlike Haskell, Scala puts no limitations on where and when we can use things like mutable state (`var`s) or evaluated external effects like `println` or `launchTheMissiles`. Since there are no restrictions on where and when we can do evil, the Scala equivalent to the above just doesn't work: - -```scala -f(e, e) -// isn't really equivalent to! -val x = e -f(x, x) -``` - -The reason it isn't equivalent comes from the different sorts of expressions that we could find in `e`. For example, what if `e` is `println("hi!")`. If we make that substitution, our snippet looks like the following: - -```scala -f(println("hi"), println("hi")) -// isn't really equivalent to! -val x = println("hi") -f(x, x) -``` - -Clearly these are not the same two programs. The first prints `"hi"` twice, while the second only prints it once. This is a violation of referential transparency, and it's why we sometimes say that Scala is an *impure* language. Any expression which is not referentially transparent must contain *side-effects*, by definition. - -Now of course, we found this problem by using a side-effecting function: namely, `println`. Haskell clearly has the ability to print to standard output, so how does it avoid this issue? If we build the same program in Haskell, can we violate referential transparency? - -```haskell -f (putStrLn "hi") (putStrLn "hi") --- is equivalent to -let x = putStrLn "hi" in f x x -``` - -As it turns out, this is still referentially transparent! These two programs still have the same meaning. This is possible only because *neither* program actually prints anything! - -In Haskell, effects are treated as first-class values. The `putStrLn` function doesn't print to standard out, it returns a value (of type `IO ()`) which describes *how* to print to standard out, but stops short of actually *doing* it. These sorts of values can be composed using the monadic operators (in Scala, `flatMap` and `pure`), allowing Haskell programmers to build up expressions composed of sequences of dependent effects, all of which are merely *descriptions* of the side-effects which will eventually be performed by the runtime. Ultimately, the description which comprises your *whole* program is the return result from the `main` function. The Haskell runtime runs the `main` function to get this description of all your effects, and then runs the effects per your instructions. - -This is kind of a clever trick. It allows Haskell to simultaneously be pure *and* still have excellent support for manipulating effects and interacting with the "real world". But why is it relevant to Scala? After all, Scala is an impure language. We don't *need* to go through this complex rigmarole of describing our effects and composing those descriptions; the language lets us *just do it!* So why wouldn't we just, you know, evaluate the effects that we need evaluated? - -The answer is that we want to reason about *where* and *when* our effects are evaluated. And of course, we want to be able to leverage laws and abstractions which assume equational semantics for expressions (i.e. referential transparency). Cats is full of these sorts of abstractions, and cats-laws provides a vast set of laws which describe them. But all of these abstractions and all of these laws break down the *moment* you introduce some sort of side-effecting expression. Because, much like our referential transparency example from earlier, these abstractions *assume* that you can substitute expressions with their evaluated results, and that's just not true in the presence of side-effects. - -What we need is a data type which allows us to encapsulate Scala-style side-effects in the form of a *pure* value, on which referential transparency holds and which we can compose using other well-defined abstractions, such as `Monad`. Scalaz defines two such data types which meet these criteria: `scalaz.effect.IO` and `scalaz.concurrent.Task`. But in practice, nearly everyone uses `Task` instead of `IO` because of its support for *asynchronous* effects. - -Cats does not define any such abstraction, and what's worse is the cats *ecosystem* also doesn't really provide any such abstraction. There are two `Task` implementations that are relatively commonly used with cats – namely, `monix.eval.Task` and `fs2.Task` – but these are not part of cats per se, nor are they deeply integrated into its abstraction hierarchy. Additionally, the proliferation of broadly equivalent options has led to confusion in the ecosystem, with middleware authors often forced to choose a solution for their end-users, and end-users uncertain as to which choice is "right". - -## Introducing cats-effect - -The [cats-effect](https://github.com/typelevel/cats-effect) project aims to change all of that. The goal of cats-effect is to provide an "easy default" `IO` type for the cats ecosystem, deeply integrated with cats-core, with all of the features and performance that are required for real world production use. Additionally, cats-effect defines a set of abstractions in the form of several typeclasses which describe what it means to *be* a pure effect type. These abstractions are extremely useful both in enabling MTL-style program composition and to ensure that other pre-existing `Task` implementations remain first-class citizens of the ecosystem. `IO` does not overshadow `monix.eval.Task` or `fs2.Task`; it *complements* them by providing a set of abstractions and laws which allow users to write safe, parametric code which supports each of them equally. - -One important sidebar here: cats-effect does *not* provide any concurrency primitives. `scalaz.concurrent.Task` and `monix.eval.Task` are both notable for providing functions such as `both`, which takes two `Task`s and runs them in parallel, returning a `Task` of a tuple of the results. The `cats.effect.IO` type does not provide any such function, and while it would be possible to define such a function (and others like it!), we strongly encourage users to instead consider full-on streaming frameworks such as **fs2** or **Monix** for their concurrency needs, as these frameworks are able to provide a much sounder foundation for such functions. See [here](https://gist.github.com/djspiewak/a775b73804c581f4028fea2e98482b3c) for a rough outline of why this is. Also note that some `Task` implementations, such as Monix's, can and do provide parallelism on a sound foundation by enriching their internal algebraic structures. Thus, `monix.eval.Task` is actually quite different from `cats.effect.IO`, despite having a similar core set of operations. - -## Enough Talk… - -What does this look like in practice? Well, ideally, as convenient as possible! Let's look at our println example: - -```scala -def putStrLn(line: String): IO[Unit] = - IO { println(line) } - -f(putStrLn("hi!"), putStrLn("hi!")) - -// is equivalent to - -val x = putStrLn("hi!") -f(x, x) -``` - -Great! We can write Haskell fanfic in Scala. 😛 - -The notable element here is the use of the `IO.apply` constructor to wrap the `println` effect in a *pure* `IO` value. This pattern can be applied to any side-effect. You can think of this sort of like an FFI that converts impure code (like `println`) into pure code (like `putStrLn`). The goal of this API was to be as simple and straightforward as possible. If you have a curly brace block of impure side-effecting code, you can wrap it in a composable and pure abstraction by just adding two characters: `IO`. You can wrap arbitrarily large or small blocks of code, potentially involving complex allocations, JNI calls, resource semantics, etc; but it is generally considered a best practice to wrap side-effects into the smallest composable units that make sense and do all of your sequentialization using `flatMap` and `for`-comprehensions. - -For example, here's a program that performs some simple user interaction in the shell: - -```scala -import cats.effect.IO - -val program = for { - _ <- IO { println("Welcome to Scala! What's your name?") } - name <- IO { Console.readLine } - _ <- IO { println(s"Well hello, $name!") } -} yield () -``` - -We could have just as easily written this program in the following way: - -```scala -val program = IO { - println("Welcome to Scala! What's your name?") - val name = Console.readLine - println(s"Well hello, $name!") -} -``` - -But this gives us less flexibility for composition. Remember that even though `program` is a pure and referentially transparent value, its *definition* is not, which is to say that `IO { expr }` is not the same as `val x = expr; IO { x }`. Anything inside the `IO {` … `}` block is not referentially transparent, and so should be treated with extreme care and suspicion. The less of our program we have inside these blocks, the better! - -As a sidebar that is actually kinda cool, we can implement a `readString` `IO` action that wraps `Console.readLine` *as a `val`!* - -```scala -val readString = IO { Console.readLine } -``` - -This is totally valid! We don't need to worry about the difference between `def` and `val` anymore, because `IO` is referentially transparent. So you use `def` when you need parameters, and you use `val` when you don't, and you don't have to think about evaluation semantics. No more subtle bugs caused by accidentally memoizing your effects! - -Of course, if `program` is referentially transparent, then clearly repeated values of `program` cannot possibly run the effects it represents multiple times. For example: - -```scala -program -program -program - -// must be the same as! - -program -``` - -If this weren't the case, then we would be in trouble when trying to construct examples like the Haskell one from earlier. But there is an implication here that is quite profound: `IO` cannot eagerly evaluate its effects, and similarly cannot memoize its results! If `IO` were to eagerly evaluate or to memoize, then we could no longer replace references to the expression with the expression itself, since that would result in a *different* `IO` instance to be evaluated separately. - -This is precisely why `scala.concurrent.Future` is *not* a suitable type for encapsulating effects in this way: constructing a `Future` that will eventually side-effect is itself a side-effect! `Future` evaluates eagerly (sort of, see below) and memoizes its results, meaning that a `println` inside of a given `Future` will only evaluate *once*, even if the `Future` is sequenced multiple times. This in turn means that `val x = Future(...); f(x, x)` is not the same program as `f(Future(...), Future(...))`, which is the very definition of a violation of referential transparency. - -Coming back to `IO`… If `program` does not evaluate eagerly, then clearly there must be some mechanism for asking it to evaluate. After all, Scala is not like Haskell: we don't return a value of type `IO[Unit]` from our `main` function. `IO` provides an FFI of sorts for wrapping side-effecting code into pure `IO` values, so it must also provide an FFI for going in the opposite direction: taking a pure `IO` value and evaluating its constituent actions as side-effects. - -```scala -program.unsafeRunSync() // uh oh! -``` - -This function is called `unsafeRunSync()`. Given an `IO[A]`, the `unsafeRunSync()` function will give you a value of type `A`. You should only call this function *once*, ideally at the very end of your program! (i.e. in your `main` function) Just as with `IO.apply`, any expression involving `unsafeRunSync()` is not referentially transparent. For example: - -```scala -program.unsafeRunSync() -program.unsafeRunSync() -``` - -The above will run `program` *twice*. So clearly, referential transparency is out the window whenever we do this, and we cannot expect the normal laws and abstractions to remain sound in the presence of this function. - -### A sidebar on `Future`'s eager evaluation - -As Viktor Klang is fond of pointing out, `Future` doesn't *need* to evaluate eagerly. It is possible to define an `ExecutionContext` in which `Future` defers its evaluation until some indefinitely later point. However, this is not the default mode of operation for 99% of all `Future`s ever constructed; most people just use `ExecutionContext.global` and leave it at that. Additionally, if someone hands me an arbitrary `Future`, perhaps as a return value from a function, I really have no idea whether or not that `Future` is secretly running without my consent. In other words, the referential transparency (or lack thereof) of functions that I write using `Future` is dependent on the runtime configuration of some other function which is hidden from me. That's not referential transparency anymore. Because we cannot be *certain* that `Future` is deferring its evaluation, we must defensively assume that it is not. - -This, in a nutshell, is precisely why `Future` is not appropriate for functional programming. `IO` provides a pair of functions (`fromFuture` and `unsafeToFuture`) for interacting with `Future`-using APIs, but in general, you should try to stick with `IO` as much as possible when manipulating effects. - -## Asynchrony and the JVM - -Scala runs on three platforms: the JVM, JavaScript and LLVM. For the moment, we'll just focus on the first two. The JVM has support for multiple threads, but those threads are *native* (i.e. kernel) threads, meaning that they are relatively expensive to create and maintain in the runtime. They are a very limited resource, sort of like file handles or heap space, and you can't just write programs which require an unbounded number of them. The exact upper bound on the JVM varies from platform to platform, and varies considerably depending on your GC configuration, but a general rule of thumb is "a few thousand", where "few" is a small number. In practice, you're going to want *far* less threads than that if you want to avoid thrashing your GC, and most applications will divide themselves into a bounded "main" thread pool (usually bounded to exactly the number of CPUs) on which all CPU-bound tasks are performed and most of the program runs, as well as a set of unbounded "blocking" thread pools on which blocking IO actions (such as anything in `java.io`) are run. When you add NIO worker pools into the mix, the final number of threads in a practical production service is usually around 30-40 on an 8 CPU machine, growing roughly linearly as you add CPUs. Clearly, this is not a very large number. - -On JavaScript runtimes (such as `node` or in the browser), the situation is even worse: you have exactly one thread! JavaScript simply doesn't have multi-threading in any (real) form, and so it's like the JVM situation, but 30-40x more constraining. - -For this reason, we need to be very careful when writing Scala to treat threads as an extremely scarce resource. *Blocking* threads (using mechanisms such as `wait`, `join` or `CountDownLatch`) should be considered absolutely anathema, since it selfishly wastes a very finite and very critical resource, leading to thread starvation and deadlocks. - -This is very different from how things are in Haskell though! The Haskell runtime is implemented around the concept of *green threads*, which is to say, *emulated* concurrency by means of a runtime dispatch lock. Haskell basically creates a global bounded thread pool in the runtime with the same number of threads as your machine has CPUs. On top of that pool, it runs dispatch trampolines that schedule and evict expression evaluation, effectively emulating an arbitrarily large number of "fake" threads atop a small fixed set of "real" threads. So when you write code in Haskell, you generally just assume that threads are extremely cheap and you can have as many of them as you want. Under these circumstances, blocking a thread is not really a big deal (as long as you don't do it in FFI native code), so there's no reason to go out of your way to avoid it in abstractions like `IO`. - -This presents a bit of a dilemma for cats-effect: we want to provide a *practical* pure abstraction for encapsulating effects, but we need to run on the JVM and on JavaScript which means we need to provide a way to avoid thread blocking. So, the `IO` implementation in cats-effect is going to *necessarily* end up looking very, very different from the one in Haskell, providing a very different set of operations. - -Specifically, `cats.effect.IO` provides an additional constructor, `async`, which allows the construction of `IO` instances from callback-driven APIs. This is generally referred to as "asynchronous" control flow, as opposed to "synchronous" control flow (represented by the `apply` constructor). To see how this works, we're going to need a bit of setup. - -Consider the following somewhat-realistic NIO API (translated to Scala): - -```scala -trait Response[T] { - def onError(t: Throwable): Unit - def onSuccess(t: T): Unit -} -// defined trait Response - -trait Channel { - def sendBytes(chunk: Array[Byte], handler: Response[Unit]): Unit - def receiveBytes(handler: Response[Array[Byte]]): Unit -} -// defined trait Channel -``` - -This is an asynchronous API. Neither of the functions `sendBytes` or `receiveBytes` attempt to block on completion. Instead, they *schedule* their operations via some underlying mechanism. This interface could be implemented on top of `java.io` (which is a synchronous API) through the use of an internal thread pool, but most NIO implementations are actually going to delegate their scheduling all the way down to the kernel layer, avoiding the consumption of a precious thread while waiting for the underlying IO – which, in the case of network sockets, may be a very long wait indeed! - -Wrapping this sort of API in a referentially transparent and uniform fashion is a very important feature of `IO`, *precisely* because of Scala's underlying platform constraints. Clearly, `sendBytes` and `receiveBytes` both represent side-effects, but they're different than `println` and `readLine` in that they don't produce their results in a sequentially returned value. Instead, they take a callback, `Response`, which will eventually be notified (likely on some other thread!) when the result is available. The `IO.async` constructor is designed for precisely these situations: - -```scala -def send(c: Channel, chunk: Array[Byte]): IO[Unit] = { - IO async { cb => - c.sendBytes(chunk, new Response[Unit] { - def onError(t: Throwable) = cb(Left(t)) - def onSuccess(v: Unit) = cb(Right(())) - }) - } -} -// send: (c: Channel, chunk: Array[Byte])cats.effect.IO[Unit] - -def receive(c: Channel): IO[Array[Byte]] = { - IO async { cb => - c.receiveBytes(new Response[Array[Byte]] { - def onError(t: Throwable) = cb(Left(t)) - def onSuccess(chunk: Array[Byte]) = cb(Right(chunk)) - }) - } -} -// receive: (c: Channel)cats.effect.IO[Array[Byte]] -``` - -Obviously, this is a little more daunting than the `println` examples from earlier, but that's mostly the fault of the anonymous inner class syntactic ceremony. The `IO` interaction is actually quite simple! - -The `async` constructor takes a function which is handed a *callback* (represented above by `cb` in both cases). This callback is *itself* a function of type `Either[Throwable, A] => Unit`, where `A` is the type produced by the `IO`. So when our `Response` comes back as `onSuccess` in the `send` example, we invoke the callback with a `Right(())` since we're trying to produce an `IO[Unit]`. When the `Response` comes back as `onSuccess` in the `receive` example, we invoke the callback with `Right(chunk)`, since the `IO` produces an `Array[Byte]`. - -Now remember, `IO` is still a monad, and `IO` values constructed with `async` are perfectly capable of all of the things that "normal", synchronous `IO` values are, which means that you can use these values inside `for`-comprehensions and other conventional composition! This is incredibly, unbelievably nice in practice, because it takes your complex, nested, callback-driven code and flattens it into simple, easy-to-read sequential composition. For example: - -```scala -val c: Channel = null // pretend this is an actual channel - -for { - _ <- send(c, "SYN".getBytes) - response <- receive(c) - - _ <- if (response == "ACK".getBytes) // pretend == works on Array[Byte] - IO { println("found the guy!") } - else - IO { println("no idea what happened, but it wasn't good") } -} yield () -``` - -This is kind of amazing. There's no thread blocking at all in the above (other than the `println` blocking on standard output). The `receive` could take quite a long time to come back to us, and our thread is free to do other things in the interim. Everything is driven by callbacks under the surface, and asynchronous actions can be manipulated just as easily as synchronous ones. - -Of course, this is an even bigger win on JavaScript, where nearly everything is callback-based, and gigantic, deeply nested chunks of code are not unusual. `IO` allows you to flatten those deeply nested chunks of code into a nice, clean, linear and sequential formulation. - -## Thread Shifting - -Now there is a caveat here. When our `Response` handler is invoked by `Channel`, it is very likely that the callback will be run on a thread which is part of a different thread pool than our main program. Remember from earlier where I described how *most* well-designed Java services are organized: - -- A bounded thread pool set to *num CPUs* in size for any non-IO actions -- A set of unbounded thread pools for blocking IO -- Some bounded internal thread worker pools for NIO polling - -We definitely want to run nearly everything on that first pool (which is probably `ExecutionContext.global`), but we're probably going to receive the `Response` callback on one of the third pools. So how can we force the rest of our program (including those `println`s) back onto the main pool? - -The answer is the `shift` function. - -```scala -import scala.concurrent._ -implicit val ec = ExecutionContext.global - -for { - _ <- send(c, "SYN".getBytes) - response <- receive(c).shift // there's no place like home! - - _ <- if (response == "ACK".getBytes) // pretend == works on Array[Byte] - IO { println("found the guy!") } - else - IO { println("no idea what happened, but it wasn't good") } -} yield () -``` - -`shift`'s functionality is a little complicated, but generally speaking, you should think of it as a "force this `IO` onto this *other* thread pool" function. Of course, when `receive` executes, most of its work isn't done on any thread at all (since it is simply registering a hook with the kernel), and so that work isn't thread shifted to any pool, main or otherwise. But when `receive` gets back to us with the network response, the callback will be handled and then *immediately* thread-shifted back onto the main pool, which is passed implicitly as a parameter to `shift` (you can also pass this explicitly if you like). This thread-shifting means that all of the subsequent actions within the `for`-comprehension – which is to say, the *continuation* of `receive(c)` – will be run on the `ec` thread pool, rather than whatever worker pool is used internally by `Channel`. This is an *extremely* common use-case in practice, and `IO` attempts to make it as straightforward as possible. - -Another possible application of thread shifting is ensuring that a blocking `IO` action is relocated from the main, CPU-bound thread pool onto one of the pools designated for blocking IO. An example of this would be any interaction with `java.io`: - -```scala -import java.io.{BufferedReader, FileReader} -// import java.io.{BufferedReader, FileReader} - -def readLines(name: String): IO[Vector[String]] = IO { - val reader = new BufferedReader(new FileReader(name)) - var back: Vector[String] = Vector.empty - - try { - var line: String = null - do { - line = reader.readLine() - back :+ line - } while (line != null) - } finally { - reader.close() - } - - back -} -// readLines: (name: String)cats.effect.IO[Vector[String]] -``` - -```scala -for { - _ <- IO { println("Name, pls.") } - name <- IO { Console.readLine } - lines <- readLines("names.txt") - - _ <- if (lines.contains(name)) - IO { println("You're on the list, boss.") } - else - IO { println("Get outa here!") } -} yield () -``` - -Clearly, `readLines` is blocking the underlying thread while it waits for the disk to return the file contents to us, and for a large file, we might be blocking the thread for quite a long time! Now if we're treating our thread pools with respect (as described above), then we probably have a pair of `ExecutionContext`(s) sitting around in our code somewhere: - -```scala -import java.util.concurrent.Executors - -implicit val Main = ExecutionContext.global -val BlockingFileIO = ExecutionContext.fromExecutor(Executors.newCachedThreadPool()) -``` - -We want to ensure that `readLines` runs on the `BlockingFileIO` pool, while everything else in the `for`-comprehension runs on `Main`. How can we achieve this? - -With `shift`! - -```scala -for { - _ <- IO { println("Name, pls.") } - name <- IO { Console.readLine } - lines <- readLines("names.txt").shift(BlockingFileIO).shift(Main) - - _ <- if (lines.contains(name)) - IO { println("You're on the list, boss.") } - else - IO { println("Get outa here!") } -} yield () -``` - -Now we're definitely in bizarro land. *Two* calls to `shift`, one after the other? Let's break this apart: - -```scala -readLines("names.txt").shift(BlockingFileIO) -``` - -One of the functions of `shift` is to take the `IO` action it is given and relocate that action onto the given thread pool. In the case of `receive`, this component of `shift` was meaningless since `receive` didn't use a thread under the surface (it was asynchronous!). However, `readLines` *does* use a thread under the surface (hint: it was constructed with `IO.apply` rather than `IO.async`), and so that work will be relocated onto the `BlockingFileIO` pool by the above expression. - -*Additionally*, the continuation of this work will also be relocated onto the `BlockingFileIO` pool, and that's definitely not what we want. The evaluation of the `contains` function is definitely CPU-bound, and should be run on the `Main` pool. So we need to `shift` a second time, but only the *continuation* of the `readLines` action, not `readLines` itself. As it turns out, we can achieve this just by adding the second `shift` call: - -```scala -readLines("names.txt").shift(BlockingFileIO).shift(Main) -``` - -Now, `readLines` will be run on the `BlockingFileIO` pool, but the *continuation* of `readLines` (namely, everything that follows it in the `for`-comprehension) will be run on `Main`. This works because `shift` creates an asynchronous `IO` that schedules the target action on the given thread pool and invokes its continuation *from a callback*. The [ExecutionContext#execute](http://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext.html#execute(runnable:Runnable):Unit) function should give you an idea of how this works. This means that the result of the first `shift` is an `IO` constructed with `async`, and cannot *itself* be thread-shifted (unlike an `IO` constructed with `apply`), but its continuation *can* be thread-shifted, which is exactly what happens. - -This sort of double-`shift` idiom is very common in production service code that makes use of legacy blocking IO libraries such as `java.io`. - -### Synchronous vs Asynchronous Execution - -Speaking of asynchrony, readers who have been looking ahead in the class syllabus probably realized that the type signature of `unsafeRunSync()` is more than a little suspicious. Specifically, it promises to give us an `A` *immediately* given an `IO[A]`; but if that `IO[A]` is an asynchronous action invoked with a callback, how can it achieve this promise? - -The answer is that it blocks a thread. (*gasp!!!*) Under the surface, a `CountDownLatch` is used to block the calling thread whenever an `IO` is encountered that was constructed with `IO.async`. Functionally, this is very similar to the `Await.result` function in `scala.concurrent`, and it is just as dangerous. Additionally, it clearly cannot possibly work on JavaScript, since you only have one thread to block! If you try to call `unsafeRunSync()` on JavaScript with an underlying `IO.async`, it will just throw an exception rather than deadlock your application. - -This is not such a great state of affairs. I mean, it *works* if `unsafeRunSync()` is being run in test code, or as the last line of your `main` function, but sometimes we need to interact with legacy code or with Java APIs that weren't designed for purity. Sometimes, we just *have* to evaluate our `IO` actions before "the end of the world", and when we do that, we don't want to block any of our precious threads. - -So `IO` provides an additional function: `unsafeRunAsync`. This function takes a callback (of type `Either[Throwable, A] => Unit`) which it will run when (and if) the `IO[A]` completes its execution. As the name implies, this function is *also* not referentially transparent, but unlike `unsafeRunSync()`, it will not block a thread. - -As a sidebar that will be important in a few paragraphs, `IO` also defines a *safe* function called `runAsync` which has a very similar signature to `unsafeRunAsync`, except it returns an `IO[Unit]`. The `IO[Unit]` which is returned from this function *will not block* if you call `unsafeRunAsync()`. In other words, it is always safe to call `unsafeRunSync()` on the results of `runAsync`, even on JavaScript. - -Another way to look at this is in terms of `unsafeRunAsync`. You can define `unsafeRunAsync` in terms of `runAsync` and `unsafeRunSync()`: - -```scala -def unsafeRunAsync[A](ioa: IO[A])(cb: Either[Throwable, A] => Unit): Unit = - ioa.runAsync(e => IO { cb(e) }).unsafeRunSync() -// unsafeRunAsync: [A](ioa: cats.effect.IO[A])(cb: Either[Throwable,A] => Unit)Unit -``` - -This isn't the actual definition, but it would be a valid one, and it would run correctly on every platform. - -## Abstraction and Lawfulness - -As mentioned earlier (about 10000 words ago…), the cats-effect project not only provides a concrete `IO` type with a lot of nice features, it also provides a set of abstractions characterized by typeclasses and associated laws. These abstractions collectively define what it means to be a type which encapsulates side-effects in a pure fashion, and they are implemented by `IO` as well as several other types (including `fs2.Task` and `monix.eval.Task`). The hierarchy looks like this: - -![cats-effect typeclasses](/img/media/cats-effect-diagram.png) - -`Monad` and `MonadError` are of course a part of cats-core, while everything else is in cats-effect. `MonadError` is functionally equivalent to the familiar `scalaz.Catchable` typeclass, which was commonly used in conjunction with `scalaz.concurrent.Task`. It literally means "a monad with error-handling capabilities". `IO` certainly fits that description, as any exceptions thrown within its `apply` method (or within `async`) will be caught and may be handled in pure code by means of the `attempt` function. `Sync`, `Async`, `LiftIO` and `Effect` are the new typeclasses. - -`Sync` simply describes the `IO.apply` function (in the typeclasses, this function is called `delay`). Which is to say, any type constructor `F[_]` which has a `Sync[F]` has the capability to suspend *synchronous* side-effecting code. `Async` is very similar to this in that it describes the `async` function. So any type constructor `F[_]` which has an `Async[F]` can suspend *asynchronous* side-effecting code. `LiftIO` should be familiar to Haskell veterans, and is broadly useful for defining parametric signatures and composing monad transformer stacks. - -`Effect` is where everything is brought together. In addition to being able to suspend synchronous and asynchronous side-effecting code, anything that has an `Effect` instance may also be *asynchronously interpreted* into an `IO`. The way this is specified is using the `runAsync` function: - -```scala -import cats.effect.{Async, LiftIO, Sync} - -trait Effect[F[_]] extends Sync[F] with Async[F] with LiftIO[F] { - def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): IO[Unit] -} -``` - -What this is saying is that any `Effect` must define the ability to evaluate as a side-effect, but of course, we don't want to have side-effects in our pure and reasonable code. So how are side-effects *purely* represented? With `IO`! - -From a parametric reasoning standpoint, `IO` means "here be effects", and so any type signature which involves `IO` thus also involves side-effects (well, *effects* anyway), and any type signature which requires side-effects must also involve `IO`. This bit of trickery allows us to reason about `Effect` in a way that would have been much harder if we had defined `unsafeRunAsync` as a member, and it ensures that downstream projects which write code abstracting over `Effect` types can do so without using any `unsafe` functions if they so choose (especially when taken together with the `liftIO` function). - -## Conclusion - -The lack of a production-ready `Task`-like type fully integrated into the cats ecosystem has been a sticking point for a lot of people considering adopting cats. With the introduction of [cats-effect](https://github.com/typelevel/cats-effect), this should no longer be a problem! As of right now, the only releases are snapshots with hash-based versions, the latest of which can be found in the maven badge at the top of the readme. These snapshots are stable versions (in the repeatable-build sense), but they should not be considered stable, production-ready, future-proof software. We are quickly moving towards a final 0.1 release, which will depend on cats-core and will represent the stable, finalized API. - -Once cats releases a final 1.0 version, cats-effect will also release version 1.0 which will depend on the corresponding version of cats-core. Changes to cats-effect are expected to be extremely rare, and thus the dependency should be considered quite stable for the purposes of upstream compatibility. Nevertheless, the release and versioning cycle is decoupled from cats-core to account for the possibility that breaking changes may need to be made independent of the cats-core release cycle. - -Check out the sources! Check out the documentation. Play around with the snapshots, and let us know what you think! Now is the time to make your opinion heard. If `IO` in its current form doesn't meet your needs, we want to hear about it! diff --git a/src/blog/jdg.md b/src/blog/jdg.md deleted file mode 100644 index c65d62db..00000000 --- a/src/blog/jdg.md +++ /dev/null @@ -1,15 +0,0 @@ -{% - author: ${typelevel} - date: "2019-09-05" - tags: [governance] -%} - -# Contributors and Community - -Effective today, John De Goes has been indefinitely barred from participation in Typelevel projects. This most directly impacts Cats Effect, but applies to our other repositories as well. The cause is John's combative style of interaction in Typelevel channels. His interactions when in agreement are always cordial, but when he disagrees with something or someone, the results are inevitably drawn out, intensely aggressive, and stressful. We have tried for the past three years, via one-on-one discussions and multiple warnings, to arrive at a style of respectful collaboration that we can all live with. These attempts have consistently failed, despite considerable time-consuming effort. - -Our overriding goal is the well-being of our contributors. Too much of their energy and enthusiasm is being drained away by these conflicts, and we're concerned about the potential chilling effect on new contributors as well. While we appreciate John's technical insight and expertise and the time he devotes to sharing those things, neither outweigh the well-being of our contributors and community. - -We are very much aware that, particularly with actions such as this, a lot of questions and concerns naturally arise. We want to be as open and as transparent as we can be, and we invite anyone who has concerns to engage with us in the [Typelevel Admin](https://gitter.im/typelevel/admin) channel on Gitter. If you prefer private correspondence for any reason, please feel free to directly contact the Typelevel leadership directly at info@typelevel.org. - -*While we are aware that the timing of this announcement is likely to be conflated with the recent action by Skills Matter, that timing is completely coincidental. This action by Typelevel has been independently in process for a considerable period of time.* diff --git a/src/blog/join-tsc-2026.md b/src/blog/join-tsc-2026.md deleted file mode 100644 index 6fc82e4e..00000000 --- a/src/blog/join-tsc-2026.md +++ /dev/null @@ -1,38 +0,0 @@ -{% - author: ${foundation} - date: "2026-04-02" - tags: [governance] -%} - -# Join the Technical Steering Committee - -Last December, we [established the Technical Steering Committee][TSC] (TSC) as an advisory committee to the [Typelevel Foundation]. We are passionate about making programming joyful and social, and our aspiration for this new committee is to bring together a diverse group of contributors, users, and enthusiasts to build camaraderie and collaborate to develop a collective vision for Typelevel and our technology. - -The TSC will meet monthly and is broadly tasked with helping to steward Typelevel's projects. This may look like many things, such as: - -* sharing your experiences, to grow our organizational knowledge -* providing early feedback on new developments -* maintaining and documenting our libraries and infrastructure -* curating our portfolio of Organization and Affiliate projects -* developing an AI policy for contributions to our repositories -* advising the Typelevel Foundation on its technical roadmap -* getting nerd-sniped with friends and brainstorming new ideas! -* being a cheerleader for our community :) - -As a member of the TSC, you certainly do not have to do all of these, but you are excited and eager to play to your strengths and help where you can. Moreover, as part of Typelevel's leadership, you will exemplify the values in our [Code of Conduct]. - -We invited the former Steering Committee to serve as the founding members of the TSC. **Today, we are welcoming additional members to join the team.** This is a special opportunity to help grow an organization that is important to you and so many people in a collaborative and social setting. If you are active on GitHub or Discord, use Typelevel projects for business or pleasure, teach functional programming, and/or build community, please consider applying. And this is by no means an exhaustive list of what a great candidate may look like! - -## Applying - -To apply, please [submit your name][application], optionally with a brief statement about why you wish to serve on the TSC, by Monday, April 20. Terms are two years long, with the opportunity to renew. - -Applications are visible only to the [current committee membership][membership]. We will thoughtfully consider all serious applications, deliberate in private, and not disclose the identities of applicants. - -We look forward to reading applications from familiar names and also hope to hear from new faces with fresh perspectives whom we have not yet had the pleasure of meeting. We are not looking for third-party nominations, but if there is someone you would like to see, encourage them to apply! - -[TSC]: https://github.com/typelevel/foundation/pull/4 -[Typelevel Foundation]: /foundation/README.md -[Code of Conduct]: /code-of-conduct/README.md -[membership]: /foundation/people.md -[application]: https://docs.google.com/forms/d/e/1FAIpQLSdE-d0YQuQWhh-QAmAXZSkvWhCfaXyAX4gG4lF8tNJ2v2vOJg/viewform diff --git a/src/blog/lake-district-workshop-2016-09-14.md b/src/blog/lake-district-workshop-2016-09-14.md deleted file mode 100644 index cbad7076..00000000 --- a/src/blog/lake-district-workshop-2016-09-14.md +++ /dev/null @@ -1,70 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-09-14" - event-date: "September 14, 2016" - event-location: "Rheged Centre, Penrith" - tags: [events] -%} - -# Typelevel Workshop in the Lake District - -@:include(/img/places/lakedistrict.md) - -## About the Workshop - -A day of talks & unconference, co-located with [Scala World](https://scala.world). - -The full set of sessions and topics will be decided by the participants on the day of the workshop. To give -us some context, and provide introductions for possible sessions on the day the following attendees have -kindly offered to do short informal presentations at the beginning of the day, - -#### Dave Gurnell — State of the Mewnion - -In this talk Dave will introduce the wonderful State monad: what it is, how it works, and how to use it to publish -books and compose songs using cat noises. - -#### Rüdiger Klaehn — abc - -Rüdiger will introduce [abc](https://github.com/rklaehn/abc), an experiment in writing a tiny collections library that -is more friendly to type classes. He will talk about the library itself and also about the projects structure and pain -points when using type classes in scala. We can dig deeper during the unconference. - -#### Eda Meadows — Monadic Wack-A-Mole - -Working with code structured as monads can start out looking beautifully straightforward but as things develop you -find yourself with for/yields that don't compile as you have a random type that doesn't match up. Eda will sketch out -some ideas on how to deal with this which we can explore during the unconference. - -#### Miles Sabin — Typelevel Scala rebooted - -Following on from the fix for SI-2712 the Typelevel Scala project has been reinvigorated. Miles is going to give a -quick overview of what Typelevel Scala adds to Lightbend Scala and set the scene for unconference sessions on using it -in your projects today and contributing to Typelevel and Lightbend Scala development. - -#### Paweł Szulc — The Cats toolbox: a quick tour of some basic typeclasses - -It's happened to all of us: we ran away from some conversation or library because it kept on using those "weird" -phrases. You know, like "type classes", "semigroups", "monoids", "applicatives". Yikes! They all seem so academic, -so pointlessly detached from real-world problems. But then again, given how frequently we run into them in functional -programming, are they REALLY irrelevant, or do they have real-world applications? Paweł will start helping us make -sense of the gobbledygook and we can continue in a later session. - -#### Pere Villega — What happens when you use Free Monads - -There's a lot of talk about Free Monads and how awesome they are. But, what happens when we try to implement a real -application using Free? Pere will show ane example using [Freek](https://github.com/ProjectSeptemberInc/freek/) and -the [StockFighter API](https://starfighter.readme.io/docs/) and highlight some of the choices and limitations -involved. Later, during the unconference, we can experiment with Free Monads to see their impact in everyday concerns -like testing, program design, etc. - -#### Dick Wall — Ensime Sublime - -Ensime isn't just for Emacs! Dick Wall will give us a taste of what's been happening in the Ensime project to support -users of the Sublime Text editor and encourage you all to hack on Ensime later. - - -## Venue - -This event took place at the Rheged Centre, situated near Penrith in the Lake District National Park, UK. - - diff --git a/src/blog/libra.md b/src/blog/libra.md deleted file mode 100644 index fb4fea69..00000000 --- a/src/blog/libra.md +++ /dev/null @@ -1,515 +0,0 @@ -{% - author: ${zainabali} - date: "2017-06-13" - tags: [technical] - katex: true -%} - -# Compile time dimensional analysis with Libra - -## Dimensional analysis - -When we code, we code in numbers - doubles, floats and ints. Those numbers always represent real world quantities. - -For example, the number of people in a room can be represented as an integer, as can the number of chairs. -Adding people and chairs together gives a nonsensical result, but dividing the number of people by the number of chairs gives a useful indicator of how full up the room is. - -```scala -val numberOfPeople = 9 -val numberOfChairs = 10 -numberOfPeople + numberOfChairs // this is a bug -numberOfPeople.toDouble / numberOfChairs.toDouble // this is useful -``` - -This is actually a form of dimensional analysis. We're mentally assigning the dimension `Person` to the quantity of people, and `Chair` to the quantity of chairs. Dimensional analysis can be summarized in two laws. - -1. Quantities can only be added or subtracted to quantities of the same dimension -2. Quantities of different dimensions can be multiplied or divided - -### Why is it important? - -Ignoring the laws can result in serious problems. -Take the Mars Climate Orbiter, a $200 million space probe which successfully reached Mars after a year long voyage, but suddenly crashed into the Martian atmosphere on arrival. Most components on the orbiter were using metric units, however a single component was sending instructions in Imperial units. The other components did not detect this, and instead began a sudden descent causing the orbiter to burn up. This was a simple unit conversion error! It was a basic mistake that could have been easily avoided. It should have been picked up during testing, or in the runtime validation layer. - -In fact, it could even have been caught at compile time. - -### Compile time dimensional analysis - -We're going to use a similar problem to demonstrate compile time dimensional analysis. -To fit with the theme of rocket physics, we will tackle a rocket launch towards the distant constellation of Libra. -We'll begin by working through our calculation in doubles before adding compile time safety with dependent types and finally supporting compile time dimensional analysis with typeclass induction. - -## Destination: Alpha Librae - -The star that we're aiming for is Alpha Librae. This is pretty far, so we can only send one very small person. We have been given the following quantities to work with: - -- rocket mass of a small person - @:math 40 \text{kg} @:@ -- fuel mass of a lot of fuel - @:math 10^4 \text{kg} @:@ -- exhaust speed of a decent fuel - @:math 10^6 \text{ms}^{-1} @:@ -- distance to Alpha Librae - @:math 77 \text{ly} @:@ - -We want to calculate when the rocket will arrive. - -To do so, we're going to make use of a formula known as the *Ideal Rocket Equation*. -This calculates the speed of a rocket in ideal conditions. - -```scala -val rocketSpeed = exhaustSpeed * log((rocketMass + fuelMass) / rocketMass) -``` -Once we have the speed, we can work out the travel time. - -```scala -val time = distance / rocketSpeed -``` - -### Plugging the numbers in - -Let's do what we're used to doing and use doubles: - - - - -```scala -val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0) -// rocketSpeed: Double = 5525452.939131783 - -val time = 77.0 / rocketSpeed -// time: Double = 1.39355091515989E-5 -``` - -Fantastic! We can get to Libra in less than a day! - -Unfortunately, this time estimate is too far off to be valid. We can't get to Libra that quickly at light speed, let alone rocket speed. We've clearly made a mistake somewhere. Instead of pouring over our code to find out where that is, let's try and use the compiler. - -### Using types - -We can add some type safety to this problem by using a case class to represent each quantity. - -```scala -case class Quantity[A](value: Double) -``` - -`A` represents the quantity dimension. So given the following dimensions: - - - - -```scala -type Kilogram -type Metre -type Second -type MetresPerSecond -type C -type LightYear -type Year -``` - -We can create quantities: - -```scala -val rocketMass = Quantity[Kilogram](40.0) -val fuelMass = Quantity[Kilogram](10000.0) -val exhaust = Quantity[MetresPerSecond](1000000.0) -val distance = Quantity[LightYear](77.0) -``` - -It's important to note that these are *types*, not classes. We never instantiate a `MetresPerSecond` - we're just using it to differentiate between `Quantity[MetresPerSecond]` and `Quantity[Year]` at the type level. - -So how does this change the code? - -```scala -val rocketSpeed = Quantity[MetresPerSecond](exhaust.value * log((rocketMass.value + fuelMass.value) / rocketMass.value)) -// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783) - -val time = Quantity[Year](distance.value / rocketSpeed.value) -// time: Quantity[Types.Year] = Quantity(1.39355091515989E-5) -``` - -In short, it doesn't. The code might be clearer, but we don't know what the bug is. This is because the compiler isn't doing anything with the types we've added. - -### Operating on quantities - -We can encode our first law of addition at compile time by creating a function to add quantities: - -```scala -def add[A](q0: Quantity[A], q1: Quantity[A]): Quantity[A] = Quantity(q0.value + q1.value) -``` - -This ensures that quantities can only be added to other quantities of the same type. Trying to add quantities of different types will result in a compilation error. - -A quantity can also be multiplied by a dimensionless scalar value to give a quantity of the same dimension. - -```scala -def times[A](q: Quantity[A], v: Double): Quantity[A] = Quantity(q.value * v) -``` - -It would be great if we could divide quantities too. Writing a divide function is more difficult: - -```scala -def divide[A, B, Magic](q0: Quantity[A], q1: Quantity[B]): Quantity[Magic] = - Quantity[Magic](q0.value / q1.value) -``` - -There's a clear problem with trying to do this. When we divide a quantity by another, we don't know what the `Magic` output type should be. -The output type is dependent on what the input types are (for example, dividing `Metre` by `Second` should give `MetresPerSecond`). The compiler needs a way of working out what the output is, provided that it knows the input types. - -### Dependent types - -What we actually want is a dependent type. A division operation should occur at the type level, taking two input types and supplying a dependent output type. -We can create the trait `Divide` with a dependent output type: - -```scala -trait Divide[A, B] { - type Out -} -``` - -We also need to define an `Aux` type alias. This is known as the Aux pattern and makes it easier to refer to all three types at once. - -```scala -object Divide { - type Aux[A, B, Out0] = Divide[A, B] { type Out = Out0 } -} -``` - -We can create instances of this divide typeclass with different output types, so the output type is dependent on the value of the divide typeclass instance. - -When dividing, the compiler looks for this implicit typeclass instance and returns a quantity corresponding to the output type. - -```scala -def divide[A, B](q0: Quantity[A], q1: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] = - Quantity[d.Out](q0.value / q1.value) -``` - -So given that we want to divide `A` by `B`, the compiler will look for a value of `Divide[A, B]` and find the `Out` type of it. If no instance exists, the code doesn't compile. - -We'll need some more types to represent the result of a division: - - - - -```scala -type LightYearSecondsPerMetre -type MetresPerSecondPerC -type Dimensionless -``` - -And we'll need to write instances for all combinations of dimensions. - -```scala -implicit val kgDivideKg: Divide.Aux[Kilogram, Kilogram, Dimensionless] = - new Divide[Kilogram, Kilogram] { type Out = Dimensionless } - -implicit val lyDivideC: Divide.Aux[LightYear, C, Year] = - new Divide[LightYear, C] { type Out = Year } - -implicit val lyDivideMps: Divide.Aux[LightYear, MetresPerSecond, LightYearSecondsPerMetre] = - new Divide[LightYear, MetresPerSecond] { type Out = LightYearSecondsPerMetre } - -implicit val mpsDivideC: Divide.Aux[MetresPerSecond, C, MetresPerSecondPerC] = - new Divide[MetresPerSecond, C] { type Out = MetresPerSecondPerC } - -implicit val mpsDivideMpsPerC: Divide.Aux[MetresPerSecond, MetresPerSecondPerC, C] = - new Divide[MetresPerSecond, MetresPerSecondPerC] { type Out = C } -``` - -And so on. - -Unfortunately, there are an infinite number of combinations, so there are an infinite number of instances. -Nevertheless, let's plough on with the ones we've written. We can modify our rocket equation to use `add`, `times` and `divide`: - - -```scala -val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value)) -// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783) - -val time: Quantity[Year] = divide(distance, rocketSpeed) -// :31: error: type mismatch; -// found : Quantity[lyDivideMps.Out] -// (which expands to) Quantity[MoreTypes.LightYearSecondsPerMetre] -// required: Quantity[Types.Year] -// val time: Quantity[Year] = divide(distance, rocketSpeed) -// ^ -``` - -Great! We've caught our bug! The result was in `LightYearSecondsPerMetre`, not `Year`. We made a unit conversion error, just like the Mars orbiter. - -We can now fix this by adding a conversion: - -```scala -val metresPerSecondPerC: Quantity[MetresPerSecondPerC] = divide(Quantity[MetresPerSecond](300000000.0), Quantity[C](1.0)) -// metresPerSecondPerC: Quantity[MoreTypes.MetresPerSecondPerC] = Quantity(3.0E8) - -val speedInC = divide(rocketSpeed, metresPerSecondPerC) -// speedInC: Quantity[mpsDivideMpsPerC.Out] = Quantity(0.018418176463772612) - -val time: Quantity[Year] = divide(distance, speedInC) -// time: Quantity[Types.Year] = Quantity(4180.65274547967) -``` - -It seems like it's going to take a lot longer than we hoped to get to Libra. Perhaps it's unwise to send a person. - -## Automatic derivation - -We found the bug, but we needed to explicitly write out typeclass instances for every combination of dimensions. -This might have worked for our small problem, but it just doesn't scale in the long run. -We need to figure out a way of deriving the typeclass instances automatically. -To attempt this, we first need to generalize what a combination of dimensions actually is. - -### Representing dimensions - -We can represent a combination of dimensions as a heterogeneous list (HList) of base dimensions. HLists are defined in [shapeless](https://github.com/milessabin/shapeless), a cornerstone of most functional libraries, and can be thought of as a type level list. - -```scala -type LightYearSeconds = LightYear :: Second :: HNil -``` - -This is good for multiples of dimensions, such as `LightYearSeconds`, but doesn't represent combinations created from division, such as `MetresPerSecond`. -To do this, we need some way of representing integer exponents as types. We can represent integers as types using Singleton types. We actually need these singleton types in type position. This is supported by a new feature present in [Typelevel Scala](https://typelevel.org/scala/), called [literal types](http://docs.scala-lang.org/sips/pending/42.type.html): - -```scala -scalaOrganization := "org.typelevel" -scalacOptions += "-Yliteral-types" -``` - -We need to represent a key value pair of dimension and integer exponent. We could use a `Tuple` for this, but will use a shapeless `FieldType` instead. This is similar to a `Tuple`, but is more compatible with some of shapeless's typeclasses. - -```scala -type MetresPerSecond = FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil -``` - -It's important to note that the number `1` above is a type, not a value. Because it's a type, the compiler can work with it. - -### Operations on Singleton types - -When we multiply and divide dimensions, we want to add or subtract from these exponents. - -We can use a library called [singleton ops]() to do this. This provides us with type level integer operations using the `OpInt` typeclass: - -```scala -OpInt.Aux[1 + 2, 3] -OpInt.Aux[3 * 2, 6] -``` -It also provides a convenient alias for integer singleton types - -```scala -type XInt = Singleton with Int -``` - -The type `1`, for example, is a subtype of `XInt`. - -### Deriving typeclass instances - -We now need to automatically derive typeclass instances of `Divide`. -To do this, we're going to derive instances for `Invert` and `Multiply` operations first. -Deriving `Divide` then becomes much simpler. - -The technique we're going to use to automatically derive instances is known as typeclass induction. - -### Typeclass Induction - -Aaron Levin gave a great introduction to induction in his talk earlier at the [Typelevel Summit](https://typelevel.org/event/2017-06-summit-copenhagen/). In summary, you can derive an implicit typeclass instance for all cases by: - -1. Providing it for the base case -2. Providing it for the n + 1 case, given that the n case is provided - -This is similar to the mathematical method of proof by induction. - -### Invert - -We're first going to derive inductive typeclass instances for the `Invert` operation. -Inverting a quantity raises it to the exponent of `-1`. This means that the exponents of all dimensions must be negated. - -For example, the inverse of `FieldType[Metre, 1] :: HNil` is `FieldType[Metre, -1] :: HNil`. -`Invert` takes one input type and returns one output type: - - - - -```scala -trait Invert[A] { - type Out -} -object Invert { - type Aux[A, Out0] = Invert[A] { type Out = Out0 } -} -``` - -To inductively derive typeclass instances for inverting, we need to prove that: - -1. We can derive an instance for `HNil` (the base case) -2. We can derive an instance for a non-empty HList (the n + 1 case), provided there is an existing instance for its tail (the n case) - -The base case operates on `HNil` - -```scala -implicit def baseCase: Invert.Aux[HNil, HNil] = new Invert[HNil] { type Out = HNil } -``` - -The inductive case assumes that the tail has an instance, and derives an instance for the head by negating the exponent: - -```scala -implicit def inductiveCase[D, Exp <: XInt, NExp <: XInt, Tail <: HList, - OutTail <: HList]( - implicit negateEv: OpInt.Aux[Negate[Exp], NExp], - tailEv: Invert.Aux[Tail, OutTail] -): Invert.Aux[FieldType[D, Exp] :: Tail, FieldType[D, NExp] :: OutTail] = - new Invert[FieldType[D, Exp] :: Tail] { - type Out = FieldType[D, NExp] :: OutTail -} -``` - -When the compiler looks for the implicit instance for `FieldType[Metre, 1] :: HNil`: -- It finds that the `inductiveCase` method has a return type which fits the signature -- It can find the required evidence `negateEv` for negating `1` from singleton ops -- It requires evidence of an implicit instance for the tail `HNil` -- It finds that `baseCase` provides this evidence - -So in hunting for implicit typeclass instance for the whole list, the compiler goes and finds instances for the tail (the n case), right up until the base. If we provide an inductive proof with a `baseCase` and an `inductiveCase`, we fit the bill for what the compiler needs. - -### Multiply - -Now that we've tested a basic example of induction, we can go on to a more complex one. - -We want to multiply two HLists of dimensions together. This means that the exponents should be added. - -```scala -trait Multiply[A, B] { - type Out -} -object Multiply { - type Aux[A, B, Out0] = Multiply[A, B] { type Out = Out0 } -} -``` - -This is harder to make inductive because there are two input lists involved. Luckily, we only need to recurse over one of them, as we can pick dimensions from the other using shapeless's `Selector`. We will recurse over the left list and can pick elements from the right list. - -Our base case can be the same: - -```scala -implicit def baseCase: Multiply.Aux[HNil, HNil, HNil] = new Multiply[HNil, HNil] { - type Out = HNil -} -``` - -We can define the inductive case using the following logic: -1. Pick the exponent in the right list corresponding to the head dimension in the left list -2. Add the left and right exponents together -3. Filter the term from the right list to get the remaining elements -4. Look for a typeclass instance for the left list tail and the remaining elements in the right list - -```scala -implicit def inductiveCase[D, R <: HList, LExp <: XInt , RExp <: XInt, - OutExp <: XInt, RTail <: HList, LTail <: HList, OutTail <: HList]( - implicit pickEv: Selector.Aux[R, D, RExp], - addEv: OpInt.Aux[LExp + RExp, OutExp], - filterEv: FilterNot.Aux[R, FieldType[D, RExp], RTail], - tailEv: Multiply.Aux[LTail, RTail, OutTail] -): Multiply.Aux[FieldType[D, LExp] :: LTail, R, FieldType[D, OutExp] :: OutTail] = - new Multiply[FieldType[D, LExp] :: LTail, R] { - type Out = FieldType[D, OutExp] :: OutTail -} -``` -When the compiler looks for an implicit instance of multiply for `FieldType[Metre, -1] :: HNil` and `FieldType[Metre, 3] :: HNil`: - -- It finds that the `inductiveCase` has a return type which fits the signature -- Given that the head of the left list is `Metre`, it selects the exponent for `Metre` from the right list -- It can find the evidence `addEv` to add the exponents `-1` and `3` -- It filters `Metre` from the right list to get `HNil` -- It requires evidence of an instance for `HNil` and `HNil` -- This is provided by the base case - -The compiler can now find instances of `Multiply`, as long as a dimension appears in both the left and right lists. -This can be extended to when a dimension doesn't appear by writing a few more inductive cases. - -### Divide - -The reason we went to the effort of writing `Invert` and `Multiply` was to divide. -Dividing a numerator by a denominator is as simple as inverting the denominator and multiplying it by the numerator. -We can write this in a single non-inductive instance: - -```scala -implicit def divide[L <: HList, R <: HList, RInverted <: HList, - Divided <: HList]( - implicit invertEv: Invert.Aux[R, RInverted], - multiplyEv: Multiply.Aux[L, RInverted, Divided] -): Divide.Aux[L, R, Divided] = new Divide[L, R] { - type Out = Divided -} -``` -That's far simpler than the work we've done before - we're just building on the typeclasses we wrote to do this. - -### Automatically derived instances - -We can now have compile time dimensional analysis without writing out divide instances for every combination of dimensions: - - -```scala -val rocketMass = Quantity[FieldType[Kilogram, 1] :: HNil](40.0) -val fuelMass = Quantity[FieldType[Kilogram, 1] :: HNil](10000.0) -val exhaust = Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil](1000000.0) -val distance = Quantity[FieldType[LightYear, 1] :: HNil](77.0) - -val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value)) -val time: Quantity[FieldType[Year, 1] :: HNil] = divide(distance, rocketSpeed) -// error: type mismatch; found: FieldType[LightYear, 1] :: FieldType[Metre, -1] :: FieldType[Second, 1] :: HNil; required: FieldType[Year, 1] :: HNil -``` -Yay! We've solved the problem! It looks a lot more verbose than what we started with, but we can tidy this up by using extension methods: - -```scala -implicit final class DoubleOps(val d: Double) { - def ly: Quantity[FieldType[LightYear,1] :: HNil] = Quantity(d) - def kg: Quantity[FieldType[Kilogram, 1] :: HNil] = Quantity(d) - def yr: Quantity[FieldType[Year, 1] :: HNil] = Quantity(d) - def mps: Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil] = Quantity(d) - def c: Quantity[FieldType[LightYear, 1] :: FieldType[Year, -1] :: HNil] = Quantity(d) -} -``` -We can also add symbolic infix operators for `add`, `times` and `divide`: - -```scala -case class Quantity[A](value: Double) { - def +(q: Quantity[A]): Quantity[A] = Quantity(value + q.value) - def *(v: Double): Quantity[A] = Quantity(value + v) - def /[B](q: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] = Quantity(value / q.value) -} -``` - -## We've reached Libra! - -We started with doubles: - -```scala -val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0) -val time = 77.0 / rocketSpeed -``` -And we finished with compile time dimensional analysis: - -```scala -val rocketSpeed = 1000000.0.mps * log(((40.0.kg +10000.0.kg) /40.0.kg).value) -val speedConversion = 300000000.0.mps / 1.c -val speedInC = rocketSpeed / speedConversion -val time = 77.0.ly / speedInC -//time: Quantity[FieldType[Year, 1] :: HNil] = Quantity(4180.65274634) -``` - -The code isn't more verbose - if anything, it's more explanatory and just as easy to work with. - -## Rolling this out to more problems - -All we need to provide for the business logic of our rocket launch problem are the dimensions and `DoubleOps`. -We could roll this out to any other problem. Let's say we wanted to do a currency conversion between `GBP` and `DKK`: - -```scala -val exchangeRate: Quantity[FieldType[DKK, 1] :: FieldType[GBP, -1] :: HNil] = - currentExchangeRate() -Val krone: Quantity[FieldType[DKK, 1] :: HNil] = 10.gbp * exchangeRate -``` - -We get dimensional analysis for any problem domain out of the box! - -Most of the code we've written is library code. In fact, it's Libra code! [Libra]() is a dimensional analysis library based on typelevel induction. It performs compile time dimensional analysis to any problem domain. It also uses [spire](https://github.com/non/spire) for its numeric typeclasses, so can be used for far more than just doubles. - -## Conclusion - -It's been a long way from the humble `Double`. We started with basic types, explored dependent types, took a look at Typelevel Scala along the way, before finally ending up performing typelevel induction. As a result, we've managed to achieve compile time dimensional analysis for any problem. If you're curious about typelevel induction take a look at [the Libra codebase](https://github.com/to-ithaca/libra) for more examples. Enjoy! diff --git a/src/blog/liskov-lifting.md b/src/blog/liskov-lifting.md deleted file mode 100644 index 4c3f9df1..00000000 --- a/src/blog/liskov-lifting.md +++ /dev/null @@ -1,277 +0,0 @@ -{% - author: ${S11001001} - date: "2014-03-09" - tags: [technical] -%} - -# When can Liskov be lifted? - -Scalaz avoids -[variance in the sense of the Scala type parameter annotation](http://docs.scala-lang.org/tutorials/tour/variances.html), -with its associated higher-kind implications, except where it has -historically featured variance; even here, variance is vanishing as -[unsoundness in its released implementations is discovered](https://github.com/scalaz/scalaz/pull/630). - -There is a deeply related concept in Scalaz's typeclasses, though: -*covariant and contravariant -functors*. [`Functor`](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/#scalaz.Functor) -is traditional shorthand for covariant functor, whereas -[`Contravariant`](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/#scalaz.Contravariant) -represents contravariant functors. - -These concepts are related, but neither subsumes the other. A -`Functor` instance does not require its parameter to be -Scala-covariant. A type can be Scala-covariant over a parameter -without having a legal `Functor` instance. - -`Liskov` --------- - -[`Liskov`](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/#scalaz.Liskov), -also known as `<~<` and very close to Scala's own -[`<:<`](http://www.scala-lang.org/api/current/#scala.Predef$$$less$colon$less), -represents a subtyping relationship, and is defined by the ability to -lift it into Scala-covariant and Scala-contravariant parameter -positions, like so: - -```scala -def liftCo[F[+_], A, B](a: A <~< B): F[A] <~< F[B] -def liftCt[F[-_], A, B](a: A <~< B): F[B] <~< F[A] -``` - -As `Liskov` is, soundly, Scala-variant, this can be implemented -without a cast. However, it can only be called with Scala-covariant -`F`. - -By definition, applying an `A <~< B` to a value of type `A` should -yield a value of type `B`, but must also do nothing but return the -value; in other words, it is an *operational identity*. Despite the -limitation of `liftCo`, for functorial values that are *parametrically -sound*, even for Scala-invariant `F`, it is operationally sound to -lift `Liskov`, though impossible to implement without exploiting Scala -soundness holes: - -```scala -def liftCvf[F[_]: Functor](a: A <~< B): F[A] <~< F[B] -``` - -For example, -[this is sound for `scalaz.IList`](https://github.com/scalaz/scalaz/blob/v7.1.0-M5/core/src/main/scala/scalaz/IList.scala#L434-L437). - -But `IList[Int]` isn't a subtype of `IList[Any]`! -------------------------------------------------- - -Sure, as far as Scala is concerned. But `Liskov` is all about making -claims that can't directly be proven due to the language's -limitations. Haskell allows you to constrain functions with type -equalities, which is very important when working with type families; -Scala doesn't, so we get -[`Leibniz`](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/#scalaz.Leibniz) -instead. - -A type is a set of values. Where *Y* is a supertype of *X*, every -value in *X* is in *Y*. Since `IList[String]("hi", "there")` has the -same representation as `IList[Any]("hi", "there")`, they are the same -value. This is true for *all* `IList[String]`s, but the opposite is -not true; therefore, `IList[Any]` is an `IList[String]` supertype, -regardless of what Scala knows. - -So doing a casting `Liskov` lift, like that into `IList`, is -essentially “admitted” in a proof system sense. You are saying, “I -can't prove that this subtype relationship holds, but it does, so -assume it.” - -**To decide whether an admitted `A <~< B` is sound**: suppose that the -compiler admits that subtyping relationship. Can it then draw -incorrect conclusions, about the sets of values, derived from that -assumption? This is the cardinal rule. - -By extension, **to decide whether an `F` permits Liskov lifting**: -does the above rule pass given `F[A] <~< F[B]` *for all* `A`, `B` -where `B` is a supertype of `A`? - -Parametrically sound covariance -------------------------------- - -Because a `Liskov` must be an operational identity, it is essential -that, given any value of `F[A]`, for all supertypes `B` of `A`, the -representation of `F[B]` must be identical. You can determine this by -analyzing the subclasses of `F` as an algebraic data type, where the -key test is to ensure that `A` *never* appears in the primitive -contravariant position: as the parameter to a function. This test is -not quite enough to prove that `Liskov` lifting is sound, but it gets -us most of the way. - -For example, an `IList` of `"hi"` and `"there"` has exactly the same -representation whether you instantiated the `IList` with `String` or -with `Any`. So that is a good first test. If a class changes its -construction behavior based on manifest type information, or its basic -data construction functions violate -[the rules of parametricity](http://failex.blogspot.com/2013/06/fake-theorems-for-free.html), -that is a good sign that the data type cannot follow these rules. - -This data type analysis is recursive: a data type being variant in a -parametrically sound way over a parameter requires that all -appearances of that parameter in elements of your data type are also -parametrically sound in that way. For example, if your `F[A]` contains -an `IList[A]` in its representation, you may rely on `IList`'s -parametrically sound covariance when considering `F`'s. - -Any `var`, or `var`-like thing such as an `Array`, places its -parameter in an invariant position, because it features a getter -(return type) and setter (parameter type). So its presence in the data -model invalidates `Liskov` lifting if the type parameter appears -within it. - -Obviously, runtime evidence of a type parameter's value eliminates the -possibility of lifting `Liskov` over that parameter. - -You cannot perform this representation analysis without considering -all subclasses of a class under consideration. For example, -considering only -[`HashSet`](http://www.scala-lang.org/api/current/#scala.collection.immutable.HashSet), -[`collection.immutable.Set`](http://www.scala-lang.org/api/current/#scala.collection.immutable.Set) -appears to allow `Liskov` lifting. However, -[`TreeSet`](http://www.scala-lang.org/api/current/#scala.collection.immutable.TreeSet), -a subclass of `Set`, contains a function `(A, A) => Ordering`. If -*any* representation contains a contradiction like this, `Liskov` -lifting is unsafe. You cannot constrain `Liskov` application by a -runtime test. - -If you permit open subclassing, you must either declare the -requirement to preserve parametric covariance, or accept that it will -be violated, and so forbid `Liskov` lifting. - -Data that doesn't use a type parameter doesn't affect its parametric -soundness. For example, here `A` is invariant, but `B` is covariant: - -```scala -final case class VA[A, B](xs: Set[A], ys: IList[B]) -``` - -GADTs ------ - -Some features of Scala resist simple ADT analysis, so must be -considered separately from the above. Despite their sound covariance -considering only the representational rules in the previous section, -they still break the cardinal rule by allowing the compiler to make -invalid assumptions about the sets of values. A “recoverable phantom” -implies a type relationship that forbids `Liskov`-lifting, for -example: - -```scala -sealed trait Gimme[A] -case object GimmeI extends Gimme[Int] -``` - -In pattern matching, given a `Gimme[A]` over unknown `A`, matching -`GimmeI` successfully recovers the type equality `A ~ Int`; therefore, -`Liskov`-lifting is unsound for `Gimme`. For example, lifting -`Int <~< Any`, applying to `GimmeI`, and matching, gives us -`Any ~ Int`, which is nonsense. - -We can reason about this type equality as a value member of `GimmeI` -of type `Leibniz[⊥, ⊤, A, Int]`, which places `A` in a -representationally invariant position. - -Some other GADTs invalidate covariance. For example: - -```scala -sealed trait P[A] -case class PP[A, B](a: P[A], b: P[B]) extends P[(A, B)] -``` - -The pattern match of a `P[A]` to `PP[_,_]` can theoretically determine -`A ~ (x, y) forSome {type x; type y}`, so `Liskov` cannot be lifted -into `P`. - -However, not all GADTs invalidate `Liskov`-lifting: - -```scala -sealed trait AM[A] -case class FAM[A, B](fa: AM[A], f: A => B) extends AM[B] -``` - -Matching `AM[A]` to `FAM[_,_]` reveals nothing about `A`; its use of -GADTs only introduces a new existential unrelated to `A`. Considering -only `B`, as the `A` parameter is called in `FAM`, its covariance is -sound in `FAM`, so `Liskov`s can be lifted into `AM`. - -Contravariance --------------- - -`Liskov`s can also be lifted into parametrically sound contravariant -positions. This looks a bit like: - -```scala -def liftCtf[F[_]: Contravariant, A, B](a: A <~< B): F[B] <~< F[A] -``` - -Analysis of parametrically sound contravariance is essentially the -same as that for covariance. The only difference is that, for `F[A]`, -`A` can *only* appear in the primitive contravariant position: the -function parameter type. - -With regard to recursion, the “flipping” behavior of -Scala-contravariance applies. For example, this data type is soundly -contravariant over `A`: - -```scala -final case class IOf[A](f: IList[A] => Unit) -``` - -`IList` is soundly covariant over `A`, and `IList[A]` appears in -soundly contravariant position, making `A` contravariant. Meanwhile, -`A` is soundly *co*variant in this data type built upon `IOf`: - -```scala -final case class IOf2[A](f: IOf[IOf[A]]) -``` - -Some surprises --------------- - -Despite the unsoundness of `Liskov`-lifting into `Gimme` earlier, it -may seem surprising that Scala allows: - -```scala -sealed trait GimmeC[+A] -case object GimmeCI extends Gimme[Int] -``` - -Moreover, this isn't a bug; it's perfectly sound. That is because, -while matching `GimmeI` causes Scala to infer `A ~ Int`, it won't do -that for `GimmeCI`! Scala can soundly determine that `A ⊇ Int` when -it matches `GimmeCI`, but I do not think it even goes so far as to do -that as of this writing. We can't blame Scala for this difference; -Scala has declared up front that its type system encodes what it -believes, and is *our* responsibility to follow the cardinal rule of -not violating its assumptions if we lift `Liskov` into `Gimme`. - -As stated earlier, `Liskov` cannot be lifted into -`collection.immutable.Set`; `TreeSet` exists to trivially demonstrate -the problem, but even if `TreeSet` was not there, we would not be able -to honestly do it because `c.i.Set` is open to new subclasses that -could perform similar violations. However, despite lacking a -`Functor`, `scalaz.ISet` *does* allow `Liskov`-lifting. -[Do the ADT analysis yourself, if you like.](https://github.com/scalaz/scalaz/blob/ac8c4684ef89f1b950e71237819d78f573e552ea/core/src/main/scala/scalaz/ISet.scala#L552-L561) -Well, so, once you convert your `ISet[Int]` to `ISet[Any]`, you can't -do many operations on it, but that's neither here nor there. - -Should this function exist? ---------------------------- - -The Scalaz community has settled on a definition of -covariant-functoriality that conforms with the principle of parametric -soundness. The rejection of `Functor` instances -[for the `scala.collection.*` classes](https://github.com/scalaz/scalaz/pull/307), -which have subclasses with mutable values over their parameters, and -[for `collection.immutable.Set`](https://github.com/scalaz/scalaz/pull/276), -which has the `TreeSet` case stated above and violates parametricity -in the construction of `HashSet`s, speak to this. As far as I know, -Scalaz contains no `Functor`s that are both Scala-invariant and -violate the rules delineated above. - -So how do you feel about the provision of a combinator of the type of -`liftCvf` for Scalaz's `Functor`? diff --git a/src/blog/machinist.md b/src/blog/machinist.md deleted file mode 100644 index 83acc099..00000000 --- a/src/blog/machinist.md +++ /dev/null @@ -1,431 +0,0 @@ -{% - author: ${non} - date: "2015-08-06" - tags: [technical] -%} - -# Machinist vs. value classes - -This article is about [machinist](https://github.com/typelevel/machinist), a stand-alone project which started out as part of the [spire](https://github.com/non/spire) project and has been originally published in [October 2014](https://gist.github.com/non/a6ff3c0796e566db20d1). -The original description can be found on [this blog](spires-ops-macros.md). -You should read that linked post first if you are not familiar with how Machinist works. - - -Introduction ------------- - - -[Machinist Issue #2](https://github.com/typelevel/machinist/issues/2) asks: - -> Is it correct, that this stuff is completely obsolete now due to -> value classes or are there still some use cases? An example of using -> value class for zero-cost implicit enrichment: (...) - -The short answer is that value classes existed before the Machinist macros were implemented, and they do not solve the same problem Machinist solves. - -This article is the long answer. - - -The base case -------------- - -The Machinist's goal is to remove *any* overhead that would distinguish -using a type class "directly" from using it indirectly via an implicit -operator. - -Imagine we have the following "toy" type class: - -```scala -trait Div[A] { - def div(lhs: A, rhs: A): A -} - -object Div { - implicit val DivString = new Div[String] { - def div(lhs: String, rhs: String): String = lhs + "/" + rhs - } -} -``` - -This allows us to write generic code such as: - -```scala -class Test1 { - def gen[A](x: A, y: A)(implicit ev: Div[A]): A = ev.div(x, y) - def test: String = gen("foo", "bar") -} -``` - -We have a generic method `gen` that works with any type we have a `Div[A]` instance for, and we verify that it works using a `test` method that operates on some strings. So far, so good. But obviously, calling `ev.div` is a bit ugly. - -Implicit conversion with a value class --------------------------------------- - -We can make the `gen` method look a bit nicer by using an implicit conversion. Here's the code: - -```scala -object Test3 { - implicit class DivOps[A](val lhs: A) extends AnyVal { - def /(rhs: A)(implicit ev: Div[A]): A = ev.div(lhs, rhs) - } -} - -class Test3 { - import Test3.DivOps - def gen[A: Div](x: A, y: A): A = x / y - def test: String = gen("foo", "bar") -} -``` - -Now, we can just say `x / y` and have that call `Div#div` automatically. We also don't need a reference to `ev: Div[A]` so we can use the nicer `[A: Div]` syntax. - -With a normal implicit conversion, every call to `gen` would construct an instance of `Test3.DivOps`. However, since we have defined `Test3.DivOps` as a value class (by extending `AnyVal`), the object instantiation is ellided. Instead, the method call is dispatched to `Test3.DivOps.$div$extension` which calls `ev.div`. - -We often talk about value classes as not having a *cost*. Since no class is instantiated, we are not required to pay a cost in allocations, but we do still pay a cost in indirection (instead of calling `ev.div` directly as in `Test1` we have an intermediate extension method). - -You can see the difference in the output from `javap`. - -In the case of `Test1.gen`, the call to `ev.div` and return are all handled with 5 instructions (8 bytes of bytecode): - -``` -// cost.Test1.gen(A, A, Div[A]): A -0: aload_3 -1: aload_1 -2: aload_2 -3: invokeinterface #16, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -8: areturn -``` - -In the case of `Test3.gen`, there is extra ceremony setting up the companion objects, and a call to the extension method (`$div$extension`), which is defined in `Test3.DivOps`: - -``` -// cost.Test3.gen(A, A, Div[A]): A -0: getstatic #25 // Field cost/Test3$DivOps$.MODULE$:Lcost/Test3$DivOps$; -3: getstatic #16 // Field cost/Test3$.MODULE$:Lcost/Test3$; -6: aload_1 -7: invokevirtual #18 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object; -10: aload_2 -11: aload_3 -12: invokevirtual #28 // Method cost/Test3$DivOps$.$div$extension:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object; -15: areturn - -// cost.Test3.DivOps.$div$extension(A, A, Div[A]): A -0: aload_3 -1: aload_1 -2: aload_2 -3: invokeinterface #20, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -8: areturn -``` - -In fact the bytecode for the extension method is uncannily similar to that of `Test1.gen`, but in this case `Test3.gen` involves 8 more instructions (15 bytes). - -In some cases these bytecode differences might not be significant (for example if the running time of `Div[A].div` is expected to dwarf the cost of method dispatch). However, when type classes are used to support primitive operations (such as addition or comparisons) it's likely that this overhead might be significant. - -Enter machinist ---------------- - -Machinist is based on a set of macros that were introduced in [Spire](https://github.com/non/spire) to remove the performance penalties associated with generic math implementations. These macros were based on an even earlier approach which used a compiler plugin. - -The basic approach has not changed: at compile-time we can detect situations where we build an object just to assemble a method call with the arguments to its constructor. In these cases we rewrite the tree, removing the object allocation and making the method call directly. Machinist's documentation goes to some trouble to explain it, but basically, we want to be able to write code like `Test3.gen` but have it interpreted as `Test1.gen`. That is literally the entire purpose of machinist. - -Here's a construction that works for this example: - -```scala -object Test2 { - implicit class DivOps[A](lhs: A)(implicit ev: Div[A]) { - def /(rhs: A): A = macro machinist.DefaultOps.binop[A, A] - } -} - -class Test2 { - import Test2.DivOps - def gen[A: Div](x: A, y: A): A = x / y - def test: String = gen("foo", "bar") -} -``` - -We use the `machinist.DefaultOps` object to provide an instance of the `binop` macros, which will rewrite `DivOps(x)(ev).$div(y)` into `ev.div(x, y)`. - -Here's what we end up with in bytecode: - -``` -// cost.Test2.gen(A, A, Div[A]): A -0: aload_3 -1: aload_1 -2: aload_2 -3: invokeinterface #26, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -8: areturn -``` - -As you can see, the sourcecode for `Test2.gen` is identical to `Test3.gen`, and the bytecode for `Test2.gen` is identical to that of `Test1.gen`. Success! - -Caveats -------- - -There are a few caveats that are worth mentioning: - -### Managing compilation units - -The issue that sparked this article used the operator `+/+`. Machinist claims to be able to support any symbolic operator. Why didn't we use that operator here? - -The answer has to do with how Scala macros work. Scala requires that macros be defined in a separate "compilation unit" from the one they are invoked in. This makes it very awkward to create a code snippet that both defines and uses a macro. In this case, it means that we can't extend `machinist.Ops` to define new symbolic operators in the same file that demonstrates their use. This is why we used `/` (which maps to `div` and is a "default operator"). - -You can arrange your "real" projects so that they are not affected by this limitation. - -### Use outside of generic methods - -Now that we've demonstrated the cost that implicit conversions to value classes impose, you might imagine wanting to perform this transformation on *all* your implicit conversions. - -Unfortunately, Machinist is not sufficiently general to support this. Right now its macros support a number of different "shapes" but assume generic method which dispatches to an implicit evidence parameter. It might be possible to write macros which inline the method body of a concrete implicit class, but that's outside the scope of the project. - - -Postscript: messy details -------------------------- - -This article throws around a lot of source code and bytecode. Below are included the files needed to build the demo (`cost.scala` and `build.sbt`) as well as the `javap` output from the three test classes, and the value class. - -### cost.scala - -```scala -package cost - -import language.implicitConversions -import scala.language.experimental.macros - -trait Div[A] { - def div(lhs: A, rhs: A): A -} - -object Div { - implicit val DivString = new Div[String] { - def div(lhs: String, rhs: String): String = lhs + "/" + rhs - } -} - -class Test1 { - def gen[A](x: A, y: A)(implicit ev: Div[A]): A = ev.div(x, y) - def test: String = gen("foo", "bar") -} - -object Test2 { - implicit class DivOps[A](lhs: A)(implicit ev: Div[A]) { - def /(rhs: A): A = macro machinist.DefaultOps.binop[A, A] - } -} - -class Test2 { - import Test2.DivOps - def gen[A: Div](x: A, y: A): A = x / y - def test: String = gen("foo", "bar") -} - -object Test3 { - implicit class DivOps[A](val lhs: A) extends AnyVal { - def /(rhs: A)(implicit ev: Div[A]): A = ev.div(lhs, rhs) - } -} - -class Test3 { - import Test3.DivOps - def gen[A: Div](x: A, y: A): A = x / y - def test: String = gen("foo", "bar") -} -``` - -### build.sbt - -```scala -name := "cost" - -scalaVersion := "2.11.2" - -resolvers += "bintray/non" at "http://dl.bintray.com/non/maven" - -libraryDependencies += "org.typelevel" %% "machinist" % "0.2.2" -``` - -### Test1.out - -``` -Compiled from "cost.scala" -public class cost.Test1 { - public A gen(A, A, cost.Div); - Code: - 0: aload_3 - 1: aload_1 - 2: aload_2 - 3: invokeinterface #16, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - 8: areturn - - public java.lang.String test(); - Code: - 0: aload_0 - 1: ldc #27 // String foo - 3: ldc #29 // String bar - 5: getstatic #35 // Field cost/Div$.MODULE$:Lcost/Div$; - 8: invokevirtual #39 // Method cost/Div$.DivString:()Lcost/Div; - 11: invokevirtual #41 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object; - 14: checkcast #43 // class java/lang/String - 17: areturn - - public cost.Test1(); - Code: - 0: aload_0 - 1: invokespecial #47 // Method java/lang/Object."":()V - 4: return -} -``` - -### Test2.out - -``` -Compiled from "cost.scala" -public class cost.Test2 { - public static cost.Test2$DivOps DivOps(A, cost.Div); - Code: - 0: getstatic #16 // Field cost/Test2$.MODULE$:Lcost/Test2$; - 3: aload_0 - 4: aload_1 - 5: invokevirtual #18 // Method cost/Test2$.DivOps:(Ljava/lang/Object;Lcost/Div;)Lcost/Test2$DivOps; - 8: areturn - - public A gen(A, A, cost.Div); - Code: - 0: aload_3 - 1: aload_1 - 2: aload_2 - 3: invokeinterface #26, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - 8: areturn - - public java.lang.String test(); - Code: - 0: aload_0 - 1: ldc #37 // String foo - 3: ldc #39 // String bar - 5: getstatic #44 // Field cost/Div$.MODULE$:Lcost/Div$; - 8: invokevirtual #48 // Method cost/Div$.DivString:()Lcost/Div; - 11: invokevirtual #50 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object; - 14: checkcast #52 // class java/lang/String - 17: areturn - - public cost.Test2(); - Code: - 0: aload_0 - 1: invokespecial #56 // Method java/lang/Object."":()V - 4: return -} -``` - -### Test3.out - -``` -Compiled from "cost.scala" -public class cost.Test3 { - public static java.lang.Object DivOps(java.lang.Object); - Code: - 0: getstatic #16 // Field cost/Test3$.MODULE$:Lcost/Test3$; - 3: aload_0 - 4: invokevirtual #18 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object; - 7: areturn - - public A gen(A, A, cost.Div); - Code: - 0: getstatic #25 // Field cost/Test3$DivOps$.MODULE$:Lcost/Test3$DivOps$; - 3: getstatic #16 // Field cost/Test3$.MODULE$:Lcost/Test3$; - 6: aload_1 - 7: invokevirtual #18 // Method cost/Test3$.DivOps:(Ljava/lang/Object;)Ljava/lang/Object; - 10: aload_2 - 11: aload_3 - 12: invokevirtual #28 // Method cost/Test3$DivOps$.$div$extension:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object; - 15: areturn - - public java.lang.String test(); - Code: - 0: aload_0 - 1: ldc #39 // String foo - 3: ldc #41 // String bar - 5: getstatic #46 // Field cost/Div$.MODULE$:Lcost/Div$; - 8: invokevirtual #50 // Method cost/Div$.DivString:()Lcost/Div; - 11: invokevirtual #52 // Method gen:(Ljava/lang/Object;Ljava/lang/Object;Lcost/Div;)Ljava/lang/Object; - 14: checkcast #54 // class java/lang/String - 17: areturn - - public cost.Test3(); - Code: - 0: aload_0 - 1: invokespecial #58 // Method java/lang/Object."":()V - 4: return -} -``` - -### Test3.DivOps.out - -``` -Compiled from "cost.scala" -public class cost.Test3$DivOps$ { - public static final cost.Test3$DivOps$ MODULE$; - - public static {}; - Code: - 0: new #2 // class cost/Test3$DivOps$ - 3: invokespecial #12 // Method "":()V - 6: return - - public final A $div$extension(A, A, cost.Div); - Code: - 0: aload_3 - 1: aload_1 - 2: aload_2 - 3: invokeinterface #20, 3 // InterfaceMethod cost/Div.div:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - 8: areturn - - public final int hashCode$extension(A); - Code: - 0: aload_1 - 1: invokevirtual #32 // Method java/lang/Object.hashCode:()I - 4: ireturn - - public final boolean equals$extension(A, java.lang.Object); - Code: - 0: aload_2 - 1: astore_3 - 2: aload_3 - 3: instanceof #36 // class cost/Test3$DivOps - 6: ifeq 15 - 9: iconst_1 - 10: istore 4 - 12: goto 18 - 15: iconst_0 - 16: istore 4 - 18: iload 4 - 20: ifeq 61 - 23: aload_2 - 24: ifnonnull 31 - 27: aconst_null - 28: goto 38 - 31: aload_2 - 32: checkcast #36 // class cost/Test3$DivOps - 35: invokevirtual #40 // Method cost/Test3$DivOps.lhs:()Ljava/lang/Object; - 38: astore 5 - 40: aload_1 - 41: aload 5 - 43: invokestatic #45 // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z - 46: ifeq 53 - 49: iconst_1 - 50: goto 54 - 53: iconst_0 - 54: ifeq 61 - 57: iconst_1 - 58: goto 62 - 61: iconst_0 - 62: ireturn - - public cost.Test3$DivOps$(); - Code: - 0: aload_0 - 1: invokespecial #47 // Method java/lang/Object."":()V - 4: aload_0 - 5: putstatic #49 // Field MODULE$:Lcost/Test3$DivOps$; - 8: return -} -``` diff --git a/src/blog/mapping-sets.md b/src/blog/mapping-sets.md deleted file mode 100644 index d124f78f..00000000 --- a/src/blog/mapping-sets.md +++ /dev/null @@ -1,138 +0,0 @@ -{% - author: ${puffnfresh} - date: "2014-06-22" - tags: [technical] -%} - -# How can we map a Set? - -Scalaz used to have a `scalaz.Functor` for `scala.collection.Set` but -it was [eventually removed](https://github.com/scalaz/scalaz/pull/276) -because it relied on -[Any's == method](http://www.scala-lang.org/api/2.10.3/index.html#scala.Any). You -can read more about why `Functor[Set]` is a bad idea at -[Fake Theorems for Free](http://failex.blogspot.jp/2013/06/fake-theorems-for-free.html). - -If `Set` had been truly parametric, we wouldn't have been able to -define a `Functor` in the first place. Luckily, a truly parametric Set -has recently been added to Scalaz as `scalaz.ISet`, with preliminary -benchmarks also showing some nice performance improvements. I highly -recommend using `ISet` whenever you can! - -Now we can see the problem more clearly; the type of `map` on `ISet` -is too restrictive to be used inside of a `Functor` because of the -`scalaz.Order` constraint: - -```scala -def map[B: Order](f: A => B): ISet[B] -``` - -And it might seem like we've lost something useful by not having a -`Functor` available. For example, we can't write the following: - -```scala -val nes = OneAnd("2014-05-01", ISet.fromList("2014-06-01" :: "2014-06-22" :: Nil)) // a non-empty Set -val OneAnd(h, t) = nes.map(parseDate) -``` - -Which is because the `map` function on `scalaz.OneAnd` requires a -`scalaz.Functor` for the `F[_]` type parameter, which is `ISet` in the -above example. - -But we have a solution! It's called -[Coyoneda](http://docs.typelevel.org/api/scalaz/nightly/#scalaz.Coyoneda) -(also known as the Free Functor) and it'll hopefully be able to -demonstrate why not having `Functor[ISet]` available has no -fundamental, practical consequences. - -Coyoneda -[can be defined in Scala](http://blog.higher-order.com/blog/2013/11/01/free-and-yoneda/) -like so: - -```scala -trait Coyoneda[F[_], A] { - type I - def k: I => A - def fi: F[I] -} -``` - -There are just three parts to it: - -1. `I` - an existential type -2. `k` - a mapping from `I` to `A` -3. `fi` - a value of `F[I]` - -We can create a couple of functions to help with constructing a -Coyoneda value: - -```scala -def apply[F[_], A, B](fa: F[A])(_k: A => B): Coyoneda[F, B] { type I = A } = - new Coyoneda[F, B] { - type I = A - val k = _k - val fi = fa - } - -def lift[F[_], A](fa: F[A]): Coyoneda[F, A] = Coyoneda(fa)(identity[A]) -``` - -The constructors allow any type constructor to become a Coyoneda value: - -```scala -val s: Coyoneda[ISet, Int] = Coyoneda.lift(ISet.fromList(1 :: 2 :: 3 :: Nil)) -``` - -Now here's the special part; we can define a `Functor` for all -Coyoneda values: - -```scala -implicit def coyonedaFunctor[F[_]]: Functor[({type λ[α] = Coyoneda[F, α]})#λ] = - new Functor[({type λ[α] = Coyoneda[F,α]})#λ] { - def map[A, B](ya: Coyoneda[F, A])(f: A => B) = Coyoneda(ya.fi)(f compose ya.k) - } -``` - -What's interesting is that the `F[_]` type does *not* have to have a -`Functor` defined for the Coyoneda to be mapped! - -Let's use this to try out our original example. We'll define a type -alias to make things a bit cleaner: - -```scala -type ISetF[A] = Coyoneda[ISet, A] -``` - -And we can use this new type instead of a plain `ISet`: - -```scala -// Scala has a really hard time with inference here, so we have to help it out. -val functor = OneAnd.oneAndFunctor[ISetF](Coyoneda.coyonedaFunctor[ISet]) -import functor.functorSyntax._ - -val nes = OneAnd[ISetF, String]("2014-05-01", Coyoneda.lift(ISet.fromList("2014-06-01" :: "2014-06-22" :: Nil))) -val OneAnd(h, t) = nes.map(parseDate) -``` - -So we've been able to map the Coyoneda! But how do we do something -useful with it? - -We couldn't define a `Functor` because it needs `scalaz.Order` on the -output type, but we can use the `map` method directly on `ISet`. We -can use that function by running the Coyoneda like so: - -```scala -// Converts ISetF back to an ISet, using ISet#map with the Order constraint -val s = t.fi.map(t.k).insert(h) -``` - -And we're done! - -We've been able to use Coyoneda to treat an `ISet` as a `Functor`, -even though its map function is too constrained to have one defined -directly. This same technique applies to `scala.collection.Set` and -any other type-constructor which would otherwise require a -[restricted `Functor`](http://okmij.org/ftp/Haskell/types.html#restricted-datatypes). I -hope this has demonstrated that `Functor[Set]` not existing has no -practical consequences, other than scalac not being as good at -type-inference. diff --git a/src/blog/meetup-lausanne-2025-08-22.md b/src/blog/meetup-lausanne-2025-08-22.md deleted file mode 100644 index 4505c730..00000000 --- a/src/blog/meetup-lausanne-2025-08-22.md +++ /dev/null @@ -1,32 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2025-08-15" - event-date: "August 22, 2025" - event-location: "École Polytechnique Fédérale de Lausanne" - tags: [events] -%} - -# Typelevel Meetup Lausanne - -@:include(/img/places/lausanne.md) - -## About the Meetup - -Join the Typelevel community at an [in-person meetup][luma] on EPFL campus in Lausanne, organized by [Arman Bilge] and [Antonio Jimenez]. This meetup is open to (aspiring) Typelevel users and contributors and anyone curious to learn more about functional programming in Scala, no matter their prior experience. - -During this meetup you can expect: - -* chat/q&a about functional programming and Typelevel libraries -* a small tutorial on [Cats Effect], [FS2], and [Calico] -* a group activity building widgets with Calico -* lunch - -More details and registration are available on the [event page][luma]. All participants and organizers must abide by the [Typelevel Code of Conduct]. - -[Arman Bilge]: https://github.com/armanbilge -[Antonio Jimenez]: https://github.com/antoniojimeneznieto -[Typelevel Code of Conduct]: /code-of-conduct/README.md -[Cats Effect]: https://typelevel.org/cats-effect -[FS2]: https://fs2.io/ -[Calico]: https://armanbilge.github.io/calico -[luma]: https://lu.ma/g7qow6h3 diff --git a/src/blog/method-equiv.md b/src/blog/method-equiv.md deleted file mode 100644 index 11041e0f..00000000 --- a/src/blog/method-equiv.md +++ /dev/null @@ -1,352 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-16" - tags: [technical] - katex: true -%} - -# When are two methods alike? - -*This is the second of a series of articles on “Type Parameters and -Type Members”. If you haven’t yet, you should -[start at the beginning](type-members-parameters.md), -which introduces code we refer to throughout this article without -further ado.* - -[In the last part](type-members-parameters.md#when-is-existential-ok), -we just saw two method types that, though different, are effectively -the same: those of `plengthT` and `plengthE`. We have rules for -deciding when an existential parameter can be lifted into a method -type parameter—or a method type parameter lowered to an -existential—but there are other pairs of method types I want to -explore that are the same, or very close. So let’s talk about how we -determine this equivalence. - -A method *R* is more general than or as general as *Q* if *Q* may be -implemented by only making a call to *R*, passing along the arguments. -By more general, we mean *R* can be invoked in all the situations that -*Q* can be invoked in, and more besides. Let us call the result of -this test @:math R <:_m Q @:@ (where @:math <:_m @:@ is pronounced “party duck”); if -the test of *Q* making a call to *R* fails, then @:math \neg(R <:_m Q) @:@. - -If @:math Q <:_m R @:@ and @:math R <:_m Q @:@, then the two method types are -*equivalent*; that is, neither has more expressive power than the -other, since each can be implemented merely by invoking the other and -doing nothing else. We write this as @:math Q \equiv_m R @:@. Likewise, if -@:math R <:_m Q @:@ and @:math \neg(Q <:_m R) @:@, that is, *Q* can be written by -calling *R*, but not vice versa, then *R* is *strictly more general* -than *Q*, or @:math R <_m Q @:@. - -What the concrete method—the one actually doing stuff, not invoking -the other one—does is irrelevant, for the purposes of this test, -because this is about types. That matters because sometimes, in -Scala, as in Java, the body will compile in one of the methods, but -not the other. Let’s see an example that doesn’t compile. - -```scala -import scala.collection.mutable.ArrayBuffer - -def copyToZero(xs: ArrayBuffer[_]): Unit = - xs += xs(0) - -TmTp2.scala:9: type mismatch; - found : (some other)_$1(in value xs) - required: _$1(in value xs) - xs += xs(0) - ^ -``` - -Likewise, the Java version has a similar problem, though the error -message doesn’t give as good a hint as to what’s going on. - -```java -import java.util.List; - -void copyToZero(final List xs) { - xs.add(xs.get(0)); -} - -TmTp2.java:11: error: no suitable method found for add(CAP#1) - xs.add(xs.get(0)); - ^ -``` - -Luckily, in both Java and Scala, we have an *equivalent* method type, -from lifting the existential (misleadingly called *wildcard* in Java -terminology) to a method type parameter. - -We can apply this transformation to put the method implementation -somewhere it will compile. - -```scala -def copyToZeroE(xs: ArrayBuffer[_]): Unit = - copyToZeroP(xs) - -private def copyToZeroP[T](xs: ArrayBuffer[T]): Unit = - xs += xs(0) -``` - -Similarly, in Java, - -```java -void copyToZeroE(final List xs) { - copyToZeroP(xs); -} - - void copyToZeroP(final List xs) { - final T zv = xs.get(0); - xs.add(zv); -} -``` - -The last gives a hint as to what’s going on, both here and in the -compiler errors above: in `copyToZeroP`’s body, the list element type -has a name, `T`; we can use the name to create variables, and the -compiler can rely on the name as well. The compiler, ideally, -shouldn’t care about whether the name can be written, but that one of -the above compiles and the other doesn’t is telling. - -If you were to define a variable to hold the result of getting the -first element in the list in either version of `copyToZeroE`, how -would you do that? In Java, the reason this doesn’t work is -straightforward: you would have to declare the variable to be of type -`Object`, but that type isn’t specific enough to allow the variable to -be used as an argument to `xs.add`. - -Scala’s type-inferred variables don’t help here; Scala considers the -existential type to be scoped to `xs`, and makes the definition of -`zv` independent of `xs` by breaking the type relationship, and -crushing the inferred type of `zv` to `Any`. - -```scala -def copyToZeroE(xs: ArrayBuffer[_]): Unit = { - val zv = xs(0) - xs += zv -} - -TmTp2.scala:19: type mismatch; - found : zv.type (with underlying type Any) - required: _$1 - xs += zv - ^ -``` - -When we call the type-parameterized variant to implement the -existential variant, with the real implementation residing in the -former, we are just helping the compiler along by using the equivalent -method type; in the simpler case of the former, both `scalac` and -`javac` manage to infer that the type `T` should be the (otherwise -unspeakable) existential. **Method equivalence and generality make it -possible to write methods, safely, that could not be written -directly.** - -Why are existentials harder to think about? -------------------------------------------- - -I think we, as humans, may have even more difficulty with the lack of -names for existentials than the compilers do. The name “unspeakable”, -which I have borrowed from Jon Skeet’s *C# in Depth*, is telling: even -in our heads, our thought processes are shaped by language. We tame -the mathematics of programming with symbols, with names. Existentials -and their “unspeakable” names rob us of the tools to talk about them, -to think about them. - -Java has done its practitioners two great disservices here. One: by -calling its existentials “wildcards”. They are not “wildcards”, in -any commonly or uncommonly understood sense. If you suppose your -preexisting notions of “wildcards” to apply to these much more exotic -creatures, you will confidently stroll into the darkness until you -trip and fall off a cliff. They are only *superficially* “wildcards”. -The effect of this sorry attempt at avoiding new terminology is -chiefly to cheat Java programmers out of learning what’s really going -on. (We will explore some of this more exotic behavior -[in a later post](nested-existentials.md).) - -Two: by -[encouraging use of existential signatures](https://docs.oracle.com/javase/tutorial/extra/generics/methods.html) -like `mdropFirstE` over parameterized versions like `mdropFirstT` that -do not require the same kind of mental gymnastics. - -For lifting these type parameters is how we can reclaim the power we -lost in the debacle of the unspeakable names. We name them, and in so -doing can once more talk and think about them without exhausting -ourselves by gesticulating wildly, comforting ourselves with -fairytales of “wildcards”. Because in parameter lifting, we have -found a *true* analogy. - -When are two methods less alike? --------------------------------- - -Now, let’s examine another pair of methods, and apply our test to -them. - -Let’s say we want to write the equivalent of this method for `MList`. - -```scala -def pdropFirst[T](xs: PList[T]): PList[T] = - xs match { - case PNil() => PNil() - case PCons(_, t) => t - } -``` - -According to the `PList` ⇔ `MList` conversion rules given -[in the previous article](type-members-parameters.md#when-is-existential-ok), -section “Why all the `{type T = ...}`?”, the equivalent for `MList` -should be - -```scala -def mdropFirstT[T0](xs: MList {type T = T0}) - : MList {type T = T0} = - xs.uncons match { - case None => MNil() - case Some(c) => c.tail - } -``` - -Let us try to drop the refinements. That seems to compile: - -```scala -def mdropFirstE(xs: MList): MList = - xs.uncons match { - case None => MNil() - case Some(c) => c.tail - } -``` - -It certainly looks nicer. However, while `mdropFirstE` can be -implemented by calling `mdropFirstT`, passing the type parameter -`xs.T`, the opposite is not true; `mdropFirstT` @:math <_m @:@ `mdropFirstE`, -or, `mdropFirstT` is *strictly more general*. - -In this case, the reason is that `mdropFirstE` fails to relate the -argument’s `T` to the result’s `T`; you could implement `mdropFirstE` -as follows: - -```scala -def mdropFirstE[T0](xs: MList): MList = - MCons[Int](42, MNil()) -``` - -The stronger type of `mdropFirstT` forbids such shenanigans. However, -I can just tell you that largely because I’m already comfortable with -existentials; how could you figure that out if you’re just starting -out with these tools? You don’t have to; the beauty of the -equivalence test is that you can apply it mechanically. **Knowing -nothing about the mechanics of the parameterization and existentialism -of the types involved, you can work out with the equivalence test** -that `mdropFirstT` @:math <_m @:@ `mdropFirstE`, and therefore, that you can’t -get away with simply dropping the refinements. - -Method likeness and subtyping, all alike ----------------------------------------- - -If you know what the symbol `<:` means in Scala, or perhaps you’ve -read -[SLS §3.5 “Relations between types”](http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#relations-between-types), -you might think, “gosh, method equivalence and generality look awfully -familiar.” - -Indeed, the thing we’re talking about is very much like subtyping and -type equality! In fact, every type-equal pair of methods *M*₁ and -*M*₂ also pass our method equivalence test, and every pair of methods -*M*₃ and *M*₄ where @:math M_3 <: M_4 @:@ passes our *M*₄-calls-*M*₃ test. -So @:math M_1 \equiv M_2 @:@ implies @:math M_1 \equiv_m M_2 @:@, and -@:math M_3 <: M_4 @:@ implies @:math M_3 <:_m M_4 @:@. - -We even follow many of the same rules as the type relations. We have -transitivity: if *M*₁ can call *M*₂ to implement itself, and *M*₂ can -call *M*₃ to implement itself, obviously we can snap the pointer and -have *M*₁ call *M*₃ directly. Likewise, every method type is -equivalent to itself: reflexivity. Likewise, if a method *M*₁ is -strictly more general than *M*₂, obviously *M*₂ cannot be strictly -more general than *M*₁: antisymmetricity. And we even copy the -relationship between ≡ and <: themselves: just as @:math T_1 \equiv T_2 @:@ -implies @:math T_1 <: T_2 @:@, so @:math R \equiv_m Q @:@ implies @:math R <:_m Q @:@. - -Scala doesn’t understand the notion of method equivalence we’ve -defined above, though. So you can’t, say, implement an abstract -method in a subclass using an equivalent or more general form, at -least directly; you have to `override` the Scala way, and call the -alternative form yourself, if that’s what you want. - -I do confess to one oddity in my terminology: **the method that has -more specific type is *the more general method*.** I hope the example -of `mdropFirstT` @:math <:_m @:@ `mdropFirstE` justifies my choice. -`mdropFirstT` has more specific type, and rejects more -implementations, such as the one that returns a list with `42` in it -above. Thus, it has fewer implementations, in the same way that more -specific types have fewer values inhabiting them. But it can be used -in more circumstances, so it is “more general”. The generality in -terms of when a method can be used is directly proportional to the -specificity of its type. - -Java’s edge of insanity ------------------------ - -Now we have enough power to demonstrate that Scala’s integration with -Java generics is faulty. Or, more fairly, that Java’s generics are -faulty. - -Consider this method type, in Scala: - -```scala -def goshWhatIsThis[T](t: T): T -``` - -This is a pretty specific method type; there are not too many -implementations. Of course you can always perform a side effect; we -don’t track that in Scala’s type system. But what can it return? -Just `t`. - -Specifically, you can’t return `null`: - -```scala -TmTp2.scala:36: type mismatch; - found : Null(null) - required: T - def goshWhatIsThis[T](t: T): T = null - ^ -``` - -Well now, let’s convert this type to Java: - -```java -public static T holdOnNow(T t) { - return null; -} -``` - -We got away with that! And, indeed, we can call `holdOnNow` to -implement `goshWhatIsThis`, and vice versa; they’re *equivalent*. But -the type says we can’t return `null`! - -The problem is that Java adds an implicit upper bound, because it -assumes generic type parameters can only have class types chosen for -them; in Scala terms, `[T <: AnyRef]`. If we encode this constraint -in Scala, Scala gives us the correct error. - -```scala -def holdOnNow[T <: AnyRef](t: T): T = TmTp2.holdOnNow(t) - -def goshWhatIsThis[T](t: T): T = holdOnNow(t) - -TmTp2.scala:38: inferred type arguments [T] do not conform -⤹ to method holdOnNow's type parameter bounds [T <: AnyRef] - def goshWhatIsThis[T](t: T): T = holdOnNow(t) - ^ -``` - -This is forgivable on Scala’s part, because it’d be annoying to add -`<: AnyRef` to your generic methods just because you called some Java -code and it’s probably going to work out fine. I blame `null`, and -while I’m at it, I blame `Object` having any methods at all, too. -We’d be better off without these bad features. - -In -[the next part, “What happens when I forget a refinement?”](forget-refinement-aux.md), -we’ll talk about what happens when you forget refinements for things -like `MList`, and how you can avoid that while simplifying your -type-member-binding code. - -*This article was tested with Scala 2.11.7 and Java 1.8.0_45.* diff --git a/src/blog/minicheck.md b/src/blog/minicheck.md deleted file mode 100644 index d9f79ec2..00000000 --- a/src/blog/minicheck.md +++ /dev/null @@ -1,765 +0,0 @@ -{% - author: ${larsrh} - date: "2016-10-17" - tags: [technical] - katex: true -%} - -# Let's build ourselves a small ScalaCheck - -_[ScalaCheck](http://scalacheck.org/) is a well-known property-based testing library, based on ideas from Haskell's [QuickCheck](https://hackage.haskell.org/package/QuickCheck). -It is also a [Typelevel project](/projects/README.md). -In this post, I'd like to show some of the underlying mechanisms, stripped down to the bare minimum._ - -Testing with properties is well-understood in academia and widely used in parts of the industry – namely the parts which embrace functional programming. -However, the design space of property-testing libraries is rather large. -I think it is high time to talk about various tradeoffs done in libraries. -Here, I'd like to contribute by implementing a ScalaCheck clone from scratch using a very similar design and explaining the design choices along the way. - -This is not an introduction to property testing. -However, it can be read as a guide to implementation ideas. -QuickCheck, ScalaCheck and the like are nice examples of functional library design, but their internals are often obscured by technicalities. -I hope that by clearing up some of the concepts it will become easier to read their code and perhaps designing your own property-testing library. - - -## The first design decision - -The basic point of a property testing library is providing an interface looking roughly like this: - -```scala -class Prop { - def check(): Unit = ??? -} - -object Prop { - def forAll[A](prop: A => Boolean): Prop = ??? -} -``` - -Now, you can use that in your test code: - -```scala -Prop.forAll { (x: Int) => - x == x -} -``` - -This expresses that you have a property which is parameterized on a single integer number. -Hence, the library must somehow provide these integer numbers. -The original Haskell QuickCheck, ScalaCheck and many other libraries use a _random generator_ for this. -This comes with a number of advantages: - -* It is relatively simple and efficient to implement. -* Random number generators compose exceedingly well. -* The confidence in the tests can be increased by just generating more inputs. -* Depending on the random distributions of the generators used, you have chances that both “exotic” and “common” inputs are covered. -* In practice, it turns out that random generators are decent at finding edge cases. - -But it is also not without problems: - -* For more complex inputs, the default generators are basically useless, because they will produce invalid input most of the time. -* Filtering random values before feeding them into the property can dramatically slow down the whole process. -* By default, it is non-deterministic (but there are remedies available). -* Generation of _random functions_ to be used as inputs for higher-order properties is quite round-about. - -Of course, there are other possible design choices: - -* [SmallCheck](https://hackage.haskell.org/package/smallcheck) instead enumerates _all_ values up to a certain size. - For example, you can specify that you want to test some function over integer lists with all lists up to size 5, containing all integers between -5 and +5. - In some situations, namely when your input is finite, you can even _exhaustively_ check all inputs, which is equivalent to a _proof_ that your program is correct. - The disadvantage is that even for small sizes, the input space may explode exponentially or worse (e.g. when generating lists of lists). -* [Isabelle](https://isabelle.in.tum.de) Quickcheck supports multiple modes, including _narrowing_, which is a form of symbolically exploring the search space. - This is based on Haskell's [Lazy SmallCheck](https://hackage.haskell.org/package/lazysmallcheck) (see also the [paper by Runciman et.al.](https://www.cs.york.ac.uk/fp/smallcheck/smallcheck.pdf)). - The basic idea is that we can try to evaluate properties with _partially-defined inputs_ and refine them on demand. - -**For this post, we're assuming that random generation is a given.** - -## The second design decision - -> Do we want to do this purely or poorly? - -Of course, this motto is tongue-in-cheek. -Just because something isn't _pure_ doesn't mean that it is _poor._ - -To understand the design space here, let's focus on the smallest building block: A primitive random generator. -There are two possible ways to model this. -The _mutable_ way is what Java, Scala and many other languages offer in their libraries: - -```scala -trait Random { - def nextInt(min: Int, max: Int): Int - def nextFloat(): Float - def nextItem[A](pool: List[A]): A -} -``` - -By looking at the types alone, we can already see that two subsequent calls of `nextInt` will produce different results; the interface is thus _impure._ - -The _pure_ way is to make the internal state (also known as “seed” in the context of random generators) explicit: - -```scala -trait Seed { - def nextInt(min: Int, max: Int): (Int, Seed) - def nextFloat: (Float, Seed) - def nextItem[A](pool: List[A]): (A, Seed) -} - -object Seed { - def init(): Seed = ??? -} -``` - -Because this is difficult to actually use (don't mix up the `Seed` instances and use them twice!), one would wrap this into a state monad: - -```scala -class Random[A](private val op: Seed => (A, Seed)) { self => - def run(): A = op(Seed.init())._1 - - def map[B](f: A => B): Random[B] = - new Random[B]({ seed0 => - val (a, seed1) = self.op(seed0) - (f(a), seed1) - }) - - def flatMap[B](f: A => Random[B]): Random[B] = - new Random[B]({ seed0 => - val (a, seed1) = self.op(seed0) - f(a).op(seed1) - }) - - override def toString: String = "" -} - -object Random { - def int(min: Int, max: Int): Random[Int] = new Random(_.nextInt(min, max)) - val float: Random[Float] = new Random(_.nextFloat) -} -``` - -Now we can use Scala's `for` comprehensions: - -```scala -for { - x <- Random.int(-5, 5) - y <- Random.int(-3, 3) -} yield (x, y) -// res2: Random[(Int, Int)] = -``` - -The tradeoffs here are the usual when we're talking about functional programming in Scala: Reasoning ability, convenience, performance, … -In the pure case, there are also multiple other possible encodings, including free monads. -Luckily, this blog covers that topic in another [post](edsls-part-1.md). - -How do other libraries fare here? - -* ScalaCheck up to 1.12.x uses a mutable random number generator; namely, `scala.util.Random`. -* ScalaCheck 1.13.x+ uses its own, immutable implementation. -* Another Scala library for property testing, [scalaprops](https://github.com/scalaprops/scalaprops), does not. - I'm not familiar with it, but as far as I can tell from the [sources](https://github.com/scalaprops/scalaprops/blob/v0.3.4/gen/src/main/scala/scalaprops/Rand.scala), it's similar to the `Seed` trait from above, and there is also an additional state-monadic layer on top of it. -* In QuickCheck, the encoding seems strange at first. - They use a primitive generator which looks a lot like `Seed`, but they don't use the updated seed. - Instead, their approach is via an additional primitive `split` of type `Seed => (Seed, Seed)`, which gets used to “distribute” randomness during composition (see the [paper by Claessen & Pałka](http://publications.lib.chalmers.se/records/fulltext/183348/local_183348.pdf) about the theory behind that). - It is worth noting that Java 8 introduced a [`SplittableRandom`](https://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html) class. - -**For this post, we're assuming that mutable state is a given.** -We'll use `scala.util.Random` (because it's readily available) in a similar fashion to ScalaCheck 1.12.x. - -## The third design decision - -Asynchronous programming is all the rage these days. -This means that many functions will not return plain values of type `A`, but rather `Future[A]`, `Task[A]` or some other similar type. -For our testing framework, this poses a challenge: -If our properties call such asynchronous functions, the framework needs to know how to deal with a lot of `Future[Boolean]` values. -On the JVM, although not ideal, we could fall back to blocking on the result and proceed as usual. -On [Scala.js](http://www.scala-js.org/), this won't fly, because you just can't block in JavaScript. - -Most general-purpose testing frameworks, like Specs2, have a [story about this](https://etorreborre.github.io/specs2/guide/SPECS2-3.8.5/org.specs2.guide.Matchers.html), enabling asynchronous checking of assertions. - -In theory, it's not a problem to support this in a property testing library. -But in practice, there are some complications: - -- Has the library been designed that way? If not, can we change it to support it? - This is a real problem: It took quite some time and some significant refactorings to support `Future`s in [ScalaTest](http://www.scalatest.org/user_guide/async_testing). -- Should random generators also return `Future` values? - We can easily imagine wanting to draw from a pool of inputs stemming from a database, or possibly to get better randomness from [random.org](https://www.random.org/). - (The latter is a joke.) -- What async type constructor should we support? - [The built-in one](http://www.scala-lang.org/files/archive/api/2.11.8/#scala.concurrent.Future)? - [Monix' `Task`](https://monix.io/docs/2x/eval/task.html)? - [fs2's `Task`](https://github.com/functional-streams-for-scala/fs2/blob/v0.9.1/core/shared/src/main/scala/fs2/Task.scala)? - All of them? - -If in the first design decisions we had chosen exhaustive generators, this problem would be even tougher, because designing a correct effectful stream type (of all possible inputs) is not trivial. - -**For this post, we're assuming that we're only interested in synchronous properties, or can always block.** -However, I'd like to add, I'd probably try to incorporate async properties right from the start if I were to implement a testing library from scratch. - -What about the existing libraries? - -* ScalaCheck itself does not support asynchronous properties. -* In SmallCheck, both [generators](https://hackage.haskell.org/package/smallcheck-1.1.1/docs/Test-SmallCheck-Series.html#t:Serial) and [properties](https://hackage.haskell.org/package/smallcheck-1.1.1/docs/Test-SmallCheck.html#t:Property) may be monadic. -* QuickCheck supports arbitrary I/O actions in a property via a function called [`morallyDubiosIOProperty`](https://hackage.haskell.org/package/QuickCheck-2.9.2/docs/Test-QuickCheck-Property.html#v:morallyDubiousIOProperty) (nowadays just `ioProperty`). - But there is also [more advanced support](https://hackage.haskell.org/package/QuickCheck-2.9.2/docs/Test-QuickCheck-Monadic.html) for monadic testing. - -## The fourth design decision - -Let's summarize what we have so far: - -1. randomly generated inputs -2. ... using a stateful primitive generator -3. synchronous properties - -Now, I'd like to talk about how to “package” random generators. -Earlier, we've only seen random integer and floating-point numbers, but of course, we want something more complex, including custom data structures. -It is convenient to abstract over this and specify the concept of a _generator_ for type `A`. -The idea is to make a generator for a type as “general” as possible and then provide combinators to compose them. - -```scala -import scala.util.Random - -trait Gen[T] { - def generate(rnd: Random): T -} - -object Gen { - val int: Gen[Int] = new Gen[Int] { - def generate(rnd: Random): Int = - rnd.nextInt() - } -} -``` - -An obvious combinator is a generator for tuples: - -```scala -def zip[T, U](genT: Gen[T], genU: Gen[U]): Gen[(T, U)] = new Gen[(T, U)] { - def generate(rnd: Random): (T, U) = - (genT.generate(rnd), genU.generate(rnd)) -} -``` - -But we still have a problem: -There is currently no way to talk about the _size_ of the generated inputs. -Let's say we want to check an expensive algorithm over lists, for example with a complexity of @:math \mathcal O(n^3) @:@ over lists. -A naive implemenation of a list generator would take a a random size, and then give you some generator for lists. -The problem arises at the use site: Whenver you want to change the size of the generated inputs, you need to change the expression constructing the generator. - -But we'd like to do better here: - -**For this post, there should be a way to specify a maximum size of generated values, together with a way to influence that size in the tests without having to modify the generators.** - -Here's how we can do that: - -```scala -import scala.util.Random - -trait Gen[T] { - def generate(size: Int, rnd: Random): T - - override def toString: String = "" -} - -object Gen { - - val int: Gen[Int] = new Gen[Int] { - def generate(size: Int, rnd: Random): Int = { - val range = size * 2 + 1 - rnd.nextInt(range) - size - } - } - - def list[A](genA: Gen[A]): Gen[List[A]] = new Gen[List[A]] { - def generate(size: Int, rnd: Random): List[A] = { - val length = rnd.nextInt(size + 1) - List.fill(length)(genA.generate(size, rnd)) - } - } - -} -``` - -We can now check this (note that for the purpose of this post we'll be using fixed seeds): - -```scala -def printSample[T](genT: Gen[T], size: Int, count: Int = 10): Unit = { - val rnd = new Random(0) - for (i <- 0 until size) - println(genT.generate(size, rnd)) -} -``` - -```scala -scala> printSample(Gen.int, 10) -2 -6 --6 --8 -1 -4 --8 -5 --4 --8 - -scala> printSample(Gen.int, 3) -2 --1 -1 - -scala> printSample(Gen.list(Gen.int), 10) -List() -List(-6, -8, 1, 4, -8, 5) -List(-8, -2) -List(4, 6) -List(4, 0) -List(10, 4, -9, -7, 7) -List(-8, 6, -4, 9, -1, 10, 4, 7, -8) -List(1, 7, -7, 4, 0, 5, 4, 9, 7, 4) -List(5, 9, -3, 3, -10) -List() - -scala> printSample(Gen.list(Gen.int), 3) -List(-1, 1) -List(1, -3) -List(-2, 3) -``` - -That's already pretty cool. -But there's another hidden design decision here: -We're using the same size on all sub-elements in the generated thing. -For example, in `Gen.list`, we're just passing the size through to the child generator. - -SmallCheck does that differently: The “size” is defined to be the total number of constructors in the generated value. -For integer numbers, the “number of constructors” is basically the number itself. -For example, the value `List(1, 2)` has size @:math 2 @:@ in our framework (length of the list), but size @:math 1 + 2 + 2 = 5 @:@ in SmallCheck (roughly: size of all elements plus length of list). - -Of course, our design decision might mean that stuff grows too fast. -The explicit size parameter can be used to alleviate that, especially for writing recursive generators: - -```scala -def recList[T](genT: Gen[T]): Gen[List[T]] = new Gen[List[T]] { - // extremely stupid implementation, don't use it - def generate(size: Int, rnd: Random): List[T] = - if (rnd.nextInt(size + 1) > 0) - genT.generate(size, rnd) :: recList(genT).generate(size - 1, rnd) - else - Nil -} -// recList: [T](genT: Gen[T])Gen[List[T]] - -printSample(recList(Gen.int), 10) -// List() -// List(-6, 1, -6) -// List(-8, 8, 4, 7, 0, 5, -4) -// List(-8) -// List(9, -3, 4) -// List(1, 3, -7, -2, -3, 0, -3, 3) -// List() -// List(10, 5, -8, -4, -5, 4, -1) -// List(-5, 9, 7) -// List(-8) -``` - -We can also provide a combinator for this: - -```scala -def resize[T](genT: Gen[T], newSize: Int): Gen[T] = new Gen[T] { - def generate(size: Int, rnd: Random): T = - genT.generate(newSize, rnd) -} -``` - -That one is useful because in reality ScalaCheck's `generate` method takes some more parameters than just the size. -Some readers might be reminded that this is just the reader monad and its `local` combinator in disguise. - -## Some sugar - -In order to make these generators nicely composable, we can leverage `for` comprehensions. -We just need to implement `map`, `flatMap` and `withFilter`: - -```scala -import scala.util.Random - -trait Gen[T] { self => - def generate(size: Int, rnd: Random): T - - // Generate a value and then apply a function to it - def map[U](f: T => U): Gen[U] = new Gen[U] { - def generate(size: Int, rnd: Random): U = - f(self.generate(size, rnd)) - } - - // Generate a value and then use it to produce a new generator - def flatMap[U](f: T => Gen[U]): Gen[U] = new Gen[U] { - def generate(size: Int, rnd: Random): U = - f(self.generate(size, rnd)).generate(size, rnd) - } - - // Repeatedly generate values until one passes the check - // (We would usually call this `filter`, but Scala requires us to - // call it `withFilter` in order to be used in `for` comprehensions) - def withFilter(p: T => Boolean): Gen[T] = new Gen[T] { - def generate(size: Int, rnd: Random): T = { - val candidate = self.generate(size, rnd) - if (p(candidate)) - candidate - else // try again - generate(size, rnd) - } - } - - override def toString: String = "" -} - -object Gen { - - // unchanged from above - - val int: Gen[Int] = new Gen[Int] { - def generate(size: Int, rnd: Random): Int = { - val range = size * 2 + 1 - rnd.nextInt(range) - size - } - } - - def list[A](genA: Gen[A]): Gen[List[A]] = new Gen[List[A]] { - def generate(size: Int, rnd: Random): List[A] = { - val length = rnd.nextInt(size + 1) - List.fill(length)(genA.generate(size, rnd)) - } - } - -} -``` - -Look how simple composition is now: - - - - -```scala -case class Frac(numerator: Int, denominator: Int) -// defined class Frac - -val fracGen: Gen[Frac] = - for { - num <- Gen.int - den <- Gen.int - if den != 0 - } yield Frac(num, den) -// fracGen: Gen[Frac] = - -printSample(fracGen, 10) -// Frac(2,6) -// Frac(-6,-8) -// Frac(1,4) -// Frac(-8,5) -// Frac(-4,-8) -// Frac(-2,-8) -// Frac(4,6) -// Frac(1,4) -// Frac(0,-10) -// Frac(10,4) -``` - -And we can even read the construction nicely: “First draw a numerator, then draw a denominator, then check that the denominator is not zero, then construct a fraction.” -However, we need to be cautious with the filtering. -If you look closely at the implementation of `withFilter`, you can see that there is potential for an infinite loop. -For example, when you pass in the filter `_ => false`. -It will just keep generating values and then discard them. -How do existing frameworks alleviate this? - -* QuickCheck has two filter combinators: one that returns `Gen[A]` as above, and one that return `Gen[Option[A]]`. - The latter uses a number of tries and if they all fail, terminates and returns `None`. - The former uses the latter, but keeps increasing the size parameter. - Of course, this might not terminate. -* ScalaCheck's `filter` method returns `Gen[A]`, but the possibility of failure is encoded in the return type of its equivalent of the `generate` method, which always returns `Option[T]`. - But there is also a combinator which retries until it finds a valid input, called `retryUntil`. - -As a side note: `Gen` as it is right now is _definitely not_ a valid monad, because it internally relies on mutable state. -But in my opinion, it is still justified to offer the `map` and `flatMap` methods, but don't give a `Monad` instance. -This prevents you from shoving `Gen` into functions which expect lawful monads. - -It's still tedious to having to construct these generators by hand. -Both QuickCheck and ScalaCheck introduce a thin layer atop generators, called `Arbitrary`. -This is just a type class which contains a generator, nothing more. -Here's how it would look like in Scala: - -```scala -trait Arbitrary[T] { - def gen: Gen[T] -} - -// in practice we would put that into the companion object -//object Arbitrary { - - implicit val arbitraryInt: Arbitrary[Int] = new Arbitrary[Int] { - def gen = Gen.int - } - -//} -``` - -Based on this definition, ScalaCheck provides a lot of pre-defined instances for all sorts of types. -For your custom types, the idea is that you define a low-level generator and wrap it into an implicit `Arbitrary`. -Then, in your tests, you just use the implicitly provided generator, and avoid to drop down to constructing them manually. - -The purpose of the additional layer is explained easily: It is common to have multiple `Gen[T]` for the same `T` depending on which context it is needed in. -But there should only be one `Arbitrary[T]` for each `T`. -For example, you might have `Gen[Int]` for positive and negative integers, but you only have a single `Arbitrary[Int]` which covers all integers. -You use the latter when you actually need to supply an integer to your property, and the former to construct more complex generators, like for `Frac` above. - -## The fifth design decision - -This is where everything really comes together. -We're now looking at how to use `Gen` to implement the desired `forAll` function we've seen early in the introduction of the post, and how that is related to the `Prop` type I didn't define. -I'll readily admit that the following isn't really a design decision per se, because we'll be guided by the presence of type classes in Scala. -Still, one could reasonably structure this differently, and in fact, the design of the `Prop` type in e.g. QuickCheck is much more complex than what you'll see. - -The rest of this post will now depart from the way it's done in ScalaCheck, although the ideas are still similar. -Instead, I'll try to show a simplified version without introducing complications required to make it work nicely. - -Let's start with the concept of a _property._ -A property is something that we can _run_ and which returns a _result_. -The result should ideally be something like a boolean: Either the property holds or it doesn't. -But one of the main features of any property testing library is that it will return a counterexample for the inputs where the property doesn't hold. -Hence, we need to store this counterexample in the failure case. -In practice, the result type would be much richer, with attached labels, reasons, expectations, counters, ... and more diagnostic fields. - -```scala -sealed trait Result -case object Success extends Result -final case class Failure(counterexample: List[String]) extends Result - -object Result { - def fromBoolean(b: Boolean): Result = - if (b) - Success - else - // if it's false, it's false; no input has been produced, - // so the counterexample is empty - Failure(Nil) -} -``` - -You'll note that I've used `List[String]` here, because in the end we only want to print the counterexample on the console. -ScalaCheck has a dedicated `Pretty` type for that. -We could do even more fancy things here if we wanted to, but let's keep it simple. - -Now we define the `Prop` type: - -```scala -trait Prop { - def run(size: Int, rnd: Random): Result - - override def toString: String = "" -} -``` - -What's missing is a way to construct properties. -Sure, we could implement the trait manually in our tests, but that would be tedious. -Type classes to the rescue! -We call something _testable_ if it can be converted to a `Prop`: - -```scala -trait Testable[T] { - def asProp(t: T): Prop -} - -// in practice we would put these into the companion object -//object Testable { - - // Booleans can be trivially converted to a property: - // They are already basically a `Result`, so no need - // to run anything! - implicit val booleanIsTestable: Testable[Boolean] = new Testable[Boolean] { - def asProp(t: Boolean): Prop = new Prop { - def run(size: Int, rnd: Random): Result = - Result.fromBoolean(t) - } - } - - // Props are already `Prop`s. - implicit val propIsTestable: Testable[Prop] = new Testable[Prop] { - def asProp(t: Prop): Prop = t - } - -//} -``` - -Now we're all set: - -```scala -def forAll[I, O](prop: I => O)(implicit arbI: Arbitrary[I], testO: Testable[O]): Prop = - new Prop { - def run(size: Int, rnd: Random): Result = { - val input = arbI.gen.generate(size, rnd) - val subprop = testO.asProp(prop(input)) - subprop.run(size, rnd) match { - case Success => - Success - case Failure(counterexample) => - Failure(input.toString :: counterexample) - } - } - } -``` - -Let's unpack this step by step. - -1. We're taking a function from `I => O`. - This is supposed to be our parameterized property, for example `{ (x: Int) => x == x }`. - Because we abstracted over values that can be generated (`Arbitrary`) and things that can be tested (`Testable`), the input and output types are completely generic. - In the `implicit` block, we're taking the instructions of how to fit everything together. -2. We're constructing a `Prop`; that is, a thing that we can run and that produces a boolean-ish `Result`. -3. To run the property, we need to construct a random input. - We can use the `Gen[I]` which we get from the `Arbitrary[I]`. -4. We pass that `I` into the parameterized property. - To stick with the example, we evaluate the anonymous function `{ (x: Int) => x == x }` at input `5`, and obtain `true`. -5. We convert the result to a `Prop` again. - This allows us to recursively nest `forAll`s, for example when we need two inputs. -6. We run the resulting property and check if it fails. - If it does, we prepend the generated input to the counterexample. - In the nested scenario, this allows us to see all generated inputs and the order in which we sticked them into the property. - -At this point we should look at an example. - -```scala -val propReflexivity = - forAll { (x: Int) => - x == x - } -// propReflexivity: Prop = -``` - -Cool, but how do we run this? - -Remember that our tool is supposed to evaluate a property on multiple inputs. -All these evaluations will produce a `Result`. -Hence, we need to merge those together into a single result. -We'll also define a convenient function that runs a property multiple times on different sizes: - -```scala -def merge(rs: List[Result]): Result = - rs.foldLeft(Success: Result) { - case (Failure(cs), _) => Failure(cs) - case (Success, Success) => Success - case (Success, Failure(cs)) => Failure(cs) - } - -def check[P](prop: P)(implicit testP: Testable[P]): Unit = { - val rnd = new Random(0) - val rs = - for (size <- 0 to 100) - yield testP.asProp(prop).run(size, rnd) - merge(rs.toList) match { - case Success => - println("✓ Property successfully checked") - case Failure(counterexample) => - val pretty = counterexample.mkString("(", ", ", ")") - println(s"✗ Property failed with counterexample: $pretty") - } -} -``` - -What is happening here? - -1. The `merge` function takes a list of `Result`s and returns the first `Failure`, if it exists. - Otherwise it returns `Success`. - In case there are multiple `Failure`s, it doesn't care and just discards the later ones. -2. The `check` function initializes a fresh random generator. -3. We have fixed the maximum size to 100 and will run the passed property with each size from 0 to 100. - This ensures that we get a nice coverage of various input sizes. - An obvious optimisation here would be to stop after the first failure, instead of merging the results in a subsequent step. -4. In case there's a failure, we just print the counterexample. - -Let's check our property! - -```scala -scala> check(propReflexivity) -✓ Property successfully checked -``` - -... and how about something wrong? - -```scala -scala> check(forAll { (x: Int) => - | x > x - | }) -✗ Property failed with counterexample: (0) -``` - -## Some more sugar - -Okay, we're almost done. -The only tedious thing that remains is that we have to use the `forAll` combinator, especially in the nested case. -It would be great if we could just use `check` and pass it a function. -But since we've used type classes for everything, we're in luck! - -```scala -implicit def funTestable[I : Arbitrary, O : Testable]: Testable[I => O] = new Testable[I => O] { - def asProp(f: I => O): Prop = - // wait for it ... - // ... - // ... - // it's really simple ... - forAll(f) -} -``` - -Now we can check our functions even easier! - -```scala -scala> check { (x: Int) => - | x == x - | } -✓ Property successfully checked - -scala> check { (x: Int) => - | x > x - | } -✗ Property failed with counterexample: (0) - -scala> check { (x: Int) => (y: Int) => - | x + y == y + x - | } -✓ Property successfully checked - -scala> check { (x: Int) => (y: Int) => - | x + y == x * y - | } -✗ Property failed with counterexample: (0, 1) -``` - -Now, if you look closely, you can basically get rid of the `Prop` class and define it as - -```scala -type Prop = Gen[Result] -``` - -If you think about this for a moment, it makes sense: A “property” is really just a thing which feeds on randomness and produces a result. -The only thing left is to define a driver which runs a couple of iterations and gathers the results; in our implementation, that's the `check` function. -I encourage you to spell out the other functions (e.g. `forAll`), and you will notice that our `Prop` trait is indeed isomorphic to `Gen[Result]`. -In practice, QuickCheck uses such a representation (although with some more contraptions). - -## Summary - -It turns out that it's not that hard to write a small property-testing library. -I'm going to stop here with the implementation, although there are still some things to explore: - -* How to [get rid of the boilerplate](https://github.com/alexarchambault/scalacheck-shapeless) to generate “boring” data structures? -* How to [generate functions](https://hackage.haskell.org/package/QuickCheck-2.9.2/docs/Test-QuickCheck-Arbitrary.html#t:CoArbitrary)? -* How to improve usability? -* How to [bundle up a bunch of properties](https://github.com/typelevel/discipline)? -* How to [show useful counterexamples](https://github.com/rickynils/scalacheck/blob/1.13.2/doc/UserGuide.md#test-case-minimisation)? -* How to test [conditional properties](https://github.com/rickynils/scalacheck/blob/1.13.2/doc/UserGuide.md#properties)? -* How to test the library itself? -* How to make sure that your generators produce reasonable values? -* How to make sure that your generators [cover a wide range of values](https://hackage.haskell.org/package/QuickCheck-2.9.2/docs/Test-QuickCheck.html#v:label)? -* How to test [polymorphic properties](https://github.com/larsrh/polycheck)? -* ... - -Finally, I'd like to note that there are many more libraries out there than I've mentioned here, some of which depart more, some less, from the original Haskell implementation. -They even exist for not-functional languages, e.g. [Javaslang](http://www.javaslang.io/javaslang-docs/#_property_checking) for Java or [Hypothesis](http://hypothesis.works/) for Python. - -_Correction: In a previous version of this post, I incorrectly stated that ScalaCheck uses a mutable random generator. This is only true up to ScalaCheck 1.12.x. I have updated that section in the post._ diff --git a/src/blog/monad-transformer-variance.md b/src/blog/monad-transformer-variance.md deleted file mode 100644 index 361925a2..00000000 --- a/src/blog/monad-transformer-variance.md +++ /dev/null @@ -1,180 +0,0 @@ -{% - author: ${ceedubs} - date: "2018-09-29" - tags: [technical] -%} - -# Variance of Monad Transformers - -A question that [repeatedly](https://github.com/typelevel/cats/issues/556) [pops](https://github.com/typelevel/cats/issues/2310) [up](https://github.com/typelevel/cats/issues/2538) about [Cats](https://typelevel.org/cats/) is why monad transformer types like `OptionT` and `EitherT` aren't covariant like their `Option` and `Either` counterparts. This blog post aims to answer that question. - -# Covariance - -What does it mean to say that `Option` is covariant? It means that an `Option[B]` is allowed to be treated as an `Option[A]` if `B` is a subtype of `A`. For example: - -```scala -abstract class Err(val msg: String) -final case class NotFound(id: Long) extends Err(s"Not found: $id") -``` - -```scala -val optionNotFound: Option[NotFound] = Some(NotFound(42L)) -// optionNotFound: Option[NotFound] = Some(NotFound(42)) - -optionNotFound: Option[Err] -// res0: Option[Err] = Some(NotFound(42)) -``` - -Great. If you want to treat your `Option[NotFound]` as an `Option[Err]`, you are free to. - -This is made possible because the `Option` type is declared as `sealed abstract class Option[+A]`, where the `+` in front of the `A` means that it is covariant in the `A` type parameter. - -What happens if we try to do the same with `OptionT`? - -```scala -import cats.Eval -import cats.data.OptionT -``` - -```scala -val optionTNotFound: OptionT[Eval, NotFound] = OptionT(Eval.now(optionNotFound)) -// optionTNotFound: cats.data.OptionT[cats.Eval,NotFound] = OptionT(Now(Some(NotFound(42)))) -``` - -```scala -optionTNotFound: OptionT[Eval, Err] -// :17: error: type mismatch; -// found : cats.data.OptionT[cats.Eval,NotFound] -// required: cats.data.OptionT[cats.Eval,Err] -// Note: NotFound <: Err, but class OptionT is invariant in type A. -// You may wish to define A as +A instead. (SLS 4.5) -// optionTNotFound: OptionT[Eval, Err] -// ^ -``` - -The compiler complains that an `OptionT[Eval, NotFound]` is _not_ an `OptionT[Eval, Err]`, but it also suggests that we may be able to fix this by using `+A` like is done with `Option`. - -`OptionT` in Cats is defined as: - -```scala -final case class OptionT[F[_], A](value: F[Option[A]]) -``` - -Let's try to make the suggested change with an experimental `MyOptionT` structure: - -```scala -final case class MyOptionT[F[_], +A](value: F[Option[A]]) -// :14: error: covariant type A occurs in invariant position in type => F[Option[A]] of value value -// final case class MyOptionT[F[_], +A](value: F[Option[A]]) -// ^ -``` - -Now it's complaining that `A` is a covariant type but shows up in an invariant position. We'll discuss more about what this means later in the post, but for now let's just try declaring `F` as covariant: - -```scala -final case class CovariantOptionT[F[+_], +A](value: F[Option[A]]) -``` - -```scala -val covOptionTNotFound: CovariantOptionT[Eval, NotFound] = CovariantOptionT(Eval.now(optionNotFound)) -// covOptionTNotFound: CovariantOptionT[cats.Eval,NotFound] = CovariantOptionT(Now(Some(NotFound(42)))) - -covOptionTNotFound: CovariantOptionT[Eval, Err] -// res2: CovariantOptionT[cats.Eval,Err] = CovariantOptionT(Now(Some(NotFound(42)))) -``` - -Woohoo! Problem solved, right? - -Well, not exactly. This works great if `F` is in fact covariant, but what if it's not? For example, the JSON library [circe](https://circe.github.io/circe/) has a `Decoder` type that _could_ be covariant but isn't (at least as of circe 0.10.0). With the invariant `OptionT` in Cats we can do something like this: - -```scala -import io.circe.Decoder - -def defaultValueDecoder[A](defaultValue: A, optionDecoder: Decoder[Option[A]]): Decoder[A] = - OptionT(optionDecoder).getOrElse(defaultValue) -``` - -However, we can't do the same with our `CovariantOptionT`, because we can't even create an `OptionT` where the `F` type isn't covariant: - -```scala -def wrap[A](optionDecoder: Decoder[Option[A]]): CovariantOptionT[Decoder, A] = - CovariantOptionT[Decoder, A](optionDecoder) -// :18: error: kinds of the type arguments (io.circe.Decoder,A) do not conform to the expected kinds of the type parameters (type F,type A). -// io.circe.Decoder's type parameters do not match type F's expected parameters: -// type A is invariant, but type _ is declared covariant -// CovariantOptionT[Decoder, A](optionDecoder) -// ^ -``` - -In this particular case, `Decoder` _could_ be declared as covariant, but it's not. It would be unfortunate to lose the ability to use a monad transformer because a 3rd party library chose not to make a type covariant. And perhaps more importantly, sometimes you might want to use an `OptionT` with an `F` type that fundamentally isn't covariant in nature, such as `Monoid` (which is invariant) or `Order` (which is contravariant in nature and is declared as invariant in its type definition). Later in this post there will be some examples of using `OptionT` with a contravariant functor type (`Eq`) to gain acess to a handy `contramap` operation. - -# Workaround - -It's completely reasonable to want to be able to take your `OptionT[Eval, NotFound]` and treat it as an `OptionT[Eval, Err]`, and in some cases it may only be typed as `OptionT[Eval, NotFound]` because of type inference picking a more specific type than you intended. Luckily Cats has some handy methods to make this easy: - -```scala -import cats.implicits._ -``` - -```scala -optionTNotFound.widen[Err] -// res4: cats.data.OptionT[cats.Eval,Err] = OptionT(Now(Some(NotFound(42)))) -``` - -The `.widen` method is available for any type that has a `Functor`. If you are working with a type that represents a `Bifunctor`, such as `EitherT`, then you can use `widen` for the type on the right and `leftWiden` for the type on the left: - -```scala -import cats.data.EitherT -``` - -```scala -val eitherTNotFound: EitherT[Eval, NotFound, Some[Int]] = EitherT.leftT[Eval, Some[Int]](NotFound(42L)) -// eitherTNotFound: cats.data.EitherT[cats.Eval,NotFound,Some[Int]] = EitherT(Now(Left(NotFound(42)))) - -eitherTNotFound.widen[Option[Int]] -// res5: cats.data.EitherT[cats.Eval,NotFound,Option[Int]] = EitherT(Now(Left(NotFound(42)))) - -eitherTNotFound.leftWiden[Err] -// res6: cats.data.EitherT[cats.Eval,Err,Some[Int]] = EitherT(Now(Left(NotFound(42)))) -``` - -Similarly there is a `narrow` method for contravariant functors: - -```scala -import cats.Eq -``` - -```scala -val optionTEqErr: OptionT[Eq, Err] = OptionT(Eq[Option[String]]).contramap((err: Err) => err.msg) -// optionTEqErr: cats.data.OptionT[cats.Eq,Err] = OptionT(cats.kernel.Eq$$anon$103@65c9a471) - -optionTEqErr.narrow[NotFound] -// res7: cats.data.OptionT[cats.Eq,NotFound] = OptionT(cats.kernel.Eq$$anon$103@65c9a471) -``` - -# Back to that compile error... - -Let's return to that `covariant type A occurs in invariant position` compile error that was triggered when we tried to declare `final case class MyOptionT[F[_], +A](value: F[Option[A]])`. Why did this happen? - -Pretend for a minute that the Scala compiler _did_ allow us to do this. We could then write: - -```scala -val eqOptionNotFound: Eq[Option[NotFound]] = Eq.instance[Option[NotFound]]{ - case (None, None) => true - case (None, Some(_)) => false - case (Some(_), None) => false - case (Some(x), Some(y)) => x.id === y.id -} -``` - -```scala -val optionTEqNotFound: MyOptionT[Eq, NotFound] = MyOptionT(eqOptionNotfound) // only allowed in our pretend world - -val optionTEqErr: MyOptionT[Eq, Err] = optionTEqNotFound // only allowed in our pretend world -``` - -Because `MyOptionT` would be covariant in `A`, this `MyOptionT[Eq, NotFound]` could be treated as a `MyOptionT[Eq, Err]`. That is, it would have a `value` that is an `Eq[Option[Err]]`. But if you look at how we implemented our equality check, it's taking two `NotFound` instances and comparing their `id` fields. For a general `Err`, we have no guarantee that it will be a `NotFound` and that it will have an `id` field. We can't treat an `Eq[Option[NotFound]]` as an `Eq[Option[Err]]`, because `Eq` is contravariant and _not_ covariant in nature. The `covariant type A occurs in invariant position` message that the scala compiler gave us was the compiler correctly identifying that our code was unsound. - -# Conclusion - -There are some use-cases in which having monad transforers such as `OptionT` and `EitherT` be defined as covariant would be convenient, such as helping to nudge type inference in the right direction. However, if Cats were to define these types as covariant, it would eliminate the possibility of using them with non-covariant types. Forcing covariance in Cats would be overly restrictive, since third-party libraries might not declare types as covariant, and monad transformers can be useful for invariant a contravariant types. Luckily, methods such as `widen`, `leftWiden`, and `narrow` provide concise solutions to turning a monad transformer (or other type that forms a functor) into the type that you need. diff --git a/src/blog/more-types-than-classes.md b/src/blog/more-types-than-classes.md deleted file mode 100644 index b8dcf746..00000000 --- a/src/blog/more-types-than-classes.md +++ /dev/null @@ -1,719 +0,0 @@ -{% - author: ${S11001001} - date: "2017-02-13" - tags: [technical] -%} - -# There are more types than classes - -As programmers, we are very incautious with our use of the word -“type”. The concept of “type” is sufficiently abstract and specific -that we are tempted to understand it by analogy, so much that we begin -to confuse analogy with sameness. - -The colloquial “runtime type”, a fair approximation of “class”, makes -it tempting to equate _types_ with “classes, interfaces, traits, that -sort of thing”, which I will name _classes_ for the rest of this -article. But they aren’t the same. The type system is much richer and -more interesting than the class system, even in Java. - -To appreciate this richness, we must stop thinking of types as classes -and stop drawing conclusions from that weak analogy. Luckily, the -compiler will readily reveal how unlike classes types are, if we ask -it some simple questions. - -## One value with class, many variables with type - -```scala -val greeting: String = "hi there!" -``` - -Here I have constructed a `String` and assigned it to a variable. (I -have also constructed the `char` array in the `String` and various -other details, but immediately handed those off to the `String` and -forgotten about them.) This value has class `String`. It has several -classes, really. - -1. `String` -2. `java.io.Serializable` -3. `CharSequence` -4. `Comparable[String]` -5. `Object`/`AnyRef` - -That seems like a lot of classes for one value. And they are genuine -classes of `greeting`, though 2-5 are all implied by #1. - -`greeting` also has all five of these _types_. We can ask the compiler -to verify that this _type_ truth holds, entirely separately from the -class truth. - -```scala -scala> (greeting: String, greeting: java.io.Serializable, - greeting: CharSequence, greeting: Comparable[String], - greeting: AnyRef) -res3: (String, java.io.Serializable, CharSequence, - Comparable[String], AnyRef) = - (hi there!,hi there!,hi there!,hi there!,hi there!) -``` - -So we have exhausted the classes, but aren’t quite done with types. - -```scala -scala> greeting: greeting.type -res0: greeting.type = hi there! -``` - -`greeting.type` is not like the other five types we just tested. It is -a strict subtype of `String`, and has no class with the same name. - -```scala -// If and only if call compiles, A is a subtype of B. -def conformance[A, B >: A]: Unit = () - -scala> conformance[greeting.type, String] - -scala> conformance[String, greeting.type] -:14: error: type arguments [String,greeting.type] do not conform - to method conformance's type parameter bounds [A,B >: A] -``` - -Fine, we can accept that object identity is represented at the type -level without our universe imploding, by inventing the theory that -this is about object identity; hold on, though: - -```scala -scala> val salutation = greeting -salutation: String = hi there! -``` - -Fine, `salutation` is just another name for `greeting`, right? - -```scala -scala> conformance[salutation.type, String] - -scala> implicitly[greeting.type =:= salutation.type] -:14: error: Cannot prove that greeting.type =:= salutation.type. -``` - -Now we have seven. I’ll spare you spelling out the induction: each new -variable defined like `salutation` will yield a new alias with a -distinct type. This is not about _objects_; this is about _variables_! - -```scala -// find a type for the literal "hi there!" -scala> val literalHiThere = shapeless.Witness("hi there!") -literalHiThere: shapeless.Witness.Aux[String("hi there!")] = shapeless.Witness$$anon$1@1d1537bb - -scala> conformance[greeting.type, literalHiThere.T] -:15: error: type arguments [greeting.type,literalHiThere.T] do not conform - to method conformance's type parameter bounds [A,B >: A] - -scala> conformance[literalHiThere.T, greeting.type] -:15: error: type arguments [literalHiThere.T,greeting.type] do not conform - to method conformance's type parameter bounds [A,B >: A] -``` - -As local variables are a strictly compile-time abstraction, and we -have anyway seen that the numbers don’t match up, that should be the -end of the “types are classes” confusion for you. But maybe this is -just some Scala oddity! And anyhow I haven’t even begun to demonstrate -the overwhelming richness of the type model as it blindingly outshines -the paucity of the class model. Let’s go further. - -## No values, infinite types: method type parameters - -To our small program of a greeting, we can add a small method. - -```scala -def pickGreeting[G](grt: G, rand: Int) = grt - -scala> pickGreeting(greeting, 42) -res9: String = hi there! -``` - -It seems like `G` must be `String`, because the argument passed to -`pickGreeting` is a string, and in that case so must its return value -be, according to the implementation. And from the perspective of this -call, [outside](existential-inside.md) -`pickGreeting`’s implementation, it is `String` indeed. - -But that implementation’s perspective matters, too; it is also part of -our program. And it sees things quite differently. We can ask its -thoughts on the matter by adding to its body - -```scala -def pickGreeting[G](grt: G, rand: Int) = { - implicitly[G =:= String] - grt -} - -:12: error: Cannot prove that G =:= String. - implicitly[G =:= String] - ^ -``` - -In fact, `G` bears no direct relationship to `String` at all. - -```scala -// replace implicitly with -conformance[G, String] - -:13: error: type arguments [G,String] do not conform - to method conformance's type parameter bounds [A,B >: A] - conformance[G, String] - ^ - -// or with -conformance[String, G] - -:13: error: type arguments [String,G] do not conform - to method conformance's type parameter bounds [A,B >: A] - conformance[String, G] - ^ -``` - -Let’s apply the pigeonhole principle. Imagine that you had a list of -every class that ever was or ever will be. Imagine that, somehow, all -of these classes, from `String` to -`AbstractFactoryMethodProxyBuilder`, were on your classpath, available -to your program. - -Next, imagine that you had the time and inclination to try the `=:=` -test with every last one of these classes. - -```scala -implicitly[G =:= javax.swing.JFrame] -implicitly[G =:= AbstractFactoryMethodProxyBuilder] -// ad [in]finitum -``` - -Your search will be futile; every class on your list-of-every-class -will give the same compiler error we got with `String`. - -So, since `G` is not equal to anything on this list, it must be -something else that doesn’t appear on the list. Because this list -contains all classes, `G` must be something other than a class. - -### It must not necessarily be anything - -It seems like it might be convenient to say “well, in this program `G` -is only ever `String` by substitution, so therefore it is, even if the -compiler doesn’t see that.” However, thinking like this misses out on -the second key advantage of type parameterization, the one not based -on multiplicity of substitution, or the type-safety of callers: -blindness. - -The implementation of type-parameterized classes and methods are -required to treat each type parameter uniquely, uniformly, and without -prejudice. The compiler enforces this by making the implementation -blind to what that parameter, like `G`, could be. It can only use what -the caller, the “outside”, has told it about `G`—arguments whose -types contain `G`, like `List[G]`, `(G, G) => G`, or `G` itself, like -the argument to `pickGreeting`. This -is -[information-hiding at the type level](information-hiding.md); -if you find information-hiding a useful tool for implementing correct -programs, you will find the same of the fresh, unique, and mysterious -types induced by each introduction of a type parameter. - -@:style(bulma-notification) - Each operation a language permits by default, not via an argument, - on values of a type parameter is a leak in this abstraction. This - includes testing the value’s class, converting to string, and - comparing to other values of supposedly utter mystery for - equality. The ability to create a “default” value is also a leak. A - function is always permitted to ask only that of these that it needs - from the caller; make them default, and this design choice is taken - away. That is why `Object#equals` is little better for - type-safety than reflection-based calls, and why total type erasure - is a desirable feature rather than a design flaw—plugging these - leaks gives the programmer as much freedom to abstract by - information-hiding as she wishes. -@:@ - -### How many calls are there? - -Put another way, when implementing the code in the scope of a type -parameter, your implementation must be equally valid _for all_ -possible `G` substitutions, including the ones that haven’t been -invented yet. This is why we call it _universal_ quantification. - -But it is not merely each declaration of a type parameter that yields -a distinct type—each call does! Consider two consecutive calls to -`pickGreeting`. - -```scala -pickGreeting(greeting, 42) -pickGreeting(33, 84) -``` - -Externally, there are two `G` types. However, the possibility of -writing this demands another level of uniqueness treatment when -typechecking `pickGreeting`’s definition: whatever `G` is now, like -`String`, it might be something else in the next call, like `Int` in -the above example. With recursion, it might even be two different -things at the same time. There’s nothing to hold this at two, either: -there may be an unbounded number of substitutions for a given type -parameter within a single program, at a single point in time. - -While `G` _may_ be the same between two invocations of `pickGreeting`, -it might not. So we have no choice but to treat the `G` types of _each -call_ as separate types. There may be infinitely many calls, so there -are so many types. - -Incidentally, the same happens for singleton types. Each time `val -greeting` comes into scope, it induces a separate singleton type. It -is easy enough to arrange for an unbounded number of scope entries in -a particular program. This isn’t so practical as the type parameter -phenomenon, though. - -## More types from variable copies - -Suppose we’d like to wait a while to compute our greeting. We can -define a type-and-class to represent that conveniently. - -```scala -// like Coyoneda Id, if that helps -sealed abstract class Later[A] { - type I - val i: I - val f: I => A -} - -def later[In, A](now: In)(later: In => A) - : Later[A] = new Later[A] { - type I = In - val i = now - val f = later -} - -val greeting3 = later(3){ - n => List.fill(n)("hi").mkString(" ") -} -``` - -How many _classes_ are involved here, in the type of `greeting3`? - -1. `Later`, obviously; -2. `Function1`, the `greeting3.f` overall class; -3. `String`, the output type of `greeting3.f`; -4. `Int`, the `I` type. - -How many types? - -The first difference is that `greeting3.I` is not `Int`. - -```scala -scala> implicitly[greeting3.I =:= Int] -:14: error: Cannot prove that greeting3.I =:= Int. - implicitly[greeting3.I =:= Int] - ^ -``` - -They are unrelated for much the same reason as `G` was unrelated to -`String` in the previous example: the only things code following -`val greeting3` may know are those embodied in the `greeting3.i` and -`greeting3.f` members. You can almost think of them as “arguments”. - -But that’s not all. - -```scala -val salut3 = greeting3 - -scala> greeting3.f(greeting3.i) -res11: String = hi hi hi - -scala> salut3.f(salut3.i) -res12: String = hi hi hi - -scala> greeting3.f(salut3.i) -:14: error: type mismatch; - found : salut3.i.type (with underlying type salut3.I) - required: greeting3.I - greeting3.f(salut3.i) - ^ - -scala> implicitly[greeting3.I =:= salut3.I] -:14: error: Cannot prove that greeting3.I =:= salut3.I. -``` - -Just like every call to `pickGreeting` induces a new `G` type, each -simple `val` copy of `greeting3` will induce a new, unique `I` -type. It doesn’t matter that they’re all the same value; this is a -matter of variables, not values, just as with singleton types. - -But that’s _still_ not all. - -## One value with class, many variable _references_ with types - -The preceding is more delicate than it seems. - -```scala -var allo = greeting3 - -scala> allo.f(allo.i) -:13: error: type mismatch; - found : Later[String]#I - required: _1.I where val _1: Later[String] - allo.f(allo.i) - ^ -``` - -All we have done differently is use a mutable `var` instead of an -immutable `val`. Why is this enough to throw a wrench in the works? - -Suppose you had another _value_ of the `Later[String]` type. - -```scala -val bhello = later("olleh")(_.reverse) -``` - -The `I` substitution here is `String`. So the `f` takes a `String` -argument, and the `I` is a `String`. - -`bhello` is of a compatible type with the `allo` var. So this -assignment will work. - -```scala -allo = bhello -``` - -In a sense, when this mutation occurs, the `I` type _also_ mutates, -from `Int` to `String`. But that isn’t quite right; types cannot -mutate. - -Suppose that this assignment happened in the middle of that line of -code that could not compile. We could imagine the sequence of events, -were it permitted. - -1. `allo.f` (which is `greeting3.f`) evaluates. It is the function - `(n: Int) => List.fill(n)("hi").mkString(" ")`. -2. The `allo = bhello` assignment occurs. -3. `allo.i` (which is `bhello.i`) evaluates. It is the string - `"olleh"`. -4. We attempt to pass `"olleh"` as the `(n: Int)` argument to complete - the evaluation, and get stuck. - -Just as it makes no difference what concrete substitutions you make -for `G`, it makes no difference whether such an assignment could ever -happen in your specific program; the compiler takes it as a -possibility because you declared a `var`. (`def allo = greeting3` gets -the same treatment, lest you think non-functional programs get to have -all the fun here.) Each _reference_ to `allo` gets a new `I` type -member. That failing line of code had two `allo` references, so was -working with two incompatible `I` types. - -Since the number of references to a variable in a program is also -unbounded...you get the picture. - -@:style(bulma-notification) - This also occurs with existential type parameters, which are equally - expressive to type members. Accordingly, Java *also* - generates new types from occurrences of expressions of existential - type. -@:@ - -## How do we tell the two apart? - -All of this is simply to say that we must be working with two separate -concepts here. - -1. The _runtime_ shape and properties of the _values_ that end up - flying around when a program actually _runs_. **This we call - class.** -2. The _compile-time_, statically-discoverable shape and properties of - the _expressions_ that fly around when a program is - _written_. **This we call type.** - -The case with `var` is revealing. Maybe the `I` type will always be -the same for a given mutable variable. But demonstrating that this -holds true for _one_ run of the program (#1, class) isn’t nearly good -enough to _prove_ that it will be true for _all_ runs of the program -(#2, type). - -We refuse to apply the term “type” to the #1, ‘class’ concept because -it does not live up to the name. The statement “these two types are -the same” is another level of power entirely; “these two values have -the same class” is extraordinarily weak by comparison. - -It is tempting to use the term “runtime type” to refer to -classes. However, in the case of Scala, as with all type systems -featuring parametric polymorphism, classes are so dissimilar to types -that the similar-sounding term leads to false intuition, not helpful -analogy. It is a detriment to learning, not an aid. - -Types are compile-time, and classes are runtime. - -### When are types real? - -The phase separation—compile-time versus runtime—is the key to -the strength of types in Scala and similar type systems. The static -nature of types means that the truths they represent must be -universally quantified—true in all possible cases, not just some -test cases. - -We need this strength because the phase separation forbids us from -taking into account anything that cannot be known about the program -without running it. We need to think in terms of “could happen”, not -“pretty sure it doesn’t”. - -## How do classes give rise to types? - -There appears to be some overlap between the classes of `greeting` and -its types. While `greeting` has the _class_ `String`, it also has the -_type_ `String`. - -We want types to represent static truths about the expressions in a -program. That’s why it makes sense to include a “model of the classes” -in the type system. When we define a class, we also define an -associated type or family of types. - -When we use a class to construct a value, as in `new Blob`, we would -like to assign as much specific meaning to that expression as we can -at compile time. So, because we know right now that this expression -will make a value of class `Blob`, we assign it the type `Blob` too. - -### How do the types disappear? - -There’s a common way to throw away type information in Scala, -especially popular in object-oriented style. - -```scala -val absGreeting: CharSequence = greeting -``` - -`absGreeting` has the same value as `greeting`, so it has the same -five classes. However, it only has two of those five types, because we -threw away the other three statically. It has lost some other types, -too, namely `greeting.type`, and acquired some new ones, namely -`absGreeting.type`. - -Once a value is constructed, the expression will naturally cast off -the types specifying its precise identity, as it moves into more -abstract contexts. Ironically, the best way to preserve that -information as it passes through abstract contexts is to take -advantage of purely abstract types—type parameters and type -members. - -```scala -scala> pickGreeting[greeting.type](greeting, 100) -res16: greeting.type = hi there! -``` - -While the implementation must treat its argument as being of the -abstract type `G`, the caller knows that the more specific -`greeting.type` must come out of that process. - -### How do the types come back? - -There is a feature in Scala that lets you use class to get back _some_ -type information via a dynamic, runtime test. - -```scala -absGreeting match { - case hiAgain: String => - conformance[hiAgain.type, String] // will compile -} -``` - -The name “type test” for this feature is poorly chosen. The -_conclusion_ affects the type level—`hiAgain` is, indeed, proven -statically to be of type `String`—but the _test_ occurs only at -the class level. - -The compiler will tell you about this limitation sometimes. - -```scala -def pickGreeting2[G](grt: G, rand: Int): G = - ("magic": Any) match { - case ok: G => ok - case _ => sys error "failed!" - } - -:13: warning: abstract type pattern G is unchecked - since it is eliminated by erasure - case ok: G => ok - ^ -``` - -But reflecting the runtime classes back to compile-time types is a -subtle art, and the compiler often can’t explain exactly what you got -wrong. - -```scala -def pickGreeting3[G](grt: G, rand: Int): G = - grt match { - case _: String => - "Surely type G is String, right?" - case _ => grt - } - -:14: error: type mismatch; - found : String("Surely type G is String, right?") - required: G - "Surely type G is String, right?" - ^ -``` - -I’ve touched upon this -mistake -[in previous articles](singleton-instance-trick-unsafe.md#types-are-erased), -but it’s worth taking at least one more look. Let’s examine how -tempting this mistake is. - -`String` is a `final class`. So it is true that `G` can contain no -more specific class than `String`, if the first `case` matches. For -example, given `trait MyAwesomeMixin`, `G` cannot be -`String with MyAwesomeMixin` if this `case` succeeds, because that -can’t be instantiated; you would need to create a subclass of `String` -that implemented `MyAwesomeMixin`. - -This pattern match isn’t enough evidence to say that `G` is exactly -`String`. There are still other class-based types it could be, like -`Serializable`. - -```scala -pickGreeting3[java.io.Serializable](greeting, 4055) -``` - -Instead, it feels like this pattern match confirms `Serializable` as a -possibility, instead of denying it. - -But we don’t need `G = String` for this code to compile; we only need -`G >: String`. If that was true, then `"Surely type G is String, -right?"`, a `String`, could simply upcast to `G`. - -However, even `G >: String` is unproven. There are no subclasses of -`String`, but there are infinitely many _subtypes_ of -`String`. Including the `G` created by each entry into -`pickGreeting3`, every abstract and existential type bounded by -`String`, and every singleton type of `String` variable definitions. - -This mistake is, once again, confusing a demonstration of one case -with a proof. Pattern matching tells us a great deal about one value, -the `grt` argument, but very little about the type `G`. All we know -for sure is that “`grt` is of type `G`, and also of type `String`, so -these types overlap by at least one value.” In the type system, if you -don’t know something for sure, you don’t know it at all. - -## Classes are a concrete source of values - -In the parlance of functional Scala, concrete classes are often called -“data constructors”. - -When you are creating a value, you must ultimately be concrete about -its class, at the bottom of all the abstractions and indirections used -to hide this potentially messy detail. - -```scala -scala> def pickGreeting4[G]: G = new G -:12: error: class type required but G found - def pickGreeting4[G]: G = new G - ^ -``` - -You’ll have to do something else here, like take an argument -`() => G`, to let `pickGreeting4` construct `G`s. - -The truly essential role that classes play is that they encapsulate -instructions for constructing concrete values of various types. In a -safe program, this is the only feature of classes you’ll use. - -In Scala, classes leave fingerprints on the values that they -construct, without fail. This is merely an auxiliary part of their -primary function as value factories, like a “Made in `class Blah`” -sticker on the back. Pattern matching’s “type tests” work by checking -this fingerprint of construction. - -## Most runtime “type test” mechanisms do not work for types - -These fingerprints only come from classes, not types. So “type tests” -only work for “classy” types, like `String` and `MyAwesomeMixin`. They -also work for specific singleton types because construction also -leaves an “object identity” fingerprint that the test can use. - -The -[`ClassTag` typeclass](http://www.scala-lang.org/api/2.12.1/scala/reflect/ClassTag.html) does -not change this restriction. When you add a `ClassTag` or `TypeTag` -context bound, you also prevent that type parameter from working with -most types. - -```scala -scala> implicitly[reflect.ClassTag[greeting3.I]] -:14: error: No ClassTag available for greeting3.I - implicitly[reflect.ClassTag[greeting3.I]] - ^ -``` - -As such, judicious use of `ClassTag` is not a great solution to -excessive use of type tests in abstract contexts. There are so many -more types than classes that this is to confine the expressivity of -your types to a very small, class-reflective box. Set them free! - -## “But doesn’t Python/JavaScript/&c have both types and classes at runtime?” - -In JavaScript, there’s a very general runtime classification of values -called “type”, meant to classify built-in categories like `string`, -`number`, and the like. - -```javascript ->> typeof "hi" -"string" ->> typeof 42 -"number" ->>> typeof [1, 'a'] -"object" -``` - -Defining a class with the new `class` keyword doesn’t extend this -partition with new “types”; instead, it further subdivides _one_ of -those with a separate classification. - -```javascript ->> class Foo() {} ->> class Bar() {} ->> typeof (new Foo) -"object" ->> typeof (new Bar) -"object" ->> new Foo().constructor -function Foo() ->> new Bar().constructor -function Bar() -``` - -So, if you treat JavaScript’s definition of the word “type” as -analogous to the usage in this article, then yes, JavaScript has -“runtime types”. - -But JavaScript can only conveniently get away with this because its -static types are uninteresting. It has one type—the type of all -values—and no opportunities to do interesting type-level modeling, -at least not as part of the standard language. - -Hence, JavaScript is free to repurpose the word “type” for a flavor of -its classes, because our “types” aren’t a tool you make much use of in -JavaScript. But when you come back to Scala, Haskell, the ML family, -et al, you need a word for the static concept once again. - -## Thinking about types as _just_ classes leads to incorrect conclusions - -Setting aside the goal of principled definition of terms, this -separation is the one that makes the most sense for a practitioner of -Scala. Consider the practicalities: - -Types and classes have different behavior, are equal and unequal -according to different rules, and there are a lot more types than -classes. So we need different words to distinguish them. - -Saying “compile-time type” or “runtime type” is not a practical -solution—no one wants to speak such an unwieldy qualifier every -time they refer to such a commonly-used concept. - -While I’ve given a sampling of the richness of the type system in this -article, it’s not necessary to know that full richness to appreciate -or remember the difference between the two: types are static and -compile-time; classes are dynamic and runtime. - -*This article was tested with Scala 2.12.1, Shapeless 2.3.2, and -Firefox 53.0a2.* diff --git a/src/blog/nested-existentials.md b/src/blog/nested-existentials.md deleted file mode 100644 index f99335fa..00000000 --- a/src/blog/nested-existentials.md +++ /dev/null @@ -1,396 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-27" - tags: [technical] - katex: true -%} - -# Nested existentials - -*This is the fifth of a series of articles on “Type Parameters and -Type Members”. If you haven’t yet, you should -[start at the beginning](type-members-parameters.md), -which introduces code we refer to throughout this article without -further ado.* - -Let’s consider a few values of type `MList`: - -```scala -val estrs: MList = MCons("hi", MCons("bye", MNil())): MList.Aux[String] - -val eints: MList = MCons(21, MCons(42, MNil())): MList.Aux[Int] - -val ebools: MList = MCons(true, MCons(false, MNil())): MList.Aux[Boolean] -``` - -Recall -[from the first part](type-members-parameters.md#why-all-the-type-t) -that the equivalent type in `PList` style is `PList[_]`. Now, these -variables all have the “same” type, by virtue of forgetting what their -specific element type is, though you know that every value of, for -example, `estrs` has the same type. - -What if we list *different* existentials? ------------------------------------------ - -Lists hold values of the same type, and as you might expect, you can -put these three lists in another list: - -```scala -val elists: PList[MList] = - PCons(estrs, PCons(eints, PCons(ebools, PNil()))) -``` - -Again, the equivalent is `PList[PList[_]]`. We can see what this -means merely by doing substitution in the `PList` type. - -```scala -sealed abstract class PList -final case class PNil() extends PList -final case class PCons(head: MList, tail: PList) -// don't compile this, it's a thought process -``` - -Equivalently, `head` would have type `PList[_]`, a homogeneous list of -unknown element type, just like `MList`. - -Method equivalence … broken? ----------------------------- - -But we come to a problem. Suppose we wish to count the elements of -doubly-nested lists. - -```scala -def plenLength(xss: PList[PList[_]]): Int = - plenLengthTP(xss) - -def plenLengthTP[T](xss: PList[PList[T]]): Int = - xss match { - case PNil() => 0 - case PCons(h, t) => plengthT(h) + plenLengthTP(t) - } - -TmTp5.scala:16: no type parameters for method plenLengthTP: -⤹ (xss: tmtp.PList[tmtp.PList[T]])Int exist so that it -⤹ can be applied to arguments (tmtp.PList[tmtp.PList[_]]) - --- because --- -argument expression's type is not compatible with formal parameter type; - found : tmtp.PList[tmtp.PList[_]] - required: tmtp.PList[tmtp.PList[?T]] -``` - -According to our equivalence test, neither of these methods works to -implement the other! This despite -[the “simple rule” we have already discussed](type-members-parameters.md#when-is-existential-ok). -Here’s the error the other way. - -```scala -TmTp5.scala:20: type mismatch; - found : tmtp.PList[tmtp.PList[T]] - required: tmtp.PList[tmtp.PList[_]] -``` - -The problem with calling `plenLengthTP` from `plenLength` is *there is -no one `T` we can choose, even an unspeakable one, to call -`plenLengthTP`*. That’s what the `?T` and the “no type parameters” -phrasing in the first error above means. - -This is an accurate compiler error because `PList[PList[_]]` means -`PList[PList[E] forSome {type E}]`. Let’s see the substitution again. - -```scala -sealed abstract class PList -final case class PNil() extends PList -final case class PCons(head: PList[E] forSome {type E}, tail: PList) -// don't compile this, it's a thought process -``` - -Java has the same problem. See? - -```java -int llLength(final List> xss) { - return llLengthTP(xss); -} - - int llLengthTP(final List> xss) { - return 0; // we only care about types in this example -} - -TmTp5.java:7: error: method llLengthTP in class TmTp5 -⤹ cannot be applied to given types; - return llLengthTP(xss); - ^ - -// or, with llLengthTP calling llLength -TmTp5.java:11: error: incompatible types: List> -⤹ cannot be converted to List> - return llLength(xss); - ^ -``` - -This discovery, which I made for myself -[in the depths of the Ermine Java code](https://bitbucket.org/ermine-language/ermine-writers/src/c63d4060a74f1c8520ea1c8c3ba51ebd5d269780/writers/javafx/src/main/java/com/clarifi/reporting/writers/jfx/table/JFXTables.java?at=default#JFXTables.java-163) -(though it was certainly already well-known to others), was my first -clue, personally, that the term -[“wildcard” was a lie, as discussed in a previous part](method-equiv.md#why-are-existentials-harder-to-think-about). - -Scoping existential quantifiers -------------------------------- - -The difference is, in Scala, we can write an equivalent for -`plenLengthTP`, using the Scala-only -[`forSome` *existential quantifier*](http://www.artima.com/pins1ed/combining-scala-and-java.html#29.3). - -```scala -def plenLengthE(xss: PList[PList[E]] forSome {type E}): Int = - plenLengthTP(xss) -``` - -Of course, this type doesn’t mean the same thing as `plenLength`’s -type; for both `plenLengthE` and `plenLengthTP`, we demand proof that -each sublist in the argument has the same element type, which is not a -condition satisfied by either `PList[PList[_]]` or its equivalent -`PList[MList]`. - -@:style(bulma-notification) - The reason you can’t invoke `plenLength` from - `plenLengthTP` is complicated, even for this article. In - short, `plenLength` demands evidence that, - *supposing* `PList` had a method taking an - argument of the element type, - e.g. `def lookAt(x: T): Unit`, it could do things like - `xss.lookAt(PList("hi", PNil()))`. In - `plenLengthTP`, this hypothetical method could only be - invoked with empty lists, or lists gotten by inspecting - `xss` itself. - - That no such method exists is irrelevant for the purposes of this - reasoning; we have written the definition of `PList` in a - way that scalac assumes that such a method may exist. You can - determine the consequences yourself by adding the - `lookAt` method to `PList`, repeating the - above substitution for `PList`, and thinking about the - meaning of the resulting `def lookAt(x: - PList[E] forSome {type E}): Unit`. -@:@ - -Let’s examine the meaning of the type -`PList[PList[E]] forSome {type E}`. It requires a little bit more -mental suspension. - -```scala -// Let there be some unknown (abstract) -type E -// then the structure of the value is -sealed abstract class PList -final case class PNil() extends PList -final case class PCons(head: PList[E], tail: PList) -// don't compile this, it's a thought process -``` - -By moving the `forSome` *existential scope* outside the outer `PList`, -we also move the existential type variable outside of the whole -structure, substituting *the same* variable for each place we’re -expanding the type under consideration. Once the `forSome` scope -extends over the whole type, Scala can pick that type as the parameter -to `plenLengthTP`. - -This isn’t possible in Java at all; `PList>` is your only -choice, as **`?` in Java, like `_` in Scala, is always scoped to -exactly one level outside**. So in Java, you simply can’t write -`plenLengthE`’s type. Luckily, the type-parameter equivalent is -perfectly expressible. - -What happens when I move the existential scope? ------------------------------------------------ - -Of course, moving the scope makes the type mean something different, -which you can tell by counting how many `E`s there will be in a value. -A `PList[PList[_]]` is a list of lists where each list may have a -different, unknown element type, like `elists`. A -`PList[PList[E]] forSome {type E}` is a list of lists where you still -don’t know the inner element type, but you know it’s the same for each -sublist. We can tell that because, in the expansion, there’s only one -`E`, whereas the expansion for the former has an `E` introduced in -each `head` value. - -So for the latter it is type-correct to, say, move elements from one -sublist to another; you know that, whichever pair of sublists you -choose to make this trade, they have the same element type. But you -*don’t know that* for `PList[PList[_]]`. - -Similarly, also by substitution, `PList[_] => Int` is a function that -takes `PList`s of any element type and returns `Int`, like `plengthE`. -You can figure this out by substituting for -[`Function1#apply`](https://github.com/scala/scala/blob/v2.11.7/src/library/scala/Function1.scala#L32-L36): - -```scala -def apply(v1: T1): R -def apply(v1: PList[_]): Int -``` - -But `(PList[E] => Int) forSome {type E}` is a function that takes -`PList`s of *one specific* element type that we don’t know. - -```scala -// Let there be some unknown (abstract) -type E -// then the method is -def apply(v1: List[E]): Int -``` - -It’s easy to use existential scoping to create functions that are -impossible to call and other values that are impossible to use besides -functions. This is almost one of those: - -```scala -def badlength: (PList[E] => Int) forSome {type E} = plengthE -badlength(??? : PList[Int]) - -TmTp5.scala:29: type mismatch; - found : tmtp.PList[Int] - required: tmtp.PList[E] where type E -badlength(??? : PList[Int]) - ^ -``` - -But in this case, there is one way we can call this function: with an -empty list. Whatever the `E` is, it will be inferred when we call -`PNil()`. So `badlength(PNil())` works. - -There is a broader theme here hinted at by the interaction between -`PNil` and `badlength`: **the most efficient, most easily understood -way to work with values of existential type is with type-parameterized -methods**. But we’ll get to that later. - -Back to type members --------------------- - -Let us translate the working existential variant we discovered above -to the `PList[MList]` form of the function, though. What is the -existential equivalent to `mlenLengthTP`? - -```scala -def mlenLengthTP[T](xss: PList[MList.Aux[T]]): Int = - xss match { - case PNil() => 0 - case PCons(h, t) => mlength(h) + mlenLengthTP(t) - } - -def mlenLength(xss: PList[MList]): Int = - mlenLengthTP(xss) - -TmTp5.scala:38: type mismatch; - found : tmtp.PList[tmtp.MList] - required: tmtp.PList[tmtp.MList.Aux[this.T]] - mlenLengthTP(xss) - ^ -``` - -`MList` is equivalent to `MList {type T = E} forSome {type E}`. We -can prove that directly in Scala. - -```scala -scala> implicitly[MList =:= (MList {type T = E} forSome {type E})] -res0: =:=[tmtp.MList,tmtp.MList{type T = E} forSome { type E }] = -``` - -That’s why we could use `runStSource` to infer a type parameter for -the existential `S` in -[the last post](type-projection.md#type-parameters-see-existentially): -the scope is on the outside, so there’s exactly one type parameter to -infer. So the scoping problem now looks very similar to the -`PList`-in-`PList` problem, and we can write: - -```scala -def mlenLengthE(xss: PList[MList.Aux[E]] forSome {type E}) - : Int = mlenLengthTP(xss) -``` - -A triangular generalization ---------------------------- - -Once again, `mlenLengthE` demands proof that each sublist of `xss` has -the same element type, by virtue of the position of its `forSome` -scope. We can’t satisfy that with `elists`. - -```scala -mlenLengthE(elists) -``` - -Or, we *shouldn’t* be able to, anyway. -[Sometimes, the wrong thing happens.](https://issues.scala-lang.org/browse/SI-9410) -We get the right error when we try to invoke `mlenLengthTP`. - -```scala -mlenLengthTP(elists) - -TmTp5.scala:43: type mismatch; - found : tmtp.PList[tmtp.MList] - required: tmtp.PList[tmtp.MList.Aux[this.T]] - (which expands to) tmtp.PList[tmtp.MList{type T = this.T}] -mlenLengthTP(elists) - ^ -``` - -So we have `mlenLengthE` @:math \equiv\_m @:@ `mlenLengthTP`. `mlenLength`, -however, is incompatible with both; neither is more general than the -other! What we really want is a function that is more general than -all three, and subsumes all their definitions. Here it is, in two -variants: one half-type-parameterized, the other wholly existential. - -```scala -def mlenLengthTP2[T <: MList](xss: PList[T]): Int = - xss match { - case PNil() => 0 - case PCons(h, t) => mlength(h) + mlenLengthTP2(t) - } - -def mlenLengthE2(xss: PList[_ <: MList]): Int = - xss match { - case PNil() => 0 - case PCons(h, t) => mlength(h) + mlenLengthTP2(t) - } -``` - -We’ve woven a tangled web, so here are, restated, the full -relationships for the `MList`-in-`PList` functions above. - -1. `mlenLengthTP2` @:math \equiv_m @:@ `mlenLengthE2` -2. `mlenLengthTP` @:math \equiv_m @:@ `mlenLengthE` -3. @:math \neg( @:@`mlenLength` @:math <:_m @:@ `mlenLengthE`@:math ) @:@ -4. @:math \neg( @:@`mlenLengthE` @:math <:_m @:@ `mlenLength`@:math ) @:@ -5. @:math \neg( @:@`mlenLength` @:math <:_m @:@ `mlenLengthTP`@:math ) @:@ -6. @:math \neg( @:@`mlenLengthTP` @:math <:_m @:@ `mlenLength`@:math ) @:@ -7. `mlenLengthTP2` @:math <_m @:@ `mlenLengthTP` -8. `mlenLengthTP2` @:math <_m @:@ `mlenLength` -9. `mlenLengthTP2` @:math <_m @:@ `mlenLengthE` -10. `mlenLengthE2` @:math <_m @:@ `mlenLengthTP` -11. `mlenLengthE2` @:math <_m @:@ `mlenLength` -12. `mlenLengthE2` @:math <_m @:@ `mlenLengthE` - -Moreover, the full existential in `mlenLengthE2` is shorthand for: - -```scala -PList[E] forSome { - type E <: MList { - type T = E2 - } forSome {type E2} -} -``` - -…a nested existential, though not in the meaning I intend in the title -of this article. You can prove it with `=:=`, as above. - -And I say all this simply as a means of saying that *this* is what -you’re signing up for when you decide to “simplify” your code by using -type members instead of parameters and leaving off the refinements -that make them concrete. - -In -[the next part, “Values never change types”](values-never-change-types.md), -we’ll get some idea of why working with existential types can be so -full of compiler errors, especially when allowing for mutation and -impure functions. - -*This article was tested with Scala 2.11.7 and Java 1.8.0_45.* diff --git a/src/blog/new-code-of-conduct-committee-members.md b/src/blog/new-code-of-conduct-committee-members.md deleted file mode 100644 index 27fd2bd3..00000000 --- a/src/blog/new-code-of-conduct-committee-members.md +++ /dev/null @@ -1,41 +0,0 @@ -{% - author: ${typelevel} - date: "2024-11-21" - tags: [governance] -%} - -# Code of Conduct Committee - -A few months back the Typelevel Steering Committee put out [a call for new members](call-for-code-of-conduct-committee-members.md) to join the Typelevel Code of Conduct Committee. Thank you very much to all who applied, it's lovely to see folks interested in keeping our community safe. - -Since that time, the Typelevel Steering Committee voted in three new members to the Code of Conduct Committee! The committee now has five members total. - -Here are some brief introductions to our current committee members. - -#### Lucas Satabin (he/him) - -> I've been using Scala since 2009 both professionally and personally, and the Typelevel ecosystem since the scalac fork. Since then I have been trying to contribute libraries in the ecosystem on a regular basis. I currently work at [commercetools](https://commercetools.com/). -> -> I am based in the Paris area in France, and in my free time I like to play board games, TTRPGs, CRPGs, and I read a lot of comic books (mostly European and American). Baking bread, making mustard, and brewing slow coffees are my favorite kitchen based activities. I also tend to talk too much about trains. - -#### Kateu Herbert (he/him) - -> I’ve been programming with Scala for about 5 years now. I am a statistician by profession, currently working as a data analyst for [Enveritas](https://www.enveritas.org/) based in Kampala, Uganda. I use Scala for all my personal projects and in my free time, I share my knowledge through [blog posts](https://hkateu.github.io/kateuherbert.github.io/). Other hobbies include playing around with Linux distros, training my dog, and watching and reading about tech related content. - -#### Arman Bilge (he/him) - -> I'm Arman, based in Seattle, and I have been involved with Typelevel for almost four years now. I was first drawn to contribute by the welcoming and friendly people in our Discord! This community is near and dear to me and I am committed to fostering a safe and inclusive environment where we can work, learn and play together :) - -#### Sam Pillsworth (she/her) - -> Hi, I'm Sam! I'm part of the Typelevel Steering Committee and now also the code of conduct committee! I care deeply about the Typelevel community and creating a welcoming and supportive environment for everyone. I live in Canada, and when I'm not doing computer related activities I like to read books and play with my dog. - -#### Andrew Valencik (he/him) - -> Hi, I'm Andrew, I live in Ottawa, Canada. I hang out around Typelevel because the people are kind and welcoming. I want to help keep our community a safe and fun space. I think the libraries are pretty cool too and enjoy contributing to open source when I can. When I'm not thinking about computers I'm probably thinking about food or my two cats. - -#### What does this mean for our community? - -Code of Conduct members will work in accordance with the [Code of Conduct](https://github.com/typelevel/governance/blob/main/CODE-OF-CONDUCT.md) and [Enforcement Policy](https://github.com/typelevel/governance/blob/main/ENFORCEMENT-POLICY.md). -The team is expected to evaluate reported incidents about Code of Conduct violations and propose behavior adjustments or consequences for the reported behavior which will be done with the help of community moderators. -This ensures Code of Conduct violations are dealt with promptly and all community members feel welcome and safe as part of the Typelevel community. diff --git a/src/blog/new-website-layout.md b/src/blog/new-website-layout.md deleted file mode 100644 index 4f6c4e09..00000000 --- a/src/blog/new-website-layout.md +++ /dev/null @@ -1,26 +0,0 @@ -{% - author: ${typelevel} - date: "2022-09-06" - tags: [governance] -%} - -# New Website Layout Launched - -In August, you may have noticed that Typelevel.org has a new layout! We are grateful to our old friends at [47 Degrees][47-deg] for their generously donated time and effort in planning, designing, and implementing this long-needed revamp. Our deepest thanks to the following individuals from 47 Degrees for working with our [Steering Committee][steering] on a proposal: [Israel "Isra" Pérez][47-isra], [Jetro Cabau Quirós][47-jetro], [Maureen Elsberry][47-maureen], [Benjy Montoya][47-benjy], and [Raúl Raja Martínez][47-raul]. - -Special thanks again to [Isra][47-isra] for the months-long development work in Jekyll, our own [Ross Baker][ross] for safely resolving countless merge conflicts, our own [Jasna Rodulfa-Blemberg][jasna] for bravely pushing the final merge to our main development branch, and all [Steering Committee][steering] members for working with [Isra][47-isra] and [Raul][47-raul] in identifying follow-up improvements. - -Some issues have already been filed against this new layout, and we thank our community for taking the time to let us know. If you find any other issues or have ideas to improve the layout, please do not hesitate to open up an issue in [our typelevel.org github issues section][github]. You can also always start up a conversation in our [Discord server][discord]. - - -[47-deg]: https://www.47deg.com/ -[47-isra]: https://github.com/israelperezglez -[47-jetro]: https://github.com/JetroCabau -[47-benjy]: https://github.com/benjymontoya -[47-maureen]: https://github.com/MaureenElsberry -[47-raul]: https://github.com/raulraja -[ross]: https://github.com/rossabaker -[steering]: https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md -[discord]: https://sca.la/typeleveldiscord -[github]: https://github.com/typelevel/typelevel.github.com/issues -[jasna]: https://github.com/JasnaMRB diff --git a/src/blog/on-recent-events.md b/src/blog/on-recent-events.md deleted file mode 100644 index 402e92ee..00000000 --- a/src/blog/on-recent-events.md +++ /dev/null @@ -1,27 +0,0 @@ -{% - author: ${typelevel} - date: "2021-11-15" - tags: [governance] -%} - -# On Recent Events - -Open source developers are free to choose the projects they contribute to and the communities they support. Martin Odersky’s recent objection to an exercise of this freedom threw the community into turmoil. We believe that those who questioned his intervention and spoke out were justified in doing so. Regardless, we encourage everyone to consider the tone and weight of their words before hitting “send”. - -We stand with those who feel excluded by the notion of politics being somehow optional, a notion we disagree with. Typelevel was founded with a mission to provide an inclusive, welcoming and safe environment, and we remain committed to that goal. We believe that human rights cannot be dismissed as mere “politics”. - -The Scala Center, which has a long and celebrated history of success in education and technical improvement to the tooling we use daily, has repeatedly been expected to take on a community management role beyond its chartered goals, for which it is ill-equipped and underfunded. We acknowledge the challenges they face and we support the Scala Community Management and Governance strategy under consideration as a promising way forward. - -Typelevel has functioned with a not-very-formal governance structure of volunteers for some time. This group is known as the Typelevel Steering Committee. In the coming weeks we will formalize how leadership is selected and what the roles and responsibilities are. - -The Typelevel Steering Committee: -* Ross A. Baker -* Oscar Boykin -* Christopher Davenport -* Luka Jacobowitz -* Alexandru Nedelcu -* Rob Norris -* Michael Pilquist -* Miles Sabin -* Daniel Spiewak -* Kailuo Wang diff --git a/src/blog/optimizing-final-tagless.md b/src/blog/optimizing-final-tagless.md deleted file mode 100644 index d71970a2..00000000 --- a/src/blog/optimizing-final-tagless.md +++ /dev/null @@ -1,327 +0,0 @@ -{% - author: ${lukajcb} - date: "2017-12-27" - tags: [technical] -%} - -# Optimizing Tagless Final – Saying farewell to Free - -The Tagless Final encoding has gained some steam recently, with some people hailing 2017 as the year of Tagless Final. -Being conceptually similar to the Free Monad, different comparisons have been brought up and the one trade-off that always comes up is the lack or the difficulty of inspection of tagless final programs and in fact, I couldn't find a single example on the web. -This seems to make sense, as programs in the tagless final encoding aren't values, like programs expressed in terms of free structures. -However, in this blog post, I'd like to dispell the myth that inspecting and optimizing tagless final programs is more difficult than using `Free`. - -Without further ado, let's get into it, starting with our example algebra, a very simple key-value store: - -```scala -trait KVStore[F[_]] { - def get(key: String): F[Option[String]] - def put(key: String, a: String): F[Unit] -} -``` - - -To get the easiest example out of the way, here's how to achieve parallelism in a tagless final program: - -```scala -import cats._ -import cats.implicits._ - -def program[M[_]: FlatMap, F[_]](a: String)(K: KVStore[M])(implicit P: Parallel[M, F]) = - for { - _ <- K.put("A", a) - x <- (K.get("B"), K.get("C")).parMapN(_ |+| _) - _ <- K.put("X", x.getOrElse("-")) - } yield x -``` - -This programs makes use of the `cats.Parallel` type class, that allows us to make use of the `parMapN` combinator to use independent computations with a related `Applicative` type. This is already much simpler than doing the same thing with `Free` and `FreeApplicative`. For more info on `Parallel` check out the cats docs [here](https://typelevel.org/cats/typeclasses/parallel.html). - -However this is kind of like cheating, we're not really inspecting the structure of our program at all, so let's look at an example where we actually have access to the structure to do optimizations with. - -Let's say we have the following program: - -```scala -def program[F[_]: Apply](F: KVStore[F]): F[List[String]] = - (F.get("Cats"), F.get("Dogs"), F.put("Mice", "42"), F.get("Cats")) - .mapN((f, s, _, t) => List(f, s, t).flatten) -``` - -Not a very exciting program, but it has some definite optimization potential. -Right now, if our KVStore implementation is an asynchronous one with a network boundary, our program will make 4 network requests sequentially if interpreted with the standard `Apply` instance of something like `cats.effect.IO`. -We also have a duplicate request with the `"Cats"`-key. - -So let's look at what we could potentially do about this. -The first thing we should do, is extract the static information. -The easiest way to do so, is to interpret it into something we can use using a `Monoid`. -This is essentially equivalent to the `analyze` function commonly found on `FreeApplicative`. - -Getting this done, is actually quite simple, as we can use `cats.Const` as our `Applicative` data type, whenever the lefthand side of `Const` is a `Monoid`. -I.e. if `M` has a `Monoid` instance, `Const[M, A]` has an `Applicative` instance. -You can read more about `Const` [here](https://typelevel.org/cats/datatypes/const.html). - -```scala -val analysisInterpreter: KVStore[Const[(Set[String], Map[String, String]), ?]] = - new KVStore[Const[(Set[String], Map[String, String]), ?]] { - def get(key: String) = Const((Set(key), Map.empty)) - def put(key: String, a: String) = Const((Set.empty, Map(key -> a))) - } - -program(analysisInterpreter).getConst -// res0: (Set[String], Map[String,String]) = (Set(Cats, Dogs),Map(Mice -> 42)) - -``` - -By using a Tuple of `Set` and `Map` as our `Monoid`, we now get all the unique keys for our `get` and `put` operations. -Next, we can use this information to recreate our program in an optimized way. - -```scala -def optimizedProgram[F[_]: Applicative](F: KVStore[F]): F[List[String]] = { - val (gets, puts) = program(analysisInterpreter).getConst - - puts.toList.traverse { case (k, v) => F.put(k, v) } - *> gets.toList.traverse(F.get).map(_.flatten) -} -``` - -And we got our first very simple optimization. -It's not much, but we can imagine the power of this technique. -For example, if we were using something like `GraphQL`, we could sum all of our `get` requests into one large request, so only one network roundtrip is made. -We could imagine similar things for other use cases, e.g. if we're querying a bunch of team members that all belong to the same team, it might make sense to just make one request to all the team's members instead of requesting them all individually. - -Other more complex optimizations could involve writing a new interpreter with the information we gained from our static analysis. -One could also precompute some of the computations and then create a new interpreter with those computations in mind. - -Embedding our Applicative program inside a larger monadic program is also trivial: - -```scala -def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] = - (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats")) - .mapN((f, s, _, t) => List(f, s, t).flatten) - -def optimizedProgram[F[_]: Applicative](mouse: String)(F: KVStore[F]): F[List[String]] = { - val (gets, puts) = program(mouse)(analysisInterpreter).getConst - - puts.toList.traverse { case (k, v) => F.put(k, v) } - *> gets.toList.traverse(F.get).map(_.flatten) -} - -def monadicProgram[F[_]: Monad](F: KVStore[F]): F[Unit] = for { - mouse <- F.get("Mice") - list <- optimizedProgram(mouse.getOrElse("64"))(F) - _ <- F.put("Birds", list.headOption.getOrElse("128")) -} yield () -``` - -Here we refactor our `optimizedProgram` to take an extra parameter `mouse`. Then in our larger `monadicProgram`, we perform a `get` operation and then apply its result to `optimizedProgram`. - -So now we have a way to optimize our one specific program, next we should see if we can introduce some abstraction. -Sadly Scala lacks Rank-N types, which makes this a bit difficult as we'll see. - -First we'll have to look at the shape of a generic program, they usually are functions from an interpreter `Algebra[F]` to an expression inside the type constructor `F`, such as `F[A]`. - -```scala -type Program[Alg[_[_]], F[_], A] = Alg[F] => F[A] -``` - -The problem of Rank-N types becomes apparent when we want to write a function where we interpret our program with two different interpreters, as we did before when interpreting into `Const`: - -```scala -def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid] - (program: Alg[F] => F[A]) - (extract: Alg[Const[M, ?]]) - (restructure: M => F[A]): Alg[F] => F[A] = { interp => - - val m = program(extract).getConst // error: type mismatch; - // found : extract.type (with underlying type Alg[[β$0$]cats.data.Const[M,β$0$]]) - // required: Alg[F] - - restructure(m) - } -``` -So, because of the lack of Rank-N types, this simple definition for our program is not enough to say that our program works for ALL type constructors `F[_]: Applicative`. - -Fortunately there is a workaround, albeit requiring a bit more boilerplate: - -```scala -trait Program[Alg[_[_]], A] { - def apply[F[_]: Applicative](interpreter: Alg[F]) : F[A] -} - -def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid] - (program: Program[Alg, A]) - (extract: Alg[Const[M, ?]]) - (restructure: M => F[A]): Alg[F] => F[A] = { interp => - val m = program(extract).getConst - - restructure(m) - } -``` - -And now it should compile without a problem. -Now we should be able to express our original optimization with this new generic approach: - -```scala -def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] = - (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats")) - .mapN((f, s, _, t) => List(f, s, t).flatten) - -def wrappedProgram(mouse: String) = new Program[KVStore, List[String]] { - def apply[F[_]: Applicative](alg: KVStore[F]): F[List[String]] = program(mouse)(alg) -} - -def optimizedProgram[F[_]: Applicative](mouse: String)(F: KVStore[F]): KVStore[F] => F[List[String]] = - optimize(wrappedProgram(mouse))(analysisInterpreter) { case (gets, puts) => - puts.toList.traverse { case (k, v) => F.put(k, v) } *> gets.toList.traverseFilter(F.get) - } -``` - -So far so good, we've managed to write a function to generically optimize tagless final programs. -However, one of the main advantages of tagless final is that implementation and logic should be separate concerns. -With what we have right now, we're violating the separation, by mixing the optimization part with the program logic part. -Our optimization should be handled by the interpreter, just as the sequencing of individual steps of a monadic program is the job of the target `Monad` instance. - -One way to go forward, is to create a typeclass that requires certain algebras to be optimizable. -This typeclass could be written using the generic function we wrote before, so let's see what we can come up with: - - -```scala -trait Optimizer[Alg[_[_]], F[_]] { - type M - - def monoidM: Monoid[M] - def monadF: Monad[F] - - def extract: Alg[Const[M, ?]] - def rebuild(m: M, interpreter: Alg[F]): F[Alg[F]] - - def optimize[A](p: Program[Alg, A]): Alg[F] => F[A] = { interpreter => - implicit val M: Monoid[M] = monoidM - implicit val F: Monad[F] = monadF - - val m: M = p(extract).getConst - - rebuild(m, interpreter).flatMap(interp => p(interp)) - } -} -``` - -This might look a bit daunting at first, but we'll go through it bit by bit. -First we define our type class `Optimizer` parameterized by an algebra `Alg[_[_]]` and a type constructor `F[_]`. -This means we can define different optimizations for different algebras and different target types. -For example, we might want a different optimization for a production `Optimizer[KVStore, EitherT[Task, E, ?]]` and a testing `Optimizer[KVStore, Id]`. -Next, for our interpreter we need a `Monoid M` for our static analysis, however we don't to parameterize our `Optimizer` with an extra type parameter, since the actual type of `M` isn't necessary for the API, so we use an abstract type member instead. - -Next we need actual `Monoid` and `Monad` instances for `F[_]` and `M` respectively. -The other two functions should seem familiar, the `extract` function defines an interpreter to get an `M` out of our program. -The `rebuild` function takes that value of `M` and the interpreter and produces an `F[Alg[F]]`, which can be understood as an `F` of an interpreter. -This means that we can statically analyze a program and then use the result of that to create a new optimized interpreter and this is exactly what the `optimize` function does. -This is also why we needed the `Monad` constraint on `F`, we could also get away with returning just a new interpreter `Alg[F]` from the `rebuild` method and get away with an `Applicative` constraint, but we can do more different things this way. - -We'll also define some quick syntax sugar for this type class to make using it a tiny bit more ergonomic. - -```scala -implicit class OptimizerOps[Alg[_[_]], A](val value: Program[Alg, A]) extends AnyVal { - def optimize[F[_]: Monad](interp: Alg[F])(implicit O: Optimizer[Alg, F]): F[A] = - O.optimize(value)(interp) -} -``` - -Let's see what our program would look like with this new functionality: - -```scala -def monadicProgram[F[_]: Monad](F: KVStore[F])(implicit O: Optimizer[KVStore, F]): F[Unit] = for { - mouse <- F.get("Mice") - list <- wrappedProgram(mouse.getOrElse("64")).optimize(F) - _ <- F.put("Birds", list.headOption.getOrElse("128")) -} yield () -``` - -Looking good so far, now all we need to run this is an actual instance of `Optimizer`. -We'll use a Monix `Task` for this and for simplicity our new optimization will only look at the `get` operations: - -```scala -implicit val kvStoreTaskOptimizer: Optimizer[KVStore, Task] = new Optimizer[KVStore, Task] { - type M = Set[String] - - def monoidM = implicitly - - def monadF = implicitly - - def extract = new KVStore[Const[Set[String], ?]] { - def get(key: String) = Const(Set(key)) - def put(key: String, a: String): Const[Set[String], Unit] = Const(Set.empty) - } - - def rebuild(gs: Set[String], interp: KVStore[Task]): Task[KVStore[Task]] = - gs.toList - .parTraverse(key => interp.get(key).map(_.map(s => (key, s)))) - .map(_.flattenOption.toMap) - .map { m => - new KVStore[Task] { - override def get(key: String) = m.get(key) match { - case v @ Some(_) => v.pure[Task] - case None => interp.get(key) - } - - def put(key: String, a: String): Task[Unit] = interp.put(key, a) - } - } - -} -``` - -Our `Monoid` type is just a simple `Set[String]` here, as the `extract` function will only extract the `get` operations inside the `Set`. -Then with the `rebuild` we build up our new interpreter. -First we want to precompute all the values of the program. -To do so, we just run all the operations in parallel and put them into a `Map`, while discarding values where the `get` operation returned `None`. -Now when we have that precomputed `Map`, we'll create a new interpreter with it, that will check if the key given to `get` operation is in the precomputed `Map` instead of performing an actual request. -We can then lift the value into a `Task[Option[String]]`. -For all the `put` operations, we'll simply run the interpreter. - -Now we should have a great optimizer for `KVStore` programs interpreted into a `Task`. -Let's see how we did by interpreting into a silly implementation that only prints whenever you use one of the operations: - -```scala -object TestInterpreter extends KVStore[Task] { - def get(key: String): Task[Option[String]] = Task { - - println("Hit network for " + key) - - Option(key + "!") - } - - def put(key: String, a: String): Task[Unit] = Task { - println("Put something: " + a) - - () - } -} -``` - -Now let's run our program with this interpreter and the optimizations! - -```scala -monadicProgram(TestInterpreter).runAsync -// Hit network for Mice -// Hit network for Cats -// Hit network for Dogs -// Put something: Mice! -// Put something: Cats! -``` - -And it works, we've now got a principled way to write programs that can then be potentially optimized. - - - -## Conclusion - -Designing a way to completely separate the problem description from the actual problem solution is fairly difficult. The tagless final encoding allows us one such fairly simple way. -Using the technique described in this blog post, we should be able to have even more control over the problem solution by inspecting the structure of our program statically. -We've seen a few roadblocks along the way, such as the lack of Rank-N types in Scala, but we might be able to come up with a macro for that in the future, making it even more ergonomic. -Another thing we haven't covered here, are programs with multiple algebras, which is quite a bit more complex as you can surely imagine, maybe that will be the topic of a follow up blog post. - -The code is published [right here](https://github.com/LukaJCB/sphynx), but might still change after getting a feeling for which API feels best. - -What kind of problems and techniques would you like to see with regards to tagless final? -Would love to hear from you in the comments! diff --git a/src/blog/optimizing-tagless-final-2.md b/src/blog/optimizing-tagless-final-2.md deleted file mode 100644 index b8ffd6bc..00000000 --- a/src/blog/optimizing-tagless-final-2.md +++ /dev/null @@ -1,420 +0,0 @@ -{% - author: ${lukajcb} - date: "2018-06-27" - tags: [technical] -%} - -# Optimizing Tagless Final – Part 2 – Monadic programs - -In our previous post on optimizing tagless final programs we learned how we could use the [sphynx library](https://github.com/LukaJCB/sphynx) to derive some optimization schemes for your tagless final code. In case you missed it and want to read up on it, you can find it [right here](optimizing-final-tagless.md) or you can watch my presentation on the topic [here](https://www.youtube.com/watch?v=E9iRYNuTIYA), but you should be able to follow this blog post without going through it all in detail. - -## Optimizing monadic programs - -One of the questions I've been getting a lot, is if we can also do something like that for the monadic parts of our program. -The answer is yes, we can, however it will have to be quite a bit different. - -I don't think the differences are quite obvious, so we'll go through them step by step. -With applicative programs, we're optimizing a bunch of independent instructions. -That means, we can look at all of them and extract information out of them statically (i.e. without running the interpreter). -They can be seen as a sequence of instructions that we can fold down to a single monoid `M`, that holds the information that we need to optimize. -We then used that monoid to recreate a new interpreter that can take this extra information into account. - -With monadic programs, we do not have such luxury. -We can only step through each of our instructions one at a time, because every instruction depends on the results of the prior one. -This means that we cannot extract any information beyond the very first instruction we have. -That might seem like a deal breaker, but there's still a few things we can do. -We could, for example, build up our monoid `M` dynamically, after each monadic instruction. -Then, before invoking the next computation in the monadic sequence, we could take that monoid and recreate that next computation with that extra information. - -Now, that might sound super abstract to you, and I wouldn't disagree, so let's look at a quick example. -Say, we're using the `KVStore` algebra again from last time: - - -```scala -trait KVStore[F[_]] { - def get(key: String): F[Option[String]] - def put(key: String, value: String): F[Unit] -} -``` - -We could optimize programs with this algebra by caching the results of `get` and we could use that same cache to also cache key-value pairs we inserted using `put`. - -So given this example program: - -```scala -def program[F[_]: Monad](key: String)(F: KVStore[F]): F[List[String]] = for { - _ <- F.put(key, "cat") - dog <- F.get(key) - cat <- F.get(dog.getOrElse("dog")) - cat2 <- F.get("cat") -} yield List(dog, cat, cat2).flatten -``` - -The naively interpreted program would be doing the following things: -1. put the value "cat" into the store with the `key` passed by the user -2. get the value "cat" back out of the store -3. access the key-value store and maybe return a value associated with the key "cat" -4. access the store again with the same "cat" key. - - -Now if accessing the key-value store means going through a network layer this is of course highly inefficient. -Ideally our fully optimized program should do the following things: -1. put the value "cat" into the store with the `key` parameter passed by the user and cache it. -2. access the cache to get the value "cat" associated with `key` -3. access the key-value-store and maybe return a value associated with the key "cat" -4. access the cache to return the previous result for the "cat" key. - -Cool, next, let's look at how we might get there. -First the type of our cache, which for our case can just be a `Map[String, String]`, but generically could just be any monoid. - -Now what we want to do is transform any interpreter for `KVStore` programs into interpreters that -1. Look in the cache before performing a `get` action with the actual interpreter -2. Write to the cache after performing either a `get` or `put` action. - -So how can we get there? It seems like we want to thread a bunch of state through our program, that we want to both read and write to. -If you're familiar with FP folklore you might recognize that that description fits almost exactly to the `State` monad. -Furthermore, because we know that our `F[_]` is a monad, that means the `StateT` monad transformer over `F` will also be a monad. - -Okay with that said, let's try to develop function that turns any interpreter `KVStore[F]` into an interpreter into `StateT[F, M, A]`, so an `KVStore[StateT[F, M, ?]]`, where `M` is the monoid we use to accumulate our extracted information. -We'll start with the `put` operation. -For `put`, we'll want to call the interpreter to perform the action and then modify the state by adding the retrieved value into our cache. -To make the code a bit more legible we'll also define a few type aliases. - -```scala -type Cache = Map[String, String] -type CachedAction[A] = StateT[F, Cache, A] - -def transform(interp: KVStore[F]): KVStore[CachedAction] = new KVStore[CachedAction] { - def put(key: String, v: String): CachedAction[Unit] = - StateT.liftF[F, Cache, Unit](interp.put(key, v)) *> StateT.modify(_.updated(key, v)) - - def get(key: String): CachedAction[Option[String]] = ??? -} -``` - -So far, so good, now let's have a look at what to do with the `get` function. -It's a bit more complex, because we want to read from the cache, as well as write to it if the cache didn't include our key. -What we have to do is, get our current state, then check if the key is included, if so, just return it, otherwise call the interpreter to perform the `get` action and then write that into the cache. - -```scala -def get(key: String): CachedAction[Option[String]] = for { - cache <- StateT.get[F, Cache] - result <- cache.get(key) match { - case s @ Some(_) => s.pure[CachedAction] - case None => StateT.liftF[F, Cache, Option[String]](interp.get(key)) - .flatTap(updateCache(key)) - } -} yield result - -def updateCache(key: String)(ov: Option[String]): CachedAction[Unit] = ov match { - case Some(v) => StateT.modify(_.updated(key, v)) - case None => ().pure[CachedAction] -} -``` - -This is quite something, so let's try to walk through it step by step. -First we get the cache using `StateT.get`, so far so good. -Now, we check if the key is in the cache using `cache.get(key)`. -The result of that is an `Option[String]`, which we can pattern match to see if it did include the key. -If it did, then we can just return that `Option[String]` by lifting it into `CachedAction` using `pure`. -If it wasn't in the cache, things are a bit more tricky. -First, we lift the interpreter action into `CachedAction` using `StateT.liftF`, that gives us a `CachedAction[Option[String]]`, which is already the return type we need and we could return it right there, but we still need to update the cache. -Because we already have the return type we need, we can use the `flatTap` combinator. -Then inside the `updateCache` function, we take the result of our interpreter, which is again an `Option[String]`, and update the cache if the value is present. -If it's empty, we don't want to do anything at all, so we just lift unit into `CachedAction`. - -In case you're wondering `flatTap` works just like `flatMap`, but will then `map` the result type back to the original one, making it a bit similar to a monadic version of the left shark (`<*`) operator, making it very useful for these "fire-and-forget" operations. -It's defined like this: - -```scala -def flatTap[F[_]: Monad, A, B](fa: F[A])(f: A => F[B]): F[A] = - fa.flatMap(a => f(a).map(b => a)) -``` - -And with that we now have a working function to turn any interpreter into an optimized interpreter. -We can also generalize this fairly easily into a function that will do all of the wiring for us. -To do so, we'll generalize away from `KVStore` and `Cache` and instead use generic `Alg[_[_]]` and `M` parameters: - - -```scala -def optimize[Alg[_[_]], F[_]: Monad, M: Monoid, A] - (program: MonadProgram[Alg, A]) - (withState: Alg[F] => Alg[StateT[F, M, ?]]): Alg[F] => F[A] = interpreter => - program(withState(interpreter)).runEmptyA -``` - -Just like last time, we have to use a `MonadProgram` wrapper around `Alg[F] => F[A]`, because Scala lacks rank-N types which would allow us to define values that work over ALL type constructors `F[_]: Monad` (Fortunately however, this will very probably soon be fixed in dotty, PR [here](https://github.com/lampepfl/dotty/pull/4672)). - -Now let's see if we can actually use it, by checking it with a test interpreter that will print whenever we retrieve or insert values into the `KVStore`. - - -```scala -optimize[KVStore, IO, Cache, List[String]](program("mouse"))(transform) - .apply(printInterpreter) - .unsafeRunSync() - -// Put key: mouse, value: cat -// Get key: cat -``` - -It works and does exactly what we want! -Nice! We could end this blog post right here, but there's still a couple of things I'd like to slightly alter. - -### Refining the API - -As you were able to tell the implementation of our transformation from the standard interpreter to the optimized interpreter is already quite complex and that is for a very very simple algebra that doesn't do a lot. -Even then, I initially wrote an implementation that packs everything in a single `StateT` constructor to avoid the overhead of multiple calls to `flatMap`, but considered the version I showed here more easily understandable. -For more involved algebras and more complex programs, all of this will become a lot more difficult to manage. -In our last blog post we were able to clearly separate the extraction of our information from the rebuilding of our interpreter with that information. -Let's have a look at if we can do the same thing here. - -First we'll want to define an extraction method. -For applicative programs we used `Const[M, ?]`, however that cannot work here, as `Const` doesn't have a `Monad` instance and also, because for extraction with monadic programs, we need to actually take the result of the computation into account. -That means, that for every operation in our algebra, we want a way to turn it into our monoid `M`. -With that said, it seems we want a function `A => M`, where `A` is the result type of the operations in our algebra. -So what we can do here is define an algebra for `? => M`, in types an `Alg[? => M]`. - -Let's try to do define such an interpreter for our `KVStore` along with `Cache`/`Map[String, String`: - -```scala -def extract: KVStore[? => Cache] = new KVStore[? => Cache] { - def get(key: String): Option[String] => Cache = { - case Some(s) => Map(key -> s) - case None => Map.empty - } - - def put(key: String, a: String): Unit => Cache = - _ => Map(key -> a) -} -``` - -Just as before we want to extract the cache piece by piece with every monadic step. -Whenever we get an `Option[String]` after using `get`, we can then turn that into a `Cache` if it's non-empty. -The same goes for `put`, where we'll create a Map using the key-value pair. -We now have a way to turn the results of our algebra operations into our information `M`, so far so good! - -Next, we'll need a way to rebuild our operations using that extracted information. -For that, let's consider what that actually means. -For applicative programs this meant a function that given a state `M` and an interpreter `Alg[F]`, gave a reconstructed interpreter inside the `F` context `F[Alg[F]]`. -So a function `(M, Alg[F]) => F[Alg[F]]`. - -For monadic programs, there's no need to precompute any values, as we're dealing with fully sequential computations that can potentially update the state after every evaluation. -So we're left with a function `(M, Alg[F]) => Alg[F]`. -Let's try building that for `KVStore`: - -```scala -def rebuild(m: M, interp: KVStore[F]): KVStore[F] = new KVStore[F] { - def get(key: String): F[Option[String]] = m.get(key) match { - case o @ Some(_) => Monad[F].pure(o) - case None => interp.get(key) - } - - def put(key: String, a: String): F[Unit] = - m => interp.put(key, a) -} -``` - -Easy enough! -For `get` we look inside our cache and use the value if it's there, otherwise we call the original interpreter to do its job. -For `put`, there's nothing to gain from having access to our extracted information and the only thing we can do is call the interpreter and let it do what needs to be done. - -Now we have a way to extract information and then also use that information, next up is finding a way to wire these two things together to get back to the behaviour we got using `StateT`. - -And as a matter of fact, we'll wire them back together using exactly `StateT`, as it's monad instance does do exactly what we want. - -Using our two functions `extract` and `rebuild` it's fairly easy to get back to `KVStore[StateT[F, Cache, ?]]`: - -```scala -def transform(interp: KVStore[F]): KVStore[StateT[F, Cache, ?]] = new KVStore[StateT[F, Cache, ?]] { - def put(key: String, v: String): StateT[F, Cache, Unit] = - StateT(cache => rebuild(cache, interp).put(key, v).map(a => - (cache |+| extract.put(key, v)) -> a)) - - def get(key: String): StateT[F, Cache, Option[String]] = - StateT(cache => rebuild(cache, interp).get(key).map(a => - (cache |+| extract.get(key)) -> a)) -} -``` - -This is fairly straightforward, we use rebuild with our cache and the interpreter to get a new interpreter that will run the operation. -Then, we use the result, which is just an `F[Unit]`/`F[Option[String]]` respectively, and map it - using the extractor to get the newest `Cache` and using its `Monoid` instance to update the state and then we tuple it with the result, giving us an `F[(Cache, Unit)]` or `F[(Cache, Option[String])]`, which is exactly what the `StateT` constructor needs. - -This is great, but can we generalize this to any algebra and any monoid? - -The answer is yes, but it's not exactly easy. -First let's look at the actual problem. -We have two interpreters `extract` and `rebuild`, but we have no way to combine them, because `Alg`, is completely unconstrained and that means we can't call any functions on a generic `Alg[F]` at all. -So, okay, we need to constrain our `Alg` parameter to be able to combine values of `Alg[F]` with values of `Alg[G]` in some way, but what kind of type class could that be? -Are there even type classes that operate on the kind of `Alg`? - - -### Higher kinded things - -There are, they're just hidden away in a small library called `Mainecoon`. -That library gives us higher kinded versions of things like functors and contravariant functors, called `FunctorK` and `ContravariantK` respectively. - -Let's have a quick look at `FunctorK`: - -```scala -@typeclass -trait FunctorK[A[_[_]]] { - def mapK[F[_], G[_]](af: A[F])(f: F ~> G): A[G] -} -``` - -Instead of mapping over type constructors `F[_]`, we map over algebras `A[_[_]]` and insteading of using functions `A => B`, we use natural transformations `F ~> G`. -This is nice, but doesn't really get us that far. - -What we really need is the equivalent of the `Applicative`/`Apply` `map2` operation. -`map2` looks like this: - -```scala -def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] -``` - -And a higher kinded version would look like this: - -```scala -def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H] -``` - -If you haven't guessed yet `Tuple2K` is just a higher kinded version of `Tuple2`: - -```scala -type Tuple2K[F[_], G[_], A] = (F[A], G[A]) -``` - -Unfortunately `Mainecoon` doesn't have an `ApplyK` type class that gives us this `map2K` operation, but it gives the next best thing! -A higher-kinded `Semigroupal`, which when combined with the higher kinded `Functor` gives us that higher kinded `Apply` type class. -It's called `CartesianK` (because cats `Semigroupal` used to be called `Cartesian`, but is renamed to `SemigroupalK` in the next version) and looks like this: - -```scala -@typeclass -trait CartesianK[A[_[_]]] { - def productK[F[_], G[_]](af: A[F], ag: A[G]): A[Tuple2K[F, G, ?]] -} -``` - -Now just like you can define `map2` using `map` and `product` we can do the same for `map2K`: - -```scala -def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H] = - productK(af, ag).mapK(f) -``` - - -### Putting it all together - -Okay, after that quick detour, let's have a look at how can make use of these type classes. - -If we look at what we have and how we'd like to use the `map2K` function, we can infer the rest that we need quite easily. - -We have an `Alg[F]` and a `Alg[? => M]`, and we want an `Alg[StateT[F, M, ?]]`, so given those two as the inputs to `map2K`, all that seems to be missing is the natural transformation `Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]`. -Nice! As so often, the types guide us and show us the way. - -Well let's try to define just that: - -```scala -new (Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]) { - def apply[A](fa: Tuple2K[F, ? => M, ?]): StateT[F, M, A] = - StateT(m => F.map(fa.first)(a => M.combine(fa.second(a), m) -> a)) -} -``` - -This looks good, but actually has a problem, to get an `Alg[F]` from `rebuild` we give it an `M` and an interpreter `Alg[F]`. -The interpreter isn't really a problem, but the `M` can prove problematic as we need to give it to the `rebuild` function after each monadic step to always receive the latest state. -If we look at our natural transformation above, that function will never receive the newest state. -So what can we do about this? -Well, we could be a bit more honest about our types: - -```scala -type FunctionM[A] = M => F[A] -def rebuild(interp: Alg[F]): Alg[FunctionM] -``` - -Hey, now we're getting there. This works, but if we look into some of the data types provided by `Cats` we can acutally see that this is just `Kleisli` or `ReaderT`, so our `rebuild` should actually look like this: - -```scala -def rebuild(interp: Alg[F]): Alg[Kleisli[F, M, ?]] -``` - -And now, we can easily implement a correct version of that natural transformation from earlier: - -```scala -new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) { - def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, ?]): StateT[F, M, A] = - StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a)) -} -``` - -Cool, then let us also adjust the rebuild function we created for `KVStore`: - -```scala -def rebuild(interp: KVStore[F]): KVStore[Kleisli[F, M, ?]] = new KVStore[Kleisli[F, M, ?]] { - def get(key: String): Kleisli[F, Cache, Option[String]] = Kleisli(m => m.get(key) match { - case o @ Some(_) => Monad[F].pure(o) - case None => interp.get(key) - }) - - def put(key: String, a: String): Kleisli[F, Cache, Unit] = - Kleisli(m => interp.put(key, a)) -} -``` - -It's stayed pretty much the same, we just needed to wrap the whole thing in a `Kleisli` and we're good! - -Now we can go ahead and define the full function signature: - -```scala -def optimize[Alg[_[_]]: FunctorK: CartesianK, F[_]: Monad, M: Monoid, A] - (program: MonadProgram[Alg, A]) - (extract: Alg[? => M]) - (rebuild: Alg[F] => Alg[Kleisli[F, M, ?]]): Alg[F] => F[A] = { interpreter => - - val tupleToState = new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) { - def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, A]): StateT[F, M, A] = - StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a)) - } - - val withState: Alg[StateT[F, M, ?]] = - map2K(extract(interpreter), rebuild))(tupleToState) - - program(withState).runEmptyA - - } -``` - -That is all, we've got a fully polymorphic function that can optimize monadic programs. - -Let's use it! - -```scala -optimize(program)(extract)(rebuild) - .apply(printInterpreter) - .unsafeRunSync() -``` - - -Now, when we run this, it should be exactly the same result as when we ran it earlier using the direct `StateT` interpreter, but the resulting code is much cleaner. -However, it does have the drawback that you'll now need additional constraints for every algebra to use this function. -That said though, one of the cool features of `Mainecoon` is that it comes with auto-derivation. -Meaning we can just add an annotation to any of our algebras and it will automatically derive the `FunctorK` and `CartesianK` instances. - -In fact, that is exactly how I defined those two instances for the `KVStore` algebra: - -```scala -@autoFunctorK -@autoCartesianK -trait KVStore[F[_]] { ... } -``` - -This makes it fairly easy to use these extra type classes and helpts mitigate the drawbacks I mentioned. - -### Conclusions - -Today we've seen a way to make optimizing monadic tagless final programs easier and intuitive, all the code is taken from the sphynx library and can be found [right here](https://github.com/LukaJCB/sphynx), but might still be subject to change, because designing a good API is hard. - -What do you think about this optimization scheme? Maybe you just prefer using `StateT` and being done with it, or maybe you like to use a typeclass based approach like the one we used last time? - -Would love to hear from you all in the comments! diff --git a/src/blog/product-with-serializable.md b/src/blog/product-with-serializable.md deleted file mode 100644 index a35cc917..00000000 --- a/src/blog/product-with-serializable.md +++ /dev/null @@ -1,58 +0,0 @@ -{% - author: ${ceedubs} - date: "2018-05-09" - tags: [technical] -%} - -# Product with Serializable - -A somewhat common Scala idiom is to make an `abstract` type extend `Product with Serializable`. There isn't an obvious reason to do this, and people have asked me a number of times why I've done this. While I don't think that `Product` or `Serializable` are particularly good abstractions, there's a reason that I extend them. - -Let's say that I'm writing a simple enum-like `Status` type: - -```scala -object EnumExample1 { - sealed abstract class Status - case object Pending extends Status - case object InProgress extends Status - case object Finished extends Status -} -``` - -Now let's create a `Set` of statuses that represent incomplete items: - -```scala -import EnumExample1._ -``` - -```scala -val incomplete = Set(Pending, InProgress) -// incomplete: scala.collection.immutable.Set[Product with Serializable with EnumExample1.Status] = Set(Pending, InProgress) -``` - -Here, I didn't give in explicit return type to `incomplete` and you may have noticed that the compiler inferred a somewhat bizarre one: `Set[Product with Serializable with Status]`. Why is that? - -The compiler generally tries to infer the most specific type possible. Usually this makes sense. If you write `val x = 3` you probably don't want it to infer `val x: Any = 3`. And in the example above, I didn't want the return type for `incomplete` to be inferred as `Any` or even `Set[Any]`. However, the compiler was a bit _too_ clever and realized that not only is every item in the set an instance of `Status`, they are also instances of `Product` and `Serializable` since every `case object` (and `case class`) automatically extends `Product` and `Serializable`. Therefore, when it calculates the least upper bound (LUB) of the types in the set, it comes up with `Product with Serializable with Status`. - -While there's nothing inherently wrong with the return type of `Product with Serializable with Status`, it is verbose, it wasn't what I intended, and in certain situations it might cause inference issues. Luckily there's a simple workaround to get the inferred type that I want: - -```scala -object EnumExample2 { - // note the `extends` addition here - sealed abstract class Status extends Product with Serializable - case object Pending extends Status - case object InProgress extends Status - case object Finished extends Status -} -``` - -```scala -import EnumExample2._ -``` - -```scala -val incomplete = Set(Pending, InProgress) -// incomplete: scala.collection.immutable.Set[EnumExample2.Status] = Set(Pending, InProgress) -``` - -Now since `Status` itself already includes `Product` and `Serializable`, `Status` is the LUB type of `Pending`, `InProgress`, and `Finished`. diff --git a/src/blog/rawtypes.md b/src/blog/rawtypes.md deleted file mode 100644 index 80b14ee2..00000000 --- a/src/blog/rawtypes.md +++ /dev/null @@ -1,393 +0,0 @@ -{% - author: ${S11001001} - date: "2015-02-26" - tags: [technical] -%} - -# Existential types are not raw types - -*While this blog is typically strictly for Scala developers interested -in strongly-typed programming, this particular article is of interest -to Java developers as well. You don’t need to know Scala to follow -along.* - -Scala makes a *welcome* simplification in its type system: -[type arguments](http://docs.scala-lang.org/tutorials/tour/generic-classes.html) -are always required. That is, in Java, you may (unsafely) leave off -the type arguments for compatibility with pre-1.5 code, -e.g. `java.util.List`, forming a -[*raw type*](http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html). -Scala does not permit this, and requires you to pass a type argument. - -The most frequent trouble people have with this rule is being unable -to implement some Java method with missing type arguments in its -signature, e.g. one that takes a raw `List` as an argument. Let us -see why they have trouble, and why this is a good thing. - -Existentials are safe, raw types are not ----------------------------------------- - -Stripping the type argument list, e.g. going from -`java.util.List` to `java.util.List` is *an unsafe cast*. -[*Wildcarding*](http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html) -the same type argument, e.g. going from `java.util.List` to -`java.util.List`, is *safe*. The latter type is written -`java.util.List[_]`, or `java.util.List[T] forSome {type T}`, in -Scala. In both Java and Scala, this is an -[existential type](http://www.artima.com/pins1ed/combining-scala-and-java.html#29.3). -As compiled with `-Xlint:rawtypes -Xlint:unchecked`: - -```java -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; - -public abstract class TestEx { - public static List words() { - return new ArrayList<>(Arrays.asList("hi", "there")); - } - - // TestEx.java:17: warning: [rawtypes] found raw type: List - // missing type arguments for generic class List - // where E is a type-variable: - // E extends Object declared in interface List - // ↓ - public static final List wordsRaw = words(); - - // there is no warning for this - public static final List wordsET = words(); -} -``` - -Also note that there is no warning for the equivalent to `wordsET` in -Scala. Because it, like javac, knows that it’s safe. - -```scala -scala> TestEx.words -res0: java.util.List[String] = [hi, there] - -scala> val wordsET = TestEx.words : java.util.List[_] -wordsET: java.util.List[_] = [hi, there] -``` - -Raw Types are bad. Stop using them ------------------------------------ - -The reason that existentials are safe is that the rules in place for -values of existential type are consistent with the rest of the generic -system, whereas raw types contradict those rules, resulting in code -that should not typecheck, and only does for legacy code support. We -can see this in action with two Java methods. - -```java -public static void addThing(final List xs) { - xs.add(42); -} - -public static void swapAround(final List xs) { - xs.add(84); -} -``` - -These methods are the same, except for the use of raw types versus -existentials. However, the second does not compile: - -``` -TestEx.java:26: error: no suitable method found for add(int) - xs.add(84); - ^ - method Collection.add(CAP#1) is not applicable - (argument mismatch; int cannot be converted to CAP#1) - method List.add(CAP#1) is not applicable - (argument mismatch; int cannot be converted to CAP#1) - where CAP#1 is a fresh type-variable: - CAP#1 extends Object from capture of ? -``` - -Why forbid adding 42 to the list? The element type of list is -unknown. The answer lies in that statement: *its unknownness isn’t a -freedom for the body of the method, it’s a restriction*. The rawtype -version treats its lack of knowledge as a freedom, and the caller pays -for it by having its data mangled. - -```java -public static void testIt() { - final List someWords = words(); - addThing(someWords); - System.out.println("Contents of someWords after addThing:"); - System.out.println(someWords); - System.out.println("Well that seems okay, what's the last element?"); - System.out.println(someWords.get(someWords.size() - 1)); -} -``` - -And it compiles: - -``` -TestEx.java:23: warning: [unchecked] unchecked call to add(E) as a - member of the raw type List - xs.add(42); - ^ - where E is a type-variable: - E extends Object declared in interface List -``` - -But when we try to run it: - -```scala -scala> TestEx.testIt() -Contents of someWords after addThing: -[hi, there, 42] -Well that seems okay, what's the last element? -java.lang.ClassCastException: java.lang.Integer cannot be cast to - java.lang.String - at rawtypes.TestEx.testIt(TestEx.java:32) - ... 43 elided -``` - -It is a mistake to think that just because some code throws -`ClassCastException`, it must be to blame for a type error. This line -is blameless. It is the fault of the unchecked cast when we called -`addThing`, and more specifically, the unsafe assumption about the -`List`’s element type that was made in its body. - -Existentials are much better ----------------------------- - -When we used the wildcard, we were forbidden from doing the unsafe -thing. But what kinds of things can we do with the safe, existential -form? Here’s one: - -```java -private static void swapAroundAux(final List xs) { - xs.add(xs.get(0)); -} - -public static void swapAround(final List xs) { - swapAroundAux(xs); -} -``` - -In other words: let `E` be the *unknown* element type of `xs`. -`xs.get()` has type `E`, and `xs.add` has argument type `E`. They -line up, so this is okay, no matter what the element type of `xs` -turns out to be. Let’s try a test: - -```scala -scala> val w = TestEx.words -w: java.util.List[String] = [hi, there] - -scala> TestEx.swapAround(w) - -scala> w.get(w.size - 1) -res1: String = hi -``` - -The body of `swapAround` is guaranteed not to mangle its argument by -the type checker, so we, as a caller, can safely call it, and know -that our argument’s type integrity is protected. - -Scala has more features to let us get away without `swapAroundAux`. -This translation uses a lowercase -[*type variable pattern*](http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#type-parameter-inference-for-constructor-patterns.) -to name the existential. To the right of the `=>`, we can declare -variables of type `e` and use `e` to construct more types, while still -referring to the `_` in the `xs` argument’s type. But in this case, -we just do the same as `swapAroundAux` above. - -```scala -def swapAround(xs: java.util.List[_]): Unit = - xs match { - case xs2: java.util.List[e] => xs2.add(xs2.get(0)) - } -``` - -Crushing the existential ------------------------- - -Let’s consider the `xs.get()` and `xs.add` methods, which have return -type and argument type `E`, respectively. As you can’t write the name -of an existential type in Java, what happens when we “crush” it, -choosing the closest safe type we can write the name of? - -First, we can simplify by considering every existential to be bounded. -That is, instead of `E`, we think about `E extends Object super -Nothing`, or `E <: Any >: Nothing` in Scala. While `Object` or `Any` -is the “top” of the type hierarchy, which *every* type is a subtype -of, `Nothing` is the “bottom”, sadly left out of Java’s type system, -which *every* type is a *supertype* of. - -For `get`, the `E` appears in the result type, a *covariant* position. -So we crush it to the upper bound, `Any`. - -```scala -scala> wordsET.get _ -res2: Int => Any = -``` - -However, for `add`, the `E` appears in the argument type, a -*contravariant* position. So if it is to be crushed, it must be -crushed to the lower bound, `Nothing`, instead. - -```scala -scala> (wordsET: java.util.Collection[_]).add _ : (Any => Boolean) -:12: error: type mismatch; - found : _$1 => Boolean where type _$1 - required: Any => Boolean - (wordsET: java.util.Collection[_]).add _ : (Any => Boolean) - ^ -scala> (wordsET: java.util.Collection[_]).add _ : (Nothing => Boolean) -res8: Nothing => Boolean = -``` - -Each occurrence of an existential in a signature may be crushed -independently. However, a variable that appears once but may be -distributed to either side, such as in a generic type parameter, is -*invariant*, and may not be crushed at that point. That is why the -existential is preserved in the inferred type of `wordsET` itself. - -```scala -scala> wordsET -res9: java.util.List[_] = [hi, there] -``` - -Herein lies something closer to a formalization of the problem with -raw types: they crush existential occurrences in contravariant and -invariant positions to the upper bound, `Object`, when the only safe -positions to crush in this way are the covariant positions. - -How do `List` and `List` relate? ------------------------------------ - -It is well understood that, in Java, `List` is not a subtype -of `List`. In Scala terms, this is because all type -parameters are *invariant*, which has exactly the meaning it had in -the previous section. However, that doesn’t mean it’s impossible to -draw subtyping relationships between different `List`s for different -type arguments; they must merely be mediated by existentials, as is -common in the Java standard library. - -The basic technique is as follows: we can convert any `T` in `List` -to `? extends T super T`. Following that, we can raise the argument -to `extends` and lower the argument to `super` as we like. A `?` by -itself, I have described above, is merely the most extreme course of -this formula you can take. So `List` for any `T` is a subtype of -`List`. (This only applies at one level of depth; -e.g. `List>` is not necessarily a subtype of `List>`.) - -Does this mean that `List` is a subtype of `List`? Well, kind of. -Following the rule for specialization of method signatures in -subclasses, we should be able to override a method that returns -`List` with one that returns `List`, and override a method that -takes `List` as an argument with one that takes `List` as an -argument. However, this is like building a house on a foam mattress: -the conversion that got us a raw type wasn’t sound in the first place, -so what soundness value does this relationship have? - -The frequent Java library bug ------------------------------ - -Let’s see the specific problem that people usually encounter in Scala. -Suppose `addThing`, defined above, is an instance member of `TestEx`: - -```java -class TestEx2 extends TestEx { - @Override - public void addThing(final List xs) {} -} -``` - -Or the Scala version: - -```scala -class TestEx3 extends TestEx { - override def addThing(xs: java.util.List[_]): Unit = () -} -``` - -`javac` gives us this error: - -``` -TestEx.java:48: error: name clash: addThing(List) in TestEx2 and - addThing(List) in TestEx have the same erasure, yet - neither overrides the other - public void addThing(final List xs) {} - ^ -TestEx.java:47: error: method does not override or implement a method - from a supertype - @Override - ^ -``` - -scalac is forgiving, though. I’m not sure how forgiving it is. -However, the forgiveness is unsound: it lets us return less specific -types when overriding methods than we got out. - -How to fix it -------------- - -1. Stop using raw types. - -2. **If you maintain a Java library with raw types in its API, you are - doing a disservice to your users. Eliminate them.** - -3. If you are using such a library, report a bug, or submit a patch, - to eliminate the raw types. If you add `-Xlint:rawtypes` to the - `javac` options, the compiler will tell you where you’re using - them. Fix all the warnings, and you’re definitely not using raw - types anymore. - -4. Help Java projects, including your own, avoid introducing raw types - by adding `-Xlint:rawtypes` permanently to their `javac` options. - **`rawtypes` is more serious than `unchecked`**; even if you do not - care about `unchecked` warnings, you should still turn on and fix - `rawtypes` warnings. - -You may also turn on `-Xlint:cast` to point out casts that are no -longer necessary now that your types are cleaner. If possible, add -`-Werror` to your build as well, to convert `rawtypes` warnings to -errors. - -Why not just add wildcards automatically? ------------------------------------------ - -Adding wildcards isn’t a panacea. For certain raw types, you need to -add a proper type parameter, even adding type parameters to your own -API. The Internet has no copy and paste solutions to offer you; it -all depends on how to model your specific scenario. Here are a few -possibilities. - -1. Pass a type argument representing what’s actually in the structure. - For example, replace `List` with `List` if that’s what it - is. - -2. Pass a wildcard. - -3. Propagate the type argument outward. For example, if you have a - method `List doThis(final List xs)`, maybe it should be ` - List doThis(final List xs)`. Or if you have a `class - Blah` containing a `List`, maybe it should be a `class Blah` containing a `List`. This is often the most flexible - option, but it can take time to implement. - -4. Combine any of these. For example, in some circumstances, a more - flexible version of #3 would be to define `Blah` containing a - `List`. - -Wildcards and existentials are historically misunderstood in the Java -community; Scala developers have the advantage of more powerful -language tools for talking about them. So **if you are unsure of how -to eliminate some raw types, consider asking a Scala developer what to -do!** Perhaps they will tell you “use Scala instead”, and maybe that’s -worth considering, but you’re likely to get helpful advice regardless -of how you feel about language advocacy. - -The Scala philosophy --------------------- - -As you can see, the Java compatibility story in Scala is not as simple -as is advertised. However, I favor the strong stance against this -unsound legacy feature. If Scala can bring an end to the scourge of -raw types, it will have been worth the compatibility trouble. - -*This article was tested with Scala 2.11.5 and javac 1.8.0_31.* diff --git a/src/blog/refactoring-monads.md b/src/blog/refactoring-monads.md deleted file mode 100644 index c8316ce5..00000000 --- a/src/blog/refactoring-monads.md +++ /dev/null @@ -1,341 +0,0 @@ -{% - author: ${mtomko} - date: "2018-08-07" - tags: [technical] -%} - -# Refactoring with Monads - -I was recently cleaning up some Scala code I'd written a few months -ago when I realized I had been structuring code in a very confusing -way for a very long time. At work, we've been trying to untangle the -knots of code that get written by different authors at different -times, as requirements inevitably evolve. We all know that code should -be made up of short, easily digestible functions but we don't always -get guidance on how to achieve that. In the presence of error handling -and nested data structures, the problem gets even harder. - -The goal of this blog post is to describe a concrete strategy for -structuring code so that the overall flow of control is clear to the -reader, even months later; and so the smaller pieces are both -digestible and testable. I'll start by giving an example function, -operating on some nested data types. Then I'll explore some ways to -break it into smaller pieces. The key insight is that we can use -computational effects in the form of -[monads](https://typelevel.org/cats/typeclasses/monad.html) (more -specifically, -[MonadError](https://typelevel.org/cats/api/cats/MonadError.html)) to -wrap smaller pieces and ultimately, compose them into an -understandable sequence of computations. - -### Example domain: reading a catalog - -Let's not worry about `MonadError` yet, but instead look at some -example code. Consider a situation where you need to translate data -from one domain model to another one with different restrictions, and -controlled vocabularies. This can happen in a number of places in a -program, for instance reading a database or an HTTP request to -construct a domain object. - -Suppose we need to read an object from a relational database. -Unfortunately, rows in the table may represent objects of a variety of -types so we have to read the row and build up the object graph -accordingly. This is the boundary between the weakly typed wilderness -and the strongly typed world within our program. - -Say our database table represents a library catalog, which might have -print books and ebooks. We'd like to look up a book by ID and get back -a nicely typed record. - -Here's a simple table - -| id | title | author | format | download_type | -|----|------------------------|----------------|--------|---------------| -| 45 | Programming In Haskell | Hutton, Graham | print | null | -| 46 | Programming In Haskell | Hutton, Graham | ebook | epub | -| 49 | Programming In Haskell | Hutton, Graham | ebook | pdf | - -We can define a simple domain model: - -```scala -sealed trait Format -case object Print extends Format -case object Digital extends Format -object Format { - def fromString(s: String): Try[Format] = ??? -} - -sealed trait DownloadType -case object Epub extends DownloadType -case object Pdf extends DownloadType -object DownloadType { - def fromString(s: String): Try[DownloadType] = ??? -} - -sealed trait Book extends Product with Serializable { - def id: Int - def title: String - def author: String - def format: Format -} - -case class PrintBook( - id: Int, - title: String, - author: String, -) extends Book { - override val format: Format = Print -} - -case class EBook( - id: Int, - title: String, - author: String, - downloadType: DownloadType -) extends Book { - override val format: Format = Digital -} - -``` - -We want to be able to define a method such as: - -```scala -def findBookById(id: Int): Try[Book] = ??? -``` - -### Monolithic function -One trivial definition of `findBookById` might be: - -```scala -import scala.util.{Failure, Success, Try} - -def findBookById(id: Int): Try[Book] = { - // unsafeQueryUnique returns a `Try[Row]` - DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row => - // pick out the properties every book possesses - val id = row[Int]("id") - val title = row[String]("title") - val author = row[String]("author") - val formatStr = row[String]("format") - - // now start to determine the types - get the format first - Format.fromString(formatStr).flatMap { - case Print => - // for print books, we can construct the book and return immediately - Success(PrintBook(id, title, author)) - case Digital => - // for digital books we need to handle the download type - row[Option[String]]("download_type") match { - case None => - Failure(new AssertionError(s"download type not provided for digital book $id")) - case Some(downloadStr) => - DownloadType.fromString(downloadStr).flatMap { dt => - Success(EBook(id, title, author, dt)) - } - } - } - } -} -``` - -Depending on your perspective, that is arguably a long function. If -you think it is not so long, pretend that the table has a number of -other fields that must also be conditionally parsed to construct a -`Book`. - -### Tail refactoring -One possible approach is a a strategy I'm going to call -"tail-refactoring", for lack of a better description. Basically, each -function does a little work or some error checking, and then calls the -next appropriate function in the chain. - -You can imagine what kind of code will result. The functions are -smaller, but it's hard to describe what each function does, and -functions occasionally have to carry along additional parameters that -they will ignore except to pass deeper into the call chain. Let's take -a look at an example refactoring: - -```scala -import scala.util.{Failure, Success, Try} - -def extractEBook( - id: Int, - title: String, - author: String, - downloadTypeStrOpt: Option[String]): Try[EBook] = - downloadTypeStrOpt match { - case None => Failure(new AssertionError()) - case Some(downloadTypeStr) => - DownloadType.fromString(downloadTypeStr).flatMap { dt => - Success(EBook(id, title, author, dt)) - } - } - -def extractBook( - id: Int, - title: String, - author: String, - formatStr: String, - downloadTypeStrOpt: Option[String]): Try[Book] = - Format.fromString(formatStr).flatMap { - case Print => - Success(PrintBook(id, title, author)) - case Digital => - extractEBook(id, title, author, downloadTypeStrOpt) - } - -def findBookById(id: Int): Try[Book] = - DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row => - val id = row[Int]("id") - val title = row[String]("title") - val author = row[String]("author") - val formatStr = row[String]("format") - val downloadTypeStr = row[Option[String]]("download_type") - extractBook(id, title, author, formatStr, downloadTypeStr) - } -``` - -As you can see, this form has more manageably-sized functions, -although they are still a little long. You can also see that the flow -of control is distributed through all three functions, which means -understanding the logic enough to modify or test it requires -understanding all three functions both individually and as a whole. To -follow the logic, we must trace the functions like a recursive descent -parser. - -### Refactoring with Monads -Without throwing exceptions and catching them at the top, it's going -to be hard to do substantially better than the "tail-refactoring" -approach, unless we start to make use of the fact that we're working -with `Try`, a data type that supports `flatMap`. More precisely, `Try` -has a monad instance - recall that monads let us model computational -effects that take place in sequence. - -Let's try to factor out smaller functions, each returning `Try`, and -then use a for-comprehension to specify the sequence of operations: - -```scala -import scala.util.{Failure, Success, Try} - -def parseDownloadType(o: Option[String], id: Int): Try[DownloadType] = { - o.map(DownloadType.fromString) - .getOrElse(Failure(new AssertionError(s"download type not provided for digital book $id"))) -} - -def findBookById(id: Int): Try[Book] = - for { - row <- DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""") - format <- Format.fromString(row[String]("format")) - id = row[Int]("id") - title = row[String]("title") - author = row[String]("author") - book <- format match { - case Print => - Success(PrintBook(id, title, author)) - case Digital => - parseDownloadType(row[Option[String]]("download_type"), id) - .map(EBook(id, title, author, _)) - } - } yield book -``` - -It's less code, the functions are smaller, and the top-level function -dictates the entire flow of control. No function takes more than 2 -arguments. These are testable, understandable functions. This version -really shows the power of using monads to sequence computation. - -Now we are truly making use of the fact that `Try` has a monad instance -and not just another container class. We can simply describe the "happy -path" and trust `Try` to short-circuit computation if something erroneous -or unexpected occurs. In that case, `Try` captures the error and stops -computation there. The code does this without the need for explicit -branching logic. - -### Abstracting effect type -Now, let's take this one step further - here's where we achieve -buzzword compliance. Let's abstract away from the effect, `Try`, and -instead make use of -[MonadError](https://typelevel.org/cats/api/cats/MonadError.html). -This lets us use a more diverse set of effect types, from -[IO](https://typelevel.org/cats-effect/datatypes/io.html) to -[Task](https://monix.io/docs/3x/eval/task.html), so we can execute our -function in whatever asynchronous context we wish. This has the feel -of a tagless final strategy (although we aren't worrying about -describing interpreters here). - -Here we go: - -```scala -import cats.MonadError -import cats.implicits._ - -def parseDownloadType[F[_]](o: Option[String], id: Int)( - implicit me: MonadError[F, Throwable]): F[DownloadType] = { - me.fromOption(o, new AssertionError(s"download type not provided for digital book $id")) - .flatMap(s => me.fromTry(DownloadType.fromString(s))) -} - -def findBookById[F[_]](id: Int)(implicit me: MonadError[F, Throwable]): F[Book] = - for { - row <- DB.queryUnique[F](sql"""select * from catalog where id = $id""") - format <- me.fromTry(Format.fromString(row[String]("format"))) - id = row[Int]("id") - title = row[String]("title") - author = row[String]("author") - book <- format match { - case Print => - me.pure(PrintBook(id, title, author)) - case Digital => - parseDownloadType[F](row[Option[String]]("downloadType"), id) - .map(EBook(id, title, author, _)) - } - } yield book -``` - -The code isn't much more complicated than the version using `Try` but -it adds a lot of flexibility. In a synchronous context, we could still -use `Try`. In that case, however, the database call is executed -eagerly, which means the function isn't referentially transparent. We -can make the function referentially transparent by using a monad such -as `IO` or `Task` as the effect type and delaying the evaluation of -the database call until "the end of the universe". - -In this example, pay attention to the use of -[fromOption](https://typelevel.github.io/cats/api/cats/syntax/ApplicativeErrorExtensionOps.html#fromOption[A](oa:Option[A],ifEmpty:=%3EE):F[A]) -and -[fromTry](https://typelevel.github.io/cats/api/cats/ApplicativeError.html#fromTry[A](t:scala.util.Try[A])(implicitev:Throwable%3C:%3CE):F[A]), -which adapt `Option` and `Try` to `F`. If you are using existing APIs -that aren't already generalized to `MonadError` these methods adapt -common error types, but require very little ceremony to use. - -### Refactoring strategy - -When faced with a similar refactoring problem, consider whether you -can break the problem into a sequence of independently executable -steps, each of which can be wrapped in a monad. If so, begin by -describing the control flow in your refactored function with a monadic -for-comprehension. Don't define the individual functions that comprise -the steps of the for-comprehension until you have filled in the -`yield` at the end. You can use pseudocode or stubs to minimize the -amount of code churn at the beginning. This is a great time to shuffle -steps around and work out exactly what arguments are needed and when, -as well as where they are coming from. - -Once the top level function looks plausible, begin implenting the -steps of the for-comprehension. You can replace the stubs or -pseudocode you wrote by refactoring code from your original function. -If the original code did not operate in a monadic context, recall that -you can convert a simple function `A => B` to `F[A] => F[B]` using -[lift](https://typelevel.org/cats/api/cats/Monad.html#lift[A,B](f:A=%3EB):F[A]=%3EF[B]) -(thanks, -[Functor](https://typelevel.org/cats/typeclasses/functor.html)!). This -makes converting your existing code even easier. - -### Conclusion -In this post, we have seen how we can use monads as an aid in -refactoring code to improve both readability and testability. We have -also demonstrated that we can do this in many cases without needing to -specify the monad in use _a priori_. As a result, we gain the -flexibility to choose the appropriate monad for our application, -independently of the program logic. diff --git a/src/blog/rethinking-monaderror.md b/src/blog/rethinking-monaderror.md deleted file mode 100644 index 5affb5bc..00000000 --- a/src/blog/rethinking-monaderror.md +++ /dev/null @@ -1,455 +0,0 @@ -{% - author: ${lukajcb} - date: "2018-04-13" - tags: [technical] -%} - -# Rethinking MonadError - -`MonadError` is a very old type class, hackage shows me it was originally added in 2001, long before I had ever begun doing functional programming, just check the [hackage page](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Error-Class.html). -In this blog post I'd like to rethink the way we use `MonadError` today. -It's usually used to signal that a type might be capable of error handling and is basically like a type class encoding of `Either`s ability to short circuit. -That makes it pretty useful for building computations from sequences of values that may fail and then halt the computation or to catch those errors in order to resume the computation. -It's also parametrized by its error type, making it one of the most common example of multi-parameter type classes. -Some very common instances include `Either` and `IO`, but there are a ton more. - -We can divide instances into 3 loosely defined groups: - -First we have simple data types like `Either`, `Option` or `Ior` (with `Validated` not having a `Monad` instance). - -Secondly we've got the `IO`-like types, the various `IO`s, `Task`s and the like. These are used to suspend side effects which might have errors and therefore need to be able to handle these. - -Thirdly and least importantly, we have monad transformers, which get their instances from their respective underlying monads. Since they basically just propagate their underlying instances we're only going to talk about the first two groups for now. - -The simple data types all define `MonadError` instances, but I wager they're not actually used as much. This is because `MonadError` doesn't actually allow us to deconstruct e.g. an `Either` to actually handle the errors. We'll see more on that later, next let's look at the `IO`-like types and their instances. - -`cats.effect.IO` currently defines a `MonadError[IO, Throwable]`, meaning that it's fully able to raise and catch errors that might be thrown during evaluation of encapsulated side effects. -Using `MonadError` with these effect types seems a lot more sensical at first, as you can't escape `IO` even when you handle errors, so it looks like it makes sense to stay within `IO` due to the side effect capture. - -The problem I see with `MonadError` is that it does not address the fundamental difference between these two types of instances. I can pattern match an `Option[A]` with a default value to get back an `A`. With `IO` that is just not possible. So these two groups of types are pretty different, when does it actually make sense to abstract over both of them? -Well, it turns out there a few instances where it might be useful, but as we'll see later, I'm proposing something that will be equally useful to both groups. - -Now before we continue, let's look at the `MonadError` type class in a bit more detail. -`MonadError` currently comprises two parts, throwing and catching errors. -To begin let's have a look at the `throw` part, sometimes also called `MonadThrow`: - -```scala -trait MonadError[F[_], E] extends Monad[F] { - def raiseError[A](e: E): F[A] - - ... -} -``` - -This looks fine for now, but one thing that strikes me is that the `F` type seems to "swallow" errors. -If we look at `F[A]` we have no clue that it might actually yield an error of type `E`, that fact is not required to be represented at all. -However, that's not a really big issue, so now let's look at the `catch` part: - -```scala -trait MonadError[F[_], E] extends MonadThrow[F, E] { - ... - - def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] -} -``` - -Immediately I have a few questions, if the errors are handled, why does it return the exact same type? -Furthermore if this is really supposed to handle errors, what happens if I have errors in the `E => F[A]` function? -This is even more blatant in the `attempt` function: - -```scala -trait MonadError[F[_], E] extends Monad[F] { - def attempt[A](fa: F[A]): F[Either[E, A]] -} -``` - -Here there is no way the outer `F` still has any errors, so why does it have the same type? -Shouldn't we represent the fact that we handled all the errors in the type system? -This means you can't actually observe that the errors are now inside `Either`. That leads to this being fully legal code: - -```scala -import cats.implicits._ -// import cats.implicits._ - -Option(42).attempt.attempt.attempt.attempt -// res0: Option[Either[Unit,Either[Unit,Either[Unit,Either[Unit,Int]]]]] = Some(Right(Right(Right(Right(42))))) -``` - -Another example that demonstrates this is the fact that calling `handleError`, which looks like this: - -```scala -def handleError[A](fa: F[A])(f: E => A): F[A] -``` -also returns an `F[A]`. This method takes a pure function `E => A` and thus can not fail during recovery like `handleErrorWith`, yet it still doesn't give us any sign that it doesn't throw errors. -For `IO`-like types this is somewhat excusable as something like an unexceptional `IO` is still very uncommon, but for simple data types like `Either` or `Some` that function should just return an `A`, since that's the only thing it can be. -Just like with `attempt`, we can infinitely chain calls to `handleError`, as it will never change the type. - -Ideally our type system should stop us from being able to write this nonsensical code and give us a way to show anyone reading the code that we've already handled errors. -Now I'm not saying that the functions on `MonadError` aren't useful, but only that they could be more constrained and thus more accurate in their representation. - - -For this purpose let's try to write a different `MonadError` type class, one that's designed to leverage the type system to show when values are error-free, we'll call it `MonadBlunder` for now. - -To mitigate the problems with `MonadError` we have a few options, the first one I'd like to present is using two different type constructors to represent types that might fail and types that are guaranteed not to. So instead of only a single type constructor our `MonadBlunder` class will have two: - -```scala -trait MonadBlunder[F[_], G[_], E] -``` - -Our type class now has the shape `(* -> *) -> (* -> *) -> * -> *`, which is quite a handful, but I believe we can justify its usefulness. -The first type parameter `F[_]` will represent our error-handling type, which will be able to yield values of type `E`. -The second type parameter `G[_]` will represent a corresponding type that does not allow any errors and can therefore guarantee that computations of the form `G[A]` will always yield a value of type `A`. - -Now that we figured out the shape, let's see what we can actually do with it. -For throwing errors, we'll create a `raiseError` function that should return a value inside `F`, as it will obviously be able to yield an error. - -```scala -trait MonadBlunder[F[_], G[_], E] { - def raiseError[A](e: E): F[A] -} -``` - -This definition looks identical to the one defined one `MonadError` so let's move on to error-handling. -For handled errors, we want to return a value inside `G`, so our `handleErrorWith` function should indeed return a `G[A]`: - -```scala -trait MonadBlunder[F[_], G[_], E] { - ... - - def handleErrorWith[A](fa: F[A])(f: E => F[A]): G[A] -} -``` - -Looks good so far, right? -Well, we still have the problem that `f` might return an erronous value, so if we want to guarantee that the result won't have any errors, we'll have to change that to `G[A]` as well: - -```scala -trait MonadBlunder[F[_], G[_], E] { - ... - - def handleErrorWith[A](fa: F[A])(f: E => G[A]): G[A] -} -``` - -And now we're off to a pretty good start, we fixed one short coming of `MonadError` with this approach. - -Another approach, maybe more obvious to some, might be to require the type constructor to take two arguments, one for the value and one for the error type. -Let's see if we can define `raiseError` on top of it: - -```scala -trait MonadBlunder[F[_, _]] { - def raiseError[E, A](e: E): F[E, A] - - ... -} -``` - -This looks pretty similar to what we already have, though now we have the guarantee that our type doesn't actually "hide" the error-type somewhere. -Next up is `handleErrorWith`. Ideally after we handled the error we should again get back a type that signals that it doesn't have any errors. -We can do exactly that by choosing an unhabited type like `Nothing` as our error-type: - - -```scala -trait MonadBlunder[F[_, _]] { - ... - - def handleErrorWith[E, A](fa: F[E, A])(f: E => F[Nothing, A]): F[Nothing, A] -} -``` - -And this approach works as well, however now we've forced the two type parameter shape onto implementors. This `MonadBlunder` has the following kind `(* -> * -> *) -> *`. -This means we can very easily define instances for types with two type parameters like `Either`. -However, one issue might be that it's much easier to fit a type with two type parameters onto a type class that expects a single type constructor `(* -> *)` than to do it the other way around. - -For example try to implement the above `MonadBlunder[F[_, _]]` for the standard `cats.effect.IO`. -It's not going to be simple, whereas with the first encoding we can easily encode both `Either` and `IO`. For this reason, I will continue this article with the first encoding using the two different type constructors. - -Next we're going to look at laws we can define to make sense of the behaviour we want. -The first two laws should be fairly obvious. -If we `flatMap` over a value created by `raiseError` it shouldn't propogate: - -```scala -def raiseErrorStops(e: E, f: A => F[A]): Boolean = - F.raiseError[A](e).flatMap(f) === F.raiseError[A](e) -``` - -Next we're going to formulate a law that states, that raising an error and then immediatly handling it with a given function should be equivalent to just calling that function on the error value: - -```scala -def raiseErrorHandleErrorWith(e: E, f: E => G[A]): Boolean = - raiseError[A](e).handleErrorWith(f) === f(e) -``` - -Another law could state that handling errors for a pure value lifted into the `F` context does nothing and is equal to the pure value in the `G` context: - -```scala -def handleErrorPureIsPure(a: A, f: E => G[A]): Boolean = - a.pure[F].handleErrorWith(f) === a.pure[G] -``` - -Those should be good for now, but we'll be able to find more when we add more derived functions to our type class. -Also note that none of the laws are set in stone, these are just the ones I came up with for now, it's completely possible that we'll need to revise these in the future. - -Now let's focus on adding extra functions to our type class. `MonadError` offer us a bunch of derived methods that can be really useful. For most of those however we need access to methods like `flatMap` for both `F` and `G`, so before we figure out derived combinators, let's revisit how exactly we define the type class. - -The easiest would be to give both `F` and `G` a `Monad` constraint and move on. -But then we'd have two type classes that both define a `raiseError` function extends `Monad`, and we wouldn't be able to use them together, since that would cause ambiguities and as I've said before, the functions on `MonadError` are useful in some cases. - -Instead, since I don't really like duplication and the fact that we're not going to deprecate `MonadError` overnight, I decided to extend `MonadBlunder` from `MonadError` for the `F` type, to get access to the `raiseError` function. -If `raiseError` and `handleErrorWith` were instead separated into separate type classes (as is currently the case in the PureScript prelude), we could extend only the `raiseError` part. -This also allows us to define laws that our counterparts of functions like `attempt` and `ensure` are consistent with the ones defined on `MonadError`. -So the type signature now looks like this (expressed in Haskell, since it's easier on the eyes): -```haskell -class (MonadError f e, Monad g) => MonadBlunder f g e | f -> e, f -> g where - ... -``` - -In Scala, we can't express this as nicely, so we're going to have to use something close to the `cats-mtl` encoding: - -```scala -trait MonadBlunder[F[_], G[_], E] { - val monadErrorF: MonadError[F, E] - val monadG: Monad[G] - - ... -} -``` - -Now since this means that any instance of `MonadBlunder` will also have an instance of `MonadError` on `F`, we might want to rename the functions we've got so far. -Here's a complete definition of what we've come up with with `raiseError` removed and `handleErrorWith` renamed to `handleBlunderWith`: - -```scala -trait MonadBlunder[F[_], G[_], E] { - val monadErrorF: MonadError[F, E] - val monadG: Monad[G] - - def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A] -} -``` - -Now let us go back to defining more derived functions for `MonadBlunder`. -The easiest probably being `handleError`, so let's see if we can come up with a good alternative: - -```scala -trait MonadBlunder[F[_], G[_], E] { - ... - - def handleBlunder[A](fa: F[A])(f: E => A): G[A] = - handleBlunderWith(fa)(f andThen (_.pure[G])) -} -``` - -This one is almost exactly like `handleBlunderWith`, but takes a function from `E` to `A` instead of to `G[A]`. We can easily reuse `handleBlunderWith` by using `pure` to go back to `E => G[A]`. - -Next another function that's really useful is `attempt`. -Our alternative, let's call it `endeavor` for now, should return a value in `G` instead, which doesn't have a `MonadError` instance and therefore can not make any additional calls to `endeavor`: - - -```scala -trait MonadBlunder[F[_], G[_], E] { - ... - - def endeavor[A](fa: F[A]): G[Either[E, A]] = - handleBlunder(fa.map(Right(_)))(Left(_)) -} -``` - -The implementation is fairly straightforward as well, we just handle all the errors by lifting them into the left side of an `Either` and map successful values to the right side of `Either`. - -Next, let's look at the dual to `attempt`, called `rethrow` in Cats. -For `MonadError` it turns an `F[Either[E, A]]` back into an `F`, but we're going to use our unexceptional type again: - -```scala -trait MonadBlunder[F[_], G[_], E] { - ... - - def absolve[A](fa: G[Either[E, A]]): F[A] = ??? -} -``` - - -But looking at this signature, we quickly realize that we need a way to get back to `F[A]` from `G[A]`. -So we're going to add another function to our minimal definition: - -```scala -trait MonadBlunder[F[_], G[_], E] { - val monadErrorF: MonadError[F, E] - val monadG: Monad[G] - - def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A] - - def accept[A](ga: G[A]): F[A] - -} -``` - -This function `accept`, allows us to lift any value without errors into a context where errors might be present. - -We can now formulate a law that values in `G` never stop propagating, so `flatMap` should always work, we do this by specifying that calling `handleBlunder` after calling `accept` on any `G[A]`, is never going to actually change the value: - -```scala -def gNeverHasErrors(ga: G[A], f: E => A): Boolean = - accept(ga).handleBlunder(f) === ga -``` - -Now we can go back to implementing the `absolve` function: - -```scala -def absolve[A](gea: G[Either[E, A]]): F[A] = - accept(gea).flatMap(_.fold(raiseError[A], _.pure[F])) -``` - -Now that we've got the equivalent of both `attempt` and `rethrow`, let's add a law that states that the two should cancel each other out: - -```scala -def endeavorAbsolve(fa: F[A]): Boolean = - absolve(fa.endeavor) === fa -``` - -We can also add laws so that `handleBlunder` and `endeavor` are consistent with their counterparts now that we have `accept`: - -```scala -def deriveHandleError(fa: F[A], f: E => A): Boolean = - accept(fa.handleBlunder(f)) === fa.handleError(f) - -def deriveAttempt(fa: F[A]): Boolean = - accept(fa.endeavor) === fa.attempt -``` - -One nice thing about `attempt`, is that it's really easy to add a derivative combinator that doesn't go to `F[Either[E, A]]`, but to the isomorphic monad transformer `EitherT[F, E, A]`. -We can do the exact same thing with `endeavor`: - - -```scala -def endeavorT[A](fa: F[A]): EitherT[G, E, A] = - EitherT(endeavor(fa)) -``` - -One last combinator I'd like to "port" from `MonadError` is the `ensureOr` function. -`ensureOr` turns a successful value into an error if it does not satisfy a given predicate. -We're going to name the counterpart `assureOr`: - -```scala -def assureOr[A](ga: G[A])(error: A => E)(predicate: A => Boolean): F[A] = - accept(ga).flatMap(a => - if (predicate(a)) a.pure[F] else raiseError(error(a)) - ) -``` - -This plays nicely with the rest of our combinators and we can again add a law that dictates it must be consistent with `ensureOr`: - -```scala -def deriveEnsureOr(ga: G[A])(error: A => E)(predicate: A => Boolean): Boolean = - ensureOr(accept(ga))(error)(predicate) === assureOr(ga)(error)(predicate) -``` - -Now we have a great base to work with laws that should guarantee principled and sensible behaviour. -Next we'll actually start defining some instances for our type class. - -The easiest definitions are for `Either` and `Option`, though I'm not going to cover both, as the instances for `Option` can simply be derived by `Either[Unit, A]`and I'm going to link to the code at the end. -For `Either[E, A]`, when we handle all errors of type `E`, all we end up with is `A`, so the corresponding `G` type for our instance should be `Id`. -That leaves us with the following definition: - -```scala -implicit def monadBlunderEither[E]: MonadBlunder[Either[E, ?], Id, E] = - new MonadBlunder[Either[E, ?], Id, E] { - val monadErrorF = MonadError[Either[E, ?], E] - val monadG = Monad[Id] - - def handleBlunderWith[A](fa: Either[E, A])(f: E => A): A = fa match { - case Left(e) => f(e) - case Right(a) => a - } - - def accept[A](ga: A): Either[E, A] = Right(ga) - } -``` - -Fairly straightforward, as `Id[A]` is just `A`, but with this instance we can already see a small part of the power we gain over `MonadError`. -When we handle errors with `handleBlunder`, we're no longer "stuck" inside the `Either` Monad, but instead have a guarantee that our value is free of errors. -Sometimes it'll make sense to stay inside `Either`, but we can easily get back into `Either`, so we have full control over what we want to do. - -Next up, we'll look at `IO` and the type that inspired this whole blog post `UIO`. -`UIO` is equivalent to an `IO` type where all errors are handled and is short for "unexceptional IO". -`UIO` currently lives inside my own `cats-uio` library, but if things go well, we might see it inside `cats-effect` eventually. This would also work for `IO` types who use two type parameters `IO[E, A]` where the first represents the error type and the second the actual value. There you'd choose `IO[E, A]` as the `F` type and `IO[Nothing, A]` as the `G` type. `IO[Nothing, A]` there is equivalent to `UIO[A]`. - -As one might expect, you can not simply go from `IO[A]` to `UIO[A]`, but we'll need to go from `IO[A]` to `UIO[Either[E, A]]` instead, which if you look at it, is exactly the definition of `endeavor`. -Now let's have a look at how the `MonadBlunder` instance for `IO` and `UIO` looks: - -```scala -implicit val monadBlunderIO: MonadBlunder[IO, UIO, Throwable] = - new MonadBlunder[IO, UIO, Throwable] { - val monadErrorF = MonadError[IO, Throwable] - val monadG = Monad[UIO] - - def handleBlunderWith[A](fa: IO[A])(f: Throwable => UIO[A]): UIO[A] = - UIO.unsafeFromIO(fa.handleErrorWith(f andThen accept)) - - def accept[A](ga: UIO[A]): IO[A] = UIO.runUIO(ga) - } -``` - -And voila! We've got a fully working implementation that will allow us to switch between these two types whenever we have a guarantee that all errors are handled. -This makes a lot of things much simpler. -For example, if one wants to use `bracket` with `UIO`, you just need to `flatMap` to the finalizer, as `flatMap` is always guaranteed to not short-circuit. - -We can also define instances for `EitherT` and `OptionT` (being isomorphic to `EitherT[F, Unit, A]`), where the corresponding unexceptional type is just the outer `F`, so `endeavor` is just a call to `.value`: - -```scala -implicit def catsEndeavorForEitherT[F[_]: Monad, E]: MonadBlunder[EitherT[F, E, ?], F, E] = - new MonadBlunder[EitherT[F, E, ?], F, E] { - val monadErrorF = MonadError[EitherT[F, E, ?], E] - val monadG = Monad[F] - - override def endeavor[A](fa: EitherT[F, E, A]): F[Either[E, A]] = - fa.value - - def handleBlunderWith[A](fa: EitherT[F, E, A])(f: E => F[A]): F[A] = - fa.value.flatMap { - case Left(e) => f(e) - case Right(a) => a.pure[F] - } - - def accept[A](ga: F[A]): EitherT[F, E, A] = - EitherT.liftF(ga) - - } -``` - -Finally, it's also possible to create instances for other standard monad transformers like `WriterT`, `ReaderT` or `StateT` as long as their underlying monads themselves have instances for `MonadBlunder`, as is typical in mtl. -As their implementations are very similar we'll only show the `StateT` transformer instance: - -```scala -implicit def catsEndeavorForStateT[F[_], G[_], S, E] - (implicit M: MonadBlunder[F, G, E]): MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] = - new MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] { - implicit val F: MonadError[F, E] = M.monadErrorF - implicit val G: Monad[G] = M.monadG - - val monadErrorF = MonadError[StateT[F, S, ?], E] - val monadG = Monad[StateT[G, S, ?]] - - def accept[A](ga: StateT[G, S, A]): StateT[F, S, A] = ga.mapK(new (G ~> F) { - def apply[T](ga: G[T]): F[T] = M.accept(ga) - }) - - def handleBlunderWith[A](fa: StateT[F, S, A])(f: E => StateT[G, S, A]): StateT[G, S, A] = - IndexedStateT(s => M.handleBlunderWith(fa.run(s))(e => f(e).run(s))) - - } -``` - -In practice this means we can call `handleBlunderWith` on things like `StateT[IO, S, A]` and get back a `StateT[UIO, S, A]`. Pretty neat! -You can also create instances for pretty much any `MonadError` using `Unexceptional`, e.g.: `MonadBlunder[Future, Unexceptional[Future, ?], Throwable]`. The `Unexceptional` type is designed to turn any erroring type into one that doesn't throw errors by catching them with `attempt`. - -## Conclusion - -In this article, I've tried to present the argument that `MonadError` is insufficient for principled error handling. -We also tried to build a solution that deals with the shortcomings described earlier. -Thereby it seeks not to replace, but to expand on `MonadError` to get a great variety of error handling capabilities. -I believe the `MonadBlunder` type class, or whatever it will be renamed to, can be a great addition not just to the Cats community, but to the functional community at large, especially as it's much easier to express in languages like `PureScript` and `Haskell`. - -For now, all of the code lives inside the [cats-uio repo](https://github.com/LukaJCB/cats-uio), which houses the `MonadBlunder` type class the `UIO` data type and the `Unexceptional` data type. -I hope that this blog post gave a motivation as to why I created the library and why it might be nice to adopt some of its features into the core typelevel libraries. - -Note again, that none of this is final or set in stone and before it arrives anywhere might still change a lot, especially in regards to naming (which I'm not really happy with at the moment), so if you have any feedback of any sorts, please do chime in! Would love to hear your thoughts and thank you for reading this far! diff --git a/src/blog/scala-center.md b/src/blog/scala-center.md deleted file mode 100644 index 19f2678e..00000000 --- a/src/blog/scala-center.md +++ /dev/null @@ -1,41 +0,0 @@ -{% - author: ${larsrh} - date: "2016-10-18" - tags: [governance] -%} - -# Typelevel representative at the Scala Center Advisory Board - -It is our pleasure to announce that the Scala Center Advisory Board has invited us to nominate a member of the Typelevel community to serve as a community representative, alongside Bill Venners. -To figure out whether or not we should accept this offer and who we pick we have held [an open discussion on GitHub](https://github.com/typelevel/general/issues/42), resulting in my nomination, which I happily accept. -Thanks everyone for their trust! -The corresponding Scala Center announcement can be found [here](http://scala-lang.org/blog/2016/10/24/lars-hupel-joins-sc-board.html). - -_On a more personal note: -Some years ago, when I initially registered the `typelevel.org` domain, I could have never anticipated the large community which has gathered around typeful functional programming in Scala. -The offer from the Scala Center to nominate a representative is an acknowledgement, and also a positive signal for community outreach. -So, naturally, I'm very excited about what lies ahead. -Keep rocking!_ - -What follows is a summary of the process. - -## Scala Center Advisory Board - -To quote [Jon Pretty](http://www.scala-lang.org/blog/2016/05/30/scala-center-advisory-board.html): - -> The Advisory Board is a separate body from the Scala Center, much as many governments have separate legislative and executive branches: the Advisory Board makes recommendations to the Scala Center on the work we should do, but it’s the Scala Center’s job to execute those recommendations. -> -> It currently has seven voting members: representatives from each of our six sponsors, plus Bill Venners, the community representative. Additionally the Executive Director of the Scala Center, Heather Miller, sits on the board to report on the Scala Center’s activities, and provide advice on the feasibility of the proposals under consideration, and Martin Odersky is the technical advisor to the board. - -More details, including minutes and bylaws, can be found on [their website](https://scala.epfl.ch/). - -## Process - -To make the nomination as transparently as possible, I [opened an issue](https://github.com/typelevel/general/issues/42) shortly after Jon mailed me about the opening. -The discussion period ended on October 12th. -All nominees endorsed me, so no vote was necessary. - -## Provisions - -As indicated in the discussion about the nomination, we have established that the term length will be one year and after that, we will have another vote. -I hope this measure will improve community participation. diff --git a/src/blog/scala-coc.md b/src/blog/scala-coc.md deleted file mode 100644 index 0280afc3..00000000 --- a/src/blog/scala-coc.md +++ /dev/null @@ -1,16 +0,0 @@ -{% - author: ${typelevel} - date: "2016-12-17" - tags: [governance] -%} - -# Endorsing the new Scala Code of Conduct - -A couple of days ago, the new [Scala Code of Conduct](https://contributors.scala-lang.org/t/please-read-scala-code-of-conduct/28) was published. -It applies to all official Scala channels, including mailing lists, Gitter channels and GitHub repositories. -We would like to take this opportunity to endorse this new Code of Conduct. -From our perspective, it does a good job of listing encouraged behaviour, instead of just banning harassment: It reflects the goal of _actively_ creating a welcoming community. -Also, we consider it to be a decent substitute of our own [Code of Conduct](/code-of-conduct/README.md). -That means that Typelevel project maintainers are free to switch to the Scala Code of Conduct if they wish. - -For some more background, please see the [discussion on GitHub](https://github.com/typelevel/general/issues/51). diff --git a/src/blog/scala-io-2016-10-27.md b/src/blog/scala-io-2016-10-27.md deleted file mode 100644 index d82add20..00000000 --- a/src/blog/scala-io-2016-10-27.md +++ /dev/null @@ -1,26 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-10-27" - event-date: "October 27-28, 2016" - event-location: "CPE Lyon, France" - tags: [events] -%} - -# Scala.IO - -@:include(/img/places/lyon.md) - -## About the Conference - -The Scala event in France, organized by French Scala community volunteers, featuring advanced functional programming talks. -CfP is still open until September 5. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Learn More](http://scala.io)@:@ -@:@ - -## Venue - -This event will take place at the CPE School in Lyon, France. - - diff --git a/src/blog/scalaxhack-2016-12-10.md b/src/blog/scalaxhack-2016-12-10.md deleted file mode 100644 index d458820a..00000000 --- a/src/blog/scalaxhack-2016-12-10.md +++ /dev/null @@ -1,48 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-12-10" - event-date: "December 10, 2016" - event-location: "CodeNode, London" - tags: [events] -%} - -# Scala Exchange Hack Day and Unconference - -@:include(/img/places/london.md) - -We're partnering with [Skills Matter][skillsmatter] -and the [London Scala User Group][lsug] -to organise the second [Scala Exchange Hack Day][scalaxhack], -a free day of unconference and hack sessions, -co-located with [Scala Exchange][scalax]. - -The event is open to the whole community, -not just attendees of Scala Exchange, -and will run on 10th December 2016 -(the day after Scala Exchange) -at [CodeNode][codenode], -Skills Matter's dedicated conference venue and tech hub -near Moorgate Station in central London. - -The full list of sessions and topics -will be decided by the participants on the day. -There are some preliminary ideas on -the [ScalaxHack web site][scalaxhack]. -We'll include hands-on hack sessions -to help people get involved with Scala open source, -including projects from inside and outside -the Typelevel family. - -ScalaxHack is a free event. -Lunch will be sponsored by [Underscore][underscore]. - -@:style(bulma-has-text-centered) -@:style(bulma-button bulma-is-large bulma-is-link)[Register](https://skillsmatter.com/conferences/7975-scalaxhack)@:@ -@:@ - -[skillsmatter]: http://skillsmatter.com -[lsug]: http://meetup.com/london-scala -[scalaxhack]: https://skillsmatter.com/conferences/7975-scalaxhack -[scalax]: http://www.scala-exchange.com -[codenode]: https://skillsmatter.com/contact-us -[underscore]: https://underscore.io diff --git a/src/blog/semirings.md b/src/blog/semirings.md deleted file mode 100644 index 7833870a..00000000 --- a/src/blog/semirings.md +++ /dev/null @@ -1,438 +0,0 @@ -{% - author: ${lukajcb} - date: "2018-11-02" - tags: [technical] -%} - -# A tale on Semirings - -*Ever wondered why sum types are called sum types? -Or maybe you've always wondered why the `<*>` operator uses exactly these symbols? -And what do these things have to do with Semirings? -Read this article and find out!* - -Most of us know and use `Monoid`s and `Semigroup`s. -They're super useful and come with properties that we can directly utilize to gain a higher level of abstractions at very little cost (In case you don't know about them, check out the [Cats documentation](https://typelevel.org/cats/typeclasses/semigroup.html) for some insight). -Sometimes, however, certain types can have multiple `Monoid` or `Semigroup` instances. -An easy example are the various numeric types where both multiplication and addition form two completely lawful monoid instances. - -In abstract algebra there is a an algebraic class for types with two `Monoid` instances that interact in a certain way. -These are called `Semiring`s (sometimes also `Rig`) and they are defined as two `Monoid`s with some special laws that define the interactions between them. -Because they are often used to describe numeric data types we usually classify them as *Additive* and *Multiplicative*. -Just like with numeric types the laws of `Semiring` state that multiplication has to distribute over addition and multiplying a value with the additive identity (i.e. zero) absorbs the value and becomes zero. - -There are different ways to encode this as type classes and different libraries handle this differently, but let's look at how the [algebra](https://typelevel.org/algebra/) project handles this. -Specifically, it defines a separate `AdditiveSemigroup` and `MultiplicativeSemigroup` and goes from there. - -```scala -import simulacrum._ - -@typeclass trait AdditiveSemigroup[A] { - def +(x: A)(y: A): A -} - -@typeclass trait AdditiveMonoid[A] extends AdditiveSemigroup[A] { - def zero: A -} - -@typeclass trait MultiplicativeSemigroup[A] { - def *(x: A)(y: A): A -} - -@typeclass trait MultiplicativeMonoid[A] extends MultiplicativeSemigroup[A] { - def one: A -} -``` - -A `Semiring` is then just an `AdditiveMonoid` coupled with a `MultiplicativeMonoid` with the following extra laws: - - -1. Additive commutativity, i.e. `x + y === y + x` -2. Right distributivity, i.e. `(x + y) * z === (x * z) + (y * z)` -3. Left distributivity, i.e. `x * (y + z) === (x * y) + (x * z)` -4. Right absorption, i.e. `x * zero === zero` -5. Left absorption, i.e. `zero * x === zero` - -To define it as a type class, we simply extend from both additive and multiplicative monoid: - -```scala -@typeclass trait Semiring[A] extends MultiplicativeMonoid[A] with AdditiveMonoid[A] -``` - -Now we have a `Semiring` class, that we can use with the various numeric types like `Int`, `Long`, `BigDecimal` etc, but what else is a `Semiring` and why dedicate a whole blog post to it? - -It turns out a lot of interesting things can be `Semiring`s, including `Boolean`s, `Set`s and [animations](https://bkase.github.io/slides/algebra-driven-design/#/). - -One very interesting thing I'd like to point out is that we can form a `Semiring` homomorphism from types to their number of possible inhabitants. -What the hell is that? -Well, bear with me for a while and I'll try to explain step by step. - -### Cardinality - -Okay, so let's start with what I mean by cardinality. -Every type has a specific number of values it can possibly have, e.g. a `Boolean` has cardinality of 2, because it has two possible values: `true` and `false`. - -So `Boolean` has two, how many do other primitive types have? -`Byte` has 2^8, `Short` has 2^16, `Int` has 2^32 and `Long` has 2^64. -So far so good, that makes sense, what about something like `String`? -`String` is an unbounded type and therefore theoretically has infinite number of different inhabitants (practically of course, we don't have infinite memory, so the actual number may vary depending on your system). - -For what other types can we determine their cardinality? -Well a couple of easy ones are `Unit`, which has exactly one value it can take and also `Nothing`, which is the "bottom" type in Scala, which means being a subtype of every possible other type and has 0 possible values. I.e you can never instantiate a value of `Nothing`, which gives it a cardinality of 0. - -That's neat, maybe we can encode this in actual code. -We could create a type class that should be able to give us the number of inhabitants for any type we give it: - -```scala -trait Cardinality[A] { - def cardinality: BigInt -} - -object Cardinality { - def of[A: Cardinality]: BigInt = apply[A].cardinality - - def apply[A: Cardinality]: Cardinality[A] = implicitly -} -``` - -Awesome! -Now let's try to define some instances for this type class: - -```scala -implicit def booleanCardinality = new Cardinality[Boolean] { - def cardinality: BigInt = BigInt(2) -} - -implicit def longCardinality = new Cardinality[Long] { - def cardinality: BigInt = BigInt(2).pow(64) -} - -implicit def intCardinality = new Cardinality[Int] { - def cardinality: BigInt = BigInt(2).pow(32) -} - -implicit def shortCardinality = new Cardinality[Short] { - def cardinality: BigInt = BigInt(2).pow(16) -} - -implicit def byteCardinality = new Cardinality[Byte] { - def cardinality: BigInt = BigInt(2).pow(8) -} - -implicit def unitCardinality = new Cardinality[Unit] { - def cardinality: BigInt = 1 -} - -implicit def nothingCardinality = new Cardinality[Nothing] { - def cardinality: BigInt = 0 -} -``` - -Alright, this is cool, let's try it out in the REPL! - -```scala -scala> Cardinality.of[Int] -res11: BigInt = 4294967296 - -scala> Cardinality.of[Unit] -res12: BigInt = 1 - -scala> Cardinality.of[Long] -res13: BigInt = 18446744073709551616 -``` - -Cool, but this is all very simple, what about things like ADTs? -Can we encode them in this way as well? -Turns out, we can, we just have to figure out how to handle the basic product and sum types. -To do so, let's look at an example of both types. -First, we'll look at a simple product type: `(Boolean, Byte)`. - -How many inhabitants does this type have? -Well, we know `Boolean` has 2 and `Byte` has 256. -So we have the numbers from `-127` to `128` once with `true` and once again with `false`. -That gives us `512` unique instances. -Hmmm.... - -`512` seems to be double `256`, so maybe the simple solution is to just multiply the number of inhabitants of the first type with the number of inhabitants of the second type. -If you try this with other examples, you'll see that it's exactly true, awesome! -Let's encode that fact in a type class instance: - -```scala -implicit def tupleCardinality[A: Cardinality, B: Cardinality] = - new Cardinality[(A, B)] { - def cardinality: BigInt = Cardinality[A].cardinality * Cardinality[B].cardinality - } -``` - -Great, now let's look at an example of a simple sum type: `Either[Boolean, Byte]`. -Here the answer seems even more straight forward, since a value of this type can either be one or the other, we should just be able to add the number of inhabitants of one side with the number of inhabitants of the other side. -So `Either[Boolean, Byte]` should have `2 + 256 = 258` number of inhabitants. Cool! - -Let's also code that up and try and confirm what we learned in the REPL: - -```scala -implicit def eitherCardinality[A: Cardinality, B: Cardinality] = - new Cardinality[Either[A, B]] { - def cardinality: BigInt = Cardinality[A].cardinality + Cardinality[B].cardinality - } -``` - -```scala -scala> Cardinality.of[(Boolean, Byte)] -res14: BigInt = 512 - -scala> Cardinality.of[Either[Boolean, Byte]] -res15: BigInt = 258 - -scala> Cardinality.of[Either[Int, (Boolean, Unit)]] -res16: BigInt = 4294967298 -``` - -So using sum types seem to add the number of inhabitants whereas product types seem to multiply the number of inhabitants. -That makes a lot of sense given their names! - - -So what about that homomorphism we talked about earlier? -Well, a homomorphism is a structure-preserving mapping function between two algebraic structures of the same sort (in this case a semiring). - -This means that for any two values `x` and `y` and the homomorphism `f`, we get -1. `f(x * y) === f(x) * f(y)` -2. `f(x + y) === f(x) + f(y)` - -Now this might seem fairly abstract, but it applies exactly to what we just did. -If we *"add"* two types of `Byte` and `Boolean`, we get an `Either[Byte, Boolean]` and if we apply the homomorphism function, `number` to it, we get the value `258`. -This is the same as first calling `number` on `Byte` and then adding that to the result of calling `number` on `Boolean`. - -And of course the same applies to multiplication and product types. -However, we're still missing something from a valid semiring, we only talked about multiplication and addition, but not about their respective identities. - -What we did see, though is that `Unit` has exactly one inhabitant and `Nothing` has exactly zero. -So maybe we can use these two types to get a fully formed Semiring? - -Let's try it out! -If `Unit` is `one` then a product type of any type with `Unit` should be equivalent to just the first type. - -Turns out, it is, we can easily go from something like `(Int, Unit)` to `Int` and back without losing anything and the number of inhabitants also stay exactly the same. - -```scala -scala> Cardinality.of[Int] -res17: BigInt = 4294967296 - -scala> Cardinality.of[(Unit, Int)] -res18: BigInt = 4294967296 - -scala> Cardinality.of[(Unit, (Unit, Int))] -res19: BigInt = 4294967296 -``` - -Okay, not bad, but how about `Nothing`? -Given that it is the identity for addition, any type summed with `Nothing` should be equivalent to that type. -Is `Either[Nothing, A]` equivalent to `A`? -It is! Since `Nothing` doesn't have any values an `Either[Nothing, A]` can only be a `Right` and therefore only an `A`, so these are in fact equivalent types. - -We also have to check for the absorption law that says that any value mutliplied with the additive identity `zero` should be equivalent to `zero`. -Since `Nothing` is our `zero` a product type like `(Int, Nothing)` should be equivalent to `Nothing`. -This also holds, given the fact that we can't construct a `Nothing` so we can never construct a tuple that expects a value of type `Nothing` either. - -Let's see if this translates to the number of possible inhabitants as well: - -Additive Identity: -```scala -scala> Cardinality.of[Either[Nothing, Boolean]] -res0: BigInt = 2 - -scala> Cardinality.of[Either[Nothing, (Byte, Boolean)]] -res1: BigInt = 258 -``` - -Absorption: -```scala -scala> Cardinality.of[(Nothing, Boolean)] -res0: BigInt = 0 - -scala> Cardinality.of[(Nothing, Long)] -res1: BigInt = 0 -``` - -Nice! -The only thing left now is distributivity. -In type form this means that `(A, Either[B, C])` should be equal to `Either[(A, B), (A, C)]`. -If we think about it, these two types should also be exactly equivalent, woohoo! - -```scala -scala> Cardinality.of[(Boolean, Either[Byte, Short])] -res20: BigInt = 131584 - -scala> Cardinality.of[Either[(Boolean, Byte), (Boolean, Short)]] -res21: BigInt = 131584 -``` - -## Higher kinded algebraic structures - -Some of you might have heard of the `Semigroupal` type class. -But why is it called that, and what is its relation to a `Semigroup`? -Let's find out! - -First, let's have a look at `Semigroupal`: - -```scala -@typeclass trait Semigroupal[F[_]] { - def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] -} -``` - -It seems to bear some similarity to `Semigroup`, we have two values which we somehow combine, and it also shares `Semigroup`s associativity requirement. - -So far so good, but the name `product` seems a bit weird. -It makes sense given we combine the `A` and the `B` in a tuple, which is a product type, but if we're using products, maybe this isn't a generic `Semigroupal` but actually a multiplicative one? -Let's fix this and rename it! - -```scala -@typeclass trait MultiplicativeSemigroupal[F[_]] { - def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] -} -``` - -Next, let us have a look at what an additive `Semigroupal` might look like. -Surely, the only thing we'd have to change is going from a product type to a sum type: - -```scala -@typeclass trait AdditiveSemigroupal[F[_]] { - def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] -} -``` - -Pretty interesting so far, can we top this and add identities to make `Monoidal`s? -Surely we can! For addition this should again be `Nothing` and `Unit` for multiplication: - -```scala -@typeclass trait AdditiveMonoidal[F[_]] extends AdditiveSemigroupal[F] { - def nothing: F[Nothing] -} - -@typeclass trait MultiplicativeMonoidal[F[_]] extends MultiplicativeSemigroupal[F] { - def unit: F[Unit] -} -``` - -So now we have these fancy type classes, but how are they actually useful? -Well, I'm going to make the claim that these type classes already exist in cats today, just under different names. - -Let's first look at the `AdditiveMonoidal`. -It is defined by two methods, `nothing` which returns an `F[Nothing]` and `sum` which takes an `F[A]` and an `F[B]` to create an `F[Either[A, B]]`. - -What type class in Cats could be similar? -First, we'll look at the `sum` function and try to find a counterpart for `AdditiveSemigroupal`. -Since we gave the lower kinded versions of these type classes symbolic operators, why don't we do the same thing for `AdditiveSemigroupal`? - -Since it is additive it should probably contain a `+` somewhere and it should also show that it's inside some context. - -Optimally it'd be something like `[+]`, but that's not a valid identifier so let's try `<+>` instead! - -```scala -def <+>[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] -``` - -Oh! The `<+>` function already exists in cats as an alias for `combineK` which can be found on `SemigroupK`, but it's sort of different, it takes two `F[A]`s and returns an `F[A]`, not quite what we have here. - -Or is it? -These two functions are actually the same, and we can define them in terms of one another as long as we have a functor: - -```scala -def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] - -def combineK[A](x: F[A], y: F[A]): F[A] = { - - val feaa: F[Either[A, A]] = sum(x, y) - feaa.map(_.merge) -} -``` - -So our `AdditiveSemigroupal` is equivalent to `SemigroupK`, so probably `AdditiveMonoidal` is equivalent to `MonoidK`, right? - -Indeed, and we can show that quite easily. - -`MonoidK` adds an `empty` function with the following definition: - -```scala -def empty[A]: F[A] -``` - -This function uses a universal quantifier for `A`, which means that it works for any `A`, which then means that it cannot actually include any particular `A` and is therefore equivalent to `F[Nothing]` which is what we have for `AdditiveMonoidal`. - -Excellent, so we found counterparts for the additive type classes, and we already now that `MultiplicativeSemigroupal` is equivalent to `cats.Semigroupal`. -So the only thing left to find out is the counterpart of `MultiplicativeMonoidal`. - -I'm going to spoil the fun and make the claim that `Applicative` is that counterpart. -`Applicative` adds `pure`, which takes an `A` and returns an `F[A]`. -`MultiplicativeMonoidal` adds `unit`, which takes no parameters and returns an `F[Unit]`. -So how can we go from one to another? -Well the answer is again using a functor: - -```scala -def unit: F[Unit] - -def pure(a: A): F[A] = unit.map(_ => a) -``` - -`Applicative` uses a covariant functor, but in general we could use invariant and contravariant structures as well. -`Applicative` also uses `<*>` as an alias for using `product` together with `map`, which seems like further evidence that our intuition that its a multiplicative type class is correct. - -So in cats right now we have `<+>` and `<*>`, is there also a type class that combines both similar to how `Semiring` combines `+` and `*`? - -There is, it is called `Alternative`, it extends `Applicative` and `MonoidK` and if we were super consistent we'd call it a `Semiringal`: - - -```scala -@typeclass -trait Semiringal[F[_]] extends MultiplicativeMonoidal[F] with AdditiveMonoidal[F] -``` - -Excellent, now we've got both `Semiring` and a higher kinded version of it. -Unfortunately the lower kinded version can't be found in Cats yet, but hopefully in a future version it'll be available as well. - -If it were available, we could derive a `Semiring` for any `Alternative` the same we can derive a `Monoid` for any `MonoidK` or `Applicative`. -We could also lift any `Semiring` back into `Alternative`, by using `Const`, just like we can lift `Monoid`s into `Applicative` using `Const`. - -To end this blog post, we'll have a very quick look on how to do that. - -```scala -import Semiring.ops._ - -case class Const[A, B](getConst: A) - -implicit def constSemiringal[A: Semiring] = new Semiringal[Const[A, ?]] { - def sum[B, C](fa: Const[A, B], fb: Const[A, C]): Const[A, Either[B, C]] = - Const(fa.getConst + fb.getConst) - - def product[B, C](fa: Const[A, B], fb: Const[A, C]): Const[A, (B, C)] = - Const(fa.getConst * fb.getConst) - - def unit: Const[A, Unit] = - Const(Semiring[A].one) - - def nothing: Const[A, Nothing] = - Const(Semiring[A].zero) -} -``` - -## Conclusion - -Rings and Semirings are very interesting algebraic structures and even if we didn't know about them we've probably been using them for quite some time. -This blog post aimed to show how `Applicative` and `MonoidK` relate to `Monoid` and how algebraic data types form a semiring and how these algebraic structures are pervasive throughout Scala and other functional programming languages. -For me personally, realizing how all of this ties together and form some really satisfying symmetry was really mind blowing and I hope this blog post can give some good insight on recognizing these interesting similarities throughout Cats and other libraries based on different mathematical abstractions. -For further material on this topic, you can check out [this talk](https://www.youtube.com/watch?v=YScIPA8RbVE). - - - -## Addendum - -This article glossed over commutativity in the type class encodings. -Commutativity is very important law for semrings and the code should show that. -However, since this post already contained a lot of different type class definitions, adding extra commutative type class definitions that do nothing but add laws felt like it would distract from what is trying to be taught. - -Moreover I focused on the cardinality of only the types we need, but for completeness' sake, we could also add instances of `Cardinality` for things like `A => B` , `Option[A]` or `Ior[A, B]`. -These are: -1. `Cardinality.of[A => B] === Cardinality.of[B].pow(Cardinality.of[A])` -2. `Cardinality.of[Option[A]] === Cardinality.of[A] + 1` -3. `Cardinality.of[Ior[A, B]] === Cardinality.of[A] + Cardinality.of[B] + Cardinality.of[A] * Cardinality.of[B]` diff --git a/src/blog/shared-state-in-fp.md b/src/blog/shared-state-in-fp.md deleted file mode 100644 index e3baafba..00000000 --- a/src/blog/shared-state-in-fp.md +++ /dev/null @@ -1,322 +0,0 @@ -{% - author: ${gvolpe} - date: "2018-06-07" - tags: [technical] -%} - -# Shared State in Functional Programming - -Newcomers to functional programming (FP) are often very confused about the proper way to share state without breaking purity and end up having a mix of pure and impure code that [defeats the purpose](https://queue.acm.org/detail.cfm?id=2611829) of having pure FP code in the first place. - -This is the reason that has motivated me to write a beginner friendly guide :) - -## Use Case - -We have a program that runs three computations at the same time and updates the internal state to keep track of the -tasks that have been completed. When all the tasks are completed we request the final state and print it out. - -You should get an output similar to the following one: - -``` -Starting process #1 -Starting process #2 -Starting process #3 - ... 3 seconds -Done #2 - ... 5 seconds -Done #1 - ... 10 seconds -Done #3 -List(#2, #1, #3) -``` - -We'll use the concurrency primitive `Ref[IO, List[String]]` to represent our internal state because it's a great fit. - -### Getting started - -So this is how we might decide to start writing our code having some knowledge about [`cats.effect.IO`](https://typelevel.org/cats-effect/datatypes/io.html): - -```scala -import cats.effect._ -import cats.effect.concurrent.Ref -import cats.instances.list._ -import cats.syntax.all._ - -import scala.concurrent.duration._ - -object sharedstate extends IOApp { - - var myState: Ref[IO, List[String]] = _ - - def putStrLn(str: String): IO[Unit] = IO(println(str)) - - val process1: IO[Unit] = { - putStrLn("Starting process #1") *> - IO.sleep(5.seconds) *> - myState.update(_ ++ List("#1")) *> - putStrLn("Done #1") - } - - val process2: IO[Unit] = { - putStrLn("Starting process #2") *> - IO.sleep(3.seconds) *> - myState.update(_ ++ List("#2")) *> - putStrLn("Done #2") - } - - val process3: IO[Unit] = { - putStrLn("Starting process #3") *> - IO.sleep(10.seconds) *> - myState.update(_ ++ List("#3")) *> - putStrLn("Done #3") - } - - def masterProcess: IO[Unit] = { - myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync() - val ioa = List(process1, process2, process3).parSequence.void - ioa *> myState.get.flatMap(rs => putStrLn(rs.toString)) - } - - override def run(args: List[String]): IO[ExitCode] = - masterProcess.as(ExitCode.Success) - -} -``` - -We defined a `var myState: Ref[IO, List[String]]` initialized as `null` so we can create it on startup and all the child processes can have access to it. A so called `global state`. - -But now we try to run our application and we encounter our first ugly problem: `NullPointerException` on line 19. All the processes are defined by using `myState` which has not yet been initialized. So an easy way to fix it is to define all our processes as `lazy val`. - -```scala -lazy val process1: IO[Unit] = ??? -lazy val process2: IO[Unit] = ??? -lazy val process3: IO[Unit] = ??? -``` - -That worked, brilliant! We have an application that meets the business criteria and most importantly it works! - -### Rethinking our application - -But let's take a step back and review our code once again, there are at least two pieces of code that should have caught your attention: - -```scala -var myState: Ref[IO, List[String]] = _ -``` - -We are using `var` and initializing our state to `null`, OMG! Also the workaround of `lazy val` should get you thinking... - -And here's the second obvious one: - -```scala -myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync() -``` - -We require our `myState` to be of type `Ref[IO, List[String]` but the smart constructor gives us an `IO[Ref[IO, List[String]]]` so we are "forced" to call `unsafeRunSync()` to get our desired type. And there's a reason for that, the creation of a `Ref[F, A]` is side-effectful, therefore it needs to be wrapped in `IO` to keep the purity. - -But wait a minute... that `unsafeRunSync()` is something that you should only see at the edge of your program, most commonly in the `main` method that is invoked by the `JVM` and that is impure by nature (of type `Unit`). But because we are using `IOApp` we shouldn't be calling any operations which names are prefixed with `unsafe`. - -You say to yourself, yes, I know this is bad and ugly but I don't know a better way to share the state between different computations and this works. But we know you have heard that funcional programming is beautiful so why doing this? - -### Functional Programming - -Okay, can we do better? Of course we do and you wouldn't believe how simple it is! - -Let's get started by getting rid of that ugly `var myState` initialized to `null` and pass it as parameter to the processes that need to access it: - -```scala -import cats.effect._ -import cats.effect.concurrent.Ref -import cats.instances.list._ -import cats.syntax.all._ - -import scala.concurrent.duration._ - -object sharedstate extends IOApp { - - def putStrLn(str: String): IO[Unit] = IO(println(str)) - - def process1(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #1") *> - IO.sleep(5.seconds) *> - myState.update(_ ++ List("#1")) *> - putStrLn("Done #1") - } - - def process2(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #2") *> - IO.sleep(3.seconds) *> - myState.update(_ ++ List("#2")) *> - putStrLn("Done #2") - } - - def process3(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #3") *> - IO.sleep(10.seconds) *> - myState.update(_ ++ List("#3")) *> - putStrLn("Done #3") - } - - def masterProcess: IO[Unit] = { - val myState: Ref[IO, List[String]] = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync() - - val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void - ioa *> myState.get.flatMap(rs => putStrLn(rs.toString)) - } - - override def run(args: List[String]): IO[ExitCode] = - masterProcess.as(ExitCode.Success) - -} -``` - -Great! We got rid of that `global state` and we are now passing our `Ref`as a parameter. Remember that it is a concurrency primitive meant to be accesed and modified in concurrent scenarios, so we are safe here. - -Notice how all our processes are now defined as `def processN(myState: Ref[IO, List[String]])`. - -### A well known method: flatMap! - -Now, we still have that `unsafeRunSync()` hanging around our code, how can we get rid of it? The answer is `flatMap`!!! - -```scala -def masterProcess: IO[Unit] = - Ref.of[IO, List[String]](List.empty[String]).flatMap { myState => - val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void - ioa *> myState.get.flatMap(rs => putStrLn(rs.toString)) - } -``` - -You only need to call `flatMap` once up in the call chain where you call the processes to make sure they all share the same state. If you don't do this, a new `Ref` will be created every time you `flatMap` (remember creating a `Ref` is side effectful!) and thus your processes will not be sharing the same state changing the behavior of your program. - -We now have a purely functional code that shares state in a simple and pure fashion. Here's the entire FP program: - -```scala -import cats.effect._ -import cats.effect.concurrent.Ref -import cats.instances.list._ -import cats.syntax.all._ - -import scala.concurrent.duration._ - -object sharedstate extends IOApp { - - def putStrLn(str: String): IO[Unit] = IO(println(str)) - - def process1(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #1") *> - IO.sleep(5.seconds) *> - myState.update(_ ++ List("#1")) *> - putStrLn("Done #1") - } - - def process2(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #2") *> - IO.sleep(3.seconds) *> - myState.update(_ ++ List("#2")) *> - putStrLn("Done #2") - } - - def process3(myState: Ref[IO, List[String]]): IO[Unit] = { - putStrLn("Starting process #3") *> - IO.sleep(10.seconds) *> - myState.update(_ ++ List("#3")) *> - putStrLn("Done #3") - } - - def masterProcess: IO[Unit] = - Ref.of[IO, List[String]](List.empty[String]).flatMap { myState => - val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void - ioa *> myState.get.flatMap(rs => putStrLn(rs.toString)) - } - - override def run(args: List[String]): IO[ExitCode] = - masterProcess.as(ExitCode.Success) - -} -``` - -## Mutable reference - -As I mentioned in one of the sections above, the creation of `Ref[F, A]` is side-effectful. So what does this mean? Does it write to disk? Does it perform HTTP Requests? Not exactly. - -It all comes down to wanting to keep the property of *referential transparency* while sharing and mutating state. So let's again put up an example to follow up along with some explanation: - -```scala -var a = 0 -def set(n: Int) = a = n -def get: Int = a -``` - -Here we have imperative and impure code that mutates state. So we can try wrapping things in `IO` to keep side effects under control: - -```scala -class IORef { - var a: Int = 0 - def set(n: Int): IO[Unit] = IO(a = n) - def get: IO[Int] = IO.pure(a) -} -``` - -This is way better since now the mutation is encapsulated within `IORef` but we are now pushing some responsibility to whoever creates an `IORef`. Consider this: - -```scala -val ref = new IORef() -``` - -If we have two or more references to `ref` in our code, they will be referring to the same mutable state and we don't really want that. We can make sure this doesn't happen and a way to achieve this is to wrap the creation of `IORef` in `IO`: - -```scala -private class IORef { - var a: Int = 0 - def set(n: Int): IO[Unit] = IO(a = n) - def get: IO[Int] = IO.pure(a) -} - -object IORef { - def apply: IO[IORef] = IO(new IORef) -} -``` - -We have now regained purity. So whenever you create an `IORef` you'll get an `IO[IORef]` instead of a mutable reference to `IORef`. This means that when you invoke `flatMap` on it twice you'll get two different mutable states, and this is the power of `Referential Transparency`. It gives you way more control than having a `val ref` hanging around in your code and gives you ***local reasoning***. - -*All these examples are written in terms of `IO` for the sake of simplicity but in practice they are polymorphic on the effect type.* - -## Applying the technique in other libraries - -Although in the example above we only see how it's done with the `cats-effect` library, this principle expands to other FP libraries as well. - -For example, when writing an `http4s` application you might need to create an `HttpClient` that needs to be used by more than one of your services. So again, create it at startup and `flatMap` it once: - -```scala -object HttpServer extends StreamApp[IO] { - - override def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, ExitCode] = - for { - httpClient <- Http1Client.stream[IO]() - endpoint1 = new HttpEndpointOne[IO](httpClient) - endpoint2 = new HttpEndpointTwo[IO](httpClient) - exitCode <- BlazeBuilder[F] - .bindHttp(8080, "0.0.0.0") - .mountService(endpoint1) - .mountService(endpoint2) - .serve - } yield exitCode - -} - -class HttpEndpointOne[F[_]](client: Client[F]) { ... } -class HttpEndpointTwo[F[_]](client: Client[F]) { ... } -``` - -When writing `fs2` applications you can apply the same technique, for example between processes that share a `Queue`, `Topic`, `Signal`, `Semaphore`, etc. - -Remember that if you are forced to call `unsafeRunSync()` other than in your `main` method it might be a *code smell*. - -## Conclusion - -To conclude this post I would like to give a big shout out to [@SystemFW](https://github.com/SystemFw) who has been untiringly teaching this concept in the Gitter channels. And here's a quote from his response on [Reddit](https://www.reddit.com/r/scala/comments/8ofc8j/shared_state_in_pure_functional_programming_github/e050wy2/): - -> At the end of the day all the benefits from *referential transparency* boil down to being able to understand and build code compositionally. That is, understanding code by understanding individual parts and putting them back together, and building code by building individual parts and combining them together. This is only possible if *local reasoning* is guaranteed, because otherwise there will be weird interactions when you put things back together, and referential transparency is *defined* as something that guarantees local reasoning. - -> In the specific case of state sharing, this gives rise to a really nice property: since the only way to share is passing things as an argument, *the regions of sharing are exactly the same of your call graph*, so you transform an important aspect of the behaviour ("who shares this state?") into a straightforward syntactical property ("what methods take this argument"?). This makes shared state in pure FP a lot easier to reason about than its side-effectful counterpart imho. - -In simple terms, remind yourself about this: **"flatMap once and pass the reference as an argument!"** diff --git a/src/blog/singleton-instance-trick-unsafe.md b/src/blog/singleton-instance-trick-unsafe.md deleted file mode 100644 index a228dd91..00000000 --- a/src/blog/singleton-instance-trick-unsafe.md +++ /dev/null @@ -1,303 +0,0 @@ -{% - author: ${S11001001} - date: "2014-07-06" - tags: [technical] -%} - -# The singleton instance trick is unsafe - -*Also, the “fake covariance” trick.* - -Sometimes, Scala programmers notice a nice optimization they can use -in the case of a class that has an invariant type parameter, but in -which that type parameter -[appears in variant or phantom position in the actual data involved](liskov-lifting.md). -[`=:=`](http://www.scala-lang.org/api/2.11.1/scala/Predef$$$eq$colon$eq.html) -is an -[example of the phantom case](https://github.com/scala/scala/blob/v2.11.1/src/library/scala/Predef.scala#L398). - -```scala -sealed abstract class =:=[From, To] extends (From => To) with Serializable -``` - -[`scala.collection.immutable.Set`](http://www.scala-lang.org/api/2.11.1/scala/collection/immutable/Set.html) -is an example of the covariant case. - -Here is the optimization, which is very similar to -[the `Liskov`-lifting previously discussed](liskov-lifting.md): -a “safe” cast of the invariant type -parameter can be made, because all operations on the casted result -remain sound. -[Here it is for Set](https://github.com/scala/scala/blob/9fc098dd0dcf1825ec55501716b4f2a0a6d197ae/src/library/scala/collection/immutable/HashSet.scala#L170), -an example of the “fake covariance” trick: - -```scala -override def toSet[B >: A]: Set[B] = this.asInstanceOf[Set[B]] -``` - -And -[here it is for `=:=`](https://github.com/scala/scala/blob/v2.11.1/src/library/scala/Predef.scala#L399-L402), -an example of the “singleton instance” trick. - -```scala -private[this] final val singleton_=:= = new =:=[Any,Any] { - def apply(x: Any): Any = x -} -object =:= { - implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A] -} -``` - -Unless you are using -[the Scalazzi safe Scala subset](https://dl.dropboxusercontent.com/u/7810909/talks/parametricity/4985cb8e6d8d9a24e32d98204526c8e3b9319e33/parametricity.pdf), -which forbids referentially nontransparent and nonparametric -operations, *these tricks are unsafe*. - -Types are erased ----------------- - -Many people are confused that they cannot write functions like this: - -```scala -def addone[A](x: A): A = x match { - case s: String => s + "one" - case i: Int => i + 1 -} -``` - -Being given an error as follows. - -```scala -:8: error: type mismatch; - found : String - required: A - case s: String => s + "one" - ^ -:9: error: type mismatch; - found : Int - required: A - case i: Int => i + 1 - ^ -``` - -Let’s consider only one case, the first. In the right-hand side (RHS) -of this case, you have not proved that `A` is `String` at all! You -have only proved that, in addition to definitely having type `A`, `x` -also definitely has type `String`. In type relationship language, - -```scala -x.type <: A -x.type <: String -``` - -All elephants are grey and are also animals, but it does not follow -that all grey things are animals or vice versa. If you use a cast to -“fix” this, you have produced type-incorrect code, period. - -Type recovery -------------- - -Under special circumstances, however, information about a type -parameter can be recovered, safe and sound. Take this: - -```scala -abstract class Box[A] -case class SBox(x: String) extends Box[String] -case class IBox(x: Int) extends Box[Int] - -def addone2[A](b: Box[A]): A = b match { - case SBox(s) => s + "one" - case IBox(x) => x + 1 -} -``` - -This compiles, and I don’t even have to have data in the box to get at -the type information that `A ~ String` or `A ~ Int`. Consider the -first case. On the RHS, I have - -```scala -b.type <: SBox <: Box[String] -b.type <: Box[A] -``` - -In addition, **`A` is invariant**, so after going up to -`Box[String]`, `b` couldn’t have widened that type parameter, or -changed it in any way, without an unsafe cast. Additionally, our -supertype tree cannot contain `Box` twice with different parameters. -So we have proved that `A` is `String`, because we proved that -`Box[A]` is `Box[String]`. - -This is very useful when defining -[GADTs](http://www.haskell.org/haskellwiki/GADTs_for_dummies). - -Partial type recovery ---------------------- - -Let’s consider a similar ADT with the type parameter marked variant. - -```scala -abstract class CovBox[+A] -case class CovSBox(x: String) extends CovBox[String] - -def addone3[A](b: CovBox[A]): A = b match { - case CovSBox(s) => s + "one" -} -``` - -This works too, because in the RHS of the case, we proved that: - -```scala -b.type <: CovSBox <: CovBox[String] <: CovBox[A] -String <: A -``` - -The only transform in type `A` could have possibly undergone is a -widening, which *must* have begun at `String`. A similar example can -be derived for contravariance. - -Singleton surety ----------------- - -In our first example, there is one type that we know must be a subtype -of `A`, no matter what! - -```scala -def addone[A <: AnyRef](x: A): A = x: x.type -``` - -(Scala doesn’t like it when we talk about singleton types without an -`AnyRef` upper bound at least. But the underlying principle holds for -all value types.) - -Where `x` is an `A` of stable type, `x.type <: A` for all possible `A` -types. You might say, “that’s uninteresting; obviously `x` is an `A` -in this code.” But that isn’t what we’re talking about; our premise -is that **any** value of type `x.type` is also an `A`! - -So if we could prove that something else had the singleton type -`x.type`, we would also prove that it shared all of `x`’s types! We -can do that with a singleton type pattern, which is implemented -(soundly in 2.11) with a reference comparison. Scala lets us use -*some* of the resulting implications. - -```scala -final case class InvBox[A](b: A) -def maybeeq[A, B](x: InvBox[A], y: InvBox[B]): A = y match { - case _: x.type => y.b -} -``` - -Unsafety --------- - -To which you might protest, “there’s only one value of any singleton -type!” Well, yes. And here’s where our seemingly innocent -optimization turns nasty. If you'll recall, it depends upon treating -a value with multiple types via an unsafe cast. - -```scala -def unsafeCoerce[A, B]: A => B = { - val a = implicitly[A =:= A] - implicitly[B =:= B] match { - case _: a.type => implicitly[A =:= B] - } -} - -def unsafeCoerce2[A, B]: A => B = { - val n = Set[Nothing]() - val b = n.toSet[B] - n.toSet[A] match { - case _: b.type => implicitly[A =:= B] - } -} -``` - -Both of these compile to what is in essence an identity function. - -```scala -scala> Some(unsafeCoerce[String, Int]("hi")) -res0: Some[Int] = Some(hi) - -scala> Some(unsafeCoerce2[String, Int]("hi")) -res1: Some[Int] = Some(hi) -``` - -In our invariant `Box` example we decided that, as it was impossible -to change the type parameter without an unsafe cast, we could use that -knowledge in the consequent types. In `unsafeCoerce`, where `?` -represents the value before the match keyword: - -```scala -?.type <: a.type <: (A =:= A) -?.type <: (B =:= B) -A ~ B -``` - -In `unsafeCoerce2`, - -```scala -?.type <: b.type <: Set[B] -?.type <: Set[A] -A ~ B -``` - -**There is nothing wrong with Scala making this logical inference. The -“optimization” of that cast is not safe.** - -Let me reiterate: **Scala’s type inference surrounding pattern -matching should not be “fixed” to make unsafe casts “safer” and steal -our GADTs. Unsafe code is unsafe.** - -Scalazzi safe Scala subset saves us ------------------------------------ - -For types like these, it is not possible to exploit this unsafety -without a reference check, which is what a singleton type pattern -compiles to. As the Scalazzi safe subset forbids referentially -nontransparent operations, if you follow its rules, these -optimizations become safe again. - -This is just yet another of countless ways in which following the -Scalazzi rules makes your code safer and easier to reason about. - -That isn’t to say it’s impossible to derive a situation where the -optimization exposes an `unsafeCoerce` in Scalazzi code. However, you -must specially craft a type in order to do so. - -```scala -abstract class Oops[A] { - def widen[B>:A]: Oops[B] = this.asInstanceOf[Oops[B]] -} -case class Bot() extends Oops[Nothing] - -def unsafeCoerce3[A, B]: A => B = { - val x = Bot() - x.widen[A] match { - case Bot() => implicitly[A <:< B] - } -} -``` - -The implication being - -```scala -?.type <: Bot <: Oops[Nothing] -?.type <: Oops[A] -Nothing ~ A -``` - -Scalaz -[uses the optimization under consideration in `scalaz.IList`](https://github.com/scalaz/scalaz/blob/v7.0.6/core/src/main/scala/scalaz/IList.scala#L436-L437). -So would generalized `Functor`-based `Liskov`-lifting, as discussed at -the end of [“When can Liskov be lifted?”](liskov-lifting.md), -were it to be implemented. However, these cases do not fit the bill -for exploitation from Scalazzi-safe code. - -On the other hand, the singleton type pattern approach may be used in -*all* cases where the optimization may be invoked by a caller, -including standard library code where some well-meaning contributor -might add such a harmless-seeming avoidance of memory allocation -without your knowledge. Purity pays, and often in very nonobvious -ways. - -*This article was tested with Scala 2.11.1.* diff --git a/src/blog/spires-ops-macros.md b/src/blog/spires-ops-macros.md deleted file mode 100644 index 9c063ab9..00000000 --- a/src/blog/spires-ops-macros.md +++ /dev/null @@ -1,312 +0,0 @@ -{% - author: ${non} - date: "2013-10-13" - tags: [technical] -%} - -# How to use Spire's Ops macros in your own project - -## What are Spire's Ops macros? - -Spire's type classes abstract over very basic operators like `+` and -`*`. These operations are normally very fast. This means that any -extra work that happens on a per-operation basis (like boxing or -object allocation) will cause generic code to be slower than its -direct equivalent. - -Efficient, generic numeric programming is Spire's raison d'être. We -have developed a set of Ops macros to avoid unnecessary object -instantiations at compile-time. This post explains how, and -illustrates how you can use these macros in your code! - -## How implicit operators on type classes usually work - -When using type classes in Scala, we rely on implicit conversions to -"add" operators to an otherwise generic type. - -In this example, `A` is the generic type, `Ordering` is the type -class, and `>` is the implicit operator. `foo1` is the code that the -programmer writes, and `foo4` is a translation of that code after -implicits are resolved, and syntactic sugar is expanded. - -```scala -import scala.math.Ordering -import Ordering.Implicits._ - -def foo1[A: Ordering](x: A, y: A): A = - x > y - -def foo2[A](x: A, y: A)(implicit ev: Ordering[A]): A = - x > y - -def foo3[A](x: A, y: A)(implicit ev: Ordering[A]): A = - infixOrderingOps[A](x)(ev) > y - -def foo4[A](x: A, y: A)(implicit ev: Ordering[A]): A = - new ev.Ops(x) > y -``` - -(This is actually slightly wrong. The expansion to `foo4` won't happen -until runtime, when `infixOrderingOps` is called. But it helps -illustrate the point.) - -Notice that we instantiate an `ev.Ops` instance for every call to -`>`. This is not a big deal in many cases, but for a call that is -normally quite fast it will add up when done many (e.g. millions) of -times. - -It is possible to work around this: - -```scala -def bar[A](x: A, y: A)(implicit ev: Ordering[A]): A = - ev.gt(x, y) -``` - -The `ev` parameter contains the method we actually want (`gt`), so -instead of instantiating `ev.Ops` this code calls `ev.gt` directly. -But this approach is ugly. Compare these two methods: - -```scala -def qux1[A: Field](x: A, y: A): A = - ((x pow 2) + (y pow 2)).sqrt - -def qux2[A](x: A, y: A)(implicit ev: Field[A]): A = - ev.sqrt(ev.plus(ev.pow(x, 2), ev.pow(y, 2))) -``` - -If you have trouble reading `qux2`, you are not alone. - -At this point, it looks like we can either write clean, readable code -(`qux1`), or code defensively to avoid object allocations (`qux2`). -Most programmers will just choose one or the other (probably the -former) and go on with their lives. - -However, since this issue affects Spire deeply, we spent a bit more -time looking at this problem to see what could be done. - -## Having our cake and eating it too - -Let's look at another example, to compare how the "nice" and "fast" -code snippets look after implicits are resolved: - -```scala -def niceBefore[A: Ring](x: A, y: A): A = - (x + y) * z - -def niceAfter[A](x: A, y: A)(implicit ev: Ring[A]): A = - new RingOps(new RingOps(x)(ev).+(y))(ev).*(z) - -def fast[A](x: A, y: A)(implicit ev: Ring[A]): A = - ev.times(ev.plus(x, y), z) -``` - -As we can see, `niceAfter` and `fast` are actually quite similar. If -we wanted to transform `niceAfter` into `fast`, we'd just have to: - -1. Figure out the appropriate name for symbolic operators. In this - example, `+` becomes `plus` and `*` becomes `times`. - -2. Rewrite the object instantiation and method call, calling the - method on `ev` instead and passing `x` and `y` as arguments. In - this example, `new Ops(x)(ev).foo(y)` becomes `ev.foo(x, y)`. - -In a nutshell, this transformation is what Spire's Ops macros do. - -Using the Ops macros --------------------- - -Your project must use Scala 2.10+ to be able to use macros. - -To use Spire's Ops macros, you'll need to depend on the `spire-macros` -package. If you use SBT, you can do this by adding the following line -to `build.sbt`: - -```scala -libraryDependencies += "org.spire-math" %% "spire-macros" % "0.6.1" -``` - -You will also need to enable macros at the declaration site of your -ops classes: - -```scala -import scala.language.experimental.macros -``` - -## Let's see an example - -Consider `Sized`, a type class that abstracts over the notion of -having a size. Type class instances for `Char`, `Map`, and `List` are -provided in the companion object. Of course, users can also provide -their own instances. - -Here's the code: - -```scala -trait Sized[A] { - def size(a: A): Int - def isEmpty(a: A): Boolean = size(a) == 0 - def nonEmpty(a: A): Boolean = !isEmpty(a) - def sizeCompare(x: A, y: A): Int = size(x) compare size(y) -} - -object Sized { - implicit val charSized = new Sized[Char] { - def size(a: Char): Int = a.toInt - } - - implicit def mapSized[K, V] = new Sized[Map[K, V]] { - def size(a: Map[K, V]): Int = a.size - } - - implicit def listSized[A] = new Sized[List[A]] { - def size(a: List[A]): Int = a.length - override def isEmpty(a: List[A]): Boolean = a.isEmpty - override def sizeCompare(x: List[A], y: List[A]): Int = (x, y) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => 1 - case (_ :: xt, _ :: yt) => sizeCompare(xt, yt) - } - } -} -``` - -(Notice that `Sized[List[A]]` overrides some of the "default" -implementations to be more efficient, since taking the full length of -a list is an O(n) operation.) - -We'd like to be able to call these methods directly on a generic type -`A` when we have an implicit instance of `Sized[A]` available. So -let's define a `SizedOps` class, using Spire's Ops macros: - -```scala -import spire.macrosk.Ops -import scala.language.experimental.macros - -object Implicits { - implicit class SizedOps[A: Sized](lhs: A) { - def size(): Int = macro Ops.unop[Int] - def isEmpty(): Boolean = macro Ops.unop[Boolean] - def nonEmpty(): Boolean = macro Ops.unop[Boolean] - def sizeCompare(rhs: A): Int = macro Ops.binop[A, Int] - } -} -``` - -That's it! - -Here's what it would look like to use this type class: - -```scala -import Implicits._ - -def findSmallest[A: Sized](as: Iterable[A]): A = - as.reduceLeft { (x, y) => - if ((x sizeCompare y) < 0) x else y - } - -def compact[A: Sized](as: Vector[A]): Vector[A] = - as.filter(_.nonEmpty) - -def totalSize[A: Sized](as: Seq[A]): Int = - as.foldLeft(0)(_ + _.size) -``` - -Not bad, eh? - -## The fine print - -Of course, there's always some fine-print. - -In this case, the implicit class **must** use the same parameter names -as above. The constructor parameter to `SizedOps` **must** be called -`lhs` and the method parameter (if any) **must** be called -`rhs`. Also, unary operators (methods that take no parameters, like -`size`) **must** have parenthesis. - -How the macros handle classes with multiple constructor parameters, or -multiple method parameters? They don't. We haven't needed to support -these kinds of exotic classes, but it would probably be easy to extend -Spire's Ops macros to support other shapes as well. - -If you fail to follow these rules, or if your class has the wrong -shape, your code will fail to compile. So don't worry. If your code -compiles, it means you got it right! - -## Symbolic names - -The previous example illustrates rewriting method calls to avoid -allocations, but what about mapping symbolic operators to method -names? - -Here's an example showing the mapping from `*` to `times`: - -```scala -trait CanMultiply[A] { - def times(x: A, y: A): A -} - -object Implicits { - implicit class MultiplyOps[A: CanMultiply](lhs: A) { - def *(rhs: A): A = macro Ops.binop[A, A] - } -} - -object Example { - import Implicits._ - - def gak[A: CanMultiply](a: A, as: List[A]): A = - as.foldLeft(a)(_ * _) - } -} -``` - -Currently, the Ops macros have a large (but Spire-specific) -[mapping](https://github.com/non/spire/blob/9eaa5c34549b7fe85c223f207f0790873075c048/macros/src/main/scala/spire/macros/Ops.scala#L143) -from symbols to names. However, your project may want to use different names -(or different symbols). What then? - -For now, you are out of luck. In Spire 0.7.0, we plan to make it -possible to use your own mapping. This should make it easier for other -libraries that make heavy use of implicit symbolic operators -(e.g. Scalaz) to use these macros as well. - -## Other considerations - -You might wonder how the Ops macros interact with -specialization. Fortunately, macros are expanded before the -specialization phase. This means you don't need to worry about it! If -your type class is specialized, and you invoke the implicit from a -specialized (or non-generic) context, the result will be a specialized -call. - -(Of course, using Scala's specialization is tricky, and deserves its -own blog post. The good news is that type classes are some of the -easiest structures to specialize correctly in Scala.) - -Evaluating the macros at compile-time also means that if there are -problems with the macro, you'll find out about those at compile-time -as well. While we expect that many projects will benefit from the Ops -macros, they were designed specifically for Spire so it's possible -that your project will discover problems, or need new features. - -If you do end up using these macros, -[let us know how](https://groups.google.com/forum/#!forum/spire-math) -they work for you. If you have problems, please open an -[issue](https://github.com/non/spire/issues), and if you have bug -fixes (or new features) feel free to open a -[pull request](https://github.com/non/spire/pulls)! - -## Conclusion - -We are used to thinking about abstractions having a cost. So we often -end up doing mental accounting: "Is it worth making this generic? Can -I afford this syntactic sugar? What will the runtime impact of this -code be?" These condition us to expect that code can either be -beautiful or fast, but not both. - -By removing the cost of implicit object instantiation, Spire's Ops -macros raise the abstraction ceiling. They allow us to make free use -of type classes without compromising performance. Our goal is to close -the gap between direct and generic performance, and to encourage the -widest possible use of generic types and type classes in Scala. diff --git a/src/blog/spotify-foss-fund.md b/src/blog/spotify-foss-fund.md deleted file mode 100644 index 1792e0dd..00000000 --- a/src/blog/spotify-foss-fund.md +++ /dev/null @@ -1,19 +0,0 @@ -{% - author: ${valencik} - date: "2025-02-21" - tags: [governance] -%} - -# Spotify FOSS Fund 2024 - -We're excited to announce that Typelevel has been chosen as a recipient of the 2024 Spotify FOSS Fund! -As a result, Spotify has donated €20,000 to [Typelevel's OpenCollective.][opencollective] - -Our current funding goes towards recurring infrastructure expenses, and sending members of our code of conduct committee for training to better support our community. -With these extra funds we’d like to expand our initiatives to encourage and support new contributors and users! -We’ve specifically discussed funding student or first time contributors through an initiative like Outreachy and engaging a technical writer to help improve documentation. - -You can read more about the 2024 Spotify FOSS Fund and the other recipients over at [Spotify's engineering blog.][spotify-blog] - -[opencollective]: https://opencollective.com/typelevel -[spotify-blog]: https://engineering.atspotify.com/2024/11/congratulations-to-the-recipients-of-the-2024-spotify-foss-fund/ diff --git a/src/blog/subtype-typeclasses.md b/src/blog/subtype-typeclasses.md deleted file mode 100644 index 683dd22b..00000000 --- a/src/blog/subtype-typeclasses.md +++ /dev/null @@ -1,410 +0,0 @@ -{% - author: ${adelbertc} - date: "2016-09-30" - tags: [technical] -%} - -# Subtype type classes don't work - -_Update: A comprehensive version of this blog post was published at the -[2017 Scala Symposium][scalaSite] and is available [for free][scalaProc] -through the ACM OpenTOC service. The corresponding talk can be found -[here][scalaTalk]._ - -The common encoding of type classes in Scala relies on subtyping. This singular -fact gives us a certain cleanliness in the code, but at what cost? - -## Problem - -Consider the following hierarchy of type classes. A similar hierarchy can be -found in both [Cats][cats] and [Scalaz 7][scalaz7]. - -```scala -trait Functor[F[_]] - -trait Applicative[F[_]] extends Functor[F] - -trait Monad[F[_]] extends Applicative[F] - -trait Traverse[F[_]] extends Functor[F] -``` - -For purposes of demonstration I will be using Cats for the rest of this post, -but the same arguments apply to Scalaz 7. - -We will also assume that there is syntax accompanying this hierarchy, allowing -us to call methods like `map`, `flatMap`, and `traverse` directly on some -`F[A]`, provided `F` has the appropriate type class instances (`Functor`, -`Monad`, and `Traverse`, respectively). - -```scala -import cats._ -import cats.implicits._ -``` - -One important consequence is we can use for comprehensions in methods -parameterized over some `Monad`. - -```scala -def foo[F[_]: Monad]: F[Int] = for { - a <- Monad[F].pure(10) - b <- Monad[F].pure(20) -} yield a + b -``` - -Notice that due to how for comprehensions [desugar][forcomp], there is also -a call to `map` in there. Since our type class hierarchy is encoded via -subtyping Scala knows a `Monad[F]` implies a `Functor[F]`, so all is well. -Or is it? - -Consider a case where we want to abstract over a data type that has -both `Monad` and `Traverse`. - -```scala -// Ignore the fact we're not even using `Traverse` - we can't even call `map`! -def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// :19: error: value map is not a member of type parameter F[Int] -// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// ^ -// :19: error: missing argument list for method identity in object Predef -// Unapplied methods are only converted to functions when a function type is expected. -// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`. -// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// ^ -``` - -We're already in trouble. In order to call `map` we need `F` to have a -`Functor` instance, which it does via `Monad` as before.. but now also via -`Traverse`. It is for precisely this reason that this does not work. Because -our encoding of type classes uses subtyping, a `Monad[F]` **is a** `Functor[F]`. -Similarly, a `Traverse[F]` **is a** `Functor[F]`. When implicit resolution -attempts to find a `Functor[F]`, it can't decide between `Monad[F]`'s or -`Traverse[F]`'s and bails out. Even though the instances may be, -[and arguably should be][tcVsWorld], the same, the compiler has no way of -knowing that. - -This problem generalizes to anytime the compiler decides an implicit is ambiguous, -such as method calls. - -```scala -// The fact we don't actually use `Functor` here is irrelevant. -def bar[F[_]: Applicative: Functor]: F[Int] = Applicative[F].pure(10) -``` - -```scala -def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F] -// :19: error: ambiguous implicit values: -// both value evidence$2 of type cats.Traverse[F] -// and value evidence$1 of type cats.Monad[F] -// match expected type cats.Functor[F] -// def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F] -// ^ -``` - -What do we do? For `map` it is easy enough to arbitrarily pick one -of the instances and call `map` on that. For function calls you -can thread the implicit through explicitly. - -```scala -def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].map(Monad[F].pure(10))(identity) - -def callBar[F[_]: Monad: Traverse]: F[Int] = bar(Monad[F], Monad[F]) - // or bar(Monad[F], Traverse[F]) -``` - -For `foo` it's not *too* terrible. For `bar` though we are -already starting to see it get unwieldy. While we could have passed in -`Monad[F]` or `Traverse[F]` for the second parameter which corresponds -to `bar`'s `Functor[F]` constraint, we can only pass in `Monad[F]` for -the first parameter to satisfy `Applicative[F]`. Because implicit resolution -can't disambiguate the `Functor[F]` by itself we've had to pass it in -explicitly, but by doing so we also have to pass in everything else explicitly! -We become the implicit resolver. And this is with just two constraints, what -if we had three, four, five? - -And the trouble doesn't end there. We asked for a `Monad` so let's try using -a for comprehension. - -```scala -def foo[F[_]: Monad: Traverse]: F[Int] = for { - a <- Monad[F].pure(10) - b <- Monad[F].pure(20) -} yield a + b -// :21: error: value map is not a member of type parameter F[Int] -// b <- Monad[F].pure(20) -// ^ -``` - -This is also broken! Because of how [for comprehensions][forcomp] desugar, a -`map` call is inevitable which leads to the for comprehension breaking down. -This drastically reduces the ergonomics of doing anything monadic. - -As with `map` we could call `flatMap` on `Monad` directly, but this quickly -becomes cumbersome. - -```scala -def foo[F[_]: Monad: Traverse]: F[Int] = { - val M = Monad[F] - M.flatMap(M.pure(10)) { a => - M.map(M.pure(20)) { b => - a + b - } - } -} -``` - -These same problems arise if you ask for two or more type classes that share a -common superclass. Some examples of this: - -* Two or more of Monad{Error, Plus, Reader, State, Writer} (ambiguous Monad) - * This prevents ergonomic use of ["MTL-style"][mtl] -* MonadPlus + Monad (ambiguous Monad) -* Alternative + Traverse (ambiguous Functor) -* MonadRec + MonadPlus (ambiguous Monad) - -This suggests every type class have only **one** subclass. -That is quite limiting as is readily demonstrated by the extremely useful -`Applicative` and `Traverse` type classes. What do we do? - -## Solution (?) - -This more or less remains an open problem in Scala. There has been -an interesting alternative prototyped in [scato][scato], now making its way to -[Scalaz 8][scalaz8], that has received some positive feedback. The gist of the -encoding completely throws out the notion of subtyping, encoding the hierarchy -via members instead. - -```scala -trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] -} - -trait Applicative[F[_]] { - def functor: Functor[F] - - def pure[A](a: A): F[A] - def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] -} - -// Definitions elided for space -trait Monad[F[_]] { def applicative: Applicative[F] } - -trait Traverse[F[_]] { def functor: Functor[F] } -``` - -Because there is no relation between the type classes, there is no -danger of implicit ambiguity. However, for that very reason, having a -`Monad[F]` no longer implies having a `Functor[F]`. Not currently -anyway. What we can do is use implicit conversions to re-encode the -hierarchy. - -```scala -implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] = - implicitly[Applicative[F]].functor - -implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] = - implicitly[Traverse[F]].functor -``` - -But now we're back to square one. - -```scala -// Syntax for Functor -implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { - def map[B](f: A => B): F[B] = F.map(fa)(f) -} -``` - -```scala -def foo[F[_]: Applicative: Traverse]: F[Int] = - implicitly[Applicative[F]].pure(10).map(identity) -// :18: error: value map is not a member of type parameter F[Int] -// implicitly[Applicative[F]].pure(10).map(identity) -// ^ -// :18: error: missing argument list for method identity in object Predef -// Unapplied methods are only converted to functions when a function type is expected. -// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`. -// implicitly[Applicative[F]].pure(10).map(identity) -// ^ -``` - -Since both implicits have equal priority, the compiler -doesn't know which one to pick. **However**, Scala has mechanisms for -[prioritizing implicits][implicits] which solves the problem. - -```scala -object Prioritized { // needed for tut, irrelevant to demonstration - trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] - } - - // Prioritize implicit conversions - Functor only for brevity - trait FunctorConversions1 { - implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] = - implicitly[Applicative[F]].functor - } - - trait FunctorConversions0 extends FunctorConversions1 { - implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] = - implicitly[Traverse[F]].functor - } - - object Functor extends FunctorConversions0 - - trait Applicative[F[_]] { - def functor: Functor[F] - - def pure[A](a: A): F[A] - def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] - } - - // Definition elided for space - trait Traverse[F[_]] { def functor: Functor[F] } - - // Syntax for Functor - implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { - def map[B](f: A => B): F[B] = F.map(fa)(f) - } - - def foo[F[_]: Applicative: Traverse]: F[Int] = { - implicitly[Applicative[F]] // we have Applicative - implicitly[Traverse[F]] // we have Traverse - implicitly[Functor[F]] // we also have Functor! - - // and we have syntax! - implicitly[Applicative[F]].pure(10).map(identity) - } -} -``` - -Because implicit resolution treats implicits in subtypes with higher priority, -we can organize conversions appropriately to prevent ambiguity. Here this means -that `applicativeIsFunctor` has lower priority than `traverseIsFunctor`, so -when both `Applicative` and `Traverse` instances are in scope and the compiler -is looking for a `Functor`, `traverseIsFunctor` wins. - -One thing to note is that we've baked the implicit hierarchy into `Functor` -itself - in general this means all superclasses are aware of their subclasses. -This is convenient from a usability perspective as companion objects are -considered during implicit resolution, but from a modularity perspective is -strange and in this case would prevent extensions to the hierarchy from external -sources. This can be solved by removing the hierarchy from the superclasses -(removing `Functor`'s `extends FunctorConversions0`), but comes at -the cost of needing an import at use sites to bring the implicits into scope. - -```scala -trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] -} - -trait Applicative[F[_]] { - def functor: Functor[F] - - def pure[A](a: A): F[A] - def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] -} - -trait Traverse[F[_]] { def functor: Functor[F] } - -implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { - def map[B](f: A => B): F[B] = F.map(fa)(f) -} - -// Separate from type class definitions - -trait FunctorConversions1 { - implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] = - implicitly[Applicative[F]].functor -} - -trait FunctorConversions0 extends FunctorConversions1 { - implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] = - implicitly[Traverse[F]].functor -} - -object Prelude extends FunctorConversions0 -``` - -```scala -// Need this import to get implicit conversions in scope -import Prelude._ - -def foo[F[_]: Applicative: Traverse]: F[Int] = { - implicitly[Applicative[F]] - implicitly[Traverse[F]] - implicitly[Functor[F]] - - implicitly[Applicative[F]].pure(10).map(identity) -} -``` - -`Prelude` could be provided by the base library, but external libraries -with their own type classes can still extend the hierarchy and provide -their own `Prelude`. - -A more developed form can be seen in the [BaseHierarchy][hierarchy] of Scalaz 8. - -Do we win? I'm not sure. This encoding is certainly more cumbersome than what -we started with, but solves the problems we ran into. - -## Compromise? - -Another thing we can try is to make some compromise of the two. We can -continue to use subtyping for a blessed subset of the hierarchy, and use -members for any branching type class. - -```scala -trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] -} - -implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { - def map[B](f: A => B): F[B] = F.map(fa)(f) -} - -trait Applicative[F[_]] extends Functor[F] { - def pure[A](a: A): F[A] - def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] -} - -trait Monad[F[_]] extends Applicative[F] - -trait Traverse[F[_]] { def functor: Functor[F] } - -def foo[F[_]: Applicative: Traverse]: F[Int] = - implicitly[Applicative[F]].pure(10).map(identity) -``` - -This works, but is even messier than the alternatives. We have to -decide which type classes get to live in the subtype hierarchy and which are -doomed (blessed?) to express the relationship with members. But maybe the -pros outweigh the cons. Pull requests with this change have been filed for -[Cats][fixCats] and [Scalaz 7.3][fixScalaz]. - -I'm not convinced that the story is over though. Maybe there's another solution -yet to be discovered. - -For further reading, there are open tickets for both [Cats][issueCats] and -[Scalaz 7][issueScalaz] documenting the subtyping problem. A discussion around -the Scato encoding for Scalaz 8 can be found [here][scatoScalaz]. - -*This article was tested with Scala 2.11.8 and Cats 0.7.2 using [tut][tut].* - -[cats]: https://github.com/typelevel/cats "Typelevel Cats" -[fixCats]: https://github.com/typelevel/cats/pull/1379 "MTL fix for Cats" -[fixScalaz]: https://github.com/scalaz/scalaz/pull/1262 "MTL fix for Scalaz" -[forcomp]: http://docs.scala-lang.org/tutorials/FAQ/yield.html "How does yield work?" -[hierarchy]: https://github.com/scalaz/scalaz/blob/611d69f6b2bff3500181d0338dec9d6143d386ad/base/src/main/scala/BaseHierarchy.scala "Scalaz 8 base hierarchy" -[implicits]: http://eed3si9n.com/revisiting-implicits-without-import-tax "revisiting implicits without import tax" -[issueCats]: https://github.com/typelevel/cats/issues/1210 "Better accommodate MTL style" -[issueScalaz]: https://github.com/scalaz/scalaz/issues/1110 "MTL-style doesn't seem to work in Scala" -[mtl]: https://hackage.haskell.org/package/mtl "mtl: Monad classes, using functional dependencies" -[scalaz7]: https://github.com/scalaz/scalaz/tree/series/7.3.x "Scalaz 7" -[scalaz8]: https://github.com/scalaz/scalaz/tree/series/8.0.x "Scalaz 8" -[scato]: https://github.com/aloiscochard/scato "Scato" -[scatoScalaz]: https://github.com/scalaz/scalaz/issues/1084 "[scalaz8] Subtyping-free encoding for typeclasses" -[scalaProc]: http://www.sigplan.org/OpenTOC/scala17.html -[scalaSite]: https://conf.researchr.org/track/scala-2017/scala-2017-papers -[scalaTalk]: https://www.youtube.com/watch?v=BGoTXO1V0HM&list=PL6KWJEIH5ulcNeQ92iKFN2k-UIV9QHExH&index=3 -[tut]: https://github.com/tpolecat/tut "tut: doc/tutorial generator for scala" -[tcVsWorld]: https://www.youtube.com/watch?v=hIZxTQP1ifo "Edward Kmett - Type Classes vs. the World" diff --git a/src/blog/summit-assistance.md b/src/blog/summit-assistance.md deleted file mode 100644 index c41432ae..00000000 --- a/src/blog/summit-assistance.md +++ /dev/null @@ -1,46 +0,0 @@ -{% - author: ${davegurnell} - date: "2016-01-14" - tags: [summits] -%} - -# Assistance and Bursaries for the Typelevel Summits - -_Update: The summits are over, which means applying for assistance is not possible any longer._ - -As it says in our [code of conduct][code-of-conduct], we are dedicated to creating a harrassment-free, inclusive community of developers. We want to extend this philosophy to the Typelevel Summits in [Philadelphia][philadelphia] and [Oslo][oslo] by providing assistance and bursaries to help speakers and attendees who would otherwise not be able to join us. - -### Bursaries for Speakers and Attendees - -We are approaching sponsors and commercial partners to arrange bursaries for conference registration, travel and accommodation. Everyone is welcome to apply. However, note that we may not be able to provide funding in all cases. - -### Speaker Assistance: Help with Talk Proposals - -Many people don’t submit talk proposals to conferences because they can’t think of something to say or don’t think their ideas are interesting. - -We are offering feedback and suggestions on talk proposals to anyone who requests it. Hopefully we can help you turn a rough idea into a compelling abstract to submit alongside abstracts from other potential speakers. - -Asking us for help won’t guarantee you a slot in the programme, but it will give you an idea for a talk that would compete against the other submissions. - -If you have a good talk idea but for some reason it doesn’t make it into the conference, we will help you find another conference or meetup group for your first public speaking experience. - -### Speaker Assistance: Speaker Training - -Many people are concerned or nervous about public speaking. We are offering free workshops to provide newer speakers with advice for tech speaking, whether your abstract is accepted or not. - -Given the international nature of the Summits, we probably won't be able to organise face-to-face workshops. We will deliver the workshops over Google Hangouts to reach as many attendees as possible. - -### Speaker Assistance: Help with Talk Writing - -If your abstract is accepted to the conference, we are offering free mentoring services to provide advice and feedback as you turn your abstract into a talk. - -We will assign a mentor to each participating speaker. Your mentor will be available to act as a sounding board as your talk develops from an outline to a fully formed presentation. - -### Sound Interesting? - -If you are planning on attending or speaking in Philadelphia or Oslo, and you would like to apply for any of the services above, please please fill out the application form linked at the top of the page. - -[code-of-conduct]: /code-of-conduct/README.md -[summits]: announcement_summit.md -[philadelphia]: summit-philadelphia-2016-03-02.md -[oslo]: summit-oslo-2016-05-04.md diff --git a/src/blog/summit-berlin-2018-05-18.md b/src/blog/summit-berlin-2018-05-18.md deleted file mode 100644 index f29cb7e8..00000000 --- a/src/blog/summit-berlin-2018-05-18.md +++ /dev/null @@ -1,84 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2018-05-18" - event-date: "May 18, 2018" - event-location: "Zalando, Zeughofstraße 1, Berlin" - tags: [summits, events] -%} - -# Typelevel Summit Berlin - -@:include(/img/places/berlin.md) - -## About the Summit - -The sixth Typelevel Summit will once again happen after **the** Scala conference: [Scala Days](https://eu.scaladays.org/), in the same city! - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the [Typelevel Code of Conduct](/code-of-conduct/README.md). - -Special thanks go to [Zalando](https://jobs.zalando.com/tech/?utm_source=typelevel&utm_medium=event-page-organic-b&utm_campaign=2018-css&utm_content=01-typelevel-summit) who kindly provide the venue. - - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:15 | Registration | - 9:00 | Opening Remarks | - 9:05 | @:style(schedule-title)Keynote: Just the right kind of Consistency!@:@ @:style(schedule-byline)Annette Bieniusa@:@ You need a data store that allows for high throughput and availability, while supporting consistency patterns referential integrity, numerical invariants, or atomic updates? Current designs for data storage forces application developers to decide early in the design cycle, and once and for all, what type of consistency the database should provide. At one extreme, strong consistency requires frequent global coordination; restricting concurrency in this way greatly simplifies application development, but it reduces availability and increases latency. At the opposite extreme, there are systems that provide eventual consistency only: they never sacrifice availability, but application developers must write code to deal with all sorts of concurrency anomalies in order to prevent violation of application invariants. But your system just needs to be consistent enough for the application to be correct! In the talk, I will discuss insights and techniques for analysing the consistency requirements of an application, and show techniques how you can establish them in your system. | - 10:00 | Break | - 10:20 | @:style(schedule-title)A Fistful of Functors@:@ @:style(schedule-byline)Itamar Ravid@:@ Functors show up everywhere in our day-to-day programming. They're so common, we take them for granted - especially in typed functional programming. Beside being common, they're incredibly useful for code reuse. However, functors have several relatively unknown variants: profunctors, bifunctors, contravariant functors, and so on. And guess what - they're amazingly useful, especially combined with other abstractions in the functional programming toolkit! In this talk, we'll cover the many species of functors and see how they can help us with tasks such as serialization, stream processing, and more. | - 10:55 | @:style(schedule-title)Cancelable IO@:@ @:style(schedule-byline)Alexandru Nedelcu@:@ Task / IO data types have been ported in Scala, inspired by Haskell's monadic IO and are surging in popularity due to the need in functional programming for referential transparency, but also because controlling side effects by means of lawful, FP abstractions makes reasoning about asynchrony and concurrency so much easier. But concurrency brings with it race conditions, i.e. the ability to execute multiple tasks at the same time, possibly interrupting the losers and cleaning up resources afterwards and thus we end up reasoning about preemption. This talk describes the design of Monix's Task for cancelability and preemption, a design that has slowly transpired in cats-effect, first bringing serious performance benefits and now a sane design for cancelation. Topics include how cancelable tasks can be described, along with examples of race conditions that people can relate to, highlighting the challenges faced when people use an IO/Task data type that cannot be interrupted. | - 11:30 | Break | - 11:50 | @:style(schedule-title)Legacy Engineering: Making Criteo Functional@:@ @:style(schedule-byline)Guillaume Bort@:@ Criteo uses a lot of Scala in its code-base. Historically for big data stuff using the usual suspects Spark & Scalding, but more and more for application development. A few Typelevel projects started to appear in our code base as developers started to embrase more sophisticated FP practices in their Scala code. Today most of our Scala projects are built around cats, fs2, doobie, algebra, shapeless, etc. In this presentation we will discuss the challenges of introducing more functional code in a large software company as Criteo and how typelevel projects have helped. We'll talk about what's worked well as well as where the dragons lie. | - 12:10 | @:style(schedule-title)Introducing namespaces into SQL result sets using nested structural types@:@ @:style(schedule-byline)Keiko Nakata@:@ Many modern programming languages support decent namespaces. Namespaces are commonly structured hierarchies. We bring this power to a database query language, using nested structural types. For this purpose, we hijack table aliases: given a table T containing two columns C of type String and D of type Int, a table `T as S` is a new table containing two columns S.C of type String and S.D of type Int. In Scala, this is neatly expressed as `T : AnyRef { def C : String, def D: Int }` `T as S : AnyRef { def S: { def C: String, def D: Int } }`. We implement the above as operation using the whitebox macro. We rely on Scala's type system's ability to compute Greatest Lower Bounds (GLBs) and Least Upper Bounds (LUBs) of structural types, to enable polymorphic and compositional query creation. To enable GLB and LUB computation for nested structured types, we have patched the Scala compiler. - 12:45 | Lunch Break | - 14:15 | @:style(schedule-title)Healthy Minds in a Healthy Community@:@ @:style(schedule-byline)Sasha Romijn@:@ Open source communities attract and boast passionate, idealistic people, and many of us invest copious amounts of time and effort to contribute to our projects and support our communities. This underlying emotional attachment can make us more vulnerable to elevated stress, burnout and conflicts. And then there are those of us who also manage mental illness. More often than not, we suffer these struggles in silence, feeling (and fearing) that we're alone in our trouble. Here, our communities can make a huge difference, by building a positive and safe environment where we can blossom and support ourselves and our peers, and feel included. This talk will take a look at open-source communities through the eyes of various mental well-being issues and struggles, and show various things that some communities already do. With this, we hope to support and inspire more communities to help foster healthy minds in a healthy environment. | - 14:50 | @:style(schedule-title)Typedapi: Define your API on the type level@:@ @:style(schedule-byline)Paul Heymann@:@ Have you ever thought “I really like Haskell’s Servant. Why don’t we have something like that in Scala?” or “Why can't I just define my APIs as types and Scala does the heavy lifting?”? If so, this talk is made for you. I will tell you a short story about excitement, pain and hate peaking in a climax of type-driven enlightenment. I will tell you my journey of developing Typedapi, a library for building typesafe APIs which moves as many computations to the type level as possible. We will fight many a beast on our way from Scala’s desugaring to folds working just on types. But eventually, we will arrive at our destination, exhausted, with scars but also able to make our code a bit safer again. - 15:10 | Break | - 15:30 | @:style(schedule-title)An Intuitive Guide to Combining Free Monad and Free Applicative@:@ @:style(schedule-byline)Cameron Joannidis@:@ The usage of Free Monads is becoming more well understood, however the lesser known Free Applicative is still somewhat of a mystery to the average Scala developer. In this talk I will explain how you can combine the power of both these constructs in an intuitive and visual manner. You will learn the motivations for using Free Structures in the first place, how we can build up a complex domain, how we can introduce parallelism into our domain and a bunch of other practical tips for designing programs with these structures. This will also give you a deeper understanding of what libraries like Freestyle are doing under the hood and why it is so powerful. | - 16:05 | @:style(schedule-title)Laws for Free@:@ @:style(schedule-byline)Alistair Johnson@:@ Everyone that uses a functional programming library like cats is aware of the methods that each type class adds and also the properties that the methods need to abide by. But in practice, the properties are not always proved, rather testing that the methods behave as expected. This is a problem waiting to happen, as the algebraic properties are not “optional extras” – if your semigroup's combine is not associative ... then it ain't a semigroup, sorry! So in this talk we will quickly review what we mean by a property and a law and show how to use the cats laws that are available. We'll see that they are simple to use and add literally hundreds of scalacheck tests for free. And impress your boss as well, the tests can be “seen” on the screen! | - 16:25 | Break | - 16:45 | @:style(schedule-title)Lifting Data Structures to the Type-level@:@ @:style(schedule-byline)Jon Pretty@:@ In this talk, I will give a fast-paced tour of how various features of the Scala type system, many of them under-explored, can be harnessed to construct type-level representations of a number of different datatypes in Scala. The type system offers a limited number of “tools”, such as subtyping, least-upper-bound inference, type unification, singleton types and dependent types and (of course) implicit search, which we can compose in interesting ways to implement type-level operations on these type-level data structures. Value-level operations follow naturally from the types, but this is much less interesting. | - 17:20 | @:style(schedule-title)Non-academic functional Workflows@:@ @:style(schedule-byline)Stefan Schneider@:@ In this talk I want to report about how we used cats to build a domain specific language that enables us to compile workflows into later executable programs. We started with the idea of having a possibility to combine the multiple unconnected tools that are typically used to analyze an image acquired by our microscopes. The Free Monad in cats looked to us as the perfect fit to write a domain specific language that provides a lot of the advantages of an a modern functional compiler plus enforcing stack safety of the program, which would ultimately provided by third party users. We started developing with a team that had only very little experience in Scala and none with cats. Thanks to the good documentation, Scala Exercises and the straightforward mapping to functional principles, known to us from the university, we were able to get a prototype running for a trade show in 6 weeks. | - 17:40 | Closing Remarks | - - -## Venue - -This event will take place at Zalando. - - - - -## Co-located Event - -The [Scala Center](https://scala.epfl.ch/) will organize a co-located event with roundtables of project maintainers. -Note that because the space is limited, tickets are not on sale for this event. -To register interest, please get in touch [via email](mailto:info@typelevel.org). - - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/zalando.png) { alt: Zalando, title: Zalando, style: legacy-event-sponsor }](https://jobs.zalando.com/tech/?utm_source=typelevel&utm_medium=event-page-organic-b&utm_campaign=2018-css&utm_content=01-typelevel-summit)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/crite_o_labs.png) { alt: Criteo, title: Criteo, style: legacy-event-sponsor }](https://www.criteo.com/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/commercetools.png) { alt: Commercetools, title: Commercetools, style: legacy-event-sponsor }](https://www.commercetools.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/lightbend.png) { alt: Lightbend, title: Lightbend, style: legacy-event-sponsor }](https://www.lightbend.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/signify.png) { alt: Signify, title: Signify, style: legacy-event-sponsor }](https://www.signifytechnology.com/)@:@ -@:@ diff --git a/src/blog/summit-boston-2018-03-20.md b/src/blog/summit-boston-2018-03-20.md deleted file mode 100644 index ec7805d9..00000000 --- a/src/blog/summit-boston-2018-03-20.md +++ /dev/null @@ -1,55 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2018-03-20" - event-date: "March 20, 2018" - event-location: "Broad Institute, Cambridge, Massachusetts" - tags: [summits, events] -%} - -# Typelevel Summit Boston - -@:include(/img/places/cambridge.md) - -## About the Summit - -The fifth Typelevel Summit will once again be co-located with the [Northeast Scala Symposium](http://www.nescala.org/) in Cambridge, Massachusetts, with one day of recorded talks and one day of (shared) unconference. -The unconference will happen on March 18, NE Scala on March 19, and finally, the Summit on March 20. -For tickets and other information about attending, please visit the website of the [Northeast Scala Symposium](http://www.nescala.org/). - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the [Typelevel Code of Conduct](/code-of-conduct/README.md). - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:15 | Registration & Breakfast by Clover Food Labs | - 9:00 | Opening Remarks | - 9:05 | @:style(schedule-title)http4s: pure, typeful, functional HTTP in Scala@:@ @:style(schedule-byline)Ross Baker@:@ http4s embraces cats.data.Kleisli for service definitions and fs2.Stream for payload processing. With its foundation on these simple types, we’ll take a whirlwind tour of how http4s can: plug into your functional business logic; snap together with other functional libraries; relate servers to clients; test with a simple function call; run on multiple backends; and support multiple higher level DSLs. This talk will not make you an expert in http4s, but show that it’s a relatively short path to become one. - 9:40 | Break | - 9:55 | @:style(schedule-title)Opaque types: understanding SIP-35@:@ @:style(schedule-byline)Erik Osheim@:@ Proposed in SIP-35, opaque types introduce a way to define types which only exist at compile-time. Despite some superficial similarities to value classes, opaque types are significantly more flexible and introduce a number of exciting new possibilities in the Scala design space. Opaque types are motivated by a number of different concerns: desire for a non-class type that exists only at compile-time; efficiency concerns with value classes; limitations of existing type aliases; and need to better support phantom types, type tags, etc. This talk will introduce opaque types, compare them to type aliases and value classes (their two nearest cousins) and then walk through some examples of using opaque types. The focus will be on advantages of using opaque types versus other encodings, including looking at how various types are represented by the JVM at runtime. The talk does not assume in-depth knowledge of the Scala compiler and will motivate the code using plausible real world examples. Attendees will come away from this talk with a better understanding of what SIP-35 means, why it was proposed, and how it could change how we write Scala code for the better. - 10:30 | @:style(schedule-title)Big Data at the Intersection of Typed FP and Category Theory@:@ @:style(schedule-byline)Long Cao@:@ Big data, functional programming, and category theory aren’t just three trendy topics smashed into a talk title as bait! Foundational ideas from typed functional programming and category theory have real and practical applications for working with big data and can also be utilized to write more principled pipelines at scale. Whether it’s aggregating with monoids or writing more typesafe Spark jobs, we’ll try and bridge these topics together in a way that can be immediately useful. Some knowledge of Scala and a big data framework like Apache Hadoop, Spark, or Beam is suggested but not necessary. | - 11:05 | Break | - 11:20 | @:style(schedule-title)Tracking with Writer Monad@:@ @:style(schedule-byline)Eugene Platonov@:@ This talk will tell the story of one team at eBay which used to do data tracking in a healthy side-effecting manner. Until the team realized that it’s not that healthy. The solution was found in a Writer Monad (residing in the cats library) as well as in the fact that the writer monad can stay in shades. Some people, especially when they are new to typed FP, don’t like/feel comfortable to see words like Semigroup, Traversable, Writer and such in their domain code. The talk will show how those “scary” parts can be “hidden” by domain specific extension methods. | - 11:40 | @:style(schedule-title)Duality and How to Delete Half (minus ε) of Your Code@:@ @:style(schedule-byline)Greg Pfeil@:@ In functional programming, we often refer to category theory to explain various concepts. We’ll go over where these concepts do and don’t map well to Scala, as well as what duality is, how we can take advantage of it in Scala, and how to distinguish other concepts that are often confused with it. | - 12:15 | Lunch on your own out in Kendall Square | - 14:00 | @:style(schedule-title)Keynote: Planning for Rainfall@:@ @:style(schedule-byline)Kathi Fisler@:@ Soloway's Rainfall problem, a classic benchmark in computing education research, has proven difficult for many CS1 students. Rainfall tests students' abilities at plan composition, the task of integrating code fragments that implement subparts of a problem into a single program. Nearly all early studies of Rainfall involved students who were learning imperative programming with arrays. Over the last few years, we've conducted studies with students who were learning functional programming instead. These students have produced atypical profiles of compositions and errors on Rainfall (and similar problems). What do these results suggest about the role of programming languages in novice programming education? This talk raises various questions about the relationships between programming languages, program design, curricula, and how students perceive code structure. The talk assumes no experience with having been rained upon. | - 15:05 | Break | - 15:20 | @:style(schedule-title)Why Monads?@:@ @:style(schedule-byline)Luca Belli@:@ Monads remain a somewhat mysterious concept in Functional Programming, even though the number of tutorials and blog posts trying to “monadsplain” is at an all-time high. Rather than answering the classical question “What is a Monad?”, we are going to dig more into “Why Monads?”. Building intuition on why monads are useful will help better understand what they are as well. We’ll start with a simple function in a monadless world and we’ll see how annoying it would be to use it in different contexts (List, Maybe, Either). As soon as we are sufficiently frustrated we’ll invoke our friendly Monad and see how much easier our life becomes. | - 15:55 | @:style(schedule-title)Pants and Monorepos@:@ @:style(schedule-byline)Dorothy Ordogh@:@ Large or quickly growing projects that consist of many interdependent sub-projects with complex dependencies on third-party libraries can be difficult to handle with standard language build tools. Add on to that code generators and the use of multiple languages and suddenly a lot of your coding life is spent figuring out the right commands to run for the right language, and waiting for all of your code to build. This is where Pants can help! Pants is an open source build tool developed and used by Twitter, Square, Foursquare, Medium, and others. This talk will begin with a brief overview of what Pants is and how it can help, and then discuss new features we have been adding to make the tool faster. In particular, I will discuss the work we have done to restrict what is going on the JVM compile classpaths to make building Scala and Java projects faster, and the work we are doing to implement a remotely executing build system. | - 16:15 | Break | - 16:30 | @:style(schedule-title)Declarative Control Flow with fs2 Stream@:@ @:style(schedule-byline)Fabio Labella@:@ fs2 is a purely functional streaming library, with support for concurrent and nondeterministic merging of arbitrary streams. Concurrency support means that we can use Stream not only to process data in constant memory, but also as a very general abstraction for control flow: whilst IO gives us an excellent model for a single effectful action, assembling behaviour with it often has a very imperative flavour (pure, but still imperative). This talk will introduce fs2 combinators by example, and will hopefully show how we can model control flow in a declarative, high level, composable fashion. In particular, we will focus on concurrent combinators. | - 17:05 | @:style(schedule-title)Scalafix @ Twitter scale@:@ @:style(schedule-byline)Uma Srinivasan@:@ Scalafix is a fairly popular OSS tool that is useful for performing syntactic and semantic rewrites of Scala code. At Twitter we use it for migration to new library interfaces and maintenance of code health by removal of deprecated code. In this talk we walk through examples of simple and complex Scalafix custom rule specifications for rewrites. We describe the core infrastructure we have set up to support rewrites across our entire monorepo, several orders of magnitude faster than if we were to apply them manually. A simple demo will be included to provide a glimpse of our developer workflow and the user experience with our code base. We envision leveraging this tool for more purposes such as improving performance, upgrading compiler revisions, and assisting developers to automatically recognize and prevent commits of disallowed code patterns. | - 17:25 | Closing Remarks | - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/comcast.png) { alt: Comcast, title: Comcast, style: legacy-event-sponsor }](https://www.comcast.com/)@:@ -@:@ diff --git a/src/blog/summit-copenhagen-2017-06-03.md b/src/blog/summit-copenhagen-2017-06-03.md deleted file mode 100644 index 4892b57f..00000000 --- a/src/blog/summit-copenhagen-2017-06-03.md +++ /dev/null @@ -1,62 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2017-06-03" - event-date: "June 3, 2017" - event-location: "Comwell Conference Center Copenhagen, Denmark" - tags: [summits, events] -%} - -# Typelevel Summit Copenhagen - -@:include(/img/places/copenhagen.md) - -## About the Summit - -The fourth Typelevel Summit will be co-located with **the** Scala conference: [Scala Days](http://event.scaladays.org/scaladays-cph-2017)! - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the [Typelevel Code of Conduct](/code-of-conduct/README.md). - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:30 | Registration | - 9:00 | Opening Remarks | - 9:05 | @:style(schedule-title)Keynote: Inviting everyone to the party@:@ @:style(schedule-byline)Andrea Magnorsky@:@ Most of today's popular general-purpose programming languages incorporate various aspects of the imperative, object and functional programming paradigms. In some cases, these languages provide clear guidelines as to what style is preferred, and why. As programmers, we have a choice to make about which paradigm(s) to use and to what extent, even if the language provides clear guidelines. How should we think about those choices? Where are the sweet spots to make trade-offs, and what do they depend on? Let's wear the hats of history and science, thinking about the past and looking to the future, examining these apparent conflicts. Paradigm change is not a new thing - perhaps we can learn something from the history books? Wear Some(hat) and party like it's a hat party. With hats. | - 10:05 | Break | - 10:30 | @:style(schedule-title)Refined types for validated configurations@:@ @:style(schedule-byline)Viktor Lövgren@:@ Are you tired of writing boilerplate code to load configurations? Have you ever had errors because of bad configuration values? Then this talk is for you! In a live-coding session we’ll see how to encode validation rules on the type-level and load validated settings without any boilerplate code.

In the first part of this talk we’ll look at the challenges associated with loading configurations. We’ll see how typesafe config is typically used, and see how we can eliminate most boilerplate code with Typelevel incubator project PureConfig. We’ll however see that it’s still very much possible to load invalid settings.

In the second part we’ll continue by exploring options to encode type invariants, for enforcing validation, looking at how we can get PureConfig to only load validated settings. We’ll ultimately end up with type-level predicates using Typelevel project refined, and see how we can get PureConfig and refined to work together seamlessly.

The end result is more precise types, with static validation guarantees, and a way of loading validated configurations without boilerplate – finally you can stop worrying about your configurations! | - 11:10 | @:style(schedule-title)Herding types with Scala macros@:@ @:style(schedule-byline)Marina Sigaeva@:@ In Scala we use the term “type safety”, but what it really means? In short, most applications model data types in a form suitable for storage, change, transmission, and use. During the life cycle of the data, we expect to always use the declared type. But reality is a bit more complicated. One of the main practical problems with the use of types occurs when our application interacts with outside world – in requests to external services, different databases or simply with getting data from file. In most cases, an attempt to support type safety leads to writing a lot of code that we always try to avoid. Fortunately we have macros to do all routine job for us! In this talk we will discuss how to use compile-time reflection in library for schemaless key-value database and the benefits of use of macros in production systems. | - 11:25 | Break | - 11:45 | @:style(schedule-title)Monad Stacks or: How I Learned to Stop Worrying and Love the Free Monad@:@ @:style(schedule-byline)Harry Laoulakos@:@ In this talk, I will demonstrate various techniques, such as: Monad Transformers, Effects libraries, and Free monads. These techniques can be used to transform scala “spaghetti” code (that is embedded maps, flatmaps and pattern matching) to cleaner code that almost looks like imperative code. | - 12:25 | @:style(schedule-title)Freestyle: A framework for purely functional FP Apps & Libs@:@ @:style(schedule-byline)Raúl Raja Martínez@:@ Freestyle is a newcomer friendly library encouraging pure FP apps & libs in Scala on top of free monads. In this talk we will discuss design choices and main features including modules, algebras, interpreter composition and what is being planned for future releases. | - 12:45 | Lunch Break | - 14:00 | @:style(schedule-title)Lenses for the masses – introducing Goggles@:@ @:style(schedule-byline)Ken Scambler@:@ Lenses, or more generally optics, are a technique that is indispensable to modern functional programming. However, implementations have veered between two extremes: incredible abstractive power with a steep learning curve; and limited domain-specific uses that can be picked up in minutes. Why can't we have our cake and eat it too? Goggles is a new Scala macro built over the powerful & popular Monocle optics library. It uses Scala's macros and scandalously flexible syntax to create a compiler-checked mini-language to concisely construct, compose and apply optics, with a gentle, familiar interface, and extravagantly informative compiler errors. In this talk I'll introduce the motivation for lenses and why usability is a problem that so badly needs solving, and how the Goggles library, with Monocle, helps address this in an important way. There'll be some juicy discussion of Scala macro sorcery too! | - 14:40 | @:style(schedule-title)The power of type classes in big data ETL: a real world use case of combining Spark and Shapeless@:@ @:style(schedule-byline)Zhenhao Li@:@ In this talk, we will explore a type driven approach of big data ETL in Spark. Through code snippets, we will see how to express data processing logic with type classes and singleton types using Shapeless, and how to build a higher level DSL over Spark to make the logic easy to read from the code. | - 14:55 | Break | - 15:15 | @:style(schedule-title)Mastering Typeclass Induction@:@ @:style(schedule-byline)Aaron Levin@:@ Typeclasses are a powerful feature of the Scala. Using typeclasses to perform type-level induction is a mysterious, yet surprisingly simple, technique used in shapeless, cats, and circe to do generic programming. We will use basic data types to walk you through how this is done and why it’s useful. | - 15:55 | @:style(schedule-title)Do it with (free?) arrows!@:@ @:style(schedule-byline)Julien Richard-Foy@:@ DSLs with a monad-based algebra (such as free monads) are becoming popular. Recently, DSLs with an applicative-based algebra (e.g. free applicatives) also aroused interest. It is not new that there exists another notion of computation that sits in between applicative functors and monads: arrows. The goal of this talk is to revisit the relationship between these notions of computation in the context of DSL algebras. Through examples of DSLs based on real world use cases, I will highlight the differences in expressive power between these three notions of computation (and some of their friends) and present the consequences for both interpreters and DSL users. At the end of the talk, you will have a better intuition of what it means that “arrows are more powerful than applicative functors but yet support more interpreters than monads”. You will get a precise understanding of “how much” expressive power you give to your users according to your DSL algebra, and, conversely, “how much” you reduce at the same time the space of the possible DSL interpreters. Finally, you will note that arrows provide an interesting trade off. Notably, they support sequencing, they can be invertible, and their computation graph can be statically analyzed. | - 16:25 | Break | - 16:45 | @:style(schedule-title)Libra: Reaching for the stars with dependent types@:@ @:style(schedule-byline)Zainab Ali@:@ When we code, we code in numerics - doubles, floats and ints. Those numerics always represent real world quantities. Each problem domain has it’s own kinds of quantities, with its own dimensions. Adding quantities of different dimensions is nonsensical, and can have disastrous consequences. In this talk, we’ll tackle the field of dimensional analysis. We’ll explore dependent types, singleton types, and dive into generic programming along the way. We’ll find that dimensional analysis can be brought much closer to home - in the compilation stage itself! And finally, we’ll end up deriving Libra - a library which brings dimensional analysis to the compile stage for any problem domain. | - 17:30 | Reception hosted by 47 Degrees | - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/underscore.png) { alt: Underscore, title: Underscore, style: legacy-event-sponsor }](http://underscore.io/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/lightbend.png) { alt: Lightbend, title: Lightbend, style: legacy-event-sponsor }](https://www.lightbend.com/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png) { alt: 47 Degrees, title: 47 Degrees, style: legacy-event-sponsor }](http://www.47deg.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/soundcloud.png) { alt: Soundcloud, title: Soundcloud, style: legacy-event-sponsor }](http://www.soundcloud.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/signify.png) { alt: Signify, title: Signify, style: legacy-event-sponsor }](https://www.signifytechnology.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/scalac.png) { alt: scalac, title: scalac, style: legacy-event-sponsor }](https://scalac.io/)@:@ -@:@ diff --git a/src/blog/summit-keynote.md b/src/blog/summit-keynote.md deleted file mode 100644 index d7800125..00000000 --- a/src/blog/summit-keynote.md +++ /dev/null @@ -1,20 +0,0 @@ -{% - author: ${larsrh} - date: "2016-01-20" - tags: [summits] -%} - -# Keynote at the Philadelphia Summit - -While the CfP for the [Philadelphia Summit][philadelphia] is still open ([have you submitted a proposal yet?][cfp]), we can already announce our keynote speaker: - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/sweirich.jpg){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) Stephanie Weirich is a Professor at the University of Pennsylvania. Her research centers on programming languages, type theory and machine-assisted reasoning. In particular, she studies generic programming, metaprogramming, dependent type systems, and type inference in the context of functional programming languages. She is currently an Editor of the Journal of Functional Programming and served as the program chair for ICFP in 2010 and the Haskell Symposium in 2009.@:@ -@:@ - -Stephanie will join the Summit on March 2nd to talk about _Dependently-Typed Haskell_. -We hope this will give us an exciting opportunity to exchange knowledge between the Haskell and Scala communities. - -[philadelphia]: summit-philadelphia-2016-03-02.md -[cfp]: http://goo.gl/forms/SX3plxsOKb diff --git a/src/blog/summit-lausanne-2019-06-14.md b/src/blog/summit-lausanne-2019-06-14.md deleted file mode 100644 index c65976ae..00000000 --- a/src/blog/summit-lausanne-2019-06-14.md +++ /dev/null @@ -1,59 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2019-06-14" - event-date: "June 14, 2019" - event-location: "École Polytechnique Fédérale de Lausanne" - tags: [summits, events] -%} - -# Typelevel Summit Lausanne - -@:include(/img/places/lausanne.md) - -## About the Summit - -The eight Typelevel Summit will once again be co-located with Scala Days. -Read more about all events in the [blog post](https://www.scala-lang.org/blog/2019/01/17/scala-days-2019-celebrating-collaborative-success.html) from the Scala Center. - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:15 | Registration | - 9:00 | Opening Remarks | - 9:05 | @:style(schedule-title)Keynote: Some Mistakes We Made When Designing Implicits (And Some Things We Got Right)@:@ @:style(schedule-byline)Martin Odersky@:@ We will talk about the history how Scala's implicits evolved, and about some of the mistakes we could have avoided in hindsight, but also about things that I believe we got right. I'll conclude with a presentation of revised implicits in Scala 3 which fixes most of the current problems and hopefully does not create too many new ones. | - 10:00 | Break | - 10:20 | @:style(schedule-title)Brave New World - tales of PureScript and Haskell in production@:@ @:style(schedule-byline)Felix Mulder@:@ The rumours are true. Writing code in purely functional languages tends to produce code that is much easier to read, modify and reason about. This talk examines how an experienced Scala team transitioned into writing production code using PureScript in AWS lambda, and services using Haskell. | - 10:55 | @:style(schedule-title)Actors Design Patterns and Arrowised FRP@:@ @:style(schedule-byline)Diego E. Alonso@:@ Object-oriented design patterns combine basic language features to solve coding problems in an extensible way. In functional Scala, we solve those coding problems with functions, combinators, and type-classes, so design patterns are less relevant. Actor design patterns combine basic features of the actors to solve coding problems in an extensible way. Arrowised functional reactive programming (based on languages like Scala and Haskell also offers a way to solve those coding problem using functions, combinators, and type-classes. This talk describes a prototype implementation of AFRP and its primitive types and functions, discusses its similarities to actors, and then describes how some actor design patterns in the existing literature corresponds to constructions of AFRP. | - 11:30 | Break | - 11:45 | @:style(schedule-title)Taking Resources to the Type Level@:@ @:style(schedule-byline)Vilem-Benjamin Liepelt@:@ With the Granule project, we are working towards making statically typed functional languages more resource-aware, hence providing a way to enforce stateful protocols regarding memory, file handles, network interaction, etc. Static enforcement of security policies and first-class support for multi-stage programming are further examples of what is possible in a type system based on Linear Logic and Graded Modalities. We present Granule, a functional programming language which combines parametric polymorphism and indexed types with such a type system. Granule programs will probably look very familiar to you, especially if you know some Haskell/ML, but in Granule’s type system we can reason about much more. Hillel Wayne’s Great Theorem Prover Showdown has made a point of the fact that there are many things we can’t easily reason about with functional (programming | proving)—up until now! We will implement leftPad in Granule and prove it correct with little more effort that writing the type signatures. We will then breeze through how Granule’s type system very naturally supports session-typed channels and safe mutable arrays. | - 12:20 | Lunch Break at Le Parmentier | - 13:45 | @:style(schedule-title)Lord of the rings: the Spire numerical towers@:@ @:style(schedule-byline)Denis Rosset@:@ Spire defines around 80 typeclasses, including 30 coming from algebra and cats-kernel. We’ll see how much of that structure is dictated by mathematical laws, and which parts are the result of design decisions that balance different tradeoffs. In particular, we’ll discuss the different roles played by typeclasses in the Scala ecosystem: as encoding operations obeying well-defined laws; as enabling the use of a particular syntax for those operations, if possible close to the mathematical notation of a domain (and subfields often disagree on the notation!); defining a context in which a combination of typeclasses implicitly imposes additional laws (for example, the ordering of numbers and addition); enabling the user to change the variant of a relation being used (Order); singling out one variant of a structure as canonical (cats: the additive Group for integers); as selecting a particular algorithm for an operation (integer factorization: deterministic or Monte-Carlo). It quickly becomes apparent that these roles conflict. With this in mind, we’ll have a look at some design choices made in Spire. We’ll discuss success stories, such as the clarification of the laws of the % operator, the commutative ring tower that formalizes integer factorization and Euclidean division. We’ll also discuss parts where trade offs have been made, such as the triplication of group structures (Group, AdditiveGroup, MultiplicativeGroup), the problem of coherent instances, especially when various typeclasses are combined. Time permitting, we’ll also discuss issues with law-based property checks (precision, range, time and memory complexity). - - 14:20 | @:style(schedule-title)Formal verification of Scala programs with Stainless@:@ @:style(schedule-byline)Romain Ruetschi@:@ Everyone knows that writing bug-free code is fundamentally difficult, and that bugs will sometimes sneak in even in the presence of unit- or property-based tests. One solution to this problem is formal software verification. Formal verification allows users to statically verify that software systems will never crash nor diverge, and will in addition satisfy given functional correctness properties. In this talk, I will present Stainless, a verification system for an expressive subset of Scala. I will start by explaining what formal verification is, what are some of the challenges people encounter when putting it into practice, and how it can be made more practical. Then I will give a high-level overview of Stainless, and finally present a few verified programs, such as a small actor system, a parallel map-reduce implementation, as well as a little surprise! I’ll also demonstrate the tooling we have developed around Stainless which lets users easily integrate Stainless in their SBT-based Scala projects. | - 14:55 | Break | - 15:10 | @:style(schedule-title)Exploring Scala Tooling ecosystem@:@ @:style(schedule-byline)Jeferson David Ossa@:@ We are going to explore and compare some build tools with special focus on LSP/BSP implementations, IDEs and text editor support. To help the audience’s judgement about the tools that are suitable for their particular needs this talk aims to get attendees familiar with terms like SemanticDB, Metals, Bloop, SBT, Pants, Bazel, Ensime, IntelliJ IDE, Scala IDE, Dotty IDE and other honorific mentions. | - 15:45 | @:style(schedule-title)TwoFace values: a bridge between terms and types@:@ @:style(schedule-byline)Oron Port@:@ Scala 2.13 introduces literal types, and with great types comes great thirst for power to control them. In this talk we get acquainted with the singleton-ops library, a typelevel programming library that enables constraining and performing operations on literal types. We learn about the library’s TwoFace value feature, and how it can be used to bridge the gap between types and terms by converting a type expression to term expression and vice-versa. | - 16:20 | Break | - 16:40 | @:style(schedule-title)GADTs in Dotty@:@ @:style(schedule-byline)Aleksander Boruch-Gruszecki@:@ GADTs (Generalized Algebraic Data Types) are a special case of ADTs (or Dotty enums) that, when we match on them, let us know more about type parameters to enclosing functions. In practice, they are mostly used to associate types with data constructors (case classes and objects in Scala’s case), and to ensure that incorrectly assembling data structures will not typecheck. Two good examples are a database query type that cannot be malformed (no integers as if conditions!) or a red-black tree data type that will only compile if it is balanced. So far Scala’s support for GADTs has been lacking and rife with runtime type errors compared to Haskell. Fortunately, I’ve been working on making it far better in Dotty! During the talk first we’ll walk through examples of GADTs, see what makes them useful and how they can be applied to solve real problems. Next, I’ll explain how GADTs in Scala naturally follow from subtyping and inheritance, completely unlike Haskell or any other language with GADTs. Finally, I’ll talk about how the support for GADTs in Dotty is tightly related to other features such as match types and (the possible) nullable types. | - 17:15 | @:style(schedule-title)Want to Diversify the Scala Community? Here is How You Can Help!@:@ @:style(schedule-byline)Yifan Xing@:@ The Scala community has grown significantly over the past 15 years. As a community, we wrote millions of lines of code and developed hundreds of projects. While the language is thriving, there is still room to contribute to the community. Different from other tech talks, this talk focuses on contributing to the diversity aspect of the community. It explains the significance and benefits of diversity, and it proposes solutions to diversify and improve the community. One of the best ways to grow the community and to bring diversity into the community is to organize ScalaBridge workshops, which are intended to provide resources for people from underrepresented populations to learn Scala. (Diversity comes in many forms: race, gender, age, religion, culture, sexual orientation, socioeconomic background, etc.) While the workshops have positive and lasting impacts, it cannot be done by one individual or by a single organization. In order for the Scala community to become more diverse, we need your help to scale up! Attend this talk to learn about how to contribute to our community! | - 17:50 | Closing Remarks | - -## Venue - -The Summit will take place at EPFL in Ecublens, building CO, lecture hall CO2. -You can find a detailed plan at [plan.epfl.ch](https://plan.epfl.ch/). -_Please note that this is a different venue than Scala Days!_ - - - -## Sponsors - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/triplequote.png) { alt: Triplequote, title: Triplequote, style: legacy-event-sponsor }](https://www.triplequote.com/)@:@ -@:@ diff --git a/src/blog/summit-nescala-2023-10-26.md b/src/blog/summit-nescala-2023-10-26.md deleted file mode 100644 index 6955039c..00000000 --- a/src/blog/summit-nescala-2023-10-26.md +++ /dev/null @@ -1,33 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2023-10-02" - event-date: "October 26, 2023" - event-location: "Online" - tags: [summits, events] -%} - -# Typelevel Summit NEScala - -![Typelevel Summit NEScala](/img/media/nescala-hero.jpg) - -## About the Summit - -The tenth Typelevel Summit will once again be co-hosted with the [Northeast Scala Symposium](https://nescalas.github.io/) virtually, with one of day of talks and one day of unconference, accompanied by discussion and socializing online throughout. - -The schedule for this year is as follows: - -* October 26 (Thursday): Typelevel Summit -* October 27 (Friday): NE Scala 2023 -* October 28 (Saturday): Unconference - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. -All attendees, speakers, and organizers must abide by the [Typelevel Code of Conduct](/code-of-conduct/README.md). - - -## Speakers and Schedule - -For information on the programme and recordings, please visit the [NE Scala website](https://nescalas.github.io/). diff --git a/src/blog/summit-nyc-2017-03-23.md b/src/blog/summit-nyc-2017-03-23.md deleted file mode 100644 index f56455a7..00000000 --- a/src/blog/summit-nyc-2017-03-23.md +++ /dev/null @@ -1,79 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2017-03-23" - event-date: "March 23, 2017" - event-location: "26 Bridge Street, Brooklyn" - tags: [summits, events] -%} - -# Typelevel Summit NYC - -![Typelevel Summit NYC](/img/places/nyc.jpg) - -## About the Summit - -The third Typelevel Summit will once again be co-located with the [Northeast Scala Symposium](http://www.nescala.org/) in New York City, with one day of recorded talks and one day of (shared) unconference. -The Summit will happen on March 23, NE Scala on March 24, and finally, the unconference on March 25. - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -This is a community conference and we strive to make it an inclusive and fulfilling event for all participants. All attendees, speakers, and organizers must abide by the [Typelevel Code of Conduct](/code-of-conduct/README.md). - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:15 | Registration & Breakfast | - 9:00 | Opening Remarks | - 9:15 | @:style(schedule-title)Keynote: LiquidHaskell: Liquid Types for Haskell@:@ @:style(schedule-byline)Niki Vazou@:@ Code deficiencies and bugs constitute an unavoidable part of software systems. In safety-critical systems, like aircrafts or medical equipment, even a single bug can lead to catastrophic impacts such as injuries or death. Formal verification can be used to statically track code deficiencies by proving or disproving correctness properties of a system. However, at its current state formal verification is a cumbersome process that is rarely used by mainstream developers. This talk presents LiquidHaskell, a usable formal verifier for Haskell programs. LiquidHaskell naturally integrates the specification of correctness properties in the development process. Moreover, verification is automatic, requiring no explicit proofs or complicated annotations. At the same time, the specification language is expressive and modular, allowing the user to specify correctness properties ranging from totality and termination to memory safety and safe resource (e.g., file) manipulation. Finally, LiquidHaskell has been used to verify more than 10,000 lines of real-world Haskell programs. LiquidHaskell serves as a prototype verifier in a future where formal techniques will be used to facilitate, instead of hinder, software development. For instance, by automatically providing instant feedback, a verifier will allow a web security developer to immediately identify potential code vulnerabilities. | - 10:15 | Break | - 10:30 | @:style(schedule-title)Introduction to Recursion Schemes@:@ @:style(schedule-byline)Ratan Sebastian@:@ Recursion is one of the most fundamental tools in the functional programmer’s kit. As with most fundamental tools, it’s quite powerful, and likely, too powerful for most applications. Abstracting away the explicit recursion from algorithms can make them easier to reason about, understand and maintain. Separating description of the program from interpretation, is a pattern we often see in functional programming. This talk is about applying that idea to recursive algorithms. This talk will attempt to be as self-contained as possible and will hopefully make {cata|ana|para|apo}morphisms less intimidating by showing the internals of how they could be implemented with as few parts of Scala as possible. | - 11:10 | @:style(schedule-title)A Tale of Two Tails: The Second Act@:@ @:style(schedule-byline)Owein Reese@:@ TwoTails is a compiler plugin written to add support to Scala for mutual tail recursion. While Trampolines or trampolined style recursion solve the direct need, they require explicit construction by a developer and add overhead in the form of additional data structures. Unfortunately, building a “native” solution directly into Scalac without using trampolines is not a straight forward task, even with basic tail recursion. In the latest version, a second compilation scheme has been introduced solving an issue peculiar to the JVM which the first scheme was not able to properly address. I’ll discuss both the motivation behind this new scheme and the trade-offs entailed by using it, highlighting which is more appropriate given your circumstances. | - 11:30 | Break | - 11:45 | @:style(schedule-title)Scalable data pipelines with shapeless and cats@:@ @:style(schedule-byline)Marcus Henry, Jr.@:@ The data pipeline is the backbone of most modern platforms. Not only is it important to make sure your pipeline is fast and reliable but, a team also needs to be able to deploy new endpoints quickly. This talk uses inductive implicits and typeclasses to make onboarding painless. With only a limited knowledge of shapeless and cats, a developer can create scalable and maintainable data pipeline architectures that are assembled at compile time. With inductive types, pipelines can be combined to create compound pipelines simply and easily. And cats provides ready-made typeclasses which can help cut down on development time. | - 12:25 | @:style(schedule-title)Frameless: A More Well-Typed Interface for Spark@:@ @:style(schedule-byline)Long Cao@:@ With Spark 2.0, Spark users were introduced to the Dataset API, which sought to combine the static guarantees of types (much like in RDDs) with enhancements from Spark SQL’s Catalyst optimizer, which were previously only available to more a weakly typed DataFrame API. In this introductory level talk, we’ll take a brief look at some of the rough edges encountered when working with Datasets and how Frameless, a Typelevel library attempting to add a more well-typed veneer over Spark, can help. | - 12:45 | Lunch Break | - 14:00 | @:style(schedule-title)Easy and Efficient Data Validation with Cats@:@ @:style(schedule-byline)Daniela Sfregola@:@ Often when we create a client/server application, we need to validate the requests: can the user associated to the request perform this operation? Can they access or modify the data? Is the input well-formed? When the data validation component in our application is not well designed, the code can quickly become not expressive enough and probably difficult to maintain. Business rules don’t help, adding more and more requirements to add in our validation, making it more and more complex to clearly represent and maintain. At the same time when the validation fails, it should be fairly straight forward to understand why the request was rejected, so that actions can be taken accordingly. This talk introduces Cats, a Scala library based on category theory, and some of its most interesting components for data validation. In particular, we’ll discuss some options to achieve efficient and expressive data validation. We will also argue that, compared to other options in the language, Cats is particularly suited for the task thanks to its easy-to-use data types and more approachable syntax. Throughout the talk, you will see numerous examples on how data validation can be achieved in a clean and robust way, and how we can easily integrate it in our code, without any specific knowledge of category theory. | - 14:40 | @:style(schedule-title)Finding the Free Way@:@ @:style(schedule-byline)Dave Cleaver@:@ Free Monads are quickly being adopted as the best technique for developing in a pure functional style. Unfortunately, the details for how to best apply them is often left as “an exercise for the reader.” Recently my team began using Free Monads to build Web Services within the Play Framework. We wanted to use Free Monads in an easy to follow way with minimum boilerplate, while still slotting naturally into the Play Framework. In this talk I’ll outline how we took some wrong turns, hit a few potholes, but ultimately found a way to use Free that works for us. | - 15:00 | Break | - 15:15 | @:style(schedule-title)A Type Inferencer for ML in 200 Lines of Scala@:@ @:style(schedule-byline)Ionuț G. Stan@:@ Scala is both acclaimed and criticized for its type inference capabilities. But most of this criticism stems from Scala’s object-functional nature, so how does type inference look like and work in functional languages without objects, such as Standard ML or Haskell? This talk aims to show one way to achieving that. We will present Wand’s type inference algorithm, a lesser known, but easier to understand and extend alternative to the classic Damas-Hindley-Milner algorithm. We’ll use a small subset of Standard ML as a vehicle language and Scala as the implementation language. | - 15:55 | @:style(schedule-title)Extensible Effects: A Leaner Cake for Purely Functional Code@:@ @:style(schedule-byline)Edmund Noble@:@ Purely functional algorithms and data structures are one thing, but purely functional program architectures are a completely different beast. Constructors and dependency injection frameworks compete in the object oriented landscape; in Scala, we have the Cake Pattern as well. Regardless, we aren’t doing purely functional programming just to pass around mutable objects with state, and the Cake Pattern has a similar problem with hiding effects from the user. Extensible effects provide not only a uniform interface to monadic effects, but a dependency injection mechanism that is aware of them. Finally tagless encodings provide an object-oriented view of the problem, which compared to the initial ADT encoding can be not only easier to understand for newcomers but more efficient. | - 16:30 | Break | - 16:45 | @:style(schedule-title)Let the Scala compiler work for you@:@ @:style(schedule-byline)Adelbert Chang@:@ Programming in some languages can feel like you’re working for the compiler - the type checker is naggy, the type system limiting, and much of your code is extraneous. This is backwards. The compiler should be working for you, helping you check your code, allowing you to express the abstractions you want, and enabling you to write clean, beautiful code. In Scala we are lucky to have such a compiler. In this talk we will explore a variety of techniques, libraries, and compiler plugins for Scala that demonstrate the utility of having a compiler that works for you. | - 17:25 | @:style(schedule-title)Adopting Scala: The Next Steps@:@ @:style(schedule-byline)Sofia Cole@:@ Six months into learning Scala, I summarised my experience and delivered a talk to help others going through the same process. This covered effective learning methods, an initial list of topics, and some tips so that others could be effective quickly whilst avoiding some common mistakes. Over a year later, I will reflect on those methods and their result, talk about how I extended my knowledge of functional programming, and explore how to introduce key concepts without feeling overwhelmed. My aim is to present the insights and challenges encountered when learning functional programming to make the experience as approachable as possible. | - 17:45 | Closing Remarks | - 18:00 | After party at the venue hosted by Tapad | - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/weight_watchers.png) { alt: Weight Watchers, title: Weight Watchers, style: legacy-event-sponsor }](http://www.weightwatchers.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/cake.jpg) { alt: Cake Solutions, title: Cake Solutions, style: legacy-event-sponsor }](http://www.cakesolutions.net/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/lightbend.png) { alt: Lightbend, title: Lightbend, style: legacy-event-sponsor }](https://www.lightbend.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/tapad.png) { alt: Tapad, title: Tapad, style: legacy-event-sponsor }](https://www.tapad.com/)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/rally.png) { alt: Rally Health, title: Rally Health, style: legacy-event-sponsor }](https://www.rallyhealth.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/giphy.png) { alt: Giphy, title: Giphy, style: legacy-event-sponsor }](https://giphy.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/driver.png) { alt: Driver, title: Driver, style: legacy-event-sponsor }](https://www.driver.xyz/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/comcast.png) { alt: Comcast, title: Comcast, style: legacy-event-sponsor }](http://www.comcast.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/data-monsters.png) { alt: Data Monsters, title: Data Monsters, style: legacy-event-sponsor }](https://datamonsters.co/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/underscore.png) { alt: Underscore, title: Underscore, style: legacy-event-sponsor }](http://underscore.io/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/iheartradio.png) { alt: iHeartRadio, title: iHeartRadio, style: legacy-event-sponsor }](https://www.iheart.com/)@:@ -@:@ - -### After Party Sponsor -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/meetup.png) { alt: Meetup, title: Meetup, style: legacy-event-sponsor }](http://meetup.com/)@:@ -@:@ diff --git a/src/blog/summit-nyc-2020-03-12.md b/src/blog/summit-nyc-2020-03-12.md deleted file mode 100644 index 8b6a3959..00000000 --- a/src/blog/summit-nyc-2020-03-12.md +++ /dev/null @@ -1,29 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2020-03-12" - event-date: "March 12, 2020" - event-location: "Online" - tags: [summits, events] -%} - -# Typelevel Summit New York City - -![Typelevel Summit New York City](/img/places/nyc.jpg) - -## About the Summit - -The ninth Typelevel Summit will once again be co-located with the [Northeast Scala Symposium](https://twitter.com/nescalas/status/1201601425211609088) in New York City, with one day of recorded talks and one day of unconference. -The schedule for this year is as follows: - -* March 12 (Thursday): Typelevel Summit -* March 13 (Friday): NE Scala 2020 -* March 14 (Saturday): Unconference - -## Important Update - -Due to the current situation regarding COVID-19, we are changing the Typelevel Summit and NE Scala to be online-only. Many attendees have been asked by their employers not to travel or attend conferences. We apologize to anyone who's inconvenienced by this, but we hope you'll still attend remotely. -For more information please visit the [NE Scala website](https://nescala.io/). - -## Speakers and Schedule - -For information on the programme and recordings, please visit the [NE Scala website](https://nescala.io/). diff --git a/src/blog/summit-oslo-2016-05-04.md b/src/blog/summit-oslo-2016-05-04.md deleted file mode 100644 index 6d2e04d6..00000000 --- a/src/blog/summit-oslo-2016-05-04.md +++ /dev/null @@ -1,64 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-05-04" - event-date: "May 4, 2016" - event-location: "Teknologihuset" - tags: [summits, events] -%} - -# Typelevel Summit Oslo - -@:include(/img/places/oslo.md) - -## About the Summit - -The second Typelevel Summit was co-located with [flatMap(Oslo)](http://2016.flatmap.no/). - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:30 | Registration | - 9:00 | Opening Remarks | - 9:15 | @:style(schedule-title)Keynote: How to bake "How to Bake Pi"@:@ @:style(schedule-byline)Dr Eugenia Cheng@:@ Mathematics is a very misunderstood subject. Many people associate it only with painful experiences with childhood, or think it's all about numbers and calculations, or that it's a cold subject with clear rights and wrongs. My mission is to bring my love of mathematics to more people, and as part of this journey I need to show the beauty and the power of abstract thinking. In this talk I will present my experiences of this, starting with the book I wrote for a very general audience, and the Category Theory course I teach to art students at the School of the Art Institute of Chicago. Using a variety of surprising examples, I will show that it is possible to convince maths phobics and maths sceptics that abstract mathematics can be relevant and useful for everyone. | - 10:15 | Break | - 10:30 | @:style(schedule-title)A Year living Freely@:@ @:style(schedule-byline)Chris Myers@:@ The Free monad and the Interpreter Pattern has gained significant interest in the Scala community of late. It is a pattern that has helped unlock the problems of separating pure functions from effects. At REA Group we have had an explosion of interest in FP and Scala in the last two years. Beginning with just a couple of experienced functional programmers to now multiple teams and dozens of developers, we have experienced the growing pains of introducing FP and Scala to a large organisation. The Free monad has been a key element in our journey. As we grew, we were particularly conscious of what patterns we could lay down, especially for beginners, that promoted the integral values of FP such as referential transparency and to allow obvious ways that software should grow. After many experiments and much research, we discovered that the Free monad and interpreter pattern has been something that tangibly isolates effects, maintains referential transparency, subsumes dependency injection, is modular and is surprisingly accessible to FP/Scala new comers. This talk briefly covers the mechanics of the Free monad and the interpreter pattern but largely looks at how a year with the Free monad has allowed us to make novice teams productive while they learn and embrace FP and Scala. | - 11:10 | @:style(schedule-title)What is macro-compat and why you might be interested in using it@:@ @:style(schedule-byline)Dale Wijnand@:@ Despite macros being an experimental feature of Scala, a number of libraries find them to provide great value and choose to make use of them. However in different Scala versions the macro support and API is different. That means that libraries that cross-build for multiple Scala versions have then had to deal with these differences. Macro-compat is a solution to this problem. In this talk I will introduce macro-compat, starting with an overview of the problems it's trying to solve, the prior art of how these problems are dealt with, how to use it and how it works. | - 11:25 | Break | - 11:45 | @:style(schedule-title)Monitoring and controlling power plants with Monix@:@ @:style(schedule-byline)Alexandru Nedelcu@:@ This talk is about my experience in dealing with modeling behavior by processing asynchronous soft-real time signals from different source using Monix, the library for building asynchronous and event-based logic. It's an experience report from my work at E.On, in monitoring and controlling power plants. We do this by gathering signals in real time and modeling state machines that give us the state in which an asset is in. The component, for lack of inspiration named Asset-Engine, is the one component in the project that definitely adheres to FP principles, the business logic being described with pure functions and data-structures and the communication being handled by actors and by Observable streams. I want to show how I pushed side effects at the edges, in a very pragmatic setup. | - 12:25 | @:style(schedule-title)Fetch: Simple & Efficient data access@:@ @:style(schedule-byline)Alejandro Gómez@:@ Fetch is a Scala library for simplifying and optimizing access to data such as files systems, databases, or web services. These data sources usually have a latency cost, and we often have to trade code clarity for performance when querying them. We can easily end up with code that complects the business logic performed on the data we're fetching with explicit synchronization or optimizations such as caching and batching. Fetch can automatically request data from multiple sources concurrently, batch multiple requests to the same data source, and cache previous requests' results without having to use any explicit concurrency construct. It does so by separating data fetch declaration from interpretation, building a tree with the data dependencies where you can express concurrency with the applicative bind, and sequential dependency with monadic bind. It borrows heavily from the Haxl (Haskell, open sourced) and Stitch (Scala, not open sourced) projects. This talk will cover the problem Fetch solves, an example of how you can benefit from using it, and a high-level look at its implementation. | - 12:45 | Lunch Break | - 14:00 | @:style(schedule-title)Decorate your types with refined@:@ @:style(schedule-byline)Frank Thomas@:@ Scala has a powerful type system that allows to create very expressive types. But sometimes we need guarantees about our values beyond what the type system can usually check, for example integers in the range from zero to fifty-nine, or chars that are either a letter or a digit. One way to realize these constraints is known as smart constructors, where the construction mechanism validates at runtime that our values satisfy the constraint. Unfortunately this technique requires some boilerplate and always incur runtime checks even if the values are kown at compile-time. This talk will introduce a library for refining types with type-level predicates, which abstracts over smart constructors. We'll go from the idea of refinement types to examples of the library using the rich set of predicates it provides, and show how it can be used at compile- and runtime alike. On that way we'll see how we can make good use of literal-based singleton types that are proposed in SIP-23. I'll also demonstrate how refined integrates with other libraries like circe, Monocle, or scodec. | - 14:40 | @:style(schedule-title)Discovering Types (from Strings) with Cats and Shapeless@:@ @:style(schedule-byline)Jonathan Merritt@:@ This talk is about a simple problem which can be solved using parts of Cats and Shapeless. While helping data scientists to use the nice, well-typed Scala tools that we build for them, we are often presented with tabular data in raw text files (CSV, PSV, etc.). These files usually have some consistent, but unknown, internal schema. Data scientists are often familiar with dynamic languages like R and Python, in which fields can be parsed speculatively, or on-demand by particular operations at runtime. They usually expect Scala tools to do the same, and they particularly dislike having to specify schemas manually up-front. This mis-match can be addressed by a spectrum of different approaches, which range from handling types outside the language proper (boo! - but it works quite well in practice), to discovering and pre-generating a schema that can be used for compile-time checking. The problem of discovering the schemas of these files in a composable way makes for an interesting tour of some features of Shapeless and Cats. It's useful for beginners because the problem is quite easy to understand. I'll discuss some approaches to this, some of the remaining challenges, and provide attendees with enough background to implement the basics of a working system. I'll focus specifically on a solution that involves Cats and Shapeless for schema pre-generation, rather than macro-based approaches of manifesting schemas. | - 14:55 | Break | - 15:15 | @:style(schedule-title)Building functional programs with bananas, catalysts, shacl's and shapes@:@ @:style(schedule-byline)Alistair Johnson@:@ This is a talk that combines both the practical, but often overlooked, topic of SBT with cutting edge distributed data technologies. The practical aspect is presented by giving an overview of catalysts, where it came from (Scalaz and banana-rdf, actually), how it evolved and how it came to be what and where it is today; and why it should be used. The evolution of catalysts then leads naturally to why current build systems play such an import role in language ecosystems and why these ecosystems can't work as they are today. This is where RDF naturally has a place, along with Shapes and Shapes Constraint Language (SHACL). | - 15:55 | @:style(schedule-title)Growing a DSL for financial calculations@:@ @:style(schedule-byline)Jan Ouwens@:@ Rabobank is a Dutch multinational banking and financial services company headquartered in Utrecht, the Netherlands. One of their services is providing mortgage loans. Determining the height of the loans involves some rather complex calculations. They were struggling to represent these calculations in an understandable and reliably testable way for both domain experts and developers. We helped them develop an internal DSL in Scala that allows them to express these complex calculations in an idiomatic way that is not just easy to read for both developers and business analysts, but more testable as well. Harnessing functional programming principles and the strong Scala compiler, it also provides full typesafety with a syntax that lies very close to human language, allowing fully typesafe constructs such as 'amount per month' and 'amount per year'. In this talk, I will explain the concepts behind the DSL, how we implemented them without adding any dependencies to the project (except ScalaTest, of course), and the design decisions we had to make along the way. | - 16:25 | Break | - 16:45 | @:style(schedule-title)Dotty and types: the story so far@:@ @:style(schedule-byline)Guillaume Martres@:@ Dotty is a new, experimental compiler for Scala. One of the main goal of Dotty is to provide a better type system for Scala that is both theoretically sound and better in practice. In this talk I'll focus on some of the practical improvements to the type system we've made in Dotty, like the new type parameter inference algorithm that, while not formally specified, should be easier to reason about and work in more cases. I will also try to shed some light on the challenges we face, like getting a set of features (like union types, singleton types and type inference) to interact well with each other, or properly implementing higher-kinded types. | - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/commbank.png) { alt: Commonwealth Bank of Australia, title: Commonwealth Bank of Australia, style: legacy-event-sponsor }](https://www.commbank.com.au/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png) { alt: 47 Degrees, title: 47 Degrees, style: legacy-event-sponsor }](http://www.47deg.com/)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/underscore.png) { alt: Underscore, title: Underscore, style: legacy-event-sponsor }](http://underscore.io/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/arktekk.png) { alt: Arktekk, title: Arktekk, style: legacy-event-sponsor }](http://www.arktekk.no/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/lightbend.png) { alt: Lightbend, title: Lightbend, style: legacy-event-sponsor }](https://www.lightbend.com/)@:@ -@:@ - -Thanks to the generous private supporters (in alphabetic order): Frank S. Thomas, Eric Torreborre, and the anonymous patrons. diff --git a/src/blog/summit-philadelphia-2016-03-02.md b/src/blog/summit-philadelphia-2016-03-02.md deleted file mode 100644 index 0652637c..00000000 --- a/src/blog/summit-philadelphia-2016-03-02.md +++ /dev/null @@ -1,72 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2016-03-02" - event-date: "March 2-3, 2016" - event-location: "Hub's Cira Centre" - tags: [summits, events] -%} - -# Typelevel Summit Philadelphia - -@:include(/img/places/philly.md) - -## About the Summit - -The first Typelevel Summit was co-located with the [Northeast Scala Symposium](http://www.nescala.org/) in Philadelphia, with one day of recorded talks and one day of unconference. - -You can find photos from the summit [here](https://goo.gl/photos/P7DDsz68koHCXrAo8). Thanks to Brian Clapper and -Alexy Khrabrov who [also](https://drive.google.com/folderview?id=0B5w3iJKynGZJaWFUbWJOZzNETU0) [documented](http://meetup.bythebay.photo/Conferences/Typelevel/Typelevel-2016-Philadelphia/) the event. - -The Summits are open to all, not just current contributors to and users of the Typelevel projects, and we are especially keen to encourage participation from people who are new to them. -Whilst many of the Typelevel projects use somewhat "advanced" Scala, they are a lot more approachable than many people think, and a major part of Typelevel's mission is to make the ideas they embody much more widely accessible. -If you're interested in types and pure functional programming we'd love to see you here! -Check our [front page](/README.md) for upcoming events. - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:30 | Registration | - 9:00 | Opening Remarks | - 9:10 | @:style(schedule-title)Becoming a cat(s) person@:@ @:style(schedule-byline)Adelbert Chang@:@ Want to contribute to Cats? Let’s head over to the Cats Issues list and do some live coding! Along the way we will see how the codebase is organized, the various bits of automation provided, and how you can use our various channels to get feedback on your work. | - 9:40 | Break | - 9:55 | @:style(schedule-title)End to End and On The Level@:@ @:style(schedule-byline)Dave Gurnell@:@ This talk answers the burning question 'Can I build a complete web service using solely Typelevel libraries?' In Scala we are spoiled for choice for web frameworks, database layers, JSON libraries, and a thousand other essential tools for application development. So much so, it's easy to become a victim of choice paralysis when starting a new project. There's so much choice, many developers favour groups of libraries that work well together. The Typesafe Reactive Platform (colloquially the 'Typesafe Stack'), is widely known as a set of interoperable libraries providing all the functionality required to build entire web applications without looking elsewhere. Enter Typelevel, endorsing a fleet of interoperable free/open source libraries providing all manner of functionality. The phrase 'Typelevel Stack' has been used frequently in the community, raising some intersting questions: Can we build complete web services using Typelevel libraries alone? What would that look like? What will the developer experience be like in terms of tooling, support, and documentation? In this talk, Dave will discuss his adventures building a web framework completely 'on the level', capturing thoughts on design, process, documentation, support, and community along the way. | - 10:35 | @:style(schedule-title)Probabilistic Programming: What It Is and How It Works@:@ @:style(schedule-byline)Noel Welsh@:@ Probabilistic programming is the other Big Thing to happen in machine learning alongside deep learning. It is also closely tied to functional programming. In this talk I will explain the goals of probabilistic programming and how we can implement a probabilistic programming language in Scala. Probabilistic models are one of the main approaches in machine learning. Probabilistic programming aims to make expressive probabilistic models cheaper to develop. This is achieved by expressing the model within an embedded DSL, and then compiling learning (inference) algorithms from the model description. This automates one of the main tasks in building a probabilistic model, and provides the same benefits as a compiler for a traditional high-level language. With the close tie of functional programming to mathematics, and the use of techniques like the free monad, functional programming languages are an ideal platform for embedding probabilistic programming. | - 11:05 | Break | - 11:20 | @:style(schedule-title)Introducing Typelevel Scala into an OO environment@:@ @:style(schedule-byline)Marcus Henry, Jr.@:@ Its difficult enough trying to introduce a new language into an established environment. This problem is compounded when the new language comes with a paradigm shift. This talk will detail one process which successfully introduced Functional Scala into an Object Oriented Java shop. The talk will explain how to bridge the OO-FP impedance mismatch when communicating ideas across project boundaries. The discussion will focus on migrating from Java style mutability, loops, get/set and coupling into Typelevel style immutability, combinators, case classes and type classes. | - 12:00 | @:style(schedule-title)Efficient compiler passes using Cats, Monocle, and Shapeless@:@ @:style(schedule-byline)Greg Pfeil@:@ Centered around a new standalone recursion scheme library (Matryoshka), this talk shows how to take advantage of various Typelevel projects to write many conceptually-independent data transformations, but have them efficiently combined into a small number of passes. Matryoshka also uses other Typelevel projects, including kind-projector and simulacrum. | - 12:30 | Lunch Break | - 14:00 | @:style(schedule-title)Keynote: Dependently-Typed Haskell@:@ @:style(schedule-byline)Stephanie Weirich@:@ Is Haskell a dependently typed programming language? The Glasgow Haskell Compiler's many type-system features, such as Generalized Algebraic Datatypes (GADTs), datatype promotion, multiparameter type classes, type families, and more recent extensions give programmers the ability to encode domain-specific invariants in their types. Clever Haskell programmers have used these features to enhance the reasoning capabilities of static type checking. But how far have we come? Could we do more? | - 15:00 | Break | - 15:20 | @:style(schedule-title)Evaluation in Cats: the Good, the Bad, and the Lazy@:@ @:style(schedule-byline)Erik Osheim@:@ A unique part of Cats' design is its Eval type. This type abstracts over evaluation strategies, and is the primary way to encode laziness in Cats APIs. It also includes a trampoline to allow safe, efficient implementations of algorithms that require laziness. Eval serves as a building block for other types, such as the Streaming data type and the Foldable type class. This talk will cover the basic design of Eval. It will walk through several different examples to help explain how the evalutation strategies work, cover some common pitfalls, and show off some interesting uses of laziness. It will also try to highlight some of the shortcomings of laziness in Scala, as well as alternate approaches. | - 15:40 | @:style(schedule-title)Easy, intuitive, direct-style syntax for Monad-comprehensions!@:@ @:style(schedule-byline)Chris Vogt, Chris Hodapp@:@ Easy, intuitive, direct-style syntax for monad comprehensions! Like Scala async or SBT .value, but generalized to any monad. Implemented, ready to be used and requiring only vanilla Scala 2.10/2.11 and blackbox macros. Future extensions could include automatic use of Applicative where possible, support for more embedded control-flow operations, comprehensions over multiple compatible monads at once for user-defined notions of compatible and compiler tweaks for syntactic improvements. | - 16:00 | @:style(schedule-title)Scala Exercises@:@ @:style(schedule-byline)Raúl Raja Martínez@:@ Scala Exercises is a web based community tool open sourced by 47 Degrees. It contains multiple koan and free form style exercises maintained by library authors and maintainers to help you master some of the most important tools in the Scala Ecosystem. Version 2 comes with a brand new backend and exercise tracking where you can login simply using your Github account and track your progress throughout exercises and libraries. Version 2 will launch with exercises for the stdlib, Cats, Shapeless and other well known libraries and frameworks part of the Scala ecosystem. | - 16:15 | Break | - 16:30 | @:style(schedule-title)From Simulacrum to Typeclassic@:@ @:style(schedule-byline)Michael Pilquist@:@ Simulacrum simplifies development of type class libraries. It is used in a number of open source libraries, including Cats. In this talk, we’ll tour the features of Simulacrum, and look at the forthcoming Typeclassic project, which merges Simulacrum with complementary projects like machinist and export-hook. | - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png) { alt: 47 Degrees, title: 47 Degrees, style: legacy-event-sponsor }](http://www.47deg.com/)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/underscore.png) { alt: Underscore, title: Underscore, style: legacy-event-sponsor }](http://underscore.io/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/verizon.png) { alt: Verizon, title: Verizon, style: legacy-event-sponsor }](http://www.verizonwireless.com/)@:@ -@:@ - -### Silver -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/lightbend.png) { alt: Lightbend, title: Lightbend, style: legacy-event-sponsor }](https://www.lightbend.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/mediamath.png) { alt: MediaMath, title: MediaMath, style: legacy-event-sponsor }](http://www.mediamath.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/comcast.png) { alt: Comcast, title: Comcast, style: legacy-event-sponsor }](http://www.comcast.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/box.png) { alt: Box, title: Box, style: legacy-event-sponsor }](http://www.box.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/scotiabank.png) { alt: Scotiabank, title: Scotiabank, style: legacy-event-sponsor }](http://www.scotiabank.com/)@:@ -@:@ - -Thanks to the generous private supporters (in alphabetic order): -Steve Buzzard, Jeff Clites, Ryan Delucchi, Pedro Furlanetto, Rob Norris, Erik Osheim, Michael Pilquist, SlamData, Stewart Stewart, Frank S. Thomas, and the anonymous patrons. diff --git a/src/blog/summit-philadelphia-2019-04-01.md b/src/blog/summit-philadelphia-2019-04-01.md deleted file mode 100644 index 932e2a4e..00000000 --- a/src/blog/summit-philadelphia-2019-04-01.md +++ /dev/null @@ -1,75 +0,0 @@ -{% - laika.html.template: event.template.html - date: "2019-04-01" - event-date: "April 1, 2019" - event-location: "Science History Institute, Philadelphia" - tags: [summits, events] -%} - -# Typelevel Summit Philadelphia - -@:include(/img/places/philly.md) - -## About the Summit - -The seventh Typelevel Summit will once again be co-located with the [Northeast Scala Symposium](https://nescala.io) in Philadelphia, with one day of recorded talks and one day of unconference. -The schedule for this year is as follows: - -* April 1st: Typelevel Summit -* April 2nd: Northeast Scala Symposium -* April 3rd: Unconference - -## Speakers and Schedule - -| Time | Talk | -|------|------| -| 8:15 | Registration & Breakfast sponsored by Coatue | - 8:55 | Opening Remarks | - 9:00 | @:style(schedule-title)Keynote: Shared Session Types for Safe, Practical Concurrency@:@ @:style(schedule-byline)Stephanie Balzer@:@ Message-passing concurrency abstracts over the details of how programs are compiled to machine instructions and has been adopted by various practical languages, such as Erlang, Go, and Rust. For example, Mozilla's Servo, a next-generation browser engine being written in Rust, exploits message-passing concurrency to parallelize loading and rendering of webpage elements, done sequentially in existing web browsers. Messages are exchanged along channels, which are typed with enumeration types. Whereas typing ensures in this setting that only messages of the appropriate type are communicated along channels, it fails to guarantee adherence to the intended protocol of message exchange. In this talk I show how session types can be used to type communication channels to check protocol adherence at compile-time. Session types were conceived in the context of process calculi, but made their ways into various practical languages using libraries. A key restriction of prior session type work is linearity. Whereas linear session types enjoy strong properties such as race freedom, protocol adherence, and deadlock-freedom, their insistance on a single client rules out common programing scenarios, such as multi-producer-consumer queues or shared databases or output devices. I report on my work on shared session types, which accommodates those programing scenarios, while upholding the guarantees of linear session types. First, I introduce manifest sharing, a discipline in which linear and shared sessions coexist, but the type system ensures that clients of shared sessions run in mutual exclusion from each other. Manifest sharing guarantees race freedom and protocol adherence, but permits deadlocks. Next, I introduce manifest deadlock freedom, which makes shared and linear sessions deadlock-free by construction. Finally, I give an overview of my current and future research plans. - 9:55 | Break | - 10:10 | @:style(schedule-title)Systematic Software with Scala@:@ @:style(schedule-byline)Adam Rosien@:@ Scala is a very flexible language, and this flexibility can make it difficult to know how to effectively design Scala code. In the nearly ten years I've been using Scala, my approach to using the language has coalesced around a ten or so strategies, which are similar to OO design patterns but broader in scope and borrow many ideas from functional programming. Using these strategies I can create code in a systematic and repeatable way. In this talk I will present the majority of my strategies, and illustrate their use by live coding a simple graphics system where the majority of the code is systematically derived by applying strategies. The strategies allow me to work at a higher-level of abstraction, and the coding itself becomes formulaic. This means I can get more work done and my code is simpler to read and use. I hope that my strategies will also enable you to design better code in Scala. | - 10:45 | @:style(schedule-title)Journey to an FP Test Harness@:@ @:style(schedule-byline)Justin du Coeur (Mark Waks)@:@ The hardest part of the pure-FP journey for many people is taking that first real step. Even after you’ve read all the books and done all the exercises, you need to start committing real code to truly grok the FP mindset. This little case study will trace my journey over that line, in building a new test harness to an existing Play application. In the course of it, we’ll explore how my assumptions evolved: from stateful members to consistent use of StateT; from Play’s native Future-centricity to IO; becoming a little more nuanced about test state using IndexedStateT; moving away from an ever-growing cake to focus on imports instead; and the payoff, being able to refactor the test code to be modular, readable and robust. The goal here is to show that, while there are a bunch of parts, none of this is rocket science. In the end, the resulting code is delightfully elegant, and the general approach should work for many Play applications. - 11:20 | Break | - 11:35 | @:style(schedule-title)The Monoiad: an epic poem on monoids@:@ @:style(schedule-byline)Greg Pfeil@:@ Monoids provide a vast landscape of concepts that we rely on in FP. Applicatives, monads, categories – all of them are monoids, as is much else. The epic takes us on a journey with this fundamental structure. We’ll move between everyday Scala, some niche areas of the language, and category theory. | - 12:10 | Lunch sponsored by Simple | - 13:45 | @:style(schedule-title)Keynote: Higher Inductive Types in Homotopy Type Theory@:@ @:style(schedule-byline)Kristina Sojakova@:@ Homotopy type theory is a new field of mathematics based on the recently-discovered correspondence between constructive type theory and abstract homotopy theory. Higher inductive types, which form a crucial part of this new system, generalize ordinary inductive types such as the natural numbers to higher dimensions. We will look at a few different examples of higher inductive types such as the integers, circles, and the torus, and indicate how we can use their associated induction principles to reason about them, e.g., to prove that the torus is equivalent to the product of two circles. | - 14:40 | Break | - 14:55 | @:style(schedule-title)Telling the Truth with Types@:@ @:style(schedule-byline)Christopher Davenport@:@ There are many problems one faces when building effective solutions. (1) Outlining proper behavior, such that desired outcomes are achieved. (2) Simplifying the problem space, such that solutions are extensible and maintainable. (3) Interfacing with existing code. Together we will walk through typical problems, and apply a set of processes to more effectively meet these criteria. We will identify what information we need to make available and how we can consume that information to build out systems which behave as we expect. We will use the type system as our guide, to lift our reasoning directly into our codebases. Whether you are just starting out, or an experienced functional programmer this talk will deliver a set of tools to approach the next set of challenges. - 15:30 | @:style(schedule-title)Composable concurrency with Ref + Deferred@:@ @:style(schedule-byline)Fabio Labella@:@ fs2 offers a very powerful and composable set of concurrent combinators and data structures, which are all built out of two deceptively simple primitives: Ref and Deferred. This talk will explain what they are, the design principles behind them, and how to use them to build your own business logic abstractions. In the process, we will discover a general pattern in the form of concurrent state machines, and see how it integrates with final tagless on one hand, and streaming control flow on the other. If you have ever wondered how to translate that complicated piece of actor logic in pure FP, or how fs2’s Queues, Topics and Signals work under the hood, this is the talk for you. - 16:05 | Break | - 16:20 | @:style(schedule-title)Extending your HTTP library with monad transformers@:@ @:style(schedule-byline)Ross Baker@:@ A tour of monad transformers and how stacking various effects onto IO can extend our HTTP library in new and interesting ways. We’ll review OptionT from last year’s talk, derive something akka-http like with EitherT, and demonstrating tracing with TraceT. | - 16:55 | @:style(schedule-title)Portable, type-fancy multidimensional arrays@:@ @:style(schedule-byline)Ryan Williams@:@ Zarr is a multidimensional-array container format that's gaining momentum in several scientific domains. It hails from the Python world, and primarily caters to numpy- and xarray-wielding scientists. It shines as a more remote- and parallel-processing-friendly HDF5 replacement. I implemented the Zarr spec in portable Scala, leveraging dependent- and higher-kinded-types. The resulting arrays have a unique type-safety profile. In this talk I'll: contextualize Zarr's use in the single-cell-sequencing domain, examine the freewheeling DSLs that scientific-Python exposes for array processing (including remote and distributed), discuss possibilities for Scala (and types!) to make inroads in these ecosystems, and show what worked well and poorly about my attempt. | - 17:30 | Closing | - -## Venue - -The Science History Institute collects and shares the stories of innovators and of discoveries that shape our lives, preserving and interpreting the history of chemistry, chemical engineering, and the life sciences. -Headquartered in Philadelphia, with offices in California and Europe, the Institute houses an archive and a library for historians and researchers, a fellowship program for visiting scholars from around the globe, a community of researchers who examine historical and contemporary issues, an acclaimed museum that is free and open to the public, and a state-of-the-art conference center. - - - -## Code of Conduct - -The Code of Conduct & reporting of incidents are handled together with the team of the Northeast Scala Symposium. -You can find details on [their website](https://nescala.io/#code). -In short: there is a Slack team where you can report incidents to the organizers, to which every ticket holder should have received an invitation. -It is possible to file anonymous reports. -The list of organizers can be found [here](https://nescala.io/organizers.html). - -## Sponsors - -We'd like to thank all our sponsors who help to make the Summit happen: - -### Platinum -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/bridgewater.png) { alt: Bridgewater, title: Bridgewater, style: legacy-event-sponsor }](http://www.bridgewater.com/)@:@ -@:@ - -### Gold -@:style(bulma-grid bulma-is-col-min-12) -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/comcast.png) { alt: Comcast, title: Comcast, style: legacy-event-sponsor }](http://www.comcast.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/azavea.png) { alt: Azavea, title: Azavea, style: legacy-event-sponsor }](http://www.azavea.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/chariot.png) { alt: Chariot Solutions, title: Chariot Solutions, style: legacy-event-sponsor }](http://www.chariotsolutions.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/simple.png) { alt: Simple, title: Simple, style: legacy-event-sponsor }](http://www.simple.com/)@:@ -@:style(bulma-cell bulma-has-text-centered)[@:image(/img/media/sponsors/coatue.png) { alt: Coatue, title: Coatue, style: legacy-event-sponsor }](http://www.coatue.com/)@:@ -@:@ diff --git a/src/blog/summit-programme.md b/src/blog/summit-programme.md deleted file mode 100644 index 0f3b3ec5..00000000 --- a/src/blog/summit-programme.md +++ /dev/null @@ -1,70 +0,0 @@ -{% - author: ${larsrh} - date: "2016-01-28" - tags: [summits] -%} - -# First batch of talks at the Philadelphia Summit - -The work on the programme for the [Philadelphia Summit][philadelphia] is in full swing! -As announced earlier, we're happy to share with you the first batch of accepted talks. -Don't worry though, there's still time until the end of the week to [submit a proposal][cfp]. - -### Becoming a cat(s) person - -Want to contribute to Cats? -Let's head over to the Cats Issues list and do some live coding! -Along the way we will see how the codebase is organized, the various bits of automation provided, and how you can use our various channels to get feedback on your work. - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/adelbertchang.jpeg){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) _Adelbert Chang is a Software Engineer at Box and a recent graduate from UC Santa Barbara where he studied Computer Science and researched graph querying and modeling. He enjoys helping with functional programming education and learning more about programming._@:@ -@:@ - -### Direct syntax for monad comprehensions - -Easy, intuitive, direct-style syntax for monad comprehensions! -Like Scala `async` or SBT `.value`, but generalized to any monad. -Implemented, ready to be used and requiring only vanilla Scala 2.10/2.11 and blackbox macros. -Future extensions could include automatic use of Applicative where possible, support for more embedded control-flow operations, comprehensions over multiple compatible monads at once for user-defined notions of compatible and compiler tweaks for syntactic improvements. - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/chrisvogt.jpg){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) _Chris Vogt. Slick co-author, Compossible records author, frequent Scala conference/user group speaker, former member of Martin's team at LAMP/EPFL, based in NYC, Senior Software Engineer at x.ai_@:@ -@:@ - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/chrishodapp.jpg){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) _Chris Hodapp. Several-time Scala GSOC student and eventually mentor, author of the ill-fated Comprehensive Comprehensions project. He's hoping to see tooling and techniques from the FP/Typelevel community improve the leverage of the average developer. Based in the SF Bay Area._@:@ -@:@ - - -### Scala Exercises - -Scala Exercises is a web based community tool open sourced by 47 Degrees. -It contains multiple koan and free form style exercises maintained by library authors and maintainers to help you master some of the most important tools in the Scala Ecosystem. -Version 2 comes with a brand new backend and exercise tracking where you can login simply using your Github account and track your progress throughout exercises and libraries. -Version 2 will launch with exercises for the stdlib, Cats, Shapeless and other well known libraries and frameworks part of the Scala ecosystem. - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/raulraja.jpg){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) _Raul Raja. Functional programming enthusiast, CTO and Co-founder at 47 Degrees, a functional programming consultancy specialized in Scala._@:@ -@:@ - -### Probabilistic Programming: What It Is and How It Works - -Probabilistic programming is the other Big Thing to happen in machine learning alongside deep learning. -It is also closely tied to functional programming. In this talk I will explain the goals of probabilistic programming and how we can implement a probabilistic programming language in Scala. -Probabilistic models are one of the main approaches in machine learning. -Probabilistic programming aims to make expressive probabilistic models cheaper to develop. -This is achieved by expressing the model within an embedded DSL, and then compiling learning (inference) algorithms from the model description. -This automates one of the main tasks in building a probabilistic model, and provides the same benefits as a compiler for a traditional high-level language. -With the close tie of functional programming to mathematics, and the use of techniques like the free monad, functional programming languages are an ideal platform for embedding probabilistic programming. - -@:style(bulma-media) -@:style(bulma-media-left) @:style(bulma-figure bulma-image bulma-is-64x64) @:image(/img/media/speakers/noelwelsh.png){ style: bulma-is-rounded } @:@ @:@ -@:style(bulma-media-content) _Noel Welsh is a partner at Underscore, a consultancy that specializes in Scala. He's been using Scala for 6 years in all sorts of applications. He's the author of Advanced Scala, which is in the process of being rewritten to use Cats._@:@ -@:@ - -[philadelphia]: summit-philadelphia-2016-03-02.md -[cfp]: http://goo.gl/forms/SX3plxsOKb diff --git a/src/blog/support-typelevel-thanks-to-triplequote-hydra.md b/src/blog/support-typelevel-thanks-to-triplequote-hydra.md deleted file mode 100644 index cc6acb0c..00000000 --- a/src/blog/support-typelevel-thanks-to-triplequote-hydra.md +++ /dev/null @@ -1,22 +0,0 @@ -{% - author: ${typelevel} - date: "2019-05-29" - tags: [governance] -%} - -# Support Typelevel thanks to Triplequote Hydra and compile Scala faster! - -Hello Community! - -As you all know, back in April we announced the [Typelevel Sustainability Program](typelevel-sustainability-program-announcement.md) and we have been delighted by the overwhelming support we received both from companies and individuals: thank you all! - -In just a little bit more than one month we reached 10% of the fundraising goal we set to $150,000. While this is an excellent start, to successfully support the long term sustainability of our ecosystem, we need you to keep advocating for us. - -One difficulty we are aware of is that it can be hard to convince a company to donate money even if it benefits extensively from our ecosystem. This, despite developers knowing how important it is to sustain the maintenance and development of the open source libraries they love and use every day. - -### Announcing: Give 25%, Get 25%, Compile Scala 75% faster! - -To help us get over this hurdle, we are stoked to announce that [Triplequote](https://triplequote.com/), the creator of [Triplequote Hydra](https://triplequote.com/hydra) - the only multicore Scala compiler with built-in compile-time monitoring - has kindly offered to donate 25% of each yearly Hydra license [purchased online](https://triplequote.com/hydra/pricing/) until June 30th using the discount code **TYPELEVEL25**. But that’s not it, by using the discount code you also benefit from a 25% discount on their list pricing! - - -Using Hydra has proven to be incredibly valuable for Scala projects that rely on our ecosystem, and it delivered impressive compilation speedups. Nowadays, even [Cats is compiled with Hydra](https://github.com/typelevel/cats/pull/2848). Therefore, if you are looking for a way to help us reach our fundraising goal, while also profiting from a great product, don’t let this opportunity slip away. Head over to the [Hydra trial page](https://triplequote.com/hydra/trial) and get started with it in no time using your preferred development tool, whether that is sbt, Maven, Gradle, or IntelliJ IDEA! Just don’t forget to use the discount code **TYPELEVEL25** diff --git a/src/blog/symbolic-operators.md b/src/blog/symbolic-operators.md deleted file mode 100644 index 61a035e7..00000000 --- a/src/blog/symbolic-operators.md +++ /dev/null @@ -1,179 +0,0 @@ -{% - author: ${non} - date: "2015-08-07" - tags: [technical] - katex: true -%} - -# Symbolic operators and type classes for Cats - -This post is an introduction into how operators are implemented in Cats and has been originally published in [August 2015](https://gist.github.com/non/3abdb35a72c39276d3d9). -Some more details can be found in the [previous post](machinist.md). - -One of the simplest and most recognizable type classes is the semigroup. -This type class abstracts over the ability to combine values of a certain -type in an associative manner. - -@:style(bulma-notification) - What does *associativity* mean? - We call an operation @:math \oplus @:@ associative, if for all @:math a @:@, @:math b @:@ and @:math c @:@, @:math a \oplus (b \oplus c) = (a \oplus b) \oplus c @:@ holds. - Read more about this in the [README of the algebra repository](https://github.com/non/algebra#algebraic-properties-and-terminology). -@:@ - -Cats provides `cats.Semigroup[A]` to model semigroups. -The `combine` method takes two values of the type `A` and returns an `A` value. - -In addition, Cats defines syntax allowing the binary operator `|+|` to be -used in place of the `combine` method. - -Small example -------------- - -Here is a small method that provides a generic way to combine the elements -of a list: - -```scala -import cats.Semigroup -import cats.implicits._ - -def gsum[A: Semigroup](values: List[A]): Option[A] = - if (values.isEmpty) None else Some(values.reduceLeft((x, y) => x |+| y)) -``` - -(A similar method is built into Cats as `Semigroup.combineAllOption`.) - -How does it work? ------------------ - -One of the parts of `gsum` that might be hard to understand is where -the `|+|` method comes from. Since `x` and `y` are totally generic values -(of type `A`) how can we call a method on them? - -To boil the example down further, consider this simpler example: - -```scala -import cats.implicits._ -19 |+| 20 // produces 39 -``` - -How does this work? We know that the `Int` type does not have a `|+|` method. -Experienced Scala developers will suspect that implicits play a role here, -but what are the details? - -In detail ---------- - -Let's walk through how the expression `19 |+| 20` is compiled. - -First, a `|+|` method is needed on `Int`. Since -[`Int`](https://github.com/scala/scala/blob/v2.11.7/src/library/scala/Int.scala) -does not provide one, the compiler searches for an implicit conversion to a -type that *does* have a `|+|` method. - -Due to our import, it will find the -[`semigroupSyntax[A]`](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/core/src/main/scala/cats/syntax/semigroup.scala#L8) -method, which returns a type that has a `|+|` method (specifically -[`SemigroupOps[A]`](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/core/src/main/scala/cats/syntax/semigroup.scala#L12)). -However, `semigroupSyntax` requires an implicit `Semigroup[A]` value to be in scope. -Do we have a `Semigroup[Int]` in scope? - -Yes we do. Our import also provides an implicit value [`intGroup`](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/std/src/main/scala/cats/std/anyval.scala#L23) -of type `AdditiveCommutativeGroup[Int]`. Leaving aside what *additive*, *commutative*, -and *group* mean here, this is a subtype of `Semigroup[Int]`, so it matches. - -Let's continue with our current example. At this point we have gone from: - -```scala -19 |+| 20 // produces 39 -``` - -to: - -```scala -semigroupSyntax[Int](19)(intGroup) |+| 20 -``` - -But we aren't out of the woods yet! We still need to see how this expression -is evaluated. - -Of macros and machinists ------------------------- - -Looking at how the `|+|` method is -[implemented](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/core/src/main/scala/cats/syntax/semigroup.scala#L13) -reveals the cryptic `macro Ops.binop[A, A]`. What is this? - -Following the rabbit hole farther, we come to -[`cats.macros.Ops`](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/macros/src/main/scala/cats/macros/Ops.scala#L6) -which provides the macro implementation that `|+|` is using. Aside from a -[suggestively named](https://github.com/non/cats/blob/82dbf4076572dfbb6e29dd49875f5e5d929f80be/macros/src/main/scala/cats/macros/Ops.scala#L18) -item in the `operatorNames` map, we don't have any clues what is going on. - -The [machinist](https://github.com/typelevel/machinist) project was created -to optimize exactly this kind of implicit syntax problem. What will happen here -is that `operatorNames` describes how to rewrite expressions using type -classes. Long-story short, we will transform: - -```scala -semigroupSyntax[Int](19)(intGroup) |+| 20 -``` - -into: - -```scala -intGroup.combine(19, 20) -``` - -The aforementioned suggestive map item tells us that we should rewrite the `|+|` operator -to method calls on the given type class (i.e. `intGroup`) using `.combine`. - -Finishing up ------------- - -Just to confirm that we're done, let's look at what `intGroup.combine` will do. -We started with a call to `AdditiveCommutativeGroup[Int]`, which will find -[`intAlgebra`](https://github.com/non/algebra/blob/v0.3.1/std/shared/src/main/scala/algebra/std/int.scala#L12). -Then we call the [`.additive`](https://github.com/non/algebra/blob/v0.3.1/core/src/main/scala/algebra/ring/Additive.scala#L93) -method on it to produce a `CommutativeGroup[Int]`. - -So putting that together, we can see that calling `intGroup.combine(19, 20)` -will call `intAlgebra.plus(19, 20)`, and that this is defined as `19 + 20`, -as we would expect. - -Whew! - -Conclusion ----------- - -This is a lot of machinery. The incredibly terse and expressive syntax it -enables is quite nice, but you can see that even leaving out one import -will cause the whole edifice to come tumbling down. - -The easiest way to use Cats is to just import `cats.implicits._`. That -way, you can be sure that you have all of it. There are individual imports -from `cats.syntax` and `cats.std` which can be used to pinpoint the exact -values and method you want to put into scope, but getting these right -can be a bit tricky, especially for newcomers. - -Some more examples of Machinist can be found in the [README](https://github.com/typelevel/machinist/blob/v0.4.1/README.md#examples). - -Errata ------- - -You may also decide that the syntax convenience is not worth it. To write our -original example without syntax implicits (but still using implicit values) -you could say: - -```scala -import cats.Semigroup -import cats.implicits._ - -def gsum[A](values: List[A])(implicit s: Semigroup[A]): Option[A] = - if (values.isEmpty) None else Some(values.reduceLeft((x, y) => s.combine(x, y))) - -// values.reduceLeft(s.combine) would also work -``` - -Whether to use syntax implicits or explicit method calls is mostly a matter -of preference. Personally, I like using syntax explicits to help make generic -code read in a clearer manner, but as always, your mileage may vary. diff --git a/src/blog/tagless-final-streaming.md b/src/blog/tagless-final-streaming.md deleted file mode 100644 index bbeb0cd4..00000000 --- a/src/blog/tagless-final-streaming.md +++ /dev/null @@ -1,252 +0,0 @@ -{% - author: ${gvolpe} - date: "2018-05-09" - tags: [technical] -%} - -# Tagless Final Algebras and Streaming - -There have been a couple of really [nice blog posts](optimizing-final-tagless.md) about `Tagless Final` and some related topics. However, I have faced some design problems when writing some algebras and haven't seen anybody talking about. So please let me introduce this problem to you. - -### Algebra definition - -Given the following data definition: - -```scala -case class ItemName(value: String) extends AnyVal -case class Item(name: ItemName, price: BigDecimal) -``` - -Consider the following algebra: - -```scala -trait ItemRepository[F[_]] { - def findAll: F[List[Item]] - def find(name: ItemName): F[Option[Item]] - def save(item: Item): F[Unit] - def remove(name: ItemName): F[Unit] -} -``` - -Let's go through each method's definition: - -- `findAll` needs to return many Items, obtainable inside a context: `F[List[Item]]`. -- `find` might or might not return an Item inside a context: `F[Option[Item]]`. -- `save` and `remove` will perform some actions without returning any actual value: `F[Unit]`. - -Everything is clear and you might have seen this kind of pattern before, so let's create an interpreter for it: - -```scala -import doobie.implicits._ -import doobie.util.transactor.Transactor -import cats.effect.Sync - -// Doobie implementation (not fully implemented, what matters here are the types). -class PostgreSQLItemRepository[F[_]](xa: Transactor[F]) - (implicit F: Sync[F]) extends ItemRepository[F] { - - override def findAll: F[List[Item]] = sql"select name, price from items" - .query[Item] - .to[List] - .transact(xa) - - override def find(name: ItemName): F[Option[Item]] = F.pure(None) - override def save(item: Item): F[Unit] = F.unit - override def remove(name: ItemName): F[Unit] = F.unit -} - -``` - -Here we are using [Doobie](http://tpolecat.github.io/doobie/), defined as `A principled JDBC layer for Scala` and one of the most popular DB libraries in the Typelevel ecosystem. And it comes with one super powerful feature: it supports `Streaming` results, since it's built on top of [fs2](https://functional-streams-for-scala.github.io/fs2/). - -Now it could be very common to have a huge amount of `Item`s in our DB that a `List` will not fit into memory and / or it will be a very expensive operation. So we might want to stream the results of `findAll` instead of have them all in memory on a `List`, making `Doobie` a great candidate for the job. But wait... We have a problem now. Our `ItemRepository` algebra has fixed the definition of `findAll` as `F[List[Item]]` so we won't be able to create an interpreter that returns a streaming result instead. - -### Rethinking our algebra - -We should think about abstracting over that `List` and two of the most common abstractions that immediately come to mind are `Foldable` and `Traverse`. But although these typeclasses are very useful, they are not enough to represent a stream of values, so we should come up with a better abstraction. - -Well, it seems that our options are either adding another higher-kinded parameter `G[_]` to our algebra or just define an abstract member `G[_]`. So let's go with the first one: - -```scala -trait ItemRepository[F[_], G[_]] { - def findAll: G[Item] - def find(name: ItemName): F[Option[Item]] - def save(item: Item): F[Unit] - def remove(name: ItemName): F[Unit] -} -``` - -Great! This looks good so far. - -### Streaming support interpreter - -Now let's write a new `PostgreSQL` interpreter with streaming support: - -```scala -import doobie.implicits._ -import doobie.util.transactor.Transactor -import fs2.Stream - -// Doobie implementation (not fully implemented, what matters here are the types). -class StreamingItemRepository[F[_]](xa: Transactor[F]) - (implicit F: Sync[F]) extends ItemRepository[F, Stream[F, ?]] { - - override def findAll: Stream[F, Item] = sql"select name, price from items" - .query[Item] - .stream - .transact(xa) - - override def find(name: ItemName): F[Option[Item]] = F.pure(None) - override def save(item: Item): F[Unit] = F.delay(println(s"Saving item: $item")) - override def remove(name: ItemName): F[Unit] = F.delay(println(s"Removing item: $item")) -} -``` - -Voilà! We got our streaming implementation of `findAll`. - -### Test interpreter - -That's all we wanted, but what about testing it? Sure, we might prefer to have a simple implementation by just using a plain `List`, so what can we possibly do? - -```scala -object MemRepository extends ItemRepository[Id, List] { - private val mem = MutableMap.empty[String, Item] - - override def findAll: List[Item] = mem.headOption.map(_._2).toList - override def find(name: ItemName): Id[Option[Item]] = mem.get(name.value) - override def save(item: Item): Id[Unit] = mem.update(item.name.value, item) - override def remove(name: ItemName): Id[Unit] = { - mem.remove(name.value) - () - } -} -``` - -That's pretty much it! We managed to abstract over the return type of `findAll` by just adding an extra parameter to our algebra. - -### About composition - -At this point the avid reader might have thought, what if I want to write a generic function that takes all the items (using `findAll`), applies some discounts and writes them back to the DB (using `save`)? - -Short answer is, you might want to define a different algebra where `findAll` and `save` have the same types (eg: both of them are streams) but in case you find yourself wanting to make this work with the current types then let's try and find out! - -```scala -class DiscountProcessor[F[_], G[_]: Functor](repo: ItemRepository[F, G], join: G[F[Unit]] => F[Unit]) { - - def process(discount: Double): F[Unit] = { - val items: G[Item] = repo.findAll.map(item => item.copy(price = item.price * (1 - discount))) - val saved: G[F[Unit]] = items.map(repo.save) - join(saved) - } -} -``` - -We defined a `join` function responsible for evaluating the effects and flatten the result to `F[Unit]`. As you can see below, this works for both a streaming interpreter and a list interpreter (shout out to [fthomas](https://github.com/fthomas) for proposing this solution): - -```scala -object StreamingDiscountInterpreter { - - private val join: Stream[IO, IO[Unit]] => IO[Unit] = _.evalMap(identity).compile.drain - - def apply(repo: ItemRepository[IO, Stream[IO, ?]]): DiscountProcessor[IO, Stream[IO, ?]] = - new DiscountProcessor[IO, Stream[IO, ?]](repo, join) - -} - -object ListDiscountInterpreter { - - private val join: List[IO[Unit]] => IO[Unit] = list => Traverse[List].sequence(list).void - - def apply(repo: ItemRepository[IO, List]): DiscountProcessor[IO, List] = - new DiscountProcessor[IO, List](repo, join) - -} -``` - -While in this case it was possible to make it generic I don't recommend to do this at home because: - -1. it involves some extra boilerplate and the code becomes harder to understand / maintain. -2. as soon as the logic gets more complicated you might run out of options to make it work in a generic way. -3. you lose the ability to use the `fs2 DSL` which is super convenient. - -What I recommend instead, is to write this kind of logic in the streaming interpreter itself. You could also write a generic program that implements the parts that can be abstracted (eg. applying a discount to an item `f: Item => Item`) and leave the other parts to the interpreter. - -### Design alternative - -Another possible and very interesting alternative suggested by [Michael Pilquist](https://github.com/mpilquist), would be to define our repository as follows: - -```scala -trait ItemRepository[F[_], S[_[_], _]] { - def findAll: S[F, Item] -} -``` - -Where the second type parameter matches the shape of `fs2.Stream`. In this case our streaming repository will remain the same (it should just extend `ItemRepository[F, Stream]` instead of `ItemRepository[F, Stream[F, ?]]`) but our in memory interpreter will now rely on `fs2.Stream` instead of a parametric `G[_]`, for example: - -```scala -object MemRepositoryAlt extends ItemRepository[Id, Stream] { - - override def findAll: Stream[Id, Item] = { - sql"select name, price from items" - .query[Item] - .stream - .transact(xa) - } - -} -``` - -I think it's an alternative worth exploring further that might require a blog post on its own, so I'll leave it here for reference :) - -### Source of inspiration - -I've come up with most of the ideas presented here during my work on [Fs2 Rabbit](https://gvolpe.github.io/fs2-rabbit/), a stream based client for `Rabbit MQ`, where I make heavy use of this technique as I originally described in [this blog post](https://partialflow.wordpress.com/2018/02/01/a-tale-of-tagless-final-cats-effect-and-streaming-fs2-rabbit-v0-1/). - -Another great source of inspiration was [this talk](https://www.youtube.com/watch?v=1h11efA4k8E) given by [Luka Jacobowitz](https://github.com/LukaJCB) at Scale by the Bay. - -### Abstracting over the effect type - -One thing you might have noticed in the examples above is that both `ItemRepository` interpreters are not fixed to `IO` or `Task` or any other effect type but rather requiring a parametric `F[_]` and an implicit instance of `Sync[F]`. This is a quite powerful technique for both library authors and application developers. Well know libraries such as [Http4s](https://http4s.org/), [Monix](https://monix.io/) and [Fs2](https://functional-streams-for-scala.github.io/fs2/) make a heavy use of it. - -And by requiring a `Sync[F]` instance we are just saying that our implementation will need to suspend synchronous side effects. - -Once at the edge of our program, commonly the main method, we can give `F[_]` a concrete type. At the moment, there are two options: `cats.effect.IO` and `monix.eval.Task`. But hopefully soon we'll have a `Scalaz 8 IO` implementation as well (fingers crossed). - -### Principle of least power - -Abstracting over the effect type doesn't only mean that we should require `Sync[F]`, `Async[F]` or `Effect[F]`. It also means that we should only require the minimal typeclass instance that satisfies our predicate. For example: - -```scala -import cats.Functor -import cats.implicits._ - -def bar[F[_]: Applicative]: F[Int] = 1.pure[F] - -def foo[F[_]: Functor](fa: F[Int]): F[String] = - fa.map(_.toString) -``` - -Here our `bar` method just returns a pure value in the `F` context, thus we need an `Applicative[F]` instance that defines `pure`. On the other hand, our `foo` method just converts the inner `Int` into `String`, what we call a pure data transformation. So all we need here is a `Functor[F]` instance. Another example: - -```scala -import cats.Monad - -def fp[F[_]: Monad]: F[String] = -` for { - a <- bar[F] - b <- bar[F] - } yield a + b -``` - -The above implementation makes use of a `for-comprehension` which is a syntactic sugar for `flatMap` and `map`, so all we need is a `Monad[F]` instance because we also need an `Applicative[F]` instance for `bar`, otherwise we could just use a `FlatMap[F]` instance. - -### Final thoughts - -I think we got quite far with all these abstractions, giving us the chance to write clean and elegant code in a pure functional programming style, and there's even more! Other topics worth mentioning that might require a blog post on their own are: - -- Dependency Injection - + Tagless Final + implicits (MTL style) enables DI in an elegant way. -- Algebras Composition - + It is very common to have multiple algebras with a different `F[_]` implementation. In some cases, `FunctionK` (a.k.a. natural transformation) can be the solution. - -What do you think about it? Have you come across a similar design problem? I'd love to hear your thoughts! diff --git a/src/blog/testing-in-the-wild.md b/src/blog/testing-in-the-wild.md deleted file mode 100644 index 6a61d9da..00000000 --- a/src/blog/testing-in-the-wild.md +++ /dev/null @@ -1,275 +0,0 @@ -{% - author: ${etorreborre} - date: "2018-07-12" - tags: [technical] -%} - -# Testing in the wild - -Writing tests seems like a wonderful idea in theory but real systems can be a real pain to test. Today I want to show a few tips on how to - use [specs2](http://specs2.org) + [ScalaCheck](http://www.scalacheck.org) to make some real-world testing somewhat bearable. - - I am currently refactoring a big piece of code. Such refactoring is more like a small rewrite and some of our previous tests also have to be - rewritten from scratch. I will try to introduce you to the problem first. - -### Creating articles - - The system-du-jour is called "Panda" (we have animal names for many of our services in my team) and is tasked with the creation of articles - on our legacy platform. An article is already a complicated beast. We have 3 levels of descriptions: - - - Model: the description for a pair of RunFast shoes, with the brand, the gender it applies to, the size chart it uses and so on - - Config: the description for a specific combination of colours - black RunFast -, images, material,... There can be several configs per model - - Simple: the description of a specific size - 37, 38, 39 -, price, stock, EAN (European Article Number),... There can be several simples - per config - - This data can be created in our legacy catalog by calling a bunch of SOAP (yes you read that right) APIs and getting back, at each level, - some identifiers for the model, the configs, the simples. - - This in itself can already be quite complicated and the creation of an article could fail in many ways. But it gets a lot more complex - considering that: - - 1. we need the service to be idempotent and not try to recreate an existing model/config/simple twice if we receive the same event twice - (we are using Kafka as our events system) - - 2. the articles sent by a merchant can be created incrementally, so some parts of the model/config/simple might have been already created - in a previous call - - 3. we are not the only ones creating articles in the system! Indeed, our team creates articles coming from external merchants but there is - also an internal "wholesale" department buying their own articles and creating them in the catalog. In that case a merchant might add - a new config to an existing model or some simples to an existing config - - 4. any step in the process could break and we have no support for transactions making sure that everything is created at once - - So many things which can go wrong, how would you go about testing it? - -### Combinations, the key to testing - - After I started rewriting the tests I realized that our current approach was barely scratching the surface of all the possible combinations. - In a similar case your first thought should be "ScalaCheck"! But this time I am going to use ScalaCheck with a twist. Instead of only modelling - input data (model/config/simples) I am also modelling the system state: - - - we have a "mapping table" to store merchant articles that have already been created. In which states can it be? - - the legacy catalog can also be in many different states: does a particular model/config/simple already exist or not? - - If we translate this into a specs2 + ScalaCheck specification, we get a property like this: - -```scala -class ArticleServiceSpec extends Specification with ScalaCheck { def is = s2""" - - - - run tests for model creation $modelCreation - -""" - - def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) => - - ok // for now - - - }.setGen1(genArticleOneConfig) -} -``` - - We are creating a ScalaCheck property, with a specs2 method `prop` which gives us some additional power over row ScalaCheck properties. - One thing we do here is to restrict the kind of generated `Article` to articles containing only one new configuration because we want to - focus first on all the possible cases of model creation. So we pass a specific generator to the property, just for the first argument - with `setGen1`. - - Then, as you can see above, we return `ok` which is a specs2 result. This is because `prop` allows us to return anything that specs2 recognizes - as a `Result` (with the `org.specs2.execute.AsResult` typeclass) and then we are not limited to booleans in our ScalaCheck properties but - we can use specs2 matchers as well (we are going use this in the next step). - - Now, for testing we need to do the following: - - 1. capture the state before the article creation - 2. execute the article creation - 3. capture the state after the article creation - 4. compare the resulting state to expected values - -```scala - val before = createBeforeState(reviewed, catalog, mappings) - val result = run(createService(catalog, mappings).createArticles[R](reviewed)) - val after = createAfterState(reviewed, catalog, mappings, result) -``` - - What are `BeforeState` and `AfterState`? They are custom case classes modelling the variables we are interested in: -```scala - case class BeforeState( - modelIdProvided: Boolean, - modelNeedsToBeCreated: Boolean, - modelExistsInCatalog: Boolean, - mappingExists: Boolean) - - case class AfterState( - modelExistsInCatalog: Boolean, - mappingExists: Boolean, - exception: Option[Throwable]) -``` - - The first 2 variables of `BeforeState` are a bit curious. The first one gives us a `ModelId` if upstream systems know that a model already - exist. Then how could `modelNeedsToBeCreated` be true? Well, the events we receive don't rule out this possibility. This is the - current state of our domain data and arguably we should model things differently and reject malformed events right away. This is where the - saying ["Listen to your tests"](https://www.obeythetestinggoat.com/book/chapter_purist_unit_tests.html#_listen_to_your_tests_ugly_tests_signal_a_need_to_refactor) comes in :-). - - If we count the number of combinations we end up with 16 possibilities for our "before state" and 8 possible outcomes. How can we represent - all those combinations in our test? - -### DataTables - - Specs2 offers to possibility to create tables of data directly inside the code for better readability of actual and expected values - when you have lots of different possible combinations. Here is what we can do here - -```scala - val results = - "#" | "model-id" | "create" | "in catalog" | "mapping" || "in catalog" | "mapping" | "exception" | "comment" | - 1 ! true ! true ! true ! true !! true ! true ! false ! "no model is created, because it can be found in the catalog, creation data is ignored" | - 2 ! true ! true ! true ! false !! true ! true ! false ! "we just updated the mapping" | - 3 ! true ! true ! false ! true !! false ! true ! true ! "the config creation must fail, no existing model" | - 4 ! true ! true ! false ! false !! true ! true ! false ! "the given model-id is ignored (a warning is logged)" | - 5 ! true ! false ! true ! true !! true ! true ! false ! "no model is created, because it can be found in the catalog" | - 6 ! true ! false ! true ! false !! true ! false ! false ! "the mappings are not updated because we did not create the model" | - 7 ! true ! false ! false ! true !! false ! true ! true ! "no corresponding model in the catalog" | - 8 ! true ! false ! false ! false !! false ! false ! true ! "no corresponding model in the catalog" | - 9 ! false ! true ! true ! true !! true ! true ! false ! "we use the mapping table to retrieve the model id and the catalog for the model" | - 10 ! false ! true ! true ! false !! true ! true ! false ! "in this case the model already exists in the catalag but we have no way to know" | - 11 ! false ! true ! false ! true !! false ! true ! true ! "the mapping exists but not the data in the catalog" | - 12 ! false ! true ! false ! false !! true ! true ! false ! "regular model + config creation case" | - 13 ! false ! false ! true ! true !! true ! true ! true ! "there is no model id and no creation data" | - 14 ! false ! false ! true ! false !! true ! false ! true ! "the model exists in the catalog but we have no way to retrieve it" | - 15 ! false ! false ! false ! true !! false ! true ! true ! "model id found in the mapping but not in the catalog" | - 16 ! false ! false ! false ! false !! false ! false ! true ! "not enough data to create the model nor the mapping" - - checkState(before, after, parseTable(results)) -``` - This looks like a strange piece of code but this is actually all valid Scala syntax! `results` is a specs2 `DataTable` created out of: - - - a header where column names are separated with `|` - - rows that are also separated with `|` - - cells on each row, separated with `!` - -We can also use `||` and `!!` as separators and we use this possibility here to visually distinguish input columns from expected results - columns. - -### Running the tests - -The table above is like a big "truth table" for all our input conditions. Running a test consists in: - - 1. using the 'before state' to locate one of the row - 2. getting the expected 'after state' from the expected columns - 3. comparing the actual 'after state' with the expected one - -The funny thing is that before executing the test I did not exactly know what the code would actually do! So I just let the test guide me. -I put some expected values, run the test and in case of a failure, inspect the input values, think hard about why the code is not behaving the -way I think it should. - -One question comes to mind: since this is a ScalaCheck property, how can we be sure we hit all the cases in the table? The first thing we -can do is to massively increase the number of tests that are going to be executed for this property, like 10000. With specs2 you have many -ways to do this. You can set the `minTestsOk` ScalaCheck property directly in the code: -```scala -def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) => - ... -}.setGen1(genArticleOneConfig).set(minTestsOk = 10000) -``` - -But you can also do it from sbt: -``` -sbt> testOnly *ArticleServiceSpec -- scalacheck.mintestsok 10000 -``` - -This is quite cool because this means that you don't have to recompile the code if you just want to run a ScalaCheck property with more tests. - -### Checking the results - -As I wrote, when a specific combination would fail I had to inspect the inputs/outputs and think hard, maybe my expectations are wrong and -I needed to change the expected values? To this end I added a "line number" column to the table and reported it in the result: - -``` - [error] > On line 6 - [error] - [error] Before - [error] model id set: true - [error] model creation data set: false - [error] model exists in catalog : true - [error] model id mapping exists: false - [error] - [error] After - [error] model exists in catalog: true - [error] expected: true - [error] - [error] model id mapping exists: false - [error] expected: false - [error] - [error] exception thrown: None - [error] expected: Some -``` - - This reporting is all done in the `checkState` method which is: - - - doing the comparison between actual and expected values - - displaying the before / after states - - displaying the difference between expected and actual values - - Actually I even enhanced the display of actual/expected values by coloring them in green or red in the console, using one of specs2 helper - classes `org.specs2.text.AnsiColors`: - -```scala -import org.specs2.text.AnsiColors - -def withColor[A](actual: A, expected: A, condition: (A, A) => Boolean = (a:A, e:A) => a == e): String = - // color the expected value in green or red, depending on the test success - color(expected.toString, if (condition(actual, expected)) green else red) - -withColor(after.modelExistsInCatalog, expected.modelExistsInCatalog) -``` - -Both the line numbering and the coloring really helps in fixing issues fast! - -### Replaying tests - -A vexing issue with property-based testing is that being random, it will generate random failures every time you re-run a property. So you -can't re-run a property with the exact same input data. But that was before ScalaCheck 1.14! Now we can pass the seed that is used by the random - generator to faithfully re-run a failing test. Indeed when a property fails, specs2 will display the current seed value: -``` -[error] The seed is 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI= -``` - -And you can pass this value on the command line to re-run with exactly the failing input data: -``` -sbt> testOnly *ArticleServiceSpec -- scalacheck.seed 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI= -``` - -This is super-convenient for debugging! - -### Comments - -Finally when a given row in the table passes, there is a `comment` column to register the reason for this specific outcomes so that future -generations have a sense of *why* the code is behaving that way. In that sense this whole approach is a bit like having "golden tests" which -are capturing the behaviour of the system as a series of examples - -### Conclusion - -This post shows how we can leverage features from both specs2 and ScalaCheck to make our tests more exhaustive, more readable, more debuggable. -The reality is still more complicated than this: - - - the total number of combinations would make our table very large. So there are actually several tables (one for model creation, one for - config creation,...) where we assume that some variables are fixed while others can move - - - specs2 datatables are currently limited to 10 columns. The `DataTable` code is actually code generated and the latest version only has 10 - columns. One easy first step would be to generate more code (and go up to the magic 22 number for example) or to re-implement this functionality - as some kind of HList - - - the input state is not trivial to generate because the objects are dependent. The `ModelId` of a generated model must be exactly the same - as the one used in the `Mappings` component to register that a model has already been created. So in reality the 2 generators for `Article` and - `Mappings` are not totally independent - - - the `Arbitrary` instance for `Article` can give us articles with 5 `Configs` and 10 `Simples` but for this test, one `Config` and one `Simple` - are enough. Unfortunately we miss a nice language to express those generation condition and easily tweak the default `Arbitrary[Article]` (I - will explore a solution to this problem during the next Haskell eXchange) - - - why are we even using ScalaCheck to generate all the cases since we already statically know all the possible 16 input conditions? We could - invert this relation and have a ScalaCheck property generated for each row of the datatable with some arbitrary data for the model (and some - fixed data given by the current row). This would not necessarily lead to easier code to implement. - -Anyway despite those remaining questions and issues I hope this post gives you some new ideas on how to be more effective when writing tests - with specs2 and ScalaCheck, please comment on your own experiments! diff --git a/src/blog/three-types-of-strings.md b/src/blog/three-types-of-strings.md deleted file mode 100644 index 31745f7b..00000000 --- a/src/blog/three-types-of-strings.md +++ /dev/null @@ -1,104 +0,0 @@ -{% - author: ${S11001001} - date: "2017-09-05" - tags: [technical] -%} - -# There are at least three types of strings - -[Newtype mechanisms](https://contributors.scala-lang.org/t/pre-sip-unboxed-wrapper-types/987) -are a great way to introduce wrapper-free, global distinctions of -“different” instances of the same type. But we can do that on a local -level, too, -[by using type parameters](https://gist.github.com/jbgi/d6b677d084fafc641fe01f7ffd00591c/70842ca600e53e8c237c681773fe4e16bd679628#file-label-java-L32). - -Consider these two signatures. - -```scala -def mungeIDs(uids: List[String], gids: List[String], - oids: List[String]): Magic[String, String, String] - -def mungeIDsSafely[UID <: String, GID <: String, OID <: String] - (uids: List[UID], gids: List[GID], - oids: List[OID]): Magic[UID, GID, OID] -``` - -The second function is a strictly more general interface; the first, -concrete signature can be implemented by calling the second function, -passing `[String, String, String]` as the type arguments. There is no -need to even have the first signature; anywhere in your program where -you pass three `List[String]`s as arguments to `mungeIDsSafely`, the -proper type arguments will be inferred. - -Yet, assuming you don’t wish `mungeIDs` to be oracular (i.e. a source -of UIDs, GIDs, and OIDs), the second signature is probably much more -reliable, because type parameters are quite as -[mysterious](more-types-than-classes.md#it-must-not-necessarily-be-anything) -as the opaque abstract type members of the newtype mechanism. - -1. `mungeIDsSafely` can’t invent new IDs, not even with `null`. -2. It can’t combine them to produce new IDs. -3. It *can* treat the three list arguments as `List[String]`. However, - it cannot convert any `String` back into an ID; any UIDs, GIDs, or - OIDs that appear in the result `Magic[UID, GID, OID]` must have - come from one of the argument lists, directly. (That’s not to say - that `mungeIDsSafely` can’t *use* the string-nature to make that - decision; for example, it could always choose the - smallest-as-string UID to put into the resulting `Magic`. But, that - UID is *still* enforced to be a proper element of the `uids` - argument, and cannot be gotten from anywhere else. -4. Perhaps most importantly, it cannot mix up UIDs, GIDs, and - OIDs. Even though, “really”, they’re all strings! - -It is entirely irrelevant that you cannot subclass `String` in Scala, -Java, or whatever. -[There are more types than classes](more-types-than-classes.md). - -Given the advantages, it’s very unfortunate that the signature of -`mungeIDsSafely` is so much noisier than that of `mungeIDs`. At least -you have the small consolation of eliminating more useless unit tests. - -This is a good first approximation at moving away from the dangers of -concreteness in Scala, and has the advantage of working in Java, too -(sort of; the `null` prohibition is sadly relaxed). - -## Non-supertype constraints - -In Scala, you can also use implicits to devise arbitrary constraints, -similar to typeclasses in Haskell, and sign your functions using -implicits instead, for much finer-grained control, improved safety, -and types-as-documentation. - -```scala -// a typeclass for "IDish types" (imagine instances) -sealed trait IDish[A] - -def mungeIDsTCey[UID: IDish, GID: IDish, OID: IDish] - (uids: List[UID], gids: List[GID], - oids: List[OID]): Magic[UID, GID, OID] -``` - -Though all three types have the same constraint, `IDish`, they are -still distinct types. And now, the coupling with `String` is broken; -as the program author, you get to decide whether you want that or not. - -## Pitfalls avoided for you - -Luckily, Java doesn’t make the mistake of “reified generics”. If it -did, you could ask whether `UID = GID = OID = String`, and all your -safety guarantees would be gone. Forcing all generics to be reified -does not grant you any new expressive power; all it does is -permanently close off large swaths of the spectrum of mystery to you, -forbidding you from using the full scope of the design space to -improve the reliability of your well-typed programs. - -The same goes for claiming that `null` ought to be a default member of -*every* type, even the abstract ones that ought to be a little more -mysterious; it’s easy to add new capabilities (e.g. Scala’s `>: Null` -constraint, if you really *must* use `null`), but taking them away is -much, much harder. - -Furthering this spirit of making good programs easier to write and bad -programs harder to write, a useful area of research in Scala might be -making signatures such as that of `mungeIDsSafely` nicer, or -signatures such as that of `mungeIDs` uglier. diff --git a/src/blog/towards-scalaz-1.md b/src/blog/towards-scalaz-1.md deleted file mode 100644 index cbec2336..00000000 --- a/src/blog/towards-scalaz-1.md +++ /dev/null @@ -1,249 +0,0 @@ -{% - author: ${adelbertc} - date: "2013-10-13" - tags: [technical] - katex: true -%} - -# Towards Scalaz (Part 1) - -A lot of people see Scalaz as a hard fringe, ivory tower, -not suited for real-world applications library, which is -unfortunate. The goal of this blog post series is to introduce -various components of Scalaz, and hopefully through this -allow folks to gain an understanding towards the power of -Scalaz. - -As a prerequisite, I assume knowledge of type classes as they -are implemented and used in Scala. - -## Part 1: Learning to Add - -Our motivation for the inaugural post of the series will be -summing a `List` of something. Lets start out with `Int`, -which is simple enough. - -```scala -def sum(l: List[Int]): Int = l.reduce(_ + _) -``` - -And this works (kind of, it fails on empty `List`s but we'll get to that). -But what if we want to sum a `List[Double]`? - -```scala -def sumDoubles(l: List[Double]): Double = l.reduce(_ + _) -``` - -The code is the same, modulo the type parameter. In fact, the -code would be the same whether it is `Int`, `Double`, or `BigInt`. -Being the good programmers that we are, let's make this generic -in that respect with the help of `scala.math.Numeric`. - -```scala -def sumNumeric[A](l: List[A])(implicit A: Numeric[A]): A = - l.reduce(A.plus) -``` - -### Problem -Awesome. We can now sum `List[Int]`, `List[Double]`, `List[BigInt]`, -and many more. - -But let's give this a bit more thought - what if we wanted to -"sum" a `List[String]` - that is, we concatenate all the `String`s -together to create one large `String` ? - -```scala -def sumStrings(l: List[String]): String = l.reduce(_ + _) -``` - -This looks exactly like summing `Int` and `Double`s! This however -does not work with our `sumNumeric` - there is no (sane) way to define -a `Numeric[String]`. - -Another way to look at this is that we only use the `plus` method -on `Numeric`, never any of the other methods that also make sense -for numeric types. So while our function works for summing a List -of numeric types, it does not work for anything else that is not -numeric but can still be "added" (`String` and string concatenation, -`List[A]` and `List#++`). - -### Making it generic -So what do we want? We want a type class that only requires instances -to be able to "add" two `A`s to get another `A`. - -```scala -trait Addable[A] { - def plus(x: A, y: A): A -} -``` - -And let's define an instance of `Addable` for all `Numeric` types and `String`. - -```scala -object Addable { - implicit def numericIsAddable[A](implicit A: Numeric[A]): Addable[A] = - new Addable[A] { - def plus(x: A, y: A): A = A.plus(x, y) - } - - implicit val stringIsAddable: Addable[String] = - new Addable[String] { - def plus(x: String, y: String): String = x + y - } -} -``` - -And here's our shiny new generic summer function! - -```scala -def sumGeneric[A](l: List[A])(implicit A: Addable[A]): A = - l.reduce(A.plus) -``` - -And now this works for `Int`, `Double`, `String`, and many more. - -A good exercise at this point is to define an `Addable` instance for `List[A]`. - -### Making an Exception -What happens when we pass in an empty `List` to our summer function though? -We get an exception! How do we prevent this? A common answer I get is -"Oh I know it won't happen" – this is not ideal, we want to guarantee safety -as much as possible without having to rely on human judgement. - -How then do we write a safer summer function? Lets turn to an alternative -way of implementing sum on `List[Int]`. - -```scala -// Old, bad version -def sum(l: List[Int]): Int = l.reduce(_ + _) - -// Shiny, new version -def sum(l: List[Int]): Int = l.foldLeft(0)(_ + _) -``` - -What happens now when we pass an empty `List` into the sum function? We get 0, -not an exception! Note that before all we gave the program was a binary -operation (what `Addable` defines), where now we give a binary option *and* a -"zero" or starting value (the 0). As it stands, we cannot write this with -`Addable` since it has no "zero". - -It may be tempting to just add a `zero` method to `Addable`, but then we may run -into the same issues we had with `Numeric` later on – we don't *always* need -a "zero", sometimes a binary operation is good enough. So instead, let's create -an `AddableWithZero` type class. - -```scala -trait AddableWithZero[A] extends Addable[A] { - def zero: A -} -``` - -Note that while you dont see the `plus` method in here, the fact -it `extends Addable` without implementing the `plus` method propagates the need to -implement that method, so programmers who want to create an `AddableWithZero[A]` instance -need to implement both. - -Programmers can now write functions that depend only on `Addable`, or perhaps if they -need a bit more power use `AddableWithZero`. Types that have `AddableWithZero` instances -also have `Addable` instances automatically due to subtyping. - -Lets move our `Addable` instances to the `AddableWithZero` object. - -```scala -object AddableWithZero { - implicit def numericIsAddableZero[A](implicit A: Numeric[A]): AddableWithZero[A] = - new AddableWithZero[A] { - def plus(x: A, y: A): A = A.plus(x, y) - def zero: A = A.zero - } - - implicit val stringIsAddableZero: AddableWithZero[String] = - new AddableWithZero[String] { - def plus(x: String, y: String): String = x + y - def zero: String = "" - } -} -``` - -And finally, our shiny new generic sum function! - -```scala -def sumGeneric[A](l: List[A])(implicit A: AddableWithZero[A]): A = - l.foldLeft(A.zero)(A.plus) -``` - -Hurrah! - -### Plot Twist -It turns out that our `Addable` and `AddableWithZero` type classes is not just us being -sly and clever, but an actual thing! They are called `Semigroup` and -`Monoid` (respectively), taken from the wonderful field of abstract algebra. Abstract -algebra is a field dedicated to studying algebraic structures as opposed -to just numbers as we may be used to. The field looks into what properties -and operations various structures have in common, such as integers and -matrices. For instance, we can add two integers, as well as two matrices of the same size. -This is analogous to how we noticed the `plus` worked on not only `Numeric` -but `String` and `List[A]` as well! This is the kind of generecity we're looking for. - -Here's what `sumGeneric` looks like in Scalaz land. - -```scala -import scalaz.Monoid - -def sumGeneric[A](l: List[A])(implicit A: Monoid[A]): A = - l.foldLeft(A.zero)((x, y) => A.append(x, y)) -``` - -Thankfully we dont have to create our own versions of `Semigroup` and `Monoid` – -Scalaz has one for us! In fact, the developers of Scalaz have been kind enough to define -several `Monoid` instances for common types such as `Numeric`, `String`, `List[A]`, etc. -There are also instances for tuples – if we have a tuple, say of type `(A, B, C)`, -and all three types have `Monoid` instances themselves, then the whole tuple has an -instance where the `zero` is the tuple `(A.zero, B.zero, C.zero)` and the `plus` is -appending corresponding pairs between the two tuples. Look for instances that may already -be defined before defining your own on existing types. - -@:style(bulma-notification) - If you are interested in learning more about numeric programming, check out - the [spire](https://github.com/non/spire) library, as well as the - accompanying post about [generic numeric programming](generic-numeric-programming.md). -@:@ - - -### Law-Abiding Citizen -To close this post off, I confess one thing: defining a `Monoid` (and `Semigroup`) instance -should not be done without some thought. It is not enough that you simply have a zero and -a binary operation – to truly have a `Monoid` or `Semigroup` certain laws must be obeyed. -These laws are as follows: - -Call the `plus` operation @:math + @:@ and the `zero` value @:math 0 @:@. Arbitrary values of type `A` will be -referred to as @:math a @:@, @:math b @:@, etc. - -The `Semigroup` law requires @:math + @:@ to be associative. That is: - -@:math - (a + b) + c = a + (b + c) -@:@ - -In addition to the `Semigroup` law for the binary operation, the `Monoid` law relates -@:math + @:@ and @:math 0 @:@: - -@:math - (a + 0) = (0 + a) = a -@:@ - -To check these laws, Scalaz provides [ScalaCheck](https://github.com/scalaz/scalaz/tree/v7.0.4/scalacheck-binding) -bindings to help you, but that is a topic for another day. - -Note that a particular type can have several `Semigroup` or `Monoid`s that make sense. -For instance, `Int` has a `Monoid` on @:math() (+, 0) @:@ as well as on @:math() (*, 1) @:@. Convince yourself -(using the above laws) that this makes sense. - -This raises the question of how we get both @:math + @:@ and @:math * @:@ `Monoid`s for `Int` without -making `scalac` freak out about ambiguous implicit values. The answer is "tagged types", -again a topic for another day. - -## Getting Help - -If you have any questions/comments/concerns, feel free to hop onto the IRC channel on -Freenode at `#scalaz`. diff --git a/src/blog/towards-scalaz-2.md b/src/blog/towards-scalaz-2.md deleted file mode 100644 index e1008bcb..00000000 --- a/src/blog/towards-scalaz-2.md +++ /dev/null @@ -1,184 +0,0 @@ -{% - author: ${adelbertc} - date: "2013-12-15" - tags: [technical] -%} - -# Towards Scalaz (Part 2) - -A lot of people see Scalaz as a hard fringe, ivory tower, -not suited for real-world applications library, which is -unfortunate. The goal of this blog post series is to introduce -various components of Scalaz, and hopefully through this -allow folks to gain an understanding towards the power of -Scalaz. - -As a prerequisite, I assume knowledge of type classes as they -are implemented and used in Scala, higher kinded types, -and sum types (e.g. `Option/Some/None`, `Either/Left/Right`). - -For a tutorial/review on (higher) kinds, I recommend the following resources: - -* [Scala: Types of a higher kind](http://blogs.atlassian.com/2013/09/scala-types-of-a-higher-kind/) -* [Generics of a Higher Kind](http://adriaanm.github.io/files/higher.pdf) -* [SO: What is a higher kinded type in Scala?](http://stackoverflow.com/questions/6246719/what-is-a-higher-kinded-type-in-scala) - -## Part 2: Summations of a Higher Kind - -[Last time](towards-scalaz-1.md) we left off after -writing our own generic `sum` function: - -```scala -import scalaz.Monoid - -def sumGeneric[A](l: List[A])(implicit A: Monoid[A]): A = - l.foldLeft(A.zero)((x, y) => A.append(x, y)) -``` - -This allowed us to sum a list not only of numeric types like -`Int`, but also others that could be added and had a "zero" such as -`String` via string concatenation and the empty string, as well as -`List[A]` via list concatenation and the empty list. - -But, we can do better! Why limit ourselves to `List`? What if we want -to sum over a `Vector`, or even a tree? We *could* use `Seq` and that -would allow us to pass in `List` or `Vector`, but it still brings up -the problem of trees, and any other data structure that may not fit -the `Seq` bill. - -### What do we want? Folds! -Recall when we "came up with" `Semigroup` and `Monoid` last time - -what did we do? We simply looked at what operations we needed -(`add/append` and `zero`) and factored it out into a type class. -Let's try doing the same this time. - -So what are we doing with `List` in our implementation? Nothing much -really, we're just folding over it. If we think about it, we could -"fold" over say, a tree as well. Let's take this operation out into -a type class, and aptly name it `Foldable`. - -```scala -trait Foldable[F[_]] { - // Instead of requiring the contents to be monoidal, let's - // make it flexible by allowing a fold as long as we can convert - // the contents to a type that has a `Monoid`. - def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B -} -``` - -And let's implement instances of this type class for `List` and our own -`Tree`. - -Our tree definition: - -```scala -sealed trait Tree[A] -case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A] -case class Leaf[A]() extends Tree[A] -``` - -and our instances: - -```scala -object Foldable { - implicit val listIsFoldable: Foldable[List] = - new Foldable[List] { - def foldMap[A, B](fa: List[A])(f: A => B)(implicit B: Monoid[B]): B = - fa.foldLeft(B.zero)((acc, elem) => B.append(acc, f(elem))) - } - - implicit val treeIsFoldable: Foldable[Tree] = - new Foldable[Tree] { - def foldMap[A, B](fa: Tree[A])(f: A => B)(implicit B: Monoid[B]): B = - fa match { - case Leaf() => - B.zero - case Node(value, left, right) => - B.append(f(value), B.append(foldMap(left)(f), foldMap(right)(f))) - } - } -} -``` - -and finally, our new summing function: - -```scala -def sumGeneric[F[_], A](fa: F[A])(implicit F: Foldable[F], A: Monoid[A]): A = - fa.foldMap(identity) -``` - -### Scalaz to the rescue -As with last time, Scalaz defines the `Foldable` type class for us. However, -to really be "foldable", not only should you define `foldMap`, but `foldRight` -as well. Some of you may be wondering why `foldRight` and not `foldLeft`, or both? -The reasons for this decision are that - -* `foldLeft` can be defined in terms of `foldRight` (a fun exercise is to try this for yourself) -* `foldLeft` fails on infinite lists (think `Stream` in Scala) - -That being said, Scalaz defines instances of `Foldable` for many of the standard -Scala types (`List`, `Vector`, `Stream`, `Option`), as well as its own (`Tree`, `EphemeralStream`). -The methods available on the type class not only include `foldMap` and `foldRight` which -are required to be implemented, but several derived ones as well including `fold` (`foldMap` with -`identity`), `foldLeft`, `toList/IndexedSeq/Stream`, among others. - -So our code with `scalaz.Foldable` now looks like: - -```scala -import scalaz.{ Foldable, Monoid } - -// Note that this is equivalent to scalaz.Foldable#fold -def sumGeneric[F[_], A](fa: F[A])(implicit F: Foldable[F], A: Monoid[A]): A = - fa.fold -``` - -Note that the implementation of the function is rather plain, but that's a good thing! -This shows the level of genericity type classes, folds, and Scalaz is capable of. If you ever -find yourself needing to fold something down, look at the methods available on -`scalaz.Foldable`. By simply adding an instance of `Foldable` to your `F[_]` by implementing -the two methods above, you get "for free" a bunch of -[derived ones](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/#scalaz.Foldable)! - -### An Aside: Taming the Elephant -In recent days, the word "Hadoop" has become synonymous with "big data." The MapReduce -system made popular by [Google](http://research.google.com/archive/mapreduce.html) -has made it's way into several companies looking to glean information from their data. - -Why am I mentioning this in a typelevel.scala blog post? Well, think about the reduce phase – -what is really happening? For a particular key, we're given a list of values emitted -for that key, and we want to reduce those values into a single value. Sound familiar? -Sounds a bit like `fold`, doesn't it? Note that not all reductions in MapReduce have to follow -monoid laws, but a surprising amount do as demonstrated by Twitter's -[Algebird](https://github.com/twitter/algebird) project. - -Going back to `fold`, recall that in order to just `fold` we need to have something -`Foldable` that contains something that already has a `Monoid` instance. A more general -approach, as taken by `scalaz.Foldable`, is to also provide a `foldMap` function which -lets us also pass in a function *map*ping each element of the `Foldable` to something -that is a `Monoid`, and reduce over that instead. - -So. Given something, say a `List[A]`, we want to *Map* each element of the list to -an element of a type that has a `Monoid` instance, and then we want to *Reduce* the -list down to a single value. What is this? All together now: MapReduce! - -Unfortunately, Hadoop MapReduce by itself does not give you anything like a `List`. -Fortunately, our good friends at [NICTA](http://www.nicta.com.au/) have developed -and open sourced the wonderful [Scoobi](https://github.com/nicta/scoobi) project, -which abstracts over Hadoop MapReduce by providing a `List`-like interface, called a -`DList` (distributed list). Users treat the `DList` very similarly to how they would -a regular Scala `List`, and perform operations on it that get compiled down into -MapReduce jobs. Such operations include not only the familiar (and expected) `map` -and `reduce` combinators, but also our friends `foldMap` and `fold`. While `DList`'s -do not have a proper `Foldable` instance due to the difficulty of implementing `foldRight` -for the MapReduce, I find it to be a great example of the power of abstractions and -genericity abstract algebra and Scalaz provides to us as programmers. - -## Further Reading - -* [List Folds at BFPG](http://tmorris.net/posts/list-folds-bfpg/index.html) -* [A tutorial on the universality and expressiveness of folds](http://www.cs.nott.ac.uk/~gmh/fold.pdf) - -## Getting Help - -If you have any questions/comments/concerns, feel free to hop onto the IRC channel on -Freenode at `#scalaz`. diff --git a/src/blog/treelog.md b/src/blog/treelog.md deleted file mode 100644 index a611a61e..00000000 --- a/src/blog/treelog.md +++ /dev/null @@ -1,193 +0,0 @@ -{% - author: ${channingwalton} - date: "2013-10-18" - tags: [technical] -%} - -# Treelog - -[Lance Walton's](https://twitter.com/lancewalton) [Treelog](https://github.com/lancewalton/treelog) is the result of a real problem that arose in a trading system that we were working on: -> How can everything that happens to a trade be audited? - -The first (and tedious) answer is copius logging by writing to some kind of audit data type or simple logger. - -There are a number of problems with this approach: - -- writing logging around computations often complicates the code as values must be extracted, recorded and then applied -- separating logic from the computation can lead to a mismatch between the log and the computation -- linear logs are very difficult to follow -- its not easy to control how much of a linear log to show a user if you do not know what is detail and what isn't - -Treelog resolves these issues by making the log itself a tree, reflecting the computational tree it logs, and uses techniques described in the [Typeclassopedia](http://www.haskell.org/wikiupload/e/e9/Typeclassopedia.pdf) to bring logging closer to the computation: the `Writer` Monad, a Monad Transformer, and a cunning Monoid. - -Note that this post is a more technical description of how Treelog was written. For a quick introduction of use please refer to the [README]. -I will also refer you to Eugene Yokota's [excellent Scalaz tutorial](http://eed3si9n.com/learning-scalaz/) to study the details of Scalaz where appropriate. - -Logging with Treelog --------------------- -Here is an example which illustrates how Treelog is used ([more examples](https://github.com/lancewalton/treelog#treelog-examples)): - -```scala -val simple: DescribedComputation[Int] = - "Calculating sum" ~< { - for { - x ← 11 ~> ("x = " + _) - y ← 2 ~> ("y = " + _) - sum ← (x + y) ~> ("Sum is " + _) - } yield sum -``` - -`DescribedComputation[Value]` is just a type alias for `EitherT[LogTreeWriter, String, Value]`. `EitherT`, a [Monad Transformer](http://eed3si9n.com/learning-scalaz/Monad+transformers.html), enables success and failure to be represented and will be covered below. - -The log and value can be retrieved with `result.run.written` and `result.run.value` respectively. The written tree will look like this: - -``` -Calculating sum - x = 11 - y = 2 - Sum is 13 -``` - -and the value will be `\/-(13)`, which is Scalaz's version of `Right`. - -Tree Nodes ----------- - -The nodes of the tree contain a `LogTreeLabel`: - -```scala -sealed trait LogTreeLabel[A] { - def success: Boolean - def annotations: Set[A] - // ... -} - -case class DescribedLogTreeLabel[A](description: String, success: Boolean, annotations: Set[A]) extends LogTreeLabel[A] - -case class UndescribedLogTreeLabel[A](success: Boolean, annotations: Set[A]) extends LogTreeLabel[A] -``` - -The node is able to represent success or failure, may have a description, and a set of annotations. Annotations allow extra information to be carried in a Node which may be useful when working with the audit later. For our trading system that was other trades that were affected by the process as a side effect of processing a trade. - -Treelog distinguishes between tree nodes that describe a computation, a `DescribedLogTreeLabel`, and an `UndescribedLogTreeLabel` which is a Tree with no description in the root. In the example above, the root node is a `DescribedLogTreeLabel` containing the text `Calculating sum`. This is an important distinction that informs the way trees must be combined by our Treelog monoid, which the `Writer` needs (see below). - -Note that originally, `LogTreeLabel` was a case class defined like this: - -```scala -case class LogTreeLabel(description: Option[String], success: Boolean, annotations: Set[Annotation]) -``` - -This meant that `DescribedLogTreeLabel` and `UndescribedLogTreeLabel` were not necessary because the optional `description` carried the equivalent information. However, we found the formulation above easier to work with. - -Syntactic Sugar ---------------- - -Treelog makes use of some syntactic sugar inspired by [Tony Morris's post](http://blog.tmorris.net/posts/the-writer-monad-using-scala-example/) on `Writer`. In the example above, `~>` is a method on an implicitly constructed class which takes any value `x: T` and returns a `DescribedComputation[T]`, representing the value `x` and a leaf node containing the description. - -There is special support for `Boolean`s, `Option`s, `Either`s and `Traversable`s which you can learn about from the Treelog [README]. - -`Writer` and Monoid -------------------- - -> Writer allows us to do computations while making sure that all the log values are combined into one log value that then gets attached to the result. – [LYAH](http://learnyouahaskell.com/for-a-few-monads-more) - -`Writer`s allow us to write a log embedded within a computation. - -Here is a simple example using Scalaz, see the references for more detailed examples. - -```scala -val r: Writer[String, Int] = - for { - a ← 3.set("Got a 3.") - b ← 5.set("Got a 5.") - } yield a * b - -println(r.written) // Got a 3.Got a 5. -println(r.value) // 15 -``` - -The `Writer` uses a monoid for the written value (a `String` in this case) to combine the logs (concatenation for `String`s). For `List`s it is `:::` and `Nil`, etc. - -Treelog's Monoid ----------------- - -Treelog uses a Scalaz [Writer](http://eed3si9n.com/learning-scalaz/Writer.html), [Tree](http://eed3si9n.com/learning-scalaz/Tree.html) and a custom [Monoid](http://eed3si9n.com/learning-scalaz/Monoid.html) implementation to record logs. - -The monoid has to provide two things: a `zero` value, and a binary operation that combines two trees in a meaningful way. The `zero` value for Treelog is just a constant used internally to the `Monoid´ implementation and never leaks out since there is always at least one value being logged. - -Combining trees is done as follows: - -- a `zero` tree with a tree is just the tree -- two undescribed trees become a new undescribed tree with the children of the right tree appended to the children of the left tree -- an undescribed tree `T1`, and a described tree, `T2`, becomes an undescribed tree with *`T2` appended to the children of `T1`* -- a described tree, `T1`, and an undescribed tree, `T2`, is an undescribed tree with *`T1` prepended to the children of `T2`* -- two described trees are combined by creating an undescribed tree with the two trees as children - -Note that the result is always an undescribed tree since there is no meaningful way to combine descriptions of child nodes. In the example above the tree contains two leaves: "Got a 3" and "Got a 5". Concatenating those descriptions isn't as meaningful as "Summing a and b", which could be done like this: - -```scala -val r: Writer[String, Int] = - "Summing a and b" ~< for { - a ← 3.set("Got a 3.") - b ← 5.set("Got a 5.") - } yield a * b -``` - -The [quadratic roots](https://github.com/lancewalton/treelog/blob/5e36e0652b575d0102f45b1c284f68a02f148b04/src/test/scala/QuadraticRootsExample.scala) example is a good one to see this. - -Success and Failure – `EitherT` -------------------------------- - -The purpose of Treelog is to audit a computation, return the log and result, and indicate whether the computation was successful or not. The Writer with the Monoid described above satisfies the first two requirements, but not the third. To add success and failure, the writer needs to be combined with `Either`. What we need is a Monad Transformer. - -> Monad Transformers are special types that allow us to roll two monads into a single one that shares the behaviour of both. – [Haskell Wikibook](http://en.wikibooks.org/wiki/Haskell/Monad_transformers) - -`EitherT` is a monad transformer that combines some monad with `Either`, which is exactly what is needed. It is constructed with three types: `EitherT[M, A, B]` where `M` is the monad, `A` is the failure type and `B` is the success type. In Treelog, `M` is a `Writer`, `A` is a `String` and `B` is the type of the result. - -Logtree includes the methods `def failure[V](description: String): DescribedComputation[V]` and `def success[V](value: V, description: String): DescribedComputation[V]` to support failure and success. They ensure that the failure case is included in the tree and that the nodes in the tree now reflect that the computation has failed. - -Here is an example from Treelog: - -```scala -val foo: String \/ Int = 11.right[String] -val bar: String \/ Int = "fubar".left[Int] - -val leftEithers: DescribedComputation[Int] = - "Calculating left either sum" ~< { - for { - x ← foo ~>? ("x = " + _) - y ← bar ~>? ("y = " + _) - sum ← (x + y) ~> (v ⇒ "Sum is " + v) - } yield sum - } - -val leftEitherWriter: LogTreeWriter[String \/ Int] = leftEithers.run -println(leftEithers.run.written.shows) -``` - -To retrieve the underying value back from `EitherT`, we call `run` which returns the `Writer` containing Scalaz's version of `Either` (which is more useful than Scala's built-in `Either`). - -The written value is: - -``` -Failed: Calculating left either sum - x = 11 - Failed: fubar - -Failure: Calculating left either sum -``` - -So the written log indicates that the whole computation failed, and the result is `-\/`, the `Left` for a Scalaz `Either`, containing "Failure: Calculating left either sum". - -In Practice ------------ - -Treelog is being used in earnest in a trading system, and the results have been a resounding success. And the level of accurate detail the system is able to show users has been invaluable in reducing support questions, which is always welcome. - -Further Reading ---------------- - -- [Monad Transformers in Scala](http://debasishg.blogspot.co.uk/2011/07/monad-transformers-in-scala.html) -- [Monad Transformers in the Wild](http://www.slideshare.net/StackMob/monad-transformers-in-the-wild) - -[README]: https://github.com/lancewalton/treelog/blob/main/README.md diff --git a/src/blog/tuple-announcement.md b/src/blog/tuple-announcement.md deleted file mode 100644 index 497c4805..00000000 --- a/src/blog/tuple-announcement.md +++ /dev/null @@ -1,19 +0,0 @@ -{% - author: ${typelevel} - date: "2022-09-12" - tags: [governance] -%} - -# New Typelevel Tuple Team - -We are pleased to announce that we have set up a Typelevel team for [Tuple][tuple], a remote pair-programming application. -Tuple provides screen-sharing, audio and video calls (video is optional), as well as a very helpful screen drawing feature for whiteboarding. -Tuple clients are available for MacOS and Linux (beta). - -We hope that Tuple will help the community by enabling maintainers to collaborate together more easily, and as a new way to onboard and share knowledge with new contributors. - -There are [instructions in the Governance repo][tuple-docs] that cover how maintainers can join the Tuple team, and how others can get started. -Happy pairing! - -[tuple]: https://tuple.app/ -[tuple-docs]: https://github.com/typelevel/governance/blob/main/resources/tuple.md diff --git a/src/blog/type-equality-to-leibniz.md b/src/blog/type-equality-to-leibniz.md deleted file mode 100644 index 63c59ef0..00000000 --- a/src/blog/type-equality-to-leibniz.md +++ /dev/null @@ -1,190 +0,0 @@ -{% - author: ${S11001001} - date: "2014-07-02" - tags: [technical] -%} - -# A function from type equality to Leibniz - -The Scala standard library provides evidence of two types being equal -at the data level: a value of type -[`(A =:= B)`](http://www.scala-lang.org/api/2.11.1/scala/Predef$$$eq$colon$eq.html) -witnesses that `A` and `B` are the same type. Accordingly, it provides -an implicit conversion from `A` to `B`. So you can write `Int`-summing -functions on your generic foldable types. - -```scala -final case class XList[A](xs: List[A]) { - def sum(implicit ev: A =:= Int): Int = - xs.foldLeft(0)(_ + _) -} -``` - -That works because `ev` is inserted as an implicit conversion over -that lambda's second parameter. - -Fragility ---------- - -That's not really what we want, though. In particular, flipping `A` -and `Int` in the `ev` type declaration will break it: - -``` -….scala:5: overloaded method value + with alternatives: - (x: Int)Int - (x: Char)Int - (x: Short)Int - (x: Byte)Int - cannot be applied to (A) - xs.foldLeft(0)(_ + _) - ^ -``` - -That doesn't make sense, though. Type equality is symmetric: Scala -knows it goes both ways, so why is this finicky? - -Additionally, we apply the conversion for each `Int`. It is a logical -implication that, if `A` is `B`, then `List[A]` must be `List[B]` as -well. But we can't get that cheap, single conversion without a cast. - -Substitution ------------- - -Scalaz instead provides -[`Leibniz`](http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/scalaz/Leibniz.html), -a more perfect type equality. A simplified version follows, which we -will use for the remainder. - -```scala -sealed abstract class Leib[A, B] { - def subst[F[_]](fa: F[A]): F[B] -} -``` - -This reads “`Leib[A, B]` can replace `A` with `B` in **any** type -function”. That “any” is pretty important: it gives us both the -theorem that we want, and a tremendous consequent power that gives us -most of what we can get in Scala from value-level type equality, by -choosing the right `F` type parameter to `subst`. - -What could it be? ------------------ - -Following the Scalazzi rules, where no `null`, type testing or -casting, or `AnyRef`-defined functions are permitted, what might go in -the body of that function? Even if you know what `A` is, as a `Leib` -implementer, it's hidden behind the unknown `F`. Even if you know that -`B` is a supertype of `A`, you don't know that `F` is covariant, -[by scalac or otherwise](liskov-lifting.md). -Even if you know that `A` is `Int` and `B` is `Double`, what are you -going to do with that information? - -So there's only one thing this `Leib` could be, because you **do** -have an `F` of *something*. - -```scala -implicit def refl[A]: Leib[A, A] = new Leib[A, A] { - override def subst[F[_]](fa: F[A]): F[A] = fa -} -``` - -Every type is equal to itself. Every well-formed `Leib` instance -starts out this way, in this function. - -Recovery --------- - -So, it's great that *we* know the implication of the `subst` method's -generality. But that's not good enough; we had that with `=:=` -already. We want to write well-typed operations that represent all the -implications of the `Leib` type equality as *new* `Leib`s representing -*those* type equalities. - -First, let's solve the original problem, using infix type application -to show the similarity to `=:=`: - -```scala -def sum2(implicit ev: A Leib Int): Int = - ev.subst[List](xs).foldLeft(0)(_ + _) -``` - -There is no more implicit conversion, the result of `subst` is the same -object as the argument, and `[List]` would be inferred, but I have -merely specified it for clarity in this example. - -This doesn't compose, though. What if, having `subst`ed `Int` into -that `List` type, I now want to `subst` `List[A]` for `List[Int]` in -some type function? Specifically, what about a `Leib` that represents -that type equality? To handle that, we can `subst` into `Leib` itself! - -```scala -def lift[F[_], A, B](ab: Leib[A, B]): Leib[F[A], F[B]] = - ab.subst[Lambda[X => Leib[F[A], F[X]]]](Leib.refl[F[A]]) -``` - -Again, the final `[F[A]]` could be inferred. - -As an exercise, define the `symm` and `compose` operations, which -represent that `Leib` is symmetric and transitive as well. Hints: the -`symm` body is the same except for the type parameters given, and -`compose` doesn't use `refl`. - -```scala -def symm[A, B](ab: Leib[A, B]): Leib[B, A] -def compose[A, B, C](ab: Leib[A, B], bc: Leib[B, C]): Leib[A, C] -``` - -Leib power ----------- - -In Scalaz, `Leibniz` is already defined, and -[used in a few places](https://github.com/scalaz/scalaz/blob/v7.0.6/core/src/main/scala/scalaz/syntax/TraverseSyntax.scala#L22-L26). -Though their `subst` definitions are completely incompatible at the -scalac level, they have a weird equivalence due to the awesome power -of `subst`. - -```scala -import scalaz.Leibniz, Leibniz.=== - -def toScalaz[A, B](ab: A Leib B): A === B = - ab.subst[A === ?](Leibniz.refl) - -def toLeib[A, B](ab: A === B): A Leib B = - ab.subst[A Leib ?](Leib.refl) -``` - -…where `?` is to type-lambdas as `_` is to Scala lambdas, thanks to -[the Kind Projector plugin](https://github.com/non/kind-projector#kind-projector). - -And so it would be with any pair of `Leibniz` representations with such -`subst` methods that you might define. Unfortunately, `=:=` cannot -participate in this universe of isomorphisms; it lacks the `subst` -method that serves as the `Leibniz` certificate of authenticity. You can -get a `=:=` from a `Leibniz`, but not vice versa. - -Why would you want that weak sauce anyway? - -Looking up ----------- - -These are just the basics. Above: - -* The weakness of Scala's own `=:=`, -* the sole primitive `Leibniz` operator `subst`, -* how to logically derive other type equalities, -* the isomorphism between each `Leibniz` representation and all - others. - -[In the next part](higher-leibniz.md), we'll -look at: - -* Why it matters that `subst` always executes to use a type equality, -* the Haskell implementation, -* higher-kinded type equalities and their `Leibniz`es, -* why - [the `=:=` singleton trick](https://github.com/scala/scala/blob/v2.11.1/src/library/scala/Predef.scala#L399-L402) - is unsafe, -* simulating GADTs with `Leibniz` members of data constructors. - -*This article was tested with Scala 2.11.1, Scalaz 7.0.6, and Kind -Projector 0.5.2.* diff --git a/src/blog/type-members-parameters.md b/src/blog/type-members-parameters.md deleted file mode 100644 index fb9b7ad0..00000000 --- a/src/blog/type-members-parameters.md +++ /dev/null @@ -1,230 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-13" - tags: [technical] -%} - -# Type members are almost type parameters - -*This is the first of a series of articles on “Type Parameters and Type -Members”.* - -Type members like `Member` - -```scala -class Blah { - type Member -} -``` - -and parameters like `Param` - -```scala -class Blah2[Param] -``` - -have more similarities than differences. The choice of which to use -for a given situation is usually a matter of convenience. In brief, a -rule of thumb: **a type parameter is usually more convenient and -harder to screw up, but if you intend to use it existentially in most -cases, changing it to a member is probably better**. - -Here, and in later posts, we will discuss what on earth that means, -among other things. In this series of articles on *Type Parameters -and Type Members*, I want to tackle a variety of Scala types that look -very different, but are really talking about the same thing, or -almost. - -Two lists, all alike --------------------- - -To illustrate, let’s see two versions of -[the functional list](http://www.artima.com/pins1ed/working-with-lists.html). -Typically, it isn’t used existentially, so the usual choice of -parameter over member fits our rule of thumb above. It’s instructive -anyway, so let’s see it. - -```scala -sealed abstract class PList[T] -final case class PNil[T]() extends PList[T] -final case class PCons[T](head: T, tail: PList[T]) extends PList[T] - -sealed abstract class MList {self => - type T - def uncons: Option[MCons {type T = self.T}] -} -sealed abstract class MNil extends MList { - def uncons = None -} -sealed abstract class MCons extends MList {self => - val head: T - val tail: MList {type T = self.T} - def uncons = Some(self: MCons {type T = self.T}) -} -``` - -We’re not quite done; we’re missing a way to *make* `MNil`s and -`MCons`es, which `PNil` and `PCons` have already provided -for themselves, by virtue of being `case class`es. But it’s already -pretty clear that *a type parameter is a more straightforward way to -define this particular data type*. - -The instance creation takes just a bit more scaffolding for our -examples: - -```scala -def MNil[T0](): MNil {type T = T0} = - new MNil { - type T = T0 - } - -def MCons[T0](hd: T0, tl: MList {type T = T0}) - : MCons {type T = T0} = - new MCons { - type T = T0 - val head = hd - val tail = tl - } -``` - -Why all the `{type T = ...}`? ------------------------------ - -After all, isn’t the virtue of type members that we don’t have to pass -the type around everywhere? - -Let’s see what happens when we attempt to apply that theory. Suppose -we remove only one of the -[*refinement*s](http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#compound-types) -above, as these `{...}` rainclouds at the type level are called. -Let’s remove the one in `val tail`, so `class MCons` looks like this: - -```scala -sealed abstract class MCons extends MList {self => - val head: T - val tail: MList -} -``` - -Now let us put a couple members into the list, and add them together. - -```scala -scala> val nums = MCons(2, MCons(3, MNil())): MCons{type T = Int} -nums: tmtp.MCons{type T = Int} = tmtp.MList$$anon$2@3c649f69 - -scala> nums.head -res1: nums.T = 2 - -scala> res1 + res1 -res2: Int = 4 - -scala> nums.tail.uncons.map(_.head) -res3: Option[nums.tail.T] = Some(3) - -scala> res3.map(_ - res2) -:21: error: value - is not a member of nums.tail.T - res3.map(_ - res2) - ^ -``` - -When we took the refinement off of `tail`, we eliminated any evidence -about what its `type T` might be. We only know that *it must be some -type*. That’s what *existential* means. - -**In terms of type parameters, `MList` is like `PList[_]`, and `MList -{type T = Int}` is like `PList[Int]`.** For the former, we say that -the member, or parameter, is existential. - -When is existential OK? ------------------------ - -Despite the limitation implied by the error above, there *are* useful -functions that can be written on the existential version. Here’s one -of the simplest: - -```scala -def mlength(xs: MList): Int = - xs.uncons match { - case None => 0 - case Some(c) => 1 + mlength(c.tail) - } -``` - -For the type parameter equivalent, the parameter on the argument is -usually carried out or *lifted* to the function, like so: - -```scala -def plengthT[T](xs: PList[T]): Int = - xs match { - case PNil() => 0 - case PCons(_, t) => 1 + plengthT(t) - } -``` - -By the conversion rules above, though, we should be able to write an -existential equivalent of `mlength` for `PList`, and indeed we can: - -```scala -def plengthE(xs: PList[_]): Int = - xs match { - case PNil() => 0 - case PCons(_, t) => 1 + plengthE(t) - } -``` - -There’s another simple rule we can follow when determining whether we -can rewrite in an existential manner. - -1. When a type parameter appears only in one argument, and -2. appears nowhere in the result type, - -we should always, ideally, be able to write the function in an -existential manner. (We will discuss why it’s only “ideally” in -[the next article](method-equiv.md).) - -You can demonstrate this to yourself by having the parameterized -variant (e.g. `plengthT`) call the existential variant -(e.g. `plengthE`), and, voilà, it compiles, so it must be right. - -This hints at what is usually, though not always, **an advantage for -type parameters: you have to ask for an existential, rather than -silently getting one just because you forgot a refinement**. We will -discuss -[what happens when you forget one in a later post](forget-refinement-aux.md). - -Equivalence as a learning tool ------------------------------- - -Scala is large enough that very few understand all of it. Moreover, -there are many aspects of it that are poorly understood in general. - -So why focus on how different features are similar? When we -understand one area of Scala well, but another one poorly, we can form -sensible ideas about the latter by drawing analogies with the former. -This is how we solve problems with computers in general: we create an -informal model in our heads, which we translate to a -mathematical statement that a program can interpret, and it gives back -a result that we can translate back to our informal model. - -My guess is that type parameters are much better understood than type -members, but that existentials via type members are better understood -than existentials introduced by `_` or `forSome`, though I’d wager -that neither form of existential is particularly well understood. - -By knowing about equivalences and being able to discover more, you -have a powerful tool for understanding unfamiliar aspects of Scala: -just translate the problem back to what you know and think about what -it means there, because the conclusion will still hold when you -translate it forward. (Category theorists, eat your hearts out.) - -In this vein, we will next generalize the above rule about existential -methods, discovering a simple tool for determining whether two -method types *in general* are equivalent, whereby things you know -about one easily carry over to the other. We will also explore -methods that *cannot* be written in the existential style, at least -under Scala’s restrictions. - -That all happens in -[the next part, “When are two methods alike?”](method-equiv.md). - -*This article was tested with Scala 2.11.7.* diff --git a/src/blog/type-projection.md b/src/blog/type-projection.md deleted file mode 100644 index 540ced8a..00000000 --- a/src/blog/type-projection.md +++ /dev/null @@ -1,165 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-23" - tags: [technical] -%} - -# Type projection isn't that specific - -*This is the fourth of a series of articles on “Type Parameters and -Type Members”. If you haven’t yet, you should -[start at the beginning](type-members-parameters.md), -which introduces code we refer to throughout this article without -further ado.* - -In the absence of the `Aux` trick presented at the end of -[the previous article](forget-refinement-aux.md), -the continuous use of structural refinement to accomplish basic tasks -admittedly imposes a high cognitive load. That is to say, it’s a lot -of work to say something that ought to be very simple. - -Some people go looking for a solution, and find something that almost -seems to make sense: -[type projection](http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#type-projection), -or `MList#T` in terms of -[our ongoing example](type-members-parameters.md#two-lists-all-alike). -But **type projection is, in almost all cases, too vague to really -solve problems you have using type members**. - -A good reason to use type members ---------------------------------- - -Let’s see a simple example. Here’s a sort of “value emitter”, that -operates in the space of some state, emitting a new value with each -step. - -```scala -sealed abstract class StSource[A] { - type S - def init: S // create the initial state - def emit(s: S): (A, S) // emit a value, and update state -} - -object StSource { - type Aux[A, S0] = StSource[A] {type S = S0} - - def apply[A, S0](i: S0)(f: S0 => (A, S0)): Aux[A, S0] = - new StSource[A] { - type S = S0 - def init = i - def emit(s: S0) = f(s) - } -} -``` - -Unlike `MList`, there are actually good reasons to use type members -for the “state” in this sort of type definition; i.e. there are -reasonable designs in which you want to use member `S` existentially. -Thus, depending on how we intend to use it, it seems to meet our first -rule of thumb about when to use type members, as described in -[the first article of this series](type-members-parameters.md#when-is-existential-ok). - -A failed attempt at simplified emitting ---------------------------------------- - -So, under this theory, you’ve got some values of type `StSource[A]` -lying around. And you want a simple function to take a source and its -state, and return the “next” value and the new state. - -```scala -def runStSource[A](ss: StSource[A], s: ??): (A, ??) = ss.emit(s) -``` - -But what do you put where the `??` is? The surprising guess is often -`StSource[A]#S`. After all, it means “the `StSource`’s `S`”, and -we’re trying to talk about an `StSource`’s `S`, right? - -```scala -def runStSource[A](ss: StSource[A], s: StSource[A]#S) - : (A, StSource[A]#S) = ss.emit(s) - -TmTp4.scala:22: type mismatch; - found : s.type (with underlying type tmtp4.StSource[A]#S) - required: ss.S - : (A, StSource[A]#S) = ss.emit(s) - ^ -``` - -Setting aside that it won’t compile with the above signature—the usual -outcome of experiments with type projection, that the types aren’t -strong enough to be workable without cheating by casting—the reality -*sounds* so close to the above that it is understandable that type -projection is often confused with something useful. - -@:style(bulma-notification) - There *are* uses for type projection. But they are so rare, so - exotic (they look - [like this](https://github.com/scalaz/scalaz/blob/bdd6d5653313b10af08efdc6884cbbefe41051a2/core/src/main/scala/scalaz/Unapply.scala#L404-L409)), - and even the legitimate ones better off rewritten to avoid them, - that the safer assumption is that you’ve gone down the wrong path if - you’re trying to use them at all. My suggestion can usually be - phrased something like “move it to a companion object”. -@:@ - -In reality, `StSource[A]#S` means *some* `StSource`’s `S`. Not the -one you gave, just any particular one. It’s the supertype of all -possible `S` choices. So, the failure of the above signature is like -the failure of `mdropFirstE` from -[the second post of this series](method-equiv.md#when-are-two-methods-less-alike): -a failure to relate types strongly enough. The problem with -`mdropFirstE` was failure to relate the result type to argument type, -whereas the problem with `runStSource` is to fail to relate the two -arguments’ types to each other. - -Type parameters see existentially ---------------------------------- - -As with `mdropFirstE`, one correct solution here is, again, lifting the -member to a method type parameter. - -```scala -def runStSource[A, S](ss: StSource.Aux[A, S], s: S): (A, S) = ss.emit(s) -``` - -The surprising feature of this sort of signature is that it can be -invoked on `ss` arguments of type `StSource[A]`. - -``` -scala> val ss: StSource[Int] = StSource(0){i: Int => (i, i)} -ss: tmtp4.StSource[Int] = tmtp4.StSource$$anon$1@300b5011 - -scala> runStSource(ss, ss.init) -res0: (Int, ss.S) = (0,0) -``` - -In other words, **methods can assign names to unspecified, existential -type members**. So even though we have a value whose type doesn’t -refine `S`, Scala still infers this type as the `S` argument to pass -to `runStSource`. - -By analogy with type parameters, though, this isn’t too surprising. -[We’ve already seen](method-equiv.md) -that `copyToZeroE` inferred its argument’s existential parameter to -pass along to the named parameter to `copyToZeroP`, in the second part -of this series. We even saw it apply directly to type members when -`mdropFirstE` was able to invoke `mdropFirstT`. However, for whatever -reason, we’re used to existential parameters being able to do this; -even Java manages the task. But it just seems *odder* that merely -calling a method can create a whole refinement `{...}` raincloud, from -scratch, filling in the blanks with sensible types along the way. - -It’s completely sound, though. An `StSource` (that exists as a value) -*must* have an `S`, even if we existentialized it away. So, as with -`_`s, let’s just give it a name to pass as the inferred type -parameter. It makes a whole lot more sense than supposing -`StSource[A]#S` will just do what I mean. - -In a future post, we’ll use this “infer the whole refinement” feature -to demonstrate that some of the most magical-seeming Scala type system -features aren’t really so magical. But before we get to that, we need -to see just why existentials are anything but “wildcards”, and why it -doesn’t *always* make sense to be able to lift existentials like `S` -to type parameters. That’s coming in -[the next post, “Nested existentials”](nested-existentials.md). - -*This article was tested with Scala 2.11.7.* diff --git a/src/blog/typedapi.md b/src/blog/typedapi.md deleted file mode 100644 index 1f1630fa..00000000 --- a/src/blog/typedapi.md +++ /dev/null @@ -1,352 +0,0 @@ -{% - author: ${pheymann} - date: "2018-06-15" - tags: [technical] -%} - -# Typedapi or how to derive your clients and servers from types - -In this blog post, I will show you how to leverage Scala's type system to derive an HTTP client function from a single type. This will also be the story of how I started to work on [Typedapi](https://github.com/pheymann/typedapi) which is basically the attempt to bring Haskell's [Servant](https://github.com/haskell-servant/servant) to Scala. - -## Servant in a nutshell and how it began -For everyone not knowing Servant, it is a library which lets you define your web apis as types and derives the client and server functions from it. When I saw it for the first time while working on a pet project I immediately loved the idea. Creating web server and clients this way reduces your code to a mere type, you get extra type safety and you can use the api types as contracts between your server and its clients. - -I couldn't find any viable alternative in Scala at the time and decided to build it on my own. But I just wanted to start with a single feature to not overwhelm myself and abandon the project after a short time. Therefore, I set out to make Scala able to derive a client function from a single api type, as we will do in this post. - -## Derive a client function from a type. How hard can it be? -Let's start with an example we will use later on to ease understanding. Consider the following api: - -``` -GET /users/:name?minAge=:age -> List[User] -``` - -It only consists of a single endpoint which returns a list of `Users`: - -```scala -final case class User(name: String, age: Int) -``` - -with a given `name: String`. Furthermore, you filter the resulting users by their `age: Int`. Our big goal is to end up with a function which is derived from a type-level representation of our endpoint: - -```scala -(name: String, minAge: Int) => F[List[User]] -``` - -### Represent the api as a type -Question: how do you represent the above api as a type in Scala? I think the best way is to break it apart and try to find type-level representations for each element. After that, we "just" merge them together. - -When we take a closer look at our endpoint we see that it consists of: - * a method `GET` to identify which kind of operation we want to do and which also describes the expected return type - * constant path elements identifying an endpoint: `/users` - * dynamic path elements called "segments" which represent input parameters with a name and type: `:name` - * queries which again represent input parameters with a name and type: `minAge=[age]` - -Or in other words, just a plain HTTP definition of a web endpoint. Now that we know what we are working with let's try and find a type-level representation. - -But how do you transform a value-level information as a type? First of all, the value has to be known at compile time which leaves us with literals. If we would work with Dotty we could leverage a concept called literal type: - -```scala -type Path = "users" -``` - -But since we want to stay in Vanilla Scala this will not work. We have to take another route by using a tool probably every developer has to use when it comes to working on the type-level called [shapeless](https://github.com/milessabin/shapeless). It has this nifty class [Witness](https://github.com/milessabin/shapeless/blob/shapeless-2.3.3/core/src/main/scala/shapeless/singletons.scala#L32) which comes with an abstract type `T`. And `T` is exactly what we need here as it transforms our literals into types. - -```scala -import shapeless.Witness - -val usersW = Witness("users") -``` - -But this isn't a pure type declaration, you will say. And you are right, but right now there is no other way in Scala. We have to go the ordinary value road first to create our types. - -Now that we know how to get a type representation from a `String` which describes our path we should clearly mark it as a path element: - -```scala -sealed trait Path[P] - -type users = Path[usersW.T] -``` - -That's it. That is the basic concept of how we can describe our apis as types. We just reuse this concept now for the remaining elements like the segment. - - -```scala -val nameW = Witness('name) - -sealed trait Segment[K, V] - -type name = Segment[nameW.T, String] -``` - -Do you see how we included the segment's identifier in the type? This way we are not only gain information about the expected type but also what kind of value we want to see. By the way, I decided to use `Symbols` as identifiers, but you could also switch to `String` literals. The remaining definitions look pretty similar: - -```scala -val minAgeW = Witness('minAge) - -sealed trait Query[K, V] - -type minAge = Query[minAgeW.T, Int] - -sealed trait Method -sealed trait Get[A] extends Method -``` - -Here, `A` in `Get[A]` represents the expected result type of our api endpoint. - -Now that we know how to obtain the types of our api elements we have to put them together into a single type representation. After looking through shapeless's features we will find `HLists`, a list structure which can store elements of different types. - -```scala -import shapeless.{::, HNil} - -type Api = Get[List[User]] :: users :: name :: minAge :: HNil -``` - -Here you go. `Api` is an exact representation of the endpoint we defined at the beginning. But you don't want to write `Witness` and `HLists` all the time so let's wrap it up into a convenient function call: - -```scala -def api[M <: Method, P <: HList, Q <: HList, Api <: HList] - (method: M, path: PathList[P], queries: QueryList[Q]) - (implicit prepQP: Prepend.Aux[Q, P, Api]): ApiTypeCarrier[M :: Api] = ApiTypeCarrier() - -val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge))) -``` - -Not clear what is happening? Let's take a look at the different elements of `def api(...)`: - * `method` should be obvious. It takes some method type. - * `PathList` is a type carrier with a function `def /(...)` to concatenate path elements and segments. In the end, `PathList` only stores the type of an `HList` and nothing more. - -```scala -final case class PathList[P <: HList]() { - - def /[S](path: Witness.Lt[S]): PathList[S :: P] = PathList() - ... -} - -val Root = PathList[HNil]() -``` - * Same is true for `QueryList`. - * The last step is to merge all these `HLists` types into a single one. Shapeless comes again with a handy type class called `Prepend` which provides us with the necessary functionality. Two `HList` types go in, a single type comes out. And again, we use a type carrier here to store the api type. - -Whoho, we did it. One thing we can mark as done on our todo list. Next step is to derive an actual client function from it. - -### Clients from types -So far we have a type carrier describing our api as type: - -```scala -ApiTypeCarrier[Get[List[User]] :: Query[minAgeW.T, Int] :: Segment[nameW.T, String] :: usersW.T :: HNil] -``` - -Now we want to transform that into a function call `(name: String, minAge: Int) => F[List[User]]`. So what we need is the following: - * the types of our expected input - * the output type - * the path to the endpoint we want to call - -All information are available but mixed up and we need to separate them. Usually, when we work with collections and want to change their shape we do a `fold` and alas shapeless has type classes to fold left and right over an `HList`. But we only have a type. How do we fold that? - -#### Type-level FoldLeft -What we want is to go from `Api <: HList` to `(El <: HList, KIn <: HList, VIn <: HList, M, Out)` with: - * `El` al the elements in our api: `"users".type :: SegmentInput :: QueryInput :: GetCall :: HNil` - * `KIn` the input key types: `nameW.T :: minAgeW.T :: HNil` - * `VIn` the input value types: `String :: Int :: HNil` - * the method type: `GetCall` - * and `Out`: `List[User]` - -Here, we introduced new types `SegmentInput` and `QueryInput` which act as placeholders and indicate that our api has the following inputs. This representation will come in handy when we construct our function. - -Now, how to fold on the type-level? The first step, we have to define a function which describes how to aggregate two types: - -```scala -trait FoldLeftFunction[In, Agg] { type Out } -``` - -That's it. We say what goes in and what comes out. You need some examples to get a better idea? Here you go: - -```scala -implicit def pathTransformer[P, El <: HList, KIn <: HList, VIn <: HList, M, Out] = - FoldLeftFunction[Path[P], (El, KIn, VIn, M, Out)] { type Out = (P :: El, KIn, VIn, Out) } -``` - -We expect a `Path[P]` and intermediate aggregation state `(El, KIn, VIn, M, Out)`. We merge the two by adding `P` to our list of api elements. The same technique is also used for more involved aggregations: - -```scala -implicit def segmentTransformer[K <: Symbol, V, El <: HList, KIn <: HList, VIn <: HList, M, Out] = - FoldLeftFunction[Segment[K, V], (El, KIn, VIn, M, Out)] { type Out = (SegmentInput :: El, K :: KIn, V :: VIn, Out) } -``` - -Here, we get some `Segment` with a name `K` and a type `V` and an intermediate aggregation state we will update by adding a placeholder to `El`, the name to `KIn` and the value type to `VIn`. - -Now that we can aggregate types we need a vehicle to traverse our `HList` type and transform it on the fly by using our `FoldLeftFunction` instances. I think yet another type class can help us here. - -```scala -trait TypeLevelFoldLeft[H <: HList, Agg] { type Out } - -object TypeLevelFoldLeft { - - implicit def returnCase[Agg] = new TypeLevelFoldLeft[HNil, Agg] { - type Out = Agg - } - - implicit def foldCase[H, T <: HList, Agg, FfOut, FOut](implicit f: FoldLeftFunction.Aux[H, Agg, FfOut], - next: Lazy[TypeLevelFoldLeft.Aux[T, FfOut, FOut]]) = - new TypeLevelFoldLeft[H :: T, Agg] { type Out = FOut } -} -``` - -The above definition describes a recursive function which will apply the `FoldLeftFunction` on `H` and the current aggregated type `Agg` and continues with the resulting `FfOut` and the remaining list. And before you bang your head against the wall for hours until the clock strikes 3 am, like I did, a small hint, make `next` lazy. Otherwise, Scala is not able to find `next`. My guess is that Scala is not able to infer `next`, because it depends on `FfOut` which is also unknown. So we have to defer `next`'s inference to give the compiler some time to work. - -And another hint, you can start with `Unit` as the initial type for your aggregate. - -#### Collect all the request data -We folded our api type into the new representation making it easier now to derive a function which collects all the data necessary to make a request. - -```scala -// path to our endpoint described by Path and Segment -type Uri = List[String] - -// queries described by Query -type Queries = Map[String, List[String]] - -VIn => (Uri, Queries) -``` - -This function will form the basis of our client function we try to build. It generates the `Uri` and a `Map` of `Queries` which will be used later on to do a request using some HTTP library. - -By now, you should be already comfortable with type classes. Therefore, it shouldn't shock you that I will introduce yet another one to derive the above function. - -```scala -trait RequestDataBuilder[El <: HList, KIn <: HList, VIn <: HList] { - - def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries) -} -``` - -Instances of this type class update `uri` and `queries` depending on the types they see. For example, if the current head of `El` is a path element we prepend its `String` literal to `uri`. Just keep in mind to reverse the `List` before returning it. - -```scala -implicit def pathBuilder[P, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[P], next: RequestDataBuilder[T, KIn, VIn]) = - new RequestDataBuilder[P :: T, KIn, VIn] { - def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries) = - next(inputs, wit.value.toString() :: uri, queries, headers) - } -``` - -Or if we encounter a query input we derive the key's type-literal, pair it with the given input value and add both to `queries`: - -```scala -implicit def queryBuilder[K <: Symbol, V, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[K], next: RequestDataBuilder[T, KIn, VIn]) = - new RequestDataBuilder[QueryInput :: T, K :: KIn, V :: VIn] { - def apply(inputs: V :: VIn, uri: Uri, queries: Queries): (Uri, Queries) = - next(inputs.tail, uri, Map(wit.value.name -> List(inputs.head.toString())) ++ queries) - } -``` - -The other cases are looking quite similar and it is up to the interested reader to find the implementations. - -What we end up with is a nested function call structure which will take an `HList` and returns the `uri` and `queries`. - -```scala -val builder = implicitly[RequestDataBuilder[El, KIn, VIn]] - -val f: VIn => (Uri, Queries) = input => builder(input, Nil, Map.empty) - -"joe" :: 42 :: HNil => (List("users", "joe"), Map("minAge" -> List("42"))) -``` - -Here, `"joe"` and `42` are our expected inputs (`VIn`) which we derived from the segments and queries of our `Api`. - -#### Make the request -We have all the data we need to make an IO request but nothing to execute it. We change that now. By adding an HTTP backend. But we don't want to expose this implementation detail through our code. What we want is a generic description of a request action and that sounds again like a job for type classes. - -```scala -trait ApiRequest[M, F[_], C, Out] { - - def apply(data: (Uri, Queries), client: C): F[Out] -} -``` - -We have to specialize that for the set of methods we have: - -```scala -trait GetRequest[C, F[_], Out] extends ApiRequest[GetCall, C, F, Out] - -... - -val request = implicitly[ApiRequest[GetCall, IO, C, List[User]]] - -val f: VIn => IO[List[User]] = - input => request(builder(input, Nil, Map.empty), c) -``` - -Let's say we want http4s as our backend. Then we just have to implement these `traits` using http4s functionality. - -#### Make it a whole -We have a bunch of type classes which in theory do a request, but so far they are completely useless. To make a working piece of code out of it we have to connect them. - -```scala -def derive[Api <: HList, El <: HList, KIn <: HList, VIn <: HList, M, Out, F[_], C] - (api: ApiTypeCarrier[Api], client: C) - (implicit fold: Lazy[TypeLevelFoldLeft.Aux[Api, Fold], (El, KIn, VIn, M, Out)] - builder: RequestBuilder[El, KIn, VIn], - request: ApiRequest[M, F, C, Out]): VIn => F[Out] = vin => request(builder.apply(vin, List.newBuilder, Map.empty), client) -``` - -The first approach gives us the desired function. It transforms our api type into a `(El, KIn, VIn, Method, Out)` representation, derives a function to collect all data to do a request, and finds an IO backend to actually do the request. But it has a major drawback. You have to fix `F[_]` somehow and the only way is to set it explicitly. But by doing that you are forced to provide definitions for all the type parameters. Furthermore, this function isn't really convenient. To use it you have to create and pass an `HList` and as we said before, we don't want to expose something like that. - -To fix the first problem we simply add a helper class which moves the step of defining the higher kind `F[_]` to a separate function call: - -```scala -final class ExecutableDerivation[El <: HList, KIn <: HList, VIn <: HList, M, O](builder: RequestDataBuilder[El, KIn, VIn], input: VIn) { - - final class Derivation[F[_]] { - - def apply[C](client: C)(implicit req: ApiRequest[M, C, F, O]): F[O] = { - val data = builder(input, List.newBuilder, Map.empty, Map.empty) - - req(data, cm) - } - } - - def run[F[_]]: Derivation[F] = new Derivation[F] -} -``` - -Making a function of arity `Length[VIn]` out of `Vin => F[O]`is possible by using `shapeless.ops.function.FnFromProduct`. - -When we apply both solutions we end up with: - -```scala -def derive[H <: HList, Fold, El <: HList, KIn <: HList, VIn <: HList, M, Out] - (apiList: ApiTypeCarrier[H]) - (implicit fold: Lazy[TypeLevelFoldLeft.Aux[H, Unit, (El, KIn, VIn, M, Out)]], - builder: RequestDataBuilder[El, KIn, VIn], - vinToFn: FnFromProduct[VIn => ExecutableDerivation[El, KIn, VIn, M, Out]]): vinToFn.Out = - vinToFn.apply(input => new ExecutableDerivation[El, KIn, VIn, M, Out](builder, input)) -``` - -I already hear the "your function signature is so big ..." jokes incoming, but this is basically what we will (and want to) end up with when doing type-level programming. In the end, our types have to express the logic of our program and that needs some space. - -But finally, we can say we did it! We convinced the Scala compiler to derive a client function from a type. Let's have a look at our example to see how it works. - -```scala -import cats.effect.IO -import org.http4s.client.Client - -val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge))) -val get = derive(Api) - -get("joe", 42).run[IO](Client[IO]) // IO[List[User]] -``` - -## Conclusion -When you take a closer look at the code above you will see that we were able to move most of the heavy lifting to the compiler or shapeless therefore reducing our code to a relatively small set of "simple" type classes. And when literal types are in thing in Scala we can also remove most of the boilerplate necessary to create our api types. - -This, again, shows me how powerful Scalas type system is and how much you can gain when you embrace it. - -## Next Step - Typedapi -Now that we are able to derive a single client function from a type we should also be able to do the same for a collection of api types. And if we are already on it, let's add server-side support. Or ... you just use [Typedapi](https://github.com/pheymann/typedapi). It already comes with the following features: - * client function derivation - * server function derivation - * single and multi api type handling - * support for htt4s - * support for akka-http in the making - * simple interface to add more HTTP frameworks/libraries diff --git a/src/blog/typelevel-boulder.md b/src/blog/typelevel-boulder.md deleted file mode 100644 index ceb18228..00000000 --- a/src/blog/typelevel-boulder.md +++ /dev/null @@ -1,25 +0,0 @@ -{% - author: ${typelevel} - date: "2016-03-24" - tags: [summits] -%} - -# The Typelevel Summit in Boulder is Cancelled - -As a result of -[LambdaConf's decision](http://degoes.net/articles/lambdaconf-inclusion) -to invite Curtis Yarvin as a speaker, the organizers of the Typelevel -Summit Boulder have decided that affiliation with LambdaConf is no -longer compatible with Typelevel's goals, and we are cancelling the -event, which was scheduled to happen on the Wednesday before the -conference. - -Yarvin is an unapologetic proponent of bigotry. As a result of his -modest celebrity in this regard, it is not possible for his views to -be "left at the door." By extending a speaking invitation, LambdaConf -places him in a position of prestige and tacit endorsement that -Typelevel cannot accept. - -We recognize LambdaConf's goal of "harmony in diversity" and applaud -them for sharing their deliberations, but respectfully disagree with -the outcome. diff --git a/src/blog/typelevel-native.md b/src/blog/typelevel-native.md deleted file mode 100644 index 64921e9c..00000000 --- a/src/blog/typelevel-native.md +++ /dev/null @@ -1,283 +0,0 @@ -{% - author: ${armanbilge} - date: "2022-09-19" - tags: [technical] -%} - -# Typelevel Native - -We recently published several major Typelevel projects for the [Scala Native] platform, most notably [Cats Effect], [FS2], and [http4s]. This blog post explores what this new platform means for the Typelevel ecosystem as well as how it works under-the-hood. - -### What is Scala Native? - -[Scala Native] is an optimizing ahead-of-time compiler for the Scala language. Put simply: it enables you to **compile Scala code directly to native executables**. - -It is an ambitious project following in the steps of [Scala.js]. Instead of targeting JavaScript, the Scala Native compiler targets the [LLVM] IR and uses its toolchain to generate native executables for a range of architectures, including x86, ARM, and in the near future [web assembly]. - -### Why is this exciting? - -**For Scala in general**, funnily enough I think [GraalVM Native Image] does a great job summarizing the advantages of native executables, namely: -* instant startup that immediately achieves peak performance, without requiring warmup or the heavy footprint of the JVM -* packagable into small, self-contained binaries for easy deployment and distribution - -It is worth mentioning that in benchmarks Scala Native handily beats GraalVM Native Image on startup time, runtime footprint, and binary size. - -Moreover, breaking free from the JVM is an opportunity to design a runtime specifically optimized for the Scala language itself. This is the true potential of the Scala Native project. - -**For Typelevel in particular**, Scala Native opens new doors for leveling up our ecosystem. Our flagship libraries are largely designed for deploying high performance I/O-bounded microservices and for the first time ever **we now have direct access to kernel I/O APIs**. - -I am also enthusiastic to use Cats Effect with (non-Scala) native libraries that expose a C API. [`Resource`] and more generally [`MonadCancel`] are powerful tools for safely navigating manual memory management with all the goodness of error-handling and cancelation. - -### How can I try it? - -Christopher Davenport has put up a [scala-native-ember-example](https://github.com/ChristopherDavenport/scala-native-ember-example) and reported some [benchmark results](#ember-native-benchmark)! - -### How does it work? - -The burden of cross-building the Typelevel ecosystem for Scala Native fell almost entirely to [Cats Effect] and [FS2]. - -#### Event loop runtime - -**To cross-build Cats Effect for Native we had to get creative** because Scala Native currently does not support multithreading (although it will in the next major release). This is a similar situation to the JavaScript runtime, which is also fundamentally single-threaded. But an important difference is that JS runtimes are implemented with an [event loop] and offer callback-based APIs for scheduling timers and performing non-blocking I/O. An *event loop* is a type of runtime that enables compute tasks, timers, and non-blocking I/O to be interleaved on a single thread (although not every event loop does all these things). - -Meanwhile, Scala Native core does not implement an event loop nor offer such APIs. There is the [scala-native-loop] project, which wraps the [libuv] event loop runtime, but we did not want to bake such an opinionated dependency into Cats Effect core. - -Fortunately Daniel Spiewak had the fantastic insight that the “dummy runtime” which I created to initially cross-build Cats Effect for Native could be reformulated into a legitimate event loop implementation by extending it with the capability to “poll” for I/O events: a `PollingExecutorScheduler`. - -The [`PollingExecutorScheduler`] implements both [`ExecutionContext`] and [`Scheduler`] and maintains two queues: -- a queue of tasks (read: fibers) to execute -- a priority queue of timers (read: `IO.sleep(...)`), sorted by expiration - -It also defines an abstract method: -```scala -def poll(timeout: Duration): Boolean -``` - -The idea of this method is very similar to `Thread.sleep()` except that besides sleeping it may also “poll” for I/O events. It turns out that APIs like this are ubiquitous in C libraries that perform I/O. - -To demonstrate the API contract, consider invoking `poll(3.seconds)`: - -*I have nothing to do for the next 3 seconds. So wake me up then, or earlier if there is an incoming I/O event that I should handle. But wake me up no later!* - -*Oh, and don’t forget to tell me whether there are still outstanding I/O events (`true`) or not (`false`) so I know if I need to call you again. Thanks!* - -With tasks, timers, and the capability to poll for I/O, we can express the event loop algorithm. A single iteration of the loop looks like this: - -1. Check the current time and execute any expired timers. - -2. Execute up to 64 tasks, or until there are none left. We limit to 64 to ensure we are fair to timers and I/O. - -3. Poll for I/O events. There are three cases to consider: - - **There is at least one task to do.** Call `poll(0.nanos)`, so it will process any available I/O events and then immediately return control. - - **There is at least one outstanding timer**. Call `poll(durationToNextTimer)`, so it will sleep until the next I/O event arrives or the timeout expires, whichever comes first. - - **There are no tasks to do and no outstanding timers.** Call `poll(Duration.Infinite)`, so it will sleep until the next I/O event arrives. - -This algorithm is not a Cats Effect original: the [libuv event loop] works in essentially the same way. It is however a first step toward the much grander Cats Effect [I/O Integrated Runtime Concept]. The big idea is that every `WorkerThread` in the `WorkStealingThreadPool` that underpins the Cats Effect JVM runtime can run an event loop exactly like the one described above, for exceptionally high-performance I/O. - -#### Non-blocking I/O - -**So, how do we implement `poll`?** The bad news is that the answer is OS-specific, which is a large reason why projects such as libuv exist. Furthermore, the entire purpose of polling is to support non-blocking I/O, which falls outside of the scope of Cats Effect. This brings us to FS2, and specifically the [`fs2-io`] module where we want to implement non-blocking TCP [`Socket`]s. - -One such polling API is [epoll], available only on Linux: - -```c -#include - -int epoll_create1(int flags); - -int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); - -int epoll_wait(int epfd, struct epoll_event *events, - int maxevents, int timeout); -``` - -After creating an epoll instance (identified by a file descriptor) we can register sockets (also identified by file descriptors) with `epoll_ctl`. Typically we will register to be notified of the “read-ready” (`EPOLLIN`) and “write-ready” (`EPOLLOUT`) events on that socket. Finally, the actual polling is implemented with `epoll_wait`, which sleeps until the next I/O event is ready or the `timeout` expires. Thus we can use it to implement a `PollingExecutorScheduler`. - -As previously mentioned, these sorts of polling APIs are ubiquitous and not just for working directly with sockets. For example, [libcurl](https://curl.se/libcurl/) (the C library behind the well-known CLI) exposes a function for polling for I/O on all ongoing HTTP requests. - -```c -#include - -CURLMcode curl_multi_poll(CURLM *multi_handle, - struct curl_waitfd extra_fds[], - unsigned int extra_nfds, - int timeout_ms, - int *numfds); -``` - -Indeed, this function underpins the `CurlExecutorScheduler` in [http4s-curl]. - -On macOS and BSDs the [kqueue] API plays an analogous role to epoll. We will not talk about Windows today :) - -**Long story short, I did not want the FS2 codebase to absorb all of this cross-OS complexity.** So in collaboration with Lee Tibbert we repurposed my cheeky [epollcat] experiment into an actual library implementing JDK NIO APIs (specifically, [`AsynchronousSocketChannel`] and friends). Since these are the same APIs used by the JVM implementation of `fs2-io`, it actually enables the `Socket` code to be completely shared with Native. - -[epollcat] implements an `EpollExecutorScheduler` for Linux and a `KqueueExecutorScheduler` for macOS. They additionally provide an API for monitoring a socket file descriptor for read-ready and write-ready events. - -```scala -def monitor(fd: Int, reads: Boolean, writes: Boolean)( - cb: EventNotificationCallback -): Runnable // returns a `Runnable` to un-monitor the file descriptor - -trait EventNotificationCallback { - def notifyEvents(readReady: Boolean, writeReady: Boolean): Unit -} -``` - -These are then used to implement the callback-based `read` and `write` methods of the JDK `AsynchronousSocketChannel`. - -It is worth pointing out that the JVM actually implements `AsynchronousSocketChannel` with an event loop as well. The difference is that on the JVM, this event loop is used only for I/O and runs on a separate thread from the compute pool used for fibers and the scheduler thread used for timers. Meanwhile, epollcat is an example of an [I/O integrated runtime][I/O Integrated Runtime Concept] where fibers, timers, and I/O are all interleaved on a single thread. - -#### TLS - -**The last critical piece of the cross-build puzzle was a [TLS] implementation** for [`TLSSocket`] and related APIs in FS2. Although the prospect of this was daunting, in the end it was actually fairly straightforward to directly integrate with [s2n-tls], which exposes a well-designed and well-documented C API. This is effectively the only non-Scala dependency required to use the Typelevel stack on Native. - -Finally, special thanks to Ondra Pelech and Lorenzo Gabriele for cross-building [scala-java-time] and [scala-java-locales] for Native and David Strawn for developing [idna4s]. These projects fill important gaps in the Scala Native re-implementation of the JDK and were essential to seamless cross-building. - -And ... that is pretty much it. **From here, any library or application that is built using Cats Effect and FS2 cross-builds for Scala Native effectively for free.** Three spectacular examples of this are: - -* [http4s] Ember, a server+client duo with HTTP/2 support -* [Skunk], a Postgres client -* [rediculous], a Redis client - -These libraries in turn unlock projects such as [feral], [Grackle], and [smithy4s]. - -### What’s next and how can I get involved? - -Please try the Typelevel Native stack! And even better deploy it, and do so loudly! - -Besides that, here is a brain-dump of project ideas and existing projects that would love contributors. I am happy to help folks get started on any of these, or ideas of your own! - -* Creating example applications, templates, and tutorials: - - If you are short on inspiration, try cross-building existing examples such as [fs2-chat], [kitteh-redis], [Jobby]. - - Spread the word: [you-forgot-a-percentage-sign-or-a-colon]. - -* Cross-building existing libraries and developing new, Typelevel-stack ones: - - Go [feral] and implement a pure Scala [custom AWS Lambda runtime] that cross-builds for Native. - - A pure Scala [gRPC] implementation built on http4s would be fantastic, even for the JVM. Christopher Davenport has published a [proof-of-concept][grpc-playground]. - - [fs2-data] has pure Scala support for a plethora of data formats. The [http4s-fs2-data] integration needs your help to get off the ground! - - Lack of cross-platform cryptography is one of the remaining sore points in cross-building. I started the [bobcats] project to fill the gap but I am afraid it needs love from a more dedicated maintainer. - -* Integrations with native libraries: - - I kick-started [http4s-curl] and would love to see someone take the reigns! - - An [NGINX Unit] server backend for http4s promises exceptional performance. [snunit] pioneered this approach. - - Using [quiche] for HTTP/3 looks yummy! - - An idiomatic wrapper for [SQLite]. See also [davenverse/sqlite-sjs#1] which proposes cross-platform API backed by Doobie on the JVM. - -* Developing I/O-integrated runtimes: - - [epollcat] supports Linux and macOS and has plenty of opportunity for optimization and development. - - A [libuv]-based runtime would have solid cross-OS support, including Windows. Prior art in [scala-native-loop]. - - Personally I am excited to work on an [io_uring] runtime. - -* Tooling. Anton Sviridov has spear-headed two major projects in this area: - - [sbt-vcpkg] is working hard to solve the native dependency problem. - - [sn-bindgen] generates Scala Native bindings to native libraries directly from `*.h` header files. I found it immensely useful while working on http4s-curl, epollcat, and the s2n-tls integration in FS2. - - Also: we are _badly_ in need of a pure Scala port of the [Java Microbenchmark Harness]. Not the whole thing obviously, but just enough to run the existing Cats Effect benchmarks for example. - -* Scala Native itself. Lots to do there! - -### Ember native benchmark - -```console -$ hey -z 30s http://localhost:8080 - -Summary: - Total: 30.0160 secs - Slowest: 0.3971 secs - Fastest: 0.0012 secs - Average: 0.0131 secs - Requests/sec: 3815.4647 - - Total data: 1145250 bytes - Size/request: 10 bytes - -Response time histogram: - 0.001 [1] | - 0.041 [114486] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ - 0.080 [7] | - 0.120 [5] | - 0.160 [5] | - 0.199 [3] | - 0.239 [5] | - 0.278 [3] | - 0.318 [4] | - 0.357 [3] | - 0.397 [3] | - - -Latency distribution: - 10% in 0.0119 secs - 25% in 0.0121 secs - 50% in 0.0122 secs - 75% in 0.0125 secs - 90% in 0.0133 secs - 95% in 0.0224 secs - 99% in 0.0234 secs - -Details (average, fastest, slowest): - DNS+dialup: 0.0000 secs, 0.0012 secs, 0.3971 secs - DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0011 secs - req write: 0.0000 secs, 0.0000 secs, 0.0013 secs - resp wait: 0.0131 secs, 0.0011 secs, 0.3941 secs - resp read: 0.0000 secs, 0.0000 secs, 0.0010 secs - -Status code distribution: - [200] 114525 responses -``` - -[`AsynchronousSocketChannel`]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/AsynchronousSocketChannel.html -[bobcats]: https://github.com/typelevel/bobcats -[Cats Effect]: https://typelevel.org/cats-effect/ -[custom AWS Lambda runtime]: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html -[davenverse/sqlite-sjs#1]: https://github.com/davenverse/sqlite-sjs/pull/1 -[`ExecutionContext`]: https://www.scala-lang.org/api/2.13.8/scala/concurrent/ExecutionContext.html -[event loop]: https://javascript.info/event-loop -[epoll]: https://man7.org/linux/man-pages/man7/epoll.7.html -[epollcat]: https://github.com/armanbilge/epollcat -[feral]: https://github.com/typelevel/feral -[FS2]: https://fs2.io/ -[fs2-chat]: https://github.com/typelevel/fs2-chat/ -[fs2-data]: https://github.com/satabin/fs2-data/ -[`fs2-io`]: https://fs2.io/#/io -[GraalVM Native Image]: https://www.graalvm.org/22.2/reference-manual/native-image/ -[Grackle]: https://github.com/gemini-hlsw/gsp-graphql -[gRPC]: https://grpc.io/ -[grpc-playground]: https://github.com/ChristopherDavenport/grpc-playground -[http4s]: https://http4s.org/ -[http4s-curl]: https://github.com/http4s/http4s-curl/ -[http4s-fs2-data]: https://github.com/http4s/http4s-fs2-data -[idna4s]: https://github.com/typelevel/idna4s -[I/O Integrated Runtime Concept]: https://github.com/typelevel/cats-effect/discussions/3070 -[io_uring]: https://en.wikipedia.org/wiki/Io_uring -[Jobby]: https://github.com/keynmol/jobby/ -[kitteh-redis]: https://github.com/djspiewak/kitteh-redis -[kqueue]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 -[Java Microbenchmark Harness]: https://github.com/openjdk/jmh -[libuv]: https://github.com/libuv/libuv/ -[libuv event loop]: https://docs.libuv.org/en/v1.x/design.html#the-i-o-loop -[libcurl]: https://curl.se/libcurl/ -[LLVM]: https://llvm.org/ -[`MonadCancel`]: https://typelevel.org/cats-effect/docs/typeclasses/monadcancel -[NGINX Unit]: https://unit.nginx.org/ -[`PollingExecutorScheduler`]: https://github.com/typelevel/cats-effect/blob/7ca03db50342773a79a01ecf137d953408ac6b1d/core/native/src/main/scala/cats/effect/unsafe/PollingExecutorScheduler.scala -[quiche]: https://github.com/cloudflare/quiche -[rediculous]: https://github.com/davenverse/rediculous -[`Resource`]: https://typelevel.org/cats-effect/docs/std/resource -[sbt-vcpkg]: https://github.com/indoorvivants/sbt-vcpkg/ -[ScalablyTyped]: https://scalablytyped.org/ -[Scala Native]: https://scala-native.org/ -[Scala.js]: https://www.scala-js.org/ -[scala-java-locales]: https://github.com/cquiroz/scala-java-locales -[scala-java-time]: https://github.com/cquiroz/scala-java-time -[scala-native-loop]: https://github.com/scala-native/scala-native-loop/ -[`Scheduler`]: https://github.com/typelevel/cats-effect/blob/236a0db0e95be829de34d7a8e3c06914738b7b06/core/shared/src/main/scala/cats/effect/unsafe/Scheduler.scala -[Skunk]: https://github.com/tpolecat/skunk -[smithy4s]: https://disneystreaming.github.io/smithy4s/ -[`Socket`]: https://www.javadoc.io/doc/co.fs2/fs2-docs_2.13/latest/fs2/io/net/Socket.html -[SQLite]: https://www.sqlite.org/index.html -[snunit]: https://github.com/lolgab/snunit -[sn-bindgen]: https://github.com/indoorvivants/sn-bindgen -[s2n-tls]: https://github.com/aws/s2n-tls -[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security\ -[`TLSSocket`]: https://www.javadoc.io/doc/co.fs2/fs2-docs_2.13/latest/fs2/io/net/tls/TLSSocket.html -[web assembly]: https://twitter.com/ShadajL/status/1548020571597811719 -[you-forgot-a-percentage-sign-or-a-colon]: https://youforgotapercentagesignoracolon.com/ diff --git a/src/blog/typelevel-org-built-with-typelevel.md b/src/blog/typelevel-org-built-with-typelevel.md deleted file mode 100644 index 2c528a2b..00000000 --- a/src/blog/typelevel-org-built-with-typelevel.md +++ /dev/null @@ -1,50 +0,0 @@ -{% - author: [${armanbilge}, ${valencik}] - date: "2026-02-18" - tags: [community] -%} - -# typelevel.org built with Typelevel - -We are proud to share that our website is now built with [Laika], a Typelevel Organization project for generating static sites! As cool as it is that we are self-hosting, the intention of this revamp was to **make it easier for our community to develop and contribute to the website**. We [chose technologies](/colophon.md) that we hope balance familiarity and ease-of-use with functionality and stability. Notably, this new website can be generated in its entirety by running a Scala script: `scala build.scala`. Stay tuned for a future blog post that dives into the details, but for now you may peruse the [PR]. - -Finally, we would like to express gratitude to our friends at 47 Degrees who generously built the [previous version](new-website-layout.md) of the website for us. - -## What’s next and how you can help - -Truthfully, so far this is a "minimally viable website" and we invite you to [help us iterate on it](https://github.com/typelevel/typelevel.github.com#get-started). Broadly, our goals are to explain: - -1. Who we are, and how you can join our community. -2. What we build, and how you can use it. - -The next phase of development will largely focus on creating new content to support these goals (and the infrastructure to support that content). Here are a few ideas we have: - -* Educational and tutorial content to facilitate onboarding. - * How to **Get Started** with Typelevel using our [Toolkit]. - * Curated pathways to **Learn** how to use Typelevel in different scenarios: web services, serverless, CLIs, UIs, etc. - * How to **Get Started Contributing** both to existing projects and also by publishing new libraries with [sbt-typelevel]. -* A **Typelevel Project Index** for exploring Organization and Affiliate projects, à la [Scaladex]. We are imagining a webapp built with [Calico], with features for browsing projects, finding version numbers, and scaffolding new applications. -* Content-agnostic enhancements to the website itself. - * Upstreaming customizations from our build to Laika. - * Integrating [mdoc], for typechecking code. - * Improvements to layout, styling, and theme. - -We are accepting ideas and help in many forms! Please use our [issue tracker] and join the discussion on the [#website] channel in our Discord server. - -## In memoriam - -This project would not have been possible without [Jens Halm] and his [vision] for a documentation tool that is native to our ecosystem. Jens raised the bar for open source stewardship: beyond the technical excellence of his work on [Laika], he consistently published feature roadmaps, detailed issue and PR descriptions, and thorough documentation. Indeed, by creating a documentation tool that integrated so well with our tech stack, he has empowered *all* of us to become exemplary maintainers. Moreover, Jens' enthusiasm to support our community (including entertaining our numerous feature requests with in-depth responses full of context and design insights!) was his most generous gift to us. - -[Calico]: https://armanbilge.com/calico/ -[issue tracker]: https://github.com/typelevel/typelevel.github.com/issues -[Jekyll]: https://jekyllrb.com/ -[Jens Halm]: https://github.com/jenshalm -[Laika]: https://typelevel.org/Laika -[mdoc]: https://scalameta.org/mdoc/ -[repository]: https://github.com/typelevel/typelevel.github.com#get-started -[PR]: https://github.com/typelevel/typelevel.github.com/pull/576 -[sbt-typelevel]: https://typelevel.org/sbt-typelevel/ -[Scaladex]: https://index.scala-lang.org/ -[Toolkit]: https://typelevel.org/toolkit -[vision]: https://typelevel.org/Laika/latest/01-about-laika/02-design-goals.html -[#website]: https://discord.gg/krrdNdSDFf diff --git a/src/blog/typelevel-scala.md b/src/blog/typelevel-scala.md deleted file mode 100644 index 57329869..00000000 --- a/src/blog/typelevel-scala.md +++ /dev/null @@ -1,70 +0,0 @@ -{% - author: ${milessabin} - date: "2014-09-02" - tags: [technical] -%} - -# Typelevel Scala and the future of the Scala ecosystem - -**tl;dr** Typelevel is forking Scala; we call on all stakeholders in the Scala ecosystem to collaborate on the creation of an independent, non-profit, open source foundation to safeguard the interests of the entire Scala community. - -Last week I tweeted the following question: - -> How much interest would there be in a community sponsored fork of the [#Scala](https://twitter.com/hashtag/Scala?src=hash) toolchain? RTs and fav's please. -> @:style(nowrap) — Miles Sabin (@milessabin) [August 25, 2014](https://twitter.com/milessabin/status/503929023635161088)@:@ - -It generated a lively response, both on Twitter and privately. The responses were sometimes perplexed, but typically excited and invariably positive. What I want to do here is provide some background to the question and sketch out the directions that positive answers lead. - -As the Scala community has matured, a number of different strands of development of the language have emerged, - -* The Typesafe Scala compiler is focused on stability and compatibility with Java. -* The LAMP/EPFL Dotty compiler is focused on providing a practical implementation of the DOT calculus. -* The Scala.js compiler is focused on targeting JavaScript as a backend platform. -* The Scala Virtualized compiler is focused on language virtualization and staging. -* The Scala.Meta system aims to provide portable metaprogramming facilities across a variety of Scala compilers. -* There are several research and private variants of the Scala compiler in development and use by a variety of academic and commercial organizations. -* The IDEs provide their own Scala variants which more or less accurately approximate Typesafe Scala. - -Of these, the only compiler targeting the JVM which is generally suitable for use by the Scala community at large in the near term is the Typesafe Scala compiler. The current roadmap for the 2.12 release of this compiler, due in early 2016, has very modest goals, consistent with Typesafe's focus. Beyond that, the Next Steps roadmap mentions many things of interest, however it is clear that these are all a long way out – it will be 2017 at the earliest before any of this sees the light of day. - -Typesafe's motivation for focusing on stability and Java 8 compatibility is very easy to understand. Typesafe is a commercial entity with products to sell in the JVM-based enterprise middleware space. Its primary software offerings, Akka and Play, are probably the most Java-friendly Scala projects of any significance, and simple arithmetic should tell you that a very large proportion (probably a large majority) of the potential customers for these (and the associated consultancy, training and support) are mainly Java enterprises with wholly or largely Java codebases and development teams. In this context it should be easy to see that for them an emphasis on Scala as a complement to Java, rather as its successor, is paramount. - -Whilst this is entirely reasonable, and meets the needs of many, there is nevertheless a significant constituency at the core of the Scala community whose needs are not being fully met. This constituency is the segment of the Scala community which puts greater emphasis on typeful functional programming styles and which has a strong interest in current developments in functional programming in the wider world beyond Scala. The projects gathered here under the Typelevel umbrella are prime examples of that constituency. - -As the producers and consumers of these and other projects we continually find ourselves running up against limitations of current Scala. Sometimes these limitations are minor and amenable to simple workarounds, many of which have passed into Scala folklore. Other limitations are more serious and can only be worked around with cumbersome encodings or otherwise elaborate and confusing hacks. These have the unfortunate consequence that elegant solutions to important problems are obscured by layers of cruft which exist only to sidestep quirks of the current compiler. - -What makes this all the more frustrating is that many of these limitations are comparatively easy to remove. Fixes for some of them are purely syntactic – for instance type constructor partial application, of huge importance to Scalaz and its users, has a clunky encoding ("type lambdas") which could be given first class syntactic support without any impact on Scala's semantics and in a completely binary compatible way. Similarly, syntactic support for singleton types for literal values (see SIP-23) would be of enormous value to shapeless and Spire and their users. And the addition of literals for Bytes and Shorts would be welcomed by Spire, Scodec and many others. Other fixes, whilst affecting semantics, would do so only in a conservative way – programs which are valid when compiled with the Typesafe Scala compiler would have the same meaning and binary representation when compiled with the fixes in place. - -With this in mind, we intend to create a new Scala distribution, as a conservative fork of the Typesafe Scala compiler, with a focus on meeting the needs of the part of the Scala community which has coalesced around the Typelevel stack. As a conservative fork, our aim is to be merge compatible from the Typesafe Scala compiler, and a key objective is to feed pull requests (many of which will resolve long standing Scala bugs or feature requests) back to the Typesafe Scala compiler. Our goal is to have a language which continues to evolve at the pace we saw until a couple of years ago, but with the difference that this will now be an opt-in process and the priorities will be set by the community. - -Of course the devil is in the details. Forking a compiler is only a small part of the story – in many ways more important is the surrounding ecosystem of libraries. As part of this initiative we intend to publish compatible builds of at least the Typelevel libraries – taking our lead from the Typesafe community build (which attempts to track ecosystem coherence over time by building a selection of community libraries against the development Scala compiler as it evolves) and Scala.js (which has ported a selection of important community libraries to its compiler). - -We welcome the participation of all other parties, individuals or organizations, who share our general goals – both those who want to contribute to the development of the compiler and those who would like their libraries and frameworks to be part of a Typelevel community build. It's early days, but we hope that with enough enthusiastic participation we will be able to produce useful binaries well before the end of the year. - -We anticipate a number of objections to this initiative, - -* That it will split the community and fragment the language. - - As I observed earlier, there are already several variants of the language in existence and it has been clear for a long time that different sections of the community have different interests. We shouldn't be afraid of acknowledging this fact – attempting to ignore it will be (arguably is already) counterproductive. Instead we should embrace diversity as a sign of a healthy and vigorous platform and community. - -* That we don't have the resources or the expertise to pull this off. - - We disagree – the community around the Typelevel projects contains many of the most able Scala programmers on the planet. Between us we have a deep understanding of Scala's type system and other semantics (both as specified and as implemented), of compiler construction in general and of Typesafe Scala compiler internals in particular. We are intimately familiar with the Scala toolchain, which many of us have been using at scale for years in our day jobs. We are also intimately familiar with the issues that we seek to address – they are ones we face daily. - - We also have the existence proof of the other Scala compiler variants. The number of full-time-equivalent people working on these projects is really very small – we believe that in practice this can be matched or exceeded by an open, inclusive and enthusiastic open source project. - -* That we underestimate the difficulty of maintaining binary and/or merge compatibility. - - No, we really don't. We fully expect this to be the most challenging part of the whole exercise. That said, we have the benefit of years of experience of Scala binary compatibility issues, and we know now that a combination of a community-build style model along with effective use of the Migration Manager (already a component of the Typelevel SBT plugin) is enormously helpful in keeping on top of the issue. - - There is a real risk here, and care will be needed. One thing is for sure though – if we don't try, we'll never know if it's possible. - -* That the fork is too conservative. - - It's certainly true that restricting ourselves to only changes which are merge compatible with the Typesafe Scala compiler puts fairly strict limits on what we can do. Many highly desirable changes fall well beyond, and some people want to explore those possibilities. - - We think that this is completely reasonable, and we don't think the two are mutually exclusive – a merge compatible Typelevel compiler meets many of our immediate needs, but we want to enable people to push further just as is being done by Scala.Meta, Scala Virtualized and Dotty. - - We believe that the same infrastructure (community builds, MiMa) that will help the merge-compatible Typelevel compiler stay close to the Typesafe compiler will also be of great assistance to people who want to experiment with more radical changes. At a minimum, community build infrastructure will enable people to work with not just a bare compiler with but a core set of compatible libraries as well. We believe that such infrastructure would also benefit Scala Virtualized, Scala.Meta and Dotty. - -This brings me to the final part of this message. It has become clear to us that there are many distinct stakeholders in the Scala ecosystem with a mixture of shared and divergent interests. This is a good thing and is something we should jointly strive to support. To that end, we believe that it is time for the formation of an independent, non-profit, open source foundation to safeguard the interests of the entire Scala community – we call on all organizations and individuals who want to see a flourishing Scala ecosystem to join with us in that project. diff --git a/src/blog/typelevel-sustainability-program-announcement.md b/src/blog/typelevel-sustainability-program-announcement.md deleted file mode 100644 index 55c4767a..00000000 --- a/src/blog/typelevel-sustainability-program-announcement.md +++ /dev/null @@ -1,83 +0,0 @@ -{% - author: ${typelevel} - date: "2019-04-24" - tags: [governance] -%} - -# Typelevel Sustainability Program Announcement - -We are excited to announce the Typelevel sustainability program. -The ultimate goal for this program is to provide ways for the user community to ensure the long-term sustainability of the development and maintenance of some Typelevel libraries. -Currently, these libraries are maintained in their contributors' spare time. This arrangement has worked so far but we want to firmly secure their long term sustainability with an institution dedicated to supporting the maintenance of these mission critical libraries. - -Based on our [Cats ecosystem community survey](cats-ecosystem-community-survey-results.md), roughly 70% of the users will gain more confidence -in the future of the ecosystem if there are compensated maintainers. We believe that our pure FP -Scala ecosystem should have an institution supporting it, somewhat like the Scala Center -supporting the language, and Lightbend supporting language as well as ecosystem libs. - -There are also some concerns, 3% of users suggested paid maintainers will reduce their confidence, the remainder are not sure one way or another. Given these numbers, we believe that the gain of confidence will outweigh the concerns especially if we design our paid maintainership program with deliberations to address them. The worries we received fall into four categories: - -* Vendor favoritism and influence -* Conflicts of interest -* Discouraging non-compensated maintainers and contributors -* Paid maintainer over contributing unnecessary features - -### We believe we can mitigate these concerns by adopting the following principles: - -* Paid maintainers focus on supporting the community contributors. In another sentence, -paid maintainers’ first task is to maintain the community-driven development. -More specific responsibilities are listed [here](https://github.com/typelevel/general/blob/master/sustainability_program.md#responsibilities-for-paid-maintainers). -* Main obligations to sponsors (individual or corporate) are limited to - - The existing license remains unchanged. - - Timely security patches and mission critical bug fixes. - - Monetary contributions will NOT grant contributors extra influence over the development. - - Monetary contributors can influence how their contribution is distributed among projects. -* The program is governed by an independent committee. -* As part of our community, sponsors hold values compatible with the [Scala Code of Conduct](https://www.scala-lang.org/conduct/). - -For more details, including goals and responsibilities for maintainers, funding sources, please go to the [program's main document](https://github.com/typelevel/general/blob/master/sustainability_program.md). - -Today we are launching the program with the help of several founding sponsors. - -@:style(bulma-columns bulma-is-col-min-1 bulma-is-vcentered) -@:style(bulma-column bulma-has-text-centered)[@:image(/img/media/sponsors/47_degrees.png)](http://47deg.com)@:@ -@:style(bulma-column bulma-has-text-centered)[@:image(/img/media/sponsors/commercetools_2.png)](https://commercetools.com/)@:@ -@:style(bulma-column bulma-has-text-centered)[@:image(/img/media/sponsors/inner-product.png)](https://www.inner-product.com/)@:@ -@:style(bulma-column bulma-has-text-centered)[@:image(/img/media/sponsors/triplequote.png)](https://triplequote.com/)@:@ -@:style(bulma-column bulma-has-text-centered)[@:image(/img/media/sponsors/underscore_2.png)](http://underscore.io)@:@ -@:@ - -Thanks to their generosity we are on an excellent start for the sustainability program. However, to successfully support the long term sustainability for our ecosystem, we need every bit of help we can get. For 2019, we have the following initiatives that require a significant investment of maintainer time. - -* Refactor build configuration -* Continue support Scala 2.11 through backporting -* Complete Scala 2.13 migration -* Support for Scala 3.0 and Scala native -* Revamp guidance documentation for contributors -* Revamp Documentations Navigation -* More tutorial/example documentation -* Complete cats-tagless' migration away from scala-meta -* Merge in typelevel/algebra -* A community build for the ecosystem - -Our initial fundraising goal is $150,000. Among other things, achieving this goal will allow us to have a dedicated half-time (20 hr/week) maintainer for at least 2019. Why half time? We want to start with a committed maintainer to bring some certainties for our projects, and yet we are not sure how much support we will be getting from the community. Hence a half-time maintainer for the year is a minimum viable solution for the program. - -Please consider talking to your employer about supporting the OSS libraries they are using. Any amount, either $5 per month from a one-person start-up or $5000 per month from a billion dollar corporation, will bring us closer to our goal. - -Aside from monetary assistance, your company can also support us by: - -* Providing computing resources/tools such as CI systems, communication platforms, development tools, etc. -* Paid employees' time for code contributions -* Sponsoring events such as free training, conferences, by providing the venue, food, etc. -* Donations of training/support services or coupons that we can then exchange for monetary contributions. - -For individual developers, another way to support us monetarily is to help our fundraising effort by: - -* spreading the news about our fundraising campaign -* mentioning and linking to our donation page when you write a blog post or give a talk about one of these libraries -* advocating for the sustainable OSS development - -Please don't hesitate to reach out with questions. Our contact address is sponsor-contact@typelevel.org. -Thank you for reading this and considering supporting us. - -[Donate at OpenCollective](https://opencollective.com/typelevel) diff --git a/src/blog/typelevel-switches-to-scala-code-of-conduct.md b/src/blog/typelevel-switches-to-scala-code-of-conduct.md deleted file mode 100644 index 65edd76c..00000000 --- a/src/blog/typelevel-switches-to-scala-code-of-conduct.md +++ /dev/null @@ -1,31 +0,0 @@ -{% - author: ${typelevel} - date: "2019-05-01" - tags: [governance] -%} - -# Typelevel Switches to the Scala Code of Conduct - -Typelevel is pleased to announce that we are retiring the Typelevel Code of -Conduct in favour of the [Scala Code of Conduct][scoc]. Many of the major projects -under the Typelevel umbrella have already made the switch and we will be asking -new projects that join Typelevel to adopt the new code. - -The Scala Code of Conduct was developed by the Scala Center with input from -Typelevel and Lightbend, and improves on Typelevel's original in several ways. -It can be thought of as the "Typelevel code of conduct 2.0". We [endorsed it -from the outset](scala-coc.md) and have -decided that now the time is right to simplify things and move to the new code -wholesale. - -A shared code of conduct means a shared standard of good behaviour. Having a -single code smooths the path for participants moving between different -organizations, projects, and events in the Scala ecosystem. - -The Scala Code of Conduct is supported by key Scala organizations and events, -both community and commercial: the Scala Center, Lightbend, ScalaBridge, Scala -Days amongst many others. And now Typelevel and the Typelevel Summits. - -We'd love you to join us. - -[scoc]: /code-of-conduct/README.md diff --git a/src/blog/typelevel-toolkit.md b/src/blog/typelevel-toolkit.md deleted file mode 100644 index 72fa5dcf..00000000 --- a/src/blog/typelevel-toolkit.md +++ /dev/null @@ -1,111 +0,0 @@ -{% - author: ${zetashift} - date: "2023-04-03" - tags: [technical] -%} - -# Typelevel Toolkit - -Getting started in the wondrous world of functional programming using [Typelevel libraries](/projects/README.md) can be daunting. Before you can even write your first pure "Hello, World!" you'll need to install a Java runtime, editor tooling and build tools. Then you'll need to setup some project using [sbt](https://www.scala-sbt.org/) or [mill](https://github.com/com-lihaoyi/mill). As an added consequence, after all the setup, the idea of using these battle-tested libraries for small scripts will seem like a chore. This is where [Typelevel Toolkit](https://typelevel.org/toolkit/) comes in. It provides an easy start for beginning and experienced developers with Scala and functional programming. - -# scala-cli to the rescue - -[scala-cli](https://scala-cli.virtuslab.org/) is a command-line interface to quickly develop and experiment with Scala, it's even on track to [becoming the new scala command](https://docs.scala-lang.org/sips/scala-cli.html). The interface has a lot of advantages, but one of the most important ones is that it makes learning, developing and building Scala scripts and small applications friction-less and easy to use. - -You can get `scala-cli` by following the installation instructions described here: [https://scala-cli.virtuslab.org/docs/overview#installation](https://scala-cli.virtuslab.org/docs/overview#installation). The great part here is that once you have `scala-cli` installed, it will take care of the rest: Java runtimes, editor tooling, compilation targets(including a native target!) and you can even use dependencies in your scripts! - -## An example of setting up your script - -First off, let's create a new directory that will contain our script(s). - -```sh -mkdir myscript && cd myscript -``` - -Now we can use `scala-cli` to create all the files necessary for editor tooling to work: - -```sh -scala-cli setup-ide . -``` - -Finally, let's create a Scala file and try to compile it. - -```sh -touch Main.scala -scala-cli compile --watch Main.scala -``` - - -The last command also includes a `--watch` flag, so we can hack on our script and `scala-cli` will try to compile the file on every save. -This creates a very nice feedback loop! - -# Putting the fun in functional - -[Typelevel Toolkit](https://typelevel.org/toolkit/) uses `scala-cli` and Typelevel libraries to provide a runway for your next Scala script or command-line interface. With a single line, Typelevel Toolkit gives you: -- [Cats Effect](https://typelevel.org/cats-effect/) a production proven pure asynchronous runtime. -- [fs2](https://fs2.io) an amazing streaming I/O library. -- [http4s-ember-client](https://http4s.org/v0.23/docs/client.html) for a full-fledged HTTP client. -- [circe](https://circe.github.io/circe/) for dealing with JSON. -- [MUnit](https://github.com/typelevel/munit-cats-effect) an unit testing library and an integration to easily unit test pure functional programs. -- [fs2-data-csv](https://fs2-data.gnieh.org/documentation/csv/) for handling CSV files. -- [decline](https://ben.kirw.in/decline/effect.html) a composable commandline parser and it's integration with Cats Effect. - -Typelevel Toolkit shines with `scala-cli`, but it can also be used by sbt or mill if that is preferred. -More concretely this means your next ad-hoc script won't be Bash or Python spaghetti, but Scala code that can be a joy to hack on as time goes on, without the boilerplate. - -You can use the toolkit by using a -[scala-cli directive](https://scala-cli.virtuslab.org/docs/guides/using-directives) - -```scala -//> using dep "org.typelevel::toolkit::0.0.4" -``` - -This will pull in the `typelevel/toolkit` dependency and then you're just an import away from your first pure functional "Hello, World!": - -```scala -import cats.effect.* - -object Hello extends IOApp.Simple { - def run = IO.println("Hello, World!") -} -``` - -You can compile and run this program by using a single command: `scala-cli run Main.scala`. - - -A "Hello, World!" is only the start, the goal here is to make functional programming friendly and practical. As such, Typelevel toolkit comes with [examples](https://typelevel.org/toolkit/examples.html) that introduces beginners on how one can use the included libraries to achieve common tasks. - - -For the full list of libraries included in Typelevel Toolkit, please see the overview: [https://typelevel.org/toolkit/#overview](https://typelevel.org/toolkit/#overview). If you feel like anything is missing, [join the discussion](https://github.com/typelevel/toolkit/issues/1). - -# We can have nice things - -Typelevel libraries are production-proven, well tested, build upon rock solid semantics, and almost all have Scala 3 support. -However their entry-point is higher than your usual scripting language. Pure functional programming has a reputation of being hard to learn, and Typelevel Toolkit is a way to play in that world, without learning an entire ecosystem first. - -The libraries in the toolkit compliment each other and target common usecases, thus providing a coherent mini-ecosystem, that scales extremely well, thanks to Cats Effect. **With support for other runtimes besides the JVM**. This means that your scripts can run in a [JavaScript environment](https://scala-cli.virtuslab.org/docs/guides/scala-js), thanks to [scala-js](https://www.scala-js.org/). Or you can use [scala-native](https://github.com/scala-native/scala-native) to get a [native binary](https://scala-cli.virtuslab.org/docs/guides/scala-native), just like Rust and Go! - -`scala-cli` again, makes things easy for us by having simple commands to compile to a certain target: - -```sh -# To compile to JavaScript -scala-cli Main.scala --js - -# To target native -scala-cli Main.scala --native -``` - -If you just want to explore Typelevel Toolkit, you can quickly open a REPL using the following command: - -```sh -scala-cli repl --dep "org.typelevel::toolkit::0.0.4" -``` - -With `scala-cli`, there a few other cool things you get here: -- [export current build into sbt or mill](https://scala-cli.virtuslab.org/docs/guides/sbt-mill) -- [create and share gists](https://scala-cli.virtuslab.org/docs/cookbooks/gists) -- [package your script using GraalVM](https://scala-cli.virtuslab.org/docs/cookbooks/native-images) - -# Summary - -`scala-cli` is great. It's easy to get started with and great to use. Typelevel Toolkit leverages its versatility and provides a "pure functional standard library" in a single directive. This will enable you to create and develop scripts fast with high refactorability, an awesome developer experience and lots of functions that compose well! All those benefits while remaining beginner-friendly. diff --git a/src/blog/update-about-sustainability-program.md b/src/blog/update-about-sustainability-program.md deleted file mode 100644 index 0c350910..00000000 --- a/src/blog/update-about-sustainability-program.md +++ /dev/null @@ -1,20 +0,0 @@ -{% - author: ${typelevel} - date: "2019-11-13" - tags: [governance] -%} - -# Update About Sustainability Program - -Six months ago, we launched the [Typelevel sustainability program](https://github.com/typelevel/general/blob/master/sustainability_program.md) to provide more ways for our community to help support Typelevel projects. Since then, we received numerous donations from individuals as well as corporations, now bringing our estimated annual budget to over $18,000. We are incredibly grateful for the generosity of our community. From the bottom of our hearts, thank you! - -Our original plan was to use the donation to hire a part-time dedicated maintainer to help support participating projects. That plan requires a higher annual budget, namely $150,000. We decided that we are going to adjust this plan to be more aligned with the current level of donations. We will start to distribute money received from donations directly to maintainers through [OpenCollective](https://opencollective.com/typelevel). -Maintainers of [pariticpating projects](https://github.com/typelevel/general/blob/master/sustainability_program.md#typelevel-libraries-in-the-program) who are interested in being paid report their hours to the [program admin](mailto:sponsor-contact@typelevel.org) each month. -If non-maintainer contributors are interested in contributing paid work, please request approvals from maintainers ahead of time. -At the end of the month, we calculate how much each maintainer can invoice based on total recorded hours and monthly budget received. Invoices can then be [submitted to opencollective](https://opencollective.com/typelevel/expenses/new) as expenses and paid. - -We believe that this new plan will support the long-term sustainability of our projects in a more budget flexible way. Please reach out to us if you have any concerns or feedback. We'll happily refund donors who prefer their donations not being distributed this way. - -From now on, please donate through the [Typelevel sustainability program's opencollective page](https://opencollective.com/typelevel). The old donorbox portal will be shut down. If you haven't, please consider talking to your employer about supporting the OSS libraries they are using. Our sponsor levels remain unchanged: annual donation from $2,000 to $5,000 for silver, from $5,000 to $10,000 for gold, and from $10,000 to $50,000 for platinum. Individual donations are much much appreciated too. The more budget we have, the more supported office hours the maintainers can have. - -Thanks again for your support! We look forward to continuing building a strong and active community for Functional programming in Scala. diff --git a/src/blog/using-scalaz-Unapply.md b/src/blog/using-scalaz-Unapply.md deleted file mode 100644 index 60063747..00000000 --- a/src/blog/using-scalaz-Unapply.md +++ /dev/null @@ -1,304 +0,0 @@ -{% - author: ${S11001001} - date: "2013-09-11" - tags: [technical] -%} - -# Using scalaz.Unapply - -Once you've started really taking advantage of Scalaz's typeclasses -for generic programming, you might have noticed a need to write -typelambdas to use some of your neat abstractions, or use syntax like -`traverse` or `kleisli` with a strangely-shaped type as an argument. -Here's a simple generalization, a `List`-based `traverse`. - -```scala -import scalaz.Applicative, scalaz.syntax.applicative._ - -def sequenceList[F[_]: Applicative, A](xs: List[F[A]]): F[List[A]] = - xs.foldRight(List.empty[A].point[F])((a, b) => ^(a, b)(_ :: _)) -``` - -This works fine for a while. - -```scala -scala> import scalaz.std.option._ -import scalaz.std.option._ - -scala> sequenceList(List(some(1),some(2))) -res1: Option[List[Int]] = Some(List(1, 2)) - -scala> sequenceList(List(some(1),none)) -res2: Option[List[Int]] = None -``` - -The problem ------------ - -The type of the input in the above example, `List[Option[Int]]`, can be -neatly destructured into the `F` and `A` type params needed by -`sequenceList`. It has the “shape” `F[x]`, so `F` can be picked out by -Scala easily. - -Consider something else with a convenient `Applicative` instance, -though. - -```scala -scala> import scalaz.\/ -import scalaz.$bslash$div - -scala> sequenceList(List(\/.right(42), \/.left(NonEmptyList("oops")))) -:23: error: no type parameters for method - sequenceList: (xs: List[F[A]])(implicit evidence$1: scalaz.Applicative[F])F[List[A]] - exist so that it can be applied to arguments - (List[scalaz.\/[scalaz.NonEmptyList[String],Int]]) - --- because --- -argument expression's type is not compatible with formal parameter type; - found : List[scalaz.\/[scalaz.NonEmptyList[String],Int]] - required: List[?F] - - sequenceList(List(\/.right(42), \/.left(NonEmptyList("oops")))) - ^ -``` - -Here, `?F` meaning it couldn't figure out that you meant `({type λ[α] -= NonEmptyList[String] \/ α})#λ`. - -```scala -scala> sequenceList[({type λ[α] = NonEmptyList[String] \/ α})#λ, Int - ](List(\/.right(42), \/.left(NonEmptyList("oops")))) -res5: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] = - -\/(NonEmptyList(oops)) -``` - -The problem is that `NonEmptyList[String] \/ Int` has the shape -`F[A, B]`, with `F` of kind `* -> * -> *` after a fashion, whereas the -`F` it wants must have kind `* -> *`, and Scala kinds aren't curried -at all. - -Finding an `Unapply` instance ------------------------------ - -`Unapply`, though, *does* have implicit instances matching the -`F[A, B]` shape, `unapplyMAB1` and `unapplyMAB2`, in its companion so -effectively always visible. What's special about them is that their -type parameters match the “shape” you're working with, `F[A, B]`. - -You should -[look at their source](https://github.com/scalaz/scalaz/blob/v7.0.3/core/src/main/scala/scalaz/Unapply.scala#L210) -to follow along. - -Let's see if one of them works. For implicit resolution to finish, -it's important that *exactly* one of them works. - -```scala -scala> import scalaz.Unapply -import scalaz.Unapply - -scala> Unapply.unapplyMAB1[Applicative, \/, NonEmptyList[String], Int] -:23: error: could not find implicit value for parameter -TC0: scalaz.Applicative[[α]scalaz.\/[α,Int]] - Unapply.unapplyMAB1[Applicative, \/, NonEmptyList[String], Int] - ^ - -scala> Unapply.unapplyMAB2[Applicative, \/, NonEmptyList[String], Int] -res7: scalaz.Unapply[scalaz.Applicative, - scalaz.\/[scalaz.NonEmptyList[String],Int]]{ - type M[X] = scalaz.\/[scalaz.NonEmptyList[String],X]; - type A = Int - } = scalaz.Unapply_0$$anon$13@5402af61 -``` - -Here, the type `res7.M` represents the typelambda being passed to -`sequenceList`. You can see that work. - -```scala -scala> sequenceList[res7.M, res7.A]( - List(\/.right(42), \/.left(NonEmptyList("oops")))) -res8: res7.M[List[res7.A]] = -\/(NonEmptyList(oops)) - -scala> res8 : NonEmptyList[String] \/ List[Int] -res9: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] = - -\/(NonEmptyList(oops)) -``` - -The `res8` conformance test shows that Scala can still reduce the -path-dependent `res7.M` and `res7.A` types at this level, outside -`sequenceList`. - -Searching for the right shape ------------------------------ - -Implicit resolution can pick the call to `unapplyMAB2` partly because -it can pick all of its type parameters without weird typelambda -structures. But in Scalaz, we use typeclasses to guide its choice. - -Why didn't `unapplyMAB1` work? In this case, you can trust `scalac` -to say exactly the right thing: it looked for -`Applicative[[α]scalaz.\/[α,Int]]`, and didn't find one. Sure enough, -`\/` being right-biased means we don't offer that instance. - -Incidentally, if you were to introduce that instance, you'd break code -relying on right-biased `Unapply` resolution to work. - -`unapplyMAB2` needs evidence of `TC[({type λ[α] = M0[A0, α]})#λ]`. -But that's okay, because we have that, where `TC=Applicative`, -`M0=\/`, and `A0=NonEmptyList[String]`! - -```scala -scala> Applicative[({type λ[α] = \/[NonEmptyList[String], α]})#λ] -res10: scalaz.Applicative[[α]scalaz.\/[scalaz.NonEmptyList[String],α]] - = scalaz.DisjunctionInstances2$$anon$1@2f658816 -``` - -Scala doesn't need to figure out any typelambda itself for this to -work; we did everything by putting the typelambda right into -`unapplyMAB2`'s evidence requirement, so it just has to find the -conforming implicit value. - -Using `Unapply` generically ---------------------------- - -Now you can write a `sequenceList` wrapper that works for `\/` and -many other shapes, including user-provided shapes in the form of new -`Unapply` implicit instances. If you're using Scala 2.9 (still?!) you -need to add `-Ydependent-method-types` to `scalacOptions` to write -this function. - -```scala -def sequenceListU[FA](xs: List[FA]) - (implicit U: Unapply[Applicative, FA] - ): U.M[List[U.A]] = - sequenceList(U.leibniz.subst(xs))(U.TC) -``` - -Instead of `xs` being `List[F[A]]`, it's `List[FA]`, and that's -destructured into `U.M` and `U.A`. The latter are path-dependent -types on `U`, the conventional name of the `Unapply` parameter. We -have also followed the convention of naming the `Unapply`-taking -variant function ending with a `U`. - -And that works great! - -```scala -scala> sequenceListU(List(\/.right(42), \/.left(NonEmptyList("oops")))) -res11: scalaz.\/[scalaz.NonEmptyList[String],List[Int]] = - -\/(NonEmptyList(oops)) -``` - -Of course, there's that strange-looking function body to consider, -still. - -Using the `U` evidence ----------------------- - -The type equalities of the original `U.M` and `U.A` to the original -types can be seen where `res8` is refined to `res9` above. But only -the *caller* of the function knows those equalities, because it -produced and supplied the `unapplyMAB2` call, which has a structural -type containing those equalities. - -The body of `sequenceListU` doesn't know those things. In particular, -it *still* can't pick type parameters to pass to `sequenceList` -without a little help. - -The `leibniz` member is a reified type equality of `FA === U.M[U.A]`, -meaning those are the same at the type level, even though Scala can't -see it in this context. It represents genuine evidence that those two -types are equal, and is much more powerful than scala-library's own -`=:=`. We're using the core Leibniz operator, `subst`, directly to -prove that, *as a consequence of that type equality*, `List[FA] === -List[U.M[U.A]]` is *also* a type equality, and that therefore this -(constant-time) coercion is valid. This lifting is applicable in all -contexts, not just covariant ones like `List`'s. Take a look at -[the full API](https://github.com/scalaz/scalaz/blob/v7.0.3/core/src/main/scala/scalaz/Leibniz.scala) -for more, though you'll typically just need to come up with the right -type parameter for `subst`. - -You can't ask for an `Unapply` and *also* ask for an -`Applicative[U.M]`; Scala won't allow it. So, because we needed to -resolve the typeclass anyway to find the `Unapply` implicit to use, we -just cart it along with the `U` and give it to the function, which -almost always needs to use it anyway. Because it's not implicitly -available, you usually need to grab it, `U.TC`, and use it directly. - -Using in `scalaz.syntax` ------------------------- - -`map` comes from functor syntax; it's not a method on `Function1`. So -how come this works? - -``` -scala> import scalaz.std.function._ -import scalaz.std.function._ - -scala> ((_:Int) + 42) map (_ * 33) -res13: Int => Int = - -scala> res13(1) -res14: Int = 1419 -``` - -When you import syntax, as `Functor` syntax was imported with -`scalaz.syntax.applicative._` above, you get at least two conversions: -the plain one, like `ToFunctorOps[F[_],A]`, which works if you have -the right shape, and the fancy one, `ToFunctorOpsUnapply[FA]`, which -uses an `Unapply` to effectively invoke `ToFunctorOps` as in the -above. The latter is lower-priority, so Scala will pick the former if -the value has the `F[A]` shape. - -That gives access to all the methods in `FunctorOps`, and other ops -classes, with only one special `U`-taking method. If you have several -functions operating on the same value type, or you can make that type -similar with Leibnizian equality as implicit arguments to your -methods, I suggest grouping them in this way, too, to cut down on -boilerplate. - -Provide both anyway -------------------- - -We sometimes get asked “why not just provide the `Unapply` version of -the function or ops?” - -We do it, and suggest it for your own code, despite the confusion, -because it's easier to work with real type equalities than with -Leibnizian equality, which you can do in your “real” function -implementation, and as seen in `res8` above, the path-dependent type -resolution can leave funny artifacts in the inferred result. Here's -an extreme example from -[an earlier demonstration](https://groups.google.com/d/msg/scalaz/9zAIGETrePI/o1rBsOcWJWAJ). - -```scala -scala> val itt = IdentityT lift it -itt: IdentityT[scalaz.Unapply[scalaz.Monad,IdentityT[scalaz.Unapply[scalaz.Monad,Identity[Int]] - {type M[X] = Identity[X]; type A = Int}#M, - Int]] - {type M[X] = IdentityT[scalaz.Unapply[scalaz.Monad,Identity[Int]] - {type M[X] = Identity[X]; type A = Int}#M, - X]; - type A = Int}#M, - Int] - = IdentityT(IdentityT(Identity(42))) -``` - -Credits -------- - -[Jason Zaugg](https://twitter.com/retronym) implemented Scalaz -`Unapply`, based on -[ideas](https://issues.scala-lang.org/browse/SI-2712?focusedCommentId=55239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-55239) -from [Miles Sabin](https://twitter.com/milessabin) and -[Paul Chiusano](https://github.com/pchiusano). - -Leibnizian equality was implemented for Scalaz by -[Edward Kmett](https://github.com/ekmett). - -[Lars Hupel](https://twitter.com/larsr_h)'s talk -([slides](https://speakerdeck.com/larsrh/seven-at-one-blow-new-and-polished-features-in-scalaz-7), -[video](https://www.youtube.com/watch?feature=player_embedded&v=KzoqOVD7mvE)) -on the features in the then-upcoming Scalaz 7 at nescala 2013, -including `Unapply`, gave me the missing “guided by typeclasses” -detail, inspiring me to tell more people about the whole thing at the -conference, and then, much later, write it down here. - -*This article was tested with Scala 2.10.2 & Scalaz 7.0.3.* diff --git a/src/blog/values-never-change-types.md b/src/blog/values-never-change-types.md deleted file mode 100644 index 942ce019..00000000 --- a/src/blog/values-never-change-types.md +++ /dev/null @@ -1,298 +0,0 @@ -{% - author: ${S11001001} - date: "2015-07-30" - tags: [technical] - katex: true -%} - -# Values never change types - -*This is the sixth of a series of articles on “Type Parameters and -Type Members”. If you haven’t yet, you should -[start at the beginning](type-members-parameters.md), -which introduces code we refer to throughout this article without -further ado.* - -In a subtyping language such as Java or Scala, first-class -existentials are a helpful ally for modeling values and methods that -abstract over subtypes in different ways. In a language with -mutation, such as Java or Scala, existentials help deal with certain -kinds of state changes without violating type safety. - -But values, *themselves*, never change types. When you first practice -with existentials, the Java and Scala compilers seem to become -veritable minefields of type errors—do something slightly different in -your code, and everything falls apart. But this is just about -[nothing being a free lunch](http://blog.higher-order.com/blog/2014/12/21/maximally-powerful/): -**the wide variety of values meeting any given existential type, -combined with the possibility for mutation, means a sound typechecker -must be very conservative about what it permits when using those -values**. - -So, in this article, we’ll explore some of these type error pitfalls, -see why it’s perfectly reasonable for the compilers to complain about -these pieces of code, and fix the errors using the equivalence rules -we’ve learned about in previous articles. This is a big topic, so in -the next article, we’ll talk about taking advantage of the freedoms -that the compilers are concerned about in this one. - -Aliasing prevents replacement ------------------------------ - -Let’s say you have a list of strings, `strs: List[String]`. But you -want that value to change: you want it to be a `List[Int]` instead. -Maybe every value in the list parses to an integer and you want to -“map in place”; maybe you want to use `-1` for every string that can’t -be parsed. - -Generic systems like those of Java, Scala, and the ML family don’t let -you do this for a few reasons. - -First, with regard to structures like `List`, let’s suppose we are -adding this feature to the type system, and that the list has type -`List[String]` before replacement, and `List[Int]` after. What type -does it have when we’re halfway done? The type system requires us to -*guarantee* that there are no strings, only ints, left once we’re -done; how do we track our progress? Remember that vague promises of -“I tested the code” are meaningless to the type system; you have to -*prove* it mathematically. This is a solvable problem, but the known -solutions far complicate the type system beyond the design goals of -these languages: the cost is far too high for the benefit. - -Second, let us suppose that we’ve solved the first problem. Or, let -us suppose that we introduce a structure for which this isn’t a -problem. - -```scala -final case class OneThing[T](var value: T) -``` - -There, no chance of “halfway done” there! But something else happens. - -```scala -val strs: OneThing[String] = OneThing("hi") -strs.value = 42 -// won't compile, just a thought experiment -``` - -Now, presumably, completely aside from the value, the variable `strs` -has *changed types* to `OneThing[Int]`. Not just the value in it, the -variable itself. OK, so what if that variable came from someplace -else? - -```scala -def replaceWithInt(ote: OneThing[_]): Unit = - t.value = 42 - -val strs: OneThing[String] = OneThing("hi") -replaceWithInt(strs) -// also won't compile, thought experiment -``` - -Now, the type of `replaceWithInt` must contain a note that “by the -way, the type of `ote`, and any variables that refer to it, and any -variables that refer to *those* variables, and so on until it stops, -will change as a result of this call”. - -This is a problem of *aliases*, all the locations that may refer to a -value. If you change the type of the value, you also have to update -every reference to it, at compile time! This is *the type system*; -your promise that you have no other references is not good enough. -You have to *prove* it. - -As with the previous problem, the known solutions to this problem -would complicate the type systems of Java and Scala beyond their -design goals. In a sense, **this aspect of their type systems can be -considered to encourage functional programming**. A type-changing map -that builds a new list of the new type, or what have you, instead of -mutating the old one, is *trivial* in the Java/Scala generics systems. -There are no chances of aliasing problems, because no one could -possibly have an unknown reference to the value you just constructed. -This is a even more obvious design choice in the ML family languages, -which make no secret of favoring functional programming. - -Assignment rewrites existentials --------------------------------- - -[We saw earlier](method-equiv.md) that a simple get from a -`List`, followed by adding that value right back to the same list, -didn’t work, but if we took that `xs` and passed it to a -type-parameterized version, everything worked fine. Why is that? - -If you have a *mutable* variable of an existential type, the -existentialized part of the type may have different (type) values at -different parts of the program. Let’s use -[the `StSource` from the projection post](type-projection.md#a-good-reason-to-use-type-members). -Note that the `S` member is existential, because we did not bind it. - -```scala -var mxs: StSource[String] = StSource(0)(x => (x.toString, x + 1)) -// at this point in the program, the S is Int -val s1 = mxs.init -// now I'm going to change mxs -mxs = StSource("ab")(x => (x, x.reverse)) -// at this point in the program, the S is String -mxs.emit(s1) - -TmTp6.scala:14: type mismatch; - found : tmtp6.Tmtp4Funs.s1.type (with underlying type tmtp4.StSource[String]#S) - required: _2.S where val _2: tmtp4.StSource[String] -mxs.emit(s1) - ^ -``` - -And it’s good that this happens, because the value we got from `init` -is definitely incompatible with the argument type to `emit`. - -If we don’t want to track when this happens—and we certainly can’t -decide, in all cases, when a mutable variable such as this has been -overwritten so as to change its existentials, given the freedoms -afforded by Java—how can we treat a mutable variable with existentials -as safe? The type system makes a simplifying assumption: *every -reference to the variable gets fresh values to fill in the -existentials*. - -If it helps, you can think of a mutable variable as an *immutable* -variable that wraps its type with an extra layer. In fact, that's -[what scalac does when you capture a `var`](https://github.com/scala/scala/blob/v2.11.7/src/library/scala/runtime/ObjectRef.java#L14-L17). -So `mxs` is, in a sense, of type `Ref[StSource[String]]`, where - -```scala -trait Ref[T] { - def value: T - def update(t: T): Unit -} -``` - -So, by substitution, the variable `mxs` is really a pair of functions, -`() => StSource[String]` and `StSource[String] => Unit`. The “getter” -returns `StSource[String]`; each time you invoke that getter, you -might get an `StSource[String]` with a different `S` member, because -the `forSome` effectively occurs inside the body, as described in -[the substitutions of “Nested existentials”](nested-existentials.md#what-if-we-list-different-existentials). - -Of course, this means you can take advantage of this in your own -designs, to get *some* of the behavior of a type-changing value -mutation. The use of variable references to delineate existentials -means that, even when we replace `mxs` above, the behavior of the -variable in the context of an instance hasn’t really changed, so -nothing about the containing value’s type has changed. We thus -preserve our property, that values never change types. - -When you make a single reference, it has to be consistent; subsequent -mutations have no effect on the value we got from that reference. So -we can pass that reference somewhere that asserts that this doesn’t -happen in its own context, such as a type-parameterized -@:math \equiv_{m} @:@ method. If you have a mutable variable -of type `List`, even if you don’t know what `T` is, you know that -any updates to that variable will keep the same `T`. - -Making variables read-only matters ----------------------------------- - -If I change the variable to `final` in Java, and remove mutation, I -shouldn’t have this problem anymore. Surprisingly, I do; this is what -happened in -[the original `copyToZero` example](method-equiv.md), -where the argument was declared `final`. I assume that this is just a -simplifying assumption in `javac`, that the extra guarantee of -unchanging existentials offered by `final` isn’t understood by the -compiler. - -In the case of Scala, though, **when you are using existential type -members, Scala can understand the implications of an immutable -variable, declared with `val`**. - -```scala -val imxs: StSource[String] = StSource(0)(x => (x.toString, x + 1)) -val s1 = imxs.init -// you can do whatever you want here; the compiler will stop you from -// changing imxs -imxs.emit(s1) -``` - -It can’t pull off this trick for type parameters, having just as much -trouble as Java there. So this is another reason for -[our original rule of thumb](type-members-parameters.md). - -Naming the existential ----------------------- - -The benefit we get from -[passing `copyToZeroP`’s argument to `copyToZeroT`](method-equiv.md) -is that we *name* the existential for the single reference to the -argument that we make. We name it `T` there, for the scope of its -invocation. - -Likewise, in Scala, each `val` introduces, while it is in scope, each -existential member it has, as a type name. There are -[a lot of rules in Scala](http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#paths) -for exactly when this happens, but you may want to simply experiment. -We got a hint of what that name is -[when we used `StSource` existentially in the REPL](type-projection.md#type-parameters-see-existentially). -Here’s the previous example again, with a type annotation for `s1`. - -```scala -val imxs: StSource[String] = StSource(0)(x => (x.toString, x + 1)) -val s1: imxs.S = imxs.init -imxs.emit(s1) -``` - -We have gained convenience, not power, with this *path-dependent -types* feature; we can always pass into a type-parameterized local -method, with only the inconvenience of having to write out the whole -polymorphic method and call dance. Moreover, this is nowhere near -[a solution to the type projection problem](type-projection.md#a-failed-attempt-at-simplified-emitting); -there are too many things that a type parameter can do that we can’t -with this feature. But we’ll dive into that in a later post. - -By-name existential arguments aren’t equivalent! ------------------------------------------------- - -There is another little corner case in method equivalence to consider. - -```scala -def copyToZeroNP(xs: => PList[_]): Unit -def copyToZeroNT[T](xs: => PList[T]): Unit -``` - -These method types are not equivalent! That’s because `copyToZeroNP` -can be called with a by-name *that evaluates to a list with a -different element type each time*; `copyToZeroNT` doesn’t allow this. - -```scala -def time: PList[_] = { - val t = System.currentTimeMillis - if (t % 2 == 0) PCons("even", PNil()) - else PCons(66, PNil()) -} - -copyToZeroNP(time) // ok -copyToZeroNT(time) // not ok -``` - -In effect, `=>` is like a type constructor; we can think of these -arguments as `byname[PList[_]]` and `byname[PList[T]]`. So we have -exactly the same problem as we had with -[`plenLength` and `plenLengthTP`](nested-existentials.md#method-equivalence-broken). - -Unfortunately, -[Scala currently accepts this, where it shouldn’t](https://issues.scala-lang.org/browse/SI-9419). - -The difference between these two methods gives us a hint about working -with existentials: if we can shift the scope for a given existential -outside the function that keeps giving us different types on every -reference, we might have an easier time working with it, even if we -can’t change it to a `val`; maybe it needs to be lazy and not saved, -for example. So, despite the occasional convenience of path-dependent -types, **type parameterized methods are still your best friends when -working with existential types**. - -In -[the next article, “To change types, change values”](change-values.md), -we’ll look at some programs that make use of the two kinds of “type -changing” discussed above. After that, we’ll finally talk about -methods that *return* values of existential type, rather than merely -taking them as arguments. - -*This article was tested with Scala 2.11.7.* diff --git a/src/blog/variance-and-functors.md b/src/blog/variance-and-functors.md deleted file mode 100644 index 0661c616..00000000 --- a/src/blog/variance-and-functors.md +++ /dev/null @@ -1,507 +0,0 @@ -{% - author: ${adelbertc} - date: "2016-02-04" - tags: [technical] -%} - -# Of variance and functors - -Scala's type system allows us to annotate type parameters with their variance: covariant, contravariant, invariant. -Variance allows us to define the subtyping relationships between type constructors – that is, under which -conditions `F[A]` is a subtype of `F[B]`. - -Similarly in functional programming, there are covariant functors, contravariant functors, and invariant functors. The -similarity in names is not coincidental. - -# Covariance -The common example is `List[+A]` which is covariant in its type parameter, denoted by the `+` next to the `A`. -A type constructor with a covariant type parameter means that if there is a subtyping relationship between the -type parameter, there is a subtyping relationship between the two instances of the type constructor. -For example if we have a `List[Circle]`, we can substitute it anywhere we have a `List[Shape]`. - -## Read -Another example of covariance is in parsing, for example in the following `Read` type class. - -```scala -trait Read[+A] { - def read(s: String): Option[A] -} -``` - -It makes sense to make `Read` covariant because if we can read a subtype, then we can read the supertype by -reading the subtype and throwing away the subtype-specific information. For instance, if we can read a -`Circle`, we can read a valid `Shape` by reading the `Circle` and ignoring any `Circle`-specific information. - -## Array -A type that cannot safely be made covariant is `Array`. If `Array` were covariant, we could substitute -an `Array[Circle]` for an `Array[Shape]`. This can get us in a nasty situation. - -```scala -val circles: Array[Circle] = Array.fill(10)(Circle(..)) -val shapes: Array[Shape] = circles // works only if Array is covariant -shapes(0) = Square(..) // Square is a subtype of Shape -``` - -If `Array` was covariant this would compile fine, but fail at runtime. In fact, Java arrays are -covariant and so the analogous Java code would compile, throwing an `ArrayStoreException` when -run. The compiler accepts this because it is valid to upcast an `Array[Circle]` into an `Array[Shape]`, -and it is valid to insert a `Shape` into an `Array[Shape]`. However the runtime representation of -`shapes` is still an `Array[Circle]` and inserting a `Square` into that isn't allowed. - -## Read-only and covariance -In general, a type can be made safely covariant if it is read-only. If we know how to read a specific type, we know -how to read a more general type by throwing away any extra information. `List` is safe to to make -covariant because it is immutable and we can only ever read information off of it. With `Array`, we -cannot make it covariant because we are able to write to it. - -## Functor -As we've just seen, covariance states that when `A` subtypes `B`, then `F[A]` subtypes `F[B]`. Put differently, -if `A` can be turned into a `B`, then `F[A]` can be turned into an `F[B]`. We can encode this behavior -literally in the notion of a `Functor`. - -```scala -trait Functor[F[_]] { - def map[A, B](f: A => B): F[A] => F[B] -} -``` - -This is often encoded slightly differently by changing the order of the arguments: - -```scala -trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] -} -``` - -We can implement `Functor` for `List` and `Read`. - -```scala -val listFunctor: Functor[List] = - new Functor[List] { - def map[A, B](fa: List[A])(f: A => B): List[B] = fa match { - case Nil => Nil - case a :: as => f(a) :: map(as)(f) - } - } - -val readFunctor: Functor[Read] = - new Functor[Read] { - def map[A, B](fa: Read[A])(f: A => B): Read[B] = - new Read[B] { - def read(s: String): Option[B] = - fa.read(s) match { - case None => None - case Some(a) => Some(f(a)) - } - } - } -``` - -With that we can do useful things like - -```scala -val circles: List[Circle] = List(Circle(..), Circle(..)) -val shapes: List[Shape] = listFunctor.map(circles)(circle => circle: Shape) // upcast - -val parseCircle: Read[Circle] = ... -val parseShape: Read[Shape] = readFunctor.map(parseCircle)(circle => circle: Shape) // upcast -``` - -or more generally: - -```scala -def upcast[F[_], A, B <: A](functor: Functor[F], fb: F[B]): F[A] = - functor.map(fb)(b => b: A) -``` - -`upcast`'s behavior does exactly what covariance does – given some supertype `A` (`Shape`) and a subtype `B` (`Circle`), -we can mechanically (and safely) turn an `F[B]` into an `F[A]`. Put differently, anywhere we expect an `F[A]` we can provide -an `F[B]`, i.e. covariance. For this reason, `Functor` is sometimes referred to in full as covariant functor. - -# Contravariance -Contravariance flips the direction of the relationship in covariance – an `F[Shape]` is considered a -subtype of `F[Circle]`. This seems strange – when I was first learning about variance I couldn't -come up with a situation where this would make sense. - -If we have a `List[Shape]` we cannot safely treat it as a `List[Circle]` – doing so comes with all the usual -warnings about downcasting. Similarly if we have a `Read[Shape]`, we cannot treat it as a `Read[Circle]` – -we know how to parse a `Shape`, but we don't know how to parse any additional information `Circle` may need. - -## Show -It appears fundamentally read-only types cannot be treated as contravariant. However, given that contravariance -is covariance with the direction reversed, can we also reverse the idea of a read-only type? Instead of reading -a value *from* a `String`, we can write a value *to* a `String`. - -```scala -trait Show[-A] { - def show(a: A): String -} -``` - -`Show` is the other side of `Read` – instead of going from a `String` to an `A`, we go from an `A` into -a `String`. This reversal allows us to define contravariant behavior – if we are asked to provide a way -to show a `Circle` (`Show[Circle]`), we can give instead a way to show just a `Shape`. This is a valid -substitution because we can show a `Circle` by throwing away `Circle`-specific information and showing just -the `Shape` bits. This means that `Show[Shape]` is a subtype of `Show[Circle]`, despite `Circle` being a -subtype of `Shape`. - -In general, we can show (or write) a subtype if we know how to show a supertype by tossing away subtype-specific -information (an upcast) and showing the remainder. Again, this means `Show[Supertype]` is substitutable, or a -subtype of, `Show[Subtype]`. - -For similar reasons that read-only types can be made covariant, write-only types can be made contravariant. - -## Array, again -`Array`s cannot be made contravariant either. If they were, we could do unsafe reads: - -```scala -val shapes: Array[Shape] = Array.fill(10)(Shape(..), Shape(..)) -val circles: Array[Circle] = shapes // Works only if Array is contravariant -val circle: Circle = circles(0) -``` - -`circle`, having been read from an `Array[Circle]` has type `Circle`. To the compiler this would be fine, but -at runtime, the underlying `Array[Shape]` may give us a `Shape` that is not a `Circle` and crash the program. - -## Contravariant -Our `Functor` interface made explicit the behavior of covariance - we can define a similar interface that -captures contravariant behavior. If `B` can be used where `A` is expected, then `F[A]` can be used where an -`F[B]` is expected. To encode this explicitly: - -```scala -trait Contravariant[F[_]] { - // Alternative encoding: - // def contramap[A, B](f: B => A): F[A] => F[B] - - // More typical encoding - def contramap[A, B](fa: F[A])(f: B => A): F[B] -} -``` - -We can implement an instance for `Show`. - -```scala -val showContravariant: Contravariant[Show] = - new Contravariant[Show] { - def contramap[A, B](fa: Show[A])(f: B => A): Show[B] = - new Show[B] { - def show(b: B): String = { - val a = f(b) - fa.show(a) - } - } - } -``` - -Here we are saying if we can show an `A`, we can show a `B` by turning a `B` into an `A` before showing it. -Upcasting is a specific case of this, when `B` is a subtype of `A`. - -```scala -def contraUpcast[F[_], A, B >: A](contra: Contravariant[F], fb: F[B]): F[A] = - contra.contramap(fb)((a: A) => a: B) -``` - -Going back to `Shape`s and `Circle`s, we can show a `Circle` by upcasting it into a `Shape` and showing that. - -# Function variance -We observed that read-only types are covariant and write-only types are contravariant. This can be -seen in the context of functions and what function types are subtypes of others. - -## Parameters -An example function: - -```scala -// Right now we only care about the input -def squiggle(circle: Circle): Unit = ??? - -// or - -val squiggle: Circle => Unit = ??? -``` - -What type is a valid subtype of `Circle => Unit`? An important note is we're not -looking for what subtypes we can *pass in* to the function, we are looking for a value with a type -that satisfies the entirety of the function type `Circle => Unit`. - -A first guess may involve some subtype of `Circle` like `Dot` (a circle with a radius of 0), such -as `Dot => Unit`. - -```scala -val squiggle: Circle => Unit = - (d: Dot) => d.someDotSpecificMethod() -``` - -This doesn't work – we are asserting with the moral equivalent of a downcast that any -`Circle` input to the function is a `Dot`, which is not safe to assume. - -What if we used a supertype of `Circle`? - -```scala -val squiggle: Circle => Unit = - (s: Shape) => s.shapeshift() -``` - -This is valid – from the outside looking in we have a function that takes a `Circle` and -returns `Unit`. Internally, we can take any `Circle`, upcast it into a `Shape`, and go from there. -Showing things a bit differently reveals better the relationship: - -```scala -type Input[A] = A => Unit -val inputSubtype: Input[Shape] = (s: Shape) => s.shapeshift() -val input: Input[Circle] = inputSubtype -``` - -We have `Input[Shape] <: Input[Circle]`, with `Circle <: Shape`, so function parameters are contravariant. - -The type checker enforces this when we try to use covariant type parameters in contravariant positions. - -```scala -scala> trait Foo[+A] { def foo(a: A): Int = 42 } -:15: error: covariant type A occurs in contravariant position in type A of value a - trait Foo[+A] { def foo(a: A): Int = 42 } - ^ -``` - -Since type parameters are contravariant, a type in that position cannot also be covariant. To solve this -we "reverse" the constraint imposed by the covariant annotation by parameterizing with a supertype `B`. - -```scala -scala> trait Foo[+A] { def foo[B >: A](a: B): Int = 42 } -defined trait Foo -``` - -## Return -Let's do the same exercise with function return types. - -```scala -val squaggle: Unit => Shape = ??? -``` - -Since using the supertype seemed to work with parameters, let's pick a supertype here, `Object`. - -```scala -val squaggle: Unit => Shape = - (_: Unit) => somethingThatReturnsObject() -``` - -For similar issues with using a subtype for the input parameter, we cannot use -a supertype for the output. The function type states the return type is `Shape`, but we're -returning an `Object` which may or may not be a valid `Shape`. As far as the type checker is concerned, -this is invalid and the checker rejects the program. - -Trying instead with a subtype: - -```scala -val squaggle: Unit => Shape = - (_: Unit) => Circle(..) -``` - -This makes sense – the function type says it returns a `Shape` and inside we return a `Circle` which is -a perfectly valid `Shape`. - -As before, rephrasing the type signatures leads to some insights. - -```scala -type Output[A] = Unit => A -val outputSubtype: Output[Circle] = (_: Unit) => Circle(..) -val output: Output[Shape] = outputSubtype -``` - -That is `Output[Circle] <: Output[Shape]` with `Circle <: Shape` – function return types are covariant. - -Again the type checker will enforce this: - -```scala -scala> trait Bar[-A] { def bar(): A = ??? } -:15: error: contravariant type A occurs in covariant position in type ()A of method bar - trait Bar[-A] { def bar(): A = ??? } - ^ -``` - -As before, we solve this by "reversing" the contraint imposed by the variance annotation. - -```scala -scala> trait Bar[-A] { def bar[B <: A](): B = ??? } -defined trait Bar -``` - -## All together now -Function inputs are contravariant and function outputs are covariant. Taking the previous examples together, -a function type `Shape => Circle` can be put in a place expecting a function type `Circle => Shape`. - -We arrived at this conclusion by observing the behavior of subtype variance and the corresponding functors. Taken -in the context of functional programming where the only primitive is a function, we can draw a conclusion in -the other direction. Where function inputs are contravariant, types in positions where computations are -done (e.g. input or read-only positions) are also contravariant (similarly for covariance). - -# Invariance -Unannotated type parameters are considered invariant – the only relationship that holds is if a type `A` -is equal to a type `B`, then `F[A]` is equal to `F[B]`. Otherwise different instantiations of a -type constructor have no relationship with one another. Given invariant -`F[_]`, an `F[Circle]` is not a subtype of `F[Shape]` – you need to explicitly provide the conversion. - -## Array once more -`Array`s are invariant in Scala because they can be neither covariant nor contravariant. If we make it -covariant, we can get unsafe writes. If we make it contravariant, we can get unsafe reads. Since -read-only types can only be covariant and write-only types contravariant, our compromise is to make -types that support both invariant. - -In order to treat an `Array` of one type as an `Array` of another, we need to have conversions -in both directions. This must be provided manually as the type checker has no way of knowing what the -conversion would be. - -## Invariant -Similar to (covariant) `Functor` and `Contravariant`, we can write `Invariant`. - -```scala -trait Invariant[F[_]] { - def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] -} -``` - -For demonstration purposes we write our own `Array` type - -```scala -class Array[A] { - private var repr = ListBuffer.empty[A] - - def read(i: Int): A = - repr(i) - def write(i: Int, a: A): Unit = - repr(i) = a -} -``` - -and define `Invariant[Array]`. - -```scala -val arrayInvariant: Invariant[Array] = - new Invariant[Array] { - def imap[A, B](fa: Array[A])(f: A => B)(g: B => A): Array[B] = - new Array[B] { - // Convert read A to B before returning – covariance - override def read(i: Int): B = - f(fa.read(i)) - - // Convert B to A before writing – contravariance - override def write(i: Int, b: B): Unit = - fa.write(i, g(a)) - } - } -``` - -## Serialization -Another example of a read-write type that doesn't involve `Array`s (or mutation) can be -found by just combining the `Read` and `Show` interfaces: - -```scala -trait Serializer[A] extends Read[A] with Show[A] { - def read(s: String): Option[A] - def show(a: A): String -} -``` - -`Serializer` both reads (from a `String`) and writes (to a `String`). We can't make it -covariant because that would cause issues with `show`, and we can't make it contravariant -because that would cause issues with `read`. Therefore our only choice is to keep it -invariant. - -```scala -val serializerInvariant: Invariant[Serializer] = - new Invariant[Serializer] { - def imap[A, B](fa: Serializer[A])(f: A => B)(g: B => A): Serializer[B] = - new Serializer[B] { - def read(s: String): Option[B] = fa.read(s).map(f) - def show(b: B): String = fa.show(g(b)) - } - } -``` - -# Bringing everything together -We can see the `Invariant` interface is more general than both `Functor` and `Contravariant` – -where `Invariant` requires functions going in both directions, `Functor` and `Contravariant` only -require one. We can make `Functor` and `Contravariant` subtypes of `Invariant` by ignoring -the direction we don't care about. - -```scala -trait Functor[F[_]] extends Invariant[F] { - def map[A, B](fa: F[A])(f: A => B): F[B] - - def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = - map(fa)(f) -} - -trait Contravariant[F[_]] extends Invariant[F] { - def contramap[A, B](fa: F[A])(f: B => A): F[B] - - def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = - contramap(fa)(g) -} -``` - -Going back to treating `Array` and `Serializer` as a read/write store, if we make it read-only (like a read-only -handle on a resource) we can safely treat it as if it were covariant. If we are asked to read -`Shape`s and we know how to read `Circle`s, we can read a `Circle` and upcast it into a `Shape` -before handing it over. - -Similarly if we make it write-only (like a write-only handle on a resource) we can safely treat -it as contravariant. If we are asked to store `Circle`s and we know how to store `Shape`s, -we can upcast each `Circle` into a `Shape` before storing it. - -Variance manifests in two levels: one at the type level where subtyping relationships are defined, and -the other at the value level where it is encoded as an interface which certain types can conform to. - -## One more thing -Thus far we have seen the three kinds of variances Scala supports: - -```scala -1. invariance: A = B → F[A] = F[B] -2. covariance: A <: B → F[A] <: F[B] -3. contravariance: A >: B → F[A] <: F[B] -``` - -This gives us the following graph: - -``` - invariance - ↑ ↑ - / \ - - + -``` - -Completing the diamond implies a fourth kind of variance, one that takes contravariance and -covariance together. This is known as phantom variance or anyvariance, a variance with no constraints on the -type parameters: `F[A] = F[B]` regardless of what `A` and `B` are. Unfortunately Scala's type system is -missing this kind of variance which leaves us just short of a nice diamond, but we can still encode it -in an interface. - -```scala -trait Phantom[F[_]] { - def pmap[A, B](fa: F[A]): F[B] -} -``` - -Given any `F[A]`, we can turn that into an `F[B]`, for all choices of `A` and `B`. With this power we can -implement covariant and contravariant functors. - -```scala -trait Phantom[F[_]] extends Functor[F] with Contravariant[F] { - def pmap[A, B](fa: F[A]): F[B] - - def map[A, B](fa: F[A])(f: A => B): F[B] = pmap(fa) - - def contramap[A, B](fa: F[A])(f: B => A): F[B] = pmap(fa) -} -``` - -This completes our diamond of variance. - -``` - invariance - ↑ ↑ - / \ - - + - ↑ ↑ - \ / - phantom -``` diff --git a/src/blog/variance-phantom.md b/src/blog/variance-phantom.md deleted file mode 100644 index 8991c91d..00000000 --- a/src/blog/variance-phantom.md +++ /dev/null @@ -1,378 +0,0 @@ -{% - author: ${S11001001} - date: "2016-09-19" - tags: [technical] -%} - -# Choosing variance for a phantom type - -When you use a type parameter to abstract over actual data in your -ADT, there is typically only one -[variance](variance-and-functors.md) that makes -sense, if you choose to incorporate subtyping into your designs at -all. This is -[the natural, “parametrically sound” variance](liskov-lifting.md#parametrically-sound-covariance). - -```scala -sealed abstract class MyModel[P, I, -T, +V] - -final case class Running[I, T, V]( - run: (I, T) => V -) extends MyModel[String, I, T, V] - -final case class Inject[I]( - config: I -) extends MyModel[Int, I, Any, Nothing] -``` - -There are only four interesting possibilities, each of which is -illustrated above. - -1. `V` occurs in only covariant positions, so can be marked covariant. -2. `T` occurs in only contravariant positions, so can be marked - contravariant. -3. `I` occurs in a pattern that meets neither of standards 1 and 2, so - may only be marked invariant, while still making sense. -4. `P` meets *both* standards 1 and 2, so…now what? - -The fourth case is interesting to me, firstly, because the design of -variance in Scala has not accounted for it; it is “phantom”, -[the missing fourth variance](variance-and-functors.md#one-more-thing). -I like to write it as I did in -[“The missing diamond of Scala variance”](https://failex.blogspot.com/2016/09/the-missing-diamond-of-scala-variance.html): - -```scala -sealed abstract class MyModel[👻P, I, -T, +V] -``` - -Second, and more practically, it illuminates the role of variance in -pattern matching in a way that can be difficult to see with that -confusing data in the way. - -## It can be covariant - -The rule for a type parameter being parametrically covariant says we -have to look at all the positions in the data where the type parameter -occurs; if every one of them is a covariant position, then the type -parameter may be marked covariant. Consider a simplified version of -`MyModel`. - -```scala -sealed abstract class Gimme[P] -case object AStr extends Gimme[String] -case object AnInt extends Gimme[Int] -``` - -The type parameter `P` appears in no positions, so it vacuously -satisfies the requirement “every occurrence is in covariant position”. - -So let us mark `P` covariant. - -```scala -sealed abstract class Gimme[+P] -// otherwise the same -``` - -## It can be contravariant - -The rule for contravariance is also based on the occurrences of the -type parameter: if every occurrence is in contravariant position, then -the type parameter may be contravariant. - -This rule seems to be contradict the rule for covariance, except that -all “every” statements are always true when the set under -consideration is empty. - -1. Set **S** is empty. -2. Every element of set **S** is a dog. -3. No element of set **S** is a dog. - -2 and 3 can be true at the same time, but only if 1 is true, too. So -let us mark `P` contravariant, in a renamed ADT. - -```scala -sealed abstract class Gotme[-P] -case object UnStr extends Gotme[String] -case object UnInt extends Gotme[Int] -``` - -## The usual relationships - -Since you can choose any variance for phantom parameters, the -important question is: what kind of type relationships should -exist within my ADT? - -At first, this seems to be merely a question of how values of `Gimme` -and `Gotme` types ought to widen. - -1. Every `Gimme[Cat]` is a `Gimme[Animal]`, and -2. every `Gotme[Animal]` is a `Gotme[Cat]`. Moreover, -3. every `Gimme[Nothing]` is a `Gimme[T]` no matter what `T` is, and -4. every `Gotme[Any]` is a `Gotme[T]` no matter what `T` is. - -Obviously, if neither of these behaviors—the 1/3 nor the 2/4—is -desirable, you shouldn’t use variance. In my experience, this is the -case for most phantom types. If one is desirable, then it may be fine, -but there’s more to consider. - -## Extracting the covariant - -Pattern-matching on the covariant `Gimme` reveals fully safe type -information. Unlike `ClassTag` and `TypeTag`, which are egregiously -broken for this use case, this method of carrying type information -forward into runtime is closed and -[Scalazzi](https://imgur.com/a04WoHn)-safe. - -What type information is revealed? - -```scala -def gimme[P](g: Gimme[P]): (P, P) = g match { - case AStr => - // implicitly[P =:= String] will fail - // implicitly[P <:< String] will fail - implicitly[String <:< P] - ("hi", "there") - case AnInt => (42, 84) -} -``` - -If we left `Gimme`’s type parameter invariant, all three tests above -would succeed. In the case of this code, on the other hand, - -1. `AStr.type` (the type of `AStr`) widens to `Gimme[String]`, -2. `Gimme[String]` can widen to `Gimme[P]` as long as `P` is a - *supertype* of `String`. - -Because we’re reversing this process, we have to assume that #2 could -have happened. - -The expression `("hi", "there")` still compiles because `P`, while -otherwise mysterious, *surely is* a supertype of `String`. So the two -`String`s can widen to `P`. - -Things do not work out so well for all such functions. - -## Extracting the contravariant - -Matching on the contravariant `Gotme` likewise reveals fully safe -type information. - -```scala -def mklength[P](g: Gotme[P]): P => Int = g match { - case UnStr => - // implicitly[P =:= String] will fail - implicitly[P <:< String] - // implicitly[String <:< P] will fail - (s: String) => s.length - case UnInt => identity[Int] -} -``` - -Now `P <:< String`, which failed for the covariant form but succeeds -for the contravariant. On the other hand, we lost `String <:< P`, -which only works for the covariant form. That’s because - -1. `UnStr.type` widens to `Gotme[String]`; -2. `Gotme[String]` can widen to `Gotme[P]` as long as `P` is a - *subtype* of `String`. - -In the covariant form, we knew that every `String` was a `P`. In this -code, we know instead that every `P` is a `String`. Functions that can -handle any `String` are thus able to handle any `P`, logically, so the -type `String => Int` widens to `P => Int`. - -## Extracting the invariant - -`gimme` would not work with the contravariant GADT; likewise, -`mklength` would not work with the covariant GADT. - -An invariant GADT supports both, as well as some supported by -neither. For example, we could produce a `(P, P) => P` from a pattern -match. We can do this because the equivalent of `AStr` for invariant -`Gimme` tells us `P = String`, so all three `implicitly` checks -succeed. - -From the behavior of pattern matching over these three sorts of GADTs, -I take away two lessons about variance in Scala. - -1. It is impractical to infer variance in Scala, because you cannot - mechanically infer what sort of GADT pattern matching functions - ought to be possible to write. -2. The type flexibility of a generic type with variance comes at the - cost of decreased flexibility in pattern-matching - code. [There ain’t no such thing as a free lunch.](https://en.wikipedia.org/wiki/TANSTAAFL) - -## A GADT skolem - -The “reverse widening” of pattern matching lifts the veil on one of -the more confusing references in type errors, a “GADT skolem”. - -```scala -def uncons[A](as: List[A]): Option[::[A]] = as match { - case c@(_ :: _) => Some(c) - // ↑ - // [error] type mismatch; - // found : ::[?A1] where type ?A1 <: A - // (this is a GADT skolem) - // required: ::[A] - case _ => None -} -``` - -These “GADT skolems” appear all the time in sensible, compiling -code. Take a `List` with some variance carelessly tossed in. - -```scala -sealed abstract class MyList[+A] -final case class MyCons[A](head: A, tail: MyList[A]) - extends MyList[A] -case object MyNil extends MyList[Nothing] -``` - -Constructing `MyCons[String]`, here’s what can happen. - -1. `MyCons[String]` widens to `MyList[String]`. -2. `MyList[String]` can widen to `MyList[U]` for any supertype `U` of - `String`. - -So in this code, we cannot reverse `MyList[A]` down to -`MyCons[A]`. But we *can* get `MyList[L]`, where `L` is an otherwise -mysterious subtype of `A`. `L` is the GADT skolem, similar to `?A1` in -the above compiler error. The difference is that this code compiles. - -```scala -def drop1[A](as: MyList[A]): MyList[A] = - as match { - case MyNil => MyNil - case MyCons(_, tl) => tl - // tl: MyList[L] (L is a GADT skolem) - // L <: A, therefore - // MyList[L] <: MyList[A] by covariance - } -``` - -## `MyList`’s type parameter is a phantom - -We saw earlier that variance has a strong influence on the usability -of pattern matching. `MyList` has something important in common with -`Gimme`: the class definition does not use `A`, it only *defines* -it. So the scalac-enforced variance rules do not apply, and we can -make `MyList` contravariant instead. - -```scala -sealed abstract class BadList[-A] -final case class BadCons[A](head: A, tail: BadList[A]) - extends BadList[A] -case object BadNil extends BadList[Any] -``` - -Curiously, `drop1` still works. - -```scala -def baddrop1[A](as: BadList[A]): BadList[A] = - as match { - case BadNil => BadNil - case BadCons(_, tl) => tl - // tl: BadList[U] (U is a GADT skolem) - // A <: U, therefore - // BadList[U] <: BadList[A] by contravariance - } -``` - -Other obvious functions will not work for non-obvious reasons. - -```scala -def badHeadOption[A](as: BadList[A]): Option[A] = - as match { - case BadNil => None - case BadCons(hd, _) => Some(hd) - // [error] type mismatch; ↑ - // found : hd.type (with underlying type Any) - // required: A - } -``` - -This fails because the skolem from a contravariant parameter is a -supertype instead of subtype. So - -1. `hd: U` (`U` is a GADT skolem), -2. `A <: U`, -3. we’re stuck; there is no `A` value. - -This is not to imply something as silly as “covariance good, -contravariance bad”; you can just as well get these errors by marking -a parameter covariant that can only meaningfully be marked -contravariant. If anything, contravariance is more important than -covariance. The problem you must face is that the compiler is less -helpful in determining what “meaningful” marking, if any, should be -applied. - -`MyModel`, from the beginning of this article, demonstrates three -situations in which each supported variance is natural. You may use it -as a guide, but its sanity is not compiler-checked. Your variances’ -sanity, or lack thereof, only becomes apparent when implementing -practical functions over a datatype. - -## Extracting the phantom - -Suppose the phantom variance was defined, and we revisit the -`String`-and-`Int` GADT one more time. - -```scala -sealed abstract class BooGimme[👻P] -case object BooStr extends BooGimme[String] -case object BooInt extends BooGimme[Int] -``` - -The trouble with letting the compiler infer covariance or -contravariance is that, on the face of it, either is as good as the -other. With phantom, we choose both. - -But this variance makes the GADT utterly useless. Consider how -`BooStr` becomes `BooGimme[P]`. - -1. `BooStr` widens to `BooGimme[String]`. -2. `BooGimme[String]` can widen to `BooGimme[P]` where `P` is…oops, - there are no conditions this time! `P` can be anything at all and - the widen will still work. - -The match tells us nothing about the type parameter; all three of the -type relationship checks via `implicitly` from the examples above -fail. We maximize the flexibility of the type parameter at the cost of -making GADT pattern matching impossible. - -Likewise, if you mark `MyList[A]`’s type parameter phantom, there are -no bounds on the GADT skolem, so there’s little you can do with the -elements of the list. - -## The case for choosing no variance - -My `scalac` error message pet peeve is the one suggesting that you -should add a variance annotation. This message treats the addition of -variance like a mechanical change: “if it compiles, it works”. On the -contrary, we have seen that - -1. The flexibility of variance costs flexibility elsewhere; -2. the compiler cannot predict how this might harm your APIs’ - practicality; -3. the semantics of pattern matching are more complex in the face of - variance. - -Even if variance is applicable to your datatype, these costs, and the -cost of the additional complexity burden, should give you pause. Yet, -I stand by the claim I made in “The missing diamond -of Scala variance”: subtyping is incomplete without variance, so if -variance is too complicated, so is subtyping. - -I don’t think subtyping—and its necessary component, variance—are too -complex for the working programmer to understand. Indeed, it can be a -fascinating exercise, with plenty of practical implications. - -But, to me, the consequence of working out such exercises is that -neither variance nor subtyping ought to be used in the design of -practical programs, especially when higher-kinded type parameters and -members are available, offering far more flexibility at a better -price. There is no need to struggle in the face of all-too-often -missing features. - -*This article was tested with Scala 2.11.8.* diff --git a/src/blog/weaver-test-release.md b/src/blog/weaver-test-release.md deleted file mode 100644 index 008567bd..00000000 --- a/src/blog/weaver-test-release.md +++ /dev/null @@ -1,31 +0,0 @@ -{% - author: ${zainabali} - date: "2025-06-10" - tags: [technical] -%} - -# Typelevel Weaver released - -We are delighted to announce the release of [weaver-test](https://typelevel.org/weaver-test/) under Typelevel. - -# What is weaver? - -Weaver is a test framework for integration and end-to-end testing. It makes tests faster and easier to debug by using `cats`, `cats-effect` and `fs2`. - -Weaver provides a high quality experience when writing and running tests: - -- Tests within a suite are run in parallel for the quickest results possible. This is especially suited to IO heavy tests, such as those making API calls or reading files. -- Expectations (ie assertions) are composable values. This enables - developers to separate the scenario of the test from the checks they perform, - generally keeping tests cleaner and clearer. -- Failures are aggregated and reported at the end of the run. This prevents the developer from having to "scroll up" forever when trying to understand what failed. -- A lazy logger is provided for each test, and log statements are only displayed in case of a test failure. This lets the developer enrich their tests with clues and works perfectly well with parallel runs. Even though all tests are run in parallel, the developer can browse a sequential log of the test failure. -- “beforeAll” and “afterAll” logic is represented using a `cats.effect.Resource`. This ensures that shared resources, such as HTTP clients, connection pools and file handles, are cleaned up correctly and predictably. - -# Why is weaver moving under the Typelevel umbrella? - -Weaver makes heavy use of the `cats-effect` and `fs2` Typelevel projects. These enable weaver to run tests concurrently, provide safe resource handling, composable assertions and much more. By becoming part of the Typelevel umbrella, weaver can be maintained more easily alongside its core dependencies. - -# Migrating to the `0.9.0` release - -If you use [Scala Steward](https://github.com/scala-steward-org/scala-steward), you will migrate automatically. If not, read the [`0.9.0` migration guide](https://github.com/typelevel/weaver-test/releases/tag/v0.9.0). diff --git a/src/blog/welcoming-new-steering-committee-members.md b/src/blog/welcoming-new-steering-committee-members.md deleted file mode 100644 index 57fb8aa8..00000000 --- a/src/blog/welcoming-new-steering-committee-members.md +++ /dev/null @@ -1,63 +0,0 @@ -{% - author: ${typelevel} - date: "2022-07-25" - tags: [governance] -%} - -# Welcoming New Steering Committee Members - -Since our [last post][last-post], the [Typelevel Steering Committee][committee] has happily welcomed six new members to our group: Zach McCoy, Sam Pillsworth, Jasna Rodulfa-Blemberg, Andrew Valencik, Vasil Vasilev, and Mark Waks (aka Justin du Coeur). In their own words: - -[Zach McCoy][zach] (he/him) - -I've been programming in Scala professionally for 10 years. I currently work at [Jack Henry][jack-henry] where I get to work in Scala and deal with observability in systems. I'm based in Iowa where I work remotely. I can usually be found reading or playing Magic: The Gathering. - -[Sam Pillsworth][sam] (she/her) - -I’ve been writing code in industry for about 7 years, and the programming before that doesn’t count because it was in Fortran. Currently I'm a senior data developer writing Scala to power search in the [Shopify][shopify] [Help Center][shopify-helpcenter]. I’m based out of Toronto, Ontario, Canada. In my free time I read a lot of books, play with my dog Janeway, and futz with my emacs config. - -[Jasna Rodulfa-Blemberg][jasna] (she/they) - -I've been programming in Scala for the past 5 years, and more broadly in the JVM for most of my 9-year career. I currently work for [Etsy][etsy], where I help scale and maintain their search platform using Scala. I work remotely from the Washington, DC, USA area with my partner and the wild rabbits in my yard. - -I love cooking, gaming, reading, writing, and staying fit. More recently, I've begun reviving my Japanese language skills from college. - -[Andrew Valencik][andrew] (he/him) - -I've been writing Scala for 6ish years, on and off. Currently I manage a data science team and a developer team at [Shopify][shopify] working on search systems in the support area. I live in Ottawa, Ontario, Canada. - -I like cooking when I have all day to do it, being outside, and rotating through hobbies. - -[Vasil Vasilev][vasil] (he/him) - -I've been doing Scala for about 5 years now and for about 3 years in the open-source community. I'm a software developer at [JetBrains][jetbrains], working on the Scala Plugin team out of Munich, Germany. - -I like to split my free time between cycling, gaming and reading, as well as Scala open-source contributions, especially Cats Effect. - -[Mark Waks, aka Justin du Coeur][justin] (he/him) - -I've been programming for about 40 years in a wide variety of languages, the past 10 full-time in Scala. My current dayjob is at [Troops][troops] (now part of Slack/Salesforce), working remotely from Somerville, MA; in my spare time, I run [Querki][querki], a sort of wiki/database hybrid written entirely in Scala. - -I'm a pretty generalized geek, active in SCA, LARP, SF/F fandom, board games, comics, etc. I'm in charge of the Boston Scala Meetup, and on the board of the NE Scala conference (both currently dormant due to covid). - --------- - -These new members are excited to continue Typelevel's efforts to foster an inclusive, welcoming, and safe environment around functional programming in Scala. You can find the members throughout the [Typelevel GitHub space][github] and on our [Discord server][discord] with the `@steering` role. - -[andrew]: https://github.com/valencik -[committee]: https://github.com/typelevel/governance/blob/main/STEERING-COMMITTEE.md -[discord]: https://sca.la/typeleveldiscord -[etsy]: https://etsy.com -[github]: https://github.com/typelevel -[jack-henry]: https://www.jackhenry.com/pages/default.aspx -[jasna]: https://github.com/JasnaMRB -[jetbrains]: https://jetbrains.co -[justin]: https://github.com/jducoeur -[last-post]: call-for-steering-committee-members.md -[querki]: https://querki.net/ -[sam]: https://github.com/samspills -[shopify]: https://www.shopify.com -[shopify-helpcenter]: https://help.shopify.com/en -[troops]: https://www.troops.ai/ -[vasil]: https://github.com/vasilmkd -[zach]: https://github.com/zmccoy diff --git a/src/blog/who-implements-typeclass.md b/src/blog/who-implements-typeclass.md deleted file mode 100644 index 312b76d1..00000000 --- a/src/blog/who-implements-typeclass.md +++ /dev/null @@ -1,573 +0,0 @@ -{% - author: ${S11001001} - date: "2017-12-20" - tags: [technical] -%} - -# Who implements the typeclass instance? - -The typeclass pattern in Scala invites you to place -implementation-specific knowledge directly in the typeclass instances, -with the interface defined as the typeclass’s abstract interface. - -However, GADTs permit a different organization of code. It is even -possible to define a typeclass that seems to do nothing at all, yet -still permits full type-safe typeclass usage. - -The possibilities between these two extremes form a design space. If -you wish to practice ad-hoc polymorphism in Scala, this space is well -worth exploring. - -## A glorified overloader - -Refactoring a set of overloads into a typeclass is a fine way to get -some free flexibility and dynamism, because expressing overloads as a -typeclass gives you free fixes for common overload problems. - -1. Methods calling the overloaded method do not themselves need to be - overloaded just to avoid suppressing the flexibility of the - overload beneath. (See `addThree` and `zipAdd` below for - examples.) -1. Return-type overloading works, even in Scala, where it does not - when attempting to write overloads in the Java style, i.e. multiple - methods with the same name. -1. Overloads may be defined as recursive type rules, admitting a - combinatorial explosion or even infinite “effective overloads”. - -Let’s make a quick example of something like a typical overload. - -```scala -object OverAdd { - def add(x: Int, y: Int): Int = x + y - - def add(x: String, y: String): String = s"$x$y" - - def add[A](l: Vector[A], r: Vector[A]): Vector[A] = - l ++ r -} -``` - -This mechanically translates to a newly introduced type, some implicit -instances of that type, and a function to let us call `add` the same -way we used to. - -```scala -// typeclasses are often defined with trait, but this is not required -final case class Adder[A](addImpl: (A, A) => A) - -// easier if all implicits are in this block -object Adder { - implicit val addInts: Adder[Int] = Adder((x, y) => x + y) - - implicit val addStrings: Adder[String] = - Adder((x, y) => s"$x$y") - - implicit def addVects[A]: Adder[Vector[A]] = - Adder((l, r) => l ++ r) -} - -// and to tie it back together -def add[A](x: A, y: A)(implicit adder: Adder[A]): A = - adder.addImpl(x, y) -``` - -## Overloaded wrapping without overloading - -While a bit more ceremonious, this allows us to write some nice -functions more easily. Here’s a function to add three values. - -```scala -def addThree[A: Adder](l: A, m: A, r: A): A = - add(l, add(m, r)) -``` - -`addThree` supports all three “overloads” of `add`. - -```scala -scala> addThree(1, 2, 3) -res0: Int = 6 - -scala> addThree("a", "ba", "cus") -res1: String = abacus - -scala> addThree(Vector(1, 1), Vector(0, 0), Vector(1, 0, 0, 1)) -res2: scala.collection.immutable.Vector[Int] = - Vector(1, 1, 0, 0, 1, 0, 0, 1) -``` - -With the overload style, we need three variants of this function, too, -each with the exact same body. The typeclass version need only be -written once, and automatically supports new overloads, that is, new -instances of `Adder`. - -Same with this function. - -```scala -def zipAdd[A: Adder](l: List[A], r: List[A]): List[A] = - l zip r map {case (x, y) => add(x, y)} -``` - -Functions like `addThree` and `zipAdd` are called *derived -combinators*. The more that you can do in derived combinators, the -more abstract and orthogonal your program will be. - -``` - +=============+ | +=============+ - | derived | (open | | primitive | (closed - | combinators | set) | | combinators | set) - +=============+ | +=============+ - | - +----------+ | +----------+ - | addThree |---→---→---→---→---→| Adder | - +----------+ calls | | -addImpl | +===========+ - ↑ |→---→ +----------+ | Instances | - | +--------+ | | +===========+ - | | zipAdd |---→---→---→- | - | +--------+ calls | +------+ +---------+ +-------+ - ↑ ↑ | | Ints | | Strings | | Vects | - | calls| | +------+ +---------+ +-------+ - | +-----+ | | - | | ??? | | | - ↑ +-----+ | | - | (derived combinators ↓ - | can derive from each other) ---→---→---→ - | | - ↑ ------------------------------- | - | To evaluate `addThree(1, 2, 3)` | - | ------------------------------- ↓ - | 1. Fetch `Adder` implicitly | - |-←---←---←---←---←---←---←---←---←---←---←-| - 2. Pass to `addThree` - 3. `addThree` uses the abstract interface to - invoke the primitive `add` combinator on what, - to it, is an abstract type, `A`. -``` - -## Infinite overloads via recursion - -Making derived combinators easier to write is very useful, but -typeclasses go further by letting you describe overloading rules that -would be impossible with normal overloading. - -Given that I can add `Int` and `Int` together, I should be able to add -`(Int, Int)` and `(Int, Int)` to get `(Int, Int)`. - -```scala -implicit val addIntPairs: Adder[(Int, Int)] = - Adder{case ((x1, x2), (y1, y2)) => - (x1 + y1, x2 + y2)} - -scala> add((2, 7), (3, 8)) -res3: (Int, Int) = (5,15) -``` - -But I should also be able to add pairs of `String`. And `(Int, -String)` pairs. And `(String, Vector[Boolean])` pairs. And pairs of -pairs of pairs. - -Typeclasses let you declare newly supported types recursively, with an -implicit argument list to the `implicit def`. - -```scala -implicit def addPairs[A: Adder, B: Adder] - : Adder[(A, B)] = - Adder{case ((a1, b1), (a2, b2)) => - (add(a1, a2), add(b1, b2)) - } -``` - -## Surely this must be going somewhere new - -If you’re familiar with type classes, all this must be old hat. But -this time, we’re going to expand the boundaries of the typeclass -design space, by exploiting *GADT pattern matching*. - -We could have designed the `Adder` type class to include `addThree` as -a primitive combinator, and implemented it afresh for each of the four -instances we’ve defined so far, as well as any future instances -someone might define. Thinking orthogonally, however, shows us that -there’s a more primitive concept which strictly generalizes it: if we -primitively define a two-value adder, we can use it to add three -items, simply by using it twice. - -This has a direct impact on how we structure the functions related to -`Adder`. The primitives must be split up, their separate -implementations appearing directly in the implicit instances. Derived -combinators may occur anywhere that is convenient to us: outside the -typeclass for full flexibility of location, or within the typeclass -for possible overrides for performance. - -But how much of the primitive implementations must occur in the -instances, really? - -## Empty tags as instances - -There is a progression of design refinements here. - -1. Ad hoc overloads, Java-style, impossible to abstract over. -1. Flip into a typeclass. -1. Refine the primitive/derived distinction to minimize code in - instances. - -For some typeclasses, *no* code needs to be put in the instances. For -example, if we want to support only `Int`, `String`, and `Vector`, -here is a perfectly sensible typeclass definition. - -```scala -sealed trait ISAdder[A] - -object ISAdder { - implicit object AddInts extends ISAdder[Int] - implicit object AddStrs extends ISAdder[String] - - final case class AddVects[E]() extends ISAdder[Vector[E]] - - implicit def addVects[E]: ISAdder[Vector[E]] = - AddVects() -} -``` - -If the instances cannot add values of the types indicated by the type -parameters, surely that code must exist somewhere! And it has a place, -in the definition of `add`. - -If you recall, this method merely called `addImpl` on the typeclass -instance before. Now there is no such thing; the instances are empty. - -Well, they are not quite empty; they contain a type. So we can define -`add`, with complete type safety, as follows. - -```scala -def isadd[A](x: A, y: A)(implicit adder: ISAdder[A]): A = - adder match { - case ISAdder.AddInts => x + y - case ISAdder.AddStrings => s"$x$y" - case ISAdder.AddVects() => - x ++ y - } -``` - -More specifically, they contain a runtime tag, which allows -information about the type of `A` to be extracted with a pattern -match. For example, determining that `adder` is `AddInts` reveals that -`A = Int`, because that’s what the `extends` clause says. This is -*GADT pattern matching*. - -The `Vector` case is a little tricky here, because we can only -determine that `A` is `Vector[e]` *for some unknown e*, but that’s -enough information to invoke `++` and get a result also of `Vector[e]` -for the same `e`. - -You can see this in action by using a [variable type -pattern](https://groups.google.com/d/msg/scala-user/JlCsy48poIU/DjsQDnzeZboJ) -to assign the name `e` (a lowercase type parameter is required for -this usage), so you can refer to it in types. - -```scala - case _: ISAdder.AddVects[e] => - (x: Vector[e]) ++ y -``` - -## The lowercase `e` names a GADT skolem - -In the `AddVects[e]` pattern immediately above, `e` is a *variable -type pattern*. This is a type that exists only in the scope of the -`case`. - -It’s *existential* because we don’t know what it is, only that it is -*some type* and we don’t get to pick here what that is. In this way, -it is no different from a type parameter’s treatment by the -implementation, which is -[existential on the inside](existential-inside.md). - -It’s a *GADT skolem* because it was bound by the pattern matching -mechanism to a “fresh” type, unequal to any other. Recall the way -`AddVects` was defined: - -```scala -AddVects[E] extends ISAdder[Vector[E]] -``` - -Matching `ISAdder` with `AddVects` doesn’t tell us anything about -bounds on the type passed to `AddVects` at construction time. This -isn’t true of all -[GADT skolems](variance-phantom.md#a-gadt-skolem), -but is only natural for this one. - -`scalac` will create this GADT skolem *regardless of whether we give -it a name*. In the pattern `case AddVects()`, it’s still known that -`A = Vector[e]` for some `e`; the only difference is that you haven’t -bound the `e` name, so you can’t actually refer to this *unspeakable* -type. - -Usually, you do not need to assign names such as `e` to such types; -`_` is sufficient. However, if you have problems getting `scalac` to -apply all the type equalities it ought to know about, a good first -step is to assign names to any skolems and try type -ascriptions. You’ll need a variable type pattern in other situations -that don’t infer, too. By contrast, with the `e` name bound, we can -confirm that `x: Vector[e]` in the above example, and `y` is -sufficiently well-typed for the whole expression to type-check. - -## Porting `addPairs` and other recursive cases - -Suppose we add support for pairs to `ISAdder`. - -```scala -final case class AddPairs[A, B]( - val fst: ISAdder[A], - val snd: ISAdder[B] - ) extends ISAdder[(A, B)] -``` - -This *should* permit us to pattern-match in `isadd` to make complex -determinations about the `A` type given to `isadd`. This *ought to be* -a big win for GADT-style typeclasses, allowing “short-circuiting” -patterns that work in an obvious way. - -```scala -// this pattern means A=(Int, String) -case AddPairs(AddInts, AddStrs) => - -// this pattern means A=(ea, Vector[eb]) -// where ea and eb are GADT skolems -case AddPairs(fst, _: AddVects[eb]) => - -// here, A=(ea, eb) (again, GADT skolems) -// calling `isadd` recursively is the most -// straightforward implementation -case AddPairs(fst, snd) => - val (f1, s1) = x - val (f2, s2) = y - (isadd(f1, f2)(fst), isadd(s1, s2)(snd)) -``` - -The final `case`’s body is fine. `scalac` effectively introduces -skolems `ea` and `eb` so that `A = (ea, eb)`, `fst: Adder[ea]`, and so -on, and everything lines up nicely. We are not so lucky with the other -cases. - -```scala -....scala:76: pattern type is incompatible with expected type; - found : ISAdder.AddInts.type - required: ISAdder[Any] - case AddPairs(AddInts, AddStrs) => - ^ -....scala:76: pattern type is incompatible with expected type; - - found : ISAdder.AddStrs.type - required: ISAdder[Any] - case AddPairs(AddInts, AddStrs) => - ^ -....scala:79: pattern type is incompatible with expected type; - found : ISAdder.AddVects[eb] - required: ISAdder[Any] - case AddPairs(fst, _: AddVects[eb]) => - ^ -``` - -This is nonsensical; the underlying code is sound, we just have to go -the long way around so that `scalac` doesn’t get confused. Instead of -the above form, you must assign names to the `AddPairs` skolems as we -described above, and do a sub-pattern-match. - -```scala -case p: AddPairs[ea, eb] => - val (f1, s1) = x - val (f2, s2) = y - (p.fst, p.snd) match { - case (AddInts, AddStrs) => - case (fst, _: AddVects[eb]) => - case (fst, snd) => -``` - -Note that we had to give up on the `AddPairs` pattern entirely, -because - -1. More complex situations require type ascription. -1. You cannot ascribe with skolems unless you’ve bound the skolems to - names with variable type patterns. -1. You can’t use variable type patterns with the structural - “ADT-style” patterns; you must instead use inelegant and - inconvenient (non-variable) type patterns. (This may be - [improved in Typelevel Scala 4](https://github.com/typelevel/scala/blob/typelevel-readme/notes/typelevel-4.md#type-arguments-on-patterns-pull5774-paulp).) - -Yet this remains entirely up to shortcomings in the current pattern -matcher implementation. An improved pattern matcher could make the -nice version work, safely and soundly. - -As such, I don’t want these shortcomings to discourage you from trying -out the pure type-tagging, “GADT-style” typeclasses. It is simply -nicer for many applications, and you aren’t going to code yourself -into a hole with them, because should you wind up in the buggy -territory we’ve been exploring, there’s still a way out. - -## Same typeclass, new “primitive” combinators - -“Empty” typeclasses like `ISAdder` contain no implementations of -primitive combinators, only “tags”. As such, they are in a sense the -purest form of “typeclass”; *to classify types* is the beginning and -end of what they do! - -Every type that is a member of the “class of types” `ISAdder` is -either - -1. the type `Int`, -1. the type `String`, -1. a type `Vector[e]`, where `e` is any type, or -1. a type `(x, y)` where `x` and `y` are types that are *also* in the - `ISAdder` class. - -This is the end of `ISAdder`’s definition; in particular, there is -nothing here about “adding two values to get a value”. All that -is said is what types are in the class! - -Given this ‘undefinedness’, if we have another function we want to -write over the exact same class-of-types, we can just write it without -making any changes to `ISAdder`. - -```scala -def backwards[X](x: X)(implicit adder: ISAdder[X]): X = adder match { - case AddInts => -x - case AddStrs => x.reverse - case _: AddVects[e] => x.reverse - case p: AddPairs[ea, eb] => - val (a, b): (ea, eb) = x - (backwards(a)(p.fst), backwards(b)(p.snd)) -} -``` - -Set aside the question of whether the class of “backwards-able” types -ought to remain in lockstep with the class of “addable” -types. Supposing that it *should*, the class need be defined only -once. - -More practically speaking, if you expose the subclasses of a typeclass -to users of your library, they can define primitives “in lockstep”, -too. The line between primitive and derived combinators is also -blurred: a would-be derived combinator can pattern-match on the -typeclass to supply special cases for improved performance, becoming -“semi-primitive” in the process. You decide whether these are good -things or not. - -## Hybrid “clopen” typeclasses - -Pattern-matching typeclass GADTs is subject to the same exhaustiveness -concerns and compiler warnings as pattern-matching ordinary ADTs. If -you eliminate a `case` from `def isadd`, you’ll see something like - -```scala -....scala:57: match may not be exhaustive. -It would fail on the following input: AddInts - adder match { - ^ -``` - -We could unseal `ISAdder`, which would eliminate the warning, but -wouldn’t really solve anything. The function would still crash upon -encountering the missing case. - -Pattern matches of unsealed hierarchies typically include a “fallback” -case, code used when none of the “special” cases match. However, for -pure typeclasses like `ISAdder`, this strategy is a dead end -too. Consider a hypothetical fallback case. - -```scala - case _ => ??? -``` - -Each of the other patterns in `isadd`, by their success, taught us -something useful about the `A` type parameter. For example, `case -AddInts` tells us that `A = Int`, and accordingly `x: Int` and `y: -Int`. It also meant that the expected result type of that block is -also `Int`. That’s plenty of information to actually implement -“adding”. - -By contrast, `case _` tells us *nothing* about the `A` type. We don’t -know anything new about `x`, `y`, or the type of value we ought to -return. All we can do is return either `x` or `y` without further -combination; while this is a sort of “adding” [in abstract -algebra](https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Monoid.html#t:First), -there’s a good chance it’s not really what the caller was expecting. - -Instead, we can reformulate a closed typeclass like `ISAdder` with one -extension point, where the typeclass is specially encoded in the usual -“embedded implementation” style. It’s closed and open, so -[“clopen”](https://mail.haskell.org/pipermail/haskell-cafe/2014-April/113373.html). - -## `sealed` doesn’t seal subclasses - -Our GADT typeclass instances work by embedding type information within -the instances, to be rediscovered at runtime. To support open -extension, we need a data case that contains *functions* instead of -types. We know how to encode that, because that is how standard, -non-GADT typeclasses work. - -```scala -sealed trait ISOAdder[A] - -trait ExtISOAdder[A] extends ISOAdder[A] { - val addImpl: (A, A) => A -} - -object ISOAdder { - implicit object AddInts extends ISOAdder[Int] - implicit object AddStrs extends ISOAdder[String] - - final class AddVects[A] extends ISOAdder[Vector[A]] - - implicit def addVects[A]: ISOAdder[Vector[A]] = - new AddVects - - def isoadd[A](x: A, y: A)(implicit adder: ISOAdder[A]): A = - adder match { - case AddInts => x + y - case AddStrs => s"$x$y" - case _: AddVects[e] => - (x: Vector[e]) ++ y - // NB: no unchecked warning here, which makes sense - case e: ExtISOAdder[A] => - e.addImpl(x, y) - } -} -``` - -By sealing `ISOAdder`, we ensure that the pattern match in `isoadd` -remains exhaustive. However, one of those cases, `ExtISOAdder`, admits -new subclasses, itself! This is fine because no matter how many -subclasses of `ExtISOAdder` we make, they’ll still match the last -pattern of `isoadd`. - -We could also define `ExtISOAdder` as a `final case class`. The point -is that you can make this “extension point” in your otherwise-closed -typeclass using whatever style you like. - -One caveat, though: “clopen” typeclasses cannot have arbitrary new -primitive combinators added to them. They are like ordinary open -typeclasses in that regard. Consider a version of `backwards` for -`ISOAdder`: what you could do in the `ExtISOAdder` case? - -## Whoever you like - -With type parameters vs. members, you can get pretty far with -[the “rule of thumb”](type-members-parameters.md#when-is-existential-ok). -Beyond that, even bugs in `scalac` typechecking can guide you to the -“right” choice. - -There is no similar rule for this design space. It might seem that -typeclass newcomers might have an easier time with the OO-style -“unimplemented method” signposts in the open style, but I have also -seen them lament the loss of flexibility that would be provided by the -GADT style. - -Likewise, as an advanced practitioner, your heart will be rent by the -tug-of-war between the boilerplate of the open style and the -pattern-matcher’s finickiness with the GADT style. You may then be -tempted to adopt the hybrid ‘clopen’ style, but this, too, is too -often a form of design excess. - -Given all that, the only help I can offer, aside from describing the -design space above, is “pick whichever you like”. You know your -program; if you are not sure which will be nicer, try both! - -*This article was tested with Scala 2.12.4.* diff --git a/src/blog/why-is-adt-pattern-matching-allowed.md b/src/blog/why-is-adt-pattern-matching-allowed.md deleted file mode 100644 index 1fd74a40..00000000 --- a/src/blog/why-is-adt-pattern-matching-allowed.md +++ /dev/null @@ -1,361 +0,0 @@ -{% - author: ${S11001001} - date: "2014-11-10" - tags: [technical] -%} - -# Why is ADT pattern matching allowed? - -One of the rules of -[the Scalazzi Safe Scala Subset](https://dl.dropboxusercontent.com/u/7810909/talks/parametricity/4985cb8e6d8d9a24e32d98204526c8e3b9319e33/parametricity.pdf) -is “no type casing”; in other words, testing the type via -`isInstanceOf` or type patterns isn’t allowed. It’s one of the most -important rules therein for preservation of free theorems. Common -functional programming practice in Scala *seems* to violate this rule -in a subtle way. However, as we will see, that practice carves out a -very specific exception to this rule that, morally, isn’t an exception -at all, carrying convenient advantages and none of the drawbacks. - -Why forbid type tests? ----------------------- - -With the “no type tests” rule, we forbid writing functions like this: - -```scala -def revmaybe[T](xs: List[T]): List[T] = { - val allInts = xs.forall{case _:Int => true - case _ => false} - if (allInts) xs.reverse else xs -} -``` - -Which violates the -[free theorem](http://failex.blogspot.com/2013/06/fake-theorems-for-free.html) -of `revmaybe`’s type `revmaybe(xs map f) = revmaybe(xs) map f`, as -follows. - -```scala -val xs = List(1, 2, 3) -def f(i:Int) = Some(i) - -scala> revmaybe(xs map f) -res2: List[Some[Int]] = List(Some(1), Some(2), Some(3)) - -scala> revmaybe(xs) map f -res3: List[Some[Int]] = List(Some(3), Some(2), Some(1)) -``` - -ADTs are OK to go ------------------ - -On the other hand, the Scalazzi rules are totally cool with pattern -matching to separate the parts of -[ADTs](https://www.haskell.org/haskellwiki/Algebraic_data_type). For -example, this is completely fine. - -```scala -def headOption[T](xs: List[T]): Option[T] = xs match { - case x :: _ => Some(x) - case _ => None -} -``` - -Even more exotic matches, where we bring type information forward into -runtime, are acceptable, as long as they’re in the context of ADTs. - -```scala -sealed abstract class Expr[T] -final case class AddExpr(x: Int, y: Int) extends Expr[Int] - -def eval[T](ex: Expr[T]): T = ex match { - case AddExpr(x, y) => x + y -} -``` - -ADTs use type tests -------------------- - -Let’s look at the compiled code of the `eval` body, specifically, the -`case` line. - -```nasm - 2: aload_2 - 3: instanceof #60 // class adts/AddExpr - 6: ifeq 39 - 9: aload_2 - 10: checkcast #60 // class adts/AddExpr - 13: astore_3 - 14: aload_3 - 15: invokevirtual #63 // Method adts/AddExpr.x:()I - 18: istore 4 - 20: aload_3 - 21: invokevirtual #66 // Method adts/AddExpr.y:()I - 24: istore 5 - 26: iload 4 - 28: iload 5 - 30: iadd -``` - -So, instead of calling `unapply` to presumably check whether `AddExpr` -matches, scalac checks and casts its argument to `AddExpr`. Why does -it do that? Let’s see if we could use `AddExpr.unapply` instead. - -```scala -scala> AddExpr.unapply _ -res4: adts.AddExpr => Option[(Int, Int)] = -``` - -In other words, the `unapply` call can’t tell you whether an `Expr` is -an `AddExpr`; it can’t be called with arbitrary `Expr`. - -The only actual check here is inserted by scalac as part of compiling -the pattern match expression, and it is a type test, supposedly -verboten under Scalazzi rules. `headOption`, too, is implemented with -type tests and casts, not `unapply` calls. - -We’ve exhorted Scala users to avoid type tests, but then turn around -and say that type tests are OK! What’s going on? - -An equivalent form ------------------- - -In every case where we use pattern matching on an ADT, there’s an -equivalent way we could write the expression without pattern matching, -by adding an encoding of the whole ADT as a method on the class or -trait we use as the base type. Let’s redefine the `Option` type with -such a method to see how this is done. - -```scala -sealed abstract class Maybe[T] { - def fold[Z](nothing: => Z, just: T => Z): Z -} - -final case class MNothing[T]() extends Maybe[T] { - override def fold[Z](nothing: => Z, just: T => Z): Z = - nothing -} - -final case class Just[T](get: T) extends Maybe[T] { - override def fold[Z](nothing: => Z, just: T => Z): Z = - just(get) -} -``` - -It’s key to our reasoning that we completely avoid `match` in our -implementations; in other words, the `fold` is *matchless*. - -With the `fold` method, the following two expressions are equivalent, -notwithstanding scalac’s difficulty optimizing the latter, even in the -presence of inlining. - -```scala -(selector: Maybe[A]) match { - case MNothing() => "default case" - case Just(x) => justcase(x) -} - -selector.fold("default case", x => justcase(x)) -``` - -It’s a simple formula: the fold takes as many arguments as there are -cases, always returns the given, sole type parameter, and each -argument is a function that results in that same parameter. There’s a -free theorem that a `fold` implementation on data structures without -recursion, like `Maybe`, can only invoke one of these arguments and -return the result directly, just as the pattern match does. - -If you prefer the clarity of named cases, just use Scala’s named -arguments. Here’s that last fold: - -```scala -selector.fold(nothing = "default case", - just = x => justcase(x)) -``` - -GADT folds ----------- - -Encoding `Expr` is a little bit more complicated. For the full power -of the type, we have to turn to `Leibniz` to encode the matchless -`fold`. - -```scala -import scalaz.Leibniz, Leibniz.{===, refl} - -sealed abstract class Expr2[T] { - def fold[Z](add: (Int, Int, Int === T) => Z, - concat: (String, String, String === T) => Z): Z -} -``` - -What does this mean? The type `Int === T`, seen in the `add` argument -signature, is inhabited if and only if the type `T` **is** the type -`Int`. So an implementation of `fold` can only call the `add` -function if it can prove that type equality. There is, of course, one -that can: - -```scala -final case class AddExpr2(x: Int, y: Int) extends Expr2[Int] { - override - def fold[Z](add: (Int, Int, Int === Int) => Z, - concat: (String, String, String === Int) => Z): Z = - add(x, y, refl) -} -``` - -Not only does `AddExpr2` know that `Expr2`’s type parameter is `Int`, -we must make the type substitution when implementing methods from -`Expr2`! At that point it is enough to mention `refl`, the evidence -that every type is equal to itself, to satisfy `add`’s signature. - -This may seem a little magical, but it is no less prosaic than -implementing `java.lang.Comparable` by making this substitution. So -you can do this sort of thing every day even in Java. - -```java -public interface Comparable { - int compareTo(T o); -} - -class MyData implements Comparable { - @Override - public int compareTo(MyData o) { // note T is replaced by MyData - // ... - } -} -``` - -If only Java had higher kinds, you could go the rest of the way and -actually implement -[GADTs](https://www.haskell.org/haskellwiki/Generalised_algebraic_datatype#Motivating_example). - -Moving on, let’s see another case for `Expr2`, and finally to tie it -all together, `eval2` with some extra constant data in for good -measure. - -```scala -final case class ConcatExpr2(x: String, y: String) extends Expr2[String] { - override - def fold[Z](add: (Int, Int, Int === String) => Z, - concat: (String, String, String === String) => Z): Z = - concat(x, y, refl) -} - -def eval2[T](ex: Expr2[T]): T = - ex.fold((x, y, intIsT) => intIsT(1 + x + y), - (x, y, strIsT) => strIsT("one" + x + y)) -``` - -Using the `Leibniz` proof is, unfortunately, more involved than -producing it in the fold implementations. See my previous posts, -[“A function from type equality to Leibniz”](type-equality-to-leibniz.md) -and -[“Higher Leibniz”](higher-leibniz.md), -for many -details on applying `Leibniz` proof to make type transformations. - -While the pattern matching `eval` didn’t have to explicitly apply type -equality evidence -- it *just knew* that `Int` was `T` when the -`IntExpr` pattern matched -- Scala has holes in its implementation, -discussed in the aforementioned posts on `Leibniz`, that sometimes -make the above implementation strategy an attractive choice even -though pattern matching is available. - -We could, but that’s good enough, so we won’t ---------------------------------------------- - -You might have noticed that adding another case to `Expr` caused us -not only to implement an extra `fold`, but to add another argument to -the base `fold` to represent the new case, and then go through every -implementation to add that argument. This isn’t so bad for just two -cases, but indeed has quadratic growth, to the point that adding a new -case to a large datatype is a majorly annoying project all by itself. - -There is an interesting property of `fold`, though: the strategy isn’t -available for our first function, `revmaybe`, to discriminate -arguments of arbitrary type! To do that, we would have to add a -signature like this to `Any`. - -```scala -def fold[Z](int: Int => Z, any: Any => Z): Z = any(this) -// and, in the body of class Int -override def fold[Z](int: Int => Z, any: Any => Z): Z = int(this) -``` - -Obviously, you cannot do this. - -You can only add `fold` methods to types you know; I can only call -`fold` in `expr2` by virtue of the fact that I know that the argument -has type `Expr2[T]` for some `T`. If the argument was just `T`, I -wouldn’t have enough static type information to call `fold`. So the -use of `fold`s doesn’t break parametricity. Equivalently, **a pattern -match that could be implemented using a matchless fold also does not -break parametricity**. - -As we have seen, it is unfortunately inconvenient to actually go -through the bother of writing `fold` methods, when pattern matching is -there. But it is enough to reason that *we could* write a matchless -`fold` and replace the pattern matching with it, to prove that the -pattern matching is safe, no matter how many underlying type tests -scalac might use to implement it. - -A simple test follows: **if you could write a matchless fold, and use -that instead, the pattern match is type-safe**. - -A selector subtlety -------------------- - -Here’s a pattern match that violates parametricity. - -```scala -selector match { - case MNothing() => "default case" - case Just(x) => justcase(x) -} -``` - -Wait, but didn’t we rewrite that using a `fold` earlier? Not quite. -Oh, I didn’t mention? The type of `selector` is `T`, because we’re in -a function like this: - -```scala -def notIdentity[T](selector: T) = - // match expression above goes here -``` - -Scala will permit this pattern match to go forward. It doesn’t -require us to prove that the selector is of the ADT root type we -happened to define; that’s an arbitrary point as far as Scala’s -subtyping system is concerned. All that is required is that the -static type of `selector` be a supertype of each of `MNothing[_]` and -`Just[_]`, which `T` is, not being known to be more refined than -`Any`. - -The test works here, though! What is ambiguous to scalac is a bright -line in our reasoning. We can’t define a matchless `fold` that can be -invoked on this `selector`, so we reach the correct conclusion, that -the match violates parametricity. - -The rule revisited ------------------- - -So we’ve carved out a clear “exception” to the “no type tests” -Scalazzi rule, and seen that it isn’t an exception at all. There’s a -straightforward test you can apply to your pattern matches, - -**If and only if I could, hypothetically, write a matchless fold, or -use an existing one, and rewrite this in its terms, this pattern -match is safe.** - -but beware the subtle case where the match’s selector has a wider type -than you anticipated. - -Finally, this is a rule specifically about expressions that don’t -violate our ability to reason about code. This doesn’t hold for -arbitrary type-unsafe rewrites: that you could write a program safely -means you *should* write it safely. Unlike arbitrary rewrites into -nonfunctional code, the pattern match uses no -non-referentially-transparent and no genuinely non-parametric -expressions. - -*This article was tested with Scala 2.11.4 and Scalaz 7.1.0.* diff --git a/src/code-of-conduct/README.md b/src/code-of-conduct/README.md deleted file mode 100644 index 55ac9cbf..00000000 --- a/src/code-of-conduct/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Typelevel Code of Conduct - -The Typelevel community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. -It is through these differences that our community experiences great successes and continued growth. -When you're working with members of the community, this Code of Conduct will help steer your interactions and keep Typelevel a positive, successful, and growing community. -Whether you are new or familiar with our community, we care about making it a welcoming and safe place for you and we're here to support you. - - -## Our Community - -Members of the Typelevel community are open, considerate, and respectful. -Behaviors that reinforce these values contribute to a positive environment, and include: - -- **Being kind.** We treat our fellow community members with the empathy, respect and dignity all people deserve. -- **Focusing on what is best for the community.** We're respectful of the processes set forth in the community, and we work within them. -- **Showing empathy towards other community members.** We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. -- **Acknowledging time and effort.** We're respectful of the volunteer efforts that permeate the Typelevel community. We're thoughtful when addressing the efforts of others, keeping in mind that often the labor was completed simply for the good of the community. -- **Being respectful of differing viewpoints and experiences.** We remember that everyone was new to Scala at some point. We want to encourage newcomers to join our community and learn the Scala language and ecosystem. Always assume good intentions and a willingness to learn, just as you are willing to evolve your own opinion as you gain new insights. -- **Being considerate.** Members of the community are considerate of their peers -- other Scala users. -- **Being respectful.** We're respectful of others, their positions, their skills, their commitments, and their efforts. -- **Gracefully accepting constructive criticism.** When we disagree, we are courteous in raising our issues. -- **Using welcoming and inclusive language.** We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference. - - -## Our Standards - -Every member of our community has the right to have their identity respected. -The Typelevel community is dedicated to providing a positive experience for everyone, regardless of age, gender identity and expression, sexual orientation, disability, neurodivergence, physical appearance, body size, ethnicity, nationality, race, or religion (or lack thereof), education, or socio-economic status. - - -### Inappropriate Behavior - -Examples of unacceptable behavior by participants include: - -- Harassment of any participants in any form -- Deliberate intimidation, stalking, or following -- Logging or taking screenshots of online activity for harassment purposes -- Publishing others' private information, such as a physical or electronic address, without explicit permission -- Violent threats or language directed against another person -- Incitement of violence or harassment towards any individual, including encouraging a person to commit suicide or to engage in self-harm -- Creating additional online accounts in order to harass another person or circumvent a ban -- Sexual language and imagery in online communities or in any conference venue, including talks -- Insults, put downs, or jokes that are based upon stereotypes, that are exclusionary, or that hold others up for ridicule -- Excessive swearing -- Unwelcome sexual attention or advances -- Unwelcome physical contact, including simulated physical contact (eg, textual descriptions like "hug" or "backrub") without consent or after a request to stop -- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others -- Sustained disruption of online community discussions, in-person presentations, or other in-person events -- Spamming, trolling, flaming, baiting or other attention-stealing behavior -- Continued one-on-one communication after requests to cease -- Other conduct that is inappropriate for a professional audience - -Community members asked to stop any inappropriate behavior are expected to comply immediately. - - -### Consequences - -If a participant engages in behavior that violates our standards, the Typelevel Code of Conduct Committee will take any action they deem appropriate, including but not limited to: warning the offender, or expelling them from the community or current community events with no refund of event tickets. - -The full list of consequences for inappropriate behavior is listed in the [Enforcement Procedures]. - - - -## Scope - -The enforcement policies listed above apply to all official Typelevel channels, including but not limited to the following: mailing lists, both organization and affiliate GitHub repositories, Typelevel Discord server, and Typelevel venues and events. -If unaffiliated projects adopt the Typelevel Code of Conduct, please contact the maintainers of those projects for enforcement. - - -## Contact - -For questions related to our code of conduct, or to report possible violations, please immediately [contact the Typelevel Code of Conduct Committee](mailto:coc@typelevel.org) or one of its members: - -* [Sam Pillsworth](mailto:sam@blerf.ca) -* [Andrew Valencik](mailto:andrew.valencik@gmail.com) -* [Kateu Herbert](mailto:hkateu@gmail.com) -* [Arman Bilge](mailto:arman@typelevel.org) -* [Lucas Satabin](mailto:lucas.satabin@gnieh.org) - -## Attribution - -This code of conduct is a modified version of the [Python Software Foundation Code of Conduct](https://www.python.org/psf/conduct), licensed under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](https://creativecommons.org/licenses/by-sa/3.0/). - -Additional language was incorporated from the following: - -* [Otter Tech](https://otter.technology/code-of-conduct-training/) resources, licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/). -* [Scala Code of Conduct](https://www.scala-lang.org/conduct/), licensed under a [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). -* [Affect Conf Code of Conduct](https://affectconf.com/coc/), licensed under a [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). -* [Citizen Code of Conduct](http://citizencodeofconduct.org/), licensed under a [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). -* [Contributor Covenant version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct), licensed[ Creative Commons Attribution 4.0 License](https://github.com/ContributorCovenant/contributor_covenant/blob/master/LICENSE.md). -* [Django Project Code of Conduct](https://www.djangoproject.com/conduct/), licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). -* [LGBTQ in Tech Slack Code of Conduct](https://lgbtq.technology/coc.html), licensed under a [Creative Commons Zero License](https://creativecommons.org/publicdomain/zero/1.0/). -* [PyCon 2018 Code of Conduct](https://us.pycon.org/2018/about/code-of-conduct/), licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). -* [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html) - -[Enforcement Procedures]: enforcement.md diff --git a/src/code-of-conduct/enforcement.md b/src/code-of-conduct/enforcement.md deleted file mode 100644 index 50b26bfa..00000000 --- a/src/code-of-conduct/enforcement.md +++ /dev/null @@ -1,219 +0,0 @@ -# Typelevel Code of Conduct Committee Enforcement Procedures - -This document summarizes the procedures the Typelevel Code of Conduct Committee uses to enforce the [Code of Conduct]. - - -## Summary of processes - -When the committee receives a report of a possible Code of Conduct violation, it will: - -1. Acknowledge the receipt of the report. -2. Evaluate conflicts of interest. -3. Call a meeting of committee members without a conflict of interest. -4. Evaluate the reported incident. -5. Propose a behavior adjustment. -6. Propose consequences for the reported behavior. -7. Vote on behavior adjustment and consequences for the reported person. -8. Contact online community administrators/moderators to approve the behavior adjustment and consequences. -9. Follow up with the reported person. -10. Decide further responses. -11. Follow up with the reporter. - - -### Affiliate project processes - -Typelevel affiliate projects are also covered by the Typelevel Code of Conduct. An affiliate project may choose to nominate dedicated moderators, who will work with the Typelevel Code of Conduct Committee to handle reports. - -If a moderator of an affiliate project receives a report, they should: - -1. Acknowledge the receipt of the report. -2. Contact the Code of Conduct Committee, stating "I have a report that involves \[REPORTED PERSON\]" so that Code of Conduct Committee members can evaluate conflicts of interest. -3. The reporting process will proceed as usual, with the affiliate moderator/s included as acting members of the Code of Conduct Committee. - -If the Code of Conduct Committee receives a report regarding an affiliate project, the affiliate moderator/s will be included in the conflict of interest evaluation. If no conflict exists, the affiliate moderator/s will be included in the reporting process as acting Code of Conduct committee members. - - -### Moderator Processes - -Typelevel Code of Conduct committee members, affiliate project moderators, and moderators in other spaces covered by the Typelevel Code of Conduct are empowered to enforce the Code of Conduct pro-actively. Code of Conduct violations should still be reported as a follow up, with a note about any action taken in the moment. - - -## Acknowledge the report - -Reporters should receive an acknowledgment of the receipt of their report within 24 hours, via their preferred communication channel. - - -## Conflict of interest policy - -Examples of conflicts of interest include but are not limited to the following circumstances, where the reporter or reported person is - -* your manager. -* a romantic partner, [metamour](https://solopoly.net/2012/09/29/whats-a-metamour-on-my-terms/), or close friend. It's fine to participate if they are an acquaintance. -* your family member. -* your direct client. -* someone you work closely with. This could be someone on your team or someone who works on the same project as you. -* a maintainer who regularly reviews your contributions. - -Committee members do not need to state why they have a conflict of interest, only that one exists. Other committee members should not ask why the person has a conflict of interest. - -Anyone who has a conflict of interest will remove themselves from the discussion of the incident, and recuse themselves from voting on a response to the report. - - -## Evaluating a report - -### Jurisdiction - -* _Is this a Code of Conduct violation?_ Is this behavior on our list of inappropriate behavior? Is it borderline inappropriate behavior? Does it violate our community norms? -* _Did this occur in a space that is within our Code of Conduct's scope?_ If the incident occurred outside the community, but a community member's mental health or physical safety may be negatively impacted if no action is taken, the incident may be in scope. Private conversations in community spaces are also in scope. - - -### Impact - -* _Did this incident occur in a private or public space?_ The more community members are able to access or were present in such a space where the incident occurred, the larger the negative impact. -* _Does this behavior negatively impact a marginalized group in our community?_ Is the reporter a person from a marginalized group in our community? How is the reporter being negatively impacted by the reported person's behavior? Are members of the marginalized group likely to disengage with the community if no action was taken on this report? -* _Does this incident involve a community leader?_ Community members often look up to community leaders to set the standard of acceptable behavior, and so reports involving a community leader can have significant negative impact. - - -### Risk - -* _Does this incident include sexual harassment?_ -* _Does this pose a safety risk?_ Does the behavior put a person's physical safety at risk? Will this incident severely negatively impact someone's mental health? -* _Is there a risk of this behavior being repeated?_ Does the reported person understand why their behavior was inappropriate? Is there an established pattern of behavior from past reports? - -Reports which involve higher risk or higher impact may face more severe consequences than reports which involve lower risk or lower impact. - - -## Propose a behavior adjustment - -The committee will propose a concrete behavior adjustment that ensures the inappropriate behavior is not repeated. The committee will also discuss what actions may need to be taken if the reported person does not agree to the proposed behavioral adjustment. - -What follows are examples of possible behavioral adjustments for incidents that occur in online spaces under the scope of this Code of Conduct. This behavioral adjustment list is not exhaustive, and the Typelevel Code of Conduct Committee reserves the right to take any action it deems necessary. - -* Requiring that the reported person not use specific language -* Requiring that the reported person not join in on specific types of discussions -* Requiring that the reported person not send private messages to a community member -* Requiring that the reported person not join, or leave, specific communication channels -* Removing the reported person from administrator or moderator rights to community infrastructure -* Removing a volunteer from their duties and responsibilities -* Removing a person from leadership of relevant organizations -* Removing a person from membership of relevant organizations - - -## Propose consequences - -What follows are examples of possible consequences to an incident report. This consequences list is not exhaustive, and the Typelevel Code of Conduct Committee reserves the right to take any action it deems necessary. - -Possible private responses to an incident include: - -* Nothing, if the behavior was determined to not be a Code of Conduct violation -* A verbal or emailed warning -* A final warning -* Temporarily removing the reported person from the online community -* Permanently removing the reported person from the online community - -If deemed necessary for community safety, a public account of an incident, and consequences, may be published. - -## Committee vote - -Some committee members may have a conflict of interest and may be excluded from discussions of a particular incident report. Excluding those members, decisions on the behavioral adjustment and consequences will be determined by a two-thirds majority vote of the Typelevel Code of Conduct Committee. - - -## Administrators/moderators Communication - -Once the committee has approved the proposed behavioral adjustment and consequences, they will communicate the recommended response to the Typelevel Foundation Board of Directors, and any specific administrators/moderators of the related community space (ex. Discord moderators, GitHub organization administrators, etc.) The committee should not state who reported this incident. They should attempt to anonymize any identifying information from the report. - -Administrators/moderators are required to respond back with whether they accept the recommended response to the report. If they disagree with the recommended response, they should provide a detailed response or additional context as to why they disagree. Administrators/moderators are encouraged to respond within a week. - -In cases where the administrators/moderators disagree on the suggested resolution for a report, the Typelevel Code of Conduct Committee shall notify the Typelevel Foundation Board of Directors. - - -## Initial follow-up with the reported person - -The Typelevel Code of Conduct Committee will draft a response to the reported person. -The response should contain: - -* A description of the person's behavior in neutral language -* The negative impact of that behavior -* A concrete proposed behavior adjustment -* Any consequences of their behavior - -The committee should not state who reported this incident. They should attempt to anonymize any identifying information from the report. The reported person should be discouraged from contacting the reporter to discuss the report. - - -## Further responses - -The reported person may respond with additional context. Depending on the response, the Typelevel Code of Conduct Committee may re-evaluate the proposed behavior adjustment and consequences. If the reported person wishes to apologize to the reporter, the committee can accept the apology on behalf of the reporter. - - -## Follow-up with the reporter - -A person who makes a report should receive a follow up, via their preferred communication channel, stating what action was taken in response to the report. If the committee decided no response was needed, they should explain why it was not a Code of Conduct violation. Reports that are not made in good faith (such as "reverse sexism" or "reverse racism") may receive no response. - -The follow up should be sent no later than one week after the receipt of the report. If deliberation or follow up with the reported person takes longer than one week, the committee should send a status update to the reporter. - - -## Documentation and Privacy Policies - -### Committee shared email address - -It is convenient for all members of the Code of Conduct committee to be reached by a single email address. The committee should use an email alias which forwards email to individual members. - -Using a mailing list is not recommended. This is because mailing lists typically archive all emails. This means new committee members gain access to all past archives. They can deliberately or accidentally see past reports where they have a conflict of interest. In order to prevent potential conflicts of interest, it is recommended to not have a mailing list archive. - - -### Committee online discussion - -The Code of Conduct Committee will use an encrypted service for online discussion and deliberation. The Committee may choose something that works for the majority of current members; two possible recommendations are Matrix or Signal. - -When a report comes in and a discussion needs to happen in an online space, care needs to be taken to avoid conflicts of interest. In the committee chat channel, state 'We have a report that involves \[REPORTED PERSON\]'. Do not say who was the reporter or who were witnesses if the report was sent to an individual committee member. Ask which committee members do not have a conflict of interest. Add those committee members to a group discussion, separate from the committee channel. If a committee member does not respond, do not add them to the new group discussion. If a committee member finds they have a conflict of interest because of who reported the incident or who witnessed it, they should recuse themselves from the discussion. - -### Shared Documentation - -The Code of Conduct committee should keep two types of shared documents: - -* A spreadsheet with the status of open and closed cases -* A separate, encrypted, document for each report - - -#### Status Spreadsheet - -The spreadsheet for Typelevel Code of Conduct reports is linked in the private steering repository README. -Keep resolutions and notes vague enough that enforcement team members with a conflict of interest don't know the details of the incident. Use gender neutral language when describing the reported person in the spreadsheet. - - -#### Report Documentation - -A template report document is linked in the status spreadsheet, as well as the private steering repository README. Report documentation must be encrypted to protect personally identifiable information (PII). - -#### Privacy Concerns - -There are some common privacy pitfalls to online tools like Google Docs. Make sure to always share the document with committee members who don't have a conflict of interest, rather than turning link sharing on. This prevents people outside of the committee from accessing the documents. - -Another common issue is that when a folder is shared with the whole committee, even if a person doesn't have edit or view access to an individual report, they can still see the document's title. This can give information away, and that's why the report template instructs naming the document with a random phrase. - -When on-boarding new committee members, they should be provided with a list of names of people who have been reported in a Code of Conduct incident. The new committee member should state whether they have any conflicts of interest with reviewing documentation for those cases. If not, they will be given access to the report documents. - - -## Changes to Code of Conduct - -When discussing a change to the Typelevel Code of Conduct or enforcement policies, the Typelevel Code of Conduct Committee will follow this decision-making process: - -* Brainstorm options. Code of Conduct committee members should discuss any relevant context and brainstorm a set of possible options. It is important to provide constructive feedback without getting side-tracked from the main question. Brainstorming should be limited to 3-7 days maximum. -* Vote. Once a working draft is in place for the Code of Conduct and procedures, the Code of Conduct committee shall provide the Typelevel Foundation Board of Directors with a PR of the changes. The Board will vote on the changes, following the Bylaws and requiring at least ⅔ affirmative vote for the changes to be accepted. - - -### Current list of Code of Conduct Committee members - -Sam Pillsworth, Andrew Valencik, Kateu Herbert, Arman Bilge, Lucas Satabin - - -## Attribution - -This enforcement policy is a modified version of the [Python Software Foundation Code of Conduct Enforcement Policy](https://www.python.org/psf/conduct/enforcement/), part of the Python Software Foundation Code of Conduct, licensed under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](https://creativecommons.org/licenses/by-sa/3.0/). - -* The [PyCon Code of Conduct](https://us.pycon.org/2018/about/code-of-conduct/) is licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). -* Ada Initiative's guide titled "[Conference anti-harassment/Responding to Reports](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Responding_to_reports)" is licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/) -* Audrey Eschright of [Safety First PDX](http://safetyfirstpdx.org/) provided the impact vs risk assessment framework, which is licensed under a [Creative Commons Attribution Share-Alike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/) by Audrey Eschright of [Safety First PDX](http://safetyfirstpdx.org/) -* [Code of Conduct template](https://github.com/sagesharp/code-of-conduct-template/) was created by [Otter Tech](https://otter.technology/code-of-conduct-training) and is licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/) - -[Code of Conduct]: README.md diff --git a/src/colophon.md b/src/colophon.md deleted file mode 100644 index 7b17fb8d..00000000 --- a/src/colophon.md +++ /dev/null @@ -1,42 +0,0 @@ -# Colophon - -We build this website with our own Scala-based tooling! It is statically generated with [Laika], a Typelevel project for transforming Markdown into HTML sites and e-books. The search bar is powered by [Protosearch], an in-memory search library with advanced querying features. Both projects use [Cats] and other Typelevel libraries. - -UI components are provided by the [Bulma] CSS framework and icons by [Font Awesome]. Mathematical expressions are rendered during the build with [KaTeX] running on [GraalJS]. - -Finally, all of this is brought together in a [Scala] script that we use to deploy to [GitHub Pages]. - -If you encounter a problem with our website or have feedback, please open an issue on the [repository]. We also welcome contributions! - -[Laika]: https://typelevel.org/Laika/ -[Protosearch]: https://cozydev-pink.github.io/protosearch/ -[Cats]: https://typelevel.org/cats/ -[Bulma]: https://bulma.io/ -[Font Awesome]: https://fontawesome.com/ -[KaTeX]: https://katex.org/ -[GraalJS]: https://www.graalvm.org/latest/reference-manual/js/ -[Scala]: https://www.scala-lang.org/ -[GitHub Pages]: https://pages.github.com/ -[repository]: https://github.com/typelevel/typelevel.github.com - -## License - -In general, the content on this website is licensed under the [Creative Commons Attribution 4.0 International License][CC BY 4.0], except where otherwise noted. - -Blog posts written before 2026 are licensed under the [Creative Commons Attribution 3.0 Unported License][CC BY 3.0]. - -The Typelevel logo is adapted from the ["Progress" Pride Flag][progress] by [Daniel Quasar] and licensed under the [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][CC BY-NC-SA 4.0]. - -[contributors]: https://github.com/typelevel/typelevel.github.com/graphs/contributors -[CC BY 4.0]: https://creativecommons.org/licenses/by/4.0/ -[CC BY 3.0]: https://creativecommons.org/licenses/by/3.0/ -[progress]: https://progress.gay/ -[Daniel Quasar]: https://quasar.digital/ -[CC BY-NC-SA 4.0]: https://creativecommons.org/licenses/by-nc-sa/4.0/ - -## In memoriam - -[Laika] was created and lovingly maintained by [Jens Halm]. This website is possible thanks to his [vision] for a documentation tool that is native to our ecosystem. Jens raised the bar for open source stewardship: beyond the technical excellence of his work on Laika, he consistently published feature roadmaps, detailed issue and PR descriptions, and thorough documentation. Indeed, by creating a documentation tool that integrated so well with our tech stack, he has empowered *all* of us to become exemplary maintainers. Moreover, Jens' enthusiasm to support our community (including entertaining our numerous feature requests with in-depth responses full of context and design insights!) was his most generous gift to us. - -[Jens Halm]: https://github.com/jenshalm -[vision]: https://typelevel.org/Laika/latest/01-about-laika/02-design-goals.html diff --git a/src/community/README.md b/src/community/README.md deleted file mode 100644 index 6c0de16e..00000000 --- a/src/community/README.md +++ /dev/null @@ -1,26 +0,0 @@ -{% - laika.html.template: about.template.html - laika.title: Typelevel Community -%} - -# Join the Typelevel Community - -Here are a few ways to participate in our community! - -@:fragment(discord) -Our Discord server is a community hub where we hang out to learn functional programming, discuss project development, and share cat pics. We embrace curiosity, love teaching, and are not afraid to deep-dive into technical details. Please join and introduce yourself! -@:@ - -@:fragment(meetups) - -Join our monthly virtual meetup! Learn about Typelevel projects and functional programming, ask questions in maintainer office hours, and make friends. Volunteer to give a presentation and practice your speaking skills in a friendly, supportive space. - -@:@ - -@:fragment(projects) -Typelevel boasts an impressive ecosystem of affiliate projects, built by our community. These projects represent our broad interests across network protocols, streaming data, UX, AI/ML, and tooling. Publish your own project and apply to become an affiliate. -@:@ - -@:fragment(coc) -The Typelevel community is dedicated to providing a positive experience for everyone. Whether you are new or familiar with our community, we care about making it a welcoming and safe place for you and we are here to support you. -@:@ diff --git a/src/community/about.template.html b/src/community/about.template.html deleted file mode 100644 index 2568634f..00000000 --- a/src/community/about.template.html +++ /dev/null @@ -1,73 +0,0 @@ -@:embed(/templates/main.template.html) -
- - ${cursor.currentDocument.content} - -
- -
-
-
- - @:svg(fa-person-chalkboard) - -

Virtual Meetups

-
- ${cursor.currentDocument.fragments.meetups} -
- - View Calendar - -
-
-
-
-
-
- - @:svg(fa-puzzle-piece) - -

Affiliate Projects

-
- ${cursor.currentDocument.fragments.projects} -
- - Explore Projects - -
-
-
-
-
-
- - @:svg(fa-hand-holding-heart) - -

Code of Conduct

-
- ${cursor.currentDocument.fragments.coc} -
- - Read the Code of Conduct - -
-
-
-
-
-@:@ diff --git a/src/community/meetups.md b/src/community/meetups.md deleted file mode 100644 index 6a40e900..00000000 --- a/src/community/meetups.md +++ /dev/null @@ -1,18 +0,0 @@ -# Virtual Meetups - -Every month, we host a friendly virtual meetup for the Typelevel Community! Each meetup begins with an icebreaker activity to get to know each other, followed by a presentation on a technical topic. - -Our meetups are open to everyone! All participants must follow the [Typelevel Code of Conduct]. We do not record virtual meetups to respect the privacy of attendees while providing a relaxed, safe space to participate. - -To receive notifications of future meetups subscribe to the calendar below or ask to join the [Google Group]. - -@:html -
- -
-@:@ - -[Typelevel Code of Conduct]: /code-of-conduct/README.md -[Google Group]: https://groups.google.com/a/typelevel.org/g/meetups diff --git a/src/default.template.html b/src/default.template.html deleted file mode 100644 index 455d2574..00000000 --- a/src/default.template.html +++ /dev/null @@ -1,5 +0,0 @@ -@:embed(/templates/main.template.html) -
- ${cursor.currentDocument.content} -
-@:@ diff --git a/src/directory.conf b/src/directory.conf deleted file mode 100644 index f2d7ddba..00000000 --- a/src/directory.conf +++ /dev/null @@ -1,145 +0,0 @@ -# authors - -armanbilge { - name: Arman Bilge - pronouns: "he/him" - title: Executive Director - avatar: "/img/authors/arman.jpg" - email: "arman@typelevel.org" - signal: "https://signal.me/#eu/bkKuxzL3kqy8tWVYy5j4qtMNf8jF9Za6AM5JD4lQzsAB6fFrHKROOIZdeHJUe8Qc" - github: armanbilge - bluesky: armanbil.ge - mastodon: "https://fosstodon.org/@armanbilge" - linkedin: "https://www.linkedin.com/in/armanbilge/" - bio: "I am a member of the Typelevel community and a core maintainer of several projects, including Cats Effect. I also serve as the Executive Director of the Typelevel Foundation, where I help grow our community and support industry adoption of our libraries. I am interested in functional programming, concurrent runtimes, and how community and technology interact in open source :) ask me how you can get involved with Typelevel!" -} - -djspiewak { - name: Daniel Spiewak - title: Director - avatar: "https://github.com/djspiewak.png" - github: djspiewak - mastodon: "https://fosstodon.org/@djspiewak" - linkedin: "https://www.linkedin.com/in/djspiewak/" - bio: "I write code, read papers, and think thoughts. Broadly, I'm interested in: type theory, parser theory, functional abstractions, data structures, performance." -} - -jducoeur { - name: Justin du Coeur (aka Mark Waks) - title: Treasurer - avatar: "https://github.com/jducoeur.png" - github: jducoeur - mastodon: "https://social.coop/@jducoeur" - linkedin: "https://www.linkedin.com/in/jducoeur/" - bio: "I’m a second-generation programmer, starting out on my father’s PDP-8 back in the mid-70s, and I’ve been a language geek ever since, working professionally in everything from LISP to Ada to assembly to C# to JavaScript to C++ to (heaven help me) COBOL, and pretty much everything in between. I picked up Scala back in 2007 (after trying to build a company in Java and winding up in a rage over its limitations); I’ve been working in Scala full-time since 2012. I’ve been doing “light FP” since picking up the style from Ruby around 2002, and "pure FP" in Scala since 2018. During the day, I work at One Pass, building out a suite of health-maintenance tools using the Typelevel stack. In my spare time, I’m the CEO and Architect of Querki, a wiki/database hybrid designed to make it easier for individuals and communities to manage and collaborate on their data." -} - -valencik { - name: Andrew Valencik - title: Secretary - avatar: "https://github.com/valencik.png" - email: "andrew.valencik@gmail.com" - github: valencik - mastodon: "https://mastodon.social/@valencik" - linkedin: "https://www.linkedin.com/in/andrewvalencik/" -} - -samspills { - name: Sam Pillsworth - pronouns: "she/her" - avatar: "https://github.com/samspills.png" - github: samspills - mastodon: "https://hachyderm.io/@spills" - url: "http://www.blerf.ca/" - email: "sam@blerf.ca" - signal: "https://signal.me/#eu/QtscKtx9wLFUMWISf4XqpLAB98KgG5s1dZv3vX88ezWQu8FHNrxI9ImqmE0CbrRW" -} - -lukajcb { - name: "Luka Jacobowitz" - avatar: "https://github.com/LukaJCB.png" - github: LukaJCB - twitter: LukaJacobowitz - bio: "Luka is a functional programmer in love with finding great abstractions to engineering problems. He’s also a maintainer of several typelevel projects and seeks to make learning of pure functional programming as easy as possible." -} - -mpilquist { - name: "Michael Pilquist" - pronouns: "he/him" - avatar: "https://github.com/mpilquist.png" - github: mpilquist - twitter: mpilquist - mastodon: "https://fosstodon.org/@mpilquist" - bio: "Michael Pilquist is the author of Scodec, a suite of open source Scala libraries for working with binary data, and Simulacrum, a library that simplifies working with type classes. He is also a committer on a number of other projects in the Scala ecosystem, including Cats and FS2. He is also the chief software architect at Combined Conditional Access Development (CCAD), a joint venture between Comcast and ARRIS, Inc., where he is responsible for the design and development of control systems that manage tens of millions of cable system devices, including set-top boxes and head-end equipment." -} - -satabin { - name: "Lucas Satabin" - pronouns: "he/him" - avatar: "https://github.com/satabin.png" - email: "lucas.satabin@gnieh.org" - url: "https://blog.gnieh.org" - github: satabin - mastodon: "https://piaille.fr/@lucassatabin" -} - -hkateu { - name: "Herbert Kateu" - avatar: "/img/authors/hkateu.jpg" - email: "hkateu@gmail.com" - github: compile-at-the-nile -} - -bpholt { - name: "Brian P. Holt" - pronouns: "he/him" - avatar: "https://github.com/bpholt.png" - url: "https://www.planetholt.com" - github: bpholt -} - -rossabaker { - name: "Ross A. Baker" - pronouns: "he/him" - avatar: "https://github.com/rossabaker.png" - url: "https://rossabaker.com/" - github: rossabaker - mastodon: "https://social.rossabaker.com/@ross" -} - -antoniojimeneznieto { - name: "Antonio Jimenez" - pronouns: "he/him" - avatar: "https://github.com/antoniojimeneznieto.png" - email: "antonio.jimenez.nieto@gmail.com" - github: antoniojimeneznieto - linkedin: "https://www.linkedin.com/in/antonio-jimenez-nieto/" - bio: "Hi, my name is Antonio. I'm originally from Spain and currently work full-time as software engineer using Scala in Switzerland. I've been using Scala for 5 years, ever since discovering the language during a functional programming course at EPFL. I participated in Google Summer of Code as a student, working on Cats Effect's new I/O integrated runtime leveraging io_uring. Today, I remain actively involved in the project as a co-mentor." -} - -# sponsors - -spotify { - logo: /img/sponsors/spotify.svg - url: "https://engineering.atspotify.com/2024/11/congratulations-to-the-recipients-of-the-2024-spotify-foss-fund" -} - -aruna { - logo: /img/sponsors/aruna.webp - url: "https://aruna.de" -} - -shopify { - logo: /img/sponsors/shopify.svg - url: "https://shopify.engineering" -} - -input-objects { - logo: /img/sponsors/input-objects.svg - url: "https://inputobjects.com/" -} - -famly { - logo: /img/sponsors/famly.svg - url: "https://famly.co" -} diff --git a/src/foundation/README.md b/src/foundation/README.md deleted file mode 100644 index 12db52bd..00000000 --- a/src/foundation/README.md +++ /dev/null @@ -1,53 +0,0 @@ -{% - laika.html.template: about.template.html - laika.title: Typelevel Foundation - sponsors: [${spotify}, ${aruna}, ${shopify}, ${input-objects}, ${famly}] -%} - -# About the Typelevel Foundation - -The Typelevel Foundation is a **nonprofit 501(c)(3) public charity** (EIN: 39-3611111). Our mission is to: -* ensure the **long-term sustainability** of the Typelevel ecosystem; -* advance **research and education** in functional programming; and -* grow our **community** of curious and passionate developers. - -We are committed to transparency and publish our [governing documents], [meeting minutes], and [financial records]. - -[governing documents]: https://github.com/typelevel/foundation -[meeting minutes]: https://github.com/typelevel/foundation/tree/main/minutes -[financial records]: https://drive.google.com/drive/folders/1-_SQ7Ps1QhLdRYf9lpoSVEPSQvFerVy6 - -### Supporting the Foundation - -If your organization relies on Typelevel projects, please consider financially sponsoring the Foundation to support our work. 100% of your donation is tax-deductible to the extent allowed by US law. - -@:html -
-
- Donate - -
-
-@:@ - -We also accept donations on [GitHub Sponsors] and [Open Collective] and are listed in employee giving platforms such as Benevity, CyberGrants, and YourCause. - -**German donors**: we have partnered with the [Maecenata Foundation] to accept tax-deductible donations from Germany. Please use their [donation form][maecenata] and be sure to designate **TG26017 Typelevel Foundation** as the recipient organization. - -**Swiss donors**: we have partnered with the [Swiss Philanthropy Foundation] to accept tax-deductible donations from Switzerland. Please contact them at international@swissphilanthropy.ch to facilitate a donation to us. - -**UK donors**: we have partnered with [Global Giving UK] to claim [Gift Aid] on donations from the United Kingdom. Please donate via [our project page][globalgiving] on their website and make sure to tick the box to add Gift Aid to your donation. - -**European donors**: if your country participates in the [Giving Europe] network, we can accept tax-efficient donations from you. Please contact us to arrange this. - -Have questions or specific needs? Please email us at donate@typelevel.org. - -[GitHub Sponsors]: https://github.com/sponsors/typelevel -[Open Collective]: https://opencollective.com/typelevel-foundation -[Maecenata Foundation]: https://www.maecenata.eu/ -[maecenata]: https://www.maecenata.eu/internationales-spenden?fb_item_id_fix=93115 -[Swiss Philanthropy Foundation]: https://www.swissphilanthropy.ch/ -[Global Giving UK]: http://www.globalgiving.org/ -[Gift Aid]: https://www.gov.uk/donating-to-charity/gift-aid -[globalgiving]: https://www.globalgiving.org/projects/typelevel/ -[Giving Europe]: https://giving.eu/ diff --git a/src/foundation/about.template.html b/src/foundation/about.template.html deleted file mode 100644 index aff432cf..00000000 --- a/src/foundation/about.template.html +++ /dev/null @@ -1,34 +0,0 @@ -@:embed(/templates/main.template.html) -
- - ${cursor.currentDocument.content} - -

- - @:svg(fa-link) - - Board of Directors -

-
- @:for(board) -
- @:include(/templates/bio.template.html) { render-title = true } -
- @:@ -
- -

- - @:svg(fa-link) - - Foundation Sponsors -

-
- @:for(sponsors) -
- -
- @:@ -
-
-@:@ diff --git a/src/foundation/directory.conf b/src/foundation/directory.conf deleted file mode 100644 index d9d08c7c..00000000 --- a/src/foundation/directory.conf +++ /dev/null @@ -1,5 +0,0 @@ -board: [${armanbilge}, ${djspiewak}, ${jducoeur}, ${valencik}] -staff: [${armanbilge}] -tsc: [${armanbilge}, ${lukajcb}, ${samspills}, ${mpilquist}, ${djspiewak}, ${jducoeur}, ${valencik}] -coc: [${armanbilge}, ${hkateu}, ${samspills}, ${satabin}, ${valencik}] -security: [${rossabaker}, ${armanbilge}, ${bpholt}, ${antoniojimeneznieto}] diff --git a/src/foundation/people.md b/src/foundation/people.md deleted file mode 100644 index 12a3d84e..00000000 --- a/src/foundation/people.md +++ /dev/null @@ -1,21 +0,0 @@ -{% - laika.html.template: people.template.html -%} - -# Leadership - -@:fragment(about-board) -The Board of Directors of the Typelevel Foundation is the governing body of Typelevel. Their primary responsibility is to provide oversight for the Foundation's operations, especially legal and financial matters. -@:@ - -@:fragment(about-tsc) -The Technical Steering Committee supports the day-to-day open source work of Typelevel. They advise the Board on overall technical priorities for the Foundation, especially the designation and stewardship of Organization Projects, and are also responsible for curating the portfolio of Typelevel Affiliate Projects. -@:@ - -@:fragment(about-coc) -The Code of Conduct Committee upholds the [Typelevel Code of Conduct](/code-of-conduct/README.md) following the procedures described in the [Enforcement Policy](/code-of-conduct/enforcement.md). Members of this committee have completed [code of conduct enforcement training](https://otter.technology/code-of-conduct-training/) with Otter Technology. -@:@ - -@:fragment(about-security) -The Security Team receives and handles reports of security issues following the procedures described in the [Typelevel Security Policy](/security.md). -@:@ diff --git a/src/foundation/people.template.html b/src/foundation/people.template.html deleted file mode 100644 index 4f44cf6a..00000000 --- a/src/foundation/people.template.html +++ /dev/null @@ -1,75 +0,0 @@ -@:embed(/templates/main.template.html) -
- - ${cursor.currentDocument.content} - -

- - @:svg(fa-link) - - Board of Directors -

-
- ${cursor.currentDocument.fragments.about-board} -
-
- @:for(board) -
- @:include(/templates/bio.template.html) { render-title = true } -
- @:@ -
- -

- - @:svg(fa-link) - - Technical Steering Committee -

-
- ${cursor.currentDocument.fragments.about-tsc} -
-
- @:for(tsc) -
- @:include(/templates/bio.template.html) -
- @:@ -
- -

- - @:svg(fa-link) - - Code of Conduct Committee -

-
- ${cursor.currentDocument.fragments.about-coc} -
-
- @:for(coc) -
- @:include(/templates/bio.template.html) -
- @:@ -
- -

- - @:svg(fa-link) - - Security Team -

-
- ${cursor.currentDocument.fragments.about-security} -
-
- @:for(security) -
- @:include(/templates/bio.template.html) -
- @:@ -
- - @:@ -
diff --git a/src/gsoc/README.md b/src/gsoc/README.md deleted file mode 100644 index 8c3f41a0..00000000 --- a/src/gsoc/README.md +++ /dev/null @@ -1,46 +0,0 @@ -{% - laika.title: Google Summer of Code - welcome-tab.class: bulma-is-active -%} - -# Welcome! - -Typelevel is an ecosystem of projects and a community of people united to foster an inclusive, welcoming, and safe environment around functional programming in Scala. -We work together to develop projects that apply functional programming to challenging problems relevant in industry. -Our community culture embraces curiosity and mentoring and we don’t shy away from experimenting with new and exciting ideas. -Most of all, we love to make programming joyful and social. - -@:style(bulma-notification bulma-is-danger bulma-is-light) - Due to overwhelming participation in GSoC 2026, we are only able to consider proposals from applicants who **complete our [onboarding process][onboarding] by Monday, March 16th**. - If you missed this deadline, we appreciate your interest and hope you will apply next year! -@:@ - -## Getting Started - -We are excited to be a Mentoring Organization in [Google Summer of Code 2026][GSoC]! If you are interested to join Typelevel as a GSoC Contributor, here are some ways to get started: - -* Make your first contribution to our [onboarding repository][onboarding]. -* Join our [Discord server][invite] and introduce yourself in the [#summer-of-code][invite] channel. -* Subscribe to the [Google Group][group] for announcements. -* Check the [calendar] for upcoming events and [videos of past events][videos]. - -We cannot wait to meet you! -You can also reach us at gsoc@typelevel.org with any questions. - -[onboarding]: https://github.com/typelevel/gsoc-onboarding -[group]: https://groups.google.com/a/typelevel.org/g/gsoc-applicants -[GSoC]: https://summerofcode.withgoogle.com/ -[invite]: https://discord.gg/382Z3w8QTj -[events]: events.md#calendar -[videos]: https://youtube.com/playlist?list=PL_5uJkfWNxdmHk2CtFxDVGiJxpU_UaD2s - -## Learning Resources - -To learn Scala and functional programming, we recommend these books. -* [*The Scala 3 Book*][scala book] -* [*Creative Scala: Form and Function*][creative] -* [*Functional Programming in Scala*][fpis], aka "The Red Book" - -[scala book]: https://docs.scala-lang.org/scala3/book/introduction.html -[creative]: https://www.creativescala.org/creative-scala/ -[fpis]: https://www.manning.com/books/functional-programming-in-scala-second-edition diff --git a/src/gsoc/ai.md b/src/gsoc/ai.md deleted file mode 100644 index 9a98d0ed..00000000 --- a/src/gsoc/ai.md +++ /dev/null @@ -1,22 +0,0 @@ -{% - laika.title: GSoC AI Policy - ai-tab.class: bulma-is-active -%} - -# Typelevel GSoC AI Policy - -Our AI policy is based on the following ideas. - -* **Respect maintainer time.** Our maintainers and GSoC mentors are volunteers who invest many hours reviewing open source contributions to ensure that Typelevel projects have high-quality, maintainable codebases. Please respect their time by submitting your best work. - -* **GSoC is about learning first, delivering second.** We prefer that you take your time and ask for help when contributing to our projects, rather than using AI to quickly write code that may work, but you do not understand. Our midterm and final evaluations of your work will consider how you grew as an open source contributor, in addition to what you created. - -* **AI has become a part of the contemporary developer's toolkit.** We do not want to blanket ban the use of AI. We recognize its value when used thoughtfully and want to support you in learning when and how to use AI effectively. - -With these principles in mind, to contribute to Typelevel as part of GSoC we ask you to follow these rules. - -1. **Demonstrate ownership of your code**, no matter how you wrote it. This means understanding why it is appropriate to Typelevel's needs and how it works, being capable of explaining that, and also creating and performing tests to confirm that it works correctly, both for yourself and for your collaborators. It is okay to ask for help! - -2. ***Never* use AI to generate descriptions for PRs or issues.** Similarly, please do not use AI to write your GSoC proposal or other communications such as email and Discord messages. The purpose of writing in these contexts is to organize your thoughts and directly communicate your ideas, not to craft a "polished" document. However, you may use AI to translate between English and another (natural) language. - -3. **Always communicate when you have used AI** to write code, typically in your PR description. It is also helpful to explain where and how you used it, such as by including any prompts. diff --git a/src/gsoc/default.template.html b/src/gsoc/default.template.html deleted file mode 100644 index dec7fef0..00000000 --- a/src/gsoc/default.template.html +++ /dev/null @@ -1,8 +0,0 @@ -@:embed(/templates/main.template.html) -
- @:include(gsoc.template.html) -
- ${cursor.currentDocument.content} -
-
-@:@ diff --git a/src/gsoc/directory.conf b/src/gsoc/directory.conf deleted file mode 100644 index a0c31996..00000000 --- a/src/gsoc/directory.conf +++ /dev/null @@ -1,213 +0,0 @@ -medium-length: Medium (~ 175 hours) -long-length: Long (~ 350 hours) - -ideas: [ - { - title: Serverless integrations for Feral - description: - Feral is a Typelevel library for building serverless functions that currently supports AWS Lambda and Google Cloud Run Functions. We want to add support for more types of serverless events and more cloud providers. - prereqs: "Scala, ideally experience with serverless" - difficulty: Medium. - length: ${medium-length} - mentors: [armanbilge, bpholt, Chingles2404] - categories: [cloud, programming languages] - repolinks: [ - { - name: feral - url: "https://github.com/typelevel/feral" - } - ] - }, - - { - title: Native I/O backend for FS2 JVM - description: - "FS2 on the JVM currently implements its networking API using JDK NIO. Unfortunately this indirection incurs a non-trivial performance penalty. We want to replace the use of JDK NIO with direct calls to system I/O APIs such as epoll and kqueue." - prereqs: "Scala, ability to read C" - difficulty: Medium. - length: ${long-length} - mentors: [antoniojimeneznieto, djspiewak, mpilquist, armanbilge] - categories: [operating systems, programming languages] - repolinks: [ - { - name: fs2 - url: "https://github.com/typelevel/fs2" - } - ] - }, - - { - title: FS2 Connection API - description: - "TCP-based protocols are common (e.g. HTTP, Postgres, Redis) and are implemented by clients to interface with these services (e.g. Ember, Skunk, Rediculous). The goal of this project is to create a "connection" API that supports pooling, error conditions, and metrics and can be shared by all of our client libraries." - prereqs: "Scala, ideally some knowledge of networking" - difficulty: Hard. - length: ${long-length} - mentors: [mpilquist, armanbilge] - categories: [operating systems, programming languages] - repolinks: [ - { - name: fs2 - url: "https://github.com/typelevel/fs2" - } - ] - }, - - { - title: Web Components for Calico - description: - "Calico is a reactive UI library built with Cats Effect and FS2. Web Components are a standard for creating framework-agnostic, reusable UI elements. The goal of this project is to enable Calico users to access the vast array of web components available by improving its DSL and code-generation." - prereqs: "Scala, ideally experience with Web APIs" - difficulty: Medium. - length: ${long-length} - mentors: [armanbilge] - categories: [web, programming languages] - repolinks: [ - { - name: calico - url: "https://github.com/armanbilge/calico" - } - ] - }, - - { - title: Upgrade sbt-typelevel to sbt 2 - description: - "sbt-typelevel is a plugin for sbt, the Scala build tool, used by hundreds of open source and enterprise projects. sbt 2 is in the final stages of development. We want to upgrade sbt-typelevel to sbt 2 and adopt its new features, such as "project matrix" for cross-building." - prereqs: Scala - difficulty: Medium. - length: ${long-length} - mentors: [mzuehlke, armanbilge] - categories: [development tools] - repolinks: [ - { - name: sbt-typelevel - url: "https://github.com/typelevel/sbt-typelevel" - } - ] - }, - - { - title: Refresh Davenverse projects - description: - "The Davenverse is a collection of several popular Typelevel libraries, including Mules and cats-scalacheck. Unfortunately, we have fallen behind on their maintenance. We want to move these libraries under the Typelevel org, refresh their build tooling, and bring them up-to-date to ensure their longevity." - prereqs: Scala - difficulty: Medium. - length: ${medium-length} - mentors: [armanbilge, valencik] - categories: [development tools, programming languages] - repolinks: [ - { - name: davenverse - url: "https://github.com/davenverse" - } - ] - }, - - { - title: "Cats Effect & FS2 on Wasm/WASI" - description: - "Web Assembly and its System Interface are emerging technologies for deploying secure, modular applications. The goal of this project is to prototype porting the Cats Effect runtime and FS2 streaming I/O to the Wasm/WASI platform, also possibly generating feedback for the Scala WASM and WASI teams." - prereqs: "Scala, ideally some experience with Wasm/WASI" - difficulty: "Hard. Wasm/WASI support in Scala is experimental." - length: ${long-length} - mentors: [armanbilge, tanishiking, valencik] - categories: [web, cloud, operating systems, programming languages] - repolinks: [ - { - name: cats-effect - url: "https://github.com/typelevel/cats-effect" - }, - { - name: fs2 - url: "https://github.com/typelevel/fs2" - } - ] - }, - - { - title: Laika enhancements for typelevel.org - description: - "Laika is a purely functional site and e-book generator and customizable text markup transformer. We recently migrated the Typelevel website from Jekyll to Laika. The goal of this project is improve and streamline Laika's support for generating non-documentation websites, such as blogs." - prereqs: Scala - difficulty: Medium. - length: ${medium-length} - mentors: [armanbilge, valencik] - categories: [web, programming languages] - repolinks: [ - { - name: Laika - url: "https://github.com/typelevel/Laika" - }, - { - name: typelevel.org - url: "https://github.com/typelevel/typelevel.github.com" - } - ] - }, - - { - title: A faster immutable list datatype - description: - "Immutable linked lists are a core datatype in functional programming languages. The goal of this project is to explore implementing a list-like datatype with enhanced performance. Along the way, you will learn about algebraic datatypes, Cats typeclasses, and mechanical sympathy." - prereqs: Interest in functional programming - difficulty: "Medium. This is a good project for beginners!" - length: ${long-length} - mentors: [armanbilge, johnynek] - categories: [web, programming languages] - repolinks: [ - { - name: Cats Collections - url: "https://github.com/typelevel/cats-collections" - } - ] - }, - - { - title: Doodle Immediate Mode Algebra - description: "Design and implement an API for Doodle that allows low-level bitmap based operations." - prereqs: "Proficient with Scala and some understanding of computer graphics." - difficulty: Medium - length: ${medium-length} - mentors: [noelwelsh] - categories: [media, programming languages] - repolinks: [ - { - name: Doodle - url: "https://github.com/creativescala/doodle/issues/93" - } - ] - }, - - { - title: Doodle Skia Backend - description: "Add a Skia backend to Doodle, greatly improving the performance and expressivity available on the JVM." - prereqs: "Proficient with Scala and some understanding of computer graphics. This involves working with a library (Skija) that is not well documented and itself wraps a C++ library with patchy documentation. Hence a willingness to dive into foreign code bases is necessary." - difficulty: Medium - length: ${medium-length} - mentors: [noelwelsh] - categories: [media, programming languages] - repolinks: [ - { - name: Doodle - url: "https://github.com/creativescala/doodle/issues/175" - } - ] - }, - - { - title: Krop Template System - description: "Add a template system to the Krop web framework that works client- and server-side." - prereqs: "Proficient with Scala and a reasonable understanding of parsing." - difficulty: Medium - length: ${medium-length} - mentors: [noelwelsh, j-mie6] - categories: [web, programming languages] - repolinks: [ - { - name: Krop - url: "https://github.com/creativescala/krop/issues/14" - } - ] - }, -] diff --git a/src/gsoc/events.md b/src/gsoc/events.md deleted file mode 100644 index 81d753f7..00000000 --- a/src/gsoc/events.md +++ /dev/null @@ -1,24 +0,0 @@ -{% - laika.title: GSoC Events - events-tab.class: bulma-is-active -%} - -# Events - -Join one of our virtual events to learn more about Typelevel and how you can get involved. - -### Past events - -We publish recordings of past events to our [YouTube Channel]. - -* February 26, 2026 - ["Intro to GSoC with Typelevel"](https://youtu.be/oOk9-HQZhRk) - -## Calendar - -@:html -
- -
-@:@ - -[YouTube Channel]: https://youtube.com/playlist?list=PL_5uJkfWNxdmHk2CtFxDVGiJxpU_UaD2s diff --git a/src/gsoc/gsoc.template.html b/src/gsoc/gsoc.template.html deleted file mode 100644 index c02ead31..00000000 --- a/src/gsoc/gsoc.template.html +++ /dev/null @@ -1,10 +0,0 @@ -

Google Summer of Code

- -
- -
diff --git a/src/gsoc/ideas.md b/src/gsoc/ideas.md deleted file mode 100644 index 187d5aee..00000000 --- a/src/gsoc/ideas.md +++ /dev/null @@ -1,13 +0,0 @@ -{% - laika.title: GSoC Ideas - ideas-tab.class: bulma-is-active - laika.html.template: ideas.template.html -%} - -Our community has identified project ideas that we believe will significantly enhance the Typelevel ecosystem. Nothing is set in stone: we may be able to adjust a project’s length and difficulty to make it the right fit for you. So if you see something here that interests you or have an idea of your own, please [get in touch][email]! - -@:fragment(hero) - @:style(bulma-is-size-5 bulma-has-text-weight-medium) Are you interested in working on a GSoC project with mentorship from Typelevel maintainers? @:@ -@:@ - -[email]: mailto:gsoc@typelevel.org diff --git a/src/gsoc/ideas.template.html b/src/gsoc/ideas.template.html deleted file mode 100644 index 8f47f29d..00000000 --- a/src/gsoc/ideas.template.html +++ /dev/null @@ -1,44 +0,0 @@ -@:embed(/templates/main.template.html) -
- @:include(gsoc.template.html) -
- ${cursor.currentDocument.content} -
- -
- @:for(ideas) -
-
-
-
-

${_.title}

-
- @:for(_.categories) - ${_} - @:@ -
-

${_.description}

-

Prerequisites
${_.prereqs}

-

Expected Difficulty
${_.difficulty}

-

Expected Length
${_.length}

-

Mentors
@:for(_.mentors) @${_}@:@

-

Related Repositories
@:for(_.repolinks) ${_.name}@:@

-
-
-
-
- @:@ -
-
- -
-
-
- ${cursor.currentDocument.fragments.hero} -
- Submit Proposal -
-
- -@:@ diff --git a/src/img/places/berlin.md b/src/img/places/berlin.md deleted file mode 100644 index 496efff5..00000000 --- a/src/img/places/berlin.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/berlin.jpg) -"[Berlin-Brandenburg Gate overview](https://commons.wikimedia.org/wiki/File:Berlin-Brandenburg_Gate_overview.jpg)" by [Cezary Piwowarski](https://en.wikipedia.org/wiki/pl:User:Cezary_p) is licensed under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/). -@:@ diff --git a/src/img/places/cadiz.md b/src/img/places/cadiz.md deleted file mode 100644 index 685dbc30..00000000 --- a/src/img/places/cadiz.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/cadiz.jpg) -"[Cádiz](https://www.flickr.com/photos/michalo/6931278196/)" by [Anna & Michal](https://www.flickr.com/people/michalo/) is licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). -@:@ diff --git a/src/img/places/cambridge.md b/src/img/places/cambridge.md deleted file mode 100644 index 61224008..00000000 --- a/src/img/places/cambridge.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/cambridge.jpg) -"View from Prudential Tower, Boston" by yeowatzup is licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). -@:@ diff --git a/src/img/places/copenhagen.md b/src/img/places/copenhagen.md deleted file mode 100644 index 152997ea..00000000 --- a/src/img/places/copenhagen.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/copenhagen.jpg) -"[Nyhavn panorama](https://commons.wikimedia.org/wiki/File:Nyhavn-panorama.jpg)" by Scythian is licensed under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/). -@:@ diff --git a/src/img/places/lakedistrict.md b/src/img/places/lakedistrict.md deleted file mode 100644 index 9a55b03b..00000000 --- a/src/img/places/lakedistrict.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/lakedistrict.jpg) -Image under commercial license. All rights reserved. -@:@ diff --git a/src/img/places/lausanne.md b/src/img/places/lausanne.md deleted file mode 100644 index 00919a89..00000000 --- a/src/img/places/lausanne.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/lausanne.jpg) -"[lauvax](https://www.flickr.com/photos/harmishhk/15052138480)" by [harmishhk](https://www.flickr.com/people/harmishhk/) is licensed under [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/). -@:@ diff --git a/src/img/places/london.md b/src/img/places/london.md deleted file mode 100644 index 2c539476..00000000 --- a/src/img/places/london.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/london.jpg) -Image by John Nuttall is licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). -@:@ diff --git a/src/img/places/lyon.md b/src/img/places/lyon.md deleted file mode 100644 index e85bdc12..00000000 --- a/src/img/places/lyon.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/lyon.jpg) -"[Saone River](https://www.flickr.com/photos/archer10/15941037949/)" by [Dennis Jarvis](https://www.flickr.com/people/archer10/) is licensed under [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/). -@:@ diff --git a/src/img/places/oslo.md b/src/img/places/oslo.md deleted file mode 100644 index 2fa0ed9d..00000000 --- a/src/img/places/oslo.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/oslo.jpg) -"Oslo opera house" by [Tobias Van Der Elst](https://www.flickr.com/people/79899037@N04/) is licensed under [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/). -@:@ diff --git a/src/img/places/philly.md b/src/img/places/philly.md deleted file mode 100644 index 9269e61d..00000000 --- a/src/img/places/philly.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.targetFormats = [] %} - -@:figure(/img/places/philly.jpg) -"[I wish I was a little bit taller](https://www.flickr.com/photos/ryanhallock/18138606858/)" by [Ryan Hallock](https://www.flickr.com/people/ryanhallock/) is licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). -@:@ diff --git a/src/projects/README.md b/src/projects/README.md deleted file mode 100644 index 17216135..00000000 --- a/src/projects/README.md +++ /dev/null @@ -1,3 +0,0 @@ -{% - laika.title: Project Index -%} diff --git a/src/projects/default.template.html b/src/projects/default.template.html deleted file mode 100644 index 8da2408d..00000000 --- a/src/projects/default.template.html +++ /dev/null @@ -1,41 +0,0 @@ -@:embed(/templates/main.template.html) - -
-

Project Index

-
- @:for(projects) -
-
-
-

- ${_.title} -

- - @:svg(fa-github) - - @:for(_.permalink) - - @:svg(fa-book) - - @:@ -
-
-

${_.description}

-
- @:if(_.affiliate) - Affiliate Project - @:else - Organization Project - @:@ - @:for(_.platforms) - ${_} - @:@ -
-
-
-
- @:@ -
-
- -@:@ diff --git a/src/projects/directory.conf b/src/projects/directory.conf deleted file mode 100644 index 2211a80c..00000000 --- a/src/projects/directory.conf +++ /dev/null @@ -1,573 +0,0 @@ -projects = [ - { - title: "argonaut-shapeless" - description: "Automatic derivation for argonaut" - github: "https://github.com/alexarchambault/argonaut-shapeless" - affiliate: true - platforms: [jvm] - }, - { - title: "banana-rdf" - description: "RDF, SPARQL and Linked Data technologies" - github: "https://github.com/banana-rdf/banana-rdf" - affiliate: true - platforms: [js, jvm] - }, - { - title: "calico" - description: "Pure, reactive UI library for building web applications with Cats Effect + FS2" - github: "https://github.com/armanbilge/calico" - permalink: "https://armanbilge.github.io/calico" - affiliate: true - platforms: [js] - }, - { - title: "cats-actors" - description: "An Actor Model implementation built on top of Cats-Effect, providing a higher-level abstraction for managing concurrency." - github: "https://github.com/suprnation/cats-actors" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "case-insensitive" - description: "A case-insensitive string for Scala" - github: "https://github.com/typelevel/case-insensitive" - platforms: [js, jvm, native] - }, - { - title: "catapult" - description: "A thin wrapper for the Launch Darkly Java server SDK using cats-effect and fs2" - github: "https://github.com/typelevel/catapult" - platforms: [jvm] - }, - { - title: "catbird" - description: "Cats instances for various Twitter Open Source Scala projects" - github: "https://github.com/typelevel/catbird" - platforms: [jvm] - }, - { - title: "Cats" - description: "A library intended to provide abstractions for functional programming in Scala, leveraging its unique features. Design goals are approachability, modularity, documentation and efficiency." - permalink: "https://typelevel.org/cats/" - github: "https://github.com/typelevel/cats" - platforms: [js, jvm, native] - }, - { - title: "Cats Collections" - description: "Data structures that facilitate pure functional programming with cats" - github: "https://github.com/typelevel/cats-collections" - platforms: [js, jvm, native] - }, - { - title: "Cats-Effect" - description: "The IO Monad for Scala, plus type classes for general effect types." - github: "https://github.com/typelevel/cats-effect/" - platforms: [js, jvm, native] - }, - { - title: "Cats MTL" - description: "Monad transformers made easy" - github: "https://github.com/typelevel/cats-mtl/" - platforms: [js, jvm, native] - }, - { - title: "cats-parse" - description: "A parsing library for the cats ecosystem" - github: "https://github.com/typelevel/cats-parse" - platforms: [js, jvm, native] - }, - { - title: "cats-scalatest" - description: "Scalatest bindings for Cats." - github: "https://github.com/IronCoreLabs/cats-scalatest" - affiliate: true - platforms: [js, jvm] - }, - { - title: "cats-stm" - description: "A STM implementation for Cats Effect" - github: "https://github.com/TimWSpence/cats-stm" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Cats Tagless" - description: "A library of utilities for tagless final algebras" - github: "https://github.com/typelevel/cats-tagless/" - platforms: [js, jvm, native] - }, - { - title: "Cats-Time" - description: "Instances for Cats Typeclasses for Java 8 Time" - github: "https://github.com/typelevel/cats-time/" - platforms: [js, jvm, native] - }, - { - title: "Circe" - description: "Yet another JSON library for Scala" - github: "https://github.com/circe/circe" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Ciris" - description: "Functional Configurations for Scala" - github: "https://github.com/vlovgr/ciris" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "coulomb" - description: "A statically typed unit analysis library for Scala" - github: "https://github.com/erikerlandson/coulomb" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "cron4s" - description: "Cross-platform CRON expression parsing for Scala" - github: "https://github.com/alonsodomin/cron4s" - affiliate: true - platforms: [js, jvm] - }, - { - title: "decline" - description: "A composable command-line parser for Scala." - github: "https://github.com/bkirwi/decline" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "discipline" - description: "Originally intended for internal use in spire, this library helps libraries declaring type classes to precisely state the laws which instances need to satisfy, and takes care of not checking derived laws multiple times." - github: "https://github.com/typelevel/discipline" - platforms: [js, jvm, native] - }, - { - title: "doobie" - description: "A pure functional JDBC layer for Scala. It is not an ORM, nor is it a relational algebra; it just provides a principled way to construct programs (and higher-level libraries) that use JDBC." - github: "https://github.com/typelevel/doobie" - platforms: [jvm] - }, - { - title: "edomata" - description: "Event-driven automata for Scala, Scala.js and scala native. This library provides purely functional state machines that can be used to create event sourced and/or CQRS style applications. It also includes production ready backends." - github: "https://github.com/hnaderi/edomata" - permalink: "https://edomata.ir/" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "eff" - description: "Extensible effects are an alternative to monad transformers for computing with effects in a functional way. This library is based on the “free-er” monad and an “open union” of effects described by Oleg Kiselyov in “Freer monads, more extensible effects”" - permalink: "http://atnos-org.github.io/eff" - github: "https://github.com/atnos-org/eff" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "endless4s" - description: "Sharded and event-sourced entities using tagless-final algebras" - permalink: "https://endless4s.github.io/" - github: "https://github.com/endless4s/endless" - affiliate: true - platforms: [jvm] - }, - { - title: "Extruder" - description: "Populate case classes from any configuration source" - github: "https://github.com/janstenpickle/extruder" - affiliate: true - platforms: [jvm] - }, - { - title: "fabric" - description: "Object-Notation Abstraction for JSON, binary, HOCON, etc." - github: "https://github.com/typelevel/fabric" - platforms: [js, jvm, native] - }, - { - title: "Feral" - description: "Feral cats are homeless, feral functions are serverless" - github: "https://github.com/typelevel/feral" - platforms: [js, jvm] - }, - { - title: "ff4s" - description: "A purely functional web frontend framework for Scala.js." - github: "https://github.com/buntec/ff4s" - affiliate: true - platforms: [js] - }, - { - title: "Fetch" - description: "Library built on top of Cats that provides efficient data access from heterogeneous dataurces" - github: "https://github.com/47deg/fetch" - affiliate: true - platforms: [js, jvm] - }, - { - title: "Finch" - description: "Purely functional basic blocks atop of Finagle for building composable HTTP APIs" - github: "https://github.com/finagle/finch" - affiliate: true - platforms: [jvm] - }, - { - title: "Frameless" - description: "Frameless is a library for working with Spark using more expressive types." - github: "https://github.com/typelevel/frameless" - platforms: [jvm] - }, - { - title: "fs2-aes" - description: "Micro library providing AES encryption/decryption of fs2.Stream[F, Byte]." - github: "https://github.com/jwojnowski/fs2-aes" - affiliate: true - platforms: [jvm] - }, - { - title: "fs2-compress" - description: "Compression Algorithms for Fs2 " - github: "https://github.com/lhns/fs2-compress" - affiliate: true - platforms: [jvm] - }, - { - title: "fs2-data" - description: "Parse and transform data (CBOR, CSV, JSON, XML) in a streaming manner" - github: "https://github.com/gnieh/fs2-data" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "fs2-dom" - description: "Idiomatic Cats Effect + FS2 integrations for Web APIs" - github: "https://github.com/armanbilge/fs2-dom" - affiliate: true - platforms: [js] - }, - { - title: "fs2-grpc" - description: "gRPC implementation for FS2/cats-effect" - github: "https://github.com/typelevel/fs2-grpc" - platforms: [jvm] - }, - { - title: "fs2" - description: "FS2 is a library for purely functional, effectful, and polymorphic stream processing library in the Scala programming language. Its design goals are compositionality, expressiveness, resource safety, and speed. The name is a modified acronym for Functional Streams for Scala (FSS, or FS2)." - github: "https://github.com/typelevel/fs2" - platforms: [js, jvm, native] - }, - { - title: "Grackle" - description: "Functional GraphQL server for the Typelevel stack" - github: "https://github.com/typelevel/grackle" - platforms: [js, jvm, native] - }, - { - title: "Hammock" - description: "Purely functional HTTP client" - github: "https://github.com/pepegar/hammock" - affiliate: true - platforms: [jvm] - }, - { - title: "http4s" - description: "A typeful, purely functional HTTP library for client and server applications" - github: "https://github.com/http4s/http4s" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "imp" - description: "Summoning implicit values" - github: "https://github.com/non/imp" - affiliate: true - platforms: [js, jvm] - }, - { - title: "jawn-fs2" - description: "Integration of jawn and fs2 for streaming, incremental JSON parsing" - github: "https://github.com/typelevel/jawn-fs2" - platforms: [js, jvm, native] - }, - { - title: "keypool" - description: "A Keyed Pool Implementation for Scala" - github: "https://github.com/typelevel/keypool" - platforms: [js, jvm, native] - }, - { - title: "kind-projector" - description: "Plugin for nicer type-lambda syntax" - github: "https://github.com/typelevel/kind-projector" - platforms: [jvm] - }, - { - title: "Kittens" - description: "Automatic type class derivation" - github: "https://github.com/typelevel/kittens" - platforms: [js, jvm, native] - }, - { - title: "Laika" - description: "Site and e-book generator and customizable text markup transformer for sbt, Scala and Scala.js" - github: "https://github.com/typelevel/Laika" - platforms: [js, jvm] - }, - { - title: "LDBC" - description: "Pure functional JDBC layer with Cats Effect 3 and Scala 3" - github: "https://github.com/takapi327/ldbc" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Lepus" - description: "Purely functional, non-blocking RabbitMQ client for scala, scala js and scala native built on top of fs2." - github: "https://github.com/hnaderi/lepus" - permalink: "https://lepus.hnaderi.dev/" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Libra" - description: "Compile time dimensional analysis for any problem domain" - github: "https://github.com/to-ithaca/libra" - affiliate: true - platforms: [js, jvm] - }, - { - title: "literally" - description: "Compile time validation of literal values built from strings" - github: "https://github.com/typelevel/literally" - platforms: [js, jvm, native] - }, - { - title: "log4cats" - description: "Logging Tools For Interaction with cats-effect" - github: "https://github.com/typelevel/log4cats" - platforms: [js, jvm, native] - }, - { - title: "Monix" - description: "High-performance library for composing asynchronous, event-based programs, exposing a Reactive Streams implementation along with primitives for dealing with concurrency and side-effects." - github: "https://github.com/monix/monix" - permalink: "https://monix.io" - affiliate: true - platforms: [js, jvm] - }, - { - title: "Monocle" - description: "Optics library offering a simple yet powerful API to access and transform immutable data" - github: "https://github.com/optics-dev/Monocle" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Mouse" - description: "Enrichments to standard library classes to ease functional programming" - github: "https://github.com/typelevel/mouse/" - platforms: [js, jvm, native] - }, - { - title: "Natchez" - description: "functional tracing for cats " - github: "https://github.com/typelevel/natchez" - platforms: [js, jvm, native] - }, - { - title: "otel4s" - description: "An OpenTelemetry library based on cats-effect" - github: "https://github.com/typelevel/otel4s" - platforms: [js, jvm, native] - }, - { - title: "Outwatch" - description: "The Functional and Reactive Web-Frontend Library for Scala.js" - github: "https://github.com/outwatch/outwatch" - affiliate: true - platforms: [js] - }, - { - title: "parsley-cats" - description: "The parsley-cats library exposes Cats instances for Parsley parsing library." - github: "https://github.com/j-mie6/parsley-cats" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Peloton" - description: "An actor library for Cats Effect" - github: "https://github.com/killaitis/peloton" - affiliate: true - platforms: [jvm] - }, - { - title: "perspective" - description: "Provides tools for generic programming, and typeclasses for monad transformers and higher kinded data." - github: "https://github.com/Katrix/perspective" - affiliate: true - platforms: [js, jvm] - }, - { - title: "PureConfig" - description: "A boilerplate-free library for loading configuration files" - github: "https://github.com/pureconfig/pureconfig" - affiliate: true - platforms: [jvm] - }, - { - title: "refined" - description: "Tools for refining types with type-level predicates which constrain the set of values described by the refined type, for example restricting to positive or negative numbers." - github: "https://github.com/fthomas/refined" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "ScalaCheck" - description: "ScalaCheck is a library for automated property-based testing. It contains generators for randomized test data and combinators for properties." - github: "https://github.com/typelevel/scalacheck" - permalink: "http://scalacheck.org/" - platforms: [js, jvm, native] - }, - { - title: "scalacheck-shapeless" - description: "Automatic derivation for ScalaCheck" - github: "https://github.com/alexarchambault/scalacheck-shapeless" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Scala Exercises" - description: "Platform and framework for Scala devs to learn about Scala libraries" - github: "https://github.com/scala-exercises/scala-exercises" - affiliate: true - platforms: [js, jvm] - }, - { - title: "scala-steward" - description: "A robot that helps keeping Scala projects up-to-date" - github: "https://github.com/fthomas/scala-steward" - affiliate: true - platforms: [jvm] - }, - { - title: "scodec" - description: "scodec is a combinator library for working with binary data. It focuses on contract-first and pure functional encoding and decoding of binary data and provides integration with shapeless." - github: "https://github.com/scodec/scodec" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Scoverage" - description: "Code coverage tool for Scala" - github: "https://github.com/scoverage/scalac-scoverage-plugin" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "Shapeless" - description: "Shapeless is a generic programming library. Starting with implementations of Scrap your boilerplate and higher rank polymorphism in Scala, it quickly grew to provide advanced abstract tools like heterogenous lists and automatic instance derivation for type classes." - github: "https://github.com/milessabin/shapeless" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "simulacrum" - description: "First-class syntax for type classes" - github: "https://github.com/typelevel/simulacrum" - platforms: [js, jvm, native] - }, - { - title: "Simulacrum Scalafix" - description: "Simulacrum as Scalafix rules" - github: "https://github.com/typelevel/simulacrum-scalafix" - platforms: [js, jvm] - }, - { - title: "singleton-ops" - description: "Operations for primitive and String singleton types" - github: "https://github.com/fthomas/singleton-ops" - affiliate: true - platforms: [js, jvm] - }, - { - title: "Skunk" - description: "A data access library for Scala + Postgres" - github: "https://github.com/typelevel/skunk" - platforms: [js, jvm, native] - }, - { - title: "sonic" - description: "Property-based testing with integrated shrinking" - github: "https://github.com/melrief/sonic" - affiliate: true - platforms: [jvm] - }, - { - title: "specs2" - description: "specs2 is a library for writing executable software specifications, aiming for conciseness, readability and extensibility." - github: "https://github.com/etorreborre/specs2" - permalink: "http://specs2.org/" - affiliate: true - platforms: [js, jvm, native] - }, - { - title: "spire" - description: "Spire is a numeric library for Scala which is intended to be generic, fast, and precise. Using features such as specialization, macros, type classes, and implicits, Spire works hard to defy conventional wisdom around performance and precision trade-offs." - github: "https://github.com/typelevel/spire" - platforms: [js, jvm, native] - }, - { - title: "Squants" - description: "The Scala API for Quantities, Units of Measure and Dimensional Analysis" - github: "https://github.com/typelevel/squants" - platforms: [js, jvm, native] - }, - { - title: "Twiddles" - description: "Micro-library for building effectful protocols" - github: "https://github.com/typelevel/twiddles" - platforms: [js, jvm, native] - }, - { - title: "TwoTails" - description: "A compiler plugin adding support for mutual tail recursion" - github: "https://github.com/wheaties/TwoTails" - affiliate: true - platforms: [jvm] - }, - { - title: "typelevel.g8" - description: "A Giter8 template for sbt-typelevel" - github: "https://github.com/typelevel/typelevel.g8" - platforms: [js, jvm] - }, - { - title: "typelevel-nix" - description: "Development tools for Typelevel projects" - github: "https://github.com/typelevel/typelevel-nix" - platforms: [js, jvm, native]}, - { - title: "uniform-scala" - description: "Functional user journeys" - github: "https://github.com/ltbs/uniform-scala" - affiliate: true - platforms: [js, jvm] - }, - { - title: "upperbound" - description: "A purely functional, interval based rate limiter" - github: "https://github.com/SystemFw/upperbound" - affiliate: true - platforms: [js, jvm, native]}, - { - title: "vault" - description: "Type-safe, persistent storage for values of arbitrary types" - github: "https://github.com/typelevel/vault" - platforms: [js, jvm, native] - } -] diff --git a/src/templates/bio.template.html b/src/templates/bio.template.html deleted file mode 100644 index 78c41008..00000000 --- a/src/templates/bio.template.html +++ /dev/null @@ -1,67 +0,0 @@ -
- @:for(_.avatar) -
-

- -

-
- @:@ -
-
-

- ${_.name} @:for(_.pronouns) ${_.pronouns} @:@ - @:if(_.render-bio) - @:for(_.bio) -
- ${_.bio} - @:@ - @:@ - @:if(_.render-title) - @:for(_.title) -
- ${_.title} - @:@ - @:@ -

-
- -
-
diff --git a/src/templates/footer.template.html b/src/templates/footer.template.html deleted file mode 100644 index bc2c1f93..00000000 --- a/src/templates/footer.template.html +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/templates/home.template.html b/src/templates/home.template.html deleted file mode 100644 index 2c0f5ef2..00000000 --- a/src/templates/home.template.html +++ /dev/null @@ -1,45 +0,0 @@ -{% -sponsors: [${spotify}, ${aruna}, ${shopify}, ${input-objects}, ${famly}] -%} - -@:embed(/templates/main.template.html) -
-
-

- We develop industry-proven,
- state-of-the-art libraries for
- functional programming. -

-

- Start building scalable, performant applications
- that you can grow and maintain - with confidence. -

-
-
-
-
-

- Typelevel is an ecosystem of Scala-based projects and a community of people united to foster an inclusive, welcoming, and safe - environment around functional programming. -

-
-
-
-
-
- @:for(sponsors) -
- -
- @:@ -
-
-
-

- Typelevel is built by a vibrant global community and backed by a nonprofit Foundation with the support of our - industry sponsors. -

-
-
-@:@ diff --git a/src/templates/main.template.html b/src/templates/main.template.html deleted file mode 100644 index 183eceb0..00000000 --- a/src/templates/main.template.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - @:if(katex) - - @:@ - - - - - - ${cursor.currentDocument.title} - - - - @:include(/templates/nav.template.html) -
- ${_.embeddedBody} -
- @:include(/templates/footer.template.html) - - - diff --git a/src/templates/nav.template.html b/src/templates/nav.template.html deleted file mode 100644 index 2b97b99c..00000000 --- a/src/templates/nav.template.html +++ /dev/null @@ -1,58 +0,0 @@ - - - -
-
-
-
-

- - - @:svg(fa-magnifying-glass) - -

-
-
-
-
- - / to open search   - Esc to close - -
-
-
diff --git a/src/templates/redirect.template.html b/src/templates/redirect.template.html deleted file mode 100644 index 1491e3c1..00000000 --- a/src/templates/redirect.template.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/steering-committee.html b/steering-committee.html new file mode 100644 index 00000000..bb21a3a1 --- /dev/null +++ b/steering-committee.html @@ -0,0 +1,4 @@ + + + + diff --git a/steering-committee/index.html b/steering-committee/index.html new file mode 100644 index 00000000..adbfb82a --- /dev/null +++ b/steering-committee/index.html @@ -0,0 +1,4 @@ + + + +