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
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.
Fields and variables
In scala try to make everything immutable, except when its silly (very rare actually).
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
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):
Type inference
As well as syntactic sugar to reduce boilerplate, types will be inferred whenever
the compiler can, because why should humans type more?
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.
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.
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}
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.
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.
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.
The scala 2 ways below are not recommended…
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
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.
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).
for comprehension is sugar
Any data type which supports withFilter, map and flatMap can be used in sequence
in a for comprehension.
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.
You can read the ‘<-‘ as ‘in’.
For example, both below are the sameAs.
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.
try catch finally
while do
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.
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)
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.
eg output is Element of a tuple is [One, or One]
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}))
In Scala 3 we have new tuple types such as *:, EmptyTuple, NonEmptyTuple and methods head, tail.
You can convert a case class to a tuple because of the Mirror type class.
eg output is [Dto(1,{“key”:3})] [(1,{“key”:3})] [Dto(1,{“key”:3})]
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, it’s more than frowned upon.
Reading the type syntax
Basically, break it all down to what is left of : and what is right of :
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 does 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
Named parameters and default parameters
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
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).
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
s -> 4, t -> 5
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.
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.
Traits with constructors
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 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, so 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
Auxilary constructors
Its unusual to need them, since default values on parameters exist. But, maybe you want to have many constructors.
Extension methods
You can add anyold ad-hoc method you want to a class which doesn’t have it.
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, why? - watch some category theory utubes, or don’t).
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.
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.
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).
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
File SillyUtil.scala
Importing
Type definition
If it makes your code more readable you can define your own types as below.
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.
Generic functions
Rather than saying a function takes an Int, you could say it takes anything.
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.
Right associative operations
If an operation ends in a : 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
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.
Java collections
Loads of build in convertions, just import and say 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.
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.
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.
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.
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.
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.
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.
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.
It allows you to change the return type from a function depending on the type of a parameter.
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
UsingUser.scala
Given imports
Scala 3 makes it obvious which Given you are Using if its from an import using the import syntax:
Context bound
A Type Class has a type param, like this: class Foo[T](i:T): 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.
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.
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
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.
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.