From a5c8aef34d4090c52c399b03cba311e285dc66f2 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Mon, 6 Jan 2020 16:33:37 +0100 Subject: [PATCH 01/27] trying to rewrite typeclasses-new --- .../reference/contextual/typeclasses-new.md | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index ee2c20226d0b..3dcf4517f92b 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -3,12 +3,13 @@ layout: doc-page title: "Implementing Typeclasses" --- -Given instances, extension methods and context bounds -allow a concise and natural expression of _typeclasses_. Typeclasses are just traits -with canonical implementations defined by given instances. Here are some examples of standard typeclasses: +In Scala 3, _typeclasses_ are just traits whose implementation are defined by given instances. +Here are some examples of standard typeclasses: ### Semigroups and monoids: +Here's the `Monoid` typeclass definition: + ```scala trait SemiGroup[T] { @infix def (x: T) combine (y: T): T @@ -17,25 +18,65 @@ trait SemiGroup[T] { trait Monoid[T] extends SemiGroup[T] { def unit: T } +``` -object Monoid { - def apply[T] with (m: Monoid[T]) = m -} +An implementation of this `Monoid` typeclass for the type `String` can be the following: +```scala given as Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } +``` +Whereas for the type `Int` one could write the following: +```scala given as Monoid[Int] { def (x: Int) combine (y: Int): Int = x + y def unit: Int = 0 } +``` -def sum[T: Monoid](xs: List[T]): T = +This monoid can now be used as _context bound_ in the following `combineAll` method: + +```scala +def combineAll[T: Monoid](xs: List[T]): T = + xs.foldLeft(summon[Monoid[T]].unit)(_ combine _) +``` + +To get rid of the `summon[...]` we can define a `Monoid` object as follows: + +```scala +object Monoid { + def apply[T] with (m: Monoid[T]) = m +} +``` + +Which would allow to re-write the `combineAll` method this way: + +```scala +def combineAll[T: Monoid](xs: List[T]): T = xs.foldLeft(Monoid[T].unit)(_ combine _) ``` +We can also benefit from [extension methods](extension-methods-new.html) to make this `combineAll` function accessible as a method on the `List` type: + + +```scala +def [T: Monoid](xs: List[T]).combineAll: T = + xs.foldLeft(Monoid[T].unit)(_ combine _) +``` + +Which allows one to write: + +```scala +assert("ab" == List("a", "b").combineAll) +``` +or: +```scala +assert(3 == List(1, 2).combineAll) +``` + ### Functors and monads: ```scala @@ -64,3 +105,6 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { ctx => x } ``` + + +The conjunction of given instances, extension methods and context bounds allow a concise and natural expression of _typeclasses_ From 1bd517efed6dd2c5d8c74bbc5fce9192ed9e321e Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 08:41:32 +0100 Subject: [PATCH 02/27] Wrap the last sentence in a whole "summary" section to avoid stray text --- docs/docs/reference/contextual/typeclasses-new.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 3dcf4517f92b..3222be01665d 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -106,5 +106,10 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { } ``` +### Summary -The conjunction of given instances, extension methods and context bounds allow a concise and natural expression of _typeclasses_ +The definition of a _typeclass_ is expressed in Scala 3 via a `trait`. +The main difference with other traits resides in how these traits are implemented. +In the case of a _typeclass_ the trait's implementations are expressed through `given ... as` type definitions, and not through classes that `extends` the trait linearly. + +In addition to these given instances, extension methods and context bounds allow a concise and natural expression of _typeclasses_. From 171a09ac7417a14fbcee973597530e4f8d9decb8 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 09:44:17 +0100 Subject: [PATCH 03/27] Step2: rewrite the Functor part in more details --- .../reference/contextual/typeclasses-new.md | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 3222be01665d..58cfffd2fdcb 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -77,13 +77,74 @@ or: assert(3 == List(1, 2).combineAll) ``` -### Functors and monads: +### Functors: + +A `Functor` represents the ability for a type containing zero or more elements, and that can be "mapped over": applying a function to every of its elements. +Let's name our "type containing zero or more elements" `F`. It's a [higher-kinded type](http://guillaume.martres.me/publications/dotty-hk.pdf), since it contains elements of an other type. +Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. +The definition of the `Functor` ability would thus be written as: + +```scala +trait Functor[F[_]] { + def map[A, B](original: F[A], mapper: A => B): F[B] +} +``` + +Which could read as follows: "The `Functor` ability for a wrapper type `F` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". +This way, we could define an instance of `Functor` for the `List` type: + +```scala +given as Functor[List] { + def map[A, B](original: List[A], mapper: A => B): List[B] = + original.map(mapper) // List already has a `map` method +} +``` + +With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used. + +For instance, we may write such a testing method: +```scala +def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = + assert(expected == summon[Functor[F]].map(original, mapping)) +``` + +And use it this way, for example: + +```scala +assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") +``` + +That's a first step, but in practice we probably would like the `map` function to be a method directly accessible on the type `F`. So that we can call `map` directly on instances of `F`, and get rid of the `summon[Functor[F]]` part. +As in the previous example of Monoids, [`extension` methods](extension-methods-new.html) help achieving that. Let's re-define the `Functor` _typeclass_ with extension methods. ```scala trait Functor[F[_]] { - def [A, B](x: F[A]).map(f: A => B): F[B] + def [A, B](original: F[A]).map(mapper: A => B): F[B] } +``` + +The instance of `Functor` for `List` now becomes: + +```scala +given as Functor[List] { + def [A, B](original: List[A]).map(mapper: A => B): List[B] = + original.map(mapper) // List already has a `map` method +} +``` +It simplifies the `assertTransformation` method: + +```scala +def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = + assert(expected == original.map(mapping)) +``` + +Since we can now use the `map` method directly accessible on `original` which is of type `F[A]`, where `F` is a `Functor`. + + +### Monads + +```scala trait Monad[F[_]] extends Functor[F] { def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) From 6b7e4979517642ffb5a5bec993a6b12b27b35c25 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 11:01:39 +0100 Subject: [PATCH 04/27] Step3: rewrite the Monad part (apart from the Reader monad) --- .../reference/contextual/typeclasses-new.md | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 58cfffd2fdcb..c775f4fa68d2 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -144,21 +144,66 @@ Since we can now use the `map` method directly accessible on `original` which is ### Monads -```scala -trait Monad[F[_]] extends Functor[F] { - def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] - def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) +Now we have a `Functor` for `List`. + +Applying the `List.map` ability with the following mapping function as parameter: `mapping: A => B` would result in a `List[B]`. + +Now, applying the `List.map` ability with the following mapping function as parameter: `mapping: A => List[B]` would result in a `List[List[B]]`. + +To avoid avoid managing lists of lists, we may want to "flatten" the values in a single list. + +That's where `Monad` enter the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: +* the flatten ability we just described: turning `F[A]` to `F[B]` when given a `mapping: A => F[B]` function +* the ability to create `F[A]` from a single value `A` + +Here is the translation of this definition in Scala 3: - def pure[A](x: A): F[A] +```scala +trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Functor[F]`" => thus has the `map` ability + def pure[A](x: A): F[A] // `pure` can construct F[A] from a single value A + def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] // the flattening ability is named `flatMap`, using extension methods as previous examples + def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) // the `map(f)` ability is simply a combination of applying `f` then turning the result into an `F[A]` then applying `flatMap` to it } +``` + +#### List +Let us declare the `Monad` ability for type `List` +```scala given listMonad as Monad[List] { - def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = - xs.flatMap(f) def pure[A](x: A): List[A] = List(x) + def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = + xs.flatMap(f) // let's rely on the existing `flatMap` method of `List` } +``` + +`map` implementation is no longer needed. + +#### Option + +`Option` is an other type having the same kind of behaviour: +* the `map` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => B` +* the `flatMap` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => Option[B]` +* the `pure` ability turning `A` into `Option[A]` + +```scala +given optionMonad as Monad[Option] { + def pure[A](x: A): Option[A] = + Option(x) + def [A, B](xs: Option[A]).flatMap(f: A => Option[B]): Option[B] = + xs.flatMap(f) // let's rely on the existing `flatMap` method of `Option` +} +``` + +#### The Reader Monad +Another example of a `Monad` is the Reader Monad. It no longer acts on a type like `List` or `Option`, but on a function. +It can be used for example for combining functions that all have need the same type of parameter, for instance, if multiple functions need to access some configuration, context, environment variables, etc. + +The Reader monad allows to abstract over such a `Config` dependency (or context, environment, ...), named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: + +```scala given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) @@ -173,4 +218,4 @@ The definition of a _typeclass_ is expressed in Scala 3 via a `trait`. The main difference with other traits resides in how these traits are implemented. In the case of a _typeclass_ the trait's implementations are expressed through `given ... as` type definitions, and not through classes that `extends` the trait linearly. -In addition to these given instances, extension methods and context bounds allow a concise and natural expression of _typeclasses_. +In addition to these given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From 74ff540ec15e3a049fd3bb73ce804bb8108128a1 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 11:05:19 +0100 Subject: [PATCH 05/27] Fix typos --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index c775f4fa68d2..79bafc8ca6ab 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -79,7 +79,7 @@ assert(3 == List(1, 2).combineAll) ### Functors: -A `Functor` represents the ability for a type containing zero or more elements, and that can be "mapped over": applying a function to every of its elements. +A `Functor` represents the ability for a type containing zero or more elements to be "mapped over", i.e. apply a function to every of its elements. Let's name our "type containing zero or more elements" `F`. It's a [higher-kinded type](http://guillaume.martres.me/publications/dotty-hk.pdf), since it contains elements of an other type. Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. The definition of the `Functor` ability would thus be written as: From 8118ed74f852dd44d0dc3e20875acd20cf065f9c Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 11:08:22 +0100 Subject: [PATCH 06/27] Rephrase assertTransformation simplification --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 79bafc8ca6ab..9c70be31a928 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -139,7 +139,7 @@ def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], ma assert(expected == original.map(mapping)) ``` -Since we can now use the `map` method directly accessible on `original` which is of type `F[A]`, where `F` is a `Functor`. +The `map` method is now directly used on `original` since it is of type `F[A]` (where `F` is a `Functor`). ### Monads From a076eeaa78b8deae1b9a6aaf4bcd79ddf77eb4e4 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 11:10:57 +0100 Subject: [PATCH 07/27] Fix typos on the reader monad --- docs/docs/reference/contextual/typeclasses-new.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 9c70be31a928..20975925139b 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -199,9 +199,9 @@ given optionMonad as Monad[Option] { #### The Reader Monad Another example of a `Monad` is the Reader Monad. It no longer acts on a type like `List` or `Option`, but on a function. -It can be used for example for combining functions that all have need the same type of parameter, for instance, if multiple functions need to access some configuration, context, environment variables, etc. +It can be used for example for combining functions that all need the same type of parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc. -The Reader monad allows to abstract over such a `Config` dependency (or context, environment, ...), named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: +The Reader monad allows to abstract over such a configuration dependency (or context, environment, ...), named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: ```scala given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { From 66ebcfbc439a1f515e71a83caaa259d3fea84c7e Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 11:32:29 +0100 Subject: [PATCH 08/27] @bishabosha's note --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 20975925139b..a81d68dcb118 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -80,7 +80,7 @@ assert(3 == List(1, 2).combineAll) ### Functors: A `Functor` represents the ability for a type containing zero or more elements to be "mapped over", i.e. apply a function to every of its elements. -Let's name our "type containing zero or more elements" `F`. It's a [higher-kinded type](http://guillaume.martres.me/publications/dotty-hk.pdf), since it contains elements of an other type. +Let's name our "type containing zero or more elements" `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. The definition of the `Functor` ability would thus be written as: From 334c4b49afa0850889bbe0c86a980d6a756afd52 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Fri, 31 Jan 2020 12:17:05 +0100 Subject: [PATCH 09/27] attempt at explaining the reader monad --- .../reference/contextual/typeclasses-new.md | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index a81d68dcb118..aea17568ad82 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -201,7 +201,58 @@ given optionMonad as Monad[Option] { Another example of a `Monad` is the Reader Monad. It no longer acts on a type like `List` or `Option`, but on a function. It can be used for example for combining functions that all need the same type of parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc. -The Reader monad allows to abstract over such a configuration dependency (or context, environment, ...), named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: +Let us have a `Config` type, and two functions using it: + +```scala +trait Config +def compute(i: Int)(config: Config): String = ??? +def show(str: String)(config: Config): Unit = ??? +``` + +We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation. +If we had a `flatMap` function as in the examples above, we would be able to write the following: + +```scala +def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show) +``` + +Let's define this `Monad` then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`. + +```scala +trait Config // the Config defined above +type ConfigDependent[Result] = Config => Result +``` + +The monad will look like this: + +```scala +given configDependentMonad as Monad[ConfigDependent] + def [A, B](r: ConfigDependent[A]).flatMap(f: A => ConfigDependent[B]): ConfigDependent[B] = + config => f(r(config))(config) + def pure[A](x: A): ConfigDependent[A] = + config => x +``` + +The type `ConfigDependent` can be written using [type lambdas](../new-types/type-lambdas.html): + +```scala +type ConfigDependent = [Result] =>> Config => Result +``` + +Using this syntax would turn the previous `configReaderMonad` into: + + +```scala +given configDependentMonad as Monad[[Result] =>> Config => Result] + def [A, B](r: Config => A).flatMap(f: A => Config => B): Config => B = + config => f(r(config))(config) + def pure[A](x: A): Config => A = + config => x +``` + + + +The Reader monad allows to abstract over the `Config` type, named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: ```scala given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { From cd501b61ae1b09a52b8a9c385abbf8cf93c076a0 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:11:24 +0100 Subject: [PATCH 10/27] less concrete and more accurate definition of a Functor Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index aea17568ad82..5979686a951d 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -79,7 +79,7 @@ assert(3 == List(1, 2).combineAll) ### Functors: -A `Functor` represents the ability for a type containing zero or more elements to be "mapped over", i.e. apply a function to every of its elements. +A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. Let's name our "type containing zero or more elements" `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. The definition of the `Functor` ability would thus be written as: From ff7ca152b7785fba4d6b1288f5e5d49b74233291 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:13:46 +0100 Subject: [PATCH 11/27] definition of A functor, not THE functor ability Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 5979686a951d..1af0d5e55d72 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -82,7 +82,7 @@ assert(3 == List(1, 2).combineAll) A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. Let's name our "type containing zero or more elements" `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. -The definition of the `Functor` ability would thus be written as: +The definition of a generic `Functor` would thus be written as: ```scala trait Functor[F[_]] { From cf29e827863e1b5f8d06e7e9ac43ed2ac0bd439b Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:14:38 +0100 Subject: [PATCH 12/27] A functor for the type constructor F[_] Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 1af0d5e55d72..710db075b3ea 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -90,7 +90,7 @@ trait Functor[F[_]] { } ``` -Which could read as follows: "The `Functor` ability for a wrapper type `F` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". +Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". We call the `Functor` definition here a _typeclass_. This way, we could define an instance of `Functor` for the `List` type: ```scala From 86070147e80933ad6bc255c79e46929fd00a8dc5 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:15:43 +0100 Subject: [PATCH 13/27] better phrasing for abstracting away Config Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 710db075b3ea..6c9429c9a5eb 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -252,7 +252,7 @@ given configDependentMonad as Monad[[Result] =>> Config => Result] -The Reader monad allows to abstract over the `Config` type, named `Ctx` in the following examples. It is therefore _parameterized_ by `Ctx`: +It is likely that we would like to use this pattern with other kinds of environments than our `Config` trait. The Reader monad allows us to abstract away `Config` as a type _parameter_, named `Ctx` in the following definition: ```scala given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { From 4c81c46718c6f33e48703ae11ffb260d05b571e2 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:16:23 +0100 Subject: [PATCH 14/27] parameterised type with abstract members => trait Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 6c9429c9a5eb..a0b5ba346e8e 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -265,7 +265,7 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { ### Summary -The definition of a _typeclass_ is expressed in Scala 3 via a `trait`. +The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. The main difference with other traits resides in how these traits are implemented. In the case of a _typeclass_ the trait's implementations are expressed through `given ... as` type definitions, and not through classes that `extends` the trait linearly. From 6b6cd3955706275140e6fca3ad31cd04a05248ef Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:17:04 +0100 Subject: [PATCH 15/27] oo polymorphism vs. parametric polymorphism Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index a0b5ba346e8e..2927ef8437fa 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -266,7 +266,7 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { ### Summary The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. -The main difference with other traits resides in how these traits are implemented. +The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. In the case of a _typeclass_ the trait's implementations are expressed through `given ... as` type definitions, and not through classes that `extends` the trait linearly. In addition to these given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From c6ec5816b2ad67fcd4c90eb35d772bbd561359ed Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:17:53 +0100 Subject: [PATCH 16/27] explaining the difference between OO polymorphism and ad-hoc polymorphism Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 2927ef8437fa..218ca2cb1cf5 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -267,6 +267,6 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. -In the case of a _typeclass_ the trait's implementations are expressed through `given ... as` type definitions, and not through classes that `extends` the trait linearly. +In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given ... as` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. In addition to these given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From 716ae0cf2c40f17c9a90a68e0fa95e69e8130d38 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:18:14 +0100 Subject: [PATCH 17/27] better phrasing for conclusion Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 218ca2cb1cf5..ca39d9de4389 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -269,4 +269,4 @@ The definition of a _typeclass_ is expressed via a parameterised type with abstr The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given ... as` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. -In addition to these given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. +To conclude, in addition to given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From c15cd6339c102ba04a235efd7e920f158ca82c4d Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:19:00 +0100 Subject: [PATCH 18/27] remove the "we don't care" part Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index ca39d9de4389..d4d426e5c339 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -81,7 +81,7 @@ assert(3 == List(1, 2).combineAll) A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. Let's name our "type containing zero or more elements" `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. -Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains. +Therefore we write it `F[_]`, hinting that it is a type with internal details we can inspect. The definition of a generic `Functor` would thus be written as: ```scala From 8b85b34d7787e194f9079bb83806104f31ef1928 Mon Sep 17 00:00:00 2001 From: Arnaud Esteve Date: Fri, 31 Jan 2020 15:19:49 +0100 Subject: [PATCH 19/27] using F as a substitution for every type that ca be mapped over Co-Authored-By: Jamie Thompson --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index d4d426e5c339..bcd64bf02e34 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -80,7 +80,7 @@ assert(3 == List(1, 2).combineAll) ### Functors: A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. -Let's name our "type containing zero or more elements" `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. +We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. Therefore we write it `F[_]`, hinting that it is a type with internal details we can inspect. The definition of a generic `Functor` would thus be written as: From df15a24538b6b081d0052f9854521be6315c84c7 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Sun, 9 Feb 2020 13:58:39 +0100 Subject: [PATCH 20/27] Trying to add an easy-to-grasp definition of type classes --- docs/docs/reference/contextual/typeclasses-new.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index b7c533a8f653..aca0582dd6f1 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -3,8 +3,12 @@ layout: doc-page title: "Implementing Typeclasses" --- -In Scala 3, _typeclasses_ are just traits whose implementation are defined by given instances. -Here are some examples of standard typeclasses: +A _typeclass_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example: +* expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior +* expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance) + +Therefore in Scala 3, _typeclasses_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**. +Here are some examples of usual typeclasses: ### Semigroups and monoids: From 4b046031b94b86c3b6513fd004ab71a496ac2c13 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Mon, 10 Feb 2020 13:17:59 +0100 Subject: [PATCH 21/27] Proper 0.23 syntax --- docs/docs/reference/contextual/typeclasses-new.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index aca0582dd6f1..90fcd9e351ea 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -27,7 +27,7 @@ trait Monoid[T] extends SemiGroup[T] { An implementation of this `Monoid` typeclass for the type `String` can be the following: ```scala -givenMonoid[String] { +given Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -35,7 +35,7 @@ givenMonoid[String] { Whereas for the type `Int` one could write the following: ```scala -givenMonoid[Int] { +given Monoid[Int] { def (x: Int) combine (y: Int): Int = x + y def unit: Int = 0 } @@ -98,7 +98,7 @@ Which could read as follows: "A `Functor` for the type constructor `F[_]` repres This way, we could define an instance of `Functor` for the `List` type: ```scala -givenFunctor[List] { +given Functor[List] { def map[A, B](original: List[A], mapper: A => B): List[B] = original.map(mapper) // List already has a `map` method } @@ -130,7 +130,7 @@ trait Functor[F[_]] { The instance of `Functor` for `List` now becomes: ```scala -givenFunctor[List] { +given Functor[List] { def [A, B](original: List[A]).map(mapper: A => B): List[B] = original.map(mapper) // List already has a `map` method } @@ -174,7 +174,7 @@ trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Funct Let us declare the `Monad` ability for type `List` ```scala -given listMonad as Monad[List] { +given listMonad: Monad[List] { def pure[A](x: A): List[A] = List(x) def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = @@ -192,7 +192,7 @@ given listMonad as Monad[List] { * the `pure` ability turning `A` into `Option[A]` ```scala -given optionMonad as Monad[Option] { +given optionMonad: Monad[Option] { def pure[A](x: A): Option[A] = Option(x) def [A, B](xs: Option[A]).flatMap(f: A => Option[B]): Option[B] = From cb6c511510117de9ffca7cc0d33dc52756de43b8 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Mon, 10 Feb 2020 13:19:10 +0100 Subject: [PATCH 22/27] Proper 0.23 syntax --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 90fcd9e351ea..55b9f90da2f3 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -52,7 +52,7 @@ To get rid of the `summon[...]` we can define a `Monoid` object as follows: ```scala object Monoid { - def apply[T] with (m: Monoid[T]) = m + def apply[T](using m: Monoid[T]) = m } ``` From cd9834ef99350b883c105ed57d05ef1ed0484b40 Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Mon, 10 Feb 2020 13:23:27 +0100 Subject: [PATCH 23/27] typo --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 55b9f90da2f3..2c163a8a21b1 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -156,7 +156,7 @@ Now, applying the `List.map` ability with the following mapping function as para To avoid avoid managing lists of lists, we may want to "flatten" the values in a single list. -That's where `Monad` enter the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: +That's where `Monad` enters the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: * the flatten ability we just described: turning `F[A]` to `F[B]` when given a `mapping: A => F[B]` function * the ability to create `F[A]` from a single value `A` From bf7b0057a087fd91aa2cb17d949833152f4f8ffd Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Mon, 10 Feb 2020 13:27:58 +0100 Subject: [PATCH 24/27] no longer `given as` --- docs/docs/reference/contextual/typeclasses-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md index 2c163a8a21b1..29284c65a49b 100644 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ b/docs/docs/reference/contextual/typeclasses-new.md @@ -271,6 +271,6 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. -In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given ... as` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. +In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. To conclude, in addition to given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From 9c1509bfdb08bc7fac4458a5567fbd8ba5602f4d Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Thu, 13 Feb 2020 13:42:40 +0100 Subject: [PATCH 25/27] typeclasses-new.md is now typeclasses.md --- .../reference/contextual/typeclasses-new.md | 276 ------------------ docs/docs/reference/contextual/typeclasses.md | 242 ++++++++++++++- 2 files changed, 226 insertions(+), 292 deletions(-) delete mode 100644 docs/docs/reference/contextual/typeclasses-new.md diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md deleted file mode 100644 index 29284c65a49b..000000000000 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -layout: doc-page -title: "Implementing Typeclasses" ---- - -A _typeclass_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example: -* expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior -* expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance) - -Therefore in Scala 3, _typeclasses_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**. -Here are some examples of usual typeclasses: - -### Semigroups and monoids: - -Here's the `Monoid` typeclass definition: - -```scala -trait SemiGroup[T] { - @infix def (x: T) combine (y: T): T -} - -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} -``` - -An implementation of this `Monoid` typeclass for the type `String` can be the following: - -```scala -given Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" -} -``` - -Whereas for the type `Int` one could write the following: -```scala -given Monoid[Int] { - def (x: Int) combine (y: Int): Int = x + y - def unit: Int = 0 -} -``` - -This monoid can now be used as _context bound_ in the following `combineAll` method: - -```scala -def combineAll[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_ combine _) -``` - -To get rid of the `summon[...]` we can define a `Monoid` object as follows: - -```scala -object Monoid { - def apply[T](using m: Monoid[T]) = m -} -``` - -Which would allow to re-write the `combineAll` method this way: - -```scala -def combineAll[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_ combine _) -``` - -We can also benefit from [extension methods](extension-methods-new.html) to make this `combineAll` function accessible as a method on the `List` type: - - -```scala -def [T: Monoid](xs: List[T]).combineAll: T = - xs.foldLeft(Monoid[T].unit)(_ combine _) -``` - -Which allows one to write: - -```scala -assert("ab" == List("a", "b").combineAll) -``` -or: -```scala -assert(3 == List(1, 2).combineAll) -``` - -### Functors: - -A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. -We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. -Therefore we write it `F[_]`, hinting that it is a type with internal details we can inspect. -The definition of a generic `Functor` would thus be written as: - -```scala -trait Functor[F[_]] { - def map[A, B](original: F[A], mapper: A => B): F[B] -} -``` - -Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". We call the `Functor` definition here a _typeclass_. -This way, we could define an instance of `Functor` for the `List` type: - -```scala -given Functor[List] { - def map[A, B](original: List[A], mapper: A => B): List[B] = - original.map(mapper) // List already has a `map` method -} -``` - -With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used. - -For instance, we may write such a testing method: -```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = - assert(expected == summon[Functor[F]].map(original, mapping)) -``` - -And use it this way, for example: - -```scala -assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") -``` - -That's a first step, but in practice we probably would like the `map` function to be a method directly accessible on the type `F`. So that we can call `map` directly on instances of `F`, and get rid of the `summon[Functor[F]]` part. -As in the previous example of Monoids, [`extension` methods](extension-methods-new.html) help achieving that. Let's re-define the `Functor` _typeclass_ with extension methods. - -```scala -trait Functor[F[_]] { - def [A, B](original: F[A]).map(mapper: A => B): F[B] -} -``` - -The instance of `Functor` for `List` now becomes: - -```scala -given Functor[List] { - def [A, B](original: List[A]).map(mapper: A => B): List[B] = - original.map(mapper) // List already has a `map` method -} -``` - -It simplifies the `assertTransformation` method: - -```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = - assert(expected == original.map(mapping)) -``` - -The `map` method is now directly used on `original` since it is of type `F[A]` (where `F` is a `Functor`). - - -### Monads - -Now we have a `Functor` for `List`. - -Applying the `List.map` ability with the following mapping function as parameter: `mapping: A => B` would result in a `List[B]`. - -Now, applying the `List.map` ability with the following mapping function as parameter: `mapping: A => List[B]` would result in a `List[List[B]]`. - -To avoid avoid managing lists of lists, we may want to "flatten" the values in a single list. - -That's where `Monad` enters the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: -* the flatten ability we just described: turning `F[A]` to `F[B]` when given a `mapping: A => F[B]` function -* the ability to create `F[A]` from a single value `A` - -Here is the translation of this definition in Scala 3: - -```scala -trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Functor[F]`" => thus has the `map` ability - def pure[A](x: A): F[A] // `pure` can construct F[A] from a single value A - def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] // the flattening ability is named `flatMap`, using extension methods as previous examples - def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) // the `map(f)` ability is simply a combination of applying `f` then turning the result into an `F[A]` then applying `flatMap` to it -} -``` - -#### List - -Let us declare the `Monad` ability for type `List` -```scala -given listMonad: Monad[List] { - def pure[A](x: A): List[A] = - List(x) - def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = - xs.flatMap(f) // let's rely on the existing `flatMap` method of `List` -} -``` - -`map` implementation is no longer needed. - -#### Option - -`Option` is an other type having the same kind of behaviour: -* the `map` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => B` -* the `flatMap` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => Option[B]` -* the `pure` ability turning `A` into `Option[A]` - -```scala -given optionMonad: Monad[Option] { - def pure[A](x: A): Option[A] = - Option(x) - def [A, B](xs: Option[A]).flatMap(f: A => Option[B]): Option[B] = - xs.flatMap(f) // let's rely on the existing `flatMap` method of `Option` -} -``` - -#### The Reader Monad - -Another example of a `Monad` is the Reader Monad. It no longer acts on a type like `List` or `Option`, but on a function. -It can be used for example for combining functions that all need the same type of parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc. - -Let us have a `Config` type, and two functions using it: - -```scala -trait Config -def compute(i: Int)(config: Config): String = ??? -def show(str: String)(config: Config): Unit = ??? -``` - -We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation. -If we had a `flatMap` function as in the examples above, we would be able to write the following: - -```scala -def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show) -``` - -Let's define this `Monad` then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`. - -```scala -trait Config // the Config defined above -type ConfigDependent[Result] = Config => Result -``` - -The monad will look like this: - -```scala -given configDependentMonad as Monad[ConfigDependent] - def [A, B](r: ConfigDependent[A]).flatMap(f: A => ConfigDependent[B]): ConfigDependent[B] = - config => f(r(config))(config) - def pure[A](x: A): ConfigDependent[A] = - config => x -``` - -The type `ConfigDependent` can be written using [type lambdas](../new-types/type-lambdas.html): - -```scala -type ConfigDependent = [Result] =>> Config => Result -``` - -Using this syntax would turn the previous `configReaderMonad` into: - - -```scala -given configDependentMonad as Monad[[Result] =>> Config => Result] - def [A, B](r: Config => A).flatMap(f: A => Config => B): Config => B = - config => f(r(config))(config) - def pure[A](x: A): Config => A = - config => x -``` - - - -It is likely that we would like to use this pattern with other kinds of environments than our `Config` trait. The Reader monad allows us to abstract away `Config` as a type _parameter_, named `Ctx` in the following definition: - -```scala -given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { - def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x -} -``` - -### Summary - -The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. -The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. -In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. - -To conclude, in addition to given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md index 8175cea64e93..29284c65a49b 100644 --- a/docs/docs/reference/contextual/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -3,12 +3,17 @@ layout: doc-page title: "Implementing Typeclasses" --- -Given instances, extension methods and context bounds -allow a concise and natural expression of _typeclasses_. Typeclasses are just traits -with canonical implementations defined by given instances. Here are some examples of standard typeclasses: +A _typeclass_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example: +* expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior +* expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance) + +Therefore in Scala 3, _typeclasses_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**. +Here are some examples of usual typeclasses: ### Semigroups and monoids: +Here's the `Monoid` typeclass definition: + ```scala trait SemiGroup[T] { @infix def (x: T) combine (y: T): T @@ -17,46 +22,243 @@ trait SemiGroup[T] { trait Monoid[T] extends SemiGroup[T] { def unit: T } +``` -object Monoid { - def apply[T](using m: Monoid[T]) = m -} +An implementation of this `Monoid` typeclass for the type `String` can be the following: +```scala given Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } +``` +Whereas for the type `Int` one could write the following: +```scala given Monoid[Int] { def (x: Int) combine (y: Int): Int = x + y def unit: Int = 0 } +``` + +This monoid can now be used as _context bound_ in the following `combineAll` method: + +```scala +def combineAll[T: Monoid](xs: List[T]): T = + xs.foldLeft(summon[Monoid[T]].unit)(_ combine _) +``` + +To get rid of the `summon[...]` we can define a `Monoid` object as follows: + +```scala +object Monoid { + def apply[T](using m: Monoid[T]) = m +} +``` -def sum[T: Monoid](xs: List[T]): T = +Which would allow to re-write the `combineAll` method this way: + +```scala +def combineAll[T: Monoid](xs: List[T]): T = xs.foldLeft(Monoid[T].unit)(_ combine _) ``` -### Functors and monads: +We can also benefit from [extension methods](extension-methods-new.html) to make this `combineAll` function accessible as a method on the `List` type: + + +```scala +def [T: Monoid](xs: List[T]).combineAll: T = + xs.foldLeft(Monoid[T].unit)(_ combine _) +``` + +Which allows one to write: + +```scala +assert("ab" == List("a", "b").combineAll) +``` +or: +```scala +assert(3 == List(1, 2).combineAll) +``` + +### Functors: + +A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. +We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. +Therefore we write it `F[_]`, hinting that it is a type with internal details we can inspect. +The definition of a generic `Functor` would thus be written as: ```scala trait Functor[F[_]] { - def [A, B](x: F[A]).map(f: A => B): F[B] + def map[A, B](original: F[A], mapper: A => B): F[B] } +``` -trait Monad[F[_]] extends Functor[F] { - def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] - def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) +Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". We call the `Functor` definition here a _typeclass_. +This way, we could define an instance of `Functor` for the `List` type: - def pure[A](x: A): F[A] +```scala +given Functor[List] { + def map[A, B](original: List[A], mapper: A => B): List[B] = + original.map(mapper) // List already has a `map` method } +``` -given listMonad as Monad[List] { - def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = - xs.flatMap(f) +With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used. + +For instance, we may write such a testing method: +```scala +def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = + assert(expected == summon[Functor[F]].map(original, mapping)) +``` + +And use it this way, for example: + +```scala +assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") +``` + +That's a first step, but in practice we probably would like the `map` function to be a method directly accessible on the type `F`. So that we can call `map` directly on instances of `F`, and get rid of the `summon[Functor[F]]` part. +As in the previous example of Monoids, [`extension` methods](extension-methods-new.html) help achieving that. Let's re-define the `Functor` _typeclass_ with extension methods. + +```scala +trait Functor[F[_]] { + def [A, B](original: F[A]).map(mapper: A => B): F[B] +} +``` + +The instance of `Functor` for `List` now becomes: + +```scala +given Functor[List] { + def [A, B](original: List[A]).map(mapper: A => B): List[B] = + original.map(mapper) // List already has a `map` method +} +``` + +It simplifies the `assertTransformation` method: + +```scala +def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = + assert(expected == original.map(mapping)) +``` + +The `map` method is now directly used on `original` since it is of type `F[A]` (where `F` is a `Functor`). + + +### Monads + +Now we have a `Functor` for `List`. + +Applying the `List.map` ability with the following mapping function as parameter: `mapping: A => B` would result in a `List[B]`. + +Now, applying the `List.map` ability with the following mapping function as parameter: `mapping: A => List[B]` would result in a `List[List[B]]`. + +To avoid avoid managing lists of lists, we may want to "flatten" the values in a single list. + +That's where `Monad` enters the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: +* the flatten ability we just described: turning `F[A]` to `F[B]` when given a `mapping: A => F[B]` function +* the ability to create `F[A]` from a single value `A` + +Here is the translation of this definition in Scala 3: + +```scala +trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Functor[F]`" => thus has the `map` ability + def pure[A](x: A): F[A] // `pure` can construct F[A] from a single value A + def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] // the flattening ability is named `flatMap`, using extension methods as previous examples + def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) // the `map(f)` ability is simply a combination of applying `f` then turning the result into an `F[A]` then applying `flatMap` to it +} +``` + +#### List + +Let us declare the `Monad` ability for type `List` +```scala +given listMonad: Monad[List] { def pure[A](x: A): List[A] = List(x) + def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = + xs.flatMap(f) // let's rely on the existing `flatMap` method of `List` } +``` + +`map` implementation is no longer needed. + +#### Option + +`Option` is an other type having the same kind of behaviour: +* the `map` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => B` +* the `flatMap` ability turning `Option[A]` into `Option[B]` if passed a function `f: A => Option[B]` +* the `pure` ability turning `A` into `Option[A]` + +```scala +given optionMonad: Monad[Option] { + def pure[A](x: A): Option[A] = + Option(x) + def [A, B](xs: Option[A]).flatMap(f: A => Option[B]): Option[B] = + xs.flatMap(f) // let's rely on the existing `flatMap` method of `Option` +} +``` + +#### The Reader Monad + +Another example of a `Monad` is the Reader Monad. It no longer acts on a type like `List` or `Option`, but on a function. +It can be used for example for combining functions that all need the same type of parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc. + +Let us have a `Config` type, and two functions using it: + +```scala +trait Config +def compute(i: Int)(config: Config): String = ??? +def show(str: String)(config: Config): Unit = ??? +``` + +We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation. +If we had a `flatMap` function as in the examples above, we would be able to write the following: + +```scala +def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show) +``` + +Let's define this `Monad` then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`. + +```scala +trait Config // the Config defined above +type ConfigDependent[Result] = Config => Result +``` + +The monad will look like this: + +```scala +given configDependentMonad as Monad[ConfigDependent] + def [A, B](r: ConfigDependent[A]).flatMap(f: A => ConfigDependent[B]): ConfigDependent[B] = + config => f(r(config))(config) + def pure[A](x: A): ConfigDependent[A] = + config => x +``` + +The type `ConfigDependent` can be written using [type lambdas](../new-types/type-lambdas.html): +```scala +type ConfigDependent = [Result] =>> Config => Result +``` + +Using this syntax would turn the previous `configReaderMonad` into: + + +```scala +given configDependentMonad as Monad[[Result] =>> Config => Result] + def [A, B](r: Config => A).flatMap(f: A => Config => B): Config => B = + config => f(r(config))(config) + def pure[A](x: A): Config => A = + config => x +``` + + + +It is likely that we would like to use this pattern with other kinds of environments than our `Config` trait. The Reader monad allows us to abstract away `Config` as a type _parameter_, named `Ctx` in the following definition: + +```scala given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) @@ -64,3 +266,11 @@ given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { ctx => x } ``` + +### Summary + +The definition of a _typeclass_ is expressed via a parameterised type with abstract members, such as a `trait`. +The main difference between object oriented polymorphism, and ad-hoc polymorphism with _typeclasses_, is how the definition of the _typeclass_ is implemented, in relation to the type it acts upon. +In the case of a _typeclass_, its implementation for a concrete type is expressed through a `given` term definition, which is supplied as an implicit argument alongside the value it acts upon. With object oriented polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. + +To conclude, in addition to given instances, other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _typeclasses_. From 466bff90629c94ae406414e897456c404648644a Mon Sep 17 00:00:00 2001 From: Arnaud ESTEVE Date: Wed, 26 Feb 2020 08:35:12 +0100 Subject: [PATCH 26/27] Adapt to 0.23 latest --- docs/docs/reference/contextual/typeclasses.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md index 29284c65a49b..70bfbec60ac7 100644 --- a/docs/docs/reference/contextual/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -174,7 +174,7 @@ trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Funct Let us declare the `Monad` ability for type `List` ```scala -given listMonad: Monad[List] { +given listMonad as Monad[List] { def pure[A](x: A): List[A] = List(x) def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = @@ -192,7 +192,7 @@ given listMonad: Monad[List] { * the `pure` ability turning `A` into `Option[A]` ```scala -given optionMonad: Monad[Option] { +given optionMonad as Monad[Option] { def pure[A](x: A): Option[A] = Option(x) def [A, B](xs: Option[A]).flatMap(f: A => Option[B]): Option[B] = @@ -209,6 +209,7 @@ Let us have a `Config` type, and two functions using it: ```scala trait Config +// ... def compute(i: Int)(config: Config): String = ??? def show(str: String)(config: Config): Unit = ??? ``` @@ -230,11 +231,12 @@ type ConfigDependent[Result] = Config => Result The monad will look like this: ```scala -given configDependentMonad as Monad[ConfigDependent] +given configDependentMonad as Monad[ConfigDependent] { def [A, B](r: ConfigDependent[A]).flatMap(f: A => ConfigDependent[B]): ConfigDependent[B] = config => f(r(config))(config) def pure[A](x: A): ConfigDependent[A] = config => x +} ``` The type `ConfigDependent` can be written using [type lambdas](../new-types/type-lambdas.html): From 219bc3c3058f5ded0e5f5c64d4f6f6fd8eb7a307 Mon Sep 17 00:00:00 2001 From: aesteve Date: Mon, 30 Mar 2020 18:13:41 +0200 Subject: [PATCH 27/27] Use new type wildcard syntax --- docs/docs/reference/contextual/typeclasses.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md index 70bfbec60ac7..b8ed6b5aee8c 100644 --- a/docs/docs/reference/contextual/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -85,16 +85,16 @@ assert(3 == List(1, 2).combineAll) A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. -Therefore we write it `F[_]`, hinting that it is a type with internal details we can inspect. +Therefore we write it `F[?]`, hinting that it is a type with internal details we can inspect. The definition of a generic `Functor` would thus be written as: ```scala -trait Functor[F[_]] { +trait Functor[F[?]] { def map[A, B](original: F[A], mapper: A => B): F[B] } ``` -Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". We call the `Functor` definition here a _typeclass_. +Which could read as follows: "A `Functor` for the type constructor `F[?]` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`". We call the `Functor` definition here a _typeclass_. This way, we could define an instance of `Functor` for the `List` type: ```scala @@ -108,7 +108,7 @@ With this `given` instance in scope, everywhere a `Functor` is expected, the com For instance, we may write such a testing method: ```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = +def assertTransformation[F[?]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = assert(expected == summon[Functor[F]].map(original, mapping)) ``` @@ -122,7 +122,7 @@ That's a first step, but in practice we probably would like the `map` function t As in the previous example of Monoids, [`extension` methods](extension-methods-new.html) help achieving that. Let's re-define the `Functor` _typeclass_ with extension methods. ```scala -trait Functor[F[_]] { +trait Functor[F[?]] { def [A, B](original: F[A]).map(mapper: A => B): F[B] } ``` @@ -139,7 +139,7 @@ given Functor[List] { It simplifies the `assertTransformation` method: ```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = +def assertTransformation[F[?]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = assert(expected == original.map(mapping)) ``` @@ -156,14 +156,14 @@ Now, applying the `List.map` ability with the following mapping function as para To avoid avoid managing lists of lists, we may want to "flatten" the values in a single list. -That's where `Monad` enters the party. A `Monad` for type `F[_]` is a `Functor[F]` with 2 more abilities: +That's where `Monad` enters the party. A `Monad` for type `F[?]` is a `Functor[F]` with 2 more abilities: * the flatten ability we just described: turning `F[A]` to `F[B]` when given a `mapping: A => F[B]` function * the ability to create `F[A]` from a single value `A` Here is the translation of this definition in Scala 3: ```scala -trait Monad[F[_]] extends Functor[F] { // "A `Monad` for type `F[_]` is a `Functor[F]`" => thus has the `map` ability +trait Monad[F[?]] extends Functor[F] { // "A `Monad` for type `F[?]` is a `Functor[F]`" => thus has the `map` ability def pure[A](x: A): F[A] // `pure` can construct F[A] from a single value A def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] // the flattening ability is named `flatMap`, using extension methods as previous examples def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) // the `map(f)` ability is simply a combination of applying `f` then turning the result into an `F[A]` then applying `flatMap` to it