F#

Equivalencias idiomáticas entre Kotlin y F#. 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

El caso privado impide construir el valor sin pasar por create, igual que un constructor privado más una fábrica en Kotlin. create devuelve Option en vez de lanzar una excepción.

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

Mecanismo
Unión discriminada de un caso con constructor privado y una función create.
Idea conservada
El caso privado impide construir EvaluationBudget sin pasar por create, que valida el invariante antes de entregar el valor.
Riesgo de diseño
Quitar el modificador private del caso, permitiendo construir EvaluationBudget directamente sin pasar por create.
Recomendación
Declara el caso como private y expón solo create devolviendo Option para dejar explícita la posibilidad de fallo.
Convención del curso
Expón únicamente la función create; nunca el caso constructor por sí mismo.
Mecanismo del lenguaje
Unión discriminada de un caso con constructor privado.

Diferencias relevantes

  • create devuelve Option en vez de lanzar una excepción.
  • El caso sigue siendo una unión discriminada de un caso, no un value class con borrado garantizado en todos los escenarios.

create es la única forma pública de obtener un EvaluationBudget.

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

val budget = EvaluationBudget(1000) // remaining = 1000
F#
type EvaluationBudget = private EvaluationBudget of int

module EvaluationBudget =
    let create remaining =
        if remaining > 0 then Some (EvaluationBudget remaining) else None

let budget = EvaluationBudget.create 1000 // Some (EvaluationBudget 1000)

Fuentes

Equivalencia desde Kotlin

Construcción controlada

Cercana

En F#, caso privado y función constructora expresa la responsabilidad del fixture: Impide crear una edad negativa.

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

Mecanismo
caso privado y función constructora
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar caso privado y función constructora.
Recomendación
Expón el contrato del fixture y usa caso privado y función constructora 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 F# debe conservarse aunque la sintaxis difiera de Kotlin.

Impide crear una edad negativa.

type Age = private Age of int
let age value = if value >= 0 then Some(Age value) else None

Fuentes

Equivalencia desde Kotlin

Clases de datos

Cercana

En F#, record expresa la responsabilidad del fixture: Modela una medición con igualdad por sus datos.

Referencia Kotlin: data class genera operaciones de valor conocidas.

Mecanismo
record
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar record.
Recomendación
Expón el contrato del fixture y usa record 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 F# debe conservarse aunque la sintaxis difiera de Kotlin.

Modela una medición con igualdad por sus datos.

type Measurement = { Name: string; Value: int }

Fuentes

Equivalencia desde Kotlin

Singletons

Cercana

En F#, valor de módulo expresa la responsabilidad del fixture: Expone una configuración compartida única.

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

Mecanismo
valor de módulo
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar valor de módulo.
Recomendación
Expón el contrato del fixture y usa valor de módulo 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 F# debe conservarse aunque la sintaxis difiera de Kotlin.

Expone una configuración compartida única.

module Settings
let retries = 3

Fuentes

Equivalencia desde Kotlin

Enumeraciones

Cercana

En F#, unión discriminada 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
unión discriminada
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar unión discriminada.
Recomendación
Expón el contrato del fixture y usa unión discriminada 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 F# debe conservarse aunque la sintaxis difiera de Kotlin.

Representa un estado finito de una tarea.

type Status = New | Done

Fuentes

Equivalencia desde Kotlin

Jerarquías selladas

Directa

Una unión discriminada declara sus casos como parte del tipo. El compilador advierte con FS0025 si el match no cubre todos los casos; el proyecto puede tratar esa advertencia como error.

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

Mecanismo
Unión discriminada con dos casos y match exhaustivo.
Idea conservada
El compilador advierte con FS0025 si el match no cubre todos los casos, igual que ocurre con when sobre una sealed interface en Kotlin.
Riesgo de diseño
Ignorar la advertencia FS0025 en un match incompleto en vez de tratarla como error en el proyecto del curso.
Recomendación
Configura --warnaserror:25 (o equivalente) para que un match incompleto sobre una unión discriminada sea un error de compilación.
Convención del curso
Trata la advertencia FS0025 como error en el proyecto del curso.
Mecanismo del lenguaje
Unión discriminada + match exhaustivo.

Diferencias relevantes

  • Por defecto la advertencia de exhaustividad (FS0025) no detiene la compilación; el proyecto puede tratarla como error con --warnaserror:25.
  • Los casos se declaran directamente como parte del tipo, no como implementaciones separadas de una interfaz.

match cubre ambos casos de la unión discriminada.

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})"
}
F#
type SearchResult =
    | Completed of bestValue: float
    | Failed of reason: string

let describe result =
    match result with
    | Completed bestValue -> $"Completed({bestValue})"
    | Failed reason -> $"Failed({reason})"

Fuentes

Equivalencia desde Kotlin

Alias de tipos

Cercana

En F#, abreviatura 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
abreviatura type
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar abreviatura type.
Recomendación
Expón el contrato del fixture y usa abreviatura 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 F# debe conservarse aunque la sintaxis difiera de Kotlin.

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

type Transform = int -> int

Fuentes