What is it?
This is a wrapper language for Golang. We are getting a little closer to the Kotlin syntax. Hence the name. Something from the TypeScript/CoffeeScript series for Javascript.
The goal: to make life easier now and maybe in the future Golang will improve the language. As can be seen from Javascript and Java, there was practically no progress before the advent of alternatives.
Accordingly, the code is as close as possible to Golang, but with improved syntax.
It’s at the concept stage now.: how to improve the Golang language with little blood.
How does it work?
The Cola translator searches for files with the kol extension and generates go files from them. Then you can do everything with them in the same way as with regular go files.
Examples
We take examples from the Golang website. There are a lot of mathematical functions in the examples (in the worst traditions of programming books of the 90s and earlier). And there is no error handling (shame?)…
Therefore, we take the first 2 examples from the site and another example from myself about working with errors.
Hello, World!
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Kol:
package main
import fmt.println
fun main() {
println("Hello, 世界")
}
Conway’s Game of Life
// An implementation of Conway's Game of Life.
package main
import (
"bytes"
"fmt"
"math/rand"
"time"
)
// Field represents a two-dimensional field of cells.
type Field struct {
s [][]bool
w, h int
}
// NewField returns an empty field of the specified width and height.
func NewField(w, h int) *Field {
s := make([][]bool, h)
for i := range s {
s[i] = make([]bool, w)
}
return &Field{s: s, w: w, h: h}
}
// Set sets the state of the specified cell to the given value.
func (f *Field) Set(x, y int, b bool) {
f.s[y][x] = b
}
// Alive reports whether the specified cell is alive.
// If the x or y coordinates are outside the field boundaries they are wrapped
// toroidally. For instance, an x value of -1 is treated as width-1.
func (f *Field) Alive(x, y int) bool {
x += f.w
x %= f.w
y += f.h
y %= f.h
return f.s[y][x]
}
// Next returns the state of the specified cell at the next time step.
func (f *Field) Next(x, y int) bool {
// Count the adjacent cells that are alive.
alive := 0
for i := -1; i <= 1; i++ {
for j := -1; j <= 1; j++ {
if (j != 0 || i != 0) && f.Alive(x+i, y+j) {
alive++
}
}
}
// Return next state according to the game rules:
// exactly 3 neighbors: on,
// exactly 2 neighbors: maintain current state,
// otherwise: off.
return alive == 3 || alive == 2 && f.Alive(x, y)
}
// Life stores the state of a round of Conway's Game of Life.
type Life struct {
a, b *Field
w, h int
}
// NewLife returns a new Life game state with a random initial state.
func NewLife(w, h int) *Life {
a := NewField(w, h)
for i := 0; i < (w * h / 4); i++ {
a.Set(rand.Intn(w), rand.Intn(h), true)
}
return &Life{
a: a, b: NewField(w, h),
w: w, h: h,
}
}
// Step advances the game by one instant, recomputing and updating all cells.
func (l *Life) Step() {
// Update the state of the next field (b) from the current field (a).
for y := 0; y < l.h; y++ {
for x := 0; x < l.w; x++ {
l.b.Set(x, y, l.a.Next(x, y))
}
}
// Swap fields a and b.
l.a, l.b = l.b, l.a
}
// String returns the game board as a string.
func (l *Life) String() string {
var buf bytes.Buffer
for y := 0; y < l.h; y++ {
for x := 0; x < l.w; x++ {
b := byte(' ')
if l.a.Alive(x, y) {
b = '*'
}
buf.WriteByte(b)
}
buf.WriteByte('\n')
}
return buf.String()
}
func main() {
l := NewLife(40, 15)
for i := 0; i < 300; i++ {
l.Step()
fmt.Print("\x0c", l) // Clear screen and print field.
time.Sleep(time.Second / 30)
}
}
Kol:
// An implementation of Conway's Game of Life.
package main
import (
bytes
fmt
math/rand
time
fmt.print
time.sleep
)
// Field represents a two-dimensional field of cells.
class Field {
val field: [][]bool
val width: int
val height: int
// returns an empty field of the specified width and height
init {
field = make([][]bool, h)
field.forEach { i ->
i = make([]bool, w)
}
}
// sets the state of the specified cell to the given value
fun set(x: int, y: int, value: bool) {
field[y][x] = value
}
// reports whether the specified cell is alive
// If the x or y coordinates are outside the field boundaries they are wrapped
// toroidally. For instance, an x value of -1 is treated as width-1.
fun isAlive(x: int, y: int): bool {
x += width
x %= width
y += height
y %= height
return field[y][x]
}
// returns the state of the specified cell at the next time step
fun next(x: int, y: int): bool {
// Count the adjacent cells that are alive.
alive := 0
for i := -1; i <= 1; i++ {
for j := -1; j <= 1; j++ {
if (j != 0 || i != 0) && isAlive(x+i, y+j) {
alive++
}
}
}
// Return next state according to the game rules:
// exactly 3 neighbors: on,
// exactly 2 neighbors: maintain current state,
// otherwise: off.
return alive == 3 || alive == 2 && isAlive(x, y)
}
}
class Life(
val width: int,
val height: int,
) {
a: Field
b: Field
// creates a new Life game state with a random initial state
init {
a = Field(w, h)
b = Field(w, h)
randomizeField(a)
}
fun randomizeField(field: Field) {
for (i in 0..(width * height / 4-1)) {
field.set(rand.intn(width), rand.intn(height), true)
}
}
// advances the game by one instant, recomputing and updating all cells
fun step() {
// Update the state of the next field (b) from the current field (a).
for y in 0..height-1 {
for x in 0..width-1 {
b.set(x, y, a.next(x, y))
}
}
// Swap fields a and b.
a, b = b, a
}
// returns the game board as a string
fun string(): string {
var buf: bytes.Buffer
for y in 0..height-1 {
for x in 0..width-1 {
b: byte := if (a.isAlive(x, y)) '*' else ' '
buf.writeByte(b)
}
buf.writeByte('\n')
}
return buf.string()
}
}
fun clearScreen() {
print("\x0c")
}
fun main() {
life := Life(40, 15)
for i in 0..299 {
life.step()
clearScreen()
print(life)
sleep(time.second / 30)
}
}
Пример с ошибками
///usr/bin/true; exec /usr/bin/env go run "$0" "$@"
package main
import (
"errors"
"fmt"
)
type User struct {
ID string
Username string
Age int
}
func Find(username string) (*User, error) {
return nil, errors.New("Some error")
}
func FindUser(username string) (*User, error) {
u, err := Find(username)
if err != nil {
return nil, fmt.Errorf("FindUser: failed executing db query: %w", err)
}
return u, nil
}
func FindAndSetUserAge(username string, age int) error {
var user *User
var err error
user, err = FindUser(username)
if err != nil {
return fmt.Errorf("FindAndSetUserAge: %w", err)
}
// code that updates user
fmt.Println("user: %s", user.Username)
return nil
}
func main() {
if err := FindAndSetUserAge("user1@example.com", 1); err != nil {
fmt.Println("failed finding or updating user:", err)
return
}
fmt.Println("successfully updated user's age")
}
Kol:
package main
import (
errors
fmt
fmt.errorf
fmt.println
)
type User struct {
ID string
Username string
Age int
}
fun find(username: string): *User, error {
// throws -- special return case aka return nil, err
throws errors.new("Some error")
}
fun findUser(username: string): *User, error {
// executing db query
u, err := find(username)
// err is handled automatically including "executing db query" parse
return u
}
fun findAndSetUserAge(username: string, age: int): error {
var user: *User
var err: error
user, err = findUser(username)
// code that updates user
println("user: %s", user.Username)
}
fun main() {
// explicit checks are still possible
if err := findAndSetUserAge("user1@example.com", 1); err != nil {
println("failed finding or updating user:", err)
return
}
// try-catch syntax added
try {
findAndSetUserAge("user2@example.com", 5)
findAndSetUserAge("user3@example.com", 10)
} catch (err) {
println("failed finding or updating user:", err)
return
}
println("successfully updated user's age")
}
The main differences
Big differences:
- the normal syntax of classes (they actually exist anyway)
- error handling
- return means nil for error, throw means nil for non-error
- automatic wrapping if there is no try-catch and there is an error in the response
- try-catch
- all functions with a small letter (it would seem that C# has nothing to do with it)
Smaller:
- import of individual functions
- fun instead of func
- for / forEach slightly different are possible
- assignment condition (a = if (b) 1 else 0)
So far, there is no description of the new language to think through all possible cases.
It is clear that this is still the first approximation. And maybe a lot of things will change, but solving problems from the “big differences” section in one form or another will remain.
Where can I download it?
Nowhere yet.
The examples look good. It is clear how this can be implemented.
At a minimum, you need:
- a description of the language
- translator in golang
- syntax subtype
- code reformation
It is still unclear when the hands will reach. So far, I have no plans to write anything on the Golang platform, and then we’ll see.