niva

Личный сайт Go-разработчика из Казани

Intro

Niva is a simple language that takes a lot of inspiration from Smalltalk.
But leaning towards the functional side and static typed. Everything is still an object, but instead of classes, interfaces, inheritance, and abstract classes,
we have tagged unions, which is the only way to achieve polymorphism.

For example, everything except the declaration is sending messages to objects.
1 + 2 is not a + operator, but a ... + Int message for Int object. (there are no extra costs for that)

C-like: 1.inc()
Niva: 1 inc

In essence, niva is highly minimalistic, like its ancestor Smalltalk.
It introduces types, unions, and associated methods. There are no functions.

Install:

1git clone https://github.com/gavr123456789/Niva.git 2cd Niva 3./gradlew buildJvmNiva 4# LSP here https://github.com/gavr123456789/niva-vscode-bundle

The Basics

Variable

Variables are immutable by default. There is no special keyword for declaring a variable.

1// this is a comment 2int = 1 3str = "qwf" 4boolean = true 5char = 'a' 6float = 3.14f 7double = 3.14 8mutltiLineStr = """ 9qwf ars zxc \n \t "qwf" 10""" 11explicit_type::Int = 5 12 13 14list = {1 2 3} 15set = #(1 2 3) 16map = #{1 'a' 2 'b'} 17 18// declare a mutable variable 19mut x = 5 20x <- 6 // mutate

Messages

1// hello world is a one liner 2"Hello world" echo // echo is a message for String obj 3 4 5// There are 3 types of messages 61 inc // 2 unary 71 + 2 // 3 binary 8"abc" at: 0 // 'a' keyword 9 10 11// they can be chained 121 inc inc inc dec // 3 131 + 1 + 2 - 3 // 1 141 to: 3 do: [it echo] // 1 2 3 15// the last one here to:do: is a single message 16// to chain 2 keyword messages you need to wrap it in parentheses 17("123456" drop: 1) dropLast: 2 // "234" 18the comma `,` is syntactic sugar for the same effect 19"123456" drop: 1, dropLast: 2 // "234" 20 21// you can mix them 221 inc + 3 dec - "abc" count // 2 + 2 - 3 -> 1 23"123" + "456" drop: 1 + 1 // "123456" drop: 2 -> "3456" 24 25// everything except type and message declarations are message sends in niva 26// for example `if` is a message for Boolean object that takes a lambda 271 > 2 ifTrue: ["wow" echo] 28// expression 29base = 1 > 2 ifTrue: ["heh"] ifFalse: ["qwf"] 30// same for while 31mut q = 0 32[q > 10] whileTrue: [q <- q inc] 33// all of this is zero cost because of inlining at compile time

Type

New lines are not significant in niva
Type declarations look like keyword messages consisting of fields and types

1type Square side: Int 2 3type Person 4 name: String 5 age: Int

Create instance

Creating an object is the same keyword message as when declaring it, but with values in place of types.

1square = Square side: 42 2alice = Person name: "Alice" age: 24 3 4// destruct fields by names 5{age name} = alice

Access fields

Getting fields is the same as sending a unary message with its name to the object

1// get age, add 1 and print it 2alice age inc echo // 25

Method for type:

Everything is an object, just like in Smalltalk, so everything can have a method declared.
Here, we add a double method to Int and then use it inside the perimeter method of Square.

1Int double = this + this 2Square perimeter = side double 3 4square = Square side: 42 5square perimeter // call 6 7 8// explicit return type 9Int double2 -> Int = this * 2 10 11// with body 12Int double3 -> Int = [ 13 result = this * 2 14 ^ result // ^ is return 15]

Keyword message declaration

1type Range from: Int to: Int 2// keyword message with one arg `to` 3Int to::Int = Range from: this to: to 4 51 to: 2 // Range

Type constructor

A type constructor functions as a message for the type itself rather than to a specific instance.

1constructor Float pi = 3.14 2x = Float pi // 3.14

It can be useful for initializing fields with default values.

1type Point x: Int y: Int 2constructor Point atStart = Point x: 0 y: 0 3 4p1 = Point x: 0 y: 0 5p2 = Point atStart 6// constructor is just a usual message, so it can have params 7constructor Point y::Int = Point x: 0 y: y 8p3 = Point y: 20 // x: 0 y: 20

Conditions

If, like everything else, is the usual sending of a message to a Boolean object.
It takes one or two lambda arguments.

1false ifTrue: ["yay" echo] 21 < 2 ifTrue: ["yay" echo] 31 > 2 ifTrue: ["yay" echo] ifFalse: ["oh no" echo] 4 5// `ifTrue:ifFalse:` message can be used as expression 6str = 42 % 2 == 0 7 ifTrue: ["even"] 8 ifFalse: ["odd"] 9// str == "even"

Cycles

There is no special syntax for cycles.
It’s just keyword messages that take codeblocks as parameters.
(it’s zero cost thanks for inlining)

1{1 2 3} forEach: [ it echo ] // 1 2 3 21..10 forEach: [ it echo ] 3 4mut c = 10 5[c > 0] whileTrue: [ c <- c dec ] 6 7c <- 3 // reset c 8[c > 0] whileTrue: [ 9 c <- c dec 10 c echo // 3 2 1 11]

whileTrue: is a message for lambda object of the type: [ -> Boolean] whileTrue::[ -> Unit]

Matching

there is special syntax for matching, since niva heavily utilize tagged unions

1x = "Alice" 2// matching on x 3| x 4| "Bob" => "Hi Bob!" 5| "Alice" => "Hi Alice!" 6|=> "Hi guest" 7 8 9// It can be used as expression as well 10y = | "b" 11 | "a" => 1 12 | "b" => 2 13 |=> 0 14y echo // 2

Tagged unions

1union Color = Red | Blue | Green 2 3// branches can have fields 4union Shape = 5 | Rectangle width: Int height: Int 6 | Circle radius: Double 7 8constructor Double pi = 3.14 9Double square = this * this 10 11// match on this(Shape) 12Shape getArea -> Double = 13 | this 14 | Rectangle => width * height, toDouble 15 | Circle => Double pi * radius square 16 17// There is exhaustiveness checking, so when you add a new branch 18// all the matches will become errors until all cases processed 19 20Shape getArea -> Double = | this 21 | Rectangle => width * height, toDouble 22// ERROR: Not all possible variants have been checked (Circle)

Collections

1// commas are optional 2list = {1 2 3} 3map = #{'a' 1 'b' 2} 4map2 = #{'a' 1, 'b' 2, 'c' 3} 5set = #(1 2 3) 6 7// common collection operations 8{1 2 3 4 5} 9 map: [it inc], 10 filter: [it % 2 == 0], 11 forEach: [it echo] // 2 4 6 12 13// iteration on map 14map forEach: [key, value -> 15 key echo 16 value echo 17]

Nullability

By default, variables cannot be assigned a null value.
To allow this, nullable types are used, indicated by a question mark at the end of the type.
You may already be familiar with this concept from TypeScript, Kotlin, or Swift.

1x::Int? = null 2q = x unpackOrPANIC 3 4// do something if it's not null 5x unpack: [it echo] 6 7// same but provide a backup value 8w = x unpack: [it inc] or: -1 9 10// just unpack or backup value 11e = x unpackOrValue: -1

Handling the error

1// exit the program with stack trace 2x = file read orPANIC 3x = file read orValue: "no file"

Errors work like effects, look for more in Error handling

Misc

Local arg names

1Int from: x::Int to: y::Int = this + x + y

Syntax sugar for this

1Person foo = [ 2 .bar 3 this bar // same thing 4]

Compile time reflection

You can get string representation of any argument from a call site.

1Int bar::Int baz::String = [ 2 // getting string representation from call side 3 a = Compiler getName: 0 4 b = Compiler getName: 1 5 c = Compiler getName: 2 6 a echo // 1 + 1 7 b echo // variable 8 c echo // "str" 9] 10 11variable = 42 12// call side 131 + 1 14 bar: variable 15 baz: "str"

Links: