2017-01-15 6 views
2

여기에서 튜토리얼을 수행하고 있습니다 (http://typelevel.org/cats/datatypes/freemonad.html). 키 값 저장소 앞에있는 캐시에서 작동하도록 수정하려고합니다. 이것이 내가 지금까지 생각해 왔지만 valueGetOperation과 컴파일러 오류가 발생했습니다. 내가 왜 컴파일 오류가 나는지 이해할 수 없다. 나는 그 문제를 해결하는 방법을 모른다. 무료 모나드를 사용할 때 조건부 동작에 대한 가장 좋은 방법은 무엇입니까? 당신이 for 이해에 알다시피무료 모나드를 사용한 조건부 동작

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

답변

4

, 당신은 if를 사용할 때 그것은 withFilter 메소드를 호출에 컴파일러에 의해 desugared되며,이 액세스 할 수없는 있다면 그것은 다시 filter 방법에 떨어진다. 구현되지 않으면 컴파일러 오류가 발생합니다.

그러나 간단히 ifelse을 사용할 수 있습니다! formar 하나 booleanValue에 따라 valueToReturn에 값을 할당합니다

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

:

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

또는 당신이 뭔가를 할 수 있습니다. 따라서 하나의 지점 만 해석됩니다. 나중에 두 값을 평가하고 valueToReturnOpt이 비어 있는지 여부에 따라 그 중 하나를 반환합니다.

개인적으로 내가 좋아하는 뭔가를 시도 할 것입니다 : 마테우스 '의 제안에 따라

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

감사합니다. if/else보다 더 "기능적인"방법을 원했지만 올바르게 작동합니다. – Mike

0

을, 이것이 내가 생각 해낸 것입니다 :

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을 구현하기위한 표준 구조가 있다면 그것에 대해 기쁘다.

+1

'withFallback'을'loadedValue.fold (fallBack) '로 정의했습니다. (v => Free.pure (Some (v)))' – AlecZorab

+0

고마워, 그게 내가 원하는 부분이다. – Mike

0

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 

주 스칼라 2.12에서 -Ypartial-unification 플래그는 KV 유형 별칭을 필요로하지 않으며, 당신이 OptionT(...) 대신 OptionT[KV, T](...) 쓸 수있다.