Scala 3

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

Modelado de dominio y datos

Cada lenguaje ofrece herramientas distintas para dar forma a un dominio: distinguir qué garantías son del compilador y cuáles son solo convención evita errores de diseño.

Equivalencia desde Kotlin

Clases de valor

Directa

Un opaque type es distinto de Int solo para el verificador de tipos; se borra en tiempo de ejecución, igual que ocurre habitualmente con un value class de Kotlin. La validación vive en el apply del companion object.

Referencia Kotlin: Declarar un tipo nominal para un dato primitivo y validar su invariante al construirse.

Mecanismo
opaque type con validación en el apply del companion object.
Idea conservada
El tipo es distinto de Int solo en tiempo de compilación y se borra en tiempo de ejecución, sin costo de asignación adicional.
Riesgo de diseño
Olvidar que, fuera del archivo donde se declara el opaque type, el compilador no permite operar con el Int subyacente directamente.
Recomendación
Usa opaque type cuando el valor deba comportarse como un Int en tiempo de ejecución sin costo adicional.
Convención del curso
Valida el invariante en el apply del companion object.
Mecanismo del lenguaje
opaque type.

Diferencias relevantes

  • opaque type no permite añadir estado adicional al valor subyacente, a diferencia de un value class con más de una propiedad lógica.
  • La validación vive en el apply del companion object, no en un bloque init como en Kotlin.

apply valida el invariante antes de exponer el valor opaco.

Kotlin
@JvmInline
value class EvaluationBudget(val remaining: Int) {
    init {
        require(remaining > 0) { "El presupuesto debe ser positivo." }
    }
}

val budget = EvaluationBudget(1000) // remaining = 1000
Scala 3
opaque type EvaluationBudget = Int

object EvaluationBudget:
  def apply(remaining: Int): EvaluationBudget =
    require(remaining > 0, "El presupuesto debe ser positivo.")
    remaining

val budget = EvaluationBudget(1000) // 1000

Fuentes

Equivalencia desde Kotlin

Construcción controlada

Cercana

En Scala 3, constructor privado y apply expresa la responsabilidad del fixture: Impide crear una edad negativa.

Referencia Kotlin: Un constructor privado y una fábrica validan el invariante.

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

Diferencias relevantes

  • La privacidad de construcción y la validación son responsabilidades distintas; declara ambas.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Impide crear una edad negativa.

final class Age private (val value: Int)
object Age: def of(value: Int) = require(value >= 0); Age(value)

Fuentes

Equivalencia desde Kotlin

Clases de datos

Cercana

En Scala 3, case class expresa la responsabilidad del fixture: Modela una medición con igualdad por sus datos.

Referencia Kotlin: data class genera operaciones de valor conocidas.

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

Diferencias relevantes

  • No atribuyas copia, destructuración o igualdad estructural a un mecanismo que no las provee.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Modela una medición con igualdad por sus datos.

case class Measurement(name: String, value: Int)

Fuentes

Equivalencia desde Kotlin

Singletons

Cercana

En Scala 3, object expresa la responsabilidad del fixture: Expone una configuración compartida única.

Referencia Kotlin: object declara una instancia única del lenguaje.

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

Diferencias relevantes

  • Distingue unicidad por módulo, por proceso y por contenedor de inyección.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Expone una configuración compartida única.

object Settings:
  val retries = 3

Fuentes

Equivalencia desde Kotlin

Enumeraciones

Cercana

En Scala 3, enum expresa la responsabilidad del fixture: Representa un estado finito de una tarea.

Referencia Kotlin: enum class declara constantes nombradas y puede contener datos o comportamiento.

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

Diferencias relevantes

  • Un enum de constantes y una unión de variantes con datos no son automáticamente equivalentes.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Representa un estado finito de una tarea.

enum Status:
  case New, Done

Fuentes

Equivalencia desde Kotlin

Jerarquías selladas

Directa

Un enum de Scala 3 es cerrado por naturaleza del lenguaje. Por defecto, un match no exhaustivo produce una advertencia del compilador, no un error; el proyecto debe activar -Xfatal-warnings para que sea un error de compilación como en Kotlin.

Referencia Kotlin: Declarar una familia cerrada de alternativas para que el compilador exija manejar cada caso.

Mecanismo
enum sellado por naturaleza del lenguaje y match sobre sus casos.
Idea conservada
match sobre un enum cerrado identifica el caso concreto y desestructura su payload, igual que when sobre una sealed interface en Kotlin.
Riesgo de diseño
Confiar en que un match no exhaustivo siempre falla la compilación sin haber configurado el proyecto para tratar esa advertencia como error.
Recomendación
Activa -Xfatal-warnings (o el chequeo de exhaustividad equivalente) en el proyecto del curso para que un match incompleto sea un error, no solo una advertencia.
Convención del curso
Activa -Xfatal-warnings para que la exhaustividad sea obligatoria.
Mecanismo del lenguaje
enum sellado + match.

Diferencias relevantes

  • Por defecto, un match no exhaustivo produce una advertencia del compilador, no un error; el proyecto debe activar -Xfatal-warnings (u otra opción equivalente) para que sea un error de compilación.
  • Los casos del enum viven agrupados bajo un solo enum, no como subtipos declarados por separado.

match desestructura cada caso del enum sellado.

Kotlin
sealed interface SearchResult {
    data class Completed(val bestValue: Double) : SearchResult
    data class Failed(val reason: String) : SearchResult
}

fun describe(result: SearchResult): String = when (result) {
    is SearchResult.Completed -> "Completed(${result.bestValue})"
    is SearchResult.Failed -> "Failed(${result.reason})"
}
Scala 3
enum SearchResult:
  case Completed(bestValue: Double)
  case Failed(reason: String)

def describe(result: SearchResult): String = result match
  case SearchResult.Completed(bestValue) => s"Completed($bestValue)"
  case SearchResult.Failed(reason)       => s"Failed($reason)"

Fuentes

Equivalencia desde Kotlin

Alias de tipos

Cercana

En Scala 3, type expresa la responsabilidad del fixture: Da un nombre legible a una firma de transformación.

Referencia Kotlin: typealias crea un nombre alternativo, no un nuevo tipo nominal.

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

Diferencias relevantes

  • No presentes un alias como una frontera de dominio cuando no cambia la identidad de tipo.
  • La forma idiomática de Scala 3 debe conservarse aunque la sintaxis difiera de Kotlin.

Da un nombre legible a una firma de transformación.

type Transform = Int => Int

Fuentes