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

如何关联两个类型参数?依赖前置结果的操作序列实现问询

Type-Safe Operation Dependencies and Execution in Scala

First, let's address your core question: how to properly relate the type parameter of an Operation to its result, and build a safe system for operations that depend on prior results. Your initial code uses Any for results, which loses type safety—we can fix that while handling dependencies cleanly.

Step 1: Define a Type-Safe Results Container

Instead of using a list of (Operation[_], Any), create a trait that guarantees we retrieve the correct type when accessing an operation's result:

trait Results {
  def get[Out](op: Operation[Out]): Out
}

This trait enforces that when you ask for the result of an Operation[Out], you get an Out—no casting required in user code.

Step 2: Refine the Operation Trait

Update the Operation trait to include a way to declare dependencies, and keep the type parameter for the result:

trait Operation[Out] {
  // Declare which operations this one depends on
  def dependencies: List[Operation[_]]
  // Execute the operation using previously computed results
  def apply(results: Results): Out
}

Step 3: Implement Concrete Operations

Now, your operations can explicitly declare dependencies and safely access their results:

class SomeOp extends Operation[String] {
  // No dependencies
  override def dependencies: List[Operation[_]] = Nil
  override def apply(results: Results): String = "foo"
}

class OtherOp(val dependency: SomeOp) extends Operation[String] {
  // Depends on SomeOp
  override def dependencies: List[Operation[_]] = List(dependency)
  override def apply(results: Results): String = {
    // Safely get the result of SomeOp with the correct type
    results.get(dependency) + "bar"
  }
}

Notice how OtherOp takes its dependency as a constructor parameter—this makes dependencies explicit and compile-time checked, avoiding the runtime pattern matching and casting from your original code.

Step 4: Implement the Results Container

We'll use a map under the hood, but wrap it to maintain type safety:

class MapResults(private val underlying: Map[Operation[_], Any]) extends Results {
  override def get[Out](op: Operation[Out]): Out = underlying(op).asInstanceOf[Out]
}

class ResultsBuilder(private val map: Map[Operation[_], Any] = Map.empty) {
  def add[Out](op: Operation[Out], result: Out): ResultsBuilder = {
    new ResultsBuilder(map + (op -> result))
  }

  def build(): Results = new MapResults(map)
}

object ResultsBuilder {
  def empty: ResultsBuilder = new ResultsBuilder()
}

The asInstanceOf here is safe because we only add results of the correct type via ResultsBuilder.add, which pairs an Operation[Out] with an Out.

Step 5: Execute All Operations with Dependency Ordering

To ensure operations run after their dependencies, we need a topological sort. Then we can execute operations in that order:

def topologicalSort(operations: List[Operation[_]]): List[Operation[_]] = {
  import scala.collection.mutable

  val visited = mutable.Set[Operation[_]]()
  val visiting = mutable.Set[Operation[_]]()
  val result = mutable.ListBuffer[Operation[_]]()

  def dfs(op: Operation[_]): Unit = {
    if (visiting.contains(op)) throw new IllegalArgumentException("Cycle detected in dependencies")
    if (!visited.contains(op)) {
      visiting.add(op)
      op.dependencies.foreach(dfs)
      visiting.remove(op)
      visited.add(op)
      result.append(op)
    }
  }

  operations.foreach(dfs)
  result.toList
}

def getAllOperations(operations: List[Operation[_]]): Set[Operation[_]] = {
  operations.flatMap(op => op +: getAllOperations(op.dependencies)).toSet
}

def applyAll(operations: List[Operation[_]]): Results = {
  // Include all dependencies even if not explicitly passed
  val allOperations = getAllOperations(operations).toList
  // Sort operations to respect dependencies
  val sortedOperations = topologicalSort(allOperations)
  
  // Execute each operation and collect results
  sortedOperations.foldLeft(ResultsBuilder.empty) { (builder, op) =>
    val result = op.apply(builder.build())
    builder.add(op, result)
  }.build()
}

Testing the Implementation

Let's see it in action:

val someOp = new SomeOp()
val otherOp = new OtherOp(someOp)

val results = applyAll(List(otherOp))

println(results.get(someOp))    // Output: "foo"
println(results.get(otherOp))   // Output: "foobar"

This code is fully type-safe: if you try to get a result of the wrong type, the compiler will throw an error. Dependencies are enforced at compile time, and cycles are detected at runtime.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:14:23