I think the main two issues are 1) how hard it is to encode in types 2) how hard it is to observe (and test) on the JVM.
Observing and testing it is a huge pain. It can be done relatively easily in the types though: https://github.com/typelevel/cats-effect/blob/ce3/core/src/main/scala/cats/effect/Async.scala#L26-L32 That's not a hard and fast guarantee, but at least the error-prone bit is on the implementor's shoulders, not on the user's.
ev
, whatever that happens to be"
trait Environment[F[_]] {
def get(v: String) : F[Option[String]]
}
object Environment {
def apply[F[_]](implicit ev: Environment[F]): ev.type = ev
def impl[F[_]: Sync]: Environment[F] = new Environment[F] {
override def get(v: String): F[Option[String]] =
Sync[F].delay(sys.env.get(v))
}
}
Effect
which lets you compile an F to an IO
trait Foo[F[_]]{
def foo: F[Int]
}
object Foo{
def mapK[F[_], G[_]](base: Foo[F], fk: F ~> G): Foo[G] =new MapKFoo(base, fk)
private class MapKFoo[F[_], G[_]](base: Foo[F], fk: F ~> G) extends Foo[G] {
def foo: G[Int] = fk(base.foo)
}
}
@cosmir17, I'd replace a mutable Map
with a Ref
containing the current state as an immutable Map
. But as you said, the whole thing does look messy. Idiomatic FP Scala code tends to emphasize operations over pure data types (case classes and the like) in favor of OOP-style encapsulation of mutable data. So you'd have something like
final case class Board(rowCount: Int, columnCount: Int, grid: Map[Point, GoString[_ <: Player]])
// Some error type you can properly report
final case class IllegalMove(...)
def placeStone[Color <: Player](point: Point, board: Board): Either[InvalidMove, (Board, NeighbourInfo[Color])]
// Same thing, but might be easier to use.
def placeStone[Color <: Player](point: Point): StateT[Either[InvalidMove, *], Board, NeighbourInfo[Color]]
I've gotten rid of parameterization by F
here, since it doesn't seem to be necessary.
By the way, parameterizing stuff with Player
subtypes looks contrived. It doesn't seem to do anything yet, and it makes expressing certain things difficult. For example, adjacentOppositeColor
should actually be a Set[GoString[<opposite of Color>]]
, but we can't conveniently express that.
@cosmir17,
placeStone operation seems complicated(multiple fold and etc) and will take a long time (around 300ms?).
Sync.delay
is intended to capture side effects. In your original example mutating grid
would, indeed, be a side effect. However a CPU-intensive expression is generally not considered a side effect, so you don't have to delay
it. If you want to have a tight control over when it gets evaluated, you should look at Eval
in Cats instead.
I am doing it as a practice for Cats effect
In my experience, while tagless final approach (parameterizing with F[_]
) seems neat, it tends to also introduce unnecessary complexity. Personally, I try to go with concrete types (Either
, IO
etc.) first, unless I know I will have to mix domains that require different monad capabilities.
My MapRef implementation does.
@ChristopherDavenport Thank you, I shall read your test class.
flatMap
effects to make them "happen".flatMap
? Why can't I, say,. map over an fs2.Stream
of effects and have them "executed"? Is the choice of flatMap
arbitrary or are there mathematical laws at play here? If so, what are these laws?