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?
F[A]
map: F[A] => (A => B) => F[B]
cannot execute further effects in the function simply because there are none there
A => F[B]
,which on more fundamental level, it's the general concept of a continuation when you represents effects in this style (programs as values)
Stream
you have evalMap: Stream[F, A] => (A => F[B) => Stream[F, B]
flatMap(a => Stream.eval(f(a))
flatMap
in strict side-effecting languages as well, if you squint
flatMap
@SystemFw I guess as a newbie I don't have the mental model. I've learned by rote that one must flatMap
but I can see why after years of mapping over Scala collections one might naively think this:
val printEffect: Int => IO[Unit] = x => IO { println(x) }
val printEach: Pipe[IO, Int, Unit] = { _.map(x => printEffect(x)) }
printEach(Stream(1, 2, 3, 4, 5)).compile.toList
prints out the numbers 1 to 5.
It is, of course, wrong. But why (other than "it just is") is currenlty eluding me.
Can you please expand on why A => F[B]
is a program and F[A] => (A => B) => F[B]
is not?
B