Created
December 19, 2020 20:20
-
-
Save AlexRogalskiy/bba259f5e9bcc74fab3409226856cd7e to your computer and use it in GitHub Desktop.
Scala reader monad mapping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.collection._ | |
import scala.collection.generic._ | |
import scala.concurrent.{ Future, ExecutionContext } | |
/** | |
* a Typeclass representing a class that can map and flatMap (collections, Option, Future..). | |
* effectively, it's a Monad without enforcing the axioms of a Monad. | |
*/ | |
trait CanMap[A, B, M[_]] { | |
def map(l: M[A])(f: A => B): M[B] | |
def flatMap(l: M[A])(f: A => M[B]): M[B] | |
def pure(v: B): M[B] | |
} | |
object CanMap { | |
/** | |
* the typeclass for Option | |
*/ | |
implicit def canmapopt[A, B] = new CanMap[A, B, Option] { | |
def map(l: Option[A])(f: A => B): Option[B] = l.map(f) | |
def flatMap(l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f) | |
def pure(v: B): Option[B] = Some(v) | |
} | |
/** | |
* the typeclass for anything that can be transformed to a TraversableLike (almost all the collections) | |
*/ | |
implicit def canmaptrav[A, B, M[+_]](implicit bf: CanBuildFrom[M[A], B, M[B]], ev: M[A] => TraversableLike[A, M[A]], eb: M[B] => TraversableLike[B, M[B]]) = new CanMap[A, B, M] { | |
def map(l: M[A])(f: (A) => B): M[B] = l.map(f) | |
def flatMap(l: M[A])(f: A => M[B]): M[B] = l.flatMap[B, M[B]] { (a: A) => | |
f(a) | |
} | |
def pure(v: B): M[B] = { | |
val build = bf() | |
build += v | |
build.result | |
} | |
} | |
/** | |
* the typeclass for Future | |
*/ | |
implicit def canmapfuture[A, B](implicit ex: ExecutionContext) = new CanMap[A, B, Future] { | |
def map(l: Future[A])(f: A => B): Future[B] = l.map(f) | |
def flatMap(l: Future[A])(f: A => Future[B]): Future[B] = l.flatMap(f) | |
def pure(v: B): Future[B] = Future.successful(v) | |
} | |
} | |
/** | |
* A monad to abstract dependencies in the code, see https://coderwall.com/p/kh_z5g | |
*/ | |
object Reader { | |
/** | |
* an implicit to convert a function A => B in a Reader[A, B] | |
*/ | |
implicit def reader[C, R](block: C => R): Reader[C, R] = Reader(block) | |
/** | |
* create a reader from an already resolved result | |
*/ | |
def pure[C, A](a: A) = Reader((c: C) => a) | |
/** | |
* transform a sequence of Reader in a Reader of a sequence. I.E. move the Reader out of each item and keep it over the list. | |
*/ | |
def sequence[C, R, D[_]](list: D[Reader[C, R]])(implicit canMap: CanMap[Reader[C, R], R, D]): Reader[C, D[R]] = reader { conn => | |
canMap.map(list) { r: Reader[C, R] => | |
r(conn) | |
} | |
} | |
/** | |
* provide a connection to read the content of a reader | |
*/ | |
def withConnection[C, R](connection: C)(reader: Reader[C, R]): R = reader(connection) | |
} | |
/** | |
* the Reader Monad in itself. run is the wrapped function. C is the argument type, A is the return type. | |
* C is contravariant, so that we can have: | |
* | |
* trait Connection | |
* class RealConnection extends Connection | |
* | |
* and have Reader[Connection, _] as a subtype of Reader[RealConnection, _] so that the we can use a RealConnection to run a Reader[Connection,_] | |
*/ | |
case class Reader[-C, +A](run: C => A) { | |
/** | |
* shortcut to apply the wrapped function | |
*/ | |
def apply(c: C) = run(c) | |
/** | |
* map a reader's return type to a new return type | |
*/ | |
def map[B](f: A => B): Reader[C, B] = | |
Reader(c => f(run(c))) | |
/** | |
* flatMap to merge two readers. The type of connection of the second reader has to be a subtype of this reader's connection | |
* to be able to use the same connection on both readers. | |
*/ | |
def flatMap[B, D <: C](f: A => Reader[D, B]): Reader[D, B] = | |
Reader(c => f(run(c))(c)) | |
/** | |
* Combine two readers | |
*/ | |
def zip[B, D <: C](other: Reader[D, B]): Reader[D, (A, B)] = | |
this.flatMap { a => | |
other.map { b => (a, b) } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment