private[sbt] class AWSLambdaClient(region: Region) {
private lazy val lambdaClient = buildAwsClient
def deployLambda(lambdaParams: LambdaParams, roleArn: RoleArn, s3Params: S3Params):
Try[Either[CreateFunctionResult, UpdateFunctionCodeResult]] = {
for {
exists <- lambdaExist(lambdaParams.name)
} yield {
if (exists) {
Right(updateExistingLambda(lambdaParams, s3Params, roleArn))
} else {
Left(createNewLambda(lambdaParams, roleArn, s3Params))
}
}
}
...
// Free monad over the free functor of TerminalOp
type TerminalIO[A] = Free.FreeC[TerminalOp, A]
// It's a monad (but the instance is not inferrable)
implicit val monadTerminalIO: Monad[TerminalIO] =
Free.freeMonad[({type λ[α] = Coyoneda[TerminalOp, α]})#λ]
// Smart constructors in TerminalIO
def readLine: TerminalIO[String] = Free.liftFC(ReadLine)
def writeLine(s: String): TerminalIO[Unit] = Free.liftFC(WriteLine(s))
Separate language (like Java, Kotlin, Clojure)
Compile to bytecode / JVM runtime
Separate ecosystem (libs, tooling, build chain, philosophy etc)
IDE
Build chain (Sbt, Gradle, Maven)
REPL
Statically typed
Basic syntax lightweight
(yet allows complex constructs)
Interoperabillity
Used to be exploration / testing ground
FP bias
Stable (16 years old now)
(with code examples in Repl - IntroSpec)
Type inference
Immutability
Everything is an expressions
Everything is an object
com.virtuslab.scalaworkshop.intro.JavaFeel
* don't use "Open URL..." option
Classes
Objects
Traits
Inheritance
sbt + console (Scala repl)
:javap -p
class MyFirstClass(name: String) {
println("My class with name: ${name}")
}
var vs val
access modifiers
methods
getters / setters?
body execution
constructors
more constructors
named argument
default parameters
extends & overriding
package & imports
qualified access modifiers
type parameters
operators as methods?
equality
Methods / operators
Singletons and companion objects
Equality
Default and named args
Basic inheritance
Singletons and companion objects
They allow to generate code by using annotations instead of writing everything again and again.
In Scala a keyword 'case' before 'class' makes the compiler generate additional code for you.
We can easily verify what is generated by using javap for a class and its companion object.
case class Client(name: String, age: Int)
1) The constructor's parameters are promoted to public vals.
You can write
case class Client(val name: String, val age: Int)
but it's redundant.
Case classes are ideal to model immutable data.
2) You get for free toString, equals and hashCode methods which take the constructor parameters into account.
scala> case class Client(name: String, age: Int)
defined class Client
scala> val c1 = Client("Jan Kowalski", 27)
c1: Client = Client(Jan Kowalski,27)
scala> val c2 = Client("Jan Kowalski", 27)
c2: Client = Client(Jan Kowalski,27) // here we have toString used
scala> c1.toString // but just to confirm
res0: String = Client(Jan Kowalski,27)
scala> c1 == c2 // equals compares the structure instead of the references
res1: Boolean = true
scala> c1 eq c2
res1: Boolean = false
3) You can create a new instance without using new keyword thanks to apply method generated in a companion object.
object Client { // this is an equivalent of what the compiler generates
def apply(name: String, age: Int): Client = new Client(name, age)
(...)
}
scala> case class Client(name: String, age: Int)
defined class Client
scala> Client("Jan Kowalski", 27)
res1: Client = Client(Jan Kowalski,27)
In fact apply is just syntactic sugar.
You can create your own factory methods and override them.
object ParseInt {
def apply(str: String): Integer = Integer.parseInt(str)
def apply(chars: Array[Char]): Integer = Integer.parseInt(new String(chars))
}
scala> ParseInt("500")
res0: Integer = 500
scala> ParseInt.apply("500")
res1: Integer = 500
scala> ParseInt(Array('2', '0'))
res2: Integer = 20
4) You can use copy method to create a shallow clone of an instance.
Optionally you can change the values of params.
scala> case class Client(name: String, age: Int)
defined class Client
scala> val c1 = Client("Jan Kowalski", 27)
c1: Client = Client(Jan Kowalski,27)
scala> val c2 = c1.copy()
c2: Client = Client(Jan Kowalski,27)
scala> c1 eq c2
res0: Boolean = false
scala> c1.copy(age = 28)
res1: Client = Client(Jan Kowalski,28)
5) You can extract the values of parameters thanks to unapply method.
(Pattern matching will be discussed in detail later)
scala> case class Client(name: String, age: Int)
defined class Client
scala> val Client(name, age) = Client("Jan Kowalski", 27)
name: String = Jan Kowalski
age: Int = 27
In sbt shell, run:
testOnly *ApplySandboxSpec
Can't we just make all classes case classes?
- sometimes you don't want to make class params public
(can be solved by using private val explicitly)
- case classes can't inherit from other case classes
- there's much code generated so the bytecode size would be much bigger
1. while
2. do while
3. for loop
4. for expression (for comprehension)
scala> var i = 0
i: Int = 0
scala> while (i < 3) { i += 1; println(i) }
1
2
3
scala> var i = 0
i: Int = 0
scala> do { i+= 1; println(i) } while (i < 3)
1
2
3
In Scala there's no equivalent of a standard for loop.
Only the version like enhanced for for collections and arrays in Java
scala> val cities = List("London", "Paris", "Madrid", "Kielce")
cities: List[String] = List(London, Paris, Madrid, Kielce)
scala> for (city <- cities) println(city)
London
Paris
Madrid
Kielce
There's no value returned (Unit type).
You can specify additional filters using if (called guard).
scala> for (i <- 1 to 10 if i % 2 == 0 && i != 6) println(i)
2
4
8
10
You can use keyword yield to return the collection of computed values.
scala> for (i <- 1 to 10 if i % 2 == 0 && i != 6) yield i
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 8, 10)
scala> for (i <- Set(1, 2, 3, 4) if i % 2 == 0) yield i
res1: scala.collection.immutable.Set[Int] = Set(2, 4)
Note the type of the enumerator determines what will be a type of a result.
You can use more than one enumerator / generator in one for.
They can be separated using semicolons.
for (i <- 1 to 6 if i % 2 == 0; j <- i to i + 2) yield (i, j)
But it's nicer when using curly braces without semicolons, isn't it?
for {
i <- 1 to 6 if i % 2 == 0
j <- i to i + 2
} yield (i, j)
Btw, what would be a result of?
for {
i <- List(1, 2, 3, 4, 5, 6) if i % 2 == 0
j <- i to i + 2
} yield (i, j)
The result is:
scala> for {
| i <- List(1, 2, 3, 4, 5, 6) if i % 2 == 0
| j <- i to i + 2
| } yield (i, j)
res0: List[(Int, Int)] =
List((2,2), (2,3), (2,4), (4,4), (4,5), (4,6), (6,6), (6,7), (6,8))
You can also define local variables directly in for block.
scala> for {
| person <- people
| address = getAddress(person) if address.country == "Poland"
| } yield s"${person.name} is from ${address.city}"
res0: List[String] =
List(Jan Kowalski is from Kielce, Piotr Nowak is from Krakow)
In sbt shell, run:
testOnly *ForComprehensionSandboxSpec
Collections will be discussed in detail later.
scala> val peanutsInPackages = Seq(500, 1000, 2000)
def prepareSpecialOffer(weight: Int) = weight * 1.2
val newWeights = peanutsInPackages.map(prepareSpecialOffer)
res0: newWeights: Seq[Int] = List(600, 1200, 2400)
This can be shortened by using lambdas
val peanutsInPackages = Seq(500, 1000, 2000)
val newWeights = peanutsInPackages.map((weight: Int) => weight * 1.2)
You can pass a function which will convert elements
val peanutsInPackages = Seq(500, 1000, 2000)
val newWeights = peanutsInPackages.map(_ * 1.2)
Or shortened even further by using a wildcard
scala> val peanutsInPackages = Seq(500, 1000, 2000)
peanutsInPackages.foreach(weight => println(weight * 1.2))
600.0
1200.0
2400.0
foreach is similar to map but there are no values returned
scala> List(1, 10, 20).flatMap(i => List(i, i + 1, i + 2))
res0: List[Int] = List(1, 2, 3, 10, 11, 12, 20, 21, 22)
It's like map but for each element we can possibly produce many values (or even no value!) and eventually we put them all in one collection
val allNumbers = customers.flatMap(customer => getPhoneNumbers(customer))
More practical example
Now we can call all these numbers and offer some pots!
scala> (1 to 10).filter(i => i % 2 == 0)
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
You can pass the predicate to get a subset of elements as a result
Select a for loop and press Ctrl-Alt-D (Linux) in your IntelliJ to desugar Scala code (unless you're on Ubuntu and it show your desktop instead...).
for (x <- c1) {...}
c1.foreach(x => {...})
Normal for without yield is translated into foreach methods (and possibly also withFilter)
for {
x <- c1
y <- c2
z <- c3
} (...)
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Which possibly can be nested
for (x <- c if condition(x)) yield {...}
c.withFilter(x => condition(x)).map(x => {...})
if guard turns into withFilter
for {
a <- 1 to 5
b <- 1 to a
} yield (a, b)
(1 to 5).flatMap(a => (1 to a).map(b => (a, b)))
For expression is translated into flatMaps, maps and withFilter (potentially also nested)
In sbt shell, run:
testOnly *ForLoopTranslationSandboxSpec
The fully abstract traits are turned into standard interfaces
trait Logger {
val prefix: String
def logInfo(msg: String)
}
Let's check using javap
The traits with implementation has additional methods generated
trait StdOutLogger {
val prefix: String = "stdout: "
def logInfo(msg: String) = println(s"$prefix $msg")
}
Let's check using javap
When inheriting, a class must be specified first.
We always use extends at the beginning and with for another traits.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
class Animal
class Duck extends Animal with HasLogger with Comparable {
(...)
}
We always use extends at the beginning and with for another traits.
Even if you inherit only traits.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
class Animal extends HasLogger with Comparable {
(...)
}
We always use extends at the beginning and with for another traits.
Also when you define a trait.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
trait Animal extends HasLogger with Comparable {
(...)
}
Which implementation will be used when a member is overridden in many places?
The rules are different than in Java.
Scala uses so-called trait linearization.
The most specific version is used when there's linear hierarchy
trait MakingSound {
def getSound: String = "beep"
}
trait Dog extends MakingSound {
override def getSound = "hau"
}
class SheepDog extends Dog with MakingSound
class Bulldog extends MakingSound with Dog
println(new SheepDog().getSound)
hau
println(new Bulldog().getSound)
hau
Scala (unlike Java) is able to choose when there's diamond inheritance
trait MakingSound {
def getSound: String = "beep"
}
trait Dog extends MakingSound {
override def getSound = "hau"
}
trait UltraFast extends MakingSound {
override def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast
println(new MysteriousCreature().getSound)
wzium...
But (like in Java) no when there are unrelated types with the same member
trait Dog {
def getSound = "hau"
}
trait UltraFast {
def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast
Error:(48, 7) class MysteriousCreature inherits conflicting members:
method getSound in trait Dog of type => String and
method getSound in trait UltraFast of type => String
(Note: this can be resolved by declaring an override in class MysteriousCreature.)
class MysteriousCreature extends Dog with UltraFast
For unrelated types it's needed to choose explicitly
trait Dog {
def getSound = "hau"
}
trait UltraFast {
def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast {
override def getSound: String = super[Dog].getSound
}
println(new MysteriousCreature().getSound)
// hau
sealed trait Answer
case object Unsure extends Answer
case object Yes extends Answer
case object No extends Answer
trait HasLogger {
protected val logger: Logger = new SplunkLogger()
}
class Person extends HasLogger {
(...)
}
trait HasTestLogger extends HasLogger {
override protected val logger: Logger = new StdOutLogger()
}
new Person() with HasTestLogger
In math we often meet definitions like
f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2)
It's possible to express this using if-else blocks but we can do better.
In Java 12 you can write such code when using the preview version of a switch expression
int fib(int n) { return switch (n) { case 0 -> 0; case 1, 2 -> 1; default -> fib(n - 1) + fib(n - 2); }; }
What's different from the standard switch?
In Scala you can use match keyword to match pattern
def fib(n: Int): Int = n match { case 0 => 0 case 1 | 2 => 1 case _ => fib(n - 1) + fib(n - 2) }
It can be generalised as
expression match {
case pattern1 [if predicate1] => expression1
case pattern2 [if predicate2] => expression2
(...)
}
In java you can match numbers or Strings.
In Scala there are many types of patterns and they can be very complex.
Let's check some of them.
When the expression doesn't match any case, MatchError is thrown.
To avoid this, usually one uses wildcard pattern matching everything
expression match {
case <some_pattern> => <some_result>
case _ => <default_value>
}
something match {
case duck: Duck => duck.quack()
case dog: Dog => dog.bark()
case _: Animal => // do nothing
}
When using sealed traits, compiler will warn you when you omit case.
sealed trait DecisionStatus
class Choice(val number: Int) extends DecisionStatus
object Resigned extends DecisionStatus
object NeedsMoreTime extends DecisionStatus
val d: DecisionStatus = (...)
d match {
case c: Choice => println(s"You've chosen ${c.number}")
case Resigned => println("You resigned")
}
// Warning:(21, 3) match may not be exhaustive.
// It would fail on the following input: NeedsMoreTime
// d match {
Remember there's type erasure on JVM so you can't rely on the generic type parameter. The compiler will generate a warning.
List(1, 2, 3) match {
case strings: List[String] => strings.foreach(println)
case _ =>
}
1
2
3
// Warning:(38, 17) fruitless type test: a value of type List[Int]
// cannot also be a List[String] (the underlying of List[String])
// (but still might match its erasure)
// case strings: List[String] => strings.foreach(println)
employee match {
case p: Programmer if p.isOnSupport => p.call(incidentDescription)
case p: Programmer => p.sendEmail(incidentDescription)
case _ => // do nothing
}
case class Player(nick: String, credits: Int)
player match {
case Player(name, 0) =>
println(s"$name, maybe do you want to buy some credits?")
case Player(_, credits) if credits > 1000 =>
println("We have a special offer for you. Wanna spend credits on lootbox?")
case _ =>
}
(competition1Score, competition2Score) match {
case (score1, score2) if score1 > 90 || score2 > 90 => Qualification
case _ => Rejection
}
val list: List[Int] = (...)
list match {
case List(1, 1, 1) => "3 x 1"
case _ :: Nil => "Only one element"
case head :: tail => s"First element is $head; others are $tail"
case Nil => "No elements"
}
You can match the structure as previously and still be able to use base instance.
case class Person(name: String, age: Int)
person match {
case p @ Person(_, age) <- people if age >= 18 => checkIdCard(p)
case _ => checkStudentCard(person)
}
You can check whether a value is the same as value of local variable or constant defined in object.
Use backticks to really match value instead of defining an identifier.
object TimeUtils {
val UtcTimeZone = "UTC"
}
val cetTimeZone = "CET"
timeZone match {
case `cetTimeZone` => "Are you in Poland?"
case TimeUtils.UtcTimeZone => s"You're in $timeZone"
case _ => s"$timeZone is not CET and UTC"
}
val pattern = "([0-9]+) ([A-Za-z]+)".r
"10 kg" match {
case pattern(number, unit) => (...)
}
Pattern matching is not only match expression. You'll find it in other places too.
Extractors in vals
scala> case class Person(name: String, age: Int)
val Person(n, a) = Person("Jan", 27)
n: String = Jan
a: Int = 27
Extractors in generators / enumerators in for comprehension
case class Person(name: String, age: Int)
for (p @ Person(_, age) <- people if age >= 18) yield p
Catching exceptions
try {
// some unsafe operations
} catch {
case e: IOException => (...)
case e: Exception => (...)
}
Function which has values only for a part of possible values passed as params.
E.g. div(a: Double, b: Double) could be a partial functions with results for all values except b == 0.
val div = new PartialFunction[(Double, Double), Double] {
def apply(numbers: (Double, Double)): Double = numbers match {
case (a, b) => a / b
}
def isDefinedAt(numbers: (Double, Double)): Boolean = numbers._2 != 0
}
Usually we don't need to think that something is partial function.
We just create them using blocks with case patterns.
val div: PartialFunction[(Double, Double), Double] = {
case (a, b) if b != 0 => a / b
}
println(div.isDefinedAt((1, 1)))
// true
println(div.isDefinedAt((1, 0)))
// false
Partial functions can be used e.g. when operating on collections.
E.g. collect method is similar to map but returns values only for elements for which function isDefinedAt.
scala> List((1.0, 1.0), (1.0, 0.0), (5.0, 2.0)).collect {
| case (a, b) if b != 0 => a / b
| }
res0: List[Double] = List(1.0, 2.5)
In sbt shell, run:
testOnly *PatternMatchingSandboxSpec
Collections Exercises
CollectionSpec2
Some(x): case class, wraps a value
None: case object, expresses lack of a value
In some ways similar to Java's Optional...
except that Optional is a final class instead.
public final class Optional<T> {
...
private final T value;
...
public T get() {
if (this.value == null) {
throw new NoSuchElementException(...);
} else {
return this.value;
}
}
}
// disclaimer: headers are simplified
abstract class Option[A] {
...
def get: A
...
}
final case class Some[A](value: A) extends Option[A] {
...
def get: A = value
}
case object None extends Option[Nothing] {
...
def get = throw new NoSuchElementException("None.get")
}
val foo: Option[String] = Some("hello")
val bar: Option[String] = None
// Or to be less explicit, but extra null-safe:
val qux: Option[String] = Option(someStringValue)
// This will resolve to None if someStringValue is null,
// and Some(...) otherwise
val a: String = foo.get // ok...
val b: String = bar.get // throws NoSuchElementException!
Calling .get on None ends up in NoSuchElementException - just like with Java's Optional :/
Better do .getOrElse(x) - as with Optional, or...
myOption match {
case Some(x) => ...
case None => ...
}
val bar: Option[String] = None
val foo: Option[String] = Some("Hello")
bar.map(x => x + "World") // --> still None!
foo.map(x => x + "World") // --> Some("HelloWorld")
bar.flatMap(x => None) // --> still None...
foo.flatMap(x => None) // --> also None!
bar.flatMap(x => Some(x + "World")) // --> still None
foo.flatMap(x => Some(x + "World")) // Some("HelloWorld")
case class Data(someImportantPart: String, /* more members */ ... )
def fetchData(input: String): Option[Data] = ...
for {
data <- fetchData(input)
} yield data.someImportantPart
// This will desugar into:
fetchData(input).map { data =>
data.someImportantPart
}
def merge(d1: Data, d2: Data): Data = ...
for {
data <- fetchData(input)
anotherData <- fetchData(anotherInput)
} yield merge(data, anotherData)
// This will desugar into:
fetchData(input).flatMap { data =>
fetchData(anotherInput).map { anotherData =>
merge(data, anotherData)
}
}
You can think of for as a fail-fast flow over Options... continuing through Somes but immediately stopping on first None!
If right sides of all arrows are Some... the final result is Some
If any right side of any arrow is None... the final result is None
In sbt shell, run:
testOnly *OptionSandboxSpec
Left(x): case class, typically wraps an error
Right(x): case class, typically wraps a correct value
No equivalent in Java standard library
Javaslang/Vavr has a similar concept, though
// disclaimer: class headers are simplified
abstract class Either[A, B] {
...
}
final case class Left[A, B](value: A) extends Either[A, B] {
...
}
final case class Right[A, B](value: B) extends Either[A, B] {
...
}
val foo: Either[Exception, String] = Right("Hello")
val bar: Either[Exception, String] = Left(new Exception("Something wrong"))
val w: String = foo.right.get // ok, will be "Hello"
val x: Exception = foo.left.get // compiles, but throws NoSuchElementException!
val y: Exception = bar.left.get // ok, will be Exception("Something wrong")
val z: String = bar.right.get // compiles, but throws NoSuchElementException!
If called on wrong type, ends up in a NoSuchElementException
It's not a type-safe way of dealing with things :/
myEither match {
case Left(l) => ...
case Right(r) => ...
}
val foo: Either[Exception, String] = Right("Hello")
val bar: Either[Exception, String] = Left(new Exception("Something wrong"))
foo.map((x: String) => x + "World") // --> Right("HelloWorld")
bar.map((x: String) => x + "World") // --> still Left(Exception("Something wrong"))
foo.flatMap((x: String) => Left(new OtherException(""))) // --> Left(OtherException(""))
bar.flatMap((x: String) => Left(new OtherException(""))) // --> still Left(Exception("Something wrong"))
foo.flatMap((x: String) => Right(x + "World")) // --> Right("HelloWorld")
bar.flatMap((x: String) => Right(x + "World")) // --> still Left(Exception("Something wrong"))
case class Tree(root: Node, /* more members */ ... )
def parseInput(data: Data): Either[Exception, Tree] = ...
for {
tree <- parseInput(data)
} yield tree.root
// This will desugar into something like:
parseInput(data).map { tree =>
tree.root
}
def computeResult(tree: Tree): Double = ...
for {
tree <- parseInput(data)
result <- computeResult(tree)
} yield result
// This will desugar into something like:
parseInput(data).flatMap { tree =>
computeResult(tree).map { result =>
result
}
}
You can think of for as a fail-fast flow over Eithers... continuing through Rights but immediately stopping on first Left!
If right sides of all arrows are Right... the final result is Right
If any right side of any arrow is Left... the final result is Left (*)
(*) storing the first encountered Left value
In sbt shell, run:
compile
In sbt shell, run:
testOnly *EitherSandboxSpec
Success(x): case class, wraps a successful result
Failure(e): case class, wraps a Throwable
// disclaimer: headers are simplified
abstract class Try[T] {
...
def get: T
...
}
final case class Failure[T](exception: Throwable) extends Try[T] {
...
override def get: T = throw exception
...
}
final case class Success[T](value: T) extends Try[T] {
...
override def get: T = value
...
}
val foo: Try[Int] = Try {
...
someJavaLibraryMethodThrowingExceptions()
...
}
// `foo` is an instance of Failure[Int] if the above code threw an exception,
// or an instance of Success[Int] if no exception has been thrown.
for {
a <- Try(potentiallyThrowingMethod())
b <- Try(otherPotentiallyThrowingMethod())
} yield ()
You can think of for as a fail-fast flow over Tries... continuing through Successes but immediately stopping on first Failure!
If right sides of all arrows are Success... the final result is Success
If any right side of any arrow is Failure... the final result is Failure (*)
(*) storing the first encountered exception
In sbt shell, run:
testOnly *TrySandboxSpec
In practice, you won't come across recursion very often in production code.
Usually higher-order functions and for-comprehensions serve for the purposes traditionally covered by imperative loops in Java.
Compiler will raise an error if the method is not tail-recursive or can be overridden.
@tailrec private def fib(n: Int): Int = {
if (n == 0) 1
else fib(n - 1) + fib(n - 2) // this won't compile!
}
How is it actually possible that we can use the word Company in the extractor?
Is there some case-class-specific magic going on?
case class Company(name: String, taxId: Int)
company match {
case Company(name: String, taxId: Int) => ...
}
No! It's just a matter of the special unapply method generated for case classes.
Let's emulate the similar effect without making Company a case class...
class Company(val name: String, val taxId: Int)
object Company {
def unapply(company: Company): Option[(String, Int)] = {
Some((company.name, company.taxId))
}
}
myCompany match {
case Company(name: String, taxId: Int) => ...
}
What we've just done is that we created the instruction on how to reduce a Company to a tuple (or, unapply it)! This is a very powerful mechanism that can be used on pre-existing types like String as well...
object Email {
def unapply(str: String): Option[(String, String)] = {
val parts = str.split("@")
if (parts.length == 2) Some(parts(0), parts(1))
else None
}
}
"pawel@lipski.com" match {
case Email(user: String, domain: String) =>
// user will be "pawel", domain will be "lipski.com"
}
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep 'do {' | wc -l
0
$ git grep 'while (' | wc -l
4
$ git grep 'for [{(]' | wc -l
463
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep ' match {' | wc -l
185
$ git grep 'case.*=>' | wc -l
954
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w 'case class' | wc -l
772
$ git grep -w '^class' | wc -l
586
$ git grep -w 'case object' | wc -l
16
$ git grep -w '^object' | wc -l
427
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w trait | wc -l
368
$ git grep -w 'sealed trait' | wc -l
23
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w flatMap | wc -l
220
$ git grep -w map | wc -l
1181
$ git grep -w 'for [{(]' | wc -l
463
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w 'Option\(T\?\)' | wc -l
1555
$ git grep -w 'Either\(T\?\)' | wc -l
665
$ git grep -w Try | wc -l
33
Software Engineer /
Entrepreneur
twitter: @pdolega
github: github.com/pdolega
mail: plipski@virtuslab.com
github: github.com/PawelLipski
mail: mpociecha@virtuslab.com
github: github.com/mpociecha
Software Engineer (mostly Scala)