vlang app

Table of Contents

v up

人生で初の体験をします。

ob$ v up
Updating V...
> git_command: git pull https://github.com/vlang/v master
V self compiling ...
V built successfully as executable "v".
> Done recompiling.
> Recompiling vup.v ...
Current V version: V 0.4.9 32ad697, timestamp: 2025-03-06 21:05:01 +0200

あっけない!! 何でvupもやるの? vに内包されてると思ってたけど。。いや、それじゃ v自身の問題が有った場合、v up出来なくなる。だから、独立させてるんだな。 それにしても、TZが +2000 って、どのあたり? 変な事に目が行くな。

ob$ v doctor
|V full version      |V 0.4.9 8dbde18.32ad697
|:-------------------|:-------------------
|OS                  |openbsd, 7.6, GENERIC#1
|Processor           |1 cpus, 32bit, little endian
|Memory              |1.9GB/2.84GB
|                    |
|V executable        |/var/SRC/v/v
|V last modified time|2025-03-06 20:02:49
:

一応、更新されたか、別な角度から確認。ありゃ、昔のバージョンが保持 されてたぞ。

ob$ ./v_old doctor
|V full version      |V 0.4.9 8dbde18.32ad697
|:-------------------|:-------------------
|OS                  |openbsd, 7.6, GENERIC#1
|Processor           |1 cpus, 32bit, little endian
|Memory              |1.89GB/2.84GB
|                    |
|V executable        |/var/SRC/v/v_old
|V last modified time|2025-02-28 21:32:31

この機構って、リナとかでgrubを使って、もしもの時に古いカーネルを起動するのと 一緒な考えだな。

doctor

Memory欄の2つの数値は、何を表わしているの? ソース嫁よだな。 v/cmd/tools/doctor.v

total_memory := f32(runtime.total_memory()) / (1024.0 * 1024.0 * 1024.0)
free_memory := f32(runtime.free_memory()) / (1024.0 * 1024.0 * 1024.0)
if total_memory != 0 && free_memory != 0 {
        a.line('Memory', '${free_memory:.2}GB/${total_memory:.2}GB')
} else {
        a.line('Memory', 'N/A')
}

更に深堀します。 free_memory とかは、どうやって収集してる? モジュールなんで そこに場所を移します。

ob$ cd vlib/runtime/
ob$ ls
README.md                               free_memory_impl_openbsd.c.v
free_memory_impl_darwin.c.v             runtime.v
free_memory_impl_default.c.v            runtime_nix.c.v
free_memory_impl_freebsd.c.v            runtime_test.v
free_memory_impl_linux.c.v              runtime_windows.c.v

どんなOSがサポートされてるか丸見えですなあ。勿論OpenBSDのファイルを参照 してみます。

module runtime

fn free_memory_impl() usize {
        $if cross ? {
                return 1
        }
        $if !cross ? {
                $if openbsd {
                        page_size := usize(C.sysconf(C._SC_PAGESIZE))
                        av_phys_pages := usize(C.sysconf(C._SC_AVPHYS_PAGES))
                        return page_size * av_phys_pages
                }
        }
        return 1
}

ここで利用されてる C._SC_AVPHYS_PAGES は、多分、C語で定義されてるsysctl関連 だよと推測される。んでもって、sysctl.hあたりを見ると

ob$ grep page sysctl.h
#define HW_PAGESIZE              7      /* int: software page size */

関係しそうなのは、こんなのしか引っかからないし、直接的に引くと

ob$ sysctl -a | grep page
vm.nkmempages=32768
hw.pagesize=4096

こんなのが出てくる。本当の所は、どうなっているのだろう?

test

testコードが付属してたんで、実行してみた。

ob$ v test .
---- Testing... ----------------------------------------------------------------
 OK       6.686 ms /var/SRC/v/vlib/runtime/runtime_test.v
--------------------------------------------------------------------------------
Summary for all V _test.v files: 1 passed, 1 total. Elapsed time: 2719 ms,
 on 1 job. Comptime: 2689 ms. Runtime: 6 ms.

tccが導入されていないので、多大な時間がかかっている。それには目をそむ けておいて、絶対に目を逸らしてはならないのは、コードの中身だ。非常に良質な サンプル・コードが提示されてるからね。

import runtime

fn test_physical_memory() {
        total := runtime.total_memory()
        free := runtime.free_memory()
        println('total memory: ${total}')
        println('free  memory: ${free}')
        assert total > 0 && free > 0
}

もう、関数名をmainに変更して、使って下さいと言わんばかりですよ。

ob$ v run mem.v
total memory: 3047378944
free  memory: 1754173440

gdb出来る様に、-g付きでコンパイルして走らせる。

(gdb) r
Starting program: /tmp/q/q

Breakpoint 1, main (___argc=1, ___argv=0xcf7cae04)
    at /tmp/v_1000/q.01JNPTJ31GSX3KTAJKJ745RCAZ.tmp.c:12226
12226           g_main_argc = ___argc;
(gdb) n
12227           g_main_argv = ___argv;
(gdb)
12229           GC_set_pages_executable(0);
(gdb)
12230           GC_INIT();
(gdb)
12232           _vinit(___argc, (voidptr)___argv);
(gdb)
12233           main__main();
(gdb)
total memory: 3047378944
free  memory: 1779781632
12234           _vcleanup();

全体の流れが判明したので、局所的に追跡。

Breakpoint 2, main__main () at /tmp/q/mem.v:4
4               total := runtime.total_memory()
(gdb) s
runtime__total_memory () at vlib/runtime/runtime_nix.c.v:12
12              page_size := usize(C.sysconf(C._SC_PAGESIZE))
(gdb)
_libc_sysconf (name=28) at /usr/src/lib/libc/gen/sysconf.c:67
67              len = sizeof(value);
(gdb)
70              switch (name) {
(gdb)
134                     if (_pagesize != 0)
(gdb)
477     }
(gdb)

定数が28番になってcallされた。

runtime__total_memory () at vlib/runtime/runtime_nix.c.v:13
13              phys_pages := usize(C.sysconf(C._SC_PHYS_PAGES))
(gdb)
_libc_sysconf (name=500) at /usr/src/lib/libc/gen/sysconf.c:67
67              len = sizeof(value);
(gdb)
70              switch (name) {
(gdb)
444                     mib[0] = CTL_HW;
(gdb)
445                     mib[1] = HW_PHYSMEM64;
(gdb) n
446                     len = sizeof(physmem);
(gdb)
447                     if (sysctl(mib, namelen, &physmem, &len, NULL, 0) == -1)
(gdb)
449                     return (physmem / getpagesize());
(gdb)
477     }

次は500番か。そして儀式が有って、カーネル用に再組立とな。 ソースをゆっくり鑑賞してみると、こんな風になってた。

/*
 * sysconf --
 *      get configurable system variables.
 *
 * XXX
 * POSIX 1003.1 (ISO/IEC 9945-1, 4.8.1.3) states that the variable values
 * not change during the lifetime of the calling process.
 :
/* 1003.1b */
        case _SC_PAGESIZE:
                if (_pagesize != 0)
                        return (_pagesize);
                mib[0] = CTL_HW;
                mib[1] = HW_PAGESIZE;
                break;

一応関係者の合意の元に、コード化されてるのね。だから皆が アクセスできるヘッダーファイルに定義しときましたとな。

vm$ cd /usr/include/
vm$ grep _SC_PAGESIZE -r .
./unistd.h:#define      _SC_PAGESIZE            28
./unistd.h:#define      _SC_PAGE_SIZE           _SC_PAGESIZE    /* 1170 compatibility */

さて、もう少しvの根幹を探る。

ob$ v -o mem.c mem.v
ob$ wc mem.c
    6428   23289  219444 mem.c

ちゃんと調査するなら、C語に変換された(面倒この上ない)コードを調べるのさ。 疲れる事請け合いだけどね。

usize runtime__total_memory(void) {
        usize page_size = ((usize)(sysconf(_SC_PAGESIZE)));
        usize phys_pages = ((usize)(sysconf(_SC_PHYS_PAGES)));
        return (usize)(page_size * phys_pages);
}

usize runtime__free_memory(void) {
        return runtime__free_memory_impl();
}

VV_LOCAL_SYMBOL void main__main(void) {
        usize total = runtime__total_memory();
        usize __v_free = runtime__free_memory();
	:

ああ、そうでもないか。

おまけで、作成した mem.c から実行バイナリーを手動作成する方法。gcが 曲者なのよね。(gc.h and libgc.a)

cc mem.c -I/var/my/srcs/v/thirdparty/libgc/include -L/var/my/srcs/v/thirdparty/tcc/lib -lgc

by python

何か軽いvlangのアプリを作成してみたい。そこで思いついたのが、前前回だかにやった、 箱ひげ図を書く時の、外れ値を除去する部分。これだけをアプリにしたい。

import numpy as np
import matplotlib.pyplot as plt

# データを読み込む(空白またはタブ区切り)
data = np.loadtxt("scall.log")

# time と offset のデータを取得
time_values = data[:, 0]
offset_values = data[:, 1]

# IQRを用いた外れ値除外関数
def remove_outliers(data):
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return data[(data >= lower_bound) & (data <= upper_bound)]

# 外れ値を除外
filtered_time = remove_outliers(time_values)
filtered_offset = remove_outliers(offset_values)

vlang app

どんな名前にしよう。matzさん曰く、名前重要、これを決めるのが第1歩です。 データのうちの外れ値を除外なんだから、 バリ取りなんてのを思いつくな。それとも純日本的に出る杭(データ)は打たれるとか。 あるいは、はみでる(データ)ものは村八分とか。村八分が、オドロオドロしてて 面白そう。これを国際的に通用する名前に昇華させよう。

単純に、m8 とでもするか。イギリスの諜報機関である、M6 の上を行くデータの 暗殺者(ジェームス・ボンド)みたいで格好いいぞ。

仕様

軽く考えておくと

  1. 一つのデータ列のファイル名を入力とする
  2. 1.5 * IQR 以上(以下)のデータを除去した、ファイルを作成
  3. 元ファイルは改名する(file.org)する

こうしておけば、diffとかで、何が除去されたか容易に判別できる。個人使用なので エラーチェックは、なるべくサボル。

資料

言語なんで真似する事で上手になる。生れてきた赤ちゃんが、いきなりしゃべる訳は ない。ママやパパや爺婆の真似をして、段々しゃべれるようになっていく。

前回読書したファインマンさんの本にも、そんな事が登場してたぞ。外国語を 習得するなら、生きた辞書を手に入れろ。生きた辞書ってのは男性ならガールフレンドの 事ね。何冊手に入れた? なんて話が堂々と語られていた。

韓国へ長期出張の某。メキメキ韓国語の腕を上げた。同僚は、あいつスゲーなーと 褒めちらす。一方、韓国人の社員は、彼の言動を聞いてニヤニヤ。何デ? だって、某のしゃべる言葉が、女言葉ばっかりだったから。夜の日韓親善もホドホドに。

sakae@lu:v$ ls examples/word_counter
README.md  cinderella.txt  word_counter.v
sakae@lu:v$ ls vlib/math/stats/
stats.v  stats_test.v

word_counter.v は、骨格の作成例。 stats_test.v は、統計属のうちの四分位点 を求める例だ。

doc

本家を見れば、それで済む事だけど、ネットが不通になった場合でも、大丈夫。 手元でドキュメンテーションを読めるよと。v doc してみると、vlib内のdir名が 単に列挙されるだけ。これで当たりをつけるんだな。

Available modules:
==================
arrays
arrays.parallel
benchmark
 :
v.gen.golang
v.gen.js
v.gen.js.sourcemap
v.gen.js.sourcemap.vlq
v.gen.native
v.gen.wasm
v.gen.wasm.serialise
 :
vdoc: No valid V files were found. Use the `-m` flag when generating docs from a directory that has multiple modules.

色々な言語に翻訳できる様に誠意努力中ですね。

sakae@lu:tmp$ v doc math.complex | grep angle
fn (c Complex) angle() f64

普通にテキスト処理なんで、こういう使い方もできる。ブラウザー上で やるには、ちと億劫な仕事だ。

sakae@lu:tmp$ v doc -o ZZ.html math.stats
Generating html in "ZZ.html/doc.css"
Generating html in "ZZ.html/normalize.css"
Generating html in "ZZ.html/doc.js"
Generating html in "ZZ.html/dark-mode.js"
Generating html in "ZZ.html/math.stats.html"
Creating search index...
Copying favicons...
sakae@lu:tmp$ w3m ZZ.html/math.stats.html

でも、これが普通の利用方法かな。

Playground

本家に遊び場なんてのが用意されてる。どんな物かRunボタンを押してみる。

Hello, Playground!

build logに切り替えると、こんなのが出てきた。

OK (0.502 sec real, 0.502 sec wall)

ならば、ちょいと追加を試してみると、

Running code...
Can't run code. The server returned an error:
code.v:15:1: error: invalid expression: unexpected keyword `import`
   13 | // Join us on Discord: https://discord.gg/vlang
   14 | // Enjoy!
   15 | import readline { read_line }
      | ~~~~~~
   16 | 
   17 | input := read_line('What is your name: ')!
Exited with error status 1
Please try again.

危険な行為は許しませんと出てきた。これじゃ何もできないじゃん。

repl

そこで、replですよ。引数無しで起動すると、説明のバナーが出てきて、待ちになる。

>>> import readline
>>> dare := readline.read_line('Your name: ')!
Your name: sakae

玩具の範疇を越える本格的な遊び場。 rubyで言うirb 機能がコンパイラー言語のくせに、ちゃんと用意されてる。昔のLisp環境を彷彿 させるな。

核になりそうな、四分位点を求めるやつを確認しとく。

import math
import math.stats

fn test_quantile() {
        // Assumes sorted array
	
        // test for int, i64, f32 array
        assert stats.quantile[int]([1, 2, 3], 1)! == 3
        assert stats.quantile[i64]([i64(1), 2, 3], 1)! == 3
        o = stats.quantile[f32]([f32(1.0), 3, 5, 7], 0.22)!
        assert math.alike(o, 2.319999933242798)

注意点がコメントされてる。与える配列はソートしとけ。使い辛いな。いや、これで いいのです。内部でソートしちゃうってやり過ぎだろう。ソートなんて時間がかかり そうなんで、ユーザーに委ねるのが正解。場合によってはソート済みのものも有る かも知れない。そんなデータをまたこの関数内でソートしちゃうと、場合によっては 大変な時間がかかる事がある。データの性質を知ってるのはユーザーだけだからね。 ちゃんとソートしたデータを渡してってのは、真っ当な判断だ。

>>> import math
>>> import math.stats
>>> stats.quantile[int]([1, 2, 3], 1)!
3
>>> stats.quantile[int]([1, 2, 3, 4, 5, 6, 7, 8, 9], 0.5)!
error: cannot use `float literal` as `int` in argument 2 to `math.stats.quantile`
    8 |
    9 | println(stats.quantile[int]([1, 2, 3], 1)!)
   10 | stats.quantile[int]([1, 2, 3, 4, 5, 6, 7, 8, 9], 0.5)!
      |                                                  ~~~

扱かうデータを少し増量して、その中点を求めようとしたら、エラーだよ。何処に トラップが有るか判ったものじゃない。

>>> stats.quantile[f64]([f64(1.0), 2, 3, 4, 5, 6, 7, 8, 9], 0.5)!
5.0
>>> stats.quantile[f64]([f64(1.0), 2, 3, 4, 5, 6, 7, 8, 9], 0.25)!
3.0

例に習って変更してみた。この関数をそのまま使う限り、データは、浮動小数点 で与える必要があるな。

modify ?

ある意味、使い難い関数が、どう定義されてるか、眺めて億。

// quantile calculates quantile points
// for more reference
// https://en.wikipedia.org/wiki/Quantile
pub fn quantile[T](sorted_data []T, f T) !T {
        if sorted_data.len == 0 {
                return T(0)
        }
        index := f * (sorted_data.len - 1)
        lhs := int(index)
        if lhs < 0 || lhs >= sorted_data.len {
                return error('index out of range')
        } else if lhs == sorted_data.len - 1 {
                return sorted_data[lhs]
        } else {
                if lhs >= sorted_data.len - 1 {
                        return error('index out of range')
                }
                delta := index - T(lhs)
                return T((1 - delta) * sorted_data[lhs] + delta * sorted_data[(lhs + 1)])
        }
}

結局、配列の何番目ってのを求めるのが仕事なんで、この関数を一寸改造すれば、 何とかなりそうな気がする。

おまけで、皆大好き平均の算出法を見て億。

// mean calculates the average
// of the given input array, sum(data)/data.len
// Based on
// https://www.mathsisfun.com/data/central-measures.html
pub fn mean[T](data []T) T {
        if data.len == 0 {
                return T(0)
        }
        mut sum := T(0)
        for v in data {
                sum += v
        }
        return T(sum / data.len)
}

型に追従してくれるって、エェーと何って言うんだっけ?

又、長くなってしまった。次回に続く。

README

歴史的プレゼン なんて本を読んだ。

そんなプレゼンなんて、仕事をしてなきゃ関係無いでしょ。そんな事は無いと 著者は言う。例えばプレゼンの原点である自己紹介。これなら今後も有りそうだな。 何かのサークルに加入した時とかの自己紹介。あるいは、某勉強会でrubyはpythonよりも ずっとお得ですってアッピールする場合とか。

python -> ruby を訴えるプレゼンなら、

pythonって言うオドロオドロしい蛇の写真を用意、気持悪いですネェ。一方 rubyと言う 輝かしい宝石の写真を用意。これなら持ってると値上りして将来も安泰とかね。 これをプレゼンの随所に挟み込み、観衆を熱狂させる事。これで成功間違いなし。

沢山の事は言うな。せいぜい3つの事柄について述べよ。大事な事は7回繰り返せ。 プレゼンの技法は、通販の喋りに沢山利用されてるぞ。ジャパネットの未来を 訴える話法は超優秀とな。


This year's Index

Home