Mike Mike - 1 year ago 73
Scala Question

Conditional Behavior With Free Monads

I'm following the tutorial here: http://typelevel.org/cats/datatypes/freemonad.html and trying to modify it to work with a cache in front of the key value store. This is what I've come up with so far but I'm getting a compiler error with

valueGetOperation
. I understand why I get the compile error, I just don't understand how to work around it. What's the best practice for conditional behavior when using a free monad?

import cats.data.Coproduct
import cats.free.{Free, Inject}

object KvStore {
sealed trait KvOp[A]
case class Get[T](key: String) extends KvOp[Option[T]]
case class Put[T](key: String, value: T) extends KvOp[Unit]
case class Delete[T](key: String) extends KvOp[Unit]
}

object CacheStore {
sealed trait CacheOp[A]
case class Get[T](key: String) extends CacheOp[Option[T]]
case class Put[T](key: String, value: T) extends CacheOp[Unit]
case class Delete[T](key: String) extends CacheOp[Unit]
}

type WriteThruCache[A] = Coproduct[KvStore.KvOp, CacheStore.CacheOp, A]

class KvOps[F[_]](implicit I: Inject[KvStore.KvOp, F]) {
import KvStore._
def get[T](key: String): Free[F, Option[T]] = Free.inject[KvOp, F](Get(key))
def put[T](key: String, value: T): Free[F, Unit] = Free.inject[KvOp, F](Put(key, value))
def delete[T](key: String): Free[F, Unit] = Free.inject[KvOp, F](Delete(key))
}

object KvOps {
implicit def kvOps[F[_]](implicit I: Inject[KvStore.KvOp, F]): KvOps[F] = new KvOps[F]
}

class CacheOps[F[_]](implicit I: Inject[CacheStore.CacheOp, F]) {
import CacheStore._
def get[T](key: String): Free[F, Option[T]] = Free.inject[CacheOp, F](Get(key))
def put[T](key: String, value: T): Free[F, Unit] = Free.inject[CacheOp, F](Put(key, value))
def delete[T](key: String): Free[F, Unit] = Free.inject[CacheOp, F](Delete(key))
}

object CacheOps {
implicit def cacheOps[F[_]](implicit I: Inject[CacheStore.CacheOp, F]): CacheOps[F] = new CacheOps[F]
}

def valueWriteOperation[T](implicit Kv: KvOps[WriteThruCache], Cache: CacheOps[WriteThruCache]): ((String, T) => Free[WriteThruCache, Unit]) = {
(key: String, value: T) =>
for {
_ <- Kv.put(key, value)
_ <- Cache.put(key, value)
} yield ()
}

// This is where I'm stuck
// desired behavior: If the value isn't in the cache, load it from the kv store and put it in the cache
def valueGetOperation[T](implicit Kv: KvOps[WriteThruCache], Cache: CacheOps[WriteThruCache]): ((String) => Free[WriteThruCache, Option[T]]) = {
(key: String) =>
for {
cacheOption <- Cache.get[T](key)
kvOption <- Kv.get[T](key) if cacheOption.isEmpty // value withFilter is not a member of cats.free.Free[A$A39.this.WriteThruCache,Option[T]]
} yield cacheOption.orElse(kvOption)
}

Answer Source

As you know in for comprehension, when you use if it is desugared by compiler to calling withFilter method, and if it's not accessible it falls back to filter method. If they are not implemented you will receive compiler error.

However you can simply use if else!

for {
  booleanValue <- myfreeAlbebra.checkCondidtion(arg1, arg2)
  valueToReturn <- if (booleanValue) {
    myfreeAlbebra.someValue
  } else {
    myfreeAlbebra.someOtherValue
  }
} yield valueToReturn

alternatively you can do something like:

for {
  booleanValue  <- myfreeAlbebra.checkCondidtion(arg1, arg2)
  valueToReturnOpt <- myfreeAlbebra.someValue
  fallbackValue <- myfreeAlbebra.someOtherValue
} yield valueToReturnOpt.getOrElse(fallbackValue)

The formar one will assign value to valueToReturn depending on booleanValue. As such only one branch will be interpreted. The later will evaluate both values and return one of them depending on whether or not valueToReturnOpt will be empty.

Personally I would try something like:

def valueGetOperation[T](implicit Kv: KvOps[WriteThruCache], Cache: CacheOps[WriteThruCache]): ((String) => Free[WriteThruCache, Option[T]]) = {
  (key: String) =>
    for {
      cacheOption <- Cache.get[T](key)
      returnedValue <- if (cacheOption.isEmpty) Cache.get[T](key) else Kv.get[T](key)
    } yield returnedValue
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download