
- 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 - Methods
- Scala - Functions
- Scala - Methods vs 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 - Polymorphic Functions
- 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 - NumericRange
- Scala - Infinite Streams
- Scala - Parallel Collections
- Scala Advanced Types
- Scala - Union Types
- Scala - Intersection Types
- Scala - Type Aliases
- Scala - Structural Types
- Scala - Match Expression
- Scala - Singleton Type Operator
- Scala - Abstract Types
- Scala - Dependent Types
- Scala - Abstract Type Bounds
- Scala - Higher-Kinded Types
- Scala - Opaque Type Alias
- Scala - Path-Dependent Types
- Scala - Type Lambdas
- Scala - Type Inference
- Scala - Algebraic Data Types
- Scala Pattern Matching
- Scala - Pattern Matching
- Scala - Guards
- Scala - Variables in Patterns
- Scala - Type Patterns
- Scala - The Matchable Trait
- Scala - Matching Arrays
- Scala - Matching Lists
- Scala - Matching Tuples
- Scala - Exception Handling
- Scala - Extractors
- Scala - Pattern Bindings
- Scala - Regular Expressions
- Scala - Case Classes
- Scala - Partial Functions
- Scala - Packaging and Imports
- Scala - Implicit Imports
- Scala - Export Clauses
- Scala - Nested Packages
- Scala - Chained Packages
- Scala - Package Objects
- Scala Files I/O
- Scala - Files I/O
- Scala - Writing Files
- Scala - Listing Files
- Scala - Deleting Directories
- Scala - Check File Exists
- 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 - Process Management
- Scala - Scaladoc
- Scala - Literal Type Arithmetic
- Scala - Inline keyword
- Scala - Def, Var & Val
- Scala - Dropped Features
- Scala Unit Testing
- Scala - Unit Testing
- Scala - uTest
- Scala - MUnit
- Scala - ScalaTest Runner
- Scala - ScalaMock
- Scala - JUnit
- Scala - Mocking
- Scala - BDD Testing
Scala - Type Lambdas
Type lambdas provide higher-kinded types directly without defining a new type. You can partially apply type constructors for more flexibility and abstraction. Type lambda lets you express a higher-kinded type directly. For example, the following type lambda defines a binary type constructor that maps arguments X and Y to Map[Y, X] -
[X, Y] =>> Map[Y, X]
Type lambdas can have bounds on type parameters but cannot carry variance annotations (+ and -)
Syntax
The syntax for type lambdas is -
Type ::= ... | TypeParamClause '=>>' Type TypeParamClause ::= '[' TypeParam {',' TypeParam} ']' TypeParam ::= {Annotation} (id [HkTypeParamClause] | '_') TypeBounds TypeBounds ::= ['>:' Type] ['<:' Type]
Example of Type Lambdas
This is type lambda that represents a binary type constructor. This maps arguments X and Y to Map[Y, X]
type MyTypeLambda = [X, Y] =>> Map[Y, X]
Type Checking and Subtyping
Type lambda, like [X] =>> F[X] defines a function from types to types. You may have bounded parameters. For example, [X >: L <: U] =>> F[X] checks that arguments conform to the bounds L and U.
For subtyping, consider two type lambdas -
type TL1 = [X >: L1 <: U1] =>> R1 type TL2 = [X >: L2 <: U2] =>> R2
Then TL1 <: TL2 if -
- The type interval .U2 is contained in L1..U1 (i.e., L1 <: L2 and U2 <: U1).
- R1 <: R2.
Relationship with Parameterized Type Definitions
A parameterized type definition -
type T[X] = R
is equivalent to:
type T = [X] =>> R
If the type definition carries variance annotations. These must be satisfied by the type lambda.
Example
An example of using type lambdas to partially apply type constructor -
trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] } type MapWithIntKey[A] = Map[Int, A] implicit val mapFunctor: Functor[MapWithIntKey] = new Functor[MapWithIntKey] { def fmap[A, B](fa: Map[Int, A])(f: A => B): Map[Int, B] = fa.mapValues(f).toMap } object Demo { def main(args: Array[String]): Unit = { val map: Map[Int, String] = Map(1 -> "one", 2 -> "two") val mappedMap = mapFunctor.fmap(map)(_.toUpperCase) println(mappedMap) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
The above code defines a Functor instance for MapWithIntKey. So you can map functions over Map[Int, A] value.
This will produce the following result -
Map(1 -> ONE, 2 -> TWO)
Type lambdas are used to solve the problem of partially applying types, like in higher-kinded type contexts.
Functor for a Partially Applied Type
There is Functor for type that takes two parameters, like Map. Generally, you cannot directly use Map as Functor because it takes two type parameters. Using type lambdas, you can partially apply Map to create a single-parameter type constructor -
// Functor type class trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] } // Provide instance of Functor for Map with fixed key type implicit def mapFunctor[A]: Functor[({ type L[B] = Map[A, B] })#L] = new Functor[({ type L[B] = Map[A, B] })#L] { def fmap[B, C](fa: Map[A, B])(f: B => C): Map[A, C] = fa.map { case (k, v) => (k, f(v)) } } // Implicit class to add fmap syntax implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { def fmap[B](f: A => B): F[B] = F.fmap(fa)(f) } // Demo object object Demo { def main(args: Array[String]): Unit = { val map: Map[Int, String] = Map(1 -> "one", 2 -> "two") val mappedMap = map.fmap(_.toUpperCase) println(mappedMap) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
The above code defines a Functor instance for partially applied Map. So you can map a function over a Map[A, B] value.
This will produce the following result -
Map(1 -> ONE, 2 -> TWO)
Higher-Kinded Type Class with Type Lambdas
If you want to define a type class Bifunctor that works with types. These take two type parameters -
trait Bifunctor[F[_, _]] { def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] } object Bifunctor { implicit val mapBifunctor: Bifunctor[Map] = new Bifunctor[Map] { def bimap[A, B, C, D](fab: Map[A, B])(f: A => C, g: B => D): Map[C, D] = { fab.map { case (k, v) => (f(k), g(v)) } } } } object Demo { def main(args: Array[String]): Unit = { val map: Map[Int, String] = Map(1 -> "one", 2 -> "two") val bimapResult = Bifunctor.mapBifunctor.bimap(map)(_.toString, _.toUpperCase) println(bimapResult) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
The above code defines a Bifunctor instance for Map. So you can map two functions over the keys and values of a Map.
This will produce the following result -
Map(1 -> ONE, 2 -> TWO)
Curried Type Parameters
Note that Scala currently does not support curried type parameters directly. But you can achieve similar functionality using type lambdas.
Example
Here is an example of curried type parameters in Scala:
import scala.language.higherKinds import scala.language.implicitConversions // Functor type class trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] } // CurriedType type alias using the correct Scala 3 syntax type CurriedType[A] = [B] =>> Map[A, B] // Provide instance of Functor for CurriedType implicit def curriedTypeFunctor[A]: Functor[CurriedType[A]] = new Functor[CurriedType[A]] { def fmap[B, C](fa: Map[A, B])(f: B => C): Map[A, C] = fa.view.mapValues(f).toMap } object Demo { def main(args: Array[String]): Unit = { val map: Map[Int, String] = Map(1 -> "one", 2 -> "two") val mappedMap = curriedTypeFunctor[Int].fmap(map)(_.toUpperCase) println(mappedMap) } }
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
The above code has functor instance for curried type Map[A, B].
This will produce the following result -
Map(1 -> ONE, 2 -> TWO)
Type Lambdas Summary
- Type lambdas can express higher-kinded types directly without defining a new type.
- Type lambdas can be used to partially apply type constructors.
- These are used in contexts where higher-kinded types are needed, like in defining typeclasses like Functor.
- Kind Projector provides clear syntax for type lambdas for easier to use.
- You can simulate curried type parameters using type lambdas for more complex type abstractions.