Skip to Content
Course content

177: Export Clauses in Scala 3

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.