Scala 3

Equivalencias idiomáticas entre Kotlin y Scala 3. Kotlin se presenta como referencia en cada concepto.

Integración con el ecosistema

Un algoritmo correcto también debe compilarse, organizarse y ejecutarse: estas herramientas son las que hacen posible entregar y comparar una implementación real.

Equivalencia desde Kotlin

Herramientas

Cercana

En Scala 3, sbt (build.sbt y las tareas compile, test, run) expresa la responsabilidad del fixture: Instala las dependencias declaradas, construye todos los módulos, ejecuta las pruebas y corre la aplicación de línea de comandos.

Referencia Kotlin: Gradle, mediante el wrapper ./gradlew, resuelve las dependencias declaradas, construye los subproyectos registrados en settings.gradle.kts y ejecuta tareas como build, test y :app:run.

Mecanismo
sbt (build.sbt y las tareas compile, test, run)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar sbt (build.sbt y las tareas compile, test, run).
Recomendación
Expón el contrato del fixture y usa sbt (build.sbt y las tareas compile, test, run) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Distingue el ejecutable de build invocado (el wrapper o la CLI de la herramienta) de los archivos de manifiesto y lockfile que declaran las dependencias y su resolución.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Instala las dependencias declaradas, construye todos los módulos, ejecuta las pruebas y corre la aplicación de línea de comandos.

docker compose run --rm app sbt compile
docker compose run --rm app sbt test
docker compose run --rm app sbt run

Dockerfile mínimo para el toolchain de Scala 3.

FROM sbtscala/scala-sbt:eclipse-temurin-25_1.10.7_3.6.3
WORKDIR /workspace
COPY . .

compose.yaml: construye la imagen y monta el proyecto para correr los comandos anteriores dentro del contenedor.

services:
  app:
    build: .
    volumes:
      - .:/workspace
    working_dir: /workspace

Fuentes

Equivalencia desde Kotlin

Módulos, workspaces y monorepos

Cercana

En Scala 3, un build multi-proyecto de sbt (lazy val modulo = project.in(file("modulo")) en build.sbt) expresa la responsabilidad del fixture: Organiza un ejecutable de línea de comandos que depende de módulos de dominio reutilizables, conservando el sentido de la dependencia.

Referencia Kotlin: Un build multi-proyecto de Gradle declara sus subproyectos (:app, :optimization:core, :optimization:execution, :optimization:baselines, :optimization:metaheuristics:single-state, :reporting:fair) en settings.gradle.kts mediante include(...), mientras que la lógica de build compartida vive en builds incluidos aparte (build-logic, build-conventions) declarados con includeBuild(...); cada dependencia entre proyectos se declara explícitamente, por ejemplo implementation(projects.optimization.core) en el build.gradle.kts de :app.

Mecanismo
un build multi-proyecto de sbt (lazy val modulo = project.in(file("modulo")) en build.sbt)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar un build multi-proyecto de sbt (lazy val modulo = project.in(file("modulo")) en build.sbt).
Recomendación
Expón el contrato del fixture y usa un build multi-proyecto de sbt (lazy val modulo = project.in(file("modulo")) en build.sbt) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Un monorepo es una estrategia de alojamiento de un repositorio, no un mecanismo de build: un repositorio puede ser un monorepo sin usar ningún workspace, y un workspace puede vivir en un repositorio que no aloja ningún otro proyecto además del suyo.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Organiza un ejecutable de línea de comandos que depende de módulos de dominio reutilizables, conservando el sentido de la dependencia.

// build.sbt
lazy val core = project.in(file("core"))
lazy val cli = project.in(file("cli")).dependsOn(core)

Fuentes

Equivalencia desde Kotlin

Soporte CLI

Cercana

En Scala 3, Decline, componiendo Opts de forma aplicativa dentro de un CommandApp expresa la responsabilidad del fixture: Ejecuta random-search con un archivo de configuración y escribe su reporte, mostrando ayuda generada y fallando antes de optimizar ante una opción inválida.

Referencia Kotlin: Clikt define atom como comando raíz sin operación propia (NoOpCliktCommand) con los subcomandos random-search y local-search; cada uno extiende AbstractExperimentCommand, que declara --config y --report como opciones requeridas y --report-format con v4 por defecto, valida la configuración antes de ejecutar el experimento y traduce los errores esperados en CliktError.

Mecanismo
Decline, componiendo Opts de forma aplicativa dentro de un CommandApp
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar Decline, componiendo Opts de forma aplicativa dentro de un CommandApp.
Recomendación
Expón el contrato del fixture y usa Decline, componiendo Opts de forma aplicativa dentro de un CommandApp solo para la garantía que realmente ofrece.

Diferencias relevantes

  • El análisis de argumentos y su validación deben completarse antes de ejecutar cualquier lógica de optimización; ninguna biblioteca madura y con pruebas propias debe reemplazarse por un analizador manual.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Ejecuta random-search con un archivo de configuración y escribe su reporte, mostrando ayuda generada y fallando antes de optimizar ante una opción inválida.

val config = Opts.option[Path]("config", help = "Configuration JSON.")
val report = Opts.option[Path]("report", help = "Report JSON.")
val reportFormat = Opts.option[String]("report-format", help = "...").withDefault("v4")

val randomSearch = Command("random-search", "Run random search.") {
  (config, report, reportFormat).mapN(runRandomSearch)
}

Contrato observable compartido por cualquier reimplementación de la CLI.

$ atom random-search --config config.json --report report.json
Completed random-search: 2 runs; report format: v4; report: report.json

Fuentes

Equivalencia desde Kotlin

Serialización

Cercana

En Scala 3, circe (un Decoder derivado semiautomáticamente y decode[T]) expresa la responsabilidad del fixture: Decodifica una configuración JSON con el objetivo, las semillas y el valor objetivo opcional de un experimento, distinguiendo un JSON sintácticamente inválido de un valor de dominio inválido.

Referencia Kotlin: kotlinx.serialization decodifica el JSON tipado mediante un Json compartido (prettyPrint = true, encodeDefaults = true) y un SerializersModule polimórfico para tipos como SearchSpace; readFromJson<T>() decodifica el archivo y deja que SerializationException se propague, mientras que un valor de dominio inválido, como un Delta no positivo, falla por separado en el constructor de su clase de valor.

Mecanismo
circe (un Decoder derivado semiautomáticamente y decode[T])
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar circe (un Decoder derivado semiautomáticamente y decode[T]).
Recomendación
Expón el contrato del fixture y usa circe (un Decoder derivado semiautomáticamente y decode[T]) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Un JSON sintácticamente válido puede seguir violando un invariante de dominio; decodificar con éxito no implica que el valor resultante sea válido.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Decodifica una configuración JSON con el objetivo, las semillas y el valor objetivo opcional de un experimento, distinguiendo un JSON sintácticamente inválido de un valor de dominio inválido.

case class ExperimentConfig(objective: String, seeds: List[Int], targetValue: Double = 0.0)
given Decoder[ExperimentConfig] = deriveDecoder

val config = decode[ExperimentConfig](jsonText)

Payload compartido: seeds es requerido; targetValue es opcional y por defecto 0.0.

{
  "objective": "sphere",
  "seeds": [7, 11],
  "targetValue": 0.0
}

Fuentes

Ecosistemas de pruebas

Verificar el comportamiento de una implementación requiere elegir, entre varias estrategias, la que responde mejor a cada tipo de afirmación sobre el algoritmo.

Equivalencia desde Kotlin

Desarrollo guiado por pruebas (TDD)

Cercana

Formula primero el comportamiento con Given–When–Then. En rojo, ejecuta el test más pequeño que demuestra que falta; en verde, implementa solo lo necesario; al refactorizar, mejora el diseño sin cambiar el comportamiento. Repite el ciclo con la siguiente afirmación. BDD puede ayudar a descubrir o expresar el comportamiento, pero Gherkin y los nombres legibles no son requisitos de TDD.

Referencia Kotlin: TDD parte por una afirmación observable y usa el test para conducir un cambio pequeño.

Mecanismo
ScalaTest con AnyFunSuite e intercept
Riesgo de diseño
Escribir una prueba grande que atraviesa configuración, red o archivos antes de haber fijado una sola regla observable.
Recomendación
Elige una afirmación pequeña, ejecútala en rojo, hazla verde con el cambio mínimo y refactoriza solo mientras permanezca verde.

Diferencias relevantes

  • El ciclo rojo–verde–refactor se conserva entre ecosistemas; cambian el runner, la sintaxis de aserciones y la forma de expresar una excepción.
  • El test debe fallar por el comportamiento ausente, no por una dependencia, una ruta o una integración todavía no necesaria.

Un presupuesto de evaluación positivo se acepta y uno no positivo se rechaza.

test("rejects a non-positive budget") {
  intercept[IllegalArgumentException](Budget(0))
}

Fuentes

Equivalencia desde Kotlin

Selección de estrategia de pruebas

Cercana

Empieza por la afirmación: usa un ejemplo enfocado para un comportamiento conocido, DDT para una matriz finita y PBT para una ley general. Añade pruebas de componente o integración cuando colaboran módulos, pruebas de contrato para una representación compartida y una verificación de entrega cuando importa el entorno documentado. Para algoritmos estocásticos controla colaboradores, conserva semillas reproducibles y prueba invariantes o evidencia estadística, no una trayectoria casual.

Referencia Kotlin: Seleccionar pruebas parte de la afirmación, la frontera afectada y las dependencias que deben controlarse.

Mecanismo
ScalaTest y sbt para suites enfocadas y de integración
Riesgo de diseño
Usar una prueba end-to-end para demostrar una regla local, o adoptar PBT solo porque hay un generador disponible.
Recomendación
Elige la evidencia más pequeña que pueda observar y falsar la afirmación; amplíala solo cuando una frontera modificada lo requiera.

Diferencias relevantes

  • La selección depende de la afirmación y la frontera, no del lenguaje ni del tamaño de una función.
  • Una prueba más amplia complementa la evidencia enfocada de TDD; no la reemplaza.

La afirmación determina la evidencia más pequeña adecuada antes de ampliar la verificación.

// Regla conocida: un test de ScalaTest.
intercept[IllegalArgumentException](Budget(0))
// Matriz finita: TableDrivenPropertyChecks.
// Ley general: ScalaCheck.

Fuentes

Equivalencia desde Kotlin

Pruebas unitarias

Cercana

En Scala 3, ScalaTest con AnyFunSuite, assert e intercept expresa la responsabilidad del fixture: Acepta el presupuesto 1000 y rechaza el presupuesto -1 antes de ejecutar optimización.

Referencia Kotlin: Una prueba unitaria llama al analizador de presupuesto y compara el valor o el error de validación.

Mecanismo
ScalaTest con AnyFunSuite, assert e intercept
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar ScalaTest con AnyFunSuite, assert e intercept.
Recomendación
Expón el contrato del fixture y usa ScalaTest con AnyFunSuite, assert e intercept solo para la garantía que realmente ofrece.

Diferencias relevantes

  • La prueba mantiene el análisis local: no inicia una ejecución de optimización ni un proceso externo.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Acepta el presupuesto 1000 y rechaza el presupuesto -1 antes de ejecutar optimización.

test("rejects a negative budget") {
  intercept[IllegalArgumentException](parseBudget("-1"))
}

Fuentes

Equivalencia desde Kotlin

Frameworks y estilos BDD

Cercana

ScalaTest no ejecuta archivos Gherkin en este estilo: AnyWordSpec usa especificaciones anidadas y legibles dentro del runner principal.

Referencia Kotlin: Un escenario describe la configuración observable; analizar el comando no ejecuta el algoritmo.

Mecanismo
el estilo AnyWordSpec de ScalaTest
Riesgo de diseño
Presentar una suite con nombres legibles como si proporcionara por sí sola colaboración mediante archivos feature.
Recomendación
Use WordSpec cuando la especificación viva junto al código; use un framework Gherkin solo si los stakeholders necesitan archivos feature ejecutables.

Diferencias relevantes

  • El estilo Given–When–Then se expresa en los bloques de especificación, no en definiciones de pasos separadas.
  • La ejecución, fixtures y reporte siguen siendo los de ScalaTest.

Describe que atom random-search --objective sphere --budget 1000 --seed 11 produce una configuración válida sin ejecutar optimización.

"the command parser" should {
  "accept a valid budget" in {
    parse(List("--budget", "1000")).budget shouldBe 1000
  }
}

Fuentes

Equivalencia desde Kotlin

Soporte DDT

Cercana

En Scala 3, TableDrivenPropertyChecks de ScalaTest expresa la responsabilidad del fixture: Una tabla cubre presupuesto válido, faltante, negativo, objetivo desconocido, semilla repetida y opción desconocida.

Referencia Kotlin: Los casos explícitos vinculan argumentos CLI con un resultado de análisis esperado.

Mecanismo
TableDrivenPropertyChecks de ScalaTest
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar TableDrivenPropertyChecks de ScalaTest.
Recomendación
Expón el contrato del fixture y usa TableDrivenPropertyChecks de ScalaTest solo para la garantía que realmente ofrece.

Diferencias relevantes

  • DDT enumera ejemplos elegidos; no sustituye la generación de dominios ni una especificación de comportamiento.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Una tabla cubre presupuesto válido, faltante, negativo, objetivo desconocido, semilla repetida y opción desconocida.

forAll(Table(("value", "valid"), ("1000", true), ("-1", false))) { (value, valid) =>
  parseBudget(value).isRight shouldBe valid
}

Fuentes

Equivalencia desde Kotlin

Soporte PBT

Cercana

En Scala 3, ScalaCheck mediante ScalaCheckPropertyChecks de ScalaTest expresa la responsabilidad del fixture: Para cualquier texto decimal positivo aceptado, el presupuesto resultante es positivo; los casos inválidos se reducen y se pueden reproducir.

Referencia Kotlin: Una propiedad general se ejecuta sobre valores generados y comunica cómo repetir un contraejemplo.

Mecanismo
ScalaCheck mediante ScalaCheckPropertyChecks de ScalaTest
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar ScalaCheck mediante ScalaCheckPropertyChecks de ScalaTest.
Recomendación
Expón el contrato del fixture y usa ScalaCheck mediante ScalaCheckPropertyChecks de ScalaTest solo para la garantía que realmente ofrece.

Diferencias relevantes

  • PBT genera y reduce datos; una matriz DDT conserva mejor los ejemplos que el lector debe reconocer directamente.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Para cualquier texto decimal positivo aceptado, el presupuesto resultante es positivo; los casos inválidos se reducen y se pueden reproducir.

forAll { (budget: Int) =>
  whenever(budget > 0) { parseBudget(budget.toString).toOption.value should be > 0 }
}

Fuentes

Equivalencia desde Kotlin

DSLs de aserciones

Cercana

En Scala 3, matchers shouldBe de ScalaTest expresa la responsabilidad del fixture: Una configuración analizada contiene objective = sphere, budget = 1000 y seeds = [11]; una falla debe identificar esperado y recibido.

Referencia Kotlin: Las aserciones expresan el resultado observable de una configuración sin ocultar su estructura relevante.

Mecanismo
matchers shouldBe de ScalaTest
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar matchers shouldBe de ScalaTest.
Recomendación
Expón el contrato del fixture y usa matchers shouldBe de ScalaTest solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Una biblioteca fluida puede mejorar el diagnóstico, pero no reemplaza una expectativa precisa ni es necesaria cuando la API idiomática ya basta.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Una configuración analizada contiene objective = sphere, budget = 1000 y seeds = [11]; una falla debe identificar esperado y recibido.

config.objective shouldBe "sphere"
config.budget shouldBe 1000
config.seeds shouldBe List(11)

Fuentes