概要
vlangに興味を持った時に軽く押さえておきたい言語仕様のさわりをコミュニティ文書に従って書いてみる。
vlangがgolangに似ているため、他言語プログラマがgolangの基本を押さえる為のまとめの形式を踏襲させていただいた。感謝。
公式ドキュメント
https://vlang.io/docs
(まだまだアップデートされそう。)
コミュニティ文書
今回参考にしたもの(y分で学ぶv)。
- メモがてらコメントを日本語にした(少しだけ、現バージョンに合わせ修正)
https://github.com/kmry/learn_v_in_y_minutes/blob/master/learnv_jp.v
...勉強させてもらったご報告がてら、プルリク出してます。
もっとよさげな文書(例で学ぶv)
- 日本語訳があった(内容ほぼ同じ、この記事書いてから存在を知った、、が、ありがたい。)
https://github.com/v-community/v_by_example/blob/master/jp/README.md
vlang の特徴
- シンプルな言語仕様、セミコロンなし
- .v の拡張子を持ちC言語へとコンパイルされる
- コンパイル・実行が高速
- Golangにインスパイアされている
- 並行プログラミングに強い(ことを目指しています。)
インストール
vlangのgithubリポジトリからcloneしてmakeなど。
https://github.com/vlang/v
windowsではwslを使えば同様(以下,例)。
https://qiita.com/e-a-st/items/a212318497c93966a651
ビルドと実行
# ビルド
$ v build
# ビルド + 実行
$ v run
サンプル全体は、
コメント
// コメント
/* */
データ型
Vの基本的なデータ型:
bool - true/false
string - 'hello' *utf-8 encoded*
i8 i16 int i64 i128[WIP] - signed integers of 8, 16, 32, 64, and 128 bits
byte u16 u32 u64 u128[WIP] - unsigned integers of 8, 16, 32, 64, and 128 bits
f32 f64 - floating point numbers of 32 and 64 bits
rune - unicode code point (ascii charのUnicode版)
byteptr
voidptr
注:intは常に32ビット
スコープ、可変
pub/mutは以下の意味
- ディフォルトは、非公開(private)
- pubを付けた場合、公開となる。
- 変数・定数・関数全てに言える事。
- 変数のディフォルトは不変(immutable)
- mutを付けた場合、可変の変数となる。
[1] グローバルスコープで定義されるもの
1-1. 定数
例:
/*
プログラムの定数はモジュールレベル(関数の外)で、「const 」構造体として定義します。
- 定数の型指定にはPascalCaseを使用することを勧めます。
- constは他の言語よりも(寛大で)柔軟です。
- グローバル変数に替え、const構造体に複雑なデータ型を作成することができます。
*/
const (
hello = 'hello'
world = 'world'
age_of_world = 42
)
1-2. 構造体
- 構造体では、複数の値を意味のあるまとまりとして新しい型を定義する事ができる。
// 構造体を定義
//Cと同じく、構造体により、異なるデータ型のグループを1つの論理型として定義できます。※pubにより公開
struct Address {
pub:
street string
city string
state string
zip int
}
1-3. 関数
① 関数宣言(golangに近しい形式):
fn 関数名(引数のリスト) 返り値のリスト {
関数の本体
}
- 引数の型は必須
- 返り値の型は必須
// パラメータを個別に宣言できます。
fn test_address(street string, city string, state string, zip int) Address {
return Address{street : street, city : city, state : state, zip : zip}
}
// または、タイプ別にグループ化できます。
fn test_address2(street, city, state string, zip int) Address {
return Address{street : street, city : city, state : state, zip : zip}
}
// 追記予定: 複数の返り値を返す事ができるかを検証
② 構造体を引数に取るメソッド
(関数の一種で良い、はず。)
- vlang ではオブジェクト志向プログラミングで出てくるクラスやその中で定義されるメソッドはサポートしていない。
- vlang のメソッドは構造体などのデータ型に紐付いた関数になっていて、データ型の定義の中ではなく、データ型とは別に書く
- 構造体とメソッド(関数)の紐付けには レシーバ(receiver) を使う
/*
構造体は、Golangと同じく、メソッドと呼ばれる特殊な関数を持つことができます。
メソッドは通常の関数と同様ですが、(構造体を示す)特別なreceiverを持ちます。
以下は、上のAddressを引数にとるメソッド。
レシーバ引数のパラメーター名は短くとのこと(通常は1文字、以下ではa)。
*/
fn (a Address) str() string {
return 'Address.str(): $a.street, $a.city, $a.state $a.zip'
}
[2] 関数スコープで定義されるもの
2-1 変数
- 変数は可。ディフォルトで不変。
いくつかの定義パターン
// 変数宣言と代入を一緒にする(ディフォルトは不変)
msg := "Super Hello"
struct Point {
x_coor int
y_coor int
}
fn test_out_of_order_calls() {
// 他の多くの言語とは異なり、変数は関数スコープでのみ定義できます。
point := Point{x_coor : 2, y_coor : 2}
// := 記号で、変数を初期化
x_diff, y_diff, distance := point.dist(point1)
// 変数は、mutをつけると可変になります。
mut point1 := Point{}
// = は、可変の値への代入
point1 = Point{x_coor : 1, y_coor : 1}
println('difference in:\nx_coors = $x_diff, y_coors = $y_diff\nthe distance between them is ${distance:.2f}')
}
2-2. 条件分岐
① if式
if-elseは、Golangに似ていますが、値を返す(式)。
if some_condition { // ※条件は括弧では囲みません。
some_conditionが真である場合に実行される文 // 文は、中括弧で囲います。
}
else if some_other_condition {
some_conditionが偽かつsome_other_conditionが真である場合に実行される文
}
else {
いずれの条件でもない場合に実行される文
}
② match文
match文は、他の言語のswitch文に類似。
fn conditional_example() {
a := 15
b := 35
if b == 2*a {
println('b ($b) is twice the value of a ($a)')
}
else if a > b {
println('a ($a) is greater than b ($b)')
}
else {
println('a ($a) is less than or equal to b ($b)')
}
// if-else節は式として、評価結果を変数に格納できます。
mult_of_3 := if a % 3 == 0 {
'a ($a) is a multiple of 3'
}
else {
'a ($a) is NOT a multiple of 3'
}
println(mult_of_3)
c := `c` // cchange this to see other results
mut x := ''
//match文(他の言語のswitch文に類似)。
match c {
`a` {
println('${c.str()} is for Apple')
x += 'Apple'
}
`b` {
println('${c.str()} is for Banana')
x += 'Banana'
}
`c` {
println('${c.str()} is for Cherry')
x += 'Cherry'
}
else {
println('NOPE')
}
}
println(x)
}
2-3 ループ(forが全て)
- goとpythonの中間的for 文(whileはない)。大別して3種類。
fn for_ex(m map[string]f64 ) str() string {
mut result := ''
// forループには、いくつかの形式が使用可能です ※V には、whileループはない。
mut count := 0
num_keys := m.size
println('Number of keys in the map $num_keys')
// 形式1 forのみを記すと、無限ループとなります。
for {
count += 2
println(num_keys.str())
if count == num_keys - 1 {
// break文に到達するまず実行されます(またはcompがリソースを使い果たすまで:])
break
}
else if count == 6{
// continueステートメントはループの次へとスキップします。
continue
}
result += 'Count is $count'
}
//形式2 標準的なforループ
for i := 1; i <= 10; i++ {
if i % 2 == 0 {
println('i ($i) is even')
}
}
//形式3-1 (pythonと同様の)for...in... ※他言語のforeachのようにふるまいます
for val in [1,2,3] {
result += '$val '
}
result += '\n'
//形式3-2 (pythonと同様の)for key, val in... ※foreachの特別版
for key, val in m {
result += 'key: $key -> value: $val '
}
// the last one is very handy for maps or when the index in arrays is needed
return result
}
2-4.例外処理
- Option型(以下では、
?DivisionResult
)とor句で実現
struct DivisionResult {
result f64
}
/*
戻りの型指定の前に?を付けるとOption型となります(vでの標準的なエラー処理の仕組み)
*/
fn divide(a, b f64) ?DivisionResult {
if b != 0 {
return DivisionResult {result : a/b }
}
return error('Can\'t divide by zero!')
}
fn error_handling_example() {
x := f64(10.0)
y := f64(0)
z := f64(2.5)
fail := divide(x, y) or {
// 「or 」句において、エラー発生時の状況を特別値errはを介して取得できます:
// (err is a special value for the 'or' clause that corresponds to the text in the error statement)
println(err)
return
// 「or 」句はreturn、break、または、continue文で終わる必要があります。
// ※break、continueはforループ内で、と中断、続行として使用
}
// succeedには成功時の値が入る
succeed := divide(x, z) or {
println(err)
return
}
println(succeed.result)
}
2.5 その他、便利機能
① in演算子
- 要素が配列またはmapのメンバであるかどうかをチェックするために使用
fn in_example() {
arr := [1,2,3,5]
// 指定した要素(4)が、配列内の要素に存在するかどうかを調べます。
x := if 4 in arr {
'There was a 4 in the array'
}
else {
'There was not a 4 in the array'
}
println(x)
m := {'ford' : 'mustang', 'chevrolet' : 'camaro', 'dodge' : 'challenger'}
// 指定した要素('chevrolet')が、map内の要素に存在するかどうかを調べます。
y := if 'chevrolet' in m {
'The chevrolet in the list is a '+m['chevrolet']
}
else {
'There were no chevrolets in the list :('
}
println(y)
}
②defer
-defer文を使用して、周囲のコードが終了した後に実行されるコードを宣言できます。
fn defer_example() {
mut a := f64(3)
mut b := f64(4)
// このブロック内のすべてのものは、スコープ内のコードの実行が完了するまで実行されません。
defer {
c := math.sqrt(a+b)
println('コードが完了したので最後に実行: The hypotenuse of the triangle is $c')
}
// これらは上記の文より前に実行されます。
a = math.pow(a, 2)
b = math.pow(b, 2)
print('square of the length of side A is $a')
println(', square of the length of side B is $b')
}
[3] データ構造
3-1.文字列、補完文字列
fn string_example() {
// cahr文字はバックティック( ` )で表されます
a_char := `a`
//既出の通りが、補間文字列(interpolated strings)が、ディフォルトで利用できます(変数のみの場合、直接補間:例 $x)。
println('The ascii value of this char is: $a_char')
// 高度な補間は、${$補完対象}の形式となります。
println('The char is: ${a_char.str()}')
// 必要に応じ、+による連結も使用できます。
mut concat := 'b' + a_char.str() + 'dnews be' + a_char.str() + 'rs'
print(concat)
// use += 文字列への追加
concat += '_appended'
println(', $concat')
}
3-2.配列
fn arrays_example() {
// 配列は、単一のデータ型のコレクションです
mut fruits := ['apple', 'banana', 'cherry']
// データ型は、最初に含まれる要素の型によって決まります。
println(fruits)
// << により、mutな配列の末尾に追加します。
fruits << 'kiwi'
println(fruits)
// 配列の領域は、事前に割り当て可能です。
ben_10 := ['ben'].repeat(10)
// 配列の長さは .len によって取得します。
// 配列名[インデックス]の形式で、インデックス番目の要素を取得します(インデックスは0始まり)。
println('There are ${ben_10.len} occurrences of ${ben_10[0]} in \n'+ben_10.str())
}
3-3.マップ(map)
他の多くの言語の辞書(pythonでいうdict)のように機能。いわゆる射影関係。
fn maps_example() {
// mapは、他の多くの言語の辞書(pythonでいうdict)のように機能します。
mut my_dict := map[string]f64 // 現時点の制限: 文字列のみをキーとして受け付けます。
my_dict['pi'] = 3.14 // 値には、任意の型が使用できます。
my_dict['tau'] = 6.28
my_dict['e'] = 2.72
println(my_dict.str())
println("alt_dictの例: キーと値のペアを、この代替初期化フォームで定義できます。")
alt_dict := {'a' : 1.1, 'b' : 2.2, 'c' : 3.3}
println(alt_dict.str())
}
終わりに
初心者なので、ミスあるかも。変なのあったらご指摘ください。
TODO
(もう少しバージョンが上がったら挑戦かな)
- インタフェース
- 純粋関数、高階関数
- generic
- 並列処理
- スライス