Skip to content
Development
Skill

/nw-fp-kotlin

Kotlin language-specific patterns with Arrow, Raise DSL, and coroutine-based effects

From plugin
nwave
628151 skills34 agents27 commands

Claude Code

$ npx -y skills add nWave-ai/nWave --skill nw-fp-kotlin --agent claude-code

SKILL.md

nw-fp-kotlin.SKILL.md
name: nw-fp-kotlin
agent: nw-functional-software-crafter
description: Kotlin language-specific patterns with Arrow, Raise DSL, and coroutine-based effects
user-invocable: false
disable-model-invocation: true

FP in Kotlin -- Functional Software Crafter Skill

Cross-references: [fp-principles](../nw-fp-principles/SKILL.md) | [fp-domain-modeling](../nw-fp-domain-modeling/SKILL.md) | [pbt-jvm](../nw-pbt-jvm/SKILL.md)

When to Choose Kotlin

  • Best for: gentlest FP onboarding for Java teams | Android | coroutines | pragmatic FP with great IDE support
  • Not ideal for: teams wanting compiler-enforced purity | full effect systems | higher-kinded type abstractions

[STARTER] Quick Setup

mkdir order-service && cd order-service
gradle init --type kotlin-application --dsl kotlin
# Add arrow-core, arrow-fx-coroutines, kotest-property to build.gradle.kts
./gradlew build && ./gradlew test

[STARTER] Type System for Domain Modeling

Choice Types (Sealed Hierarchies)

sealed interface PaymentMethod {
    data class CreditCard(val cardNumber: String, val expiryDate: String) : PaymentMethod
    data class BankTransfer(val accountNumber: String) : PaymentMethod
    data object Cash : PaymentMethod
}

Exhaustive `when` expressions ensure all cases handled.

Record Types and Domain Wrappers

data class Customer(
    val customerId: CustomerId,
    val customerName: CustomerName,
    val customerEmail: EmailAddress
)

@JvmInline
value class OrderId(val value: Int)

@JvmInline
value class EmailAddress private constructor(val value: String) {
    companion object {
        fun from(raw: String): Either<ValidationError, EmailAddress> =
            if ("@" in raw) EmailAddress(raw).right()
            else InvalidEmail(raw).left()
    }
}

Value classes inlined by compiler -- zero allocation overhead.

[INTERMEDIATE] Validated Construction with Raise DSL

import arrow.core.raise.Raise
import arrow.core.raise.ensure

context(Raise<ValidationError>)
fun validateEmail(raw: String): EmailAddress {
    ensure(raw.contains("@")) { InvalidEmail(raw) }
    return EmailAddress.unsafeCreate(raw)
}

[INTERMEDIATE] Composition Style

Scope Functions as Lightweight Pipelines

val result = rawOrder
    .let(::validateOrder)
    .map(::priceOrder)
    .map(::confirmOrder)

Arrow Raise DSL (Direct-Style Typed Errors)

import arrow.core.raise.either

fun placeOrder(raw: RawOrder): Either<OrderError, Confirmation> = either {
    val validated = validateOrder(raw).bind()
    val priced = priceOrder(validated).bind()
    confirmOrder(priced).bind()
}

Looks imperative, behaves functionally. `bind()` short-circuits on `Left`.

Accumulating Errors

import arrow.core.raise.zipOrAccumulate

fun validateCustomer(raw: RawCustomer): Either<NonEmptyList<ValidationError>, Customer> = either {
    zipOrAccumulate(
        { validateName(raw.name) },
        { validateEmail(raw.email) },
        { validateAddress(raw.address) }
    ) { name, email, address -> Customer(name, email, address) }
}

[INTERMEDIATE] Effect Management

Kotlin is impure by default. `suspend` marks functions performing side effects or async work.

Pure Core / Imperative Shell with Coroutines

// Pure domain logic (no suspend, no side effects)
object OrderDomain {
    fun calculateDiscount(order: Order): Discount =
        if (order.lines.size > 10) Discount(0.1) else Discount(0.0)
}

// Imperative shell (suspend = side effects)
class OrderService(
    private val orderRepo: OrderRepository,
    private val pricingService: PricingService
) {
    suspend fun placeOrder(raw: RawOrder): Either<OrderError, Confirmation> = either {
        val validated = OrderDomain.validateOrder(raw).bind()
        val priced = pricingService.price(validated).bind()
        orderRepo.save(priced)
        Confirmation(priced.orderId)
    }
}

[ADVANCED] Hexagonal Architecture with Interfaces

// Port: interface defining capability
interface OrderRepository {
    suspend fun findOrder(id: OrderId): Order?
    suspend fun saveOrder(order: Order)
}

// Adapter: concrete implementation
class PostgresOrderRepository(private val db: Database) : OrderRepository {
    override suspend fun findOrder(id: OrderId): Order? =
        db.query("SELECT * FROM orders WHERE id = ?", id.value)
    override suspend fun saveOrder(order: Order) =
        db.execute("INSERT INTO orders ...", order)
}

DI: constructor injection via Koin, Dagger/Hilt, or manual wiring.

[INTERMEDIATE] Testing

**Frameworks**: Kotest (test framework + PBT) | jqwik (PBT on JUnit 5) | MockK (coroutine-aware mocking). See [pbt-jvm](../nw-pbt-jvm/SKILL.md) for detailed PBT patterns.

Property Test (Kotest)

import io.kotest.core.spec.style.FunSpec
import io.kotest.property.forAll

class OrderSpec : FunSpec({
    test("serialization round-trips") {
        forAll(orderArb) { order -> deserialize(serialize(order)) == order.right() }
    }
    test("validated orders have positive totals") {
        forAll(rawOrderArb) { raw ->
            when (val result = validateOrder(raw)) {
                is Either.Left -> true
                is Either.Right -> result.value.total.value > 0
            }
        }
    }
})

Custom Generator

val emailArb: Arb<EmailAddress> = arbitrary {
    val user = Arb.string(minSize = 1, maxSize = 10, codepoints = Codepoint.az()).bind()
    val domain = Arb.string(minSize = 1, maxSize = 8, codepoints = Codepoint.az()).bind()
    EmailAddress.unsafeCreate("$user@$domain.com")
}

[ADVANCED] Idiomatic Patterns

Null Safety as Option Alternative

fun findCustomer(id: CustomerId): Customer? = ...
val email = findCustomer(id)?.customerEmail?.value

// Use Either/Raise when you need error context
context(Raise<CustomerError>)
fun getCustomer(id: CustomerId): Customer =
    findCustomer(
Read more
@nwave-ai
Ships withnwave

AI agents that guide you from idea to working code, with human judgment at every gate. nWave runs inside Claude Code. It breaks feature delivery into seven waves (discover, diverge, discuss, design, devops, distill, deliver).

628151 skills34 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
628
Star history of nWave-ai/nWave on GitHub, a chart by star-history.com

Other skills on nwave.

nw-ad-critique-dimensions

nw-ad-critique-dimensi…

Review dimensions for acceptance test quality - happy path bias, GWT compliance, business language purity, coverage completeness,…

@nwave-ai@nwave-aiInstall Skill
nw-agent-testing

nw-agent-testing

5-layer testing approach for agent validation including adversarial testing, security validation, and prompt injection resistance

@nwave-ai@nwave-aiInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about nw-fp-kotlin.

Which coding agents can use nw-fp-kotlin?
nw-fp-kotlin installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.