F#

Equivalencias idiomáticas entre Kotlin y F#. 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 F#, la CLI de .NET (dotnet build, dotnet test, dotnet run, archivos .fsproj) 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
la CLI de .NET (dotnet build, dotnet test, dotnet run, archivos .fsproj)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar la CLI de .NET (dotnet build, dotnet test, dotnet run, archivos .fsproj).
Recomendación
Expón el contrato del fixture y usa la CLI de .NET (dotnet build, dotnet test, dotnet run, archivos .fsproj) 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 F# 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 dotnet build
docker compose run --rm app dotnet test
docker compose run --rm app dotnet run

Dockerfile mínimo para el toolchain de F#.

FROM mcr.microsoft.com/dotnet/sdk:9.0
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 F#, una solución .NET (dotnet sln add modulo/Modulo.fsproj) 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
una solución .NET (dotnet sln add modulo/Modulo.fsproj)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar una solución .NET (dotnet sln add modulo/Modulo.fsproj).
Recomendación
Expón el contrato del fixture y usa una solución .NET (dotnet sln add modulo/Modulo.fsproj) 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 F# 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.

<!-- cli/Cli.fsproj -->
<ItemGroup>
  <ProjectReference Include="../core/Core.fsproj" />
</ItemGroup>

Fuentes

Equivalencia desde Kotlin

Soporte CLI

Cercana

En F#, Argu, describiendo las opciones como casos de una unión discriminada con atributos 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
Argu, describiendo las opciones como casos de una unión discriminada con atributos
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar Argu, describiendo las opciones como casos de una unión discriminada con atributos.
Recomendación
Expón el contrato del fixture y usa Argu, describiendo las opciones como casos de una unión discriminada con atributos 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 F# 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.

type RandomSearchArgs =
    | [<Mandatory>] Config of path: string
    | [<Mandatory>] Report of path: string
    | Report_Format of format: string
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | Config _ -> "Configuration JSON."
            | Report _ -> "Report JSON."
            | Report_Format _ -> "Report contract to write: v4 (default), v3, or v2."

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 F#, Thoth.Json (decodificadores explícitos y componibles con Decode.fromString) 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
Thoth.Json (decodificadores explícitos y componibles con Decode.fromString)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar Thoth.Json (decodificadores explícitos y componibles con Decode.fromString).
Recomendación
Expón el contrato del fixture y usa Thoth.Json (decodificadores explícitos y componibles con Decode.fromString) 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 F# 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.

type ExperimentConfig = { Objective: string; Seeds: int list; TargetValue: float }

let decoder =
    Decode.object (fun get ->
        { Objective = get.Required.Field "objective" Decode.string
          Seeds = get.Required.Field "seeds" (Decode.list Decode.int)
          TargetValue = get.Optional.Field "targetValue" Decode.float |> Option.defaultValue 0.0 })

let config = Decode.fromString decoder 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
Expecto con Expect.throws
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.

testCase "rejects a non-positive budget" <| fun _ ->
    Expect.throws (fun () -> Budget.create 0 |> ignore) "budget is invalid"

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
Expecto y dotnet test para agrupar evidencia por afirmació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 testCase de Expecto.
Expect.throws (fun () -> Budget.create 0 |> ignore) "invalid"
// Matriz finita: varios testCase.
// Ley general: FsCheck.

Fuentes

Equivalencia desde Kotlin

Pruebas unitarias

Cercana

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

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

testCase "rejects a negative budget" <| fun _ ->
    Expect.throws (fun () -> parseBudget "-1" |> ignore) "budget is invalid"

Fuentes

Equivalencia desde Kotlin

Frameworks y estilos BDD

Cercana

En F#, TickSpec con escenarios Gherkin sobre .NET expresa la responsabilidad del fixture: Describe que atom random-search --objective sphere --budget 1000 --seed 11 produce una configuración válida sin ejecutar optimización.

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

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

Diferencias relevantes

  • BDD comunica comportamiento y colaboración; un nombre de prueba legible por sí solo no crea un flujo de especificación completo.
  • La forma idiomática de F# debe conservarse aunque la sintaxis difiera de Kotlin.

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

[<Then "a valid experiment configuration is produced">]
let validConfiguration () =
    parse [ "--budget"; "1000" ] |> should not' (be Null)

Fuentes

Equivalencia desde Kotlin

Soporte DDT

Cercana

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

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

[ "1000", true; "-1", false ]
|> List.map (fun (value, valid) ->
    testCase $"budget {value}" <| fun _ ->
        Expect.equal (parseBudget value |> Result.isOk) valid "matches case")

Fuentes

Equivalencia desde Kotlin

Soporte PBT

Cercana

En F#, FsCheck integrado mediante testProperty de Expecto 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
FsCheck integrado mediante testProperty de Expecto
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar FsCheck integrado mediante testProperty de Expecto.
Recomendación
Expón el contrato del fixture y usa FsCheck integrado mediante testProperty de Expecto 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 F# 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.

testProperty "positive budgets remain positive" <| fun (PositiveInt budget) ->
    parseBudget (string budget.Get) |> Result.get > 0

Fuentes

Equivalencia desde Kotlin

DSLs de aserciones

Cercana

En F#, expectativas Expect.equal de Expecto 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
expectativas Expect.equal de Expecto
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar expectativas Expect.equal de Expecto.
Recomendación
Expón el contrato del fixture y usa expectativas Expect.equal de Expecto 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 F# 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.

Expect.equal config.Objective "sphere" "objective"
Expect.equal config.Budget 1000 "budget"
Expect.equal config.Seeds [ 11 ] "seeds"

Fuentes