@coralai/sps-cli 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -3
- package/dist/commands/projectInit.d.ts.map +1 -1
- package/dist/commands/projectInit.js +40 -53
- package/dist/commands/projectInit.js.map +1 -1
- package/dist/commands/skillCommand.d.ts +2 -0
- package/dist/commands/skillCommand.d.ts.map +1 -0
- package/dist/commands/skillCommand.js +235 -0
- package/dist/commands/skillCommand.js.map +1 -0
- package/dist/core/skillStore.d.ts +46 -0
- package/dist/core/skillStore.d.ts.map +1 -0
- package/dist/core/skillStore.js +197 -0
- package/dist/core/skillStore.js.map +1 -0
- package/dist/core/skillStore.test.d.ts +2 -0
- package/dist/core/skillStore.test.d.ts.map +1 -0
- package/dist/core/skillStore.test.js +190 -0
- package/dist/core/skillStore.test.js.map +1 -0
- package/dist/main.js +19 -17
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/skills/architecture-decision-records/SKILL.md +207 -0
- package/skills/backend/SKILL.md +62 -0
- package/skills/backend/references/api-design.md +168 -0
- package/skills/backend/references/caching.md +181 -0
- package/skills/backend/references/data-access.md +173 -0
- package/skills/backend/references/layering.md +181 -0
- package/skills/backend/references/observability.md +190 -0
- package/skills/backend/references/resilience.md +201 -0
- package/skills/backend/references/security.md +186 -0
- package/skills/backend-architect/SKILL.md +119 -0
- package/skills/code-reviewer/SKILL.md +143 -0
- package/skills/coding-standards/SKILL.md +60 -0
- package/skills/coding-standards/references/clean-code.md +258 -0
- package/skills/coding-standards/references/code-review.md +192 -0
- package/skills/coding-standards/references/commits-and-prs.md +226 -0
- package/skills/coding-standards/references/error-strategy.md +193 -0
- package/skills/coding-standards/references/naming.md +185 -0
- package/skills/coding-standards/references/tdd.md +171 -0
- package/skills/database/SKILL.md +53 -0
- package/skills/database/references/indexing.md +190 -0
- package/skills/database/references/migrations.md +199 -0
- package/skills/database/references/nosql.md +185 -0
- package/skills/database/references/queries.md +295 -0
- package/skills/database/references/scaling.md +203 -0
- package/skills/database/references/schema.md +191 -0
- package/skills/database-optimizer/SKILL.md +168 -0
- package/skills/debugging-workflow/SKILL.md +244 -0
- package/skills/devops/SKILL.md +55 -0
- package/skills/devops/references/ci-cd.md +204 -0
- package/skills/devops/references/containers.md +272 -0
- package/skills/devops/references/deploy.md +201 -0
- package/skills/devops/references/iac.md +252 -0
- package/skills/devops/references/observability.md +228 -0
- package/skills/devops/references/secrets.md +178 -0
- package/skills/devops-automator/SKILL.md +164 -0
- package/skills/frontend/SKILL.md +52 -0
- package/skills/frontend/references/accessibility.md +222 -0
- package/skills/frontend/references/components.md +206 -0
- package/skills/frontend/references/performance.md +219 -0
- package/skills/frontend/references/routing.md +209 -0
- package/skills/frontend/references/state.md +190 -0
- package/skills/frontend/references/testing.md +216 -0
- package/skills/frontend-developer/SKILL.md +115 -0
- package/skills/git-workflow/SKILL.md +355 -0
- package/skills/golang/SKILL.md +49 -0
- package/skills/golang/references/concurrency.md +284 -0
- package/skills/golang/references/errors.md +241 -0
- package/skills/golang/references/idioms.md +285 -0
- package/skills/golang/references/testing.md +238 -0
- package/skills/java/SKILL.md +50 -0
- package/skills/java/references/concurrency.md +194 -0
- package/skills/java/references/idioms.md +283 -0
- package/skills/java/references/testing.md +228 -0
- package/skills/kotlin/SKILL.md +47 -0
- package/skills/kotlin/references/coroutines.md +240 -0
- package/skills/kotlin/references/idioms.md +268 -0
- package/skills/kotlin/references/testing.md +219 -0
- package/skills/mobile/SKILL.md +50 -0
- package/skills/mobile/references/architecture.md +204 -0
- package/skills/mobile/references/navigation.md +158 -0
- package/skills/mobile/references/performance.md +152 -0
- package/skills/mobile/references/platform.md +166 -0
- package/skills/mobile/references/state-and-data.md +174 -0
- package/skills/python/SKILL.md +51 -0
- package/skills/python/THIRD_PARTY.md +14 -0
- package/skills/python/references/async.md +218 -0
- package/skills/python/references/error-handling.md +254 -0
- package/skills/python/references/idioms.md +279 -0
- package/skills/python/references/packaging.md +233 -0
- package/skills/python/references/testing.md +269 -0
- package/skills/python/references/typing.md +292 -0
- package/skills/qa-tester/SKILL.md +186 -0
- package/skills/rust/SKILL.md +50 -0
- package/skills/rust/references/async.md +224 -0
- package/skills/rust/references/errors.md +240 -0
- package/skills/rust/references/ownership.md +263 -0
- package/skills/rust/references/testing.md +274 -0
- package/skills/rust/references/traits.md +250 -0
- package/skills/security-engineer/SKILL.md +157 -0
- package/skills/swift/SKILL.md +48 -0
- package/skills/swift/references/concurrency.md +280 -0
- package/skills/swift/references/idioms.md +334 -0
- package/skills/swift/references/testing.md +229 -0
- package/skills/typescript/SKILL.md +51 -0
- package/skills/typescript/references/async.md +241 -0
- package/skills/typescript/references/errors.md +208 -0
- package/skills/typescript/references/idioms.md +246 -0
- package/skills/typescript/references/testing.md +225 -0
- package/skills/typescript/references/tooling.md +208 -0
- package/skills/typescript/references/types.md +259 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kotlin
|
|
3
|
+
description: Kotlin language skill — idioms, null safety, coroutines, testing. Covers JVM backend and Android. Pair with `backend` / `mobile` end skills and `coding-standards` for cross-language principles.
|
|
4
|
+
origin: original
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kotlin
|
|
8
|
+
|
|
9
|
+
Null safety, coroutines, expressive stdlib. Used for JVM backend (Ktor, Spring) and Android. **Language-focused**.
|
|
10
|
+
|
|
11
|
+
## When to load
|
|
12
|
+
|
|
13
|
+
- Project primary language is Kotlin (JVM / Android / Multiplatform)
|
|
14
|
+
- Reviewing Kotlin code
|
|
15
|
+
- Coroutines / Flow / async design
|
|
16
|
+
- Interop with Java (calling Java APIs, exposing Kotlin to Java)
|
|
17
|
+
|
|
18
|
+
## Core principles
|
|
19
|
+
|
|
20
|
+
1. **Nullability in the type system.** `String?` and `String` are different types; a `NullPointerException` in pure Kotlin code is almost always a mistake.
|
|
21
|
+
2. **`val` by default, `var` only when mutation is required.**
|
|
22
|
+
3. **Data classes for value objects.** `copy`, `equals`, `hashCode`, `toString` for free.
|
|
23
|
+
4. **Prefer expressions over statements.** `if`, `when`, `try` all return values.
|
|
24
|
+
5. **Sealed hierarchies over type codes.** Use `sealed class` / `sealed interface` + `when` with exhaustiveness.
|
|
25
|
+
6. **Coroutines for async.** Never `Thread.sleep` or `.get()` on a `Future` in a coroutine.
|
|
26
|
+
7. **No Java collections when Kotlin collections exist.** `List<T>` (Kotlin) is read-only; `MutableList<T>` is mutable.
|
|
27
|
+
8. **`let` / `run` / `apply` / `also` / `with` — know the difference, don't chain all five.**
|
|
28
|
+
|
|
29
|
+
## How to use references
|
|
30
|
+
|
|
31
|
+
| Reference | When to load |
|
|
32
|
+
|---|---|
|
|
33
|
+
| [`references/idioms.md`](references/idioms.md) | Data classes, sealed classes, scope functions, extension fns, null safety |
|
|
34
|
+
| [`references/coroutines.md`](references/coroutines.md) | `suspend`, `CoroutineScope`, structured concurrency, `Flow`, dispatchers |
|
|
35
|
+
| [`references/testing.md`](references/testing.md) | JUnit 5, Kotest, MockK, turbine for Flow |
|
|
36
|
+
|
|
37
|
+
## Forbidden patterns (auto-reject)
|
|
38
|
+
|
|
39
|
+
- `!!` (force-unwrap) without a comment justifying it's definitely non-null
|
|
40
|
+
- `lateinit var` for anything that isn't initialized by a framework (DI, test setup)
|
|
41
|
+
- `runBlocking` in library or backend request-path code
|
|
42
|
+
- `GlobalScope` — always use a structured `CoroutineScope`
|
|
43
|
+
- `Thread.sleep` inside a `suspend` function
|
|
44
|
+
- `println` for logging in production code
|
|
45
|
+
- Returning Kotlin `List<T>` from an API that Java callers will treat as mutable (or vice versa)
|
|
46
|
+
- `Object` singletons holding mutable state
|
|
47
|
+
- `runCatching { }.getOrNull()` swallowing errors silently
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Kotlin — Coroutines
|
|
2
|
+
|
|
3
|
+
`suspend`, scopes, dispatchers, `Flow`, cancellation. Structured concurrency is the model.
|
|
4
|
+
|
|
5
|
+
## `suspend` functions
|
|
6
|
+
|
|
7
|
+
A `suspend fun` is a function that can pause (free its thread) and resume later.
|
|
8
|
+
|
|
9
|
+
```kotlin
|
|
10
|
+
suspend fun fetchUser(id: String): User {
|
|
11
|
+
val raw = httpClient.get("/users/$id")
|
|
12
|
+
return User.fromJson(raw)
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Call from another `suspend` function or from a `CoroutineScope.launch { ... }`. You can't call them from a regular function without `runBlocking` (for main / tests only).
|
|
17
|
+
|
|
18
|
+
## Structured concurrency
|
|
19
|
+
|
|
20
|
+
Every coroutine runs in a `CoroutineScope`. When the scope is cancelled, all its children are cancelled. No orphan work.
|
|
21
|
+
|
|
22
|
+
```kotlin
|
|
23
|
+
coroutineScope {
|
|
24
|
+
val user = async { fetchUser(id) }
|
|
25
|
+
val prefs = async { fetchPrefs(id) }
|
|
26
|
+
render(user.await(), prefs.await())
|
|
27
|
+
// if either throws, the other is cancelled and the exception bubbles
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Rule: **no `GlobalScope.launch`, no bare `launch` at top level.** Attach every coroutine to a scope with a known lifecycle.
|
|
32
|
+
|
|
33
|
+
## Dispatchers
|
|
34
|
+
|
|
35
|
+
| Dispatcher | Use |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `Dispatchers.Default` | CPU-bound work (limited to # of cores) |
|
|
38
|
+
| `Dispatchers.IO` | Blocking I/O (file, network, blocking JDBC) |
|
|
39
|
+
| `Dispatchers.Main` | UI thread (Android, JavaFX) |
|
|
40
|
+
| `Dispatchers.Unconfined` | Rarely needed; tests and hacks |
|
|
41
|
+
|
|
42
|
+
```kotlin
|
|
43
|
+
suspend fun readFile(path: String): String =
|
|
44
|
+
withContext(Dispatchers.IO) { File(path).readText() }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Only switch dispatchers at boundaries. Don't spray `withContext(IO)` inside tight logic.
|
|
48
|
+
|
|
49
|
+
## `coroutineScope` vs. `supervisorScope`
|
|
50
|
+
|
|
51
|
+
```kotlin
|
|
52
|
+
// coroutineScope: one failure cancels siblings (default)
|
|
53
|
+
coroutineScope {
|
|
54
|
+
launch { critical1() }
|
|
55
|
+
launch { critical2() }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// supervisorScope: failures isolated
|
|
59
|
+
supervisorScope {
|
|
60
|
+
launch { optional1() } // if this fails, optional2 still runs
|
|
61
|
+
launch { optional2() }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Default to `coroutineScope`. Use `supervisorScope` for independent tasks where one failure shouldn't kill the rest (e.g., background refreshes).
|
|
66
|
+
|
|
67
|
+
## Cancellation
|
|
68
|
+
|
|
69
|
+
Cancellation is cooperative. A coroutine is cancelled when:
|
|
70
|
+
- Its scope is cancelled.
|
|
71
|
+
- A structured sibling throws (in `coroutineScope`).
|
|
72
|
+
- `cancel()` is called on its `Job`.
|
|
73
|
+
|
|
74
|
+
`suspend` functions check cancellation at suspension points. CPU-heavy loops must opt in:
|
|
75
|
+
|
|
76
|
+
```kotlin
|
|
77
|
+
repeat(1_000_000) {
|
|
78
|
+
doWork()
|
|
79
|
+
yield() // or ensureActive()
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Handle `CancellationException`:
|
|
84
|
+
```kotlin
|
|
85
|
+
try {
|
|
86
|
+
doWork()
|
|
87
|
+
} catch (e: CancellationException) {
|
|
88
|
+
// clean up, then RETHROW — don't swallow
|
|
89
|
+
throw e
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
log.error("failed", e)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Swallowing `CancellationException` breaks the scope contract.** Always re-throw.
|
|
96
|
+
|
|
97
|
+
## `async` / `await`
|
|
98
|
+
|
|
99
|
+
For parallel computations that return values.
|
|
100
|
+
|
|
101
|
+
```kotlin
|
|
102
|
+
val (u, o) = coroutineScope {
|
|
103
|
+
val userD = async { getUser(id) }
|
|
104
|
+
val ordsD = async { getOrders(id) }
|
|
105
|
+
userD.await() to ordsD.await()
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Don't use `async { ... }.await()` back-to-back when you could just call the `suspend` function. That's sequential work dressed up as parallel.
|
|
110
|
+
|
|
111
|
+
## Timeouts
|
|
112
|
+
|
|
113
|
+
```kotlin
|
|
114
|
+
withTimeout(5.seconds) {
|
|
115
|
+
slowOp()
|
|
116
|
+
} // throws TimeoutCancellationException on timeout
|
|
117
|
+
|
|
118
|
+
withTimeoutOrNull(5.seconds) {
|
|
119
|
+
slowOp()
|
|
120
|
+
} // returns null on timeout
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## `Flow` — asynchronous streams
|
|
124
|
+
|
|
125
|
+
`Flow` is a cold async sequence. It doesn't run until collected.
|
|
126
|
+
|
|
127
|
+
```kotlin
|
|
128
|
+
fun tick(interval: Duration): Flow<Int> = flow {
|
|
129
|
+
var i = 0
|
|
130
|
+
while (currentCoroutineContext().isActive) {
|
|
131
|
+
emit(i++)
|
|
132
|
+
delay(interval)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
scope.launch {
|
|
137
|
+
tick(1.seconds)
|
|
138
|
+
.map { it * 2 }
|
|
139
|
+
.filter { it > 10 }
|
|
140
|
+
.take(5)
|
|
141
|
+
.collect { println(it) }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Operators (`map`, `filter`, etc.) return new Flows; `collect` is the terminal.
|
|
146
|
+
|
|
147
|
+
### Cold vs. hot
|
|
148
|
+
|
|
149
|
+
- **Cold** (`flow { }`, `flowOf`): starts fresh per collector.
|
|
150
|
+
- **Hot** (`SharedFlow`, `StateFlow`): shared; values emitted regardless of collectors.
|
|
151
|
+
|
|
152
|
+
Use `StateFlow` for "current value of X" (UI state). Use `SharedFlow` for events (broadcast). Use plain `Flow` for request/response.
|
|
153
|
+
|
|
154
|
+
### Backpressure
|
|
155
|
+
|
|
156
|
+
```kotlin
|
|
157
|
+
flow.buffer(100) // buffered producer
|
|
158
|
+
flow.conflate() // keep only latest
|
|
159
|
+
flow.collectLatest { ... } // cancel prior collector when new value arrives
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Channels
|
|
163
|
+
|
|
164
|
+
One-shot or producer/consumer.
|
|
165
|
+
|
|
166
|
+
```kotlin
|
|
167
|
+
val channel = Channel<Job>(capacity = 100)
|
|
168
|
+
launch {
|
|
169
|
+
for (job in channel) { process(job) }
|
|
170
|
+
}
|
|
171
|
+
channel.send(job)
|
|
172
|
+
channel.close()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For most use cases, `Flow` is enough. Use `Channel` when you need direct send/receive semantics.
|
|
176
|
+
|
|
177
|
+
## Exception handling
|
|
178
|
+
|
|
179
|
+
Exceptions propagate up the structured-concurrency tree. The parent coroutine sees them at `.await()` or when children complete.
|
|
180
|
+
|
|
181
|
+
```kotlin
|
|
182
|
+
try {
|
|
183
|
+
coroutineScope {
|
|
184
|
+
launch { throwSomething() }
|
|
185
|
+
}
|
|
186
|
+
} catch (e: SomeError) {
|
|
187
|
+
// caught here
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Install a `CoroutineExceptionHandler` on a `CoroutineScope` for top-level handlers (Android ViewModel, server main). Don't scatter try/catch inside coroutines.
|
|
192
|
+
|
|
193
|
+
## `runBlocking` — only for `main` and tests
|
|
194
|
+
|
|
195
|
+
Bridges blocking and suspending worlds. Inside a request handler or a library, `runBlocking` deadlocks threads. Don't.
|
|
196
|
+
|
|
197
|
+
```kotlin
|
|
198
|
+
fun main() = runBlocking {
|
|
199
|
+
myApp()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@Test fun test() = runBlocking {
|
|
203
|
+
assertEquals(5, add(2, 3))
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
For tests, prefer `runTest` (from `kotlinx-coroutines-test`) — it virtualizes time.
|
|
208
|
+
|
|
209
|
+
## `runTest` — deterministic async tests
|
|
210
|
+
|
|
211
|
+
```kotlin
|
|
212
|
+
@Test
|
|
213
|
+
fun fetches() = runTest {
|
|
214
|
+
val result = fetchUser("u1") // no real delay; virtual scheduler
|
|
215
|
+
assertEquals("u1", result.id)
|
|
216
|
+
advanceTimeBy(5.seconds) // control time
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Android-specific coroutine scopes
|
|
221
|
+
|
|
222
|
+
- `viewModelScope` — cancels on `ViewModel.onCleared()`
|
|
223
|
+
- `lifecycleScope` — cancels on lifecycle destroy
|
|
224
|
+
- `repeatOnLifecycle(STARTED) { flow.collect { ... } }` — cancels on stop, resumes on start
|
|
225
|
+
|
|
226
|
+
Never use `GlobalScope` or raw `Job()` from within a component with a defined lifecycle.
|
|
227
|
+
|
|
228
|
+
## Anti-patterns
|
|
229
|
+
|
|
230
|
+
| Anti-pattern | Fix |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `GlobalScope.launch` | Attach to a scope with a known lifecycle |
|
|
233
|
+
| `runBlocking` in request path / library code | Make the function `suspend` |
|
|
234
|
+
| Swallowing `CancellationException` | Rethrow; scope relies on it |
|
|
235
|
+
| `withContext(IO) { ... }` wrapping every line | Switch at I/O boundary only |
|
|
236
|
+
| `async { x() }.await()` with no other work in parallel | Just call `x()` as a suspend fun |
|
|
237
|
+
| `Thread.sleep` in `suspend fun` | Use `delay` |
|
|
238
|
+
| `while (true) { doWork() }` with no yield | Add `yield()` / `ensureActive()` for cancellation |
|
|
239
|
+
| `StateFlow` for events (drops duplicates) | Use `SharedFlow` for events |
|
|
240
|
+
| Long-running Flow in `collect { ... }` on UI thread that blocks | `flowOn(Dispatchers.IO)` upstream |
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Kotlin — Idioms
|
|
2
|
+
|
|
3
|
+
Null safety, data classes, sealed classes, scope functions, extension functions.
|
|
4
|
+
|
|
5
|
+
## `val` / `var` / `const val`
|
|
6
|
+
|
|
7
|
+
```kotlin
|
|
8
|
+
val name = "A" // immutable reference
|
|
9
|
+
var count = 0 // mutable
|
|
10
|
+
const val MAX = 10 // compile-time constant (top-level or in `object`)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`val` by default. Reach for `var` only when you reassign.
|
|
14
|
+
|
|
15
|
+
## Nullable types
|
|
16
|
+
|
|
17
|
+
```kotlin
|
|
18
|
+
var name: String? = null
|
|
19
|
+
name.length // ❌ compile error
|
|
20
|
+
name?.length // Int? — null if name is null
|
|
21
|
+
name!!.length // throws NPE if null — avoid
|
|
22
|
+
name ?: "unknown" // Elvis: use default if null
|
|
23
|
+
|
|
24
|
+
if (name != null) {
|
|
25
|
+
name.length // smart-cast to String
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`!!` is a code smell. Every `!!` should have a comment saying why the value is guaranteed non-null there (and, ideally, a test that checks it).
|
|
30
|
+
|
|
31
|
+
## Type inference
|
|
32
|
+
|
|
33
|
+
```kotlin
|
|
34
|
+
val n = 10 // Int
|
|
35
|
+
val s = "hi" // String
|
|
36
|
+
val users: List<User> = fetch() // annotate when intent is ambiguous
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Annotate public API return types for clarity; let locals infer.
|
|
40
|
+
|
|
41
|
+
## Data classes
|
|
42
|
+
|
|
43
|
+
```kotlin
|
|
44
|
+
data class User(val id: String, val email: String, val active: Boolean)
|
|
45
|
+
|
|
46
|
+
val u = User("u1", "a@x.com", true)
|
|
47
|
+
val updated = u.copy(email = "b@x.com")
|
|
48
|
+
u == User("u1", "a@x.com", true) // true — structural equality
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Data classes give you `equals`, `hashCode`, `toString`, `copy`, destructuring for free.
|
|
52
|
+
|
|
53
|
+
Don't use data classes for types with behaviour or invariants. If you need a factory or a validation step, make it a regular class with a private constructor + `companion object` factory.
|
|
54
|
+
|
|
55
|
+
## Sealed classes — the Kotlin discriminated union
|
|
56
|
+
|
|
57
|
+
```kotlin
|
|
58
|
+
sealed class Result<out T> {
|
|
59
|
+
data class Ok<T>(val value: T) : Result<T>()
|
|
60
|
+
data class Err(val error: String) : Result<Nothing>()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fun handle(r: Result<User>) = when (r) {
|
|
64
|
+
is Result.Ok -> use(r.value)
|
|
65
|
+
is Result.Err -> log(r.error)
|
|
66
|
+
// `when` is exhaustive — no else needed
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`sealed` limits subclasses to the same file (Kotlin < 1.5) or same module (1.5+).
|
|
71
|
+
|
|
72
|
+
## `when` — the superior switch
|
|
73
|
+
|
|
74
|
+
```kotlin
|
|
75
|
+
val label = when (status) {
|
|
76
|
+
Status.Active -> "active"
|
|
77
|
+
Status.Pending -> "pending"
|
|
78
|
+
Status.Banned -> "banned"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Smart patterns
|
|
82
|
+
val x = when {
|
|
83
|
+
n < 0 -> "neg"
|
|
84
|
+
n == 0 -> "zero"
|
|
85
|
+
n in 1..10 -> "small"
|
|
86
|
+
else -> "big"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// With is + smart-cast
|
|
90
|
+
when (v) {
|
|
91
|
+
is String -> v.length // v: String
|
|
92
|
+
is Int -> v + 1 // v: Int
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Exhaustive `when` over `sealed` / `enum` → no `else` required; the compiler forces you to update every branch when you add a case.
|
|
97
|
+
|
|
98
|
+
## Scope functions — `let` / `run` / `apply` / `also` / `with`
|
|
99
|
+
|
|
100
|
+
```kotlin
|
|
101
|
+
user?.let { send(it) } // null-safe "do something with it"
|
|
102
|
+
val formatted = user.run { "$name <$email>" } // use members inside
|
|
103
|
+
val req = Request().apply { url = x; timeout = 5 } // configure, return self
|
|
104
|
+
list.also { log("about to sort: $it") }.sort() // side effect, return self
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| | Receiver | Returns |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `let` | `it` | lambda result |
|
|
110
|
+
| `run` | `this` | lambda result |
|
|
111
|
+
| `apply` | `this` | receiver |
|
|
112
|
+
| `also` | `it` | receiver |
|
|
113
|
+
| `with(x)` | `this` | lambda result |
|
|
114
|
+
|
|
115
|
+
Quick guide:
|
|
116
|
+
- **Side effect, keep value** → `also`
|
|
117
|
+
- **Configure a builder** → `apply`
|
|
118
|
+
- **Null-safe transformation** → `let`
|
|
119
|
+
- **Computation using members** → `run`
|
|
120
|
+
|
|
121
|
+
Don't chain three+ scope functions — it gets confusing fast.
|
|
122
|
+
|
|
123
|
+
## Extension functions
|
|
124
|
+
|
|
125
|
+
Add methods to existing types without inheriting.
|
|
126
|
+
|
|
127
|
+
```kotlin
|
|
128
|
+
fun String.toSlug(): String = lowercase().replace(" ", "-")
|
|
129
|
+
|
|
130
|
+
"Hello World".toSlug() // "hello-world"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Rules:
|
|
134
|
+
- Can't access private members; no reflection shortcuts.
|
|
135
|
+
- Dispatch is static (by declared type, not runtime type) — don't use to "override" methods.
|
|
136
|
+
- Place in a file named for the extended type (`StringExt.kt`) or feature (`Slug.kt`).
|
|
137
|
+
|
|
138
|
+
## Destructuring
|
|
139
|
+
|
|
140
|
+
```kotlin
|
|
141
|
+
val (id, email) = user // positional, for data classes
|
|
142
|
+
val (key, value) = map.entries.first()
|
|
143
|
+
|
|
144
|
+
for ((i, x) in list.withIndex()) { ... }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Don't destructure too-far-apart fields; order becomes a secret convention.
|
|
148
|
+
|
|
149
|
+
## Default & named arguments
|
|
150
|
+
|
|
151
|
+
```kotlin
|
|
152
|
+
fun send(to: String, subject: String, body: String = "", priority: Int = 0) { ... }
|
|
153
|
+
|
|
154
|
+
send(to = "a@x.com", subject = "hi")
|
|
155
|
+
send("a@x.com", "hi", priority = 1)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Prefer named args at call sites with 3+ params. Defaults obviate overload explosions.
|
|
159
|
+
|
|
160
|
+
## Single-expression functions
|
|
161
|
+
|
|
162
|
+
```kotlin
|
|
163
|
+
fun double(x: Int) = x * 2
|
|
164
|
+
fun greet(name: String): String = "hi $name"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Keep for functions that really fit on one line. Force explicit return type on public API.
|
|
168
|
+
|
|
169
|
+
## Collection API
|
|
170
|
+
|
|
171
|
+
```kotlin
|
|
172
|
+
// Read-only vs mutable
|
|
173
|
+
val immutable: List<Int> = listOf(1, 2, 3)
|
|
174
|
+
val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
|
|
175
|
+
|
|
176
|
+
// Transformations
|
|
177
|
+
users.filter { it.active }.map { it.email }
|
|
178
|
+
users.groupBy { it.role }
|
|
179
|
+
users.associateBy { it.id } // Map<id, user>
|
|
180
|
+
users.sortedBy { it.name }
|
|
181
|
+
users.partition { it.active } // Pair<active, inactive>
|
|
182
|
+
|
|
183
|
+
// Aggregations
|
|
184
|
+
users.count { it.active }
|
|
185
|
+
users.sumOf { it.balance }
|
|
186
|
+
users.maxByOrNull { it.age }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Chain 2–3 ops. Beyond that, break into named intermediate values or pull into a function.
|
|
190
|
+
|
|
191
|
+
`asSequence()` for lazy evaluation over large collections:
|
|
192
|
+
```kotlin
|
|
193
|
+
users.asSequence().filter { ... }.map { ... }.take(10).toList()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Ranges
|
|
197
|
+
|
|
198
|
+
```kotlin
|
|
199
|
+
1..10 // IntRange (inclusive)
|
|
200
|
+
1 until 10 // IntRange 1..9 (exclusive upper)
|
|
201
|
+
10 downTo 1 // reversed
|
|
202
|
+
1..10 step 2 // 1, 3, 5, 7, 9
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Prefer ranges over manual `for (i = ...; i < ...; i++)` — clearer intent.
|
|
206
|
+
|
|
207
|
+
## String templates
|
|
208
|
+
|
|
209
|
+
```kotlin
|
|
210
|
+
val msg = "Hello $name, you have ${messages.size} messages"
|
|
211
|
+
val raw = """
|
|
212
|
+
Multi-line
|
|
213
|
+
${expression}
|
|
214
|
+
""".trimIndent()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
No need for format strings in most cases.
|
|
218
|
+
|
|
219
|
+
## Object expressions & declarations
|
|
220
|
+
|
|
221
|
+
```kotlin
|
|
222
|
+
// Singleton
|
|
223
|
+
object Config {
|
|
224
|
+
val url = "https://api.example.com"
|
|
225
|
+
}
|
|
226
|
+
Config.url
|
|
227
|
+
|
|
228
|
+
// Anonymous object
|
|
229
|
+
val listener = object : Listener {
|
|
230
|
+
override fun onEvent(e: Event) { ... }
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Use `object` for singletons and one-off anonymous impls. Avoid `object` for mutable state — it's a global and hard to test.
|
|
235
|
+
|
|
236
|
+
## `companion object`
|
|
237
|
+
|
|
238
|
+
```kotlin
|
|
239
|
+
class User private constructor(val id: String) {
|
|
240
|
+
companion object {
|
|
241
|
+
fun create(email: String): User = User(generateId(email))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
User.create("a@x.com")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Factory methods, constants, static-like helpers live here.
|
|
249
|
+
|
|
250
|
+
## Java interop essentials
|
|
251
|
+
|
|
252
|
+
- `@JvmStatic` on `companion object` members to expose them as static methods to Java
|
|
253
|
+
- `@JvmField` to expose a `val` / `var` as a Java field
|
|
254
|
+
- `@JvmOverloads` to generate overloads for default params
|
|
255
|
+
- Platform types (`String!`) — values from Java with unknown nullability; treat as non-null by convention OR check.
|
|
256
|
+
|
|
257
|
+
## Anti-patterns
|
|
258
|
+
|
|
259
|
+
| Anti-pattern | Fix |
|
|
260
|
+
|---|---|
|
|
261
|
+
| `!!` used as "I promise this isn't null" | Restructure so the type proves it; or check and handle |
|
|
262
|
+
| `lateinit` in application code | Use `by lazy { ... }` or pass via constructor |
|
|
263
|
+
| `Unit`-returning function ending with `return Unit` | Implicit; remove |
|
|
264
|
+
| `if-else` chain where `when` is clearer | Use `when` |
|
|
265
|
+
| `var x: List<T> = mutableListOf()` — type vs reality mismatch | Pick one: `val x: MutableList<T>` or use `+` for new lists |
|
|
266
|
+
| Data class used for behaviour-heavy type | Regular class; keep data classes for values |
|
|
267
|
+
| Extension functions that hide bugs (subtle override attempts) | Extension dispatch is static; don't rely on polymorphism |
|
|
268
|
+
| `companion object` holding request-scoped state | Request scope belongs on a request-scoped object |
|