自由 Monad 的条件行为
Conditional Behavior With Free Monads
我正在按照此处的教程进行操作:http://typelevel.org/cats/datatypes/freemonad.html 并尝试修改它以使用键值存储前面的缓存。到目前为止,这是我想出的,但我遇到 valueGetOperation
的编译器错误。我明白为什么会出现编译错误,我只是不明白如何解决它。使用免费 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)
}
正如您在 for
理解中所知道的,当您使用 if
时,编译器会对其进行去糖处理以调用 withFilter
方法,如果它不可访问,则会回退到 filter
方法。如果它们未实现,您将收到编译器错误。
但是您可以简单地使用 if
else
!
for {
booleanValue <- myfreeAlbebra.checkCondidtion(arg1, arg2)
valueToReturn <- if (booleanValue) {
myfreeAlbebra.someValue
} else {
myfreeAlbebra.someOtherValue
}
} yield valueToReturn
或者你可以这样做:
for {
booleanValue <- myfreeAlbebra.checkCondidtion(arg1, arg2)
valueToReturnOpt <- myfreeAlbebra.someValue
fallbackValue <- myfreeAlbebra.someOtherValue
} yield valueToReturnOpt.getOrElse(fallbackValue)
公式会根据booleanValue
给valueToReturn
赋值。因此,只会解释一个分支。后者将根据 valueToReturnOpt
是否为空来评估两个值和 return 其中之一。
就我个人而言,我会尝试类似的方法:
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
}
根据 Mateusz 的建议,这是我想出的:
def withFallback[A[_], T](loadedValue: Option[T], fallback: => Free[A, Option[T]]): Free[A, Option[T]] = {
if(loadedValue.isDefined) {
Free.pure[A, Option[T]](loadedValue)
} else {
fallback
}
}
def valueGetOperation[T](implicit Kv: KvOps[WriteThruCache], Cache: CacheOps[WriteThruCache]): ((String) => Free[WriteThruCache, Option[T]]) = {
(key: String) =>
for {
cachedOption <- Cache.get[T](key)
actualValue <- withFallback[WriteThruCache, T](cachedOption, fallback = Kv.get[T](key))
} yield actualValue
}
如果有一个标准的构造来实现 withFallback,我很乐意知道。
您也可以使用 OptionT#orElse
.
import cats.data.OptionT
type KV[A] = Free[WriteThruCache, A]
def valueGetOperation[T](
implicit
Kv: KvOps[WriteThruCache],
Cache: CacheOps[WriteThruCache]
): String => KV[Option[T]] =
key => OptionT[KV, T](Cache.get[T](key)).orElse(OptionT[KV, T](Kv.get[T](key))).value
或OptionT#orElseF
:
def valueGetOperation[T](
implicit
Kv: KvOps[WriteThruCache],
Cache: CacheOps[WriteThruCache]
): String => KV[Option[T]] =
key => OptionT[KV, T](Cache.get[T](key)).orElseF(Kv.get[T](key)).value
请注意,使用 Scala 2.12 中的 -Ypartial-unification
标志,您不需要 KV
类型别名,您可以编写 OptionT(...)
而不是 OptionT[KV, T](...)
。
我正在按照此处的教程进行操作:http://typelevel.org/cats/datatypes/freemonad.html 并尝试修改它以使用键值存储前面的缓存。到目前为止,这是我想出的,但我遇到 valueGetOperation
的编译器错误。我明白为什么会出现编译错误,我只是不明白如何解决它。使用免费 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)
}
正如您在 for
理解中所知道的,当您使用 if
时,编译器会对其进行去糖处理以调用 withFilter
方法,如果它不可访问,则会回退到 filter
方法。如果它们未实现,您将收到编译器错误。
但是您可以简单地使用 if
else
!
for {
booleanValue <- myfreeAlbebra.checkCondidtion(arg1, arg2)
valueToReturn <- if (booleanValue) {
myfreeAlbebra.someValue
} else {
myfreeAlbebra.someOtherValue
}
} yield valueToReturn
或者你可以这样做:
for {
booleanValue <- myfreeAlbebra.checkCondidtion(arg1, arg2)
valueToReturnOpt <- myfreeAlbebra.someValue
fallbackValue <- myfreeAlbebra.someOtherValue
} yield valueToReturnOpt.getOrElse(fallbackValue)
公式会根据booleanValue
给valueToReturn
赋值。因此,只会解释一个分支。后者将根据 valueToReturnOpt
是否为空来评估两个值和 return 其中之一。
就我个人而言,我会尝试类似的方法:
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
}
根据 Mateusz 的建议,这是我想出的:
def withFallback[A[_], T](loadedValue: Option[T], fallback: => Free[A, Option[T]]): Free[A, Option[T]] = {
if(loadedValue.isDefined) {
Free.pure[A, Option[T]](loadedValue)
} else {
fallback
}
}
def valueGetOperation[T](implicit Kv: KvOps[WriteThruCache], Cache: CacheOps[WriteThruCache]): ((String) => Free[WriteThruCache, Option[T]]) = {
(key: String) =>
for {
cachedOption <- Cache.get[T](key)
actualValue <- withFallback[WriteThruCache, T](cachedOption, fallback = Kv.get[T](key))
} yield actualValue
}
如果有一个标准的构造来实现 withFallback,我很乐意知道。
您也可以使用 OptionT#orElse
.
import cats.data.OptionT
type KV[A] = Free[WriteThruCache, A]
def valueGetOperation[T](
implicit
Kv: KvOps[WriteThruCache],
Cache: CacheOps[WriteThruCache]
): String => KV[Option[T]] =
key => OptionT[KV, T](Cache.get[T](key)).orElse(OptionT[KV, T](Kv.get[T](key))).value
或OptionT#orElseF
:
def valueGetOperation[T](
implicit
Kv: KvOps[WriteThruCache],
Cache: CacheOps[WriteThruCache]
): String => KV[Option[T]] =
key => OptionT[KV, T](Cache.get[T](key)).orElseF(Kv.get[T](key)).value
请注意,使用 Scala 2.12 中的 -Ypartial-unification
标志,您不需要 KV
类型别名,您可以编写 OptionT(...)
而不是 OptionT[KV, T](...)
。