Description
This little play module provides some syntactic sugar that allows boilerplate-free Actions using for-comprehensions.
Play monadic actions alternatives and similar packages
Based on the "Extensions" category.
Alternatively, view Play monadic actions alternatives based on common mentions on social networks and blogs.
-
Shapeless
A type class and dependent type based generic programming library for Scala. -
Twitter Util
General-purpose Scala libraries, including a future implementation and other concurrency tools. -
Ammonite-Ops
Safe, easy, filesystem operations in Scala as convenient as in the Bash shell. -
Scalactic
Small library of utilities related to quality that helps keeping code clear and correct. -
better-files
Simple, safe and intuitive Scala I/O. better-files is a dependency-free pragmatic thin Scala wrapper around Java NIO. -
Cassovary
A Scala library that is designed from the ground up for space efficiency, handling graphs with billions of nodes and edges. -
Scala Async
An asynchronous programming facility for Scala. -
scala.meta
A clean-room implementation of a metaprogramming toolkit for Scala. -
Enumeratum
A macro to replace Scala enumerations with a sealed family of case objects. This allows additional checks for the compiler, e.g. for missing cases in a match statement. Has additinal support for Json libraries and the Play framework. -
Scala-Logging
Convenient and performant logging library for Scala wrapping SLF4J. -
Simulacrum
First class syntax support for type classes in Scala. -
Freestyle
A cohesive & pragmatic framework of FP centric Scala libraries. -
Quicklens
modify deeply nested case class fields with an elegant API -
Scala Graph
A Scala library with basic graph functionality that seamlessly fits into the Scala standard collections library. -
Eff
Extensible effects are an alternative to monad transformers for computing with effects in a functional way. -
Hamsters
A mini Scala utility library. Compatible with functional programming beginners. Featuring validation, monad transformers, HLists, Union types. -
scribe
Practical logging framework that doesn't depend on any other logging framework and can be completely configured programmatically. -
Stateless Future
Asynchronous programming in fully featured Scala syntax. -
Scala Blitz
A library to speed up Scala collection operations by removing runtime overheads during compilation, and a custom data-parallel operation runtime. -
Records for Scala
Labeled records for Scala based on structural refinement types and macros. -
enableIf.scala
A library that switches Scala code at compile-time, like #if in C/C++. -
Freasy Monad
Easy way to create Free Monad using Scala macros with first-class Intellij support. -
wvlet-log
A library for enhancing your application logs with colors and source code locations. -
Persist-Logging
Comprehensive logging library for Scala. -
Resolvable
A library to optimize fetching immutable data structures from several endpoints in several formats. -
Freedsl
A library to implement composable side effects, weaving typeclasses on a wrapping type and the free monad.
Get performance insights in less than 4 minutes
* Code Quality Rankings and insights are calculated and provided by Lumnify.
They vary from L1 to L5 with "L5" being the highest. Visit our partner's website for more details.
Do you think we are missing an alternative of Play monadic actions or a related project?
README
Play monadic actions
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 foo
returns 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
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
... 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.