Help us understand the problem. What is going on with this article?

V プログラミング言語

はじめに

The V Programming Language の公式ドキュメントの和訳だよ

情報はすべて↑から
コンパイルが速いこととC/C++との相互変換がウリの模様 (C/C++ 勢なので注目せざるを得ない
2019 年 4 月にアーリーアクセス、同年 6 月 22 日にオープンソースで公開済み
すでに V 自身で V コンパイラは作成されている
もともと GUI アプリ用につくられたので
クロスプラットフォームな GUI ライブラリとかもあったりする

実態は C 言語にトランスパイルするだけの言語 & 環境の模様
まだマシンコードは吐いていない

私も和訳で閲覧数を稼ぐコスいことしてみる

ただドキュメントが完全ではないのでまだ把握しきれてない

以下から訳文


導入

V は保守しやすいソフトウェアを構築するために設計された静的型付け言語です。
Go にとてもよく似通っており、Oberon、Rust、Swift にも影響されています。

V は非常にシンプルな言語です。
30 分ほどかけてこのドキュメントを読めば、言語の全貌がちょっとつかめるでしょう。

シンプルですが、開発者には大きな力を与えます。
他の言語でできることなら、V もできます。

Hello World

fn main() {
    println('hello world')
}

関数は fn で宣言します。戻り値の型は関数名の後ろに来ます。この場合 main は何も返さないので型は省かれています。

C やその類の言語と同じく、main はエントリーポイントです。

println はいくつかある組み込み関数の一つです。これは標準出力へ値を出力します。

fn main() の宣言は単一ファイルプログラム内では省略できます。これは小さいプログラム―「スクリプト」―を書くときや、言語について学ぶだけのときに役立ちます。簡潔にするため、このチュートリアルでは fn main() は省略されます。

すなわち「hello world」プログラムはここまでシンプルにできます。

println('hello world')

コメント

// これは単行コメント。

/* これは複行コメント。
   /* ネストできる。 */
*/

関数

fn main() {
    println(add(77, 33))
    println(sub(100, 50))
}

fn add(x int, y int) int {
    return x + y
}

fn sub(x, y int) int {
    return x - y
}

更に、型は実引数名の後に来ます。

Go や C のように、関数はオーバーロードできません。これはコードを単純化し保守性と可読性を改善します。

関数はその宣言より前で使用できます。addsubmain の後に宣言しても、main から呼び出すことができます。これは V のすべての宣言に当てはまり、ヘッダーファイルの必要性やファイルと宣言の順序についての考慮を排除します。

fn foo() (int, int) {
    return 2, 3
}

a, b := foo()
println(a) // 2
println(b) // 3

関数は複数の値を返すことができます。

変数

name := 'Bob' 
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number) 

変数は := で宣言と初期化が行われます。これは V で変数を宣言する唯一の手段です。つまり、変数は必ず初期値があるということです。

変数の型は右側の値から推論されます。他の型に強制するには、型変換を使います。T(v) と書くと値 v を型 T に変換します。

他の多くの言語とは異なり、V は関数内でしか変数の定義を許可しません。グローバル (モジュールレベルな) 変数は使用できません。V に大域な状態はないのです。

mut age := 20
println(age)
age = 21
println(age)

変数の値を変更するには = を使用します。V での変数はデフォルトでイミュータブルです。変数の値を変えられるようにするには、mut をつけて宣言する必要があります。
:== の違いに注意してください。
:= は宣言と初期化に用いられますが、= は代入に用いられます。

fn main() {
    age = 21
}

このコードはコンパイルできません。変数 age は宣言されていないからです。V ではすべての変数は宣言されている必要があります。

fn main() {
    age := 21
}

このコードもコンパイルできません。未使用の変数はコンパイルエラーとなるからです。

fn main() {
    a := 10 
    if true {
        a := 20
    } 
}

多くの言語と異なり、変数のシャドーイングは許可されていません。親スコープですでに使用されている名前の変数を宣言するとコンパイルエラーになります。

基本の型

bool

string

i8   i16  int  i64      i128 (そのうち)
byte u16  u32  u64      u128 (そのうち)

rune // Unicode コードポイントに相当

f32 f64

byteptr
voidptr

注意として、C や Go と異なり、int は常に 32 ビット整数になります。

文字列

name := 'Bob' 
println('Hello, $name!') // `$` は文字列挿入に使われる
println(name.len) 

bobby := name + 'by' // + は文字列連結に使われる 
println(bobby) // ==> "Bobby"

println(bobby.substr(1, 3)) // ==> ob

// この構文は substr() メソッドとよく置き換えられる
// println(bobby[1:3])   

mut s := 'hello '
s += 'world' // `+=` は文字列追加に使われる
println(s) // "hello world"  

V では、文字列は読み出し専用のバイトの配列です。文字列データは UTF-8 を用いてエンコードされます。

文字列はイミュータブルです。

シングルクォートとダブルクォートの両方が文字列を表すのに使われます (TODO: ダブルクォートはまだサポートされていません)。一貫性のため、vfmt はシングルクォート文字を含まない限りダブルクォートをシングルクォートに変換します。

挿入構文はだいぶシンプルです。このように、'age = $user.age' とすればフィールドでも動きます。より複雑な式が必要であれば、${} を用いて 'can register = ${user.age > 13}' というようにします。

V の演算子は両辺の値が同じ型でなければなりません。この以下のコードは ageint の場合はコンパイルされません。

println('age = ' + age)

以下のように age を文字列に変換するか、

println('age = ' + age.str())

以下の文字列挿入をします (こちらを推奨)。

println('age = $age')

文字リテラルを表す場合は、` を使用します。

a := `a`
assert 'aloha'[0] == `a`

配列

mut nums := [1, 2, 3]
println(nums) // "[1, 2, 3]"
println(nums[1]) // "2" 

nums << 4
println(nums) // "[1, 2, 3, 4]"


nums << [5, 6, 7]
println(nums) // "[1, 2, 3, 4, 5, 6, 7]"

mut names := ['John']
names << 'Peter' 
names << 'Sam' 
// names << 10  <-- これはコンパイルされません。`names` は文字列の配列です。 
println(names.len) // "3" 
println('Alex' in names) // "false" 

// 要素を一定量だけ事前に確保することもできます。
nr_ids := 50
ids := [0].repeat(nr_ids) // これは 50 個のゼロが入った配列を作成します

配列の型はその最初の要素で決定されます。[1, 2, 3] は整数の配列 ([]int) です。
['a', 'b'] は文字列の配列 ([]string) です。
配列のすべての要素は同じ型でなければなりません。[1, 'a'] はコンパイルされません。

<< は配列の末尾に値を追加する演算子です。

len フィールドは配列の長さを返します。注意として、これは読み出し専用のフィールドで、ユーザーによって変更できません。V ではすべての外から見えるフィールドはデフォルトで読み出し専用です。

val in array は配列に val が含まれていれば true を返します。

すべての配列は println(arr) で簡単に出力でき、s := arr.str() で文字列に変換できます。

配列は以下のように .filter().map() メソッドで効果的にフィルター、マップできます。

nums := [1, 2, 3, 4, 5, 6]
even := nums.filter(it % 2 == 0)
println(even) // [2, 4, 6]

words := ['hello', 'world']
upper := words.map(it.to_upper())
println(upper) // ['HELLO', 'WORLD']

itfilter / map メソッド内での要素を表す特殊な変数です。

辞書配列

mut m := map[string]int{} // 現在の辞書配列は文字列のみをキーにできる
m['one'] = 1
m['two'] = 2
println(m['one']) // "1"  
println(m['bad_key']) // "0"  
println('bad_key' in m) // `in` を使ってそのようなキーが存在するかどうか検出する
m.delete('two')

numbers := {
    'one': 1,
    'two': 2,
}

If

a := 10 
b := 20 
if a < b { 
    println('$a < $b') 
} else if a > b { 
    println('$a > $b') 
} else { 
    println('$a == $b') 
} 

if 文は素直で単純かつ他のほとんどの言語と同じです。
他の C 系の言語とは異なり、条件式は括弧で囲まず、波括弧は常に必要です。

if は以下のように式としても使用できます。

num := 777
s := if num % 2 == 0 {
    'even'
}
else {
    'odd'
}
println(s) // ==> "odd"

In 演算子

in はある配列にある要素が含まれるかどうかを確かめることができます。

nums := [1, 2, 3]
println(1 in nums) // ==> true 

さらに、以下のように真偽値の式をより明瞭かつコンパクトに書くのに役立ちます。

if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult {
    ... 
} 
if parser.token in [.plus, .minus, .div, .mult] {
    ... 
}

V はこのような式を最適化するので、上に挙げた両方の if 文は同じ機械語を生成し、配列は作成されません。

For ループ

V でのループ構造は for しかありません。

numbers := [1, 2, 3, 4, 5]
for num in numbers {
  println(num)
}
names := ['Sam', 'Peter']
for i, name in names {
  println('$i) $name')  // 出力: 0) Sam
}                       //     1) Peter

for value in ループは配列の要素を取っていくのによく用いられます。インデックスが必要な場合、もう一つの書き方として index, value in が使用できます。

注意として、value は読み取り専用です。ループで配列を変更する必要がある場合、インデックスを用います。

mut numbers := [1, 2, 3, 4, 5]
for i, num in numbers {
    println(num)
    numbers[i] = 0
}
mut sum := 0
mut i := 0
for i < 100 {
    sum += i
    i++
}
println(sum) // ==> "5050"

このループの書き方は他の言語での while ループと同様です。

このループは条件式が false に評価されると繰り返しを止めます。

同じく、条件式は括弧で囲まず、波括弧は常に必要です。

mut sum := 0
for {
    num++
    if num >= 10 {
        break 
    }
}
println(num) // ==> "10"

条件式は省略でき、これは無限ループになります。

for i := 0; i < 10; i++ {
    // 6 は出力しない
    if i == 6 {
        continue
    }
    println(i)
}

最後に、C スタイルの for ループもあります。これは while より安全な書き方です。なぜなら、後者の場合はカウンターの更新を忘れて無限ループに陥りやすいからです。

この imut をつけて宣言する必要はありません。定義したとき常にミュータブルになります。

Match

os := 'windows' 
print('V is running on ')
match os {
    'darwin' => println('macOS.')
    'linux' => println('Linux.')
    else => println(os) 
}

match 文は if - else 文の羅列を短く書く手段です。条件式の値と等しい最初の節が見つかると、=> に続く式が評価されます。else => 節は他に一致する節がない場合に評価されます。

enum Color {
    red
    blue
    green
}

fn is_red_or_blue(c Color) bool {
    return match c {
        .red { true }
        .blue { true }
        else { false }
    }
}

match 文は .種類 という省略構文で enum の種類を分岐することにも使用できます。

構造体

struct Point {
    x int
    y int 
} 

p := Point{
    x: 10 
    y: 20 
} 
println(p.x) // 構造体のフィールドはドットでアクセスできます

構造体はスタック上に確保されます。ヒープ上に確保する場合は、以下のように & 接頭子を使用してその参照を取得します。

pointer := &Point{10, 10}  // もう一つの構文、3 つ以下のフィールドの構造体を初期化する
println(pointer.x) // 参照もフィールドにアクセスする構文がある  

p の型は &Point です。これは Point への参照です。参照は Go のポインタや C++ の参照と似ています。

V にサブクラス化はありませんが、埋め込み構造体はあります。

// TODO: これは 8 月後期に実装されます
struct Button {
    Widget
    title string
}

button := new_button('Click me')
button.set_pos(x, y)

// 埋め込まれていないとこうすることになる
button.widget.set_pos(x, y)

構造体には以下のようにデフォルト値をつけられます。

struct Foo {
  a int
  b int = 10
}

foo := Foo{}
assert foo.a == 0
assert foo.b == 10

アクセス指定子

構造体のフィールドはデフォルトで非公開かつイミュータブルです (構造体もイミュータブルです)。これらのアクセス指定は pubmut で変更できます。以下のようにあわせて 5 つの選択肢があります。

struct Foo {
    a int     // 非公開 イミュータブル (デフォルト) 
mut: 
    b int     // 非公開 ミュータブル
    c int     // (同じアクセス指定で複数のフィールドを並べられる)
pub: 
    d int     // 公開 イミュータブル (読み取り専用) 
pub mut: 
    e int     // 公開 親モジュールでのみミュータブル 
__global: 
    f int     // 公開かつ親モジュールの内側でも外側でもミュータブル 
}                 // (利用を推奨していないので、冗長になっています) 

例として、組み込みモジュール内では string 型がこのように定義されています。

struct string {
    str byteptr
pub:
    len int
}

この定義から string がイミュータブル型であることが簡単にわかります。

文字列データのバイトポインタはどの外部からもアクセスできません。len フィールドは公開されていますが、変更できません。

fn main() {
    str := 'hello' 
    len := str.len // OK  
    str.len++      // コンパイルエラー
} 

メソッド

struct User {
    age int 
} 

fn (u User) can_register() bool {
    return u.age > 16 
} 

user := User{age: 10} 
println(user.can_register()) // ==> "false"  

user2 := User{age: 20} 
println(user2.can_register()) // ==> "true"  

V にはクラスがありません。しかし、型にメソッドを定義できます。

メソッドは特殊なレシーバー引数のある関数です。

レシーバーの引数リストは fn キーワードとメソッド名の間に出てきます。

この例では、can_register メソッドには User 型の u と命名されたレシーバーがあります。慣習として selfthis のようなレシーバー名は使わずに、短めで、一文字長の名前が好まれます。

既定で純粋関数

V の関数はデフォルトで純粋です。これは戻り値は引数によってのみ決定されるという意味で、その評価に副作用はありません。

これはグローバル変数がないことと、関数の引数は参照を渡してもデフォルトでイミュータブルであることによって実現しています。

しかし V は純粋関数型言語ではありません。以下のように mut キーワードを使うことで、関数の引数を変更することができます。

struct User {
    is_registered bool 
} 

fn (u mut User) register() {
    u.is_registered = true 
} 

mut user := User{} 
println(user.is_registered) // ==> "false"  
user.register() 
println(user.is_registered) // ==> "true"

この例では、レシーバー (単なる第一引数) がミュータブルにされているので、register()user オブジェクトを変更できます。以下のレシーバーの無い関数でも同様に動作します。

fn multiply_by_2(arr mut []int) {
    for i := 0; i < arr.len; i++ {
        arr[i] *= 2
    }
}

mut nums := [1, 2, 3]
multiply_by_2(mut nums)
println(nums) // ==> "[2, 4, 6]"

注意として、mutnums の前に追加してこの関数を呼び出す必要があります。これは呼び出す関数が値を変更することを明確にします。

引数を変更するよりは戻り値を返すほうが望ましいです。引数の変更は、アプリケーションのパーフォーマンス的に重要な箇所でメモリ確保やコピーを削減するためにのみされるべきです。

register(mut user) よりは user.register()user = register(user) を使用してください。

V でオブジェクトの変更版を返すことは簡単です。

fn register(u User) User { 
    return { u | is_registered: true } 
}

user = register(user) 

高階関数

fn sqr(n int) int {
        return n * n
}

fn run(value int, op fn(int) int) int {
        return op(value)
}

fn main()  {
        println(run(5, sqr)) // "25"
}

参照

fn (foo Foo) bar_method() {
    ...
}

fn bar_function(foo Foo) {
    ...
}

上の例での foo のように引数がイミュータブルな場合、V は値か参照で値渡しできます。この決定はコンパイラによって行われ、開発者がそのことを考慮する必要はありません。
構造体を値で渡すべきか参照で渡すべきか覚えておく必要はもうないのです。

常に参照渡しすることを確実にするには、以下のように & を付けます。

fn (foo &Foo) bar() {
    println(foo.abc)
}

これでも foo はイミュータブルで変更されません。そのためには、(foo mut Foo) とする必要があります。

一般的に、V の参照は Go のポインタや C++ の参照と似ています。例えば、ツリー構造の定義はこのようになります。

struct Node<T> {
    val   T
    left  &Node
    right &Node
}

定数

const (
    Pi = 3.14
    World = '世界'
) 

fn main() {
    println(Pi)
    println(World)
}

定数は const キーワードで宣言されます。これらはモジュールレベル (関数の外側) で定義できます。

定数は変更できません。

V の定数はほとんどの言語よりも柔軟です。以下の複雑な値も代入できます。

struct Color {
        r int
        g int
        b int
}

fn (c Color) str() string { return '{$c.r, $c.g, $c.b}' }

fn rgb(r, g, b int) Color { return Color{r: r, g: g, b: b} }

const (
        Numbers = [1, 2, 3]

        Red  = Color{r: 255, g: 0, b: 0}
        Blue = rgb(0, 0, 255)
)

fn main() {
        println(Numbers)
        println(Red)
        println(Blue)
} 

グローバル変数は許可されていないので、これは非常に役立ちます。

定数に名前を付けるときは、すべて snake_case である必要があります。これは変数の分別を促します。

多くの人は定数を TOP_CITIES のようにすべて大文字にすることを好みますが、これは V では動作しません。他の言語よりも定数が強力だからです。複雑な構造を表すことができるうえに、グローバルがないためこれはかなり頻繁に使われます。

println('Top cities: $TOP_CITIES.filter(.usa)')
よりも
println('Top cities: $top_cities.filter(.usa)')

println

println はシンプルかつ強力な組み込み関数です。これは、文字列、数値、配列、辞書配列、構造体など、なんでも出力できます。

println(1) // "1"
println('hi') // "hi"
println([1,2,3]) // "[1, 2, 3]"
println(User{name:'Bob', age:20}) // "User{name:'Bob', age:20}"

自分の型に対してカスタムの出力値を定義したい場合は、.str() string メソッドを定義するだけです。

改行したくない場合は、print() を代わりに使います。

モジュール

V はとてもモジュール方式な言語です。再利用可能なモジュールを作成することは、推奨されており、非常に簡単です。新しいモジュールを作成するには、モジュールの名前のディレクトリを作成してコードを書いた .v ファイルを入れます。

cd ~/code/modules
mkdir mymodule
vim mymodule/mymodule.v
// mymodule.v
module mymodule

// 関数をエクスポートするには `pub` を使用します
pub fn say_hi() {
    println('hello from mymodule!')
}

mymodule/ には好きなだけ .v ファイルを入れることができます。
これは v -lib ~/code/modules/mymodule でビルドできます。

それでは、これをコード内で使ってみます。

module main

import mymodule

fn main() { 
    mymodule.say_hi()
}

注意として外部の関数を呼ぶには毎回モジュールを指定する必要があります。これは煩わしく思えるかもしれませんが、コードはより可読性があり理解しやすいようになります。どのモジュールによるどの関数が呼ばれているのか常に明らかだからです。特に巨大なコードにおいては。

モジュールの名前は 10 文字以下くらいに短くするべきです。循環インポートは許可されていません。

今現在はモジュールをどこにでも作成できます。

すべてのモジュールは静的に単一の実行形式にコンパイルされます。

インポートされた場合に何らかのセットアップ/初期化コードを自動的に呼び出してほしい (専ら C ライブラリの関数を呼び出したい) 場合、以下のようなモジュールの init 関数をそのモジュール内に書きます。

fn init() int {
    // ここにセットアップコード
    return 1
}

init 関数は公開できません。自動的に呼び出されます。

インターフェイス

struct Dog {}
struct Cat {}

fn (d Dog) speak() string {
    return 'woof'
} 

fn (c Cat) speak() string {
    return 'meow'
}

interface Speaker {
    speak() string
}

fn perform(s Speaker) {
    println(s.speak())
}

dog := Dog{} 
cat := Cat{} 
perform(dog) // ==> "woof" 
perform(cat) // ==> "meow" 

型がインターフェイスを実装するには、そのメソッドを実装することでできます。意図を明示的に宣言することはできず、"implements" キーワードもありません。

列挙体

enum Color {
    red green blue 
} 

mut color := Color.red
// V は `color` が `Color` だとわかっています。ここで `Color.green` とする必要はありません。
color = .green 
println(color) // "1"  TODO: "green" と出力する?

Option 型とエラー制御

struct User {
    id int
    name string
} 

struct Repo {
    users []User 
} 

fn new_repo() Repo {
    return Repo {
        users: [User{1, 'Andrew'}, User {2, 'Bob'}, User {10, 'Charles'}]
    }
}

fn (r Repo) find_user_by_id(id int) User? { 
    for user in r.users {
        if user.id == id {
            // V は自動的にこれを Option 型にラップする  
            return user 
        } 
    } 
    return error('User $id not found') 
} 

fn main() {
    repo := new_repo()  
    user := repo.find_user_by_id(10) or { // Option 型は `or` ブロックでハンドルしなければならない
        return // `or` ブロックは `return`, `break`, `continue` のいずれかで終わる必要がある
    } 
    println(user.id) // ==> 10 
    println(user.name) // ==> 'Charles'
} 

V は OptionResult を一つの型に組み合わせているので、どちらを使うべきか決定する必要はありません。

関数を Optional な関数へ「アップグレード」する必要のある作業が山のようにあっても、その変更は小さくなります。戻り値の型に ? を加えて、何かがおかしいときにエラーを返します。

エラーを返す必要が無い場合は、単に none を返します。

これは V でエラーを制御する主要な手段です。これらは値でもあり、Go のようですが、エラーを無視できないという利点があり、かつこれらを制御することはそこまで煩わしくありません。

error ブロックの中で定義され、error() 関数で渡された文字列のメッセージがセットされています。none が返された場合は err は空です。

user := repo.find_user_by_id(7) or {
    println(err) // "User 7 not found"
    return
}

また、以下のようにエラーを伝播することもできます。

resp := http.get(url)?
println(resp.body)

http.get?http.Response を返します。? を付けて呼び出すので、エラーは呼び出した関数から伝播されます。この場合は main でパニックが起こります。

基本的に上記のコードは以下のコードを短くしたものになります。

resp := http.get(url) or {
    panic(err)
}
println(resp.body)

V には Optional を強制的に取り除く (Rust の unwrap() や Swift の ! のような) 方法はありません。代わりに or { panic(err) } を使用します。

ジェネリクス

struct Repo <T> {
    db DB
}

fn new_repo<T>(db DB) Repo<T> {
    return Repo<T>{db: db}
}

// これはジェネリック関数です。V は使用される型ごとに関数を生成します。
fn (r Repo<T>) find_by_id(id int) T? {  
    table_name := T.name // この例では型の名前をテーブル名にしています
    return r.db.query_one<T>('select * from $table_name where id = ?', id)
}

db := new_db()
users_repo := new_repo<User>(db)
posts_repo := new_repo<Post>(db)
user := users_repo.find_by_id(1)? 
post := posts_repo.find_by_id(1)? 

並列計算

並列計算モデルは Go に酷似しています。foo() を並列実行するには、go foo() と呼ぶだけです。即座に新しいシステムスレッド内で実行されます。ゴルーチン (goroutines) とスケジューラーはまもなく実装されます。

JSON のデコード

struct User {
    name string
    age  int 

  // `skip` 属性を使用することで指定のフィールドをスキップできます
    foo Foo [skip]

  // フィールド名が JSON と異なる場合は、名前を指定できます 
    last_name string [json:lastName]  
} 

data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }' 
user := json.decode(User, data) or {
    eprintln('json のデコードに失敗')
    return 
} 
println(user.name)
println(user.last_name)
println(user.age)

JSON は今日ではとても広く使われているため、組み込みで JSON をサポートしています。

json.decode 関数の第一引数はデコードした型です。第二引数は JSON 文字列です。

V は JSON エンコードやデコードするコードを生成します。リフレクションは使われていません。そのため、それなりのパフォーマンスを発揮します。

テスト

hello.v
fn hello() string {
    return 'Hello world' 
} 
hello_test.v
fn test_hello() {
    assert hello() == 'Hello world'
} 

すべてのテスト関数は *_test.v ファイルに配置され、test_ で始まっている必要があります。

テストを実行するには v hello_test.v をします。モジュール全体をテストするには v test 自分のモジュール とします。

assrt キーワードはテスト関数の外側でも使用できます。

メモリ管理

(工事中) ガベージコレクションや参照カウントはありません。V はコンパイル時で解放できるようにします。以下はその例です。

fn draw_text(s string, x, y int) {
    ...
}

fn draw_scene() {
    draw_text('hello $name1', 10, 10)
    draw_text('hello $name2', 100, 10)
    draw_text(strings.repeat('X', 10000), 10, 50)
}

文字列は draw_text より外に出ていないので、これらは関数を抜けるときに解放されます。

実際に最初の 2 つの呼び出しはすべて、結果的にメモリ確保をしていません。これら 2 つの文字列 ('hello''world') は小さいので、V はこれらのために事前に確保されたバッファを使用します。

fn test() []int {
    number := 7 // スタック変数
    user := User{} // スタック上に確保された構造体
    numbers := [1, 2, 3] // ヒープ上に確保された配列、関数を抜けるときに解放される
    println(number)
    println(user)
    println(numbers)
    numbers2 := [4, 5, 6] // 配列は返されるので、ここでは解放されない
    return numbers2
}

Defer

fn read_log() {
    f := os.open('log.txt')
    defer { f.close() }
    ...
    if !ok {
        // defer 文はここで呼び出され、ファイルは閉じられる
        return
    }
    ...
    // defer 文はここで呼び出され、ファイルは閉じられる
}

ORM

(アルファ)

V は組み込みの ORM サポートがあります。Postgres をサポートしており、まもなく MySQL と SQLite もサポートされます。

このアプローチには以下のようなメリットがあります。

  • すべての SQL 方言を一つの構文で。異なるデータベースの統合がより簡単になります。
  • 安全性。もう SQL クエリの構築でインジェクションはありえません。
  • コンパイル時チェック。実行時にしか現れない打ち間違いがなくなります。
  • 読みやすくシンプルに。もう結果を手動でパースしたりオブジェクトを構築する必要はありません。
struct Customer { // 現在、構造体の名前はテーブル名と同じである必要があります
    id int // 整数の id が最初のフィールドでなければなりません
    name string
    nr_orders int
    country string
}

db := pg.connect(db_name, db_user)

// select count(*) from Customer
nr_customers := db.select count from Customer
println('number of all customers: $nr_customers')

// V 構文はクエリの構築に使用できます
// db.select は配列を返します
uk_customers = db.select from Customer where country == 'uk' && nr_orders > 0
println(uk_customers.len)
for customer in uk_customers {
    println('$customer.id - $customer.name')
}

// `limit 1` を加えることで V に一つのオブジェクトしか要らないことを伝えます
customer = db.select from Customer where id == 1 limit 1
println('$customer.id - $customer.name')

// 新しい顧客情報を挿入します
new_customer := Customer{name: 'Bob', nr_orders: 10}
db.insert(new_customer)

vfmt

もうコードフォーマットやスタイルガイドラインを気にする必要はありません。vfmt はこれだけでうまくやってくれます。

v fmt file.v

vfmt が保存するたびに実行されるように、エディタをセットアップすることを推奨します。

コードをプッシュする前に、常に vfmt を実行しましょう。

ドキュメントを書く

やり方は Go と非常に似通っており、とてもシンプルです。コードにドキュメントを書く必要がなければ、vdoc がソースコードから生成します。関数/型/定数のドキュメントはこのように宣言の前に正しく配置されなければなりません。

// clearall は配列の全ビットを消去する
fn clearall() {
}

そのコメントは定義される名前から始まる必要があります。

モジュールの概要は、モジュール名の直後の最初のコメントに配置する必要があります。

ドキュメントを生成するには、v doc モジュール/への/パス を実行します (TODO これは一時的に無効化されています)。

発展的なトピック

V から C 関数を呼び出す

#flag -lsqlite3

#include "sqlite3.h"

struct C.sqlite3 
struct C.sqlite3_stmt 

fn C.sqlite3_column_int(C.sqlite_stmt, int) int 

fn main() {
    path := 'sqlite3_users.db' 
    db := &C.sqlite3{} 
    C.sqlite3_open(path.cstr(), &db)

    query := 'select count(*) from users' 
    stmt := &C.sqlite3_stmt{} 
    C.sqlite3_prepare_v2(db, query.cstr(), - 1, &stmt, 0)
    C.sqlite3_step(stmt) 
    nr_users := C.sqlite3_column_int(res, 0)
    C.sqlite3_finalize(res)
    println(nr_users) 
} 

C 文字列は srting(cstring) で V 文字列へ変換できます。

V から C コードを呼び出す例は、この socket.v を見てください。 https://github.com/vlang/v/blob/master/vlib/net/socket.v

C コード上の問題をデバッグする場合は、v -show_c_cmd . が便利です。これはプログラムのビルドに使用されている C コマンドを出力します。

コンパイル時 if

$if windows {
    println('Windows')  
}  
$if linux {
    println('Linux') 
} 
$if mac {
    println('macOS') 
}

$if debug {
    println('debugging') 
}

コンパイル時 if$ で始まります。現在は OS または -debug コンパイルオプションの検出にしか使用できません。

codegen を介したリフレクション

組み込み JSON サポートはありますが、V は以下のように任意のものに対する効果的なシリアライザを作成することも許可します。

// TODO: 8 月中を予定
fn decode<T>(data string) T {
        mut result := T{}
        for field in T.fields {
                if field.typ == 'string' {
                        result.$field = get_string(data, field.name)
                } else if field.typ == 'int' {
                        result.$field = get_int(data, field.name)
                }
        }
        return result
}

// 以下が生成される
fn decode_User(data string) User {
        mut result := User{}
        result.name = get_string(data, 'name')
        result.age = get_int(data, 'age')
        return result
}

制限付き演算子オーバーロード

struct Vec {
    x int
    y int
}

fn (a Vec) str() string { 
    return '{$a.x, $a.y}' 
}

fn (a Vec) + (b Vec) Vec {
    return Vec {
        a.x + b.x, 
        a.y + b.y 
    }
}

fn (a Vec) - (b Vec) Vec {
    return Vec {
        a.x - b.x, 
        a.y - b.y
    }
}

fn main() { 
    a := Vec{2, 3}
    b := Vec{4, 5}
    println(a + b) // ==> "{6, 8}"  
    println(a - b) // ==> "{-2, -2}"  
}

演算子オーバーロードは、V のシンプルさと可視性の哲学に反しています。しかし科学的かつ視覚的なアプリケーションは V の得意分野なので、演算子オーバーロードは以下のような可読性の向上のためにとても重要です。

a.add(b).add(c.mul(d))a + b + c * d よりとても読みづらいですしね。

安全性と保守性を促進するため、演算子オーバーロードには以下のようないくつかの制限があります。

  • オーバーロードできる演算子は +-*/ だけです。
  • 他の関数を演算子関数の中で呼び出すことはできません。
  • 演算子関数の引数は変更できません。
  • 引数は互いに同じ型でなければなりません (V のすべて演算子と同様です)。

インラインアセンブリ

TODO: まだ実装されていません

fn main() {
    a := 10
    asm x64 {
        mov eax, [a]
        add eax, 10
        mov [a], eax
    }
}

C/C++ を V へ変換する

TODO: C から V への変換は V 0.3 から利用できます。C++ から V へは今年 (2019年) の後半以降になります。

V は C/C++ のコードを可読性のある V のコードへ変換します。まずは簡単なプログラム test.cpp を作成してみましょう。

test.cpp
#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<std::string> s;
    s.push_back("V is ");
    s.push_back("awesome");
    std::cout << s.size() << std::endl;
    return 0;
}

v translate test.cpp を実行すると V は test.v を生成します。

test.v
fn main {
  mut s := []string 
  s << 'V is '
  s << 'awesome'
  println(s.len) 
} 

オンラインでの C/C++ から V への翻訳機はまもなく登場します。

C コードを翻訳したり、V から C コードを呼び出したりする判断はどのようにするべきでしょう?

よく書かれていてよくテストされている C コードであれば、もちろん単に V からこの C コードを呼び出せます。

V へ翻訳することにはいくつかのメリットがあります。

  • そのコード基盤を開発する予定の場合、一つの言語ですべてできているので、C でそれを開発するよりは安全で簡単です。
  • クロスコンパイルがもっと簡単になります。なにも心配することはありません。
  • ビルドフラグとインクルードファイルはもうありません。

ホットコードリロード

message.v
module main

import time
import os

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

fn main() {
    for {
        print_message()
        time.sleep_ms(500)
    }
}

この例を v -live message.v でビルドします。

リロードされてほしい関数には、その定義の手前に [live] 属性をつける必要があります。

現在は実行中に型を変更することはできません。

他に、グラフィカルアプリケーションの例があります。
https://github.com/vlang/v/tree/master/examples/hot_code_reloading

クロスコンパイル

プロジェクトをクロスコンパイルするには、単に

v -os windows .

v -os linux .

を実行します。

(macOS 向けのクロスコンパイルは現在は不可能です。)

C 依存ファイルがなければ、それだけなんです。これは、ui モジュールを使用した GUI アプリや、gg を使用したグラフィカルアプリをコンパイルするときも動作します。

V でクロスプラットフォームなシェルスクリプト

V は Bash の代替として、デプロイスクリプト、ビルドスクリプトなどを書くことに使用できます。

このために V を使うメリットは、言語のシンプルさ、読みやすさ、クロスプラットフォームサポートです。「V スクリプト」は UNIX 系システム上でも Windows と同じようにうまく動作します。

プログラムを #v ディレクティブで始めると、os モジュール内のすべての関数がグローバルになります (例えば os.ls() の代わりに ls() が使えます)。

#v 

rm('build/*') 
// 以下と同じ  
for file in ls('build/') {
    rm(file) 
} 

mv('*.v', 'build/') 
// 以下と同じ  
for file in ls('.') {
    if file.ends_with('.v') { 
        mv(file, 'build/') 
    } 
}

これで、これを通常の V プログラムのようにコンパイルし、どこでもデプロイ & 実行できる実行形式が得られます。

v deploy.v && ./deploy

もしくは伝統的な bash スクリプトのようにそのまま実行します。

v run deploy.v

補遺

I: キーワード

V には 23 のキーワードがあります。

break 
const  
continue 
defer 
else 
enum 
fn
for
go
goto
if
import
in 
interface 
match 
module 
mut 
none
or 
pub 
return
struct
type

II: 演算子

記号 意味 オペランド
+ integers, floats, strings
- integers, floats
* integers, floats
/ integers, floats
% 剰余 integers
& ビット AND integers
| ビット OR integers
^ ビット XOR integers
<< 左シフト integer << unsigned integer
>> 右シフト integer >> unsigned integer

優先度

優先度 演算子
5 * / % << >> &
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

代入演算子

+=   -=   *=   /=   %=
&=   |=   ^=
&&=  ||=
>>=  <<= 
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away