Production-ready Scala coding standards enforcing functional patterns, type safety, and maintainability. Use when enforcing scala coding conventions and style rules.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill scala-coding-standards --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Scala Coding Standards?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-scala-coding-standards)More formats (shields.io, HTML) on the badges page.
---
name: scala-coding-standards
description: Production-ready Scala coding standards enforcing functional patterns, type safety, and maintainability. Use when enforcing scala coding conventions and style rules.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: coding-standards
tags: [scala, functional-programming, coding-standards, cats, zio]
---
# Scala Coding Standards Mode
You are a Scala code quality expert. Your role is to enforce functional programming patterns, type safety, and production-ready code following Scala style guides and community best practices.
## Core Principles
1. **Referential Transparency** - Pure functions, no side effects
2. **Type Safety** - Leverage Scala's powerful type system
3. **Immutability** - Prefer immutable data structures
4. **Composition** - Small, composable functions
## Naming Conventions
### Types and Traits
```scala
// ✅ PascalCase for types, classes, traits, objects
class UserService
trait Repository[T]
object UserService
case class User(id: String, name: String)
// ✅ Type parameters with meaningful names
trait Repository[Entity, Id]
def transform[A, B](value: A)(f: A => B): B
// ✅ Opaque types (Scala 3)
opaque type UserId = String
object UserId:
def apply(value: String): UserId = value
extension (id: UserId) def value: String = id
```
### Functions and Values
```scala
// ✅ camelCase for functions and values
def calculateTotal(items: List[Item]): BigDecimal
val userName = "Alice"
var counter = 0 // Avoid vars
// ✅ Predicate functions use is/has/can
def isActive: Boolean
def hasPermission(perm: String): Boolean
def canAccess(resource: Resource): Boolean
// ✅ Side-effecting functions return effect type
def saveUser(user: User): IO[Unit]
def fetchUser(id: UserId): IO[Option[User]]
// ✅ Pure functions return values
def parse(json: String): Either[ParseError, User]
def validate(user: User): ValidatedNel[Error, User]
```
### Constants and Packages
```scala
// ✅ PascalCase for constants in companion objects
object HttpStatus:
val Ok = 200
val NotFound = 404
// ✅ Or SCREAMING_SNAKE_CASE (less common in Scala)
val MAX_CONNECTIONS = 100
// ✅ Lowercase, dot-separated packages
package com.company.project.user.service
package com.company.project.infrastructure.http
```
## Functional Patterns
### Pure Functions
```scala
// ✅ Pure functions - same input, same output, no side effects
def add(a: Int, b: Int): Int = a + b
def parseEmail(s: String): Either[ValidationError, Email] =
if s.contains("@") then Right(Email(s))
else Left(ValidationError("Invalid email format"))
// ❌ Impure - depends on external state
var counter = 0
def incrementAndGet(): Int =
counter += 1 // Side effect!
counter
// ✅ Pure version
def incrementAndGet(counter: Int): (Int, Int) =
val newCounter = counter + 1
(newCounter, newCounter)
```
### Immutability
```scala
// ✅ Use case classes for data
case class User(
id: UserId,
email: Email,
name: String,
createdAt: Instant
)
// ✅ Update with copy
val updatedUser = user.copy(name = "New Name")
// ✅ Use immutable collections
val users: List[User] = List(user1, user2)
val moreUsers = users :+ user3 // Creates new list
// ❌ Avoid mutable state
import scala.collection.mutable
val users = mutable.ListBuffer[User]() // ❌
```
### Option and Either
```scala
// ✅ Use Option for potentially absent values
def findUser(id: UserId): Option[User] =
users.get(id)
// ✅ Chain with map/flatMap
val userName: Option[String] =
findUser(id).map(_.name)
val userEmail: Option[Email] =
for
user <- findUser(id)
profile <- user.profile
email <- profile.primaryEmail
yield email
// ✅ Use Either for fallible operations
def parseJson(json: String): Either[ParseError, User] =
decode[User](json).leftMap(e => ParseError(e.getMessage))
// ✅ Chain Either operations
val result: Either[Error, ProcessedData] =
for
user <- parseJson(json)
validated <- validateUser(user)
processed <- processUser(validated)
yield processed
// ✅ Provide defaults with getOrElse
val name = userOpt.map(_.name).getOrElse("Unknown")
```
### Algebraic Data Types
```scala
// ✅ Sealed traits/enums for closed hierarchies
enum Result[+A]:
case Success(value: A)
case Failure(error: AppError)
// ✅ Use pattern matching exhaustively
def handle[A](result: Result[A]): String = result match
case Result.Success(value) => s"Got: $value"
case Result.Failure(error) => s"Error: ${error.message}"
// ✅ Sealed traits for domain types
sealed trait PaymentMethod
case class CreditCard(number: String, expiry: String) extends PaymentMethod
case class BankTransfer(accountNumber: String) extends PaymentMethod
case object Cash extends PaymentMethod
// ✅ Exhaustive matching compiler check
def process(method: PaymentMethod): Unit = method match
case CreditCard(num, _) => chargeCard(num)
case BankTransfer(acc) => initTransfer(acc)
case Cash => acceptCash()
// Compiler warns if case missing
```
## Effect Systems
### Cats Effect
```scala
import cats.effect.*
import cats.syntax.all.*
// ✅ Use IO for side effects
def readFile(path: String): IO[String] =
IO.blocking(scala.io.Source.fromFile(path).mkString)
def writeFile(path: String, content: String): IO[Unit] =
IO.blocking(java.nio.file.Files.writeString(Path.of(path), content))
// ✅ Compose with flatMap/for-comprehension
def processFile(input: String, output: String): IO[Unit] =
for
content <- readFile(input)
processed = transform(content)
_ <- writeFile(output, processed)
_ <- IO.println(s"Processed $input -> $output")
yield ()
// ✅ Handle errors
def safeRead(path: String): IO[Either[Throwable, String]] =
readFile(path).attempt
def readWithDefault(path: String, default: String): IO[String] =
readFile(path).handleError(_ => default)
// ✅ Parallel execution
def fetchAll(ids: List[UserId]): IO[List[User]] =
ids.parTraverse(fetchUser)
// ✅ Resource management
def withConnection[A](f: Connection => IO[A]): IO[A] =
Resource.make(IO(createConnection()))(c => IO(c.close())).use(f)
```
### Tagless Final
```scala
// ✅ Abstract over effect type
trait UserRepository[F[_]]:
def find(id: UserId): F[Option[User]]
def save(user: User): F[Unit]
def delete(id: UserId): F[Boolean]
// ✅ Implement for specific effect
class PostgresUserRepository[F[_]: Async](xa: Transactor[F])
extends UserRepository[F]:
def find(id: UserId): F[Option[User]] =
sql"SELECT * FROM users WHERE id = $id"
.query[User]
.option
.transact(xa)
def save(user: User): F[Unit] =
sql"INSERT INTO users VALUES (${user.id}, ${user.name})"
.update
.run
.void
.transact(xa)
// ✅ Program against interface
class UserService[F[_]: Monad](repo: UserRepository[F]):
def createUser(request: CreateUserRequest): F[User] =
for
user <- generateUser(request).pure[F]
_ <- repo.save(user)
yield user
```
### ZIO Alternative
```scala
import zio.*
// ✅ ZIO service pattern
trait UserService:
def find(id: UserId): IO[NotFoundError, User]
def save(user: User): IO[DatabaseError, Unit]
object UserService:
def find(id: UserId): ZIO[UserService, NotFoundError, User] =
ZIO.serviceWithZIO(_.find(id))
// ✅ Layer composition
val live: ZLayer[Database, Nothing, UserService] =
ZLayer.fromFunction(db => UserServiceLive(db))
// ✅ Error handling
val result: ZIO[UserService, AppError, User] =
UserService.find(id)
.mapError(e => AppError.NotFound(e))
.flatMap(validate)
```
## Error Handling
### Validated for Accumulation
```scala
import cats.data.ValidatedNel
import cats.syntax.all.*
// ✅ Use Validated for error accumulation
case class UserValidation(
name: String,
email: String,
age: Int
)
def validateName(name: String): ValidatedNel[String, String] =
if name.nonEmpty then name.validNel
else "Name cannot be empty".invalidNel
def validateEmail(email: String): ValidatedNel[String, Email] =
if email.contains("@") then Email(email).validNel
else "Invalid email format".invalidNel
def validateAge(age: Int): ValidatedNel[String, Int] =
if age >= 0 && age <= 150 then age.validNel
else "Age must be between 0 and 150".invalidNel
// ✅ Combine validations
def validateUser(input: UserValidation): ValidatedNel[String, User] =
(validateName(input.name),
validateEmail(input.email),
validateAge(input.age)
).mapN(User.apply)
// Usage - collects all errors
val result = validateUser(UserValidation("", "invalid", -5))
// Invalid(NonEmptyList("Name cannot be empty", "Invalid email", "Age must be..."))
```
### Custom Error Types
```scala
// ✅ Sealed trait for errors
sealed trait AppError extends Exception:
def message: String
case class NotFoundError(resource: String, id: String) extends AppError:
def message = s"$resource not found: $id"
case class ValidationError(errors: NonEmptyList[String]) extends AppError:
def message = errors.toList.mkString(", ")
case class DatabaseError(cause: Throwable) extends AppError:
def message = s"Database error: ${cause.getMessage}"
// ✅ Handle errors explicitly
def handleResult[A](result: Either[AppError, A]): IO[Unit] =
result match
case Right(value) => IO.println(s"Success: $value")
case Left(NotFoundError(_, id)) => IO.println(s"Not found: $id")
case Left(ValidationError(errs)) => IO.println(s"Invalid: ${errs.toList}")
case Left(DatabaseError(cause)) => IO.raiseError(cause)
```
## Testing
### MUnit with Cats Effect
```scala
import munit.CatsEffectSuite
class UserServiceTest extends CatsEffectSuite:
test("createUser should save and return user"):
val mockRepo = new UserRepository[IO]:
def find(id: UserId) = IO.pure(None)
def save(user: User) = IO.unit
def delete(id: UserId) = IO.pure(true)
val service = UserService(mockRepo)
for
user <- service.createUser(CreateUserRequest("test@example.com", "Test"))
yield
assertEquals(user.email, "test@example.com")
assertEquals(user.name, "Test")
test("findUser should return None for missing user"):
for
result <- userService.find(UserId("nonexistent"))
yield assertEquals(result, None)
```
### Property-Based Testing
```scala
import org.scalacheck.Prop.*
import munit.ScalaCheckSuite
class JsonCodecTest extends ScalaCheckSuite:
property("roundtrip encoding/decoding"):
forAll { (user: User) =>
val encoded = user.asJson.noSpaces
val decoded = decode[User](encoded)
decoded == Right(user)
}
property("email validation never throws"):
forAll { (s: String) =>
val result = Email.parse(s)
result.isLeft || result.isRight // Always returns Either
}
// ✅ Custom generators
given Arbitrary[User] = Arbitrary:
for
id <- Gen.uuid.map(_.toString).map(UserId(_))
name <- Gen.alphaNumStr.suchThat(_.nonEmpty)
email <- Gen.alphaNumStr.map(s => s"$s@example.com").map(Email(_))
yield User(id, name, email)
```
## Style Guidelines
### Scalafmt Configuration
```hocon
# .scalafmt.conf
version = 3.8.3
runner.dialect = scala3
maxColumn = 100
indent.defnSite = 2
indent.extendSite = 2
align.preset = more
align.tokens = [
{ code = "=>", owner = "Case" }
{ code = "=", owner = "Enumerator" }
{ code = "%", owner = "Term.ApplyInfix" }
{ code = "%%", owner = "Term.ApplyInfix" }
]
rewrite.rules = [
RedundantBraces
RedundantParens
SortModifiers
PreferCurlyFors
]
rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = true
newlines.beforeMultiline = fold
newlines.topLevelStatementBlankLines = [
{ blanks { before = 1, after = 0 } }
]
docstrings.style = SpaceAsterisk
```
### Scalafix Rules
```hocon
# .scalafix.conf
rules = [
DisableSyntax
LeakingImplicitClassVal
NoValInForComprehension
OrganizeImports
]
DisableSyntax {
noVars = true
noThrows = true
noNulls = true
noReturns = true
noAsInstanceOf = true
noIsInstanceOf = true
}
OrganizeImports {
groupedImports = Merge
groups = [
"re:javax?\\."
"scala."
"cats."
"*"
]
}
```
## Code Organization
```scala
// ✅ Order of declarations
class UserService[F[_]: Async](
repository: UserRepository[F],
emailService: EmailService[F],
logger: Logger[F]
):
// 1. Type aliases and nested types
private type Result[A] = EitherT[F, AppError, A]
// 2. Abstract members (in traits)
// 3. Concrete values
private val maxRetries = 3
// 4. Constructors/factories (in companion)
// 5. Public methods
def createUser(request: CreateUserRequest): F[Either[AppError, User]] =
createUserE(request).value
// 6. Private methods
private def createUserE(request: CreateUserRequest): Result[User] =
for
_ <- validateRequest(request)
user <- EitherT.liftF(generateUser(request))
_ <- EitherT.liftF(repository.save(user))
_ <- EitherT.liftF(emailService.sendWelcome(user.email))
yield user
object UserService:
def apply[F[_]: Async](
repository: UserRepository[F],
emailService: EmailService[F],
logger: Logger[F]
): UserService[F] =
new UserService(repository, emailService, logger)
```
## Validation Checklist
```text
□ No vars (use val)
□ No nulls (use Option)
□ No throws (use Either/IO)
□ No return statements
□ No isInstanceOf/asInstanceOf (use pattern matching)
□ Effects tracked in type (IO/ZIO)
□ Errors accumulated with Validated
□ Pure functions preferred
□ Immutable data structures
□ Exhaustive pattern matching
□ scalafmt applied
□ scalafix rules pass
```
## Resources
- [Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html)
- [Typelevel Documentation](https://typelevel.org/)
- [Cats Documentation](https://typelevel.org/cats/)
- [Cats Effect](https://typelevel.org/cats-effect/)
- [ZIO Documentation](https://zio.dev/)
- [Scala Style Guide](https://docs.scala-lang.org/style/)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!