2017-05-11 3 views
1

Ich spiele mit Cats und Free Monads und ich habe eine Spielzeug REST Service Algebra und ein "Programm" namens ensureOneProduct geschrieben. Leider hat ensureOneProduct mehr Kesselplattencode, als ich gerne sehen würde. Gibt es eine bessere Möglichkeit, die ensureOneProduct Methode unten zu schreiben? Oder habe ich gerade verwöhnt von Haskell's Do Notation? Vielen Dank!Handling Bedingungen und kostenlose Monaden in Scala

import cats.free.Free 
import cats.free.Free.liftF 
import cats.{Id, ~>} 

object Algebra3 { 

    type Url = String 

    /** 
    * The REST Service Algebra 
    */ 
    sealed trait Service[+A] 
    case class Get[T](url: Url) extends Service[Option[T]] 
    case class Put[T](url: Url, rep: T) extends Service[T] 
    case class Post[T](url: Url, rep: T) extends Service[Option[Url]] 
    case class Delete(url: Url) extends Service[Unit] 

    // A Free REST Service 
    type ServiceF[A] = Free[Service, A] 

    // The Product resource 
    case class Product(name: String, quantity: Int) 

    /** 
    * Bad example of REST but I'm focusing on learning about Free Monads. 
    */ 
    def ensureOneProduct[T](url: Url, rep: T): ServiceF[Url] = { 
    for { 
    // Attempt to retrieve the product... 
     res <- get[Product](url) 
     _ <- if (res.isDefined) 
     for { 
     // The product existed so delete it. 
      _ <- delete(url) 
      // Now create the product 
      _ <- put(url, rep) 
     } yield() 
     else { 
     // The product did not exist so create it. 
     put(url, rep) 
     } 
    } yield url 
    } 

    def get[T](url: Url): ServiceF[Option[T]] = liftF[Service, Option[T]](Get[T](url)) 
    def put[T](url: Url, rep: T): ServiceF[T] = liftF[Service, T](Put[T](url, rep)) 
    def post[T](url: Url, value: T): ServiceF[Option[Url]] = liftF[Service, Option[Url]](Post[T](url, value)) 
    def delete(key: String): ServiceF[Unit] = liftF(Delete(key)) 

    def defaultCompiler: Service ~> Id = 
    new (Service ~> Id) { 
     def apply[A](fa: Service[A]): Id[A] = 
     fa match { 
      case Get(key) => 
      println(s"GET($key)") 
      Some(new Product("Hat", 3)) 
      case Put(key, rep) => 
      println(s"PUT($key, $rep)") 
      rep 
      case Post(url, rep) => 
      println(s"POST($url)") 
      Some(url) 
      case Delete(key) => 
      println(s"DELETE($key)") 
      () 
     } 
    } 

    def main(args: Array[String]) = { 
    val url = "https://www.example.com/api/v1/hats/1024" 
    val product = new Product("Hat", 1) 

    println(ensureOneProduct(url, product).foldMap(defaultCompiler)) 
    } 
} 

Dieser Code druckt:

GET(https://www.example.com/api/v1/hats/1024) 
DELETE(https://www.example.com/api/v1/hats/1024) 
PUT(https://www.example.com/api/v1/hats/1024, Product(Hat,1)) 
https://www.example.com/api/v1/hats/1024 

Es war interessant und ein wenig über das, wenn ich die verschachtelten delete und put Anrufe in eine für den Ausdruck zu umschließen vergessen, kompiliert es aber laufen nicht die delete Betrieb. Es macht Sinn, warum der Aufruf delete weggelassen wurde, aber ich würde lieber eine Art Kompilierzeit-Feedback bekommen.

Antwort

3

Sie könnten in der Lage sein, die innere für das Verständnis verschrotten die von (>>), gefolgt mit Operator:

for { 
    res <- get[Product](url)    // Attempt to retrieve the product... 
    _ <- if (res.isDefined) 
      delete(url) >> put(url, rep) // It exists: delete & recreate it 
     else 
      put(url, rep)     // It does not exist: create it 
} yield url 
+0

Alles, was ich hinzufügen zu tun hatte: import cats.implicits._ und das hat super funktioniert. –