Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
177: Export Clauses in Scala 3
One of the biggest headaches when building a library is balancing internal organization with external usability. I love splitting my logic into small, focused objects—say, one for linear algebra and one for trigonometry—but I hate forcing the user to remember exactly which object holds which method. This is where the export clause in Scala 3 comes in. It lets us "pull" members from another object into our current one, effectively creating a facade without manually wrapping every single function.
The problem with nested utility objects
Let's imagine we're building a small physics engine. Internally, I want to keep my math logic separated because that's just cleaner for maintenance. I'll start by defining two specialized objects: one for basic vector operations and one for trigonometric helpers.
object VectorMath:
def add(x1: Double, y1: Double, x2: Double, y2: Double): (Double, Double) =
(x1 + x2, y1 + y2)
def magnitude(x: Double, y: Double): Double =
scala.math.sqrt(x*x + y*y)
object TrigMath:
def degreesToRadians(deg: Double): Double =
deg * (scala.math.Pi / 180.0)
def magnitude(angle: Double, length: Double): (Double, Double) =
(scala.math.cos(angle) * length, scala.math.sin(angle) * length)
If I leave it like this, the user has to call VectorMath.add(...) or TrigMath.degreesToRadians(...). It's fine for a tiny project, but in a large API, this leads to a fragmented experience. I want a single PhysicsMath object that feels like a one-stop shop.
Flattening the API with export
Instead of writing "wrapper" methods that just call the underlying objects, I can use the export keyword. This tells the compiler: "Take these members from that object and make them look like they belong to me."
object PhysicsMath:
export VectorMath.*
export TrigMath.*
Now, the user can just call PhysicsMath.add(1, 2, 3, 4). It looks like add is defined inside PhysicsMath, but it's actually just being routed to VectorMath. It's a clean way to keep your source code modular while keeping your public API flat.
Dealing with naming collisions
Here is where I usually trip up. I just wrote export TrigMath.* and export VectorMath.*, but if you look closely at my objects above, both of them have a method called magnitude. When I try to compile this, the compiler is going to scream at me because PhysicsMath now has two different definitions for the same method name.
// This will cause a compilation error:
// Ambiguous reference to 'magnitude'
object PhysicsMath:
export VectorMath.*
export TrigMath.*
I can't just export everything blindly when there's an overlap. To fix this, I need to be explicit about which one I'm exporting or rename the exported member using the as keyword. In this case, since the TrigMath version of magnitude is actually calculating a vector from a polar coordinate, I'll rename it to fromPolar to make it clearer.
object PhysicsMath:
export VectorMath.*
// I'll export everything from TrigMath, but rename the conflicting method
export TrigMath.*
export TrigMath.magnitude as fromPolar
Wait, that's not quite right. If I export TrigMath.*, I'm still exporting the original magnitude, which causes the clash. I have to be more surgical. I'll export the specific methods I want from TrigMath, or I'll avoid the wildcard if the collision is too messy. Let's do it the right way: export the vector basics broadly, and be specific with the trig tools.
object PhysicsMath:
export VectorMath.*
export TrigMath.degreesToRadians
export TrigMath.magnitude as fromPolar
Now it works. PhysicsMath.magnitude refers to the vector length, and PhysicsMath.fromPolar handles the trigonometry. We've kept our internal organization but gave the user a predictable, collision-free interface.
📋 Practical Task
Building a CloudService Facade
You are designing a cloud management library. You have two internal objects: S3Storage (with methods uploadFile and deleteFile) and EC2Compute (with methods startInstance and stopInstance).
Create a facade object named CloudService that uses export clauses to make all four of these methods available directly on CloudService.
To add a twist: both S3Storage and EC2Compute have a method called getStatus. You must export both, but rename S3Storage.getStatus to getStorageStatus and EC2Compute.getStatus to getComputeStatus within the CloudService object.
There are no comments for now.