442 lines
9.9 KiB
Markdown
442 lines
9.9 KiB
Markdown
# Gotlin
|
|
|
|
Gotlin is a Kotlin-flavored language implemented in Go. It compiles typed
|
|
Gotlin source to Go and uses the Go toolchain for binaries, packages, and
|
|
interop.
|
|
|
|
```kotlin
|
|
package main
|
|
|
|
fun fibonacci(value: Int): Int {
|
|
if (value < 2) { return value }
|
|
return fibonacci(value - 1) + fibonacci(value - 2)
|
|
}
|
|
|
|
fun main() {
|
|
println(fibonacci(10))
|
|
}
|
|
```
|
|
|
|
## Compiler pipeline
|
|
|
|
```text
|
|
Lexer
|
|
-> parser and syntax AST
|
|
-> package resolver and structural TypeRef resolution
|
|
-> lexical symbols, type checking, effects, and diagnostics
|
|
-> typed HIR
|
|
-> Go emission
|
|
-> Go compiler
|
|
```
|
|
|
|
Semantic types distinguish named values, Gotlin classes, nullable values, Go
|
|
pointers, functions, generics, tuples, and imported Gotlin classes. A Gotlin
|
|
class remains `User` throughout semantic analysis; only Type-to-Go lowering
|
|
chooses the `*User` representation.
|
|
|
|
Separately compiled Gotlin packages expose a versioned `.gti.json` interface
|
|
containing classes, enums, signatures, generics, and inferred effects. The
|
|
compiler and LSP load package interfaces before using `go/types` for ordinary
|
|
Go dependencies.
|
|
|
|
## Functions and values
|
|
|
|
```kotlin
|
|
fun greet(name: String): String {
|
|
val message = "Hello " + name
|
|
return message
|
|
}
|
|
|
|
fun doubled(value: Int) = value * 2
|
|
```
|
|
|
|
`val` is immutable and `var` is mutable:
|
|
|
|
```kotlin
|
|
val accountId = "account-1"
|
|
var attempts = 0
|
|
attempts += 1
|
|
```
|
|
|
|
Supported control flow includes `if`, `else`, `while`, `for`, `try`, `catch`,
|
|
`throw`, exhaustive `match`, and `defer`.
|
|
|
|
## Classes and interfaces
|
|
|
|
Gotlin classes are reference-valued by default:
|
|
|
|
```kotlin
|
|
interface Greeter {
|
|
fun greet(name: String): String
|
|
}
|
|
|
|
class PrefixGreeter(val prefix: String): Greeter {
|
|
fun greet(name: String) = prefix + name
|
|
}
|
|
|
|
fun create(): PrefixGreeter = PrefixGreeter("Hello ")
|
|
```
|
|
|
|
Use `*` only for Go pointer types:
|
|
|
|
```kotlin
|
|
fun handle(request: *http.Request, pool: *pgxpool.Pool) { }
|
|
```
|
|
|
|
Imported Gotlin classes retain reference semantics across package boundaries:
|
|
|
|
```kotlin
|
|
fun publish(lifecycle: platform.Lifecycle) { }
|
|
```
|
|
|
|
## Null safety
|
|
|
|
Types are non-nullable unless marked with `?`:
|
|
|
|
```kotlin
|
|
fun email(user: User?): String {
|
|
if (user == null) { return "missing" }
|
|
return user.email
|
|
}
|
|
```
|
|
|
|
Safe access and non-null assertions are available:
|
|
|
|
```kotlin
|
|
val city: String? = user?.address?.city
|
|
val required: User = optionalUser!!
|
|
```
|
|
|
|
The semantic analyzer smart-casts values after null checks and guard clauses.
|
|
|
|
## Data classes and JSON
|
|
|
|
```kotlin
|
|
@jsonNaming(camelCase)
|
|
data class AccountReply(
|
|
var accountId: String,
|
|
var availableBalance: Double
|
|
)
|
|
```
|
|
|
|
Supported JSON policies are `snakeCase`, `camelCase`, `pascalCase`, and
|
|
`kebabCase`.
|
|
|
|
```kotlin
|
|
val body = json.marshal(reply).unwrap()
|
|
val decoded = json.decode<AccountReply>(body).unwrap()
|
|
```
|
|
|
|
Resources can be embedded at package scope:
|
|
|
|
```kotlin
|
|
import embed
|
|
|
|
@embed("static/*") val assets: embed.FS
|
|
```
|
|
|
|
## Enums and match
|
|
|
|
Enums support payloadless and payload variants:
|
|
|
|
```kotlin
|
|
enum PaymentResult {
|
|
Accepted(String)
|
|
Rejected(String)
|
|
Pending
|
|
}
|
|
```
|
|
|
|
Matches are exhaustive and may return values:
|
|
|
|
```kotlin
|
|
fun description(result: PaymentResult) = match (result) {
|
|
PaymentResult.Accepted(id) -> "accepted " + id
|
|
PaymentResult.Rejected(reason) -> "rejected " + reason
|
|
PaymentResult.Pending -> "pending"
|
|
}
|
|
```
|
|
|
|
Payloadless enums are represented as exact string-backed values, making them
|
|
suitable for JSON and PostgreSQL columns.
|
|
|
|
## Result error handling
|
|
|
|
```kotlin
|
|
fun parse(value: String): Result<Int, Error> = strconv.atoi(value)
|
|
|
|
fun doubled(value: String): Result<Int, Error> {
|
|
val parsed = strconv.atoi(value)?
|
|
return Result.Ok(parsed * 2)
|
|
}
|
|
```
|
|
|
|
Available operations include `?`, `unwrap()`, `unwrapOr(value)`, explicit
|
|
destructuring, and exhaustive `Result` matching. Go `(T, error)` and error-only
|
|
returns adapt to `Result` when required by context.
|
|
|
|
## Generics and lambdas
|
|
|
|
```kotlin
|
|
data class Box<T>(var value: T) {
|
|
fun get(): T = value
|
|
}
|
|
|
|
fun identity<T>(value: T): T = value
|
|
|
|
val inferred = identity(42)
|
|
val explicit = identity<String>("value")
|
|
val boxed = Box("text")
|
|
```
|
|
|
|
Higher-order functions use Kotlin-style function types and trailing lambdas:
|
|
|
|
```kotlin
|
|
fun transform<T, R>(value: T, block: (T) -> R): R = block(value)
|
|
|
|
val answer = transform(21) { value ->
|
|
value * 2
|
|
}
|
|
```
|
|
|
|
The final expression is returned automatically for value lambdas. Unit lambdas
|
|
execute their final expression as a statement. Explicit `return` is also
|
|
supported.
|
|
|
|
## Structured concurrency and context
|
|
|
|
Coroutine effects are inferred from direct and transitive calls:
|
|
|
|
```kotlin
|
|
fun poll() {
|
|
while (isActive()) {
|
|
receiveEvents()
|
|
delay(1000)
|
|
}
|
|
}
|
|
|
|
fun main() {
|
|
runBlocking {
|
|
launch { poll() }
|
|
}
|
|
}
|
|
```
|
|
|
|
`launch` creates a structured Unit child. `async<T>` creates a typed deferred
|
|
child. Scopes wait for children, propagate failures, and cancel siblings.
|
|
|
|
Available operations include `runBlocking`, `withContext`, `coroutineScope`,
|
|
`launch`, `async`, `await`, `delay`, `withTimeout`, `isActive`, and
|
|
`coroutineContext`.
|
|
|
|
HTTP request contexts can establish an ambient scope with an expression-bodied
|
|
function:
|
|
|
|
```kotlin
|
|
fun handle(request: *http.Request) = withContext(request.context()) {
|
|
service.processRequest()
|
|
}
|
|
```
|
|
|
|
Go calls whose first parameter is `context.Context` receive the ambient context
|
|
automatically when that argument is omitted:
|
|
|
|
```kotlin
|
|
fun command(): *exec.Cmd = exec.commandContext("date")
|
|
```
|
|
|
|
Passing an explicit context suppresses automatic injection.
|
|
|
|
## Collections and channels
|
|
|
|
```kotlin
|
|
val names = listOf<String>("Ada", "Linus")
|
|
val scores = mapOf<String, Int>("Ada", 10, "Linus", 8)
|
|
val channel = Channel<String>(1)
|
|
|
|
channel.send("ready")
|
|
val message = channel.read()
|
|
```
|
|
|
|
Collection types include `List<T>`, `MutableList<T>`, `Map<K, V>`, and
|
|
`MutableMap<K, V>`.
|
|
|
|
## Structural mapping
|
|
|
|
`mapTo` maps compatible classes, enums, collections, and nullable values:
|
|
|
|
```kotlin
|
|
fun response(account: AccountEntity): AccountResponse = account.mapTo()
|
|
|
|
val response = account.mapTo<AccountResponse>()
|
|
```
|
|
|
|
Fields are matched by name and mappings are validated recursively with
|
|
path-specific compile errors.
|
|
|
|
## Typed PostgreSQL DSL
|
|
|
|
SQL rows are data classes with table metadata:
|
|
|
|
```kotlin
|
|
@table("accounts")
|
|
data class AccountRow(
|
|
@generated @id var id: String,
|
|
var customerId: String,
|
|
var balance: Double,
|
|
var closedAt: time.Time?
|
|
)
|
|
```
|
|
|
|
### Queries, aliases, joins, groups, and aggregates
|
|
|
|
```kotlin
|
|
@table("customers")
|
|
data class CustomerRow(@id var id: String, var name: String)
|
|
|
|
data class AccountSummary(
|
|
var customerId: String,
|
|
var customerName: String,
|
|
var total: Double,
|
|
var entries: Long
|
|
)
|
|
|
|
fun summaries(minimum: Double): GotlinSQLQuery = sql.from<AccountRow>()
|
|
.alias("account")
|
|
.leftJoin<CustomerRow>("customer") { account, customer ->
|
|
account.customerId == customer.id
|
|
}
|
|
.select { account, customer ->
|
|
AccountSummary(
|
|
account.customerId,
|
|
customer.name,
|
|
sum(account.balance),
|
|
count()
|
|
)
|
|
}
|
|
.where { account, customer -> account.balance > minimum }
|
|
.groupBy { account, customer -> listOf(account.customerId, customer.name) }
|
|
.having { account, customer -> sum(account.balance) > minimum }
|
|
.orderByDescending { account, customer -> sum(account.balance) }
|
|
.limit(100)
|
|
.offset(20)
|
|
.build()
|
|
```
|
|
|
|
Join methods include `join`, `leftJoin`, and `rightJoin`. Aggregates include
|
|
`count`, `countDistinct`, `sum`, `avg`, `min`, and `max`.
|
|
|
|
### Inserts and bulk inserts
|
|
|
|
```kotlin
|
|
val insert = sql.insert<AccountRow>(row).build()
|
|
val bulk = sql.insertAll<AccountRow>(rows).build()
|
|
```
|
|
|
|
Bulk inserts create runtime-sized PostgreSQL placeholder lists and reject empty
|
|
input.
|
|
|
|
### Conflicts and updates
|
|
|
|
```kotlin
|
|
val upsert = sql.insert<AccountRow>(row)
|
|
.onConflict { it.id }
|
|
.doUpdate { excluded ->
|
|
AccountRow.balance = excluded.balance
|
|
}
|
|
.build()
|
|
|
|
val update = sql.update<AccountRow>()
|
|
.set { row ->
|
|
row.balance = row.balance + amount
|
|
row.closedAt = now()
|
|
}
|
|
.where { it.id == accountId }
|
|
.returning { it }
|
|
.build()
|
|
```
|
|
|
|
Write targets are checked for existence, mutability, nullability, duplicate
|
|
assignment, and value compatibility.
|
|
|
|
### Execution
|
|
|
|
```kotlin
|
|
val accounts = sql.from<AccountRow>()
|
|
.where { it.customerId == customerId }
|
|
.fetch(pool, ctx)
|
|
.unwrap()
|
|
|
|
val account = sql.from<AccountRow>()
|
|
.where { it.id == accountId }
|
|
.single(pool, ctx)
|
|
.unwrap()
|
|
```
|
|
|
|
Execution terminals are `fetch`, `single`, and `iterator`. They return typed
|
|
`Result` values and use generated row scanners.
|
|
|
|
## Go interop
|
|
|
|
Go packages are imported directly:
|
|
|
|
```kotlin
|
|
import http net.http
|
|
import pgxpool "github.com/jackc/pgx/v5/pgxpool"
|
|
```
|
|
|
|
Go selectors are written in lower camel case and emitted with exported Go
|
|
names. Named arguments construct external Go structs:
|
|
|
|
```kotlin
|
|
val client = http.Client(timeout = 3 * time.second)
|
|
```
|
|
|
|
`go/types` and `go/packages` provide function, method, field, alias, tuple,
|
|
variadic, and error-return signatures to semantic analysis and the LSP.
|
|
|
|
## Build and run
|
|
|
|
```bash
|
|
go run ./cmd/gotlinc build ./examples/hello.gt
|
|
./hello
|
|
|
|
go run ./cmd/gotlinc run ./examples/hello.gt
|
|
```
|
|
|
|
Emit Go source:
|
|
|
|
```bash
|
|
go run ./cmd/gotlinc build -src ./examples/hello.gt -o /tmp/hello.go
|
|
```
|
|
|
|
Emit and consume package interfaces:
|
|
|
|
```bash
|
|
gotlinc build -src \
|
|
-metadata-output platform.gti.json \
|
|
-metadata-package example/platform \
|
|
platform.gt -o platform.go
|
|
|
|
gotlinc build -src \
|
|
-metadata platform.gti.json \
|
|
service.gt -o service.go
|
|
```
|
|
|
|
## Language server and editor
|
|
|
|
Build the language server:
|
|
|
|
```bash
|
|
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
|
|
```
|
|
|
|
The VS Code extension under `tools/vscode-gotlin` provides syntax highlighting,
|
|
snippets, diagnostics, hover, document symbols, definitions, references, and a
|
|
`gopls` bridge for imported Go APIs.
|
|
|
|
```bash
|
|
cd tools/vscode-gotlin
|
|
npm install
|
|
npm run check
|
|
```
|