Skip to content

Scala Basics

Pramode C E edited this page May 7, 2019 · 1 revision

Welcome to the learn-spinalhdl wiki!

Scala Crash Course!

Hello, world

We can directly define objects in Scala!

object Hello {
    def main(args: Array[String]): Unit = {
            println("Hello, world")
    }
}

Type Inference

Scala is statically typed, but types are inferred in almost all contexts.

object Infer1 {
    def main(args: Array[String]): Unit = {
        // Explicit type specification
        val x:Int = 20
        // Scala will infer types. Type of "y"
        // is Int.
        val y = 10
    }
}

Define functions using def

object Def1 {
    // A function with no arguments.
    // Call like this: Def1.foo
    def foo:String = "hello"
    // A function with one arg
    def sqr(x:Int):Int = x*x
    
    def main(args: Array[String]): Unit = {
        println(Def1.foo)
        println(Def1.sqr(10))
    }
}

The "Unit" type

object Unit1 {
    def main(args: Array[String]): Unit = {
        // println returns "nothing", and this "nothing"
        // has type "Unit".
        val x:Unit = println("world")
        // Type of y is "Unit"; it is inferred
        // by Scala.
        val y = println("hello")
        // The only object of type "Unit" is (), it
        // is printed as () itself.
        println(x)
        println(y)
        val z = ()
        println(z)
    }
}

Operators

Postfix

import scala.language.postfixOps
object Operators1 {
    def main(args: Array[String]): Unit = {
            val msg = "Hello,world"
            val newmsg = msg.toUpperCase
            // Without the import, the next line will give
            // a warning. This notation is not encouraged, but
            // Spinal code seems to be using it liberally.
            val newmsg2 = (msg toUpperCase)
            println("newmsg = "+ newmsg)
            println("newmsg2 = " + newmsg2)
    }
}

Infix

// Infix operators

object Operators2 {
    def main(args: Array[String]): Unit = {
            println(1 + 2)
            println(1.+(2))
            val x = "abc def ijk".split(" ")    
            val y = "abc def ijk" split " "

            for (item <- x) {
                println(item)
            }

            for (item <- y) {
                println(item)
            }

    }
}

Val and Var

Example 1

object ValAndVar1 {
    def main(args: Array[String]): Unit = {
        val x = Array(1, 2, 3)
        // Compile error. Can't rebind a val
        x = Array(10, 20, 30)

    }
}

Example 2

object ValAndVar2 {
    def main(args: Array[String]): Unit = {
        var x = Array(1, 2, 3)
        // No problem, can reassign to a var
        x = Array(10, 20, 30)
        println(x(0))
    }
}

Example 3

object ValAndVar3 {
    def main(args: Array[String]): Unit = {
        val x = Array(1, 2, 3)
        // Even though we can't reassign to "x", we 
        // can  change the object that "x" refers to
        // because it is a mutable Scala object.
        x(0) = 10
        println(x(0))
    }
}

If-Else

object IfElse1 {
    def main(args: Array[String]): Unit = {
        val x = if (1 > 2) "hello" else "world"
        println(x)
    }
}

Blocks

object Block {
    def name: String = {
        val title = "Dr."
        val name = "Einstein"
        title + " " + name
    }
    
    def main(args: Array[String]): Unit = {
        println(Block.name)
    }
}

Classes

Example 1

class Student {
    val firstName = "Foo"
    val lastName = "Bar"
    
    def name = firstName + " " + lastName
}

object Class1 {
    def main(args: Array[String]):Unit = {
        val s = new Student
        println(s.name)
    }
}

Example 2 - Constructor

class Student(first: String, last: String) {
    val firstName = first
    val lastName = last
    
    def name = firstName + " " + lastName
}

object Class2 {
    def main(args: Array[String]):Unit = {
        val s = new Student("Foo", "Bar")
        println(s.name)
    }
}

Example 3 - Constructor

Note that we are now prefixing the constructor arguments with "val"; this automatically sets them up as fields of the created object thereby avoiding unnecessary boilerplate.

class Student(val firstName: String, val lastName: String) {
    def name = firstName + " " + lastName
}

object Class3 {
    def main(args: Array[String]):Unit = {
        val s = new Student("Foo", "Bar")
        println(s.name)
    }
}

Example 4 - Apply

class Adder(amount: Int) {
    def apply(n: Int):Int = amount + n
}

object Apply1 {
    def main(args: Array[String]):Unit = {
        val a = new Adder(10)
        val b = a(1) // same as a.apply(1)
        println(b)
    }
}

Case Classes

You can avoid a lot of boilerplate using case classes.

// "val x" and "val y" is not required; 
// "x" and "y" automatically become fields
// of the created object.
case class Foo(x: Int, y: Int) { }

object CaseClass1 {
    def main(args: Array[String]):Unit = {
        val f1 = new Foo(1, 2)
        println(f1.x)
        // For a case class, a companion object is automatically
        // created.
        val f2 = Foo(1, 2)
        println(f2.x)
    }
}

Pattern matching can be performed on case classes:

// "val x" and "val y" is not required; 
// "x" and "y" automatically become fields
// of the created object.
case class Foo(x: Int, y: Int) { }

object CaseClass2 {
    def check(f: Foo):String = {
        f match {
            case Foo(0, 0) => "Both x and y 0"
            case Foo(0, y) => "x is zero, y may be non zero"
            case Foo(-1, -1) => "x and y are -1"
            case Foo(_, _) => "default case"
        }
    }

    def main(args: Array[String]):Unit = {
        val f1 = Foo(1, 2)
        println(CaseClass2.check(f1))
        val f2 = Foo(-1, -1)
        println(CaseClass2.check(f2))
    }
}

Functions named like operators

class Foo(var x: Int) {
    def := (xx: Int) = {
        x = xx
    }
}

object OperatorFunctions {
    def main(args: Array[String]):Unit = {
        val f = new Foo(10)
        f := 20
        println(f.x)
    }
}

Lambda

object Lambda {
    def main(args: Array[String]):Unit = {
        val f = (x: Int, y: Int) => x + y
        println(f(1,2))
    }
}

Methods on collections

object Map1 {
    def main(args: Array[String]):Unit = {
        val xs = List(1, 2, 3, 4)
        println(xs.map((x: Int) => x + 1))
    }
}

Traits

Example 1

sealed trait Color {}

case object Red extends Color
case object Green extends Color

object Traits1 {
    def main(args: Array[String]):Unit = {
        val c1:Color = Red
        val s = c1 match {
            case Red => "red"
            case Green => "green"
        }
        println(s)
    }
}

Example 2

sealed trait Option {}

case object None extends Option
final case class Some(x: Int) extends Option

object Traits2 {
    def main(args: Array[String]):Unit = {
        val o1:Option = Some(10)
        val s = o1 match {
            case Some(x) => "Some: " + x
            case None => "None"
        }
        println(s)
    }
}

Example 3

Important: This gives a warning as we are not checking for all possible values of Color!

sealed trait Color {}

case object Red extends Color
case object Green extends Color
case object Blue extends Color

object Traits3 {
    def main(args: Array[String]):Unit = {
        val c1:Color = Red
        val s = c1 match {
            case Red => "red"
            case Green => "green"
        }
        println(s)
    }
}