V at a Glance — A Language a Day, Advent Calendar Day 16/24

About this ‘A Language a Day’ Advent Calendar

Andrew Shitov. A Language a Day

This series of publications transformed into my book ‘A Language a Day’, which you can get in both electronic and paper format. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.

Learn more where to get the book

Welcome to Day 16 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the V programming language.

Facts about the language

Some facts about the V programming language:

  • Inspired by Go
  • Is still under heavy development
  • Compiled language
  • Statically typed
  • Appeared in 2019
  • Website: vlang.io
  • Searchable term: #vlang

Installing and running V

You can either download a package or install V from sources. In my case, it took a few seconds and went flawless. After that, you will have the v executable, to which you can point a symlink or update your PATH variable.

To compile a program, run:

$ v hello-world.v

This command builds an executable file:

$ ./hello-world

Alternatively, run and compile with a single command:

$ v run hello-world.v

Hello, World!

Here is a Hello, World! program in the V language:

println("Hello, World!")

Actually, the Hello, World! example could be re-written with the main() function:

fn main() {
    println("Hello, World!")
}

Variables

No keywords are required to create a variable in V, but you need the := operator do declare and initialise it. Variables cannot be global, so you need a function to create a variable:

fn main() {
    name := "John"
    println("Hello, $name!")
}

To change the value of a variable, use the assignment operator =, but make sure to declare the variable as mut:

fn main() {
    mut age := 21
    println("Their age was ${age}.")

    age = 22
    println("Their age is ${age} now.")
}

In the above examples you’ve seen how the variable is interpolated in a string: "$name". In the second example, curly braces were added so that the compiler does not treat the following dot as a method call: "${age}.".

Functions

Functions are declared with the fn keyword. We have already seen the use of the main function, which does not take any arguments and does not return a value.

Functions can take parameters and return values; it is also possible to return comma-separated multiple values. You should add type annotations to both the parameters and the result.

fn main() {
    println(greet("Alla"))
}

fn greet(name string) string {
    return 'Hello, ' + name + '!'
}

Note that you can declare a function after you use it in the code.

In this example, string concatenation was done with +, and the strings were surrounded with single quotes unlike the earlier examples. The interpolations with a $ work both with single- and double-quoted strings.

A Factorial example

Let us create a recursive function for computing factorials:

fn factorial(n int) int {
    if n < 1 {return 1}
    else {return n * factorial(n - 1)}
}

fn main() {
    println(factorial(1)) // 1
    println(factorial(5)) // 120
}

Structs and methods

There are no classes in V, so let us look at structures and how to create functions operating them.

Let us write code for keeping personal data. First, the data structure:

struct Person {
    name string
    age int
}

You can now create an object of this type:

p := Person{name: "John", age: 22}

Notice that there are no keywords involved here at all (if we don’t count the := sequence).

To define a method for this data type, create a function that takes a receiver as a special kind of arguments:

fn (p Person) info(delta int) string {
    return "In $delta years, $p.name will be ${p.age+delta} years old."
}

Here, the (p Person) construct indicates that you can call the method on a Person object using the dot:

fn main() {
    p := Person{name: "John", age: 22}
    println(p.info(5))
}

The method can take other arguments as any function does.

Overloading methods

It is not possible to overload a function based on the arguments. The following code will not compile:

struct Cat {}
struct Dog {}

fn f(x Dog) {
    println("Dog")
}

fn f(x Cat) {
    println("Cat")
}

The compiler sees the second f as an attempt to redefine the function. But you can use the receiver to dispatch function calls as it is demonstrated in the next example.

A Polymorphic example

Here is the solution of the zoo task. The two structures are independent, as you don’t need to express hierarchy.

struct Cat {}
struct Dog {}

fn (x Dog) info() string {
    return "Dog"
}

fn (x Cat) info() string {
    return "Cat"
}

zoo1 := [Cat{}, Cat{}]
zoo2 := [Dog{}, Dog{}]

for a in zoo1 {
    println(a.info())
}
for a in zoo2 {
    println(a.info())
}

You might have noticed that although the we are using the same code a.info() to invoke a method for both cats and dogs, there are two arrays that keep them separately. In V, all array elements should be of the same type.

Interfaces

Let us modify the zoo program to introduce interfaces. First, here is the interface itself. The name of the interface must end with er as of today:

interface Informer {
    info() string
}

Second, the function that accepts the argument of the Informer type:

fn zoo_info(i Informer) string {
    return i.info()
}

Having the interface defined, you can call the zoo_info function and pass the objects of different types to it:

for a in zoo1 {
    println(zoo_info(a)) // a is a Cat
}
for a in zoo2 {
    println(zoo_info(a)) // a is a Dog
}

Concurrency

The V language offers the go system thread launcher at the moment, and it looks like more concurrency support will be soon implemented.

Sleep sort

In the below implementation, the sort_me function is launched in a separate thread for each data item.

import time

fn sort_me(n int) {
    time.sleep_ms(n * 20)
    println(n)
}

fn main() {
    data := [10, 4, 2, 6, 2, 7, 1, 3]
    for n in data {
        go sort_me(n)
    }

    time.sleep_ms(1000)
}

To join the threads, a big enough delay is added at the end of the program. Examine the example in the project repository to get an idea of how to make this better using wait().

Hot code reloading

V’s fast compilation and execution allows them to implement the so-called hot code reloading, where you can modify the source code, and it will be immediately recompiled and executed. Here is a slightly modified example from the documentation page:

module main

import time
import os

[live]
fn print_message() {
    println('Hello! Modify this message while the program is running.')
}

fn main() {
    mut counter := 1
    for {
        print_message()
        println('Run #$counter')
        counter++
        time.sleep_ms(500)
    }
}

The [live] attribute allows you to change the marked function while the program is running. Run it with using the following command line:

$ v -live run hot-reload.v

The program prints the message in an infinite loop, but you can change the text in the print_message function and see the result without recompiling the program.

Get more

This time, I can recommend you not only to read additional materials about the language, but also to do it again in a few months, as the language is still under development (but already demonstrates excellent result in both the design and the implementation).

You will find the source files for today’s article on GitHub.

Next: Day 17. Go

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code