1) General Rules About Null and Option
1) General Rules About Null and Option
1) General Rules About Null and Option
com/scala/scala-null-values-option-uninitialized-variables
https://alvinalexander.com/scala/best-practice-eliminate-null-values-from-code-scala-idioms
We begin with the following general rules regarding the use of null values in
Scala code:
As you can infer by this statement, it's important to note that Scala collection
classes are created to work with Options. While using Option with constructs
like for comprehensions and match expressions are nice, an enormous benefit
of using Option is that they're well supported by the methods of the collection
classes.
There are times you will need to create uninitialized fields in a Scala class.
When this need arises, use one of these approaches:
o Assign a default to the field that isn't null, such as an empty list,
array, or vector.
o If the field can be accessed before its initialized, declare it as
an Option, with a default value of None.
3) Writing Scala methods
o Never return null.
o If you feel like returning a null value from a method, declare that
the method will return an Option[T] (such as Option[String]), then
return Some[T] and None from the method.
Benefits
Following these “rules” will eventually lead to the following benefits for you
and your Scala code:
Nil– Represents an emptry List of anything of zero length. Its not that it refers
to nothing but it refers to List which has no contents.
None– Used to represent a sensible return value. Just to avoid null pointer
exception. Option has exactly 2 subclasses- Some and None. None signifies
no result from the method.
Note: Any is supertype of AnyRef and AnyVal. AnyRef is the supertype of all
the reference classes (like String, List, Iterable) in scala. AnyVal is the
supertype of all the value classes (like Int, Float, Double, Byte, Short..). Null is
a subtype of all the reference classes. null is its only instance. Nothing is
subtype of every other type i.e of reference and value classes.
null
Scala’s null is the same as in Java. Any reference type can be null, like Strings, Objects, or
your own classes. Also just like Java, value types like Ints can’t be null. Odds are you'll see
this one.
Null
Null is a trait whose only instance is null. It is a subtype of all reference types, but not of
value types. It purpose in existing is to make it so reference types can be assigned null and
value types can’t. Don't worry about this one.
Nothing
Nothing is a trait that is guaranteed to have zero instances. It is a subtype of all other types.
It has two main reasons for existing: to provide a return type for methods
that never return normally (i.e. a method that always throws an exception). The other
reason is to provide a type for Nil (explained below). Don't worry about this one.
Nil
Nil is just an empty list, exactly like the result of List(). It is of type List[Nothing]. And since
we know there are no instances of Nothing, we now have a list that is statically verifiable as
empty. Nice to have. Odds are you'll see this one.
None
None is the counterpart to Some, used when you’re using Scala’s Option class to help
avoid null references. If you’re not familiar with the idea of Option or Maybe, here’s
an introduction to Option. Odds are you'll see this one.
Unit
Unit in Scala is the equivalent of void in Java. It’s used in a function’s signature when that
function doesn’t return a value. Odds are you'll see this one.
scala> Option(str1).getOrElse("XXX")
res0: String = abc
scala> Option(str2).getOrElse("XXX")
res1: String = XXX
Solution
Possibly the most tempting time to use a null value is when a field in a class or
method won’t be initialized immediately. For instance, imagine that you’re
writing code for the next great social network app. To encourage people to sign
up, during the registration process, the only information you ask for is an
email address and a password. Because everything else is initially optional,
you might write some code like this:
At some point later you can assign the other values like this:
u.firstName = Some("Al")
u.lastName = Some("Alexander")
Later in your code, you can access the fields like this:
println(firstName.getOrElse("<not assigned>"))
Or this:
u.address.foreach { a =>
println(a.city)
println(a.state)
println(a.zip)
In both cases, if the values are assigned, they’ll be printed. With the example
of printing the firstName field, if the value isn’t assigned, the string <not
assigned> is printed. In the case of the address, if it’s not assigned,
the foreach loop won’t be executed, so the print statements are never reached.
This is because an Option can be thought of as a collection with zero or one
elements. If the value is None, it has zero elements, and if it is a Some, it has one
element — the value it contains.
On a related note, you should also use an Option in a constructor when a field
is optional:
id: Long,
This brings up the question, “If you can’t return null, what can you do?”
Answer: Return an Option. Or, if you need to know about an error that may
have occurred in the method, use Try instead of Option.
For instance, when reading a file, a method could return null if the process
fails, but this code shows how to read a file and return an Option instead:
try {
Some(io.Source.fromFile(filename).getLines.toList)
} catch {
}
This method returns a List[String] wrapped in a Some if the file can be found
and read, or None if an exception occurs.
Try(io.Source.fromFile(filename).getLines.toList)
readTextFile(filename) match {
This code prints the lines from the /etc/passwd file if the code succeeds, or
prints an error message like this if the code fails:
java.io.FileNotFoundException: Foo.bar (No such file or directory)
A Null Object is an object that extends a base type with a “null” or neutral
behavior. Here’s a Scala implementation of Wikipedia’s Java example of a Null
Object:
trait Animal {
def makeSound()
def makeSound() {}
}
The third major place you’ll run into null values is in working with legacy Java
code. There is no magic formula here, other than to capture the nullvalue and
return something else from your code. That may be an Option, a Null Object,
an empty list, or whatever else is appropriate for the problem at hand.
}
Currying – decomposition of function with multiple arguments into a chain of
single-argument functions.
Notice, that Scala allows to pass a function as an argument to another
function.
Currying examples
Partial application examples
def isInRange(left: Int, n: Int, right: Int): Boolean = {
if (left < n && n < right) true else false
}
def is5InRange = isInRange(_: Int, 5, _: Int)
//(Int, Int) => Boolean
is5InRange(0, 8)
//true
def between0and10 = isInRange(0, _: Int, 10)
//Int => Boolean
between0and10(5)
//true
between0and10(100)
//false
https://stackoverflow.com/questions/18468786/understand-how-to-use-apply-and-unapply
https://theburningmonk.com/2017/01/from-f-to-scala-extractors/
The apply function
In Scala, if you assign a function to a value, that value will have the type Function1[TInput,
TOutput]. Since everything in Scala is an object, this value also have a couple of functions on
it.
You can use andThen or compose to compose it with another function (think of them as
F#‘s » and « operators respectively).
The apply function applies the argument to the function, but you can invoke the function
without it.
Ok, now that we know what apply function’s role is, let’s go back to object.
If you declare an apply function in an object, it essentially allows the object to be used as a
factory class (indeed this is called the Factory pattern in Scala).
You see this pattern in Scala very often, and there are some useful built-in factories such
as Option (which wraps an object as Some(x) unless it’s null, in which case returns None).
String and BigInt defines their own apply function too (in String’s case, it returns the char at
the specified index) .
You can also define an apply function on a class as well as an object, and it works the same
way. For instance…
I find this notion of applying arguments to an object somewhat alien, almost as if this is an
elaborate way of creating a delegate even though Scala already have first-class functions…
Check, and Check. Although this is consistent with inheritance and OOP in Java, I can’t help
but to feel it has the potential to create ambiguity and one should just stick with plain old
functions.
side note: point 2 is interesting. Looking at all the examples on the Internet one
might assume the unapply function must return an Option[T], but turns out it’s OK to return
any type so long it has the necessary members!
Whilst I can’t think of a situation where I’d need to use anything other than an Option[T],
this insight gives me a better understanding of how pattern matching in Scala works.
Whether or not the pattern matches is determined by the value of isEmpty of the result
type T. And the value returned by your pattern — ie msg in the example above — is
determined by the value of get of the result type T. So if you’re feeling a bit cheeky, you can
always do something like this:
Since the unapply function is a member on an object (like the apply function), it means it
should work with a class too, and indeed it does.
As you can see from the snippet above, this allows you to create parameterized
patterns and work around the limitation of having only one argument
in the unapply function.
You can nest patterns together too, for example.
Here, the Int pattern returns an Int, and instead of binding it to a name we can apply
another pattern inline to check if the value is even.
And whilst it doesn’t get mentioned in any of the articles I have seen, these patterns are not
limited to the match clause either. For instance, you can use it as part of declaration. (but be
careful, as you’ll get a MatchError if the pattern doesn’t match!)
https://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html
Array Buffers
An ArrayBuffer buffer holds an array and a size. Most operations on an array buffer have
the same speed as for an array, because the operations simply access and modify the
underlying array. Additionally, array buffers can have data efficiently added to the end.
Appending an item to an array buffer takes amortized constant time. Thus, array buffers
are useful for efficiently building up a large collection whenever the new items are
always added to the end.
scala> buf += 1
scala> buf += 10
scala> buf.toArray
res34: Array[Int] = Array(1, 10)
List Buffers
A ListBuffer is like an array buffer except that it uses a linked list internally instead of an
array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead
of an array buffer.
scala> buf += 1
scala> buf += 10
scala> buf.toList
res37: List[Int] = List(1, 10)
StringBuilders
Just like an array buffer is useful for building arrays, and a list buffer is useful for building
lists, a StringBuilder is useful for building strings. String builders are so commonly used
that they are already imported into the default namespace. Create them with a
simple new StringBuilder, like this:
buf: StringBuilder =
scala> buf.toString
res41: String = abcdef
Linked Lists
Linked lists are mutable sequences that consist of nodes which are linked with next
pointers. They are supported by class LinkedList. In most languages null would be picked
as the empty linked list. That does not work for Scala collections, because even empty
sequences must support all sequence methods. In
particular LinkedList.empty.isEmpty should return true and not throw
a NullPointerException. Empty linked lists are encoded instead in a special way:
Their next field points back to the node itself. Like their immutable cousins, linked lists
are best traversed sequentially. In addition linked lists make it easy to insert an element
or linked list into another linked list.
Mutable Lists
A MutableList consists of a single linked list together with a pointer that refers to the
terminal empty node of that list. This makes list append a constant time operation
because it avoids having to traverse the list in search for its terminal node. MutableList is
currently the standard implementation of mutable.LinearSeqin Scala.
Queues
Scala provides mutable queues in addition to immutable ones. You use a mQueuesimilarly
to how you use an immutable one, but instead of enqueue, you use the += and +
+= operators to append. Also, on a mutable queue, the dequeuemethod will just remove
the head element from the queue and return it. Here’s an example:
scala> queue
scala> queue.dequeue
res13: String = a
scala> queue
Array Sequences
Array sequences are mutable sequences of fixed size which store their elements
internally in an Array[Object]. They are implemented in Scala by class ArraySeq.
You would typically use an ArraySeq if you want an array for its performance
characteristics, but you also want to create generic instances of the sequence where you
do not know the type of the elements and you do not have a ClassTag to provide it at
run-time. These issues are explained in the section on arrays.
Stacks
You saw immutable stacks earlier. There is also a mutable version, supported by
class mutable.Stack. It works exactly the same as the immutable version except that
modifications happen in place.
scala> stack.push( 1)
scala> stack.push( 2)
scala> stack
scala> stack.top
res8: Int = 2
scala> stack
res9: scala.collection.mutable. Stack[Int] = Stack(1, 2)
scala> stack.pop
res10: Int = 2
scala> stack
Array Stacks
ArrayStack is an alternative implementation of a mutable stack which is backed by an
Array that gets re-sized as needed. It provides fast indexing and is generally slightly more
efficient for most operations than a normal mutable stack.
Hash Tables
A hash table stores its elements in an underlying array, placing each item at a position in
the array determined by the hash code of that item. Adding an element to a hash table
takes only constant time, so long as there isn’t already another element in the array that
has the same hash code. Hash tables are thus very fast so long as the objects placed in
them have a good distribution of hash codes. As a result, the default mutable map and
set types in Scala are based on hash tables. You can access them also directly under the
names mutable.HashSet and mutable.HashMap.
Hash sets and maps are used just like any other set or map. Here are some simple
examples:
scala> val map = scala.collection.mutable.HashMap.empty[Int,String]
res43: map. type = Map(1 -> make a web site, 3 -> profit!)
scala> map( 1)
Iteration over a hash table is not guaranteed to occur in any particular order. Iteration
simply proceeds through the underlying array in whichever order it happens to be in. To
get a guaranteed iteration order, use a linked hash map or set instead of a regular one. A
linked hash map or set is just like a regular hash map or set except that it also includes a
linked list of the elements in the order they were added. Iteration over such a collection
is always in the same order that the elements were initially added.
Concurrent Maps
A concurrent map can be accessed by several threads at once. In addition to the
usual Map operations, it provides the following atomic operations:
m replace (k, old, Replaces value associated with key k to new, if it was previously
new) bound to old.
WHAT IT IS WHAT IT DOES
ConcurrentMap is a trait in the Scala collections library. Currently, its only implementation
is Java’s java.util.concurrent.ConcurrentMap, which can be converted automatically into
a Scala map using the standard Java/Scala collection conversions.
Mutable Bitsets
A mutable bit of type mutable.BitSet set is just like an immutable one, except that it is
modified in place. Mutable bit sets are slightly more efficient at updating than immutable
ones, because they don’t have to copy around Longs that haven’t changed.
scala> bits += 1
scala> bits += 3
https://learning.oreilly.com/learning-paths/learning-path-scala/9781789616002/9781788295567-
video2_6
Scala Enumerations
https://pedrorijo.com/blog/scala-enums/
scala> Weekday.Monday.toString
res0: String = Monday
scala> Weekday.withName("Monday")
res1: Weekday.Value = Monday
scala> Weekday.withName("Mondai")
java.util.NoSuchElementException: No value found for
'Mondai'
at scala.Enumeration.withName(Enumeration.scala:124)
... 32 elided
scala> Weekday.Monday.toString
res0: String = Mo.
scala> Weekday.values
res0: Weekday.ValueSet = Weekday.ValueSet(Monday,
Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
scala> Weekday.values.toList.sorted
res0: List[Weekday.Value] = List(Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday, Sunday)
object Weekday extends Enumeration {
val Monday = Value(1)
val Tuesday = Value(2)
val Wednesday = Value(3)
val Thursday = Value(4)
val Friday = Value(5)
val Saturday = Value(6)
val Sunday = Value(0)
}
scala> Weekday.values.toList.sorted
res1: List[Weekday.Value] = List(Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday)
Problems with scala.Enumeration
Nevertheless, this approach has some problems. There are two main
disadvantages:
In Scala we heavily rely on the compiler powerful type system, and with this
approach, the compiler is not able to find non exhaustive pattern matching
clauses, nor to use overloaded methods for different Enumerations.
Another really nice feature, is the possibility to include more fields on the
enumeration values (Scala enumerations only provides an index and a name).
Just use a (sealed) abstract classinstead of a (sealed) trait.
itemized
itemized is a OSS lib that’s part of rbricks, a collection of composable, small-
footprint libraries for Scala.
import io.rbricks.itemized.annotation.enum
scala> ItemizedCodec[Weekday].fromRep("Monday")
res0: Option[Weekday] = Some(Monday)
scala> weekday.toRep
res1: String = Earth
Problems with itemized
Although it makes easier to create type safe enumerations by using small
annotations, it also brings some disadvantages:
enumeratum
Enumeratum is a type-safe and powerful enumeration implementation for
Scala that offers exhaustive pattern match warnings.
import enumeratum._
scala> Weekday.withName("Monday")
res0: Weekday = Monday
scala> Weekday.withName("Momday")
java.util.NoSuchElementException: Momday is not a member
of Enum (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday)
at enumeratum.Enum$
$anonfun$withName$1.apply(Enum.scala:82)
at enumeratum.Enum$
$anonfun$withName$1.apply(Enum.scala:82)
at scala.Option.getOrElse(Option.scala:121)
at enumeratum.Enum$class.withName(Enum.scala:81)
at Weekday$.withName(<console>:13)
... 43 elided
scala> Weekday.withNameOption("Monday")
res2: Option[Weekday] = Some(Monday)
scala> Weekday.withNameOption("Momday")
res3: Option[Weekday] = None
Adding extra values to enumeration. It is very similar to how we could
add extra values with simple sealed case objects
Support for several libraries and frameworks that can be real useful on
bigger applications. Check the project documentation
Conclusion
If you are just starting into Scala I would recommend to use the native
approach with scala.Enumeration. As you feel more comfortable using
more Scala features, and as you start enjoying compiler safeties you will
probably want to migrate to something else. My two recommendations would
be:
Follow up
If you want to see more alternatives, check out the follow up on Scala
Enumerations - Return of the (Java) Jedi
https://docs.scala-lang.org/overviews/core/string-interpolation.html#the-raw-interpolator