You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala能否实现添加元素时的编译期类型检查?

Can Scala enforce at compile time that no elements are added to a repository after a ComplexThing is added?

Problem Description

I'm trying to build a list of Thing instances where, after adding a subclass of ComplexThing, no more elements can be added. Right now I can enforce this with runtime checks, but I want to make this a compile-time constraint so my IDE highlights errors immediately. Here's my current code:

trait Thing
trait SimpleThing extends Thing
trait ComplexThing extends Thing
sealed case class DummySimpleThing() extends SimpleThing
sealed case class InstanceOfComplexThing() extends ComplexThing
class ThingRepository {
  private val buffer = new ListBuffer[Thing]()
  def addThing(thing: Thing): ThingRepository = {
    buffer += thing
    this
  }
}
object TestThingRepository {
  def testAddThings() = {
    new ThingRepository()
      .addThing(DummySimpleThing())
      .addThing(DummySimpleThing()) // it's ok to add SimpleThing after SimpleThing
      .addThing(InstanceOfComplexThing()) // it's ok to add ComplexThing after SimpleThing
      .addThing(DummySimpleThing()) // Not allowed to add anything after ComplexThing, how can I get compile time error here?
      .addThing(InstanceOfComplexThing()) // Not allowed anything after ComplexThing, how can I get compile time error here?
  }
}

Solution: Type State Pattern

Absolutely! You can enforce this rule at compile time in Scala using the Type State Pattern, which leverages Scala's strong type system to track the repository's state (open for additions vs. closed after a ComplexThing is added). This lets the compiler block invalid addThing calls before runtime.

Here's the revised implementation:

import scala.collection.mutable.ListBuffer

// Define state markers to track if the repository accepts new items
trait RepositoryState
sealed trait Open extends RepositoryState // Can add any Thing
sealed trait Closed extends RepositoryState // Cannot add anything after ComplexThing

trait Thing
trait SimpleThing extends Thing
trait ComplexThing extends Thing
sealed case class DummySimpleThing() extends SimpleThing
sealed case class InstanceOfComplexThing() extends ComplexThing

// Repository with a generic state parameter
class ThingRepository[S <: RepositoryState] private (private val buffer: ListBuffer[Thing]) {
  // Private constructor: use the companion object to create instances
  def this() = this(new ListBuffer[Thing]())

  // Add a SimpleThing: keeps the repository in Open state
  def addThing(thing: SimpleThing)(implicit ev: S =:= Open): ThingRepository[Open] = {
    buffer += thing
    this.asInstanceOf[ThingRepository[Open]]
  }

  // Add a ComplexThing: transitions the repository to Closed state
  def addThing(thing: ComplexThing)(implicit ev: S =:= Open): ThingRepository[Closed] = {
    buffer += thing
    this.asInstanceOf[ThingRepository[Closed]]
  }
}

// Companion object to create an initial Open-state repository
object ThingRepository {
  def apply(): ThingRepository[Open] = new ThingRepository[Open](new ListBuffer[Thing]())
}

Testing the Compile-Time Constraint

Now when you try to add items after a ComplexThing, the compiler (and your IDE) will throw an error immediately:

object TestThingRepository {
  def testAddThings() = {
    ThingRepository()
      .addThing(DummySimpleThing()) // ✅ Ok: stays Open
      .addThing(DummySimpleThing()) // ✅ Ok: stays Open
      .addThing(InstanceOfComplexThing()) // ✅ Ok: transitions to Closed
      // The lines below will cause COMPILE ERRORS (IDE will highlight them)
      // .addThing(DummySimpleThing()) 
      // .addThing(InstanceOfComplexThing())
  }
}

How This Works

  • State Tracking: The generic parameter S tracks whether the repository is Open or Closed.
  • Implicit Evidence: The ev: S =:= Open parameter ensures addThing can only be called on an Open repository—no valid evidence exists for Closed state, so those calls are blocked.
  • State Transition: Adding a ComplexThing returns a ThingRepository[Closed], which has no valid addThing methods, preventing any further additions.

This approach turns your runtime check into a compile-time guarantee, catching mistakes early in development.

内容的提问来源于stack exchange,提问作者Capacytron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 07:52:48