
- Scala - Home
- Scala - Overview
- Scala - Features
- Scala - Environment Setup
- Scala - Build Tool (SBT)
- Scala - REPL
- Scala - Dot & Dotty
- Scala - Basic Syntax
- Scala - Hello World Program
- Scala - Identifiers
- Scala - Keywords
- Scala - Comments
- Scala - Code Blocks
- Scala - Semicolon
- Scala - Constructs
- Scala - Expressions
- Scala - Input and Output
- Scala - Optional Braces
- Scala - Underscore (_)
- Data Types and Variables
- Scala - Data Types
- Scala - Type Bounds
- Scala - Context Bound
- Scala - Variances
- Scala - Type Hierarchy
- Scala - Variables
- Scala - Variable Scopes
- Scala - Literals
- Scala - Numeric Types
- Scala - Boolean Types
- Scala - Char Type
- Scala - Unit Types
- Scala - Strings
- Scala - Arrays
- Scala - Null Type
- Scala - Nothing
- Scala - Any Type
- Scala - AnyRef Type
- Scala - Unified Types
- Scala - Dates and Times
- Scala - Ranges
- Scala - Multidimensional Arrays
- Scala - WrappedArray
- Scala - StringBuilder
- Scala - String Interpolation
- Scala - StringContext
- Scala - Type Casting
- Scala var vs val
- Scala Operators
- Scala - Operators
- Scala - Rules for Operators
- Scala - Arithmetic Operators
- Scala - Relational Operators
- Scala - Logical Operators
- Scala - Bitwise Operators
- Scala - Assignment Operators
- Scala - Operators Precedence
- Scala - Symbolic Operators
- Scala - Range Operator
- Scala - String Concatenation Operator
- Scala Conditional Statements
- Scala - IF ELSE
- Scala - IF-ELSE-IF-ELSE Statement
- Scala - Nested IF-ELSE Statement
- Scala Loop Statements
- Scala - Loop Statements
- Scala - while Loop
- Scala - do-while Loop
- Scala - Nested Loops
- Scala - for Loop
- Scala - break Statement
- Scala - yield Keyword
- Scala Classes & Objects
- Scala - Classes & Objects
- Scala - Constructors
- Scala - Auxiliary Constructor
- Scala - Primary Constructor
- Scala - This Keyword
- Scala - Nested Classes
- Scala - Getters and Setters
- Scala - Object Private Fields
- Scala - Singleton Object
- Scala - Companion Objects
- Scala - Creating Executable Programs
- Scala - Stateful Object
- Scala - Enumerations
- Scala - Polymorphism
- Scala - Access Modifiers
- Scala - Apply Method
- Scala - Update Methods
- Scala - UnapplySeq Method
- Scala - Inheritance
- Scala - Extending a Class
- Scala - Method Overloading
- Scala - Method Overriding
- Scala - Generic Classes
- Scala - Generic Functions
- Scala - Superclass Construction
- Scala Methods & Functions
- Scala - Functions
- Scala - Main Methods
- Scala - Functions Call-by-Name
- Scala - Functions with Named Arguments
- Scala - Function with Variable Arguments
- Scala - Recursion Functions
- Scala - Default Parameter Values
- Scala - Functions without Parameters
- Scala - Implicit Parameters
- Scala - Higher-Order Functions
- Scala - Nested Functions
- Scala - Extension Methods
- Scala - Anonymous Functions
- Partially Applied Functions
- Scala - Lazy Val
- Scala - Pure Function
- Scala - Currying Functions
- Scala - Control Abstractions
- Scala - Corecursion
- Scala - Unfold
- Scala - Tail Recursion
- Scala - Infinite Sequences
- Scala - Dynamic Invocation
- Scala - Lambda Expressions
- Scala Collections
- Scala - Collections
- Mutable and Immutable Collections
- Scala - Lists
- Scala - Sets
- Scala - Maps
- Scala - TreeMap
- Scala - SortedMap
- Scala - Tuples
- Scala - Iterators
- Scala - Options
- Scala - Infinite Streams
- Scala - Parallel Collections
- Scala - Algebraic Data Types
- Scala Pattern Matching
- Scala - Pattern Matching
- Scala - Type Patterns
- Scala - Exception Handling
- Scala - Extractors
- Scala - Regular Expressions
- Scala Files I/O
- Scala - Files I/O
- Scala Advanced Concepts
- Scala - Closures
- Scala - Futures
- Scala - Promises
- Scala - Traits
- Scala - Trait Mixins
- Scala - Layered Traits
- Scala - Trait Linearization
- Scala - Sealed Traits
- Scala - Transparent Traits
- Scala - Literal Type Arithmetic
- Scala - Inline keyword
- Scala - Def, Var & Val
- Scala - Dropped Features
- Scala - BDD Testing
Scala - Literal Types and Compile-Time Arithmetic
You can define types that correspond to specific values. These types are used for enforcing constraints. You can use certain values correctly throughout your code. You can write more precise code by combining literal types with arithmetic operations.
Literal Types
Literal types are types that have a single possible value. You can create for various basic types like integers, floats, strings, and booleans.You can create variables that are constrained to specific values using literal types.
Example
val one: 1 = 1 val trueLiteral: true = true val hello: "hello" = "hello"
Benefits of Literal Types
- Type Safety − You can use literal types for only specific values assigned to variables. You can prevent accidental errors.
- Code Readability − Your Code becomes more expressive and easier to understand when literal types are used.
- Compile-Time Error Checking − Compiler can catch more errors at compile-time to reduce runtime issues.
Using Literal Types in Arithmetic Operations
Literal type arithmetic to perform arithmetic operations on literal types. You can ensure type safety and consistency through compile-time checks. This can be used in generic programming where operations depend on specific values.
Defining Literal Types for Arithmetic
You use singleton types and appropriate type constraints to define literal types for arithmetic operations. For example -
val three: 3 = 3 val four: 4 = 4
Singleton Types
Variables can be typed with the exact value it holds. This is used for literal type arithmetic, where operations involve specific values.
Example
val x: 1 = 1 val y: x.type = x // y has the type of the value x
Arithmetic Operations with Literal Types
When performing arithmetic operations using literal types, Scala ensures that these operations are type-safe and consistent.
Addition
You can define a method that takes two singleton types and returns their sum as a literal type. For example -
import singleton.ops._ def add[A <: Int with Singleton, B <: Int with Singleton](a: A, b: B): A + B = a + b
Subtraction
You can subtract two literal types, like this -
def subtract[A <: Int with Singleton, B <: Int with Singleton](a: A, b: B): A - B = a - b
Multiplication
You can multiply two literal types, like this -
def multiply[A <: Int with Singleton, B <: Int with Singleton](a: A, b: B): A * B = a * b
Division
You can divide two literal types. But you should avoid the divisor is not zero, like this -
def divide[A <: Int with Singleton, B <: Int with Singleton](a: A, b: B) (implicit ev: B =:!= 0): A / B = a / b
These are some advanced use cases of literal type arithmetic.
Dimensional Analysis
You can enforce constraints in dimensional analysis with type-safe operations involving units. For example -
import spire.implicits._ import libra._ import libra.si._ val distance: Quantity[Double, Meter] = 5.m val time: Quantity[Double, Second] = 2.s val speed: Quantity[Double, Meter / Second] = distance / time
Type-Safe Vector Arithmetic
You can create type-safe vector operations that only compile if dimensions match using the shapeless library. For example -
import shapeless.Witness trait Vec[N <: Int with Singleton] object Vec { def apply[N <: Int with Singleton](implicit w: Witness.Aux[N]): Vec[N] = new Vec[N] {} } val vec3 = Vec[Witness.`3`.T] val vec4 = Vec[Witness.`4`.T] // This won't compile as dimensions don't match // val vecSum = vec3 ++ vec4
Compile-Time Arithmetic with Singleton Types
You can perform compile-time arithmetic using literal. You can enforce constraints and ensure type safety using singleton types. For example -
import singleton.ops._ class Vec[L <: Int with Singleton] { def doubleSize: Vec[L * 2] = new Vec[L * 2] def nSize[N <: Int with Singleton]: Vec[N * L] = new Vec[N * L] def getLength(implicit length: SafeInt[L]): Int = length.value } object Vec { def apply[L <: Int with Singleton](implicit w: Witness.Aux[L]): Vec[L] = new Vec[L] } val vec10 = Vec[Witness.`10`.T] val vec20 = vec10.doubleSize val vec50 = vec10.nSize[Witness.`5`.T]
Literal Types with Macros
Macros are used to work with literal types, compile-time computation and validation. So, you can have advanced literal type arithmetic.
Example
Note that you cannot define and use macros within the same compilation unit. You need to separate the macro definition from its usage. You must split the code into two different files or even two different projects/modules.
For this example, we have created two files named Macros.scala and Main.scala in the Scala folder. This folder is look like this:
MyMacrosProject/ build.sbt macros/ build.sbt src/ main/ scala/ Macros.scala main/ build.sbt src/ main/ scala/ Main.scala
Also, your build.sbt file should be this before executing these macros codes -
import Dependencies._ ThisBuild / scalaVersion := "2.13.14" lazy val macros = (project in file("macros")) .settings( name := "macros", libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value, "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided ) ) lazy val main = (project in file("main")) .settings( name := "main", libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value ) ).dependsOn(macros)
Now, your Macros.scala code should be this -
import scala.reflect.macros.blackbox.Context import scala.language.experimental.macros object LiteralMacros { def addLiterals(a: Int, b: Int): Int = macro addLiteralsImpl def addLiteralsImpl(c: Context)(a: c.Expr[Int], b: c.Expr[Int]): c.Expr[Int] = { import c.universe._ (a.tree, b.tree) match { case (Literal(Constant(aVal: Int)), Literal(Constant(bVal: Int))) => val result = aVal + bVal c.Expr[Int](Literal(Constant(result))) case _ => c.abort(c.enclosingPosition, "Both arguments must be literal integers") } } }
And, your Main.scala code should be this -
object Main extends App { val sum = LiteralMacros.addLiterals(3, 4) // Evaluates to 7 at compile time println(s"Sum: $sum") }
Now, you can compile and execute using following commands -
Commands
\> sbt clean compile \> sbt main/run
Output
Sum: 7
Notes
- You can define types that correspond to specific values. These are used for enforcing constraints and ensuring correct values throughout your code.
- It is possible to create variables with constrained values like val one: 1 = 1 and val hello: "hello" = "hello".
- There are several benefits, including type safety, code readability, and compile-time error checking to reduce runtime issues.
- You can perform type-safe arithmetic using methods like add, subtract, multiply, and divide for literal types.
- It is possible to use singleton types to have variables with exact value types for precise operations, e.g., val x: 1 = 1.
- There are advanced use cases, like enforcing constraints in dimensional analysis and creating type-safe vector operations using libraries like shapeless.
- You can use macros for compile-time computation and validation with literal types for advanced arithmetic operations.