/nw-fp-kotlin
Kotlin language-specific patterns with Arrow, Raise DSL, and coroutine-based effects
$ npx -y skills add nWave-ai/nWave --skill nw-fp-kotlin --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/nw-fp-kotlin
Context preview
The summary Claude sees to decide when to auto-load this skill.
Kotlin language-specific patterns with Arrow, Raise DSL, and coroutine-based effects
SKILL.md
nw-fp-kotlin.SKILL.mdname: 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
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(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).
Repo: nWave-ai/nWave
Other skills on nwave.
- /nw-ab-critique-dimensions
Review dimensions for validating agent quality - template compliance, safety, testing, and priority validation
Open skill - /nw-abr-critique-dimensions
Review dimensions for validating agent quality - template compliance, safety, testing, and priority validation
Open skill - /nw-ad-critique-dimensions
Review dimensions for acceptance test quality - happy path bias, GWT compliance, business language purity, coverage completeness, walking skeleton user-centricity, priority validation, observable behavior assertions, traceability coverage, and walking skeleton boundary proof
Open skill - /nw-agent-creation-workflow
Detailed 5-phase workflow for creating agents - from requirements analysis through validation and iterative refinement
Open skill - /nw-agent-testing
5-layer testing approach for agent validation including adversarial testing, security validation, and prompt injection resistance
Open skill - /nw-architectural-styles-tradeoffs
Architectural style selection decision matrices, trade-off analysis, structural enforcement rules, and combination patterns. Load when choosing or evaluating architecture styles.
Open skill

