Important
Variables and functions should be defined into object, class, function. You can’t define them on the root of a Scala file.
Basics
Types
In Scala, there are 5 major types
Type |
Literal |
Description |
|---|---|---|
Boolean |
true, false |
|
Int |
3, 0x32 |
32 bits integer |
Float |
3.14f |
32 bits floating point |
Double |
3.14 |
64 bits floating point |
String |
“Hello world” |
UTF-16 string |
Variables
In scala, you can define a variable by using the var keyword:
var number : Int = 0
number = 6
number += 4
println(number) // 10
Scala is able to infer the type automatically. You don’t need to specify it if the variable is assigned at declaration:
var number = 0 //The type of 'number' is inferred as a Int during the compilation.
But, in fact, it’s not very common to use var in Scala. Instead, constant values defined by val are often used:
val two = 2
val three = 3
val six = two * three
Functions
For example, if you want to define a function which returns true if the sum of its two arguments is bigger than zero, you can do as follows:
def sumBiggerThanZero(a: Float, b: Float): Boolean = {
return (a + b) > 0
}
Then, to call this function, you can just write:
sumBiggerThanZero(2.3f, 5.4f)
You can also specify arguements by name, which is useful if you have many arguements:
sumBiggerThanZero(
a = 2.3f,
b = 5.4f
)
Return
The return keyword is not necessary. In absence of it, Scala takes the last statement of your function as the returned value.
def sumBiggerThanZero(a: Float, b: Float): Boolean = {
(a + b) > 0
}
Return type inferation
Scala is able to automatically infer the return type. You don’t need to specify it:
def sumBiggerThanZero(a: Float, b: Float) = {
(a + b) > 0
}
Curly braces
Scala function doesn’t require to have curly braces if your function contains only one statement:
def sumBiggerThanZero(a: Float, b: Float) = (a + b) > 0
Function that returns nothing
If you want a function to return nothing, the return type should be set to Unit. It’s equivalent to the C/C++ void.
def printer(): Unit = {
println("1234")
println("5678")
}
Arguements default value
You can specify a default value for each arguement of a function:
def sumBiggerThanZero(a: Float, b: Float = 0.0f) = {
(a + b) > 0
}
Apply
Functions named apply are special because you can call them without having to type their name:
class Array(){
def apply(index: Int): Int = index + 3
}
val array = new Array()
val value = array(4) //array(4) is interpreted as array.apply(4) and will return 7
This concept is also applicable for scala object (static)
object MajorityVote{
def apply(value: Int): Int = ...
}
val value = MajorityVote(4) // Will call MajorityVote.apply(4)
Object
In scala, there is no static keyword. In place of that, there is object. Everything defined into an object is static.
The following example defines a static function named pow2 which takes a floating point value as parameter and returns a floating point as well.
object MathUtils{
def pow2(value: Float): Float = value*value
}
Then you can call it by writing:
MathUtils.pow2(42.0f)
Entry point (main)
The entry point of a Scala program (the main function) should be defined inside an object as a function named main.
object MyTopLevelMain{
def main(args: Array[String]) {
println("Hello world")
}
}
Class
The class syntax is very similar to Java. Imagine that you want to define a Color class which takes as construction parameters three Float values (r,g,b) :
class Color(r: Float, g: Float, b: Float){
def getGrayLevel(): Float = r * 0.3f + g * 0.4f + b *0.4f
}
Then, to instantiate the class from the previous example and use its getGrayLevel function:
val blue = new Color(0, 0, 1)
val grayLevelOfBlue = blue.getGrayLevel()
Be careful, if you want to access a construction parameter of the class from the outside, this construction parameter should be defined as a val:
class Color(val r: Float, val g: Float, val b: Float){ ... }
...
val blue = new Color(0, 0, 1)
val redLevelOfBlue = blue.r
Inheritance
As an example, suppose that you want to define two classes, Rectangle and Square, which extend the class Shape:
class Shape{
def getArea(): Float
}
class Square(sideLength: Float) extends Shape {
override def getArea() = sideLength * sideLength
}
class Rectangle(width: Float, height: Float) extends Shape {
override def getArea() = width * height
}
Case class
Case class is an alternative way of declaring classes.
case class Rectangle(width: Float, height: Float) extends Shape {
override def getArea() = width * height
}
Then there are some differences between case class and class :
case classes don’t need the
newkeyword to be instantiatedconstruction parameters are accessible from outside, you don’t need to define them as
val.
In SpinalHDL, this explains the reasoning behind the coding conventions: it’s in general recommended to use case class instead of class in order to have less typing and more coherency.
Templates / Type parameterization
Imagine you want to design a class which is a queue of a given datatype, in that case you need to provide a type parameter to the class:
class Queue[T](){
def push(that: T) : Unit = ...
def pop(): T = ...
}
If you want to restrict the T type to be a sub class of a given type (for example Shape), you can use the <: Shape syntax :
class Shape() {
def getArea(): Float
}
class Rectangle() extends Shape { ... }
class Queue[T <: Shape](){
def push(that: T): Unit = ...
def pop(): T = ...
}
The same is possible for functions:
def doSomething[T <: Shape](shape: T): Something = { shape.getArea() }