xiaohongshu
xiaohongshu
lang-scala-library-dev
Scala-specific library development patterns. Use when creating Scala libraries, designing public APIs with immutability, configuring sbt/Mill build tools, managing cross-Scala version builds, publishing to Maven Central, and writing ScalaDoc. Extends lang-scala-dev with library-specific tooling a...
Full skill instructions
Scala-specific patterns for library development. This skill extends lang-scala-dev with library tooling, API design patterns, and ecosystem practices for publishing reusable Scala libraries.
lang-scala-dev - Foundational Scala patterns (immutability, traits, pattern matching, type system)For general Scala concepts like case classes, for-comprehensions, and collections, see the base skill first.
lang-scala-devlang-scala-play-dev or framework-specific skillslang-scala-akka-devlang-scala-spark-dev| Task | sbt Command | Mill Command |
|---|---|---|
| New library project | sbt new scala/scala-seed.g8 | mill init com-lihaoyi/mill-scala-hello.g8 |
| Compile | sbt compile | mill _.compile |
| Test | sbt test | mill _.test |
| Package JAR | sbt package | mill _.jar |
| Generate docs | sbt doc | mill _.docJar |
| Publish local | sbt publishLocal | mill _.publishLocal |
| Publish signed | sbt publishSigned | mill mill.scalalib.PublishModule/publish |
| Cross build | sbt +compile | mill __.compile |
| Check binary compat | sbt mimaReportBinaryIssues | N/A (use sbt-mima plugin) |
my-library/
├── build.sbt # Build configuration
├── project/
│ ├── build.properties # sbt version
│ ├── plugins.sbt # sbt plugins
│ └── Dependencies.scala # Dependency management (optional)
├── src/
│ ├── main/
│ │ └── scala/ # Library source code
│ ├── test/
│ │ └── scala/ # Tests
│ └── it/ # Integration tests (optional)
│ └── scala/
└── docs/ # Documentation (optional)
Required fields for publishing:
// build.sbt
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := "2.13.12"
lazy val root = (project in file("."))
.settings(
name := "my-library",
// Library dependencies
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
"org.scalatest" %% "scalatest" % "3.2.17" % Test
),
// Publishing metadata
publishMavenStyle := true,
licenses := Seq("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
homepage := Some(url("https://github.com/username/my-library")),
scmInfo := Some(
ScmInfo(
url("https://github.com/username/my-library"),
"scm:[email protected]:username/my-library.git"
)
),
developers := List(
Developer(
id = "username",
name = "Your Name",
email = "[email protected]",
url = url("https://github.com/username")
)
),
// Maven Central publishing
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
)
// build.sbt
lazy val scala213 = "2.13.12"
lazy val scala3 = "3.3.1"
ThisBuild / crossScalaVersions := Seq(scala213, scala3)
ThisBuild / scalaVersion := scala213 // Default
// Version-specific dependencies
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) =>
Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4")
case Some((3, _)) =>
Seq.empty // Not needed in Scala 3
case _ =>
Seq.empty
}
}
// Version-specific source directories
Compile / unmanagedSourceDirectories ++= {
val sourceDir = (Compile / sourceDirectory).value
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) => Seq(sourceDir / s"scala-2.$n")
case Some((3, _)) => Seq(sourceDir / "scala-3")
case _ => Seq.empty
}
}
Cross-build commands:
# Compile for all versions
sbt +compile
# Test all versions
sbt +test
# Publish all versions
sbt +publishSigned
// build.sc
import mill._, scalalib._, publish._
object mylibrary extends PublishModule with ScalaModule {
def scalaVersion = "2.13.12"
def publishVersion = "0.1.0"
def pomSettings = PomSettings(
description = "My Scala library",
organization = "com.example",
url = "https://github.com/username/my-library",
licenses = Seq(License.`Apache-2.0`),
versionControl = VersionControl.github("username", "my-library"),
developers = Seq(
Developer("username", "Your Name", "https://github.com/username")
)
)
def ivyDeps = Agg(
ivy"org.typelevel::cats-core:2.10.0"
)
object test extends Tests with TestModule.ScalaTest {
def ivyDeps = Agg(
ivy"org.scalatest::scalatest:3.2.17"
)
}
}
Cross-building with Mill:
// build.sc
import mill._, scalalib._
val scala213 = "2.13.12"
val scala3 = "3.3.1"
trait MyModule extends ScalaModule with PublishModule {
def publishVersion = "0.1.0"
// ... common settings
}
object mylibrary extends Cross[MyLibraryModule](scala213, scala3)
class MyLibraryModule(val crossScalaVersion: String) extends MyModule {
def scalaVersion = crossScalaVersion
}
Prefer immutable data types:
// Good: Immutable case class
case class Config(
timeout: Duration,
retries: Int,
baseUrl: String
)
// Modification returns new instance
val updated = config.copy(retries = 5)
// Avoid: Mutable class
class Config {
var timeout: Duration = _
var retries: Int = _
// ...
}
Use sealed traits for ADTs:
sealed trait Result[+A]
case class Success[A](value: A) extends Result[A]
case class Failure(error: String) extends Result[Nothing]
case object Pending extends Result[Nothing]
// Exhaustive pattern matching
def handle[A](result: Result[A]): String = result match {
case Success(value) => s"Got: $value"
case Failure(error) => s"Error: $error"
case Pending => "Waiting..."
}
Builder pattern for complex configuration:
case class HttpClient private (
timeout: Duration,
retries: Int,
followRedirects: Boolean,
userAgent: String
)
object HttpClient {
def builder(): Builder = Builder()
case class Builder private[HttpClient] (
timeout: Duration = Duration(30, TimeUnit.SECONDS),
retries: Int = 3,
followRedirects: Boolean = true,
userAgent: String = "MyLibrary/1.0"
) {
def withTimeout(timeout: Duration): Builder = copy(timeout = timeout)
def withRetries(retries: Int): Builder = copy(retries = retries)
def withFollowRedirects(follow: Boolean): Builder = copy(followRedirects = follow)
def withUserAgent(ua: String): Builder = copy(userAgent = ua)
def build(): HttpClient = HttpClient(timeout, retries, followRedirects, userAgent)
}
}
// Usage
val client = HttpClient.builder()
.withTimeout(Duration(60, TimeUnit.SECONDS))
.withRetries(5)
.build()
Type classes for extensibility:
trait Encoder[A] {
def encode(value: A): String
}
object Encoder {
def apply[A](implicit enc: Encoder[A]): Encoder[A] = enc
def instance[A](f: A => String): Encoder[A] = new Encoder[A] {
def encode(value: A): String = f(value)
}
// Instances
implicit val intEncoder: Encoder[Int] = instance(_.toString)
implicit val stringEncoder: Encoder[String] = instance(identity)
// Derived instance
implicit def optionEncoder[A](implicit enc: Encoder[A]): Encoder[Option[A]] = {
instance {
case Some(value) => enc.encode(value)
case None => "null"
}
}
}
// Usage
def toJson[A: Encoder](value: A): String = {
Encoder[A].encode(value)
}
Use @deprecated for gradual migration:
object MyLibrary {
@deprecated("Use newMethod instead", "1.2.0")
def oldMethod(): Unit = newMethod()
def newMethod(): Unit = {
// New implementation
}
}
Package private for internal APIs:
// Visible only within package
private[mylibrary] class InternalHelper {
// ...
}
// Visible to this module only
private[this] val internalState = mutable.Map.empty[String, Int]
Use opaque types (Scala 3) for type safety:
// Scala 3
opaque type UserId = Long
object UserId {
def apply(value: Long): UserId = value
extension (id: UserId) {
def toLong: Long = id
}
}
// Cannot accidentally pass Long where UserId expected
val userId: UserId = UserId(123L)
val rawId: Long = userId.toLong
Setup in build.sbt:
// project/plugins.sbt
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.3")
// build.sbt
import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._
lazy val root = (project in file("."))
.enablePlugins(MimaPlugin)
.settings(
mimaPreviousArtifacts := Set(organization.value %% name.value % "0.1.0"),
// Binary compatibility checks
mimaReportBinaryIssues := {
mimaReportBinaryIssues.value
// Fail build on incompatibilities
},
// Allow specific breakages
mimaBinaryIssueFilters ++= Seq(
// Example: Allow removal of private class
ProblemFilters.exclude[MissingClassProblem]("com.example.internal.PrivateClass")
)
)
Check compatibility:
# Report binary compatibility issues
sbt mimaReportBinaryIssues
# Allow breaking changes for major version
sbt "set mimaPreviousArtifacts := Set()" publishLocal
| Change | Binary Compatible? | Source Compatible? |
|---|---|---|
| Add method to class | ✓ Yes | ✓ Yes |
| Add method to trait | ✗ No (before Scala 2.12) | ✓ Yes |
| Remove public method | ✗ No | ✗ No |
| Add parameter with default | ✓ Yes | ✓ Yes |
| Add parameter without default | ✗ No | ✗ No |
| Change return type | ✗ No | ✗ No |
| Make final class | ✗ No | Depends |
| Seal trait | ✗ No | ✗ No |
| Add case to sealed trait | ✗ No | ✗ No |
| Widen visibility | ✓ Yes | ✓ Yes |
| Narrow visibility | ✗ No | ✗ No |
com.github.username or io.github.username)Generate GPG key:
# Generate key
gpg --gen-key
# List keys
gpg --list-keys
# Export public key to keyserver
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
Configure sbt-pgp:
// project/plugins.sbt
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
// build.sbt
useGpg := true // Use GPG command-line tool
Create ~/.sbt/1.0/sonatype.sbt:
credentials += Credentials(
"Sonatype Nexus Repository Manager",
"oss.sonatype.org",
"your-sonatype-username",
"your-sonatype-password"
)
Or use environment variables:
credentials += Credentials(
"Sonatype Nexus Repository Manager",
"oss.sonatype.org",
sys.env.getOrElse("SONATYPE_USERNAME", ""),
sys.env.getOrElse("SONATYPE_PASSWORD", "")
)
Using sbt-sonatype plugin:
// project/plugins.sbt
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.10.0")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
// build.sbt
import xerial.sbt.Sonatype._
sonatypeProjectHosting := Some(GitHubHosting("username", "project", "[email protected]"))
sonatypeCredentialHost := "s01.oss.sonatype.org" // For new projects
publishTo := sonatypePublishToBundle.value
Publish commands:
# 1. Update version in build.sbt (remove -SNAPSHOT for release)
# 2. Create git tag
git tag -a v0.1.0 -m "Release 0.1.0"
# 3. Publish and sign
sbt +publishSigned
# 4. Release to Maven Central (bundle workflow)
sbt sonatypeBundleRelease
# Or manual workflow:
# sbt sonatypeClose # Close staging repo
# sbt sonatypeRelease # Release to Maven Central
# 5. Push tag
git push origin v0.1.0
-SNAPSHOT)sbt +testsbt mimaReportBinaryIssuessbt +packagesbt docgit tag -a vX.Y.Z -m "Release X.Y.Z"sbt +publishSignedsbt sonatypeBundleReleasegit push origin vX.Y.ZX.Y.Z-SNAPSHOT/**
* Parses a JSON string into a case class.
*
* This method uses the implicit [[Decoder]] to convert the JSON string
* into the target type `A`.
*
* @param json the JSON string to parse
* @tparam A the target type (must have an implicit Decoder)
* @return a [[scala.util.Try]] containing the parsed value or error
* @throws IllegalArgumentException if the JSON is malformed
* @see [[Decoder]] for information on creating custom decoders
* @example
* {{{
* case class Person(name: String, age: Int)
* implicit val decoder: Decoder[Person] = ...
*
* val result = parseJson[Person]("""{"name":"Alice","age":30}""")
* // result: Success(Person("Alice", 30))
* }}}
*/
def parseJson[A: Decoder](json: String): Try[A] = ???
| Tag | Purpose | Example |
|---|---|---|
@param | Parameter description | @param name the user's name |
@tparam | Type parameter | @tparam A the element type |
@return | Return value | @return the parsed result |
@throws | Exception thrown | @throws IOException if file not found |
@see | Reference | @see [[OtherClass]] |
@example | Code example | @example {{{ ... }}} |
@note | Important note | @note This method is thread-safe |
@since | Version added | @since 1.2.0 |
@deprecated | Deprecation notice | @deprecated("Use newMethod", "1.3.0") |
sbt:
# Generate API docs
sbt doc
# Open in browser
open target/scala-2.13/api/index.html
# Generate for all Scala versions
sbt +doc
Mill:
# Generate docs
mill mylibrary.docJar
# Extract and view
unzip out/mylibrary/docJar.dest/out.jar -d docs
Create package.scala:
/**
* Core library for JSON parsing and serialization.
*
* == Overview ==
* This package provides type-safe JSON encoding and decoding using type classes.
*
* == Quick Start ==
* {{{
* import com.example.json._
*
* case class User(name: String, age: Int)
* implicit val decoder = Decoder.derive[User]
*
* val json = """{"name":"Alice","age":30}"""
* val user = parseJson[User](json)
* }}}
*
* @see [[Encoder]] for creating custom encoders
* @see [[Decoder]] for creating custom decoders
*/
package object json {
// Package-level type aliases
type Result[A] = Either[JsonError, A]
}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ConfigSpec extends AnyFlatSpec with Matchers {
"Config" should "parse valid configuration" in {
val config = Config.parse("timeout=30,retries=3")
config shouldBe defined
config.get.timeout shouldBe Duration(30, TimeUnit.SECONDS)
config.get.retries shouldBe 3
}
it should "reject invalid timeout values" in {
val config = Config.parse("timeout=-1")
config shouldBe empty
}
it should "use default values when not specified" in {
val config = Config.parse("")
config shouldBe defined
config.get.retries shouldBe 3 // Default
}
}
import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
object JsonPropertiesSpec extends Properties("Json") {
property("roundtrip") = forAll { (user: User) =>
val json = toJson(user)
val parsed = fromJson[User](json)
parsed == Right(user)
}
property("never crashes") = forAll { (s: String) =>
try {
fromJson[User](s)
true
} catch {
case _: Exception => false
}
}
}
Create src/it/scala/ directory:
// src/it/scala/HttpClientIntegrationSpec.scala
import org.scalatest.flatspec.AnyFlatSpec
class HttpClientIntegrationSpec extends AnyFlatSpec {
"HttpClient" should "make real HTTP requests" in {
val client = HttpClient.builder().build()
val response = client.get("https://httpbin.org/get")
assert(response.status == 200)
}
}
Configure in build.sbt:
lazy val IntegrationTest = config("it") extend Test
lazy val root = (project in file("."))
.configs(IntegrationTest)
.settings(
Defaults.itSettings,
IntegrationTest / scalaSource := baseDirectory.value / "src/it/scala"
)
// Run integration tests
// sbt it:test
// build.sbt
lazy val commonSettings = Seq(
organization := "com.example",
scalaVersion := "2.13.12",
version := "0.1.0"
)
lazy val core = (project in file("core"))
.settings(
commonSettings,
name := "my-library-core",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0"
)
)
lazy val http = (project in file("http"))
.dependsOn(core)
.settings(
commonSettings,
name := "my-library-http",
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % "0.23.23"
)
)
lazy val json = (project in file("json"))
.dependsOn(core)
.settings(
commonSettings,
name := "my-library-json",
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % "0.14.6"
)
)
lazy val root = (project in file("."))
.aggregate(core, http, json)
.settings(
commonSettings,
name := "my-library",
publish / skip := true // Don't publish root
)
Module commands:
# Build specific module
sbt core/compile
# Test all modules
sbt test
# Publish specific module
sbt core/publishSigned
# Publish all modules
sbt +publishSigned
// build.sc
import mill._, scalalib._
trait CommonModule extends ScalaModule {
def scalaVersion = "2.13.12"
def publishVersion = "0.1.0"
}
object core extends CommonModule {
def ivyDeps = Agg(
ivy"org.typelevel::cats-core:2.10.0"
)
}
object http extends CommonModule {
def moduleDeps = Seq(core)
def ivyDeps = Agg(
ivy"org.http4s::http4s-dsl:0.23.23"
)
}
object json extends CommonModule {
def moduleDeps = Seq(core)
def ivyDeps = Agg(
ivy"io.circe::circe-core:0.14.6"
)
}
// v1.0.0
trait Parser {
def parse(input: String): Result
}
// v1.1.0 - WRONG! Breaks binary compatibility
trait Parser {
def parse(input: String): Result
def parseWithOptions(input: String, options: Options): Result
}
// v1.1.0 - Correct: Provide default implementation
trait Parser {
def parse(input: String): Result
def parseWithOptions(input: String, options: Options): Result = {
// Default implementation
parse(input)
}
}
// Bad: Exposes mutable collection
class Registry {
private val items = mutable.ListBuffer.empty[Item]
def getItems: mutable.ListBuffer[Item] = items // Dangerous!
}
// Good: Return immutable view
class Registry {
private val items = mutable.ListBuffer.empty[Item]
def getItems: List[Item] = items.toList // Safe copy
}
// Bad: Too many implicit conversions
implicit def intToString(x: Int): String = x.toString
implicit def stringToInt(s: String): Int = s.toInt
// Good: Explicit type classes
trait Show[A] {
def show(a: A): String
}
implicit val intShow: Show[Int] = (a: Int) => a.toString
lang-scala-dev - Foundational Scala patternsxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience