All Projects → Kanaka-io → Play Monadic Actions

Kanaka-io / Play Monadic Actions

Licence: other
A simple scala DSL to allow clean and monadic style for Play! Actions

Programming Languages

scala
5932 projects
dsl
153 projects

Projects that are alternatives of or similar to Play Monadic Actions

Play Ebean
Play Ebean module
Stars: ✭ 82 (-36.43%)
Mutual labels:  playframework
Cats Stm
An STM implementation for Cats Effect
Stars: ✭ 106 (-17.83%)
Mutual labels:  monad
Expected
What did you expect?
Stars: ✭ 113 (-12.4%)
Mutual labels:  monad
Play Java Websocket Example
Example Play Java application showing Websocket usage with Akka actors
Stars: ✭ 86 (-33.33%)
Mutual labels:  playframework
Ok jose
Pipe elixir functions that match ok/error tuples or custom patterns.
Stars: ✭ 91 (-29.46%)
Mutual labels:  monad
Crocks
A collection of well known Algebraic Data Types for your utter enjoyment.
Stars: ✭ 1,501 (+1063.57%)
Mutual labels:  monad
Cyclops
An advanced, but easy to use, platform for writing functional applications in Java 8.
Stars: ✭ 1,180 (+814.73%)
Mutual labels:  monad
Alembic
⚗️ Functional JSON Parser - Linux Ready 🐧
Stars: ✭ 115 (-10.85%)
Mutual labels:  monad
Play Circe
circe for play
Stars: ✭ 95 (-26.36%)
Mutual labels:  playframework
F
Functional stuff for Python
Stars: ✭ 113 (-12.4%)
Mutual labels:  monad
Grokking Monad
购买 👉 https://gum.co/grokking-monad 在线阅读 👉
Stars: ✭ 87 (-32.56%)
Mutual labels:  monad
Scalatestplus Play
ScalaTest + Play
Stars: ✭ 88 (-31.78%)
Mutual labels:  playframework
Play Pdf
A PDF module for the Play framework
Stars: ✭ 108 (-16.28%)
Mutual labels:  playframework
Play Slick3 Example
A simple skeleton for play scala slick applications.
Stars: ✭ 83 (-35.66%)
Mutual labels:  playframework
Mayre
Maybe render a React component, maybe not 😮
Stars: ✭ 114 (-11.63%)
Mutual labels:  monad
Cfp Devoxx
Call for Papers application created for Devoxx France, used by many conferences
Stars: ✭ 79 (-38.76%)
Mutual labels:  playframework
Functionaljava
Functional programming in Java
Stars: ✭ 1,472 (+1041.09%)
Mutual labels:  monad
Play Jsmessages
Library to compute localized messages of your Play application on client side
Stars: ✭ 125 (-3.1%)
Mutual labels:  playframework
Dunai
Classic and Arrowized Functional Reactive Programming, Reactive Programming, and Stream programming, all via Monadic Stream Functions
Stars: ✭ 115 (-10.85%)
Mutual labels:  monad
Masala Parser
Javascript Generalized Parser Combinators
Stars: ✭ 110 (-14.73%)
Mutual labels:  monad

Play monadic actions

Build Status Gitter chat Coverage Status Maven Central

This little play module provides some syntactic sugar that allows boilerplate-free Actions using for-comprehensions.

Motivation

It is commonly admitted that controllers should be lean and only focus on parsing an incoming HTTP request, call (possibly many) service methods and finally build an HTTP response (preferably with a proper status). In the context of an asynchronous framework like Play!, most of these operations results are (or can be) wrapped in a Future, and since their outcome can be either positive or negative, these results have a type that is more or less isomorphic to Future[Either[X, Y]].

This matter of facts raises some readability issues. Consider for example the following action :

class ExampleController extends Controller {
  
  val beerOrderForm: Form[BeerOrder] = ???
  def findAdultUser(id: String): Future[Either[UnderageError, User]] = ???
  def sellBeer(beerName: String, customer: User): Future[Either[OutOfStockError, Beer]] = ???
  
  def orderBeer() = Action.async {
     beerOrderForm.bindFromRequest().fold(
       formWithErrors => BadRequest(views.html.orderBeer(formWithErrors),
       beerOrder => 
        findAdultUser(beerOrder.userId).map(
          _.fold(
            ue => Conflict(displayError(ue)),
            user => 
              sellBeer(beerOrder.beerName, user).map(
                _.fold(
                  oose => NotFound(displayError(oose)),
                  beer => Ok(displayBeer(beer)  
                )
              )    
          )
        )
  }
}

This is pretty straightforward, and yet the different steps of the computation are not made very clear. And since I've typed this in a regular text editor with no syntax highlighting nor static code analysis, there is an obvious error that you may not have spotted (there's a map instead of a flatMap somewhere).

This library addresses this problem by defining a Step[A] monad, which is roughly a Future[Either[Result, A]], but with a right bias on the Either part, and providing a little DSL to lift relevant types into this monad's context.

Using it, the previous example becomes :

import io.kanaka.monadic.dsl._

// don't forget to import an implicit ExecutionContext
import play.api.libs.concurrent.Execution.Implicits.defaultContext 

class ExampleController extends Controller {
  
  val beerOrderForm: Form[BeerOrder] = ???
  def findAdultUser(id: String): Future[Either[UnderageError, User]] = ???
  def sellBeer(beerName: String, customer: User): Future[Either[OutOfStockError, Beer]] = ???
  
  def orderBeer() = Action.async {
    for {
      beerOrder <- beerOrderForm.bindFromRequest()    ?| (formWithErrors => BadRequest(views.html.orderBeer(formWithErrors))
      user      <- findAdultUser(beerOrder.userId)    ?| (ue => Conflict(displayError(ue))
      beer      <- sellBeer(beerOrder.beerName, user) ?| (oose => NotFound(displayError(oose))  
    } yield Ok(displayBeer(beer))
  }
}

IMPORTANT NOTE : one MUST provide an implicit ExecutionContext for the DSL to work

How it works

The DSL introduces the binary ?| operator. The happy path goes on the left hand side of the operator and the error path goes on the right : happy ?| error. Such expression produces a Step[A] which has all the required methods to make it usable in a for-comprehension.

So for example, if a service methods fooreturns a Future[Option[A]], we assume the happy path to be the case where the Future succeeds with a Some[A] and the error path to be the case where it succeeds with a None (the case where the Future fails is already taken care of by play's error handler). So we need to provide a proper Result to be returned in the error case (most probably a NotFound) and then we can write

for {
 // ...
 a <- foo ?| NotFound    
 // ...
} yield {
 // ...
}

The a here would be of type A, meaning that we've extracted the meaningful value from the Future[Option[A]] return by foo. Of course, if foo returns a Future[None] the for-comprehension is not evaluated further, and returns NotFound.

The right hand side of the ?| operator (the error management part) is a function (or a thunk) that must return a Result and whose input type depends of the type of the expression on the left hand side of the operator (see the table of supported conversions below).

Filtering and Pattern-matching

Step[_] defines a withFilter method, which means that one can use pattern matching and filtering in for-comprehensions involving Step[_].

For example, if bar is of type Future[Option[(Int, String)]], one can write

for {
 (i, s) <- bar ?| NotFound if s.length >= i
} yield Ok(s.take(i))

Please note though that in the case where the predicate s.length >= i does not hold, the whole Future will fail with a NoSuchElementException, and there is no easy way to transform this failure into a user-specified Result.

Supported conversions

The DSL supports the following conversions :

Defining module Source type Type of the right hand side Type of the extracted value
play-monadic-actions Boolean => Result Unit
play-monadic-actions Option[A] => Result A
play-monadic-actions Try[A] Throwable => Result A
play-monadic-actions Either[B, A] B => Result A
play-monadic-actions Form[A] Form[A] => Result A
play-monadic-actions JsResult[A] Seq[(JsPath, Seq[ValidationError])] => Result A
play-monadic-actions Future[A] Throwable => Result A
play-monadic-actions Future[Boolean] => Result Unit
play-monadic-actions Future[Option[A]] => Result A
play-monadic-actions Future[Either[B, A]] B => Result A
play-monadic-actions-cats B Xor A B => Result A
play-monadic-actions-cats Future[B Xor A] B => Result A
play-monadic-actions-cats XorT[Future, B, A] B => Result A
play-monadic-actions-cats OptionT[Future, A] => Result A
play-monadic-actions-cats Validated[B Xor A] B => Result A
play-monadic-actions-cats Future[Validated[B Xor A]] B => Result A
play-monadic-actions-scalaz-7-1 B \/ A B => Result A
play-monadic-actions-scalaz-7-1 Future[B \/ A] B => Result A
play-monadic-actions-scalaz-7-1 Validation[B, A] B => Result A
play-monadic-actions-scalaz-7-1 EitherT[Future, B, A] B => Result A
play-monadic-actions-scalaz-7-1 OptionT[Future, A] Unit => Result A
play-monadic-actions-scalaz-7-1 Future[Validation[B, A]] B => Result A
play-monadic-actions-scalaz-7-2 B \/ A B => Result A
play-monadic-actions-scalaz-7-2 Future[B \/ A] B => Result A
play-monadic-actions-scalaz-7-2 Validation[B, A] B => Result A
play-monadic-actions-scalaz-7-2 EitherT[Future, B, A] B => Result A
play-monadic-actions-scalaz-7-2 OptionT[Future, A] Unit => Result A
play-monadic-actions-scalaz-7-2 Future[Validation[B, A]] B => Result A

Installation

As of version 2.2.1, all modules are published for Scala versions 2.11 up to 2.13.

Using sbt :

Current version is 2.2.0

libraryDependencies += "io.kanaka" %% "play-monadic-actions" % "2.2.0"

There are also contrib modules for interoperability with scalaz and cats :

module name is compatible with / built against
play-monadic-actions-cats cats 2.0.0
play-monadic-actions-scalaz_7-2 scalaz 7.2.28

Each of these module provides Functor and Monad instances for Step[_] as well as conversions for relevant types in the target library

These instances and conversions are made available by importing io.kanaka.monadic.dsl.compat.cats._ and io.kanaka.monadic.dsl.compat.scalaz._ respectively.

Compatibility

  • Version 2.2.0 is compatible with Play! 2.7.x
  • Version 2.1.0 is compatible with Play! 2.6.x
  • Version 2.0.0 is compatible with Play! 2.5.x
  • Version 1.1.0 is compatible with Play! 2.4.x
  • Version 1.0.1 is compatible with Play! 2.3.x

From version 2.0.0 up, dependencies toward play and cats are defined as provided, meaning that you can use the DSL along with any version of these projects you see fit. The sample projects under samples/ demonstrate this capability.

From version 2.1.0 up, the modules are published for scala 2.11 and 2.12. Previous versions are only published for scala 2.11.

Contributors

Valentin Kasas

Damien Gouyette

David R. Bild

Bjørn Madsen

Christophe Calves

Maxim Karpov

Richard Searle

Andrew Adams

... your name here

Credits

This project is widely inspired from the play-monad-transformers activator template by Lunatech.

It also uses coursier to fetch dependencies in parallel, which is a pure bliss. Take a look if you don't know it yet.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].