The V Programing Language の基礎部分


初めに

本日?(June 20th)に使用可能になるという事で、Documentationを読んで基礎部分を書きだしたいなと思いました。
https://vlang.io/docs

何か参考にしていただければ嬉しいです。

仕様

安定した実行のために以下のことが仕様として定義されています。

  • null無し
  • グローバル変数が使えない
  • 未定義な変数があってはいけない
  • 未定義な関数があってはいけない
  • variable shadowingが禁止
    • ある狭いスコープのものを作成した際に定義した変数に関して、そこよりも広いスコープのもので定義されている変数と同名のものがある場合に発生する。
  • Bounds checking
    • 配列の要素にアクセスする際に、配列要素が実際に存在することを確認すること
  • 変数やstruct形のイミュータブル性
  • ジェネリクス (Cとかにあるやつと変わらないのかな?)
  • メソッドのみ変更可能
  • 関数はデフォルトで純粋関数である [mutというキーワードを用いて、例外を発生]
    • (戻り値が引数によってのみ決定されるという意味)

Hello World

Cやその類の言語と同じく、mainがエントリーポイントとなります。

しかし、scriptのような1つのファイル内で処理が書き終わる場合はfn main()を省略できます。
すなわち、以下のようなものを実行してもちゃんと動きます。

sample.v
println('hello world')

コメント

以下の2パターンでなされます。

複数行のコメントをする際、ネストもできるそうです。

// This is a single line comment.

/* This is a multiline comment.
   /* It can be nested. */
*/

関数

関数はfnで定義します。
fn (変数名 型) 戻り値の型 {} の形で定義していきます。

関数はオーバーロードできません。
main()の後に定義した変数であっても、main()の中で呼び出すことができます。

fn main() {
    println(add(77, 33))
}

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

複数の引数を持つ場合に、型が等しければまとめて書くことができます。

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

変数

変数は := でのみ宣言と初期化が行われます。
Goでいえば、型推論しか変数を宣言できないという事ですね。

ほかの型に変換する場合は、Type(v) と書くことで可能です。

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

変数の値を変更するには = を使用します。
V での変数はデフォルトでイミュータブルとなっているため、変数の値を変えられるようにするには、mut をつけて宣言します。

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

基本の型

bool
string
i8  i16  i32  i64
u8  u16  u32  u64 

byte // alias for u8  
int  // alias for i32  
rune // alias for i32, represents a Unicode code point  

f32 f64

CやGoとは違って、intは32ビット整数となることに注意してください。

String型

文字列は読み出し専用のバイトの配列となっており、イミュータブルです。

文字列の中で、変数を呼ぶ場合、$を用います。
文字列を連結する場合、 +を用います。

name := 'Bob' 
println('Hello, $name!')  // `$` is used for string interpolation 
println(name.len) 

bobby := name + 'by' // + is used to concatenate strings 
println(bobby) // ==> "Bobby"    

//$ の使い勝手の良さ
age := 20
println('age = ' + age) //string + int なのでcompileされない
println('age = $age')   //compaileされる

Array型

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

<< は配列の末尾に値を追加する演算子です。(mut でarrayを定義しないと追加できなさそう)
len フィールドは配列の長さを返します。
val in array メソッドは配列に val が含まれていれば true を返します。

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

mut names := ['John']
names << 'Peter' 
names << 'Sam' 

println(names.len) // ==> "3" 

println('Alex' in names) // ==> "false" 

map型

map型であり、keyがstring のものに関してのみ、「変数宣言だけをする」という事ができる。
存在しないkeyを呼び出すと、valueの型の初期値が返されるみたいです。

mut m := map[string]int{} 
m['one'] = 1
println(m['one']) // ==> "1"  
println(m['bad_key']) // ==> "0" 

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') 
} 

このように、表すこともできます。

num := 777
s := if num % 2 == 0 {
    'even'
} else {
    'odd'
}

println(s) // ==> "odd"

in

inは arrayの中に指定した要素が含まれているかをチェックします。

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

以下の書き方を用いて、ifの条件を簡易化できます。

if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult {
    ... 
} 

↓↓↓

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

forループ

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

inの前で変数を1つ定義した場合、array内の要素が代入されていきます。
inの前で変数を2つ定義した場合、1つ目がindex, 2つ目が要素となります。

numbers := [1, 2, 3, 4, 5]
for num in numbers {
    println(num)
}

names := ['Sam', 'Peter']
for i, name in names {
    println('$i) $name')  // Output: 0) Sam
}             

whileのようなものを書きたい場合、以下のように書きます。

mut sum := 0
mut i := 0
for i <= 100 {
    sum += i
    i++
}
println(sum) // ==> "5050" 

for の後に何も書かなければ無限ループになります。

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

Switch

Switchも特に変わった内容はありませんでした。
caseで条件を指定し、どれにも当てはまらなければdefaultの中が実行されます。

os := 'windows' 
print('V is running on ')
switch os {
case 'darwin':
    println('macOS.')
case 'linux':
    println('Linux.')
default:
    println(os) 
}

Structs

Goと全く同じ作りですね。
インスタンスを定義して、後にやるmethodと組み合わせて力を発揮するものです。

struct Point {
    x int
    y int 
} 

p := Point{
    x: 10 
    y: 20 
} 
println(p.x)

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

pointer := &Point{10, 10} 
println(pointer.x)

Access修正子

構造体のフィールドはデフォルトで非公開かつイミュータブルです (構造体自体もイミュータブル)。
しかし、 pubmut を用いることで変更することができます。

struct Foo {
    a int     // 非公開, イミュータブル (デフォルト) 
mut: 
    b int     // 非公開, ミュータブル
    c int     
pub: 
    d int     // 公開, イミュータブル (読み取りのみ) 
pub mut: 
    e int     // 公開, 親モジュールにおいてのみミュータブル 
pub mut mut: 
    f int     // 公開, 親モジュールの内側及び外側でミュータブル 
}            

メソッド

こちらもGoと全く同じですね。

レシーバ(どのstructに使わせるかを決めるもの)の引数リストは fn とメソッド名の間で指定します。
fn (インスタンス名 struct名) 関数名(変数 型) 戻り値の型 {} という書き方です。

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"

純粋関数

これは、戻り値が引数によってのみ決定されるという意味です。
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"  

メソッドでなく、普通の関数でも同様のことができます。

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]"

定数

定数は const で宣言されます。
定数は必ずすべて大文字でなければなりません(変数との差別化)

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

println(PI)
println(World)

Module

Goのpackageと同じです。
モジュール名にしたい名前でディレクトリを作成し、コードを書いた .v ファイルを入れます。

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

mymodule.v
module mymodule

pub fn say_hi() {
    println('hello from mymodule!')
}

作成したmoduleは、importで呼び出します。

module main

import mymodule

fn main() {
    mymodule.say_hi()
}

Interfaces

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()) } 

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

Enums

enum Color {
    red green blue 
} 

mut color := Color.red
color = .green // `Color.green` とする必要がない
println(color) // ==> "1" 

Option/Result types と error制御

戻り値の型に ? を加えて、何かがおかしいときにエラーを返します。

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 {
            return user 
        } 
    } 
    return error('User $id not found') 
} 

fn main() {
    repo := new_repo() 
    user := repo.find_user_by_id(10) or { return } 
    println(user.id) // ==> "10"  
    println(user.name) // ==> 'Charles'
}

ジェネリクス

struct Repo⟨T⟩ {
    db DB
}

fn new_repo⟨T⟩(db DB) Repo⟨T⟩ {
    return Repo⟨T⟩{db: db}
}

// This is a generic function. V will generate it for every type it's used with. 
fn (r Repo⟨T⟩) find_by_id(id int) ?T {  
    table_name := T.name // in this example getting the name of the type gives us the table 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)? 

並列処理

foo() を並列実行するには、go foo() と呼ぶだけということで、goroutineが実装されるのでしょうか。