-
-
Save EECOLOR/c9037d1c56c3c4b59c75 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
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 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.
👏
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
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.
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.