2017-01-15 4 views
2

Je suis le tutoriel ici: http://typelevel.org/cats/datatypes/freemonad.html et en essayant de le modifier pour travailler avec un cache en face de la banque de valeur de clé. C'est ce que j'ai trouvé jusqu'à présent, mais je reçois une erreur de compilation avec valueGetOperation. Je comprends pourquoi je reçois l'erreur de compilation, je ne comprends tout simplement pas comment contourner cela. Quelle est la meilleure pratique pour un comportement conditionnel lors de l'utilisation d'une monade gratuite?Comportement conditionnel avec monades libres

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) 
} 

Répondre

4

Comme vous le savez dans for compréhension, lorsque vous utilisez if il est désucré par le compilateur d'appeler la méthode withFilter, et si ce n'est pas accessible, il retombe à la méthode filter. Si elles ne sont pas implémentées, vous recevrez une erreur de compilation.

Cependant, vous pouvez simplement utiliser ifelse!

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

Sinon, vous pouvez faire quelque chose comme:

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

Celui formar attribue la valeur à valueToReturn selon booleanValue. En tant que tel, une seule branche sera interprétée. Ce dernier évaluera les deux valeurs et retournera l'une d'elles selon que le valueToReturnOpt sera vide ou non.

Personnellement, je voudrais essayer quelque chose comme:

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 
} 
+0

Merci, j'espérais un moyen plus «fonctionnel» que si/sinon, mais vous avez raison, ça marche. – Mike

0

Après suggestions « Mateusz, ce que je suis venu avec:

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 
} 

S'il y a une construction standard pour mettre en œuvre withFallback je serais heureux de savoir à ce sujet.

+1

Je définirais votre 'withFallback' comme' loadedValue.fold (fallBack) (v => Free.pure (Some (v))) ' – AlecZorab

+0

Merci, c'est exactement ce que je cherchais. – Mike

0

Vous pouvez également utiliser 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 

Ou 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 

Notez que le drapeau -Ypartial-unification à Scala 2.12 vous n'avez pas besoin de l'alias de type KV et vous pouvez écrire OptionT(...) au lieu de OptionT[KV, T](...).