From 131887d6068884f21b02a81e4e02f5aabdfe062e Mon Sep 17 00:00:00 2001 From: Logan McGrath Date: Thu, 12 Aug 2021 09:40:42 -0700 Subject: [PATCH] Project setup --- .gitignore | 4 ++ build.sbt | 16 ++++++ project/build.properties | 1 + src/main/scala/conversions/package.scala | 19 +++++++ src/main/scala/data/LispList.scala | 33 ++++++++++++ src/main/scala/data/Schrodinger.scala | 18 +++++++ src/main/scala/data/Superposition.scala | 27 ++++++++++ src/main/scala/data/package.scala | 17 ++++++ .../scala/extensions/flatMap/package.scala | 8 +++ src/main/scala/extensions/map/package.scala | 29 +++++++++++ src/main/scala/extensions/pure/package.scala | 8 +++ .../scala/instances/identity/package.scala | 24 +++++++++ .../scala/instances/lisplist/package.scala | 20 +++++++ .../scala/instances/schrodinger/package.scala | 24 +++++++++ src/main/scala/instances/state/package.scala | 20 +++++++ .../instances/superposition/package.scala | 24 +++++++++ src/main/scala/ops/identity/package.scala | 8 +++ src/main/scala/ops/lisplist/package.scala | 8 +++ src/main/scala/ops/schrodinger/package.scala | 8 +++ src/main/scala/ops/state/package.scala | 8 +++ .../scala/ops/superposition/package.scala | 8 +++ src/main/scala/typeclasses/Applicative.scala | 24 +++++++++ .../scala/typeclasses/ApplicativeError.scala | 25 +++++++++ src/main/scala/typeclasses/Functor.scala | 13 +++++ src/main/scala/typeclasses/Monad.scala | 17 ++++++ src/main/scala/typeclasses/MonadError.scala | 31 +++++++++++ src/main/scala/typeclasses/Monoid.scala | 16 ++++++ src/main/scala/typeclasses/Semigroup.scala | 15 ++++++ src/test/scala/extensions/MapSpec.scala | 52 +++++++++++++++++++ 29 files changed, 525 insertions(+) create mode 100644 .gitignore create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 src/main/scala/conversions/package.scala create mode 100644 src/main/scala/data/LispList.scala create mode 100644 src/main/scala/data/Schrodinger.scala create mode 100644 src/main/scala/data/Superposition.scala create mode 100644 src/main/scala/data/package.scala create mode 100644 src/main/scala/extensions/flatMap/package.scala create mode 100644 src/main/scala/extensions/map/package.scala create mode 100644 src/main/scala/extensions/pure/package.scala create mode 100644 src/main/scala/instances/identity/package.scala create mode 100644 src/main/scala/instances/lisplist/package.scala create mode 100644 src/main/scala/instances/schrodinger/package.scala create mode 100644 src/main/scala/instances/state/package.scala create mode 100644 src/main/scala/instances/superposition/package.scala create mode 100644 src/main/scala/ops/identity/package.scala create mode 100644 src/main/scala/ops/lisplist/package.scala create mode 100644 src/main/scala/ops/schrodinger/package.scala create mode 100644 src/main/scala/ops/state/package.scala create mode 100644 src/main/scala/ops/superposition/package.scala create mode 100644 src/main/scala/typeclasses/Applicative.scala create mode 100644 src/main/scala/typeclasses/ApplicativeError.scala create mode 100644 src/main/scala/typeclasses/Functor.scala create mode 100644 src/main/scala/typeclasses/Monad.scala create mode 100644 src/main/scala/typeclasses/MonadError.scala create mode 100644 src/main/scala/typeclasses/Monoid.scala create mode 100644 src/main/scala/typeclasses/Semigroup.scala create mode 100644 src/test/scala/extensions/MapSpec.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..051c9b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.bsp/ +.idea/ +target/ +project/target/ diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..7e0938c --- /dev/null +++ b/build.sbt @@ -0,0 +1,16 @@ +addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.0" cross CrossVersion.full) + +lazy val root = (project in file(".")). + settings( + inThisBuild(List( + organization := "com.example", + scalaVersion := "2.12.9" + )), + name := "scalatest-example", + scalacOptions ++= Seq( + "-language:implicitConversions", + "-language:higherKinds", + ) + ) + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.9" % Test diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..10fd9ee --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.5.5 diff --git a/src/main/scala/conversions/package.scala b/src/main/scala/conversions/package.scala new file mode 100644 index 0000000..8808165 --- /dev/null +++ b/src/main/scala/conversions/package.scala @@ -0,0 +1,19 @@ +import data._ + +/** + * Implicit conversions + */ +package object conversions { + + /** + * Wholesale conversion of a LispList to a Scala Seq + */ + implicit def convertToSeq[A](list: LispList[A]): Seq[A] = ??? + + /** + * Wholesale conversion of a LispList to a Scala List + */ + implicit def convertToList[A](list: LispList[A]): List[A] = ??? + + // TODO what other conversions? +} diff --git a/src/main/scala/data/LispList.scala b/src/main/scala/data/LispList.scala new file mode 100644 index 0000000..158c7eb --- /dev/null +++ b/src/main/scala/data/LispList.scala @@ -0,0 +1,33 @@ +package data + +/** + * A Lisply-named representation of a singly-linked list and an analogue to + * Scala's immutable List. This is a special case of any type A where there + * exists a nondeterministic selection of its values that must be specially + * handled such that the number of values is inconsequential. + */ +sealed trait LispList[+A] { + + def car: A + + def cdr: LispList[A] +} + +case class LispCons[+A](car: A, cdr: LispList[A]) extends LispList[A] + +case object LispNil extends LispList[Nothing] { + + def car: Nothing = throw new Exception("LispList with no car!") + + def cdr: LispList[Nothing] = throw new Exception("LispList with no cdr!") +} + +object LispList { + + def apply[A](): LispList[A] = LispNil + + def apply[A](items: A*): LispList[A] = items.foldRight(LispList[A]())(LispCons[A]) + + // TODO how to make a Seq + // def unapplySeq[A](list: LispList[A]): Option[Seq[A]] = Some(list.toSeq) +} diff --git a/src/main/scala/data/Schrodinger.scala b/src/main/scala/data/Schrodinger.scala new file mode 100644 index 0000000..89d271c --- /dev/null +++ b/src/main/scala/data/Schrodinger.scala @@ -0,0 +1,18 @@ +package data + +/** + * Schrodinger is an analogue to Scala's Option and a special case of + * Superposition where a cat may or may not exist and the cat must be specially + * handled as if it were simultaneously alive or dead. + */ +sealed trait Schrodinger[+A] { + + def cat: A +} + +case class Alive[+A](cat: A) extends Schrodinger[A] + +case object Dead extends Schrodinger[Nothing] { + + def cat: Nothing = throw new Exception("He's dead Jim!") +} diff --git a/src/main/scala/data/Superposition.scala b/src/main/scala/data/Superposition.scala new file mode 100644 index 0000000..8e66c18 --- /dev/null +++ b/src/main/scala/data/Superposition.scala @@ -0,0 +1,27 @@ +package data + +/** + * Superposition is an analogue to Scala's Either and \a special case of a + * product (any type of A × B) where only A or B exists, but must be treated + * specially as though it could be either one. + */ +sealed trait Superposition[+A, +B] + +/** + * Implied negative case of the Superposition. This is probably not the one you + * want. + * + * @param value The thing you probably don't want. + * @tparam A Downside type + * @tparam B Upside type + */ +case class Downside[+A, +B](value: A) extends Superposition[A, B] + +/** + * Implied positive case of the Superposition. This is usually the one you want. + * + * @param value The thing you usually want. + * @tparam A Downside type + * @tparam B Upside type + */ +case class Upside[+A, +B](value: B) extends Superposition[A, B] diff --git a/src/main/scala/data/package.scala b/src/main/scala/data/package.scala new file mode 100644 index 0000000..5b1d03c --- /dev/null +++ b/src/main/scala/data/package.scala @@ -0,0 +1,17 @@ +package object data { + + /** + * A modeled stateful computation that for any state of type S there exists + * a computation that derives from S a product of type A and a successive + * instance of S representing the state of the next computation. + * + * @tparam S The state type + * @tparam A The value returned by the operation + */ + type State[S, +A] = S => (S, A) + + /** + * A special case of any type A that is just itself. + */ + type Identity[+A] = A +} diff --git a/src/main/scala/extensions/flatMap/package.scala b/src/main/scala/extensions/flatMap/package.scala new file mode 100644 index 0000000..3d84ae0 --- /dev/null +++ b/src/main/scala/extensions/flatMap/package.scala @@ -0,0 +1,8 @@ +package extensions + +/** + * Implicit method extensions for flatMap(). + */ +package object flatMap { + +} diff --git a/src/main/scala/extensions/map/package.scala b/src/main/scala/extensions/map/package.scala new file mode 100644 index 0000000..59a4bfb --- /dev/null +++ b/src/main/scala/extensions/map/package.scala @@ -0,0 +1,29 @@ +package extensions + +import data._ + +/** + * Implicit method extensions for map(). + */ +package object map { + + implicit class LispListOps[+A](val list: LispList[A]) extends AnyVal { + + def map[B](f: A => B): LispList[B] = ??? + } + + implicit class SuperpositionOps[+A, +B](val fallible: Superposition[A, B]) extends AnyVal { + + def map[C](f: B => C): Superposition[A, C] = ??? + } + + implicit class SchrodingerOps[+A](val possibly: Schrodinger[A]) extends AnyVal { + + def map[B](f: A => B): Schrodinger[B] = ??? + } + + implicit class StateOps[S, +A](val state: State[S, A]) extends AnyVal { + + def map[B](f: A => B): State[S, B] = ??? + } +} diff --git a/src/main/scala/extensions/pure/package.scala b/src/main/scala/extensions/pure/package.scala new file mode 100644 index 0000000..757c138 --- /dev/null +++ b/src/main/scala/extensions/pure/package.scala @@ -0,0 +1,8 @@ +package extensions + +/** + * Implicit method extensions for pure(). + */ +package object pure { + +} diff --git a/src/main/scala/instances/identity/package.scala b/src/main/scala/instances/identity/package.scala new file mode 100644 index 0000000..ab32192 --- /dev/null +++ b/src/main/scala/instances/identity/package.scala @@ -0,0 +1,24 @@ +package instances + +import data._ +import typeclasses._ + +/** + * Typeclass instances for Identity + */ +package object identity { + + implicit val identityFunctor: Functor[Identity] = ??? + + implicit val identityApplicative: Applicative[Identity] = ??? + + implicit def identityApplicativeError[E]: ApplicativeError[Identity, E] = ??? + + implicit val identityMonad: Monad[Identity] = ??? + + implicit def identityMonadError[E]: MonadError[Identity, E] = ??? + + implicit def identitySemigroup[A]: Semigroup[Identity[A]] = ??? + + implicit def identityMonoid[A]: Monoid[Identity[A]] = ??? +} diff --git a/src/main/scala/instances/lisplist/package.scala b/src/main/scala/instances/lisplist/package.scala new file mode 100644 index 0000000..d239198 --- /dev/null +++ b/src/main/scala/instances/lisplist/package.scala @@ -0,0 +1,20 @@ +package instances + +import data._ +import typeclasses._ + +/** + * Typeclass instances for LispList + */ +package object lisplist { + + implicit val lispListFunctor: Functor[LispList] = ??? + + implicit val lispListApplicative: Applicative[LispList] = ??? + + implicit val lispListMonad: Monad[LispList] = ??? + + implicit def lispListMonoid[A]: Monoid[LispList[A]] = ??? + + implicit def lispListSemigroup[A]: Semigroup[LispList[A]] = ??? +} diff --git a/src/main/scala/instances/schrodinger/package.scala b/src/main/scala/instances/schrodinger/package.scala new file mode 100644 index 0000000..79d9c8c --- /dev/null +++ b/src/main/scala/instances/schrodinger/package.scala @@ -0,0 +1,24 @@ +package instances + +import data._ +import typeclasses._ + +/** + * Typeclass instances for Schrodinger + */ +package object schrodinger { + + implicit val schrodingerFunctor: Functor[Schrodinger[*]] = ??? + + implicit val schrodingerApplicative: Applicative[Schrodinger[*]] = ??? + + implicit def schrodingerApplicativeError[E]: ApplicativeError[Schrodinger[*], E] = ??? + + implicit val schrodingerMonad: Monad[Schrodinger[*]] = ??? + + implicit def schrodingerMonadError[E]: MonadError[Schrodinger[*], E] = ??? + + implicit def schrodingerMonoid[A]: Monoid[Schrodinger[A]] = ??? + + implicit def schrodingerSemigroup[A]: Semigroup[Schrodinger[A]] = ??? +} diff --git a/src/main/scala/instances/state/package.scala b/src/main/scala/instances/state/package.scala new file mode 100644 index 0000000..315295e --- /dev/null +++ b/src/main/scala/instances/state/package.scala @@ -0,0 +1,20 @@ +package instances + +import data._ +import typeclasses._ + +/** + * Typeclass instances for State + */ +package object state { + + implicit def stateFunctor[S]: Functor[State[S, *]] = ??? + + implicit def stateApplicative[S]: Applicative[State[S, *]] = ??? + + implicit def stateApplicativeError[S, E]: ApplicativeError[State[S, *], E] = ??? + + implicit def stateMonad[S]: Monad[State[S, *]] = ??? + + implicit def stateMonadError[S, A]: MonadError[State[S, *], S] = ??? +} diff --git a/src/main/scala/instances/superposition/package.scala b/src/main/scala/instances/superposition/package.scala new file mode 100644 index 0000000..ccaa90e --- /dev/null +++ b/src/main/scala/instances/superposition/package.scala @@ -0,0 +1,24 @@ +package instances + +import data._ +import typeclasses._ + +/** + * Typeclass instances for Superposition + */ +package object superposition { + + implicit def superpositionFunctor[A]: Functor[Superposition[A, *]] = ??? + + implicit def superpositionApplicative[A]: Applicative[Superposition[A, *]] = ??? + + implicit def superpositionApplicativeError[A]: ApplicativeError[Superposition[A, *], A] = ??? + + implicit def superpositionMonad[A]: Monad[Superposition[A, *]] = ??? + + implicit def superpositionMonadError[A]: MonadError[Superposition[A, *], A] = ??? + + implicit def superpositionMonoid[A, B]: Monoid[Superposition[A, B]] = ??? + + implicit def superpositionSemigroup[A, B]: Semigroup[Superposition[A, B]] = ??? +} diff --git a/src/main/scala/ops/identity/package.scala b/src/main/scala/ops/identity/package.scala new file mode 100644 index 0000000..d888422 --- /dev/null +++ b/src/main/scala/ops/identity/package.scala @@ -0,0 +1,8 @@ +package ops + +/** + * Typeclass extension methods for Identity + */ +package object identity { + +} diff --git a/src/main/scala/ops/lisplist/package.scala b/src/main/scala/ops/lisplist/package.scala new file mode 100644 index 0000000..f98c3a1 --- /dev/null +++ b/src/main/scala/ops/lisplist/package.scala @@ -0,0 +1,8 @@ +package ops + +/** + * Typeclass extension methods for LispList + */ +package object lisplist { + +} diff --git a/src/main/scala/ops/schrodinger/package.scala b/src/main/scala/ops/schrodinger/package.scala new file mode 100644 index 0000000..8fb8a8e --- /dev/null +++ b/src/main/scala/ops/schrodinger/package.scala @@ -0,0 +1,8 @@ +package ops + +/** + * Typeclass extension methods for Schrodinger. + */ +package object schrodinger { + +} diff --git a/src/main/scala/ops/state/package.scala b/src/main/scala/ops/state/package.scala new file mode 100644 index 0000000..266afff --- /dev/null +++ b/src/main/scala/ops/state/package.scala @@ -0,0 +1,8 @@ +package ops + +/** + * Typeclass extension methods for State. + */ +package object state { + +} diff --git a/src/main/scala/ops/superposition/package.scala b/src/main/scala/ops/superposition/package.scala new file mode 100644 index 0000000..ee4e637 --- /dev/null +++ b/src/main/scala/ops/superposition/package.scala @@ -0,0 +1,8 @@ +package ops + +/** + * Typeclass extension methods for Superposition. + */ +package object superposition { + +} diff --git a/src/main/scala/typeclasses/Applicative.scala b/src/main/scala/typeclasses/Applicative.scala new file mode 100644 index 0000000..9265046 --- /dev/null +++ b/src/main/scala/typeclasses/Applicative.scala @@ -0,0 +1,24 @@ +package typeclasses + +/** + * Defines the Applicative operations for any type of kind F[_]. Applicative + * is a special case of Functor that can apply a function lifted into F[_] to + * a value also lifted into F[_]. + */ +trait Applicative[F[_]] extends Functor[F] { + + /** + * Lift a value into F[_]. + */ + def pure[A](a: A): F[A] = ??? + + /** + * Lift a value into F[_] and apply it to a lifted function. + */ + def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] + + /** + * This can be defined in terms of ap and pure alone, try it out! + */ + override def map[A, B](fa: F[A])(f: A => B): F[B] = ??? +} diff --git a/src/main/scala/typeclasses/ApplicativeError.scala b/src/main/scala/typeclasses/ApplicativeError.scala new file mode 100644 index 0000000..5d5e964 --- /dev/null +++ b/src/main/scala/typeclasses/ApplicativeError.scala @@ -0,0 +1,25 @@ +package typeclasses + +/** + * Defines the ApplicativeError operations for any type of kind F[_] for error + * type E. ApplicativeError is a special case of Applicative where the type + * kind of F[_] may represent a superposition of "success" or "error" cases + * and affords operations to create and recover these error cases. + */ +trait ApplicativeError[F[_], E] extends Applicative[F] { + + /** + * Lift an error handler into F[_] to apply to E. + */ + def handleError[A](fa: F[A])(f: E => A): F[A] + + /** + * Lift an error handler into F[_] to apply to E, returning a lifted result. + */ + def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] + + /** + * A special case of pure() which lifts an error E into F[_]. + */ + def raiseError[A](e: E): F[A] = ??? +} diff --git a/src/main/scala/typeclasses/Functor.scala b/src/main/scala/typeclasses/Functor.scala new file mode 100644 index 0000000..11a7e1c --- /dev/null +++ b/src/main/scala/typeclasses/Functor.scala @@ -0,0 +1,13 @@ +package typeclasses + +/** + * Defines the Functor operations for any type of kind F[_]. Functors allow for + * a function to be applied to a value lifted into F[_]. + */ +trait Functor[F[_]] { + + /** + * Applies a lifted value to a function. + */ + def map[A, B](fa: F[A])(f: A => B): F[B] +} diff --git a/src/main/scala/typeclasses/Monad.scala b/src/main/scala/typeclasses/Monad.scala new file mode 100644 index 0000000..67fe55a --- /dev/null +++ b/src/main/scala/typeclasses/Monad.scala @@ -0,0 +1,17 @@ +package typeclasses + +/** + * Defines the Monad operations for any type of kind F[_]. A Monad is a special + * case of Applicative where the application of a function to a lifted value + * in F[_] may itself return a lifted value. This is especially powerful because + * the lifted result may represent a case against which no further operations + * may be performed, which can model short-circuiting on errors! + */ +trait Monad[F[_]] extends Applicative[F] { + + /** + * Lift an apply a function into F[_] and apply it to the value. The function + * returns its own lifted result. + */ + def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] +} diff --git a/src/main/scala/typeclasses/MonadError.scala b/src/main/scala/typeclasses/MonadError.scala new file mode 100644 index 0000000..c84023e --- /dev/null +++ b/src/main/scala/typeclasses/MonadError.scala @@ -0,0 +1,31 @@ +package typeclasses + +import data.Superposition + +/** + * Defines the MonadOperations operations for any type of kind F[_] for error + * type E. MonadError is a special case of ApplicativeError and Monad that + * further augments error handling operations. + */ +trait MonadError[F[_], E] extends Monad[F] with ApplicativeError[F, E] { + + /** + * Replaces A with E if the lifted A does not satisfy the predicate. + */ + def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] + + /** + * Replaces A with E by means of A if the lifted A does not satisfy the predicate. + */ + def ensureOr[A](fa: F[A])(error: A => E)(predicate: A => Boolean): F[A] + + /** + * Applies a partial function to the E, if any. + */ + def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] + + /** + * Inverse of ApplicativeError attempt(). + */ + def rethrow[A, EE <: E](fa: F[Superposition[EE, A]]): F[A] +} diff --git a/src/main/scala/typeclasses/Monoid.scala b/src/main/scala/typeclasses/Monoid.scala new file mode 100644 index 0000000..c82b8fc --- /dev/null +++ b/src/main/scala/typeclasses/Monoid.scala @@ -0,0 +1,16 @@ +package typeclasses + +/** + * A Monoid is a special case of a Semigroup for which an element of A, when + * applied to any other element of A in any order, returns that other element. + * It forms an identity value with which its application merely returns the + * identity of its associated operand, similar in concept to the identity + * function. + */ +trait Monoid[A] extends Semigroup[A] { + + /** + * The identity of A. + */ + def empty: A +} diff --git a/src/main/scala/typeclasses/Semigroup.scala b/src/main/scala/typeclasses/Semigroup.scala new file mode 100644 index 0000000..039d8ec --- /dev/null +++ b/src/main/scala/typeclasses/Semigroup.scala @@ -0,0 +1,15 @@ +package typeclasses + +/** + * A semigroup is a special case of a binary operation that is both associative + * and for any two A the operation returns another member of A. In a sense, the + * Semigroup operation can be used from any two starting members of A to produce + * all successive members of A. + */ +trait Semigroup[A] { + + /** + * Binary, associative operation against two of A to produce another A. + */ + def <>(x: A, b: A): A +} diff --git a/src/test/scala/extensions/MapSpec.scala b/src/test/scala/extensions/MapSpec.scala new file mode 100644 index 0000000..9fad410 --- /dev/null +++ b/src/test/scala/extensions/MapSpec.scala @@ -0,0 +1,52 @@ +package extensions + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class MapSpec extends AnyWordSpecLike with Matchers { + "ConsList" can { + "map" which { + "transforms every element it contains" in { + pending + } + } + } + + "Superposition" can { + "map" which { + "ignores the value of Downside" in { + pending + } + "transforms the value of Upside" in { + pending + } + } + } + + "Schrodinger" can { + "map" which { + "ignores Dead cat" in { + pending + } + "transforms Alive cat" in { + pending + } + } + } + + "State" can { + "map" which { + "transforms the result of a stateful computation" in { + pending + } + } + } + + "Identity" can { + "map" which { + "transforms itself" in { + pending + } + } + } +}