Terse Scala 3 notes

Back in 2016 I learnt Scala 2 and made terse Scala notes. With the great new Scala 3 release I am starting again.

These are crib, crammer, revision notes based on other internet searches, and made for my own reference, but perhaps they are useful for others.

All code snippets are downloadable as a single project you can look at: https://github.com/PendaRed/scala3-terse-notes In IntelliJ, you can use the code snippet class name to find the class (ctrl-n), then click on the green triangle in the left margin to run the @main and see the output from the code.

Why Functional Programming?
Cores increase with local caches, spread over networks with local memory. Object oriention shares and maintains state using objects and that made sense in big long lived programs. In a distributed world, the data flows to the functions. No shared memory, so FP has come of age.

Michael Feathers: Object-oriented programming makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts.

Make it readable
“the ability to pass functons into other functions helps you create code that is concise and still readable - expressive.”

As with Java and C++ and so on, you can write clever unreadable Scala. If your tech lead writes code that looks like

val fn[F[A]](x:A) = x >> foo

then you can tell them to stop. All code should still use meaningful and descriptive names, and code which is too terse is worse than long winded but readable code.

Terminology

Pure functions
Pure function will produce an output which only depends on the inputs. Same input means same output. They do not modify their inputs, do not mutate hidden state or access other data other than the parameters.

Monads
Just think of them as functions which run other functions. eg any time you give a callback in another language - that callback will be executed by someone else. In FP the thing that calls your function is a Monad.

Monads typically sequence the way the ‘callback’ is called. So the map function will call the callback for each item in a collection - so its a Monad because it sequences the execution of your function.

(p.s. there are wars about how to define Monad, and in reality, none of it matters)

REPL
Read-Evaluate-Print-Loop, use :paste to enter multi line.

An alternative for playing is scastie.scala-lang.org

Syntactic Sugar
Anytime in Scala they thought of short cuts to syntax to make the code smaller they call it syntactic suger.

Imperative vs functional
Loops in imperative programs can always be modeled by recursion in functional programs. Lifted from the Scala glossary: http://docs.scala-lang.org/glossary/#functional-style

Imperative programming
The imperative style of programming emphasizes careful sequencing of operations so that their effects happen in the right order. The style is characterized by iteration with loops, mutating data in place, and methods with side effects. It is the dominant paradigm of languages such as C, C++, C# and Java, and contrasts with the functional style.

Declarative programming
Expresses the logic of a computation without describing its control flow – eg SQL, regexp, functional programming

Functional programming
The functional style of programming emphasizes functions and evaluation results and deemphasizes the order in which operations occur. The style is characterized by passing function values into looping methods, immutable data, methods with no side effects.

Scala 3 hates boilerplate
So, no semi-colon, no {}, no () for single param functions, no System.println, and so on. As mentioned above, any time you can avoid boilerplate they put a trick into the language and call it syntactic sugar.

enum Tribes:
  case Belgae, Brigantes, Cantii, Carvetii

@main def showTribes: Unit =
  println(s"Tribes enums are : ${Tribes.values.mkString("\n\t")}")

Fields and variables
In scala try to make everything immutable, except when its silly (very rare actually).

val iAmImmutable = 2
var changeMyValue = 3
changeMyValue += 1

Lazy values
You can say something is Lazy which means it is instantiated when it is first used, and if never used it is never created.

The code below produces:
Foo
Thing
THINKY
FOO

@main def lazyVal =
  lazy val thingy =
    println("Thing")
    "thing"

  val foo =
    println("Foo")
    "foo"


  if (thingy.nonEmpty) println("THINKY")
  if (foo.nonEmpty) println("FOO")

Access Modifiers
public is the default, protected is visible in derived classes, private is private to this.

There is no package scope keyword, but you can do private[traitsclassesobjects].. which makes it package scope (handy for tests):

class Person(private val name:String) : // private member
  val fullName = name+" surname" // public
  private val nickname = name.substring(0,3) // private member
  private[traitsclassesobjects] val iAmPackageScope = 2

@main def scopes =
  val p = Person("Jonathan")
  println(p.fullName)
//  println(p.name) // error
//  println(p.nickname) // error
  println(p.iAmPackageScope)

Type inference
As well as syntactic sugar to reduce boilerplate, types will be inferred whenever the compiler can, because why should humans type more?

@main def typeInference =
  val iAmInt = 3
  val iAmString = "Cornovii"

  def aTribe(i:Int) = iAmString // infers return type of String
  val s = aTribe(iAmInt)  // infers s is a string
  println(s"It works ${s.getClass.getName}")
end typeInference

Built in types
Byte, Int, Long, Short, Double, Float, String, Char - all are objects, there are no primitives.

Also BigInt and BigDecimal for really large numbers.

val x = 1_000L // L means long, note _ as a seperator for long numbers
val y = 2.2D // D means Double
val z = 3.3F // F means Float

Type hierarchy
Base is Any, extended by Matchable, extended by both AnyVal and AnyRef.

AnyVal is the base of non-nullable value types: Double, Float, Long, Int, Short, Byte, Char, Unit and Boolean.

AnyRef is the base of user defined types and everything else.

Unit
Unit has one instance which we can refer to as (). It carries no meaningful information. If a function returns no value it returns Unit.

def onlySideEffect():Unit = println("Side effect")

null
Don’t use it when coding in scala. Use Option, Either, Try, List.empty and so on.

Strings
String interpolation means you can embed expressions if you stick s in front and then use $value or ${v.value}

@main def stringInterpolation =
  def show(nm:String) = println(s"My name is $nm")

  show("Jonathan")
  show("Bill")

Multiline strings can be build up using “"”Stuff””” rather than “Stuff”, and you can interpolate as well. You can embed anything in “”“\n”\n””” without escaping it.

@main def multiLine : Unit =
  println(
    s"""I will show some maths
       |1+1=${if (Math.random()>0.5) 1+1 else 3}
       |2+2=${2 + 2}
       |""".stripMargin)

You can also use printf style formatting by prefixing with f rather than s.

No ternary operator
The java syntax (predicate==1)?”One”:”other” does not exist in scala.

Varargs A function can consume a variable number of arguments.

@main def varargs =
  def iTakeStrings(strs:String*) =
    strs.foreach(e=>println(s"I am a param printer $e"))

  iTakeStrings("1")
  iTakeStrings("a", "b")

Expressions return values
An expression returns a result, while a statement does not and is typically used for side effects - such as println(). Remember in FP a pure function has outputs which only depend on inputs and has no side effects. So you can distribute them and run them in parallel etc.

Expression-oriented programming (EOP)
When every expression returns a value it is called EOP. When code doesn’t it is a statement and only exists for its side effects.

Main method Load of ways of doing it in Scala 3.

@main def mainDef =
  println("Hello world")
@main def mainWithArg(nm:String) =
  println(s"Hello $nm")

The scala 2 ways below are not recommended…

object MyMain extends App :
  println("Hello World")
object ReallyMain :
  def main(args:Array[String]) =
    println(s"Hello ${args(0)}")

Scala control structures
Everything is an expression that returns a value - the returned value needs no return keyword, its simply the last value in the expression.

if then else

@main def ifElse =
  val x = Math.random()*10.toInt
  if x<5 then   // Note (x<5) is not needed
    println("Unlucky")
  else if x<7 then
    println("Average")
  else
    println("lucky")
  end if // This is optional

  // The if is really an expression, so returns a value, which is inferred to be string
  val result = if x<5 then "Unlucky" else "Lucky"
  println(s"You are very $result")

  // You can still use if () {} else {} format if you prefer.
  val oldWay = if (x<5) "Unlucky" else "Lucky"
  println(s"And now you are very $oldWay")
end ifElse

for do
In Scala you can create a generator from a collection and this is then looped over.

The for do is used for side effects. ie do something.

@main def forDo =
  val lst = List(1,2,3)
  for (i<-lst) do {println(s"First $i")}
  for i<-lst do println(s"Second $i")

  // A guard can be used as the last part
  for
    i<-lst
    if i>2
  do
    println(s"Third $i")

  // And you can use multiple generators and guards
  for
    i<-lst
    j<-(1 to 5) // This is a range used as a generator
    if i==2
    if j>4
  do
    println(s"Fourth i=$i j=$j")

for yield - for comprehension expressions
Rather than doing something in the loop, you want an expression which yields a result. ‘do’ above implies side effects, and we try not to do those in Scala. (Obviously all programs have side effects, and in FP libraries like Cats they wrap them in IO’s).

@main def forYield =
  val query1Results = List("Caladan Brood","Anomanda Rake")
  val query2Results = List("Rasberry Pi Model 3","Surface Pro")

  val r =
    for
      userDetails <- query1Results
      equip <- query2Results
    yield
      Tuple2(userDetails,equip)

  println(r.mkString("\n"))

  // You can use guards and multiline yields
  println(
    for
      userDetails <- query1Results
      equip <- query2Results
      if !equip.startsWith("Ras")
    yield
      val firstName = userDetails.split(" ").head
      s"$firstName likes $equip"
  )
end forYield

for comprehension is sugar Any data type which supports withFilter, map and flatMap can be used in sequence in a for compregension.

That is because each generator which generates a type is actually converted into a nested .map call, and guards are withFilter. Not only that but you can ember other values in the for comprehension which will then be placed inside the map call.

For example, both below are the sameAs.

@main def forComprehensionSugar =
  val v1 = for {
    i1 <- (1 to 3)
    _ = println(s"i1=$i1")
    j1 <- (4 to 6)
    t = j1*i1
    if (j1!=5)
  } yield (i1, j1, t)

  val v2= (1 to 3)
    .flatMap({ case i1 =>
      println(s"i1=$i1")
      (4 to 6)
        .withFilter({case j1=> j1!= 5})
        .map({ case j1 =>
          val t = j1 * i1
          (i1, j1, t)
        })
    })
  println(s"v1 = $v1")
  println(s"v2 = $v2")

match expressions
A fantastic Scala construct, which can match on the internals of case classes or on simple values, with guards (ie if something) and return expressions which return a value.

enum Sex:
  case Male, Female

case class Person(forename:String, lastName:String, gender:Sex, age:Int)

@main def matcher =
  // very silly collection on numbers and people
  val folk = Vector[Any](
    Person("Anomander", "Rake", Sex.Male, 4000),
    Person("Caladan", "Brook", Sex.Male, 4000),
    1,2,3)

  val idx = (Math.random()*folk.length).toInt
  folk(idx) match {
    // calls unaply method, underscore means don't want it
    // This extracts the fields from the case class, and uses a guard as well
    case Person(fname, _, _, _) if fname=="Anomander" =>
      println("Its Anomander the Lord")
    case p @ Person(_, "Brook", _, _) => // Matching the surname value and aliasing to p
      println(s"Its $p")
    case 1|2|3 => println("A number") // match multiple value
    case _ =>
      println("Some unimportant person")
  }

  // You can just compare by type, and you can use allias as well.
  def getAStr(x:Any):String = x match
    case i:Int => "Integer"
    case d:Double => "Double"
    case List(first, _*) => s"List with head $first"
    case anything @ _ => s"unknown ${anything.getClass.getSimpleName}" // Alliasing the wild card

  println(
    s"""So:
       |${getAStr(1)}
       |${getAStr(1.0)}
       |${getAStr(List("head","mid","tail"))}
       |${getAStr(folk(0))}
       |""".stripMargin)
end matcher

try catch finally

@main def tryCatchFinally =
  try
    val i = 0
    val j = 0
    i/j
  catch
    case ex: ArithmeticException => println(s"I expected that: ${ex.getMessage}")
    case ex: Throwable => println(s"Oh dear ${ex.getClass.getName}")
  finally
    println("I would normally close something")

while do

@main def whileLoop =
  def f(i:Int) :Int =
    println(s"called f with $i")
    if (Math.random()<0.2) -1 else i

  var x = f(1)
  while x >= 0 do x = f(x)

  x = 2
  while (x >= 0) {x = f(x)} // Scala 2 compatable

  x=f(3)
  while // multiline
    x >= 0
  do
    x = f(x)

Algebraic Data Type
Just think about them as enums. ie its a type where all the possible values are known. Then read about them on wikipedia and other places.

infix for a DSL An infix modifier on a method allows you to miss out the dot when invoking a method, and you can miss out () whenever you call a function with one parameter you can write code like ths.

case class Dragon(projectile:String="", target:String="") :
  infix def shoots(projectile:String): Dragon = this.copy(projectile=projectile)
  infix def at(target:String): Dragon = this.copy(target=target)

trait DragonsTest :
  protected val Smaug=Dragon()

class MyFirstProg extends DragonsTest:
  Smaug shoots "Fire" at "Bilbo"
  // Which is the same as
  Smaug.shoots("Fire").at("Bilbo")

Immutable Collections
List, Vector, Map, Set and so on. Its a huge library, and full of useful things like .map (loop over elements and change), forEach (loop over elements and perform a side effect) and so on. Being immutable you cannot change them, so if you add an element you get back a new collection - iternally it is usually efficient, reusing the original collection when it can.

A simple example of List, notice you can do “+”*2 which gives “++”

The output would be: +, ++, +++
+, ++, +++
+, ++, +++
List(2)

@main def simpleUsefulFunctions =
  val l = (1 to 3).toList // make a range and convert to a list

  println(l.map((item: Int) => "+" * item).mkString(", "))
  println(l.map(item => "+" * item).mkString(", "))
  // _ is positional, ie the 1st _ means the 1st parameter
  println(l.map("+" * _).mkString(", "))

  println(l.filter(_==2))

Tuples
A collection of different types, eg Tuple2(“two”, 1). There is syntactic suger which means you can miss out the TupleX part. You access the elements using (1) etc. Also you can extract the values.

eg output is: ((1,One,Dto(101,{“key”:3})), 1, One) is from (1,One,Dto(101,{“key”:3})) which is the same as (1,One,Dto(101,{“key”:3}))

@main def tuples =
  case class Dto(id:Int, json:String)
  val a = Tuple3(1, "One", Dto(101, """{"key":3}"""))
  val b = (1, "One", Dto(101, """{"key":3}"""))
  val (c,d,e) = b
  println(s"($b, $c, $d) is from $b which is the same as $a")

Map An immutable key value lookup, which you can create and use as below:

You can use a mutable.Map if you want, although beware of mutability, its more than frowned upon.

@main def maps =
  val kings = Map(
    "Penda"->"Wessex",
    "Oswald" -> "Northumbria",
    "Ecgberht" -> "Wessex")

  // convert an entry to just the value
  val kingdoms0 = kings.map(entry=>entry._2).toList // List[String]
  // Use sugar to remove boilerplace, placeholder _
  val kingdoms1 = kings.map(_._2).toList // List[String]
  // Use a partial function to extract the key and value from the entry
  val kingdoms2 = kings.map{case (k,v)=>v}.toList // List[String]
  // replace k with _ as we dont care about it
  val kingdoms3 = kings.map{case (_,v)=>v}.toList // List[String]
  // actually just grab the values
  val kingdoms4 = kings.values.toList

  // map is immutable, so add a value and get a new map.
  val kings2 = kings + ("Alfred"->"Wessex")
  println(kings2.mkString(", "))

  // You can extract the keys and values in for
  for (who, where) <- kings do println(s"$who ruled $where")
end maps

Reading the type syntax
Basically, break it all down to what is left of : and what is right of :

@main def readingTypes =
  // name colon type
  def str:String = "a string"

  // Function fn takes a parm of type String and returns type String
  def fn(param: String) : String = param.reverse

  // function looper takes
  //   a param p of type string
  //   a param fn of type : A function which takes a string and returns a string
  // and looper returns a string
  def looper(p:String, fn: String => String) : String = fn(p)

Method parameters by name or value

They can be by value or by name. If by value they are executed as the method is invoked. If by name they are executed within the method as they are used - it doesn this by (kindof) wrapping the parameter in a function which is invoked when it is used.

Note that : => Int is actually a function signature, ie the type is to the right of :, and the type is a parameterless function returning an Int.

Output below is

byValue1 = 1
byValue2 = 1
byName1 = 1
byName2 = 2

@main def PassByNameOrValue =
  var n = 0
  def counterFn() =
    n= n+1
    n
  end counterFn

  def byValue(counter:Int) =
    println(s"byValue1  = $counter")
    println(s"byValue2  = $counter")

  def byName(counter: =>Int) =
    println(s"byName1  = $counter")
    println(s"byName2  = $counter")

  byValue(counterFn())
  n=0
  byName(counterFn())

Named parameters and default parameters

@main def usingNamedParameters =
  def fn1(fname:String, sname:String, age:Int = 6) =
    s"$fname $sname $age"

  // Parameter order no longer matters, as naming the parameters
  // no value given for the default parameters
  val s = fn1(sname="Rake", fname="Anomander")
  println(s)

No parameter methods
Methods taking no parameters (arity 0) and performs side effects declare it with empty parameters. If it has no side effects leave the parenthesis off.

Functions are objects

@main def functionsAreObjects =
  val fn = foo
  val fn1 = (s:String)=>s.length
  val lst = List(fn, fn1)
  // I put the type in to show you, for each string in a list of strings
  // I call each function from my list of functions.
  val mad :List[List[Int]]= List("A","BB").map( v => lst.map(f=>f(v)))
  //  lets use _ as a placeholder for the function....(really I wouldnt in my code)
  val madAgain :List[List[Int]]= List("A","BB").map( v => lst.map(_(v)))

Eta expansion Methods belong to classes, and don’t exist unless the class instance is created. Functions can be top level functions, extension methods, accepted as parameter and so on. Once you have a class instance you can happily pass one of its methods as a parameter to a function or method which takes a function.

The conversion of the method to being a function is called eta expansion.

The underscore As part of scala’s removal of boilerplate via syntactic sugar, the underscore has been used a lot, in many ways, as shown below, although less than in Scala 2.

Note that scala 3 converts methods to functions (eta expansion) almost seamlessly, so no need for _ anymore.

Note tha scala 3 does not use _ for wildcard type for higher kinded types. It no longer uses it for wild card imports.

It does still use it to hide things when importing (more on that later).

@main def underscore =
  val l = List(1,2,3)
  // Placeholder syntax.  In anonymous function the first _ will
  // match the first param, the second the second param and so on.
  val lPlus1 = l.map(_+1)
  // Placeholder again, where _ + _ is first parm + 2nd param
  val tot = l.reduce(_ + _)

  // In a match it means anything
  "b" match
    case _ => println("Anything!")

  // When separating a number
  val aMillion = 1_000_000

  // When using an extractor (unapply) and don't care about a field
  case class Fruit(n:String, sz:Int)
  Fruit("Apple", 10) match
    case Fruit(name, _) => println(name)

When does map() need ({}) or {} You will often see .map called with (), or ({}) or {}. Since .map takes a function, ie a single parameter, then () is optional. So .map() is the same as .map({}) where _ is a positional parameter indicator, ie the first parameter, equivalent to .map( (p) => p ).

The .map can also take a partial function - of which the case expression is an example, a partial function being a function where only some of the values match, ie its a partial match. Case expressions also support the extraction of values - ie invoke the unapply, so you map see .map{case (k,v)=>k->(v+1)} as below.

This code outputs
2, 3, 4
1, 4, 3
s -> 3, t -> 4

@main def bracesInMapCall =
  val s1 = (1 to 3).map(_ + 1).mkString(", ")
  val s2 = (1 to 3).map{
    case 2 => 4
    case i => i
  }.mkString(", ")

  val s3 = Map("s"->2,"t"->3).map((k,v) => k->(v+1)).mkString(", ")

  println(
    s"""$s1
       |$s2
       |$s3
       |""".stripMargin)

Enums, Traits and Classes
When modelling in FP you should use enums for Algebraic Data Types (ADT), case classes and traits.

Enums
They can define a closed set of constants (google Algebraic Data Types). You can match on them, and compare with ==. You can also parameterise them, and give them members - fields and methods, also companion objects. You can make them compatible with java enums (but who uses Java these days?) - extends java.lang.Enum[Type].

You can even have recursive enum definitions, use the fromOrdinal method and more!

If you know, really know the full set then enums are better than traits and classes.

enum Gender:
  case Male, Female

// Don't forget the val to make it a field rather than just a ctor arg.
enum Sapient(val gender:Gender):
  case Man extends Sapient(Gender.Male)
  case Woman extends Sapient(Gender.Female)

enum Group(val desc:String, val folk:List[Sapient]):
  case Staff(f:List[Sapient]) extends Group("Staff", f)
  case Clients(f:List[Sapient]) extends Group("Clients", f)

  def countGenders():Map[Gender, Int] =
    folk.groupBy(p=>p.gender).map{case (sex, lst) => sex-> lst.size}

@main def enumAdt =
  import Group.*
  import Sapient.*
  val people = Staff(List(Woman, Man, Woman, Woman))
  // outputs 1: Male, 3: Female
  println(people.countGenders().map{case (k,v)=>s"$v: $k"}.mkString(", "))

Traits
Traits can contain abstract and concrete methods and fields and you can mix many of them into a class or object. A class can extend a class or trait, with many other traits.

trait Flyer
trait Walker
trait Crawler
trait Swimmer

class Camel(n:String) extends Walker
class Snake(n:String) extends Crawler, Swimmer
class Penguin(n:String) extends Crawler, Swimmer, Walker

Traits with constructors

trait Insect
object Butterfly extends Insect
trait Caterpiller(legs:Int) extends Insect :
  def mutate:Insect = Butterfly

In scala 2 you would use an abstract class, but in scala 3 when you have composable functionality you should use traits.

Niether case classes or normal classes need a new keyword when creating them.

Classes
Classes have constructors and can extends other classes with many Traits. The constructor is just inlined on the class definition though. See below BigCat. Constructor arguments will be made into member fields if they are used in the class, or if they are declared val or var. You can make them private if you want.

Case Classes
Case classes are immutable data types. Constructor arguments are public immutable with generated accessor (getXXX in java terms) methods. They can be used in match expressions as they have a generated unapply method, a copy method makes it easy to create a new instance with some changes, equals and hashcode, toString… basically they are great.

Do not make them mutable, ie no var fields. Use copy instead.

Example of how to mutate a case class.

case class Car(make:String, model:String)

// silly example, but shows matching on case classes
def isItOutYet(c:Car) = c match
  case Car(_, model) if model=="Model 3" => true
  case _ => false

@main def caseClassCopy =
  val c1 = Car("Tesla", "Model 3")
  val cars = List(c1, c1.copy(model="Model Y"))
  cars.foreach(c=>println(s"$c is it out?  ${isItOutYet(c)}"))

Case Objects
They are singletons which you can use in pattern matching, ie more useful than normal singletons.

Abstract Classes
Now traits have constructor parameters, one of the remaining times you may want to use an abstract class is for Java interop. An abstract class can define function which have no implementation, but then again, do can traits.

Objects in brief
Objects are Scala’s singletons - ie java static. They are initialised lazily - ie the first time they are used is when the single instance is created.

Did you know you can override a parameterless def with a val? Only concrete methods need the override specified, its optional in other situations.

Examples

trait Animal :
  def movement:String  // This function is undefined in the trait

object Human extends Animal :
  val movement = "Walks" // you can override a parameterless def with a val

class Cat extends Animal :
  // override not needed as movement is not a concrete method
  def movement = if Math.random<0.5 then "Walks" else "Jumps"

case class BigCat(name:String) extends Cat :
  println("I execute in the constructor")
  override def movement = "prowls" // override needed as its a concrete method

@main def traits =
  def move(a:Animal):String = a.movement

  println(move(Human))  // Only one Human ever
  println(move(Cat()))  // new keyword is not needed
  println(move(BigCat("Tiger")))

Auxilary constructors
Its unusual to need them, since default values on parameters exist. But, maybe you want to have many constructors.

// Primary constructor
class Person(name:String, age:Int) :
  private var weight:Double = 80

  // Auxilary constructor
  def this(name:String, age:Int, weight:Double) =
    this(name, age)
    this.weight = weight

Extension methods
You can add anyold ad-hoc method you want to a class which doesn’t have it.

@main def ExtensionMethods =
  case class Person(fname:String, sname:String)

  extension(p:Person)
    def greet:String = s"Elbow bump ${p.fname}"
    def hug:String = s"Covid alert ${p.fname.toUpperCase}"

  val ano = Person("Anomander", "Rake")
  println(ano.greet)
  println(ano.hug)

Higher order Functions
Higher order functions take functions as parameters. So you can hold references to them in variable. You can lift class methods into functions (its called Eta expansion for some reason).

It’s actually a really simple and powerful idea, you are just passing callbacks everywhere, or holding references to them.

Lambda
Anonymous functions or Lambdas can be created on the fly, and they have no function name. Anonymous function can also be called a function literal.

@main def higherOrderFunctions =
  def callMe(name:String, fn: (String, Int)=>Int) =
    fn(name, name.length)

  def oneParam(name:String, fn: (String)=>Int) = fn(name)

  def addLen(s:String, len:Int) : Int =
    len+s.length

  // Passing a function as a parameter
  println(callMe("Anomander", addLen))
  // passing a lambda
  println(callMe("Anomander", (s:String, len:Int)=> len+s.length))

  def len(s:String) = s.length

  // Sugar for single param
  println(oneParam("Anomander", s => len(s)))
  // Sugar for single param lambda, _ as a positional reference
  println(oneParam("Anomander", len(_)))
  // Sugar again for a single param, no positional reference needed!
  println(oneParam("Anomander", len))

  // making a value from an anonymous function
  val foo = (s:String) => s.length

Objects
Singletons can go into Objects. You can have collections of utility methods held by an object. Objects can also be companion objects to classes - when they are used as factory methods (apply), or to provide utilities which don’t need the classes state (fields).

Objects can extends traits and so on just like classes.

import java.time.LocalDate

object CalUtil :
  private val endDate = LocalDate.of(2012, 12, 21)
  def endOfTheWorld(d: LocalDate): Boolean = d.isEqual(endDate)

@main def objects =
  import CalUtil.*
  println(
    s"""Is today the end of the world?
       |Answer: ${if (endOfTheWorld(LocalDate.now())) "Yes" else "No"}
       |""".stripMargin)

Apply methods
Companion objects can implement apply which will be invoked as if it was a constructor. For instance you could have a class with a constructor injecting args for testing, but the default instance could be created in the companions apply method(s).

case class Foo(nm:String)
object Foo:
  def apply(i:Int,j:Int):Foo = new Foo(s"Integers $i $j")

@main def applyMethods =
  println(s"Default foo is ${Foo(1, 2)}")

Top level definitions
You can place vals, traits, enums, anything into a file and those artifacts are usable in that package and any sub package.

eg Scala file TopLevelDefs.scala

val MyHeight: Int = 202

@main def topLevelDefinitions =
  printMyHeight()

File SillyUtil.scala

def printMyHeight() = println(s"My height is $MyHeight cm")

Importing

object Person :
  val CrowdSize=10
  def sit() = println("Sit")
  def walk() = println("Walk")

@main def importEg =
  {
    import Person.*
    println(s"Crowd = $CrowdSize")
  }
  {
    import Person.{sit, walk}  // Only import some functions
    println(s"Crowd = $CrowdSize") // error!
  }

Type definition
If it makes your code more readable you can define your own types as below.

trait Fish
case class Cod(size:Double) extends Fish :
  override def toString() = "Cod"

@main def typeDefinition =
  type S = String
  type I = Int
  def display(s:S, i:I, f:Fish) = println(s"$i ${f}s $s")
  display("jump", 10, Cod(2.0) )

Self type
Within a class you can say that the concrete instance that implements me has to be of a certain type. Self can actually be any valid identier, such as fooBar.

trait Plant :
  def shineOnMe():Unit

trait Living :
  self : Plant =>
  shineOnMe()  

trait StillLiving :
  foobar : Plant => // can be any valid identifier
  shineOnMe()

Generic functions
Rather than saying a function takes an Int, you could say it takes anything.

def ordinaryFn(i:Int) = "Silly Eg"+i
def genericFn[T](i: T) = "Silly Eg"+i

@main def genericFunction =
  println(ordinaryFn(2))
  println(genericFn(3))
  println(genericFn("Horse"))

Packages
You can have multiple packages in one file, and even nest them in one file to restrict scope. Typically packages follow directory naming and structure.

Imports
No longer use _ to wildcard, they use *. You can import and rename things. You can use _ to hide things from a wildcard import.

In scala you automatically import java.lang.* and scala.* as well as Predef. If some idiot has caused a name conflict you can import from the root of the project using import root.whatever.*

Importing given’s is discussed later.

package foo {
  import bar.{silly1, silly3} // multiple imports
  val TopLevelInt1 = 1
  val TopLevelInt2 = 2
  val TopLevelInt4 = 2
}

package bar {
  import foo.*
  def silly1() = println(TopLevelInt1)
  def silly2() = println(TopLevelInt2)
  def silly3() =
    import watever.TopLevelInt3 // imports can be anywhere
    println(TopLevelInt3)
  object P2:
    def doStuff() = println("Stuff done")
}

package watever {
  val TopLevelInt3 = 3
  import foo.{TopLevelInt1 as _, TopLevelInt4 as _, *} // hide TopLevelInt1 and 4
  import bar.{silly1 as barSilly1} // rename it
  def silly1() = barSilly1()

  class G(n:String):
    import bar.P2.*  // can access P2.doStuff as doStuff now
    doStuff()
}

Right associative operations If an operation ends in :: then its actually a method call on the thing to the right of the operation. Its a useful trick for immutable lists, where you have a List and want to prepend something to it, but want it to read like you are creating the list from left to right. see the eg below

// using cons, ie :: to concatinate lists, it ends in : so is right associative
// ie its actually a call on Nil which is a list singleton.
val l1 = "a" :: "b" :: "c" :: Nil
val sameAs = Nil.::("c").::("b").::("a")
println(s"${l1.mkString(",")}\n${sameAs.mkString(",")}")

Brief overview of collections
When you need to use them (always) google them and learn the APIs.

I’ve highlighted some of the funky things below. There are loads more, but you simple need to practice and google the APIs.

Lists are linked lists, so if large and you need to access by index use a Vector, or if mutable an ArrayBuffer.

@main def funkyStuff =
  val str = "abcd"
  // abc,cd,ab,b,abcd,c,bcd,d,bc,a
  println(getAllStringsIn(str).mkString(","))

  val l = List.empty[String] // create an empty list
  val lAgain = List("a", "b", "c")
  val l1 = "a" :: "b" :: "c" :: Nil
  // Mattern matching on strings
  l1 match
    case List(h , _*) => println(s"First element is $h")
  l1 match
    case List(h , "b", "c") => println(s"First element is $h")

  val l3 = l1 :+ "d" // add to end, List(a, b, c, d)
  val l4 = "_" :: l1  // add to start, List(_, a, b, c)
  val l5 = "_" +: l1  // add to start, List(_, a, b, c)
  println(""+l3+"\n"+l4+"\n"+l5)

  // You should google and work this out for yourself.
  val a1 = List("A", "A", "B", "C", "C")
  val luvFolds = a1.foldLeft(scala.collection.mutable.Map.empty[String, Int])(
    (acc,element)=>
      val count :Int = acc.getOrElse(element, 0) +1
      acc += (element->count)
  )
  val whoNeedsFold = a1.groupBy(e=>e).map( (k,v)=>(k->v.size))
  println(""+luvFolds+"\n"+whoNeedsFold) // prints HashMap(A -> 2, B -> 1, C -> 2) twice

  List((1,"a"), (2, "b")).toMap // convert a list of Tuple2s to a map

  // Using a collection as a generator in a for statement
  for i <- a1 do println(s"Well $i")

Java collections
Loads of build in convertions, just import and say asScala

@main def javaInterop =
  import java.util.List as JavaList

  def gimmeAJavaList() =
    import scala.jdk.javaapi.CollectionConverters
    CollectionConverters.asJava(List("a", "b"))

  import scala.jdk.CollectionConverters.*
  val javaList: java.util.List[String] = gimmeAJavaList()
  val scalaList = javaList.asScala

Try
Scala does not declare checked exceptions in function signatures. You can still code as below using try{}catch{..}, but better to use Try, or Either, or Option.

Some examples of approaches. In my experience, if you are coding a database layer, everything should return a Try[], and queries should return Try[Option[]], so you can differentiate a SQL exception from an empty result.

import java.security.InvalidParameterException
import scala.util.*

def iCanFail() :Int =
  if Math.random()<0.5 then throw new InvalidParameterException("Bang")
  1

// Returns a Success(1) or a Failyre(InvalidParameterException("Bang"))
def iCanFailBetter() : Try[Int]=
  Try {
    if Math.random() < 0.5 then throw new InvalidParameterException("Bang")
    1
  }

@main def execptions =
  // You can still do this, but really you should not.
  try
    iCanFail()
  catch
    case ex:InvalidParameterException => // expected
    case ex @ _ => println(s"NOT EXPECTED - ${ex.getClass.getName}:${ex.getMessage}")
  finally
    println("Done")

  iCanFailBetter()
    .map(println(_))  // Only Success(result) will execute this
    .recover{case ex:Throwable=> Try{println(s"NOT EXPECTED - ${ex.getClass.getName}:${ex.getMessage}")}}
  // Or
  iCanFailBetter() match
    case Success(i) => println(i)
    case Failure(ex) => println(s"NOT EXPECTED - ${ex.getClass.getName}:${ex.getMessage}")

  val result : Try[Int]= for {
    res1 <- iCanFailBetter()
    res2 <- iCanFailBetter() // only fires if res1 works
  } yield res2
  result
    .map(println(_))  // Only Success(result) will execute this
    .recover{case ex:Throwable=> Try{println(s"NOT EXPECTED - ${ex.getClass.getName}:${ex.getMessage}")}}

Either
A way to return a Left(“Bad value”), or Right(“Success value”). If a success then .map will work. So a function returning Either will return either Left or Right and you can pattern match on it.

@main def either =
  def someThingDone():Either[String, String] =
    if Math.random()<0.5 then Left("Failed") else Right("Worked")

  // loop 5 times
  val l = (1 to 5).map(v=>
    someThingDone().map(v => "Good" +v).orElse(Right("Recovered"))
  )

  l.foreach(v=>v match {
    case Right(s) => println(s)
    case Left(ex) => println("Impossible")
  })

Option
No nulls in scala (well there are, but don’t code like that). An Option[String] can be Some(“string”) or None. Calling .map will only work for Some, None will not run the function. You can convert Try, Either and Option with toTry etc, so they are all pretty similar.

@main def egOption =
  def anOptionFn(f:String, l:String, m:Option[String]=None) =
    s"$f ${m.getOrElse("")} $l"

  println(anOptionFn("J", "G"))
  println(anOptionFn("J", "G", Some("P")))
  val o = Option(null) // gives a None, useful when calling Java
  o.foreach(v=>println("Will never print since o is None"))
  o.fold(println("Shows if None"))(v=>println("Shows if some")) // Nice trick
  o match
    case Some(v)=>println("v="+v)
    case None => println("None")

Type parameters
You can provide a type parameter to create a generic class, trait or function. This should have a single letter, ie [T] - scala convention.

Formatter below can take any type which supports toString. Its a silly example but at least its not a collection which is what everyone else uses.

case class Formatter[T](thing:T) :
  def formatMe : String =
    thing.toString.replace("\n", "\n\t\t")

@main def generalGeneric =
  val g1 = Formatter(1)
  val g2 = Formatter(
    """I am
      |a string
      |oh yes
      |""".stripMargin)

  println(g1.formatMe)
  println(g2.formatMe)

Generic types by default have non-varient subtyping
So if you define a parameterised stack and instantiate a Stack[String] and a Stack[Any] there is no sub-type relationship between them, even though Any is a subtype of String.

Co-varient subtyping
Putting a plus sign (+) before the type parameter. The class or trait then subtypes covariantly with—in the same direction as—the type annotated parameter. For example, List is covariant in its type parameter, so List[String] is a subtype of List[Any].

Contravarient subtyping
Putting a minus sign (-) before the type parameter. The class or trait then subtypes contravariantly with—in the opposite direction as—the type annotated parameter. For example, Function1 is contravariant in its first type parameter, and so Function1[Any, Any] is a subtype ofFunction1[String, Any].

Type parameter which is a sub type of base type (upper bound)
Written [T <: MyClass] means I will take any type derived from MyClass.

Type parameter which is a super type of another type (lower bound)
Retrict a type parameter to range over supertypes. Written [T >: MyClass].

Type parameter view bound
Written [T <% MyClass] means I will take any type which can be converted to type MyClass.

Intersection types
New to Scala 3, A & B represents values that are both A and B. Note that & is commutative: A & B is the same type as B & A.

@main def intersectionTypes =
  trait Printable:
    def printMe:String
  trait Reportable:
    def reportMe:String

  def printReverse(s: Printable & Reportable) =
    s.reportMe
    s.printMe

Union types
It can be either Foo or Bar if written Foo | Bar. Again it is commutative: A | B is the same as B | A.

@main def unionTypes =
  case class RefreshListAction(rowNun:Int)
  case class CallServerAction(actionName:String)
  type UiAction = RefreshListAction | CallServerAction

  def reduceer(action: UiAction) = action match
    case RefreshListAction(rowNum)=>// do stuff
    case CallServerAction(actionName)=>// do stuff

Opaque Types
If you define a type SafeName = String in your class then others can see what the type is, and rather than use SafeName they could use the String. By making this opaque they will not be able to replace SafeName with a string.

@main def opaqueType =
  object SafePerson :
    type SafeMobile = String
    opaque type SafeName = String
    def genName(n:String):SafeName = n
    def showSafe(n:SafeName, m:SafeMobile):String = s"$n m:$m"

  val name : SafePerson.SafeName = SafePerson.genName("Anomander")
  val mobile : String = "000100010001"

  // mobile was allowed as a String paraameter, it leaked out that String was fine.
  // but name had to go through the genName factory function
  // which created the right type.  Only SafePerson is aware that SafeName is
  // implemented as a String.
  SafePerson.showSafe(name, mobile)

Structural Types
If you are writing a database framework where you want to make column names as keys to column values then these are golden. You extends the marker trait scala.Selectable and implement def selecteDynamic and then you can provide refinement types for each result set you may ever get back.

For the rest of us, I’m not sure they apply, so no examples given.

Dependent Method Types
If you are a library developer then you will use this. No one else will I hope.

Given and Using for contextual parameters

Cannonical
literally a ‘rule’, and has also come to mean a ‘standard’.

Canonical form, a natural unique representation of an object, or a preferred notation for some object.

Canonical form, data that has been cannonicalised into a completely unique representation, from a previous form that had more than one possible representation.

back to Given and Using Rather than pass parameters through your system, sometimes a large part of the code wants access to the same kind of thing. Obviously global variables are bad - so you do want to pass them through the code, but that can create lots of boilerplate.

Scala has this idea of a bag of objects, classes and functions which can be keyed by type. Imagine you have a DbConnection and 100 classes which need it. Rather than pass them in by hand you could say that each class needs to Use a DbConnection. Someone else will Give the current canonical connection.

I will Give this dbCon of type DBConnection. I want to Use a connection of type DBConnection.

Maybe during testing we could replace the instance of DBConnection by Giving another class for it, and from then on functions Using it will get the new one. ie it looks up the value by the type from this bag of stuff, finding the current canonical one.

In scala you can specify Given’s by type and instance, and then Use them by their type to find the instance.

userSessionUtils.scala

package com.jgibbons.tersescala.l.givenusing

import java.time.LocalDateTime

case class User(id:Int, username:String, permissions:Map[String,Boolean])
case class UserSession(user:User, startTimeMs:LocalDateTime = LocalDateTime.now)

object SessionUtil {
  def logUser()(using u:User) :Unit=
    println(u.username)

  def confirmPermission(perm:String)(using u:User) :Boolean=
    u.permissions.getOrElse(perm, false)

  // Notice here we do not access User in this function, so no local variable name
  def isAllowed(perm:String)(using User) : Boolean =
    if (confirmPermission(perm)) then
      logUser()
      true
    else
      false

  def doSearch()(using sess:UserSession) = {
    // explicit using param extracted from my contextual param
    if (isAllowed("search")(using sess.user))
      println("I am searching")
    else
      println("Disallowed")
  }
}

UsingUser.scala

package com.jgibbons.tersescala.l.givenusing

import com.jgibbons.tersescala.l.givenusing.UserSession

@main def UsingUser =
  import SessionUtil.*

  val u1 = User(1,"Anomander", Map("search"->true))
  val sess = UserSession(u1)

  // Explicitly use u1 as the contextual parameter
  confirmPermission("search")(using u1)
  // Now make u1 a contextual parameter, so no need to give it
  given User = u1
  logUser()

  if (isAllowed("saerch")) then
    println("foo")
  end if
  {
    // put into its own expression block to scope the given
    given UserSession = sess
    doSearch()
  }
  // Cannot redefine the given instance in the same scope
  given UserSession = UserSession(u1.copy(permissions = Map.empty))
  doSearch()

Given imports
Scala 3 makes it obvious which Given you are Using if its from an import using the import syntax:

import com.jgibbons.something.given  // This imports the given
import com.jgibbons.something.*  // This imports everything other than the given
import com.jgibbons.something.{given, *}  // merge of the above two imports

Context bound
A Type Class is like class FooT: ie it has a member field i of some type T - eg a String or Int or Option etc. If you want to also have an contextual parameter using Foo[T], then we can say [T:Foo], ie we also want a contextual Foo[T]. Hopefully the snippet below illustrated.

case class UserDao[T]() :
  def store(j:T) = println("do store")

object UserDaoUtil :
  def doStore[T](n:T)(using dao:UserDao[T]) =
    dao.store(n)

// Longhand, using clause
case class Foo[T](i:T)(using UserDao[T]) :
  UserDaoUtil.doStore(i)

// shorthand syntactic sugar
// Note the T : UserDao, this is context bound.  ie a contextual paramete
// must be Given which is of type UserDao[T]
case class FooCithContextBound[T : UserDao](i:T) :
  UserDaoUtil.doStore(i)

Type Class
Normally, to add behaviour to an existing class you would extend it, but being functional coders we do not want to (that is very informal, but I recon its true). So, a ‘type class’ is an abstract, parameterized type that lets you add new behaviour to any closed data type without using sub-typing.

You create a trait with one or more parameters, and then implement it with given instances - see mynewlibrary below.

import java.util.Random

// Library written for a hour planning app
package kitchenlibrary :
  case class Spoon(metal:String)
  case class Fork(metal:String)
  case class FryingPan(diameter:Int)
end kitchenlibrary

// Library made for a battle simulator
package garagelibrary :
  import java.util.Random

  trait Tool(damage:Int) :
    def damage()(using rnd:Random):Int = damage/2+rnd.nextInt(damage)

  case class Hammer() extends Tool(10)
  case class Wrench() extends Tool(15)
  case class ScrewDriver() extends Tool(5)
end garagelibrary

// Now I want to fight in the house
package mynewlibrary :
  import kitchenlibrary.*
  import garagelibrary.ScrewDriver

  trait Weaponized[A]:
    extension(a:A) def damageCaused()(using rnd:Random):Int

  given Weaponized[ScrewDriver] with
    extension(w:ScrewDriver) def damageCaused()(using rnd:Random):Int = w.damage()
  given Weaponized[Spoon] with
    extension(s:Spoon) def damageCaused()(using rnd:Random):Int = 2
  given Weaponized[Fork] with
    extension(s:Fork) def damageCaused()(using rnd:Random):Int = 4
  given Weaponized[FryingPan] with
    extension(s:FryingPan) def damageCaused()(using rnd:Random):Int = 7
end mynewlibrary

@main def egTypeClass = {
  import kitchenlibrary.*
  import garagelibrary.*
  import mynewlibrary.{given, *}

  def showDam[T:mynewlibrary.Weaponized](l:T)(using Random) =
    println(s"Damage taken from ${l.getClass.getSimpleName} is ${l.damageCaused()}")


  val rnd = new Random(20)
  given Random = rnd

  showDam(Spoon("Silver"))
  showDam(ScrewDriver())
}

Implicit Conversions
Create given instances of scala.Conversion and you can convert types automatically by using them.

The output is
FIRST
dto->dbRow
SECOND
dbRow->dto
dto->dbRow
dbRow->dto

case class PersonDto(fname:String, sname:String)
case class PersonDbRow(id:Option[Long], fname:String, sname:String)

@main def implicitConversion =

  given Conversion[PersonDto, PersonDbRow] with
    def apply(i:PersonDto):PersonDbRow =
      println("dto->dbRow")
      PersonDbRow(None, i.fname, i.sname)

  // Shorter version
  given Conversion[PersonDbRow, PersonDto]  =
    i=>
      println("dbRow->dto")
      PersonDto(i.fname, i.sname)

  // to enable implicit conversions in the file.
  import scala.language.implicitConversions

  // Insane this..
  // so the return type being dbRow means it invokes the conerter and returns the dbRow
  def takeInDtoGiveDb(dto:PersonDto):PersonDbRow = dto

  // Proof
  println("FIRST")
  val dbRow:PersonDbRow = takeInDtoGiveDb(PersonDto("Anomander","Rake"))
  // But Look, the converter changes the row param to the dto BEFORE calling the function
  // then it converts the dbRow returns to the dto again using the converter
  println("SECOND")
  val dto:PersonDto = takeInDtoGiveDb(dbRow)

Multiversal equality
If you are the kind of dev who worries that s==i compiles, but if s and i are different types will equate to false, and instead you want a compiler error then multiversal equality is for you. You should google it.

Future
“A Future represents a value which may or may not currently be available, but will be available at some point, or an exception if that value could not be made available.”

In scala there is a global execution context with a number of threads, and a Future by default uses this (if you import it). When you declare the Future it immediately gets scheduled to run on a thread from the pool (if available).

To run code when the furture completes you can use .onComplete to handle success or failure. Or .foreach or .andThen, or .recover, .recoverWith. Google them or glance below.

import java.time.*

def doSomeStuff(pauseMs:Long = 500, thenFail:Boolean = false) : LocalDateTime=
  println(s"${Thread.currentThread().getName} Busy")
  Thread.sleep(pauseMs)
  println(s"${Thread.currentThread().getName} ..Done")
  if (thenFail) throw new Exception("Failed")
  LocalDateTime.now()

@main def egFuture =
  import scala.concurrent._
  import scala.concurrent.duration._
  import scala.util.{Success, Failure}
  import concurrent.ExecutionContext.Implicits.global

  // just run something synchronously, is on my thread
  println(s"Synchronous finished at ${doSomeStuff()}")

  // The inferred type is Future[LocalDateTime]
  val notReadyYet : Future[LocalDateTime]= Future{doSomeStuff()}
  Await.result(notReadyYet, Duration.Inf) // normally not done, but I want you see the effect
  notReadyYet.onComplete {
    case Success(tm) => println(s"Async1 finished at $tm")
    case Failure(ex) => println(s"Failued with ${ex.getClass.getName}")
  }

  // Or without the interim variable
  Future{doSomeStuff()} onComplete {
    case Success(tm) => println(s"Async2 finished at $tm")
    case Failure(ex) => println(s"Failued with ${ex.getClass.getName}")
  }
  // Or you don't care about failure
  Future{doSomeStuff()}.foreach(tm =>println(s"Synchronous finished at $tm"))
  Thread.sleep(600)

  val fallback:LocalDateTime = LocalDateTime.of(2021,6,5,11,10,50)
  // Or you do care, and recover with
  Future{doSomeStuff(thenFail = true)}.recover {
      case ex:Throwable => fallback
    }.foreach(tm=>println(s"Failure finished at $tm"))
  Thread.sleep(600)

Future in for comprehension
Scala will start the thread as soon as the Future is constructed (if there is a thread available.) So to run Futures in parallel you could start some off, and then use a for comprehension.

MORE TO ADD SOON!  Now I have a working scala 3 microservice I can keep going....

** **

** **

** **

** **