
- 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 - Corecursion
This chapter takes you through the concept of corecursion in Scala programming. Corecursion is a dual of recursion, where you generate potentially infinite data structures and can call itself. Instead of calculating the result by breaking the problem into smaller sub-problems, corecursion focuses on progressively generating the output.
Corecursion
Corecursion produces stream-like data structures, where each element is produced on demand. Corecursion functions are used when you work with infinite sequences and when you need to model continuous data generation.
Recursion vs Corecursion
There is difference between recursion and corecursion in Scala. Recursion is a process where function calls itself. Whereas, corecursion is a process where a function produces its output gradually and may call itself.
Feature | Recursion | Corecursion |
---|---|---|
Definition | A process where a function calls itself. | A process where a function produces its output gradually and may call itself. |
Base Case | Must have a base case to terminate the recursive calls. | Not always required to have a base case, as it can produce infinite structures. |
Nature | Consumes input and works towards a terminating condition. | Generates output progressively, often working towards an infinite structure. |
Evaluation | Typically evaluated eagerly, consuming stack space with each call. | Can be evaluated lazily, producing elements as needed. |
Example Use Case | Calculating factorial, Fibonacci numbers, tree traversal. | Generating infinite data streams, lazy lists. |
Termination | Must ensure that the recursive process terminates. | May not terminate if designed to produce infinite sequences. |
Memory Usage | Can lead to stack overflow if not properly managed. | Uses constant space due to lazy evaluation and does not consume stack space like recursion. |
Example in Scala | def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1) |
def streamFrom(n: Int): Stream[Int] = n #:: streamFrom(n + 1) |
Libraries | Often uses standard library functions and techniques like tail recursion. | Often utilizes lazy collections like Stream and LazyList. |
Definition of Corecursion
Corecursion is a technique where a function produces its output gradually and may call itself. Unlike recursion, base condition may not always be required in corecursion because it can produce infinite structures.
Syntax
The syntax of corecursive function in Scala is -
def corecursiveFunction(params: Type): Stream[ReturnType] = { // generate the first element // generate the rest of the stream corecursively firstElement #:: corecursiveFunction(nextParams) }
Example
The following example shows defining and using a corecursive function in Scala programming -
object Demo { def fibonacci(a: Int, b: Int): Stream[Int] = { a #:: fibonacci(b, a + b) } def main(args: Array[String]): Unit = { val fibs = fibonacci(0, 1) println(fibs.take(10).toList) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
In the example, the fibonacci function generates an infinite stream of Fibonacci numbers. Each element is produced on demand using the #:: operator. It prepends first element to rest of the stream generated corecursively.
Advantages of Corecursion
Corecursion is the dual of recursion. It can be used when you work with lazy evaluation, where elements are generated only when needed. So corecursions are efficient infinite data structures. You can use corecursion for modular and composable code, where data production logic is separated from data consumption logic. You can also use corecursive functions for stream processing, where data is processed as it is generated. So these are efficient for large and infinite sequences.
Corecursion with Streams
Streams in Scala are used to implement corecursive functions. Streams are lazy lists. You can use streams where elements are computed only when they are accessed.
Syntax
The syntax of corecursion with streams is -
def corecursiveFunction(params: Type): Stream[ReturnType] = { firstElement #:: corecursiveFunction(nextParams) }
Example
Consider the example of generating an infinite stream of natural numbers using corecursion in Scala programming -
object Demo { def naturalNumbers(start: Int): Stream[Int] = { start #:: naturalNumbers(start + 1) } def main(args: Array[String]): Unit = { val nums = naturalNumbers(1) println(nums.take(10).toList) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
In the example, the naturalNumbers function generates an infinite stream of natural numbers starting from a given number. Each number is produced on demand using corecursion.
Corecursion with Trees
You can also use corecursion to generate tree-like structures, where each node is generated step by step.
Syntax
The syntax of corecursion with trees is -
case class Tree(value: Type, children: Stream[Tree]) def corecursiveTreeFunction(params: Type): Tree = { Tree(rootValue, corecursiveTreeFunction(nextParams)) }
Example
Consider the example of generating an infinite binary tree using corecursion in Scala programming -
case class Tree(value: Int, left: () => Tree, right: () => Tree) object Demo { def infiniteTree(value: Int): Tree = { lazy val leftSubtree = infiniteTree(value * 2) lazy val rightSubtree = infiniteTree(value * 2 + 1) Tree(value, () => leftSubtree, () => rightSubtree) } def main(args: Array[String]): Unit = { val tree = infiniteTree(1) println(tree.left().left().value) println(tree.right().right().value) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
4 7
In the example, the infiniteTree function generates an infinite binary tree, where each node has a left and right child. The tree is generated corecursively, with each node value doubling or doubling plus one from its parent node.
Corecursion with Streams and Filtering
You can use corecursion combined with filtering to generate streams that satisfy given conditions.
Syntax
The syntax of corecursion with streams and filtering is -
def corecursiveFilteredFunction(params: Type): Stream[ReturnType] = { val nextValue = computeNextValue(params) if (condition(nextValue)) nextValue #:: corecursiveFilteredFunction(nextParams) else corecursiveFilteredFunction(nextParams) }
Example
Consider the example of generating an infinite stream of even numbers using corecursion and filtering in Scala programming -
object Demo { def evenNumbers(start: Int): Stream[Int] = { if (start % 2 == 0) start #:: evenNumbers(start + 2) else evenNumbers(start + 1) } def main(args: Array[String]): Unit = { val evens = evenNumbers(1) println(evens.take(10).toList) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
In the example, the evenNumbers function generates infinite stream of even numbers starting from a given number. You can produce each even number on demand using corecursion and filtering.
Corecursion Summary
- Corecursion is used to generate potentially infinite data structures step by step.
- Corecursive functions can use lazy evaluation to produce elements on demand. So it is efficient for working with large and infinite sequences.
- You can apply corecursion to streams, trees, and other data structures, etc,.for modular and composable code.
- You can also filter with corecursion to generate streams that satisfy given conditions.