implicit class StringDecorator(string: String) {
def cleanString = string.toLowerCase.trim
}
" MY STRING ".cleanString
sealed trait Nat
case object Zero extends Nat
case class Succ[N <: Nat](value: N) extends Nat
implicit val natZero: Zero = Zero
implicit def natSucc[N <: Nat](implicit nat: N): Succ[N] = Succ(n)
implicitly[Succ[Succ[Succ[Zero]]]] // Succ(Succ(Succ(Zero)))
def fun1[A](value: A)(implicit: fa: TypeClass[A]): A = ...
def fun2[A : TypeClass](value: A): A = ...
val handleSome: PartialFunction[Option[String], Unit] = {
case Some(result) => println(s"Result is $result")
}
val handleNone: PartialFunction[Option[String], Unit] = {
case None => println(s"There is no result")
}
val handleOption = handleSome orElse handleNone
StringBuilder sb = new StringBuilder();
for (String line : fileContent) {
sb.append("line: ")
.append(line)
.append("\n");
}
String result = sb.toString();
val result =
fileContent.foldLeft(List.empty[String]) {
case (result, line) =>
result :+ "line: " :+ line
} mkString "\n"
case class MyConfig(
isDebug: Boolean = false,
flags: Set[String] = Set.empty)
MyConfig().copy(isDebug = true, flags = flags)
// or MyConfig(isDebug = true, flags = flags)
// or MyConfig(isDebug = true).copy(flags = flags)
// etc
lazy initialization
interface X {
String getResult;
}
class LazyXProxy extends X {
private X inner = null;
private X getX() {
if (inner == null)
inner = ...;
return X;
}
String getResult() {
return getX.getResult();
}
}
trait X {
def getResult: String
}
lazy val lazyX: X = ...
singleton
class Singleton {
private static Singleton self = null;
private Singleton() {}
public static Singleton get() {
// this should be synchronized!
if (self == null)
self = new Singleton();
return self;
}
}
val printXMl: Data => String = ...
val printJson: Data => String = ...
interpreter
sealed trait CalcDSL[T]
object CalcDSL {
case class AskForInput(question: String) extends CalcDSL[Int]
case class PrintResult(result: String) extends CalcDSL[Unit]
case class Add(i: Int, j: Int) extends CalcDSL[Int]
case class Mul(i: Int, j: Int) extends CalcDSL[Int]
class Ops {
def askForInput(question: String): Free[CalcDSL, Int] =
Free.liftF(AskForInput(question))
def printResult(result: String): Free[CalcDSL, Unit] =
Free.liftF(PrintResult(result))
def add(i: Int, j: Int): Free[CalcDSL, Int] = Free.liftF(Add(i: Int, j: Int))
def mul(i: Int, j: Int): Free[CalcDSL, Int] = Free.liftF(Mul(i: Int, j: Int))
}
}
interpreter
val interpreter = new (CalcDSL ~> Id) {
def apply[A](in: CalcDSL[A]): A = in match {
case CalcDSL.AskForInput(question) => println(question); StdIn.readInt()
case CalcDSL.PrintResult(result) => println(result)
case CalcDSL.Add(i, j) => i + j
case CalcDSL.Mul(i, j) => i * j
}
}
interpreter
val ops = new CalcDSL.Ops
val program: Free[CalcDSL, (Int, Int)] = for {
a <- ops.askForInput("a = ")
b <- ops.askForInput("b = ")
c <- ops.add(a, b)
_ <- ops.printResult(s"a + b = $c")
d <- ops.mul(a, b)
_ <- ops.printResult(s"a * b = $d")
} yield (c, d)
val (c, d) = program.foldMap(interpreter)
bind(classOf[Dependency])
.to(classOf[DependencyImpl])
class Component @Inject (deps: Dependency) {}
// what if Dependency is abstract
// and you forgot to configure:
// - providers,
// - bindings,
// - etc?
// why not simply?
new Component(dependency)
there are no statements, only expressions
and so we never need to use return
// no need to use var, return
val r1 = if (condition) "some value" else "some other value"
val r2 = for (i <- 0 to 100)
yield i * 2
def ok = List(1, 2, 3).foldLeft(0) { _ + _ } // 6
def invalid = List(1, 2, 3).foldLeft(0) { return _ + _ } // 1 !!!
there are no statements, only expressions
and so we never need to use return
// no need to use var, return
val r1 = if (condition) "some value" else "some other value"
val r2 = for (i <- 0 to 100)
yield i * 2
def ok = List(1, 2, 3).foldLeft(0) { _ + _ } // 6
def invalid = List(1, 2, 3).foldLeft(0) { return _ + _ } // 1 !!!
using exception for error handling
def doSomething: Int =
if (condition) 1024
else throw new Exception("invalid result")
val result = Try(doSomething) // we need to handle exception
// (and be aware that the function throws it)
def doSomething: Either[String, Int] =
if (condition) Right(1024)
else Left("invalid result")
val result = doSomething // type indicate 2 possible results
def doSomething: Validated[String, Int] =
if (condition) Validated.valid(1024)
else Validated.invalid("invalid result")
val result = doSomething // possible error indicated by type even better
other offenders
deep hierarchies - difference in behavior can injected as functions
heavy violations of SOLID/DRY/YAGNI/... - never good
using mutable state in not-exceptional cases - a lot of optimizations can be achieved by changing used algorithms and data structures, and making data more CPU-cache friendly
using nulls - exception: interop with Java
Summary
language and libraries give us greater power of expressions using functions
harvesting the whole power of a language/paradigm requires learning some design patterns it uses
Java comes with some inherited baggage - in Scala we can avoid some solutions that Java just cannot get rid of