Skip to content
Development
Skill

/nw-fp-scala

Scala 3 language-specific patterns with ZIO, Cats Effect, and opaque types

From plugin
nwave
628151 skills34 agents27 commands

Claude Code

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

SKILL.md

nw-fp-scala.SKILL.md
name: nw-fp-scala
agent: nw-functional-software-crafter
description: Scala 3 language-specific patterns with ZIO, Cats Effect, and opaque types
user-invocable: false
disable-model-invocation: true

FP in Scala 3 -- 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 Scala

  • Best for: JVM with full FP power | large-scale systems | data engineering | richest effect ecosystem
  • Not ideal for: small teams wanting simplicity | Android (use Kotlin) | teams allergic to OO/FP duality

[STARTER] Quick Setup

cs install scala3-compiler scala3-repl sbt
sbt new scala/scala3.g8 && cd order-service
# Add zio, zio-test, scalacheck to build.sbt
sbt compile && sbt test

[STARTER] Type System for Domain Modeling

Choice Types and Record Types

enum PaymentMethod:
  case CreditCard(cardNumber: String, expiryDate: String)
  case BankTransfer(accountNumber: String)
  case Cash

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

Case classes provide structural equality, copy, and pattern matching for free.

[STARTER] Domain Wrappers (Opaque Types) -- Zero Cost

object OrderDomain:
  opaque type OrderId = Int
  object OrderId:
    def apply(value: Int): OrderId = value
  extension (id: OrderId) def value: Int = id

  opaque type EmailAddress = String
  object EmailAddress:
    def from(raw: String): Either[ValidationError, EmailAddress] =
      if raw.contains("@") then Right(raw)
      else Left(InvalidEmail(raw))

Inside defining scope, alias is transparent. Outside, only exported operations available.

[INTERMEDIATE] Composition Style

For-Comprehensions (Monadic Chaining)

def placeOrder(raw: RawOrder): Either[OrderError, Confirmation] =
  for
    validated <- validateOrder(raw)
    priced    <- priceOrder(validated)
    confirmed <- confirmOrder(priced)
  yield confirmed

Error Accumulation (Cats Validated or ZIO)

import cats.data.Validated
import cats.syntax.all.*

def validateCustomer(raw: RawCustomer): ValidatedNel[ValidationError, Customer] =
  (validateName(raw.name), validateEmail(raw.email), validateAddress(raw.address))
    .mapN(Customer.apply)

[ADVANCED] Effect Management

ZIO vs Cats Effect

ZIO: `ZIO[R, E, A]` with built-in typed errors, DI (ZLayer), batteries-included. Cats Effect: `IO[A]`, minimal type-class-based, Typelevel ecosystem (http4s, FS2, Doobie). Pick one and stay consistent.

ZIO Hexagonal Architecture

trait OrderRepository:
  def findOrder(id: OrderId): Task[Option[Order]]
  def saveOrder(order: Order): Task[Unit]

def placeOrder(raw: RawOrder): ZIO[OrderRepository & PricingService, OrderError, Confirmation] =
  for
    repo      <- ZIO.service[OrderRepository]
    validated <- ZIO.fromEither(validateOrder(raw))
    priced    <- ZIO.fromEither(priceOrder(validated))
    _         <- repo.saveOrder(priced)
  yield Confirmation(priced.orderId)

// Adapter
class PostgresOrderRepository(ds: DataSource) extends OrderRepository:
  def findOrder(id: OrderId): Task[Option[Order]] = ZIO.attemptBlocking { /* query */ }
  def saveOrder(order: Order): Task[Unit] = ZIO.attemptBlocking { /* insert */ }

val appLayer: ZLayer[Any, Nothing, OrderRepository & PricingService] =
  PostgresOrderRepository.layer ++ PricingServiceLive.layer

Cats Effect / Tagless Final

trait OrderRepository[F[_]]:
  def findOrder(id: OrderId): F[Option[Order]]

def placeOrder[F[_]: Monad](repo: OrderRepository[F])(raw: RawOrder): F[Either[OrderError, Confirmation]] =
  for
    validated <- Monad[F].pure(validateOrder(raw))
    result <- validated match
      case Left(err) => Monad[F].pure(Left(err))
      case Right(v)  => repo.findOrder(v.orderId).map(_.toRight(OrderNotFound))
  yield result

[INTERMEDIATE] Testing

**Frameworks**: ScalaCheck (PBT) | ZIO Test (integrated PBT + unit) | ScalaTest (BDD) | MUnit (lightweight). See [pbt-jvm](../nw-pbt-jvm/SKILL.md) for detailed PBT patterns.

Property Test (ScalaCheck)

import org.scalacheck.Properties
import org.scalacheck.Prop.forAll

object OrderSpec extends Properties("Order"):
  property("serialization round-trips") = forAll { (order: Order) =>
    deserialize(serialize(order)) == Right(order)
  }
  property("validated orders have positive totals") = forAll { (raw: RawOrder) =>
    validateOrder(raw) match
      case Left(_)     => true
      case Right(valid) => valid.total.value > 0
  }

[ADVANCED] Idiomatic Patterns

Enum-Based State Machines

enum OrderState:
  case Unvalidated(raw: RawOrder)
  case Validated(order: ValidatedOrder)
  case Priced(order: PricedOrder)
  case Confirmed(confirmation: Confirmation)

def transition(state: OrderState, command: OrderCommand): Either[OrderError, OrderState] =
  (state, command) match
    case (OrderState.Unvalidated(raw), OrderCommand.Validate) =>
      validateOrder(raw).map(OrderState.Validated(_))
    case (OrderState.Validated(order), OrderCommand.Price) =>
      priceOrder(order).map(OrderState.Priced(_))
    case _ => Left(InvalidTransition(state, command))

Extension Methods for Domain Operations

extension (order: PricedOrder)
  def totalWithTax(taxRate: BigDecimal): Money = Money(order.total.value * (1 + taxRate))
  def isHighValue: Boolean = order.total.value > 1000

Maturity and Adoption

  • **Ecosystem fragmentation**: ZIO vs Cats Effect creates split ecosystem. Libraries often target one or the other. Mixing is painful.
  • **Slow compilation**: Scala 3 faster than 2 but still significantly slower than Kotlin or Java. Keep modules small; consider Mill over sbt.
  • **Complexity reputation**: Scala's power (implicits, type-level programming, macros) creates wildly varying codebases. Establish team c
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.

skills10d309k
@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…

react1d252k
@facebook@facebookInstall Skill

Questions about nw-fp-scala.

Which coding agents can use nw-fp-scala?
nw-fp-scala 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.