Project setup

This commit is contained in:
Logan McGrath 2021-08-12 09:40:42 -07:00
commit d59fbba056
39 changed files with 611 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.bsp/
.idea/
target/
project/target/

2
.scalafmt.conf Normal file
View File

@ -0,0 +1,2 @@
version = 2.5.0
maxColumn = 120

16
build.sbt Normal file
View File

@ -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

1
project/build.properties Normal file
View File

@ -0,0 +1 @@
sbt.version=1.5.5

View File

@ -0,0 +1,6 @@
package conversions
/**
* Implicit conversions for Identity.
*/
package object identity {}

View File

@ -0,0 +1,6 @@
package conversions
/**
* Implicit conversions for LispList.
*/
package object lisplist {}

View File

@ -0,0 +1,6 @@
package conversions
/**
* Implicit conversions for Schrodinger.
*/
package object schrodinger {}

View File

@ -0,0 +1,6 @@
package conversions
/**
* Implicit conversions for State.
*/
package object state {}

View File

@ -0,0 +1,6 @@
package conversions
/**
* Implicit conversions for Superposition.
*/
package object superposition {}

View File

@ -0,0 +1,33 @@
/**
* DO NOT MODIFY THIS FILE. All exercises are performed using implicits only.
*/
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 Cons[+A](car: A, cdr: LispList[A]) extends LispList[A]
case object Nil 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](items: A*): LispList[A] = items.foldRight(LispList[A]())(Cons[A])
def apply[A](): LispList[A] = Nil
}

View File

@ -0,0 +1,27 @@
/**
* DO NOT MODIFY THIS FILE. All exercises are performed using implicits only.
*/
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
}
/**
* The cat is here for pets and he is fluffy.
*/
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!")
}

View File

@ -0,0 +1,33 @@
/**
* DO NOT MODIFY THIS FILE. All exercises are performed using implicits only.
*/
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 but not both.
*/
sealed trait Superposition[+A, +B] {
def downside: A
def upside: B
}
/**
* Implied negative case of the Superposition. This is probably not the one you
* want.
*/
case class Downside[+A, +B](downside: A) extends Superposition[A, B] {
def upside = throw new Exception("I got the downside!")
}
/**
* Implied positive case of the Superposition. This is usually the one you want.
*/
case class Upside[+A, +B](upside: B) extends Superposition[A, B] {
def downside = throw new Exception("I got the upside!")
}

View File

@ -0,0 +1,21 @@
/**
* DO NOT MODIFY THIS FILE. All exercises are performed using implicits only.
*/
package object data {
/**
* A structure for any type S for which a function exists that derives from
* S a product of a value of itself and a value of type A. This can model
* stateful computation where state S is given to a function which produces
* a potentially-changed state and the result of the computation.
*
* @tparam S The state type
* @tparam A The type returned by the operation
*/
type State[S, +A] = S => (S, A)
/**
* A special case of any type A that is simply itself.
*/
type Identity[+A] = A
}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Applicative operations.
*/
package object applicative {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for ApplicativeError operations.
*/
package object applicativeError {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Functor operations.
*/
package object functor {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Identity.
*/
package object identity {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for LispList.
*/
package object lisplist {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Monad operations.
*/
package object monad {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for MonadError operations.
*/
package object monadError {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Monoid operations.
*/
package object monoid {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Schrodinger.
*/
package object schrodinger {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Semigroup operations.
*/
package object semigroup {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for State.
*/
package object state {}

View File

@ -0,0 +1,6 @@
package extensions
/**
* Extension methods for Superposition.
*/
package object superposition {}

View File

@ -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]] = ???
}

View File

@ -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]] = ???
}

View File

@ -0,0 +1,25 @@
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]] = ???
}

View File

@ -0,0 +1,21 @@
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] = ???
}

View File

@ -0,0 +1,28 @@
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]] =
???
}

View File

@ -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] = ???
}

View File

@ -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] = ???
}

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,66 @@
package conversions
import data.{Cons, Nil}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import java.io.ByteArrayOutputStream
class LispListSpec extends AnyWordSpecLike with Matchers {
"LispList" can {
/**
* Exercise: Implicit Conversions
*/
"convert types" which {
val lispList = Cons(1, Cons(2, Cons(3, Nil)))
def lengthAsList[A](list: List[A]): Int = list.length
def lengthAsSeq[A](seq: Seq[A]): Int = seq.length
"become a List" in {
pending
//lengthAsList(lispList) should eq(3)
}
"become a Seq" in {
pending
//lengthAsSeq(lispList) should eq(3)
}
"throw an exception if getting length as a Seq but actually is a List" in {
pending
//lengthAsSeq(lispList) should eq(3)
}
"become a Seq, but loudly" in {
val out = new ByteArrayOutputStream()
Console.withOut(out) {
pending
//lengthAsSeq(lispList) should eq(3)
}
out.toString should contain("THREE OF THEM!")
}
}
/**
* Exercise: Explicit conversions with extension methods
*/
"explicitly convert types" which {
val lispList = Cons(1, Cons(2, Cons(3, Nil)))
"asList" can {
"explicitly become a List" in {
pending
//lispList.asList should eq(List(1, 2, 3))
}
}
"asSeq" can {
"explicitly become a Seq" in {
pending
//lispList.asList should eq(Seq(1, 2, 3))
}
}
}
}
}

View File

@ -0,0 +1,47 @@
package extensions
import data.{Cons, Nil}
import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class LispListSpec extends AnyWordSpecLike with Matchers {
"LispList" can {
/**
* Exercise: Create a length method
*/
"length" which {
"returns 0 if the list is Nil" in {
val lispList = Nil
pending
//lispList.length should eq(0)
}
"returns the length of the list" in {
val lispList = Cons(1, Cons(2, Cons(3, Nil)))
pending
//lispList.length should eq(3)
}
}
/**
* Exercise: Create a schrodingersCar method
*/
"schrodingersCar" which {
"returns Dead" when {
"list is Nil" in {
val lispList = Nil
pending
//lispList.schrodingersCar should eq(Dead)
}
}
"returns Alive containing the first value" when {
"the list is not Nil" in {
val lispList = Cons("Ferrari", Cons("Lamborghini", Cons("Honda Fit", Nil)))
pending
//lispList.schrodingersCar should eq(Alive("Ferrari"))
}
}
}
}
}