Scala's Basic Types and Literals

Introduction

Every programming language has fundamental data types that serve as building blocks for more complex structures. Scala's type system is both powerful and intuitive, providing a rich set of basic types while maintaining compatibility with Java.

In this lesson, we'll explore Scala's core data types, learn how to write their literal values, and understand how Scala's type system works to help you write safer, more expressive code.

Scala's Type Hierarchy

Before diving into specific types, it's important to understand Scala's type hierarchy. Unlike Java, everything in Scala is an object:

Any
├── AnyVal (value types)
│   ├── Unit
│   ├── Boolean
│   ├── Byte
│   ├── Short
│   ├── Char
│   ├── Int
│   ├── Long
│   ├── Float
│   └── Double
└── AnyRef (reference types)
    ├── String
    ├── List
    ├── Option
    └── ... (all other objects)
  • Any: The root type of all types
  • AnyVal: Parent of all value types (primitives)
  • AnyRef: Parent of all reference types (objects)

Numeric Types

Scala provides several numeric types with different ranges and precision:

Int (32-bit integers)

val age = 25
val negative = -42
val zero = 0

// You can use underscores for readability in large numbers
val million = 1_000_000
val binary = 0b1010      // Binary literal (equals 10)
val hex = 0xFF           // Hexadecimal literal (equals 255)

// Type annotation (usually not needed)
val explicitInt: Int = 42

Range: -2,147,483,648 to 2,147,483,647

Long (64-bit integers)

val bigNumber = 9876543210L  // Note the 'L' suffix
val timestamp = System.currentTimeMillis()

// Without 'L', large numbers default to Int and may cause errors
val tooBigForInt = 3_000_000_000L  // Must use L suffix

Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Double (64-bit floating point)

val pi = 3.14159
val e = 2.71828
val negative = -42.5

// Scientific notation
val avogadro = 6.022e23
val planck = 6.626e-34

// Explicit type annotation
val price: Double = 19.99

Float (32-bit floating point)

val percentage = 98.6f  // Note the 'f' suffix
val ratio = 0.5f

// Without 'f', decimal numbers default to Double
val explicitFloat: Float = 3.14f

Smaller Integer Types

// Byte: -128 to 127
val smallNumber: Byte = 127

// Short: -32,768 to 32,767
val mediumNumber: Short = 32000

// These usually require explicit type annotation
// because literals default to Int

Boolean Type

Boolean values represent true or false:

val isActive = true
val isComplete = false
val isValid: Boolean = true

// Boolean operations
val result = true && false   // AND: false
val result2 = true || false  // OR: true
val result3 = !true         // NOT: false

// Comparison operations return Boolean
val isEqual = (5 == 5)      // true
val isGreater = (10 > 5)    // true
val isLessOrEqual = (3 <= 3) // true

Character Type

The Char type represents a single Unicode character:

val letter = 'A'
val digit = '5'
val symbol = '@'

// Unicode escapes
val heart = '\u2764'        // ❤
val newline = '\n'
val tab = '\t'
val backslash = '\\'
val quote = '\''

// You can also convert between Char and Int
val charCode = 'A'.toInt    // 65
val charFromCode = 65.toChar // 'A'

String Type

Strings are sequences of characters and are reference types:

val name = "Alice"
val empty = ""
val greeting = "Hello, World!"

// Strings are immutable
val original = "Hello"
val modified = original + " World"  // Creates new string
println(original)  // Still "Hello"
println(modified)  // "Hello World"

// Multi-line strings
val poem = """Roses are red,
             |Violets are blue,
             |Scala is awesome,
             |And so are you!""".stripMargin

// Raw strings (useful for regex, file paths)
val pattern = raw"C:\Users\Alice\Documents"

String Interpolation

Scala provides powerful string interpolation:

val name = "Bob"
val age = 30

// s-interpolation (most common)
val intro = s"My name is $name and I am $age years old"
val calculation = s"Next year I'll be ${age + 1}"

// f-interpolation (formatted strings)
val price = 19.99
val formatted = f"Price: $$${price}%.2f"  // "Price: $19.99"

// raw-interpolation (no escape processing)
val path = raw"C:\Users\$name\Documents"  // Backslashes not escaped

Unit Type

Unit is similar to void in other languages, representing "no meaningful value":

def printMessage(): Unit = {
  println("This function returns Unit")
}

val nothing: Unit = ()  // Unit literal
val alsoNothing = println("Hello")  // println returns Unit

Type Inference and Explicit Types

Scala's type inference is powerful, but sometimes explicit types are helpful:

// Type inference (preferred when obvious)
val count = 42              // Int inferred
val price = 19.99          // Double inferred
val isReady = true         // Boolean inferred
val name = "Alice"         // String inferred

// Explicit types (useful for clarity or when inference isn't enough)
val percentage: Double = 85     // Could be Int, but we want Double
val status: Option[String] = None  // Inference can't determine the type parameter
val users: List[User] = fetchUsers()  // When function return type is complex

Type Conversion

Scala is strict about type conversions to prevent errors:

val intValue = 42
val longValue = 100L
val doubleValue = 3.14

// Implicit conversions that are safe (widening)
val intToLong: Long = intValue     // OK: Int to Long
val intToDouble: Double = intValue // OK: Int to Double
val floatToDouble: Double = 3.14f  // OK: Float to Double

// Explicit conversions for narrowing (potential data loss)
val doubleToInt = doubleValue.toInt      // 3 (truncates decimal)
val longToInt = longValue.toInt          // 100
val charToInt = 'A'.toInt               // 65

// String conversions
val numberAsString = intValue.toString   // "42"
val stringToNumber = "123".toInt        // 123
val stringToDouble = "3.14".toDouble    // 3.14

// Safe conversion with Try
import scala.util.Try
val safeConversion = Try("not-a-number".toInt)  // Failure
val successfulConversion = Try("123".toInt)     // Success(123)

Working with Numbers

Scala provides rich operations on numeric types:

val a = 10
val b = 3

// Basic arithmetic
val sum = a + b        // 13
val difference = a - b // 7
val product = a * b    // 30
val quotient = a / b   // 3 (integer division)
val remainder = a % b  // 1

// Floating point division
val preciseQuotient = a.toDouble / b  // 3.3333333333333335

// Math operations
val absolute = math.abs(-5)           // 5
val maximum = math.max(a, b)          // 10
val minimum = math.min(a, b)          // 3
val power = math.pow(2, 3)           // 8.0
val squareRoot = math.sqrt(16)       // 4.0
val rounded = math.round(3.7)        // 4

// Comparing floating point numbers (be careful!)
val x = 0.1 + 0.2
val y = 0.3
val equal = x == y                   // false (due to floating point precision)
val almostEqual = math.abs(x - y) < 0.0001  // true (better approach)

String Operations

Strings come with many useful methods:

val text = "  Hello, Scala World!  "

// Basic operations
val length = text.length                    // 21
val upper = text.toUpperCase               // "  HELLO, SCALA WORLD!  "
val lower = text.toLowerCase               // "  hello, scala world!  "
val trimmed = text.trim                    // "Hello, Scala World!"

// Checking content
val isEmpty = text.isEmpty                 // false
val nonEmpty = text.nonEmpty              // true
val contains = text.contains("Scala")     // true
val startsWith = text.startsWith("  H")   // true
val endsWith = text.endsWith("!  ")       // true

// Extracting parts
val substring = text.substring(2, 7)      // "Hello"
val firstChar = text.head                 // ' '
val lastChar = text.last                  // ' '
val dropFirst = text.drop(2)             // "Hello, Scala World!  "
val takeFirst = text.take(7)             // "  Hello"

// Splitting and joining
val words = "apple,banana,cherry".split(",")  // Array("apple", "banana", "cherry")
val joined = words.mkString(" | ")            // "apple | banana | cherry"

// Replacing
val replaced = text.replace("Scala", "Java")  // "  Hello, Java World!  "
val regex = text.replaceAll("\\s+", " ")     // " Hello, Scala World! "

Practical Examples

Example 1: Temperature Converter

object TemperatureConverter extends App {
  def celsiusToFahrenheit(celsius: Double): Double = {
    celsius * 9.0 / 5.0 + 32.0
  }

  def fahrenheitToCelsius(fahrenheit: Double): Double = {
    (fahrenheit - 32.0) * 5.0 / 9.0
  }

  val freezingC = 0.0
  val boilingC = 100.0

  println(f"${freezingC}%.1f°C = ${celsiusToFahrenheit(freezingC)}%.1f°F")
  println(f"${boilingC}%.1f°C = ${celsiusToFahrenheit(boilingC)}%.1f°F")

  val roomTempF = 72.0
  println(f"${roomTempF}%.1f°F = ${fahrenheitToCelsius(roomTempF)}%.2f°C")
}

Example 2: String Processor

import scala.io.StdIn

object StringProcessor extends App {
  print("Enter some text: ")
  val input = StdIn.readLine()

  if (input != null && input.nonEmpty) {
    val cleaned = input.trim.toLowerCase
    val wordCount = cleaned.split("\\s+").length
    val charCount = cleaned.length
    val vowelCount = cleaned.count("aeiou".contains(_))

    println(s"""
       |Text Analysis:
       |Original: "$input"
       |Cleaned: "$cleaned"
       |Words: $wordCount
       |Characters: $charCount
       |Vowels: $vowelCount
       |Starts with vowel: ${cleaned.headOption.exists("aeiou".contains(_))}
       |""".stripMargin)
  } else {
    println("No input provided!")
  }
}

Example 3: Simple Calculator

object Calculator extends App {
  def calculate(operation: String, a: Double, b: Double): Either[String, Double] = {
    operation.toLowerCase match {
      case "add" | "+" => Right(a + b)
      case "subtract" | "-" => Right(a - b)
      case "multiply" | "*" => Right(a * b)
      case "divide" | "/" => 
        if (b == 0) Left("Division by zero")
        else Right(a / b)
      case "power" | "^" => Right(math.pow(a, b))
      case _ => Left(s"Unknown operation: $operation")
    }
  }

  val result1 = calculate("+", 10.5, 3.2)
  val result2 = calculate("/", 15, 0)
  val result3 = calculate("^", 2, 3)

  println(s"10.5 + 3.2 = ${result1.getOrElse("Error")}")
  println(s"15 / 0 = ${result2.fold(identity, _.toString)}")
  println(s"2 ^ 3 = ${result3.getOrElse("Error")}")
}

Common Type-Related Gotchas

1. Integer Division

val result = 5 / 2        // 2 (not 2.5!)
val correct = 5.0 / 2     // 2.5
val alsoCorrect = 5 / 2.0 // 2.5

2. Floating Point Precision

val x = 0.1 + 0.2
println(x == 0.3)         // false!
println(x)                // 0.30000000000000004

// Better approach for comparisons
def almostEqual(a: Double, b: Double, epsilon: Double = 1e-10): Boolean = {
  math.abs(a - b) < epsilon
}

3. String Interpolation Gotchas

val value = 42
val wrong = "Value: $value"      // Literal string "Value: $value"
val right = s"Value: $value"     // "Value: 42"

// Be careful with special characters
val path = s"C:\Users\$name"     // Wrong: \U and \n are escape sequences
val correctPath = raw"C:\Users\$name" // or s"C:\\Users\\$name"

Best Practices

1. Use Appropriate Numeric Types

// Good: Use Int for most integer values
val count = 42

// Good: Use Long only when needed
val timestamp = System.currentTimeMillis()

// Good: Use Double for most decimal values
val price = 19.99

// Avoid: Using larger types unnecessarily
val overkill: Long = 5L  // Just use Int

2. Prefer Type Inference

// Good: Let Scala infer obvious types
val name = "Alice"
val age = 30
val price = 19.99

// Good: Be explicit when needed for clarity
val percentage: Double = 85  // Could be Int, but we want Double
val users: List[User] = Nil  // Helps with type inference

3. Use String Interpolation

val name = "Bob"
val age = 25

// Good: s-interpolation
val message = s"Hello, $name! You are $age years old."

// Avoid: String concatenation
val message = "Hello, " + name + "! You are " + age + " years old."

Summary

In this lesson, you've learned about Scala's fundamental types:

✅ Numeric Types: Int, Long, Double, Float, Byte, Short
✅ Boolean Type: true and false values
✅ Character Type: Single Unicode characters
✅ String Type: Immutable sequences of characters
✅ Unit Type: Represents "no value"
✅ Type Conversion: Safe and explicit conversions
✅ String Interpolation: s, f, and raw interpolators
✅ Best Practices: When to use type inference vs explicit types

Understanding these basic types is crucial because they're the building blocks for all Scala programs. The type system helps catch errors early and makes your code more reliable.

What's Next

In the next lesson, we'll explore expressions and control structures, starting with if/else. You'll learn how Scala treats almost everything as an expression that returns a value, which is a fundamental concept that makes Scala code more concise and functional.

We'll see how this expression-oriented approach differs from statement-based languages and how it enables more elegant and composable code.

Ready to make your code more expressive? Let's continue!