Sash translates regular Scala code into monadic expressions via a blackbox (meaning that it is mostly transparent to
your IDE typechecker) macro. Unlike some alternatives, Sash clearly splits the code it translates
into statements, which are chained together via some version of flatMap
, and expressions, which are searched for
effectful subexpressions. This approach eliminates the need for the infamous _ <- EFFECT
construct or its
equivalents:
import com.github.mvv.sash.cats._
import cats.effect._
import cats.effect.concurrent.Ref
import cats.effect.Console.io._
def askFor(thing: String): IO[Unit] = effect[IO] {
def ask = effect {
try readLn
catch {
case e: IOException =>
putStrLn("Yikes!")
throw e
}
}
putStrLn(s"Enter $thing")
val answer = +Ref.of[IO, String](+ask)
while (+answer.get != thing) {
putStrLn(s"No, enter $thing")
answer.set(+ask)
}
putStrLn("You pass")
}
or, with ZIO:
import com.github.mvv.sash.zio._
import zio._
import zio.console._
import java.io.IOException
def askFor(thing: String): ZIO[Console, IOException, Unit] = effect[Console, IOException] {
def ask = effect {
try getStrLn
catch {
case e: IOException =>
putStrLn("Yikes!")
throw e
}
}
putStrLn(s"Enter $thing")
val answer = +Ref.make(+ask)
while (+answer.get != thing) {
putStrLn(s"No, enter $thing")
answer.set(+ask)
}
putStrLn("You pass")
}
The only noticeable difference from a regular Scala code is the prolific use of +EFFECT
construct, which is roughly
equivalent to VAL <- EFFECT
in for-comprehensions. For example
val answer = +Ref.make(+ask)
would look like
tmp <- ask
answer <- Ref.make(tmp)
and
answer.set(+ask)
would look like
tmp <- ask
_ <- answer.set(tmp)
inside a for
.
Core Sash module
libraryDependencies += "com.github.mvv.sash" %% "sash" % "0.1-M7"
provides a "simple" version of the effect
macro, which relies only on flatMap
method. It can handle conditionals
and loops, but cannot handle try-catch-finally. The module also exposes the imlementation of the macro, which can be
configured to handle your favourite monads via a small compatibility layer. Sash comes with two such layers: one for
the Cats library
libraryDependencies += "com.github.mvv.sash" %% "sash-cats" % "0.1-M7"
and one for the ZIO
libraryDependencies += "com.github.mvv.sash" %% "sash-zio" % "0.1-M7"
Translation starts with the argument of the macro, which is treated as a statement. A statement can be
- A unit value
()
- A block
{ [STMT]* }
of statements - A conditional
if (EXPR) STMT [else STMT]
- A match
EXPR match { [case ... => STMT]* }
- A loop
while (EXPR) STMT
ordo STMT while (EXPR)
- A variable declaration
[implicit] val NAME[: TYPE] = EXPR
- An error raising statement
throw EXPR
- An error handling statement
try STMT [catch { [case ... => STMT]* }] [finally STMT]
- An import or a type/class/trait/object/function definition. Those are left left as-is, meaning that they are simply brought into scope of the subsequent statements.
- Impure code
impure CODE
, whereCODE
is a regular Scala code - An expression
EXPR
Expressions EXPR
are analyzed further, to see if they are
- Effectful
+STMT
- Pure
pure CODE
, whereCODE
is a regular Scala code - Typed
EXPR: TYPE
- An application
EXPR([EXPR]*)
- An accessor
EXPR.NAME
- A conditional
if (EXPR) CODE else CODE
- A match
EXPR match { [case ... => CODE] }
- A regular Scala code
CODE