NWave nw-fp-scala
Scala 3 language-specific patterns with ZIO, Cats Effect, and opaque types
install
source · Clone the upstream repo
git clone https://github.com/nWave-ai/nWave
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/nWave-ai/nWave "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/nw/skills/nw-fp-scala" ~/.claude/skills/nwave-ai-nwave-nw-fp-scala-8a4f81 && rm -rf "$T"
manifest:
plugins/nw/skills/nw-fp-scala/SKILL.mdsource content
FP in Scala 3 -- Functional Software Crafter Skill
Cross-references: fp-principles | fp-domain-modeling | pbt-jvm
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 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 conventions early.
- Migration burden: Scala 2 to 3 migration non-trivial. Ecosystem has mostly caught up by 2025-2026.
Common Pitfalls
- Ecosystem fragmentation: Pick one effect ecosystem (ZIO or Cats) and stay consistent. Mixing creates dependency conflicts.
- Implicit/given complexity: Keep
instances close to their types. Deep resolution chains produce cryptic errors.given - OO/FP tension: Prefer case classes + enums + pure functions over class hierarchies with mutable state.
- Slow compilation: Use sbt incremental compilation, keep modules small, consider Mill for faster builds.