-
-
Save EECOLOR/c9037d1c56c3c4b59c75 to your computer and use it in GitHub Desktop.
package p { | |
object Test { | |
import construction.Monadic | |
import execution.Bimonad | |
def main(args: Array[String]): Unit = { | |
val expression = Monadic[List](10) flatMap (1 to _ toList) coflatMap (_.sum) | |
val result = expression runWith ListBimonad | |
println(result + " <- " + expression) | |
// output: 55 <- CoflatMap(FlatMap(Pure(10),<function1>),<function1>) | |
} | |
private object ListBimonad extends Bimonad.Of[List] { | |
def fmap[A, B] = _ map _ | |
def join[A] = _.flatten | |
def pure[A] = List(_) | |
def cojoin[A] = List(_) | |
def copure[A] = _.head | |
} | |
} | |
package execution { | |
trait `Some name in the domain` { type F[_] } | |
trait Functor extends `Some name in the domain` { def fmap[A, B]: (F[A], (A => B)) => F[B] } | |
trait Copointed extends Functor { def copure[A]: F[A] => A } | |
trait Comonad extends Copointed { def cojoin[A]: F[A] => F[F[A]] } | |
trait Pointed extends Functor { def pure[A]: A => F[A] } | |
trait Monad extends Pointed { def join[A]: F[F[A]] => F[A] } | |
trait Bimonad extends Monad with Comonad | |
object Bimonad { | |
type Of[G[_]] = `It should not be necessary to add this trait`[G] | |
trait `It should not be necessary to add this trait`[G[_]] extends Bimonad { | |
type F[x] = G[x] | |
} | |
} | |
} | |
package construction { | |
import execution.Bimonad | |
sealed trait Monadic[F[_], A] extends Monadic.Operations[F, A] | |
object Monadic { | |
def apply[F[_], A](fa: F[A]): Monadic[F, A] = Copure(fa) | |
def apply[F[_]]: Factory[F] = new Factory[F] | |
class Factory[F[_]] { | |
def apply[A](a: A): Monadic[F, A] = Pure[F, A](a) | |
} | |
private case class Pure[F[_], A](x: A) extends Monadic[F, A] | |
private case class Copure[F[_], A](x: F[A]) extends Monadic[F, A] | |
private case class Map[F[_], A, B](prev: Monadic[F, A], f: A => B) extends Monadic[F, B] | |
private case class FlatMap[F[_], A, B](prev: Monadic[F, A], f: A => F[B]) extends Monadic[F, B] | |
private case class CoflatMap[F[_], A, B](prev: Monadic[F, A], f: F[A] => B) extends Monadic[F, B] | |
trait Operations[F[_], A] { _: Monadic[F, A] => | |
private type Result[B] = Monadic[F, B] | |
def map[B](f: A => B): Result[B] = Map(this, f) | |
def flatMap[B](f: A => F[B]): Result[B] = FlatMap(this, f) | |
def coflatMap[B](f: F[A] => B): Result[B] = CoflatMap(this, f) | |
def runWith(implicit F: Bimonad.Of[F]): A = executed |> F.copure | |
private[Operations] def executed(implicit F: Bimonad.Of[F]): F[A] = { | |
def fmap[A, B]: (A => B) => F[A] => F[B] = f => fa => F.fmap(fa, f) | |
this match { | |
case Pure(x) => x |> F.pure | |
case Copure(x) => x | |
case Map(prev, f) => prev.executed |> fmap(f) | |
case FlatMap(prev, f) => prev.executed |> fmap(f) |> F.join | |
case CoflatMap(prev, f) => prev.executed |> F.cojoin |> fmap(f) | |
} | |
} | |
private implicit class ForwardPipe[A](x: A) { def |>[B](f: A => B): B = f(x) } | |
} | |
} | |
} | |
} |
I think most of the changes are great. I have no substantive disagreement with any of them given the premises under which you made them, but I will elaborate on a couple things:
Moved methods from implicit enhancement class to the correct type to improve discovery.
These days I have the luxury of writing code which is striving for "how we ought to be able to write it" and not "how we're stuck writing it." The fact that tooling tends to find the inherited methods and not the implicitly available ones is an artifact of tooling history and the unusability of the scala compiler except as a black box. I think using inheritance only for the minimum set of fundamental methods (and whatever allocated memory there may be) is the best way. It's much more difficult for accidental dependencies or unintentional privileging of internal details to sprout up when the core class is kept to the absolute minimum and the extension methods manipulate it.
So yes in "real life" complicating the inheritance hierarchy to assist discovery is usually necessary, but I'm done with babying scala that way.
Renamed implicit z to F to reduce mental translation.
This is a convention I've widely adopted in my code - it might seem less arbitrary given the wider context. The main thing is that I don't want to give it a name at all, but I am often forced to - so rather than wasting brain cells on the name of every implicit parameter I always call a single one z if I can.
I am convinced by your arguments and it's quite funny that you mention "I don't want to give it a name at all, but I am often forced to", I totally agree.
Moved methods from implicit enhancement class to the correct type to improve discovery.
Another reason was that by placing them in a trait helps readers of the source code could see that there are methods available. I however tend to lean to your side on this one.
👏
A summary of the changes:
run
torunWith
and explicitly passed in the argument to increase understanding.z
toF
to reduce mental translation.reduce
toexecuted
, I think another name would be better.executed
methodexecuted
a tiny bit more consistent