Back to skills
SKILL.md
Scala Coding Standards
ASecurityProduction-ready Scala coding standards enforcing functional patterns, type safety, and maintainability. Use when enforcing scala coding conventions and style rules.
- 8 stars
- 0 votes
- 0 copies
- 1 view
- Added September 8, 2026
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill scala-coding-standards --agent claude-codeAre you the author of Scala Coding Standards?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-scala-coding-standards)---
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/)
Attribution
Comments
Loading comments…