Browse Source

Refactored and moved everything around

part3
Logan McGrath 4 months ago
parent
commit
12b3538cfb
  1. 19
      .scalafmt.conf
  2. 92
      README.md
  3. 24
      console/imports.scala
  4. 10
      macro/src/main/scala/green/thisfieldwas/embracingnondeterminism/data/GenerateTupleSyntax.scala
  5. 1
      project/plugins.sbt
  6. 102
      src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Applicative.scala
  7. 74
      src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Monad.scala
  8. 175
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Either.scala
  9. 38
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Functor.scala
  10. 303
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/List.scala
  11. 101
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyChain.scala
  12. 163
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyList.scala
  13. 219
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Option.scala
  14. 70
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Semigroup.scala
  15. 7
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Tuple.scala
  16. 150
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Validated.scala
  17. 61
      src/main/scala/green/thisfieldwas/embracingnondeterminism/data/package.scala
  18. 159
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/Either.scala
  19. 246
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/List.scala
  20. 129
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/NonEmptyList.scala
  21. 163
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/Option.scala
  22. 157
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/Validated.scala
  23. 62
      src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/package.scala
  24. 24
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/Either.scala
  25. 25
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/Extractable.scala
  26. 28
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/List.scala
  27. 12
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/NonEmptyList.scala
  28. 21
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/Option.scala
  29. 24
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/Either.scala
  30. 25
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/Extractable.scala
  31. 29
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/List.scala
  32. 21
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/Option.scala
  33. 24
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/Either.scala
  34. 25
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/Extractable.scala
  35. 36
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/List.scala
  36. 21
      src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/Option.scala
  37. 35
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/applicative.scala
  38. 36
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/either.scala
  39. 31
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/functor.scala
  40. 50
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/monad.scala
  41. 9
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/package.scala
  42. 38
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/semigroup.scala
  43. 35
      src/main/scala/green/thisfieldwas/embracingnondeterminism/syntax/validated.scala
  44. 103
      src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Applicative.scala
  45. 57
      src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Functor.scala
  46. 102
      src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Monad.scala
  47. 27
      src/test/scala/green/thisfieldwas/embracingnondeterminism/control/ApplicativeLaws.scala
  48. 28
      src/test/scala/green/thisfieldwas/embracingnondeterminism/control/MonadLaws.scala
  49. 18
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/EitherSpec.scala
  50. 23
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/FunctorLaws.scala
  51. 10
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/IdSpec.scala
  52. 13
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/ListSpec.scala
  53. 19
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/MonoidLaws.scala
  54. 32
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyChainSpec.scala
  55. 8
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyListSpec.scala
  56. 21
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/OptionSpec.scala
  57. 9
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/SemigroupLaws.scala
  58. 8
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/TupleSpec.scala
  59. 32
      src/test/scala/green/thisfieldwas/embracingnondeterminism/data/ValidatedSpec.scala
  60. 84
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/EitherSpec.scala
  61. 21
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/ExtractableSpecification.scala
  62. 86
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/ListSpec.scala
  63. 58
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/NonEmptyListSpec.scala
  64. 68
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/nel/OptionSpec.scala
  65. 84
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/EitherSpec.scala
  66. 21
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/ExtractableSpecification.scala
  67. 86
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/ListSpec.scala
  68. 68
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/one/OptionSpec.scala
  69. 84
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/EitherSpec.scala
  70. 21
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/ExtractableSpecification.scala
  71. 86
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/ListSpec.scala
  72. 68
      src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables/seq/OptionSpec.scala
  73. 2
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/BooleanSpec.scala
  74. 2
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/IntegerSpec.scala
  75. 16
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/ListSpec.scala
  76. 16
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/SetSpec.scala
  77. 6
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/StringSpec.scala
  78. 6
      src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/package.scala
  79. 2
      src/test/scala/green/thisfieldwas/embracingnondeterminism/util/Laws.scala
  80. 32
      src/test/scala/green/thisfieldwas/embracingnondeterminism/util/LiftedGen.scala
  81. 22
      src/test/scala/green/thisfieldwas/embracingnondeterminism/util/TypeTagSyntax.scala
  82. 48
      src/test/scala/green/thisfieldwas/embracingnondeterminism/util/package.scala

19
.scalafmt.conf

@ -1,9 +1,13 @@
version: 2.7.5
version: 3.5.8
runner.dialect = scala213
maxColumn: 120
preset: default
align.preset: some
docstrings: "ScalaDoc"
docstrings.style: "spaceasterisk"
docstrings.wrap: yes
docstrings.wrapMaxColumn: 80
danglingParentheses.preset: true
assumeStandardLibraryStripMargin: true
trailingCommas: multiple
@ -16,16 +20,21 @@ continuationIndent {
}
newlines {
alwaysBeforeTopLevelStatements: true
topLevelStatementBlankLines: [
{
blanks { before: 1 }
}
]
alwaysBeforeElseAfterCurlyIf: false
alwaysBeforeCurlyBraceLambdaParams: false
beforeCurlyLambdaParams: false
}
danglingParentheses.exclude: [def]
verticalMultiline {
atDefnSite: true
newlineAfterOpenParen: true
arityThreshold: 5
excludeDanglingParens: []
}
rewrite.rules: [

92
README.md

@ -5,85 +5,49 @@ managing nondeterminism and unknown states in production code. Please follow
the series for a full explanation of the concepts represented by the code in
this repository:
1. [Part 1: Contexts and Effects](https://thisfieldwas.green/blog/2022/03/15/embracing-nondeterminism-part-1/)
2. [Part 2: Permitting or Halting Computation](https://thisfieldwas.green/drafts/embracing-nondeterminism-part-2)
1. [Part 1: Contexts and Effects](https://thisfieldwas.green/blog/2022/03/15/contexts-and-effects/)
2. [Part 2: Permitting or Halting Computation](https://thisfieldwas.green/blog/2022/06/05/permitting-or-halting-computation/)
## Structure of the repository
**Scala packages under [`src/main/scala:green.thisfieldwas.embracingnondeterminism`](src/main/scala/green/thisfieldwas/embracingnondeterminism)**
* [`control`](src/main/scala/green/thisfieldwas/embracingnondeterminism/control)
contains the control-flow typeclasses such as
[`Applicative`](src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Applicative.scala).
* [`data`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data)
contains data types supporting contexts defined in `effects`
* [`effects`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects)
contains commonly-used contexts that Scala defines in its own Standard
Library, such as [`Either`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/Either.scala),
[`List`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/List.scala),
and [`Option`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/Option.scala)
and includes [`Id`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/package.scala#L20)
and [`NonEmptyList`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects/NonEmptyList.scala).
These code examples demonstrate different encodings of presence as the
effects of operations and stand to illustrate how idioms in the series may be
contains commonly-used contexts that Scala defines in its own Standard
Library, such as [`Either`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Either.scala),
[`List`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/List.scala),
and [`Option`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Option.scala)
and includes [`Id`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/package.scala#L20)
and [`NonEmptyList`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyList.scala).
These code examples demonstrate different encodings of presence as the
effects of operations and stand to illustrate how idioms in the series may be
implemented across varying types.
* [`extractables`](src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables)
contains illustrations of abstraction over presence using subtype
polymorphism with abbreviated definitions of the classes defined in the
[`effects`](src/main/scala/green/thisfieldwas/embracingnondeterminism/effects)
package. These exist to highlight the value in using
[`Functor`](src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Functor.scala)
as a programming pattern.
* [`typeclasses`](src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses)
contains the definitions of:
* [`Functor`](src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Functor.scala)
* [`Applicative`](src/main/scala/green/thisfieldwas/embracingnondeterminism/typeclasses/Applicative.scala)
The [`Functor`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Functor.scala)
and [`Semigroup`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Semigroup.scala)
typeclasses are found here as well.
[`data`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data)
package. These exist to highlight the value in using
[`Functor`](src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Functor.scala)
as a design pattern.
**Scala packages under [`src/test/scala:green.thisfieldwas.embracingnondeterminism`](src/main/scala/green/thisfieldwas/embracingnondeterminism)**
* [`effects`](src/test/scala/green/thisfieldwas/embracingnondeterminism/effects)
contains specs for each of the effect types as well as "laws" tests which
* [`control`](src/test/scala/green/thisfieldwas/embracingnondeterminism/control)
contains the laws definitions for the control-flow typeclasses.
* [`data`](src/test/scala/green/thisfieldwas/embracingnondeterminism/data)
contains specs for each of the effect types as well as "laws" tests which
assert that the associated effect type is well-defined for particular
typeclasses, such as `Functor`.
* [`extractables`](src/test/scala/green/thisfieldwas/embracingnondeterminism/extractables)
contains specs and property checks for the
[`extractables`](src/main/scala/green/thisfieldwas/embracingnondeterminism/extractables)
classes to illustrate how they work and how their abstractions break down.
* [`stdlib`](src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib)
contains property checks to illustrate how classes in the Scala Standard
Library conform to typeclass laws themselves, and highlight that you, the
* [`stdlib`](src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib)
contains property checks to illustrate how classes in the Scala Standard
Library conform to typeclass laws themselves, and highlight that you, the
reader of this series, have been using some of these concepts for some time.
* [`typeclasses`](src/test/scala/green/thisfieldwas/embracingnondeterminism/typeclasses)
contains laws specifications for individual typeclasses.
* [`util`](src/test/scala/green/thisfieldwas/embracingnondeterminism/util)
* [`util`](src/test/scala/green/thisfieldwas/embracingnondeterminism/util)
contains utility classes to aid in testing.
## Using code and navigating imports
Import the `effect` you wish to use:
```scala
import green.thisfieldwas.embracingnondeterminism.effects.NonEmptyList
```
In order to use `NonEmptyList` as a `Functor` import both `Functor` and `NonEmptyList`'s instances:
```scala
import green.thisfieldwas.embracingnondeterminism.effects.NonEmptyList
import green.thisfieldwas.embracingnondeterminism.effects.NonEmptyList.Instances._
import green.thisfieldwas.embracingnondeterminism.typeclasses.Functor
Functor[NonEmptyList].map(NonEmptyList(1, 2, 3))(x => x * 2) // => NonEmptyList(1, 4, 6)
```
If you wish to use object-oriented syntax, import `Functor`s extension methods:
```scala
import green.thisfieldwas.embracingnondeterminism.effects.NonEmptyList
import green.thisfieldwas.embracingnondeterminism.effects.NonEmptyList.Instances._
import green.thisfieldwas.embracingnondeterminism.typeclasses.Functor
import green.thisfieldwas.embracingnondeterminism.typeclasses.Functor.Syntax._
NonEmptyList(1, 2, 3).map(x => x * 2) // => NonEmptyList(1, 4, 6)
```
## Using `sbt console`
Import all project definitions by loading the following script after launching

24
console/imports.scala

@ -1,19 +1,11 @@
import green.thisfieldwas.{embracingnondeterminism => en}
import en.effects._
import Id.Instances._
import Either.Instances._
import Either.Syntax._
import List.Instances._
import NonEmptyList.Instances._
import Option.Instances._
import en.typeclasses._
import Functor.Syntax._
import Applicative.Syntax._
import Monad.Syntax._
import en.control._
import en.data._
import NonEmptyChain.Instances._
import Semigroup.Syntax._
import Tuple.Syntax._
import en.syntax.applicative._
import en.syntax.either._
import en.syntax.functor._
import en.syntax.monad._
import en.syntax.semigroup._
import en.syntax.tuple._
import en.syntax.validated._

10
macro/src/main/scala/green/thisfieldwas/embracingnondeterminism/data/GenerateTupleSyntax.scala

@ -5,8 +5,8 @@ import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/** Generates the following two extension methods for the Tuple{2-22} types:
* - (F[A], F[B], ...).tupled = F[(A, B, ...)]
* - (F[A], F[B], ...).mapN((A, B, ...) => Out) = F[Out]
* - (F[A], F[B], ...).tupled = F[(A, B, ...)]
* - (F[A], F[B], ...).mapN((A, B, ...) => Out) = F[Out]
*/
class GenerateTupleSyntax extends StaticAnnotation {
@ -37,8 +37,8 @@ object GenerateTupleSyntax {
def tupled(implicit F: Applicative[F]): F[(..$tupleTypeArgs)] = mapN(${TermName(s"Tuple${n + 1}")}.apply)
def mapN[Out](f: (..$tupleTypeArgs) => Out)(implicit F: Applicative[F]): F[Out] =
${tupleMembers.tail.foldLeft(q"F.ap(F.pure(f.curried))(${tupleMembers.head})") { (acc, tupleMember) =>
q"F.ap($acc)($tupleMember)"
}}
q"F.ap($acc)($tupleMember)"
}}
}
"""
}
@ -53,7 +53,7 @@ object GenerateTupleSyntax {
}
q"""
import green.thisfieldwas.embracingnondeterminism.typeclasses.Applicative
import green.thisfieldwas.embracingnondeterminism.control.Applicative
..$classes
"""
}

1
project/plugins.sbt

@ -0,0 +1 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")

102
src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Applicative.scala

@ -0,0 +1,102 @@
package green.thisfieldwas.embracingnondeterminism.control
import green.thisfieldwas.embracingnondeterminism.data.Functor
/** Applicative is a specialization of Functor where the type contained within
* the context, `A` in `F[A]`, in known specifically to have some type of `A =>
* B`. That is, the Functor contains a function and has the form of `F[A =>
* B]`. This make the Functor an ''Applicative'' Functor, which allows it to be
* treated as a lifted function.
*
* @tparam F
* The context type.
*/
trait Applicative[F[_]] extends Functor[F] {
/** Applicative provides a default map() implementation. This can be
* overridden if it makes sense to.
*
* @param fa
* The instance of the structure to apply the function to.
* @param f
* The function to apply, if the structure is in the desired case.
* @tparam A
* The type of instances contained within the structure.
* @tparam B
* The type to map the instances into.
* @return
* The resulting structure.
*/
override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates a
* new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
def pure[A](a: A): F[A]
/** Pronounced "apply", this function takes an applicative functor of the form
* `F[A => B]` and applies it to the functor `F[A]`, giving the result of
* `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
/** A two-argument analog of `map()`.
*
* @param fa
* The first argument context.
* @param fb
* The second argument context.
* @param f
* The function to apply to the contents of the arguments.
* @tparam A
* The type of the first argument.
* @tparam B
* The type of the second argument.
* @tparam C
* The type of the result.
* @return
* The lifted result.
*/
def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
ap(ap(pure(f.curried))(fa))(fb)
/** Take a list of contexts and transform them into a context containing a
* list of their contents. If all contexts are in their desired case, then
* the output context will be a desired case containing a list of their
* contents. Otherwise, the output context will be in the undesired case if
* any context is in the undesired case.
*
* @param listFa
* The list of functors.
* @tparam A
* The type contained within the functors.
* @return
*/
def sequence[A](listFa: List[F[A]]): F[List[A]] =
listFa.foldLeft(pure(List[A]()))((fListA, fa) => map2(fa, fListA)(_ :: _))
}
object Applicative {
def apply[F[_]: Applicative]: Applicative[F] = implicitly[Applicative[F]]
}

74
src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Monad.scala

@ -0,0 +1,74 @@
package green.thisfieldwas.embracingnondeterminism.control
/** Monad is a specialization of Functor where the type contained within the
* context, `A` in `F[A]`, is known specifically to have some type of `F[A]`.
* That is, the Functor is nested as `F[F[A]]`. The Monad defines a function
* called `flatten()` which joins the inner `F[A]` with the outer `F[_]`, and
* defines a specialization of the `map()` function called `flatMap()` which
* permits the usage of a function producing a context: `f: A => F[B]`.
*
* By permitting the context to nest within itself, the inner context may alter
* the outer context's case when flattened. This allows for the function `f: A
* \=> F[B]` to control whether computation proceeds or halts against the
* context.
*
* Monads must define one or both of `flatten()` or `flatMap()`, as one is used
* to define the other.
*
* @tparam F
* The context type.
*/
trait Monad[F[_]] extends Applicative[F] {
/** Map the contents of this context with a function which returns its results
* in a new instance of the context. This new context is joined with the
* current context.
*
* If the context `fa` is in the desired case, two things can happen based on
* the output of function `f`:
*
* 1. If `f` returns `F[B]` in the desired case, then `flatMap()` returns
* `F[B]` in the desired case.
* 1. If `f` returns `F[B]` in the undesired case, then `flatMap()` returns
* `F[B]` in the undesired case and further computation is halted.
*
* If the context `fa` is in the undesired case, then `f` does not apply and
* no computation occurs.
*
* @param fa
* The context to map.
* @param f
* The function to map the contents of the context.
* @tparam A
* The term of the instances within the context.
* @tparam B
* The term of the instances within the context produced by the function.
* @return
* The context produced by the function joined with the context `fa`.
*/
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = flatten(map(fa)(f))
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are in
* the desired case, then a flattened context in the desired case is
* produced.
*
* @param ffa
* The nested context.
* @tparam A
* The term contained by the inner context.
* @return
* A flattened context containing term `A`.
*/
def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(fa => fa)
}
object Monad {
def apply[F[_]: Monad]: Monad[F] = implicitly[Monad[F]]
}

175
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Either.scala

@ -0,0 +1,175 @@
package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.Monad
/** The `Either` class encodes a dimension contextualized by "Either" the
* "Right" term that you want, or you're "Left" with the term you don't want.
* This context is frequently used to model success or failure, the specific
* instance of which is unknown at runtime without specific knowledge of the
* inputs to the operation producing the context.
*
* The presence of the "Right" term `A` is the desired case of this context. If
* the "Left" term `X` is present, then the context is in the undesired case.
*
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
sealed trait Either[+X, +A] {
/** Gets the instance of the "Left" term if it exists.
*
* @return
* The "Left" instance.
* @throws NoSuchElementException
* if the context is a "Right".
*/
def left: X
/** Gets the instance of the "Right" term if it exists.
*
* @return
* The "Right" instance.
* @throws NoSuchElementException
* if the context is a "Left".
*/
def right: A
/** Gets whether this context is a "Left".
*
* @return
* True if left.
*/
def isLeft: Boolean = false
/** Gets whether this context is a "Right".
*
* @return
* True if right.
*/
def isRight: Boolean = false
}
/** Encodes the "Left" or undesired case of the context.
*
* @param left
* The instance of the "Left" term.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
case class Left[+X, +A](left: X) extends Either[X, A] {
def right: A = throw new NoSuchElementException("Left.right")
override def isLeft: Boolean = true
}
/** Encodes the "Right" or desired case of the context.
*
* @param right
* The instance of the "Right" case.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
case class Right[+X, +A](right: A) extends Either[X, A] {
def left: X = throw new NoSuchElementException("Right.left")
override def isRight: Boolean = true
}
object Either {
/** A smart constructor to create a "Left" typed specifically as an "Either".
*
* @param x
* The "Left" instance.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
* @return
* An "Either" in the undesired case.
*/
def left[X, A](x: X): Either[X, A] = Left(x)
/** A smart constructor to create a "Right" typed specifically as an "Either".
*
* @param a
* The "Right" instance.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
* @return
* An "Either" in the desired case.
*/
def right[X, A](a: A): Either[X, A] = Right(a)
implicit def eitherMonad[X]: Monad[Either[X, *]] = new Monad[Either[X, *]] {
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates
* a new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
override def pure[A](a: A): Either[X, A] = Right(a)
/** Pronounced "apply", this function takes an applicative functor of the
* form `F[A => B]` and applies it to the functor `F[A]`, giving the result
* of `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
override def ap[A, B](ff: Either[X, A => B])(fa: Either[X, A]): Either[X, B] =
(ff, fa) match {
case (Right(f), Right(a)) => Right(f(a))
case (Left(x), _) => Left(x)
case (_, Left(x)) => Left(x)
}
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are
* in the desired case, then a flattened context in the desired case
* is produced.
*
* @param ffa
* The nested context.
* @tparam A
* The term contained by the inner context.
* @return
* A flattened context containing term `A`.
*/
override def flatten[A](ffa: Either[X, Either[X, A]]): Either[X, A] =
ffa match {
case Right(fa) => fa
case Left(x) => Left(x)
}
}
}

38
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Functor.scala

@ -0,0 +1,38 @@
package green.thisfieldwas.embracingnondeterminism.data
/** Functors are structures that define a `map()` operation that allows for
* functions to be applied to them independent of specific knowledge about the
* state of their cases. What this means is that for a Functor in the desired
* case, the function applied via `map()` will be applied to the present
* instances of the term contained within the Functor. If the Functor is in the
* undesired case, then the function will not apply and the undesired case will
* propagate instead.
*
* @tparam F
* The type of the structure.
*/
trait Functor[F[_]] {
/** Given a structure `F[A]` and function `f: A => B`, if the structure is in
* the desired case, then apply the function such that `F[A] => F[B]`. If the
* structure `F[A]` is in the undesired case, then propagate the undesired
* case as `F[B]`.
*
* @param fa
* The instance of the structure to apply the function to.
* @param f
* The function to apply, if the structure is in the desired case.
* @tparam A
* The type of instances contained within the structure.
* @tparam B
* The type to map the instances into.
* @return
* The resulting structure.
*/
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def apply[F[_]: Functor]: Functor[F] = implicitly[Functor[F]]
}

303
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/List.scala

@ -0,0 +1,303 @@
package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.Monad
import scala.annotation.tailrec
/** The `List` class encodes a dimension contextualized by the presence of an
* unknown number of instances of term `A`. The length of the `List` in
* practices is nondeterministic at runtime without specific knowledge of the
* inputs to the operation that produces it.
*
* This context is in the desired case if there are a non-zero number of
* instances of term `A` present within it. If the `List` is empty, it is in
* the undesired case.
*
* This `List` is modeled as a singly-linked list.
*
* @tparam A
* The type contained within the `List`.
*/
sealed trait List[+A] {
/** The first instance in the `List`, if the list is non-empty.
*
* @return
* The first instance.
* @throws NoSuchElementException
* if the list is empty.
*/
def head: A
/** The remainder of the `List`, which may be empty.
* @return
* The rest of the `List`.
* @throws NoSuchElementException
* if the list is empty.
*/
def tail: List[A]
/** Gets whether this `List` is empty.
*
* @return
* True if empty.
*/
def isEmpty: Boolean = this == Nil
/** Gets the length of the `List`. This is a linear-time operation.
*
* @return
* The length of the `List`.
*/
def length: Int = {
@tailrec
def recur(n: Int, current: List[A]): Int =
current match {
case _ :: tail => recur(n + 1, tail)
case _ => n
}
recur(0, this)
}
/** Conses an instance to the beginning of the `List`, making it the new
* `head` and this `List` becomes the `tail`. This is a constant-time
* operation.
*
* @param x
* The new head to cons.
* @tparam B
* The type of the head.
* @return
* The new `List`.
*/
def ::[B >: A](x: B): List[B] =
new ::[B](x, this)
/** Conses an instance to the beginning of the `List` and returns a
* `NonEmptyList`. This is a constant-time operation.
*
* @param x
* The new head to cons.
* @tparam B
* The type of the head.
* @return
* A new `NonEmptyList`.
*/
def |:[B >: A](x: B): NonEmptyList[B] = NonEmptyList(x, this)
/** Folds the `List` starting from the end using a seed value and a custom
* function to perform the fold. This is a linear-time operation.
*
* @param z
* The seed value.
* @param f
* The custom folding function.
* @tparam B
* The type of the folded value.
* @return
* The result of the fold.
*/
def foldRight[B](z: B)(f: (A, B) => B): B = reverse.foldLeft(z)((acc, head) => f(head, acc))
/** Folds the `List` starting from the beginning using a seed value and a
* custom function to perform the fold. This is a linear-time operation.
*
* @param z
* The seed value.
* @param f
* The custom folding function.
* @tparam B
* The type of the folded value.
* @return
* The result of the fold.
*/
def foldLeft[B](z: B)(f: (B, A) => B): B = {
@tailrec
def recur(acc: B, current: List[A]): B =
current match {
case head :: tail => recur(f(acc, head), tail)
case _ => acc
}
recur(z, this)
}
/** Reverses the order of instances in the `List`.
*
* @return
* The reversed `List`.
*/
def reverse: List[A] = foldLeft(List[A]())((tail, head) => head :: tail)
/** Zips this `List` with another `List`, creating a new `List` of pairs of
* instances from the first two `List`s. The new `List` will be as long as
* the shortest `List`.
*
* @param other
* The right-hand `List` to zip with.
* @tparam B
* The type of the instances held by the right-hand `List`.
* @return
* A `List` containing pairs of instances from both `List`s.
*/
def zip[B](other: List[B]): List[(A, B)] = {
@tailrec
def recur(acc: List[(A, B)], left: List[A], right: List[B]): List[(A, B)] =
(left, right) match {
case (leftHead :: leftTail, rightHead :: rightTail) => recur((leftHead, rightHead) :: acc, leftTail, rightTail)
case _ => acc.reverse
}
recur(List(), this, other)
}
/** Converts the `List` into a Scala `Seq`.
*
* @return
* A new Scala `Seq`.
*/
def toSeq: Seq[A] = foldRight(Seq[A]())(_ +: _)
override def equals(obj: Any): Boolean = {
if (!obj.isInstanceOf[List[Any]]) {
return false
}
val other = obj.asInstanceOf[List[Any]]
if (this.length != other.length) {
return false
}
this.zip(other).foldLeft(true) { (equals, pair) =>
pair match {
case (left, right) => equals && left == right
}
}
}
override def hashCode(): Int = foldLeft(7)((hash, head) => 31 * hash + head.hashCode())
}
/** Encodes a present instance within the `List`. This is referred to as a
* "cons" (short for "construct" or "construction" of a head and tail) and is
* considered the desired case of a `List` as it contains at least one instance
* of the `List`'s term `A`.
*
* @param head
* The instance held by the cons.
* @param tail
* The rest of the `List`, which may be empty.
* @tparam A
* The type contained within the `List`.
*/
case class ::[+A](head: A, tail: List[A]) extends List[A]
/** Encodes an empty `List`. This is considered the undesired case of a `List`
* as it contains no instances of the `List`'s term `A`.
*/
case object Nil extends List[Nothing] {
def head: Nothing = throw new NoSuchElementException("Nil.head")
def tail: Nothing = throw new NoSuchElementException("Nil.tail")
}
object List {
/** A smart constructor that allows for creating a `List` using the following
* idiomatic syntax:
*
* {{{
* List(1, 2, 3, 4, ...) // creates a non-empty List of Int's
* List[Int]() // creates an empty List of Int's
* }}}
*
* @param as
* The instances to construct the `List` from.
* @tparam A
* The type contained within the `List`.
* @return
* The new `List`.
*/
def apply[A](as: A*): List[A] =
if (as.isEmpty) Nil
else as.head :: apply(as.tail: _*)
/** Allows for idiomatic pattern matching syntax:
*
* {{{
* list match {
* case List(a, b, c) => ???
* }
* }}}
*
* @param as
* The `List` to unapply.
* @tparam A
* The type contained within the `List`.
* @return
* A present `Seq` of instances contained in this `List`.
*/
def unapplySeq[A](as: List[A]): scala.Option[Seq[A]] = scala.Some(as.toSeq)
implicit val listMonad: Monad[List] = new Monad[List] {
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates
* a new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
override def pure[A](a: A): List[A] = List(a)
/** Pronounced "apply", this function takes an applicative functor of the
* form `F[A => B]` and applies it to the functor `F[A]`, giving the result
* of `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
override def ap[A, B](ff: List[A => B])(fa: List[A]): List[B] =
ff.foldLeft(List[B]()) { (outerResult, f) =>
fa.foldLeft(outerResult) { (innerResult, a) =>
f(a) :: innerResult
}
}.reverse
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are
* in the desired case, then a flattened context in the desired case
* is produced.
*
* @param ffa
* The nested context.
* @tparam A
* The term contained by the inner context.
* @return
* A flattened context containing term `A`.
*/
override def flatten[A](ffa: List[List[A]]): List[A] =
ffa
.foldLeft(List[A]()) { (outerResult, as) =>
as.foldLeft(outerResult) { (innerResult, a) =>
a :: innerResult
}
}
.reverse
}
}

101
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyChain.scala

@ -13,7 +13,8 @@ package green.thisfieldwas.embracingnondeterminism.data
* non-zero presence of errors, this Chain structure represents one or more
* values indicating reasons for failure.
*
* @tparam A The type of the values held within the chain.
* @tparam A
* The type of the values held within the chain.
*/
trait NonEmptyChain[+A] {
@ -21,50 +22,63 @@ trait NonEmptyChain[+A] {
/** The first value in the chain.
*
* @return The value.
* @return
* The value.
*/
def head: A
/** All but the first value in the chain, if the chain has more than one
* value.
*
* @return The rest of the chain
* @return
* The rest of the chain
*/
def tail: Option[NonEmptyChain[A]]
/** The length of the chain, which is always non-zero.
*
* @return The length of the chain.
* @return
* The length of the chain.
*/
def length: Int
/** Conses a value to the start of the chain.
*
* @param newHead The new start of the chain.
* @tparam B The type of the head.
* @return The new chain.
* @param newHead
* The new start of the chain.
* @tparam B
* The type of the head.
* @return
* The new chain.
*/
def cons[B >: A](newHead: B): NonEmptyChain[B] = prepend(Singleton(newHead))
/** Prepends a chain to the start of this one.
*
* @param prefix The chain to prepend.
* @tparam B The type of the chain.
* @return The new chain.
* @param prefix
* The chain to prepend.
* @tparam B
* The type of the chain.
* @return
* The new chain.
*/
def prepend[B >: A](prefix: NonEmptyChain[B]): NonEmptyChain[B] = prefix.append(this)
/** Appends a chain to the end of this one.
*
* @param suffix The chain to append.
* @tparam B The type of the chain.
* @return The new chain.
* @param suffix
* The chain to append.
* @tparam B
* The type of the chain.
* @return
* The new chain.
*/
def append[B >: A](suffix: NonEmptyChain[B]): NonEmptyChain[B] = Append(this, suffix)
/** Converts the chain to a Scala Seq.
*
* @return The values in a Seq.
* @return
* The values in a Seq.
*/
def toSeq: Seq[A] = head +: tail.fold(Seq[A]())(_.toSeq)
@ -82,16 +96,22 @@ trait NonEmptyChain[+A] {
object NonEmptyChain {
/** Creates a `NonEmptyChain`` using idiomatic syntax:
import green.thisfieldwas.embracingnondeterminism.syntax.functor._
/** Creates a `NonEmptyChain` using idiomatic syntax:
*
* {{{
* NonEmptyChain(1, 2, 3, ...)
* }}}
*
* @param value The first, and required, value of the chain.
* @param rest The rest, and optional, values of the chain.
* @tparam A The type of the values.
* @return The new chain.
* @param value
* The first, and required, value of the chain.
* @param rest
* The rest, and optional, values of the chain.
* @tparam A
* The type of the values.
* @return
* The new chain.
*/
def apply[A](value: A, rest: A*): NonEmptyChain[A] =
rest.map[NonEmptyChain[A]](Singleton(_)).foldLeft[NonEmptyChain[A]](Singleton(value))(_ append _)
@ -104,17 +124,22 @@ object NonEmptyChain {
* }
* }}}
*
* @param chain The chain to unapply.
* @tparam A The type of values in the chain.
* @return A Some containing the Seq of values in the chain.
* @param chain
* The chain to unapply.
* @tparam A
* The type of values in the chain.
* @return
* A Some containing the Seq of values in the chain.
*/
def unapply[A](chain: NonEmptyChain[A]): scala.Option[Seq[A]] =
scala.Some(chain.toSeq)
/** Represents the singleton case of the chain, where only one value exists.
*
* @param head The single value.
* @tparam A The type of the values held within the chain.
* @param head
* The single value.
* @tparam A
* The type of the values held within the chain.
*/
case class Singleton[+A](head: A) extends NonEmptyChain[A] {
@ -126,9 +151,12 @@ object NonEmptyChain {
/** The concatenation case of the chain. This represents the linkage between
* two chains and is a constant-time encoding of the operation.
*
* @param prefix The left-hand side of the concatenated chain.
* @param suffix The right-hand side of the concatenated chain.
* @tparam A The type of the values held within the chain.
* @param prefix
* The left-hand side of the concatenated chain.
* @param suffix
* The right-hand side of the concatenated chain.
* @tparam A
* The type of the values held within the chain.
*/
case class Append[+A](prefix: NonEmptyChain[A], suffix: NonEmptyChain[A]) extends NonEmptyChain[A] {
@ -139,14 +167,13 @@ object NonEmptyChain {
override def length: Int = prefix.length + suffix.length
}
object Instances {
/** The Semigroup instance of the NonEmptyChain. This is a simple proxy over
* the append() operation.
*
* @tparam A The type of the values held by the chain.
* @return The Semigroup instance.
*/
implicit def nonEmptyChainSemigroup[A]: Semigroup[NonEmptyChain[A]] = _ append _
}
/** The Semigroup instance of the NonEmptyChain. This is a simple proxy over
* the append() operation.
*
* @tparam A
* The type of the values held by the chain.
* @return
* The Semigroup instance.
*/
implicit def nonEmptyChainSemigroup[A]: Semigroup[NonEmptyChain[A]] = _ append _
}

163
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyList.scala

@ -0,0 +1,163 @@
package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.{Applicative, Monad}
/** The `NonEmptyList` class encodes a dimension contextualized by the presence
* of an unknown number of instances of term `A`. The length of the
* `NonEmptyList` in practices is nondeterministic at runtime without specific
* knowledge of the inputs to the operation that produces it. In contrast with
* `List`, a `NonEmptyList` is guaranteed to always hold at least one instance
* of term `A` in its `head`.
*
* This `NonEmptyList` is modeled as a special case of a cons: a typed, present
* construction of a head and a tail of a potentially empty `List`.
*
* As at least one instance of the term `A` is guaranteed to be present, this
* context is always in the desired case.
*
* @param head
* The present instance of term `A`.
* @param tail
* A potentially empty remainder of the list.
* @tparam A
* The type contained within the `NonEmptyList`.
*/
case class NonEmptyList[+A](head: A, tail: List[A]) {
/** The length of the `NonEmptyList`. This will always be at least 1 and is a
* linear-time operation.
*
* @return
* The length of the `NonEmptyList`.
*/
def length: Int = 1 + tail.length
/** Conses a new head to this `NonEmptyList`. The resulting `NonEmptyList`
* should be treated as though its length is guaranteed to only be at least
* 1. This is a constant-time operation.
*
* @param newHead
* The new head.
* @tparam B
* The type of the head.
* @return
* The new `NonEmptyList`.
*/
def |:[B >: A](newHead: B): NonEmptyList[B] = NonEmptyList(newHead, head :: tail)
/** Converts the `NonEmptyList` to a `List`. The resulting `List` should be
* treated as though it may be empty. This is a constant-time operation.
*
* @return
* The new `List`.
*/
def toList: List[A] = head :: tail
/** Converts the `NonEmptyList` to a Scala `Seq`. This is a linear-time
* operation.
*
* @return
* The new `Seq`.
*/
def toSeq: Seq[A] = head +: tail.toSeq
}
object |: {
/** Allows for idiomatic pattern matching against a `NonEmptyList`:
*
* {{{
* nonEmptyList match {
* head |: tail => ???
* }
* }}}
*
* @param nel
* The `NonEmptyList` to unapply.
* @tparam A
* The type contained within the `NonEmptyList`.
* @return
* A present 2-tuple of the head and tail.
*/
def unapply[A](nel: NonEmptyList[A]): scala.Some[(A, List[A])] = scala.Some((nel.head, nel.tail))
}
object NonEmptyList {
/** Allows for the idiomatic construction of a `NonEmptyList` using this
* syntax:
*
* {{{
* NonEmptyList(1, 2, 3, 4, ...)
* }}}
*
* @param head
* @param tail
* @tparam A
* @return
*/
def apply[A](head: A, tail: A*): NonEmptyList[A] = NonEmptyList(head, List(tail: _*))
implicit val nonEmptyListMonad: Monad[NonEmptyList] = new Monad[NonEmptyList] {
import green.thisfieldwas.embracingnondeterminism.syntax.functor._
import green.thisfieldwas.embracingnondeterminism.syntax.monad._
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates
* a new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
override def pure[A](a: A): NonEmptyList[A] = NonEmptyList(a)
/** Pronounced "apply", this function takes an applicative functor of the
* form `F[A => B]` and applies it to the functor `F[A]`, giving the result
* of `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
override def ap[A, B](ff: NonEmptyList[A => B])(fa: NonEmptyList[A]): NonEmptyList[B] =
Applicative[List].ap(ff.toList)(fa.toList) match {
case head :: tail => head |: tail
case _ => throw new IllegalStateException("Impossible!")
}
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are
* in the desired case, then a flattened context in the desired case
* is produced.
*
* @param ffa
* The nested context.
* @tparam A
* The term contained by the inner context.
* @return
* A flattened context containing term `A`.
*/
override def flatten[A](ffa: NonEmptyList[NonEmptyList[A]]): NonEmptyList[A] =
ffa match {
case (head |: tail) |: rest => head |: tail.foldRight(rest.map(_.toList).flatten)(_ :: _)
}
}
}

219
src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Option.scala

@ -0,0 +1,219 @@
package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.Monad
/** The `Option` class encodes a dimension contextualized by the presence or
* absence of an instance of the term `A`. The presence of `A` is unknown at
* runtime without specific knowledge of the inputs to the operation producing
* the context.
*
* This context is in the desired case if an instance of `A` is present. The
* absence of `A` is the undesired case.
*
* This context may be regarded as a "null done right", as it strongly-types
* the notion of absence and requires that the absent case be handled.
*
* @tparam A
* The type of the instance contained.
*/
sealed trait Option[+A] {
/** Gets the instance of term `A` if it exists.
*
* @return
* The instance of `A`.
* @throws NoSuchElementException
* if the instance of `A` is absent.
*/
def get: A
/** Gets whether the instance of `A` is present.
*
* @return
* True if present.
*/
def isSome: Boolean = !isNone
/** Gets whether the instance of `A` is absent.
*
* @return
* True if absent.
*/
def isNone: Boolean = this == None
/** Fold the `Option` into a single value.
*
* @param noneCase
* The value to use if the instance of term `A` is absent.
* @param someCase
* The function to transform the instance of term `A` if present.
* @tparam B
* The type to transform term `A` into.
* @return
* The transformed instance of `A`.
*/
def fold[B](noneCase: => B)(someCase: A => B): B
/** If this option is empty, use the other option.
*
* @param other
* The other option to use.
* @tparam B
* The type of the other option, compatible with this one.
* @return
* The option chosen.
*/
def orElse[B >: A](other: Option[B]): Option[B]
}
/** Encodes the present case of an instance of term `A`. That is to say, there
* is "Some" `A` here.
*
* @param get
* The instance of term `A`.
* @tparam A
* The type of the instance contained.
*/
case class Some[+A](get: A) extends Option[A] {
override def fold[B](noneCase: => B)(someCase: A => B): B = someCase(get)
override def orElse[B >: A](other: Option[B]): Option[B] = this
}
/** Encodes the absent ccase of an instance of term `A`. That is, if you were to
* inspect for some instance of term `A`, you would see "None" here.
*/
case object None extends Option[Nothing] {
override def get: Nothing = throw new NoSuchElementException("None.get")
override def fold[B](noneCase: => B)(someCase: Nothing => B): B = noneCase
override def orElse[B >: Nothing](other: Option[B]): Option[B] = other
}
object Option {
/** A smart constructor for a "Some" typed specifically as an `Option`.
*
* @param a
* The present instance of term `A`.
* @tparam A
* The type of the instance contained.
* @return
* An `Option` in the desired case with a present instance of term `A`.
*/
def apply[A](a: A): Option[A] = Some(a)
/** A smart constructor for a "None" typed specifically as an `Option`.
*
* @tparam A
* The type of the instance contained.
* @return
* An `Option` in the undesired case with an absent instance of term `A`.
*/
def apply[A](): Option[A] = None
/** Allows for idiomatic pattern matching syntax:
*
* {{{
* option match {
* case Some(x) => ???
* case None => ???
* }
* }}}
*
* @param o
* The option to unapply.
* @tparam A
* The type of the instance contained.
* @return
* A Scala `Option` corresponding to this `Option`.
*/
def unapply[A](o: Option[A]): scala.Option[A] = o match {
case Some(a) => scala.Some(a)
case None => scala.None
}
/** Given a truthy condition, lift an instance of term `A` into the `Option`,
* giving the desired `Some` case. If the condition is falsy, then return the
* undesired `None` case.
*
* @param test
* The condition to test.
* @param presentCase
* The value to lift into the present case if the condition is true.
* @tparam A
* The type of the instance.
* @return
* The lifted value if the condition is true and `None` otherwise.
*/
def cond[A](test: => Boolean)(presentCase: => A): Option[A] =
if (test) {
Option(presentCase)
} else {
None
}
implicit val optionMonad: Monad[Option] = new Monad[Option] {
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates
* a new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
override def pure[A](a: A): Option[A] = Some(a)
/** Pronounced "apply", this function takes an applicative functor of the
* form `F[A => B]` and applies it to the functor `F[A]`, giving the result
* of `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
override def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] =
(ff, fa) match {
case (Some(f), Some(a)) => Some(f(a))
case _ => None
}
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are
* in the desired case, then a flattened context in the desired case
*