m8

Table of Contents

.vmodules/.cache/

rustとかだと、隠しdirが非常に速く肥大化してく。vlangの場合はどうよ?

ob$ cd .vmodules/.cache/
ob$ ls
4a/        86/        92/        9e/        README.md  a9/
ob$ cat README.md
This folder contains cached build artifacts from the V build system.
You can safely delete it, if it is getting too large.
It will be recreated the next time you compile something with V.
You can change its location with the VCACHE environment variable.

キャッシュって普通はバイナリーが多いけど、読めるものが有ったので 開いてみた。

ob$ ls */*
4a/4ab666e3dee91981a528e1f77e5187ba.module.builtin.description.txt
4a/4ab666e3dee91981a528e1f77e5187ba.module.builtin.o
 :
ob$ cat 4a/4ab666e3dee91981a528e1f77e5187ba.module.builtin.description.txt
/var/SRC/v/thirdparty/libgc/gc.o @ 'cc' -std=c99 -D_DEFAULT_SOURCE
 -O3 -flto -DNDEBUG  -fPIC -D GC_THREADS=1 -D GC_BUILTIN_ATOMIC=1
 -I "/var/SRC/v/thirdparty/libgc/include"
 -o '/home/sakae/.vmodules/.cache/4a/4ab666e3dee91981a528e1f77e5187ba.module.builtin.o'
 -c '/var/SRC/v/thirdparty/libgc/gc.c'

まだモジュールまで手を出していないんで静かだけど、いずれDLしたモジュールが鎮座 する事になるんだろうな。

v fmt

-diff          Display the differences between the formatted source(s)
               and the original source(s). This will attempt to find a
               working `diff` command automatically unless you specify one
               with the VDIFF_CMD environment variable.

-w             Write result to (source) file(s) instead of to stdout.

m8

前回からdeferしてたアプリである 村八分を書いてみた。最初に緩く決めたファイル名の 扱かい(元ファイルはfile.orgの様にする)は止めて、新規の方に接頭語として、New.を つける事にした。

import os
import strconv
import math.stats

fn main() {
        mut path := os.args[1]
        mut datin := []f64{}
        for s in os.read_lines(path)! {
                datin << strconv.atof64(s)!
        }

        ul, ll := get_limit(datin)!
        seld := datin.filter(it < ul && it > ll)

        mut datout := []string{}
        for v in seld {
                datout << strconv.f64_to_str_lnd1(v, 0) // v.str() for f64
        }
        os.write_lines('New.${path}', datout)!
}

fn get_limit(org []f64) !(f64, f64) {
        mut tm := org.clone()
        tm.sort()
        q1 := stats.quantile(tm, 0.25)!
        q3 := stats.quantile(tm, 0.75)!
        iqr := q3 - q1
        return q3 + iqr * 1.5, q1 - iqr * 1.5
}

実行例

何はともあれ、実行例をば。

sakae@lu:m8$ v -prod m8.v
sakae@lu:m8$ ./m8 scall
sakae@lu:m8$ diff -u scall New.scall
--- scall       2025-03-13 05:22:17.882440261 +0900
+++ New.scall   2025-03-13 05:58:28.829149970 +0900
@@ -1,8 +1,6 @@
-100
 50
 48
 55
-12
 52
 49
 51

エラーチェックしてないので、存在しないファイルを指定すると 勿論落ちる。

sakae@lu:m8$ ./m8 fugahoge
V panic: result not set (failed to open file "fugahoge")
v hash: 62cbc8b
         | 0x5d79928f4fc9 | ./m8(+0x16fc9)
         | 0x5d79928e0ca9 | ./m8(+0x2ca9)
         | 0x7a2edba2a1ca | /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)
         | 0x7a2edba2a28b | /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)
         | 0x5d79928e0d45 | ./m8(+0x2d45)

リリース版じゃ無いdebug版だと、もう少し親切なエラー表示だ。

sakae@lu:m8$ v .
sakae@lu:m8$ ./m8 fugahoge
V panic: result not set (failed to open file "fugahoge")
v hash: 62cbc8b
/tmp/v_1000/m8.01JP6314YMZ9M8RX2YM0GPQE81.tmp.c:5252: at _v_panic: Backtrace
/tmp/v_1000/m8.01JP6314YMZ9M8RX2YM0GPQE81.tmp.c:5218: by panic_result_not_set
/tmp/v_1000/m8.01JP6314YMZ9M8RX2YM0GPQE81.tmp.c:8378: by main__main
/tmp/v_1000/m8.01JP6314YMZ9M8RX2YM0GPQE81.tmp.c:8474: by main

解説ってかメモ

一画面を越える様なコードは書くな。多機能不要、容易に改造できる様なテンプレート 版をめざした。

冒頭にmain関数を記述して主題を明示する。機能の詳細は、その都度、別関数にする。 C言語の様に、宣言の順番に気を使う事が必要ないので楽だ。

大概はファイルを読んで、処理して、結果をファイルに出力なんで、入出力の部分は 定型になるはずだ。osモジュールを参照したら、そんな目的にピッタリな関数が 提供されてたので read_lines() write_lines() を使った。これらの対象は、 配列になる。配列なら、schemeとかのリスト処理関数と同等のものが利用できるな。

読み込んだデータはf64に変換してdatin配列に蓄積。書き出すデータは、f64の場合 なら、v.str()で文字列に、整数が欲しかったら、少数点以下を抑制する関数で 文字列に変換してdataout配列に蓄積って塩梅だ。

後残るは処理の部分だけど、main()の中央に鎮座してる2行がそれだ。 get_linit は、多値を返す関数だ。datainから、リミット値を抽出してる。それが得られれば、 後は、有名なfilter()を使って、条件を満たす値だけの配列(seld)を作成。

今回のリミット抽出には、math.statsで提供されてる、quantileを利用。この関数の 引数はソートされてる必要が有る。でソートをするんだけど、 get_limit に渡され たものをそのまま使ってしまうと、main側と共有されてる為、順番が変ってしまう。 そこで、clone()して、それをソートしてる。

尚、あちこちの関数とかに、! が付いている場合が有るが、これは、エラーになる 可能性が有るよって警告だ。今回は、全く無視してるけど、or { … } とかして、 エラー処理する事を推奨します。

see vlib

言語を勉強するには、付属してるライブラリィーを見るに限る。Proが作成した 綺麗なコードだからね。ましてや、自分が利用したものだと、理解も速い。

os.read_lines

os/os.v

// read_lines reads the file in `path` into an array of lines.
@[manualfree]
pub fn read_lines(path string) ![]string {
        buf := read_file(path)!
        res := buf.split_into_lines()
        unsafe { buf.free() }
        return res
}

strconv.f64_to_str_lnd1

もう一つ、小数点以下をサプレスする奴。思いの他難しい事をやってるな。

// f64_to_str_lnd1 formats a f64 to a `string` with `dec_digit` digits after th\
e dot.
@[direct_array_access; manualfree]
pub fn f64_to_str_lnd1(f f64, dec_digit int) string {
        unsafe {
                // we add the rounding value
                s := f64_to_str(f + dec_round[dec_digit], 18)
                // check for +inf -inf Nan
                if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
                        return s
                }
		:
                // no more digits needed, stop here
                if dec_digit <= 0 {
                        // C.printf(c'f: %f, i: %d, res.data: %p | dot_res_sp: \
%d | *(res.data): %s \n', f, i, res.data, dot_res_sp, res.data)
                        if dot_res_sp < 0 {
                                dot_res_sp = i + 1
                        }
                        tmp_res := tos(res.data, dot_res_sp).clone()
                        res.free()
                        return tmp_res
                }

もっと優しい方法は無いか? 直感的に考えるんだ。浮動小数点を整数に変換、そして それを文字列にすればいいじゃん。ずっと素直な方法だと思うぞ。

datout << i64(v).str()

出たな、必殺のメソッド・チェーン。

method

最初、配列のコピーをどうやろうと、目を皿にしてarraysで提供されてる関数を 調べていたんだ。それで、ズバリのcopyってのを発見した。でも、ちと使い方が 難しい。んでもって、基本に立ち返って、vlangそのもののドキュメントを参照 したんだ。そしたら、cloneなんて記述を発見。これって、配列に付随するメソッド だよね。他にも有用なfilterとかmapとか、色々と紹介されてた。 これらを一望できないか?

rubyだとメソッド命なんで、ri Array するなり、もってお手軽に、

sakae@lu:~$ irb
irb(main):001> [1,2].filter
               [1,2].filter    Press Alt+d to read the full document
               [1,2].filter_mapArray#filter
               [1,2].filter!
                               (from ruby core)
                               ----------------------------------------
                                 filter()

                               ----------------------------------------

                               With a block given, calls the block with
                               each element of self; returns a
                               new array containing those elements of
                               self for which the block returns
                               a truthy value:

                                 a = [:foo, 'bar', 2, :bam]
                                 a.select {|element| element.to_s.start
                                 # => ["bar", :bam]

                               With no block given, returns a new

目当てのものを、簡単に探し出せる。vlangは、そこまで根性有るかな? 配列のメソッド とかを見るしかないのだろうか? で、得意の家捜しをしてみる。

sakae@lu:v$ pwd
/var/my/srcs/v/vlib/v
sakae@lu:v$ fgrep 'method_name ==' checker/fn.v
        if method_name == 'map' {
        } else if method_name == 'filter' {
        } else if method_name == 'count' {
        } else if method_name == 'clone' {
        } else if method_name == 'sorted' {
                if method_name == 'sorted_with_compare' {
	:

更に追跡で、こんな所。/var/my/srcs/v/vlib/v/gen/c/array.v

// `nums.filter(it % 2 == 0)`
fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
        past := g.past_tmp_var_new()
        defer {
                g.past_tmp_var_done(past)
        }
	:

所で、rubyはこんな事が出来るけど、vlangはどうよ?

irb(main):001> [1,2,3].zip([7,8,9])
=> [[1, 7], [2, 8], [3, 9]]

言い出したら、きりが無いので、これぐらいにするか。Haskellとかのzipだと、タプルの 配列になるんだけどな。そうだタプルを使いそうな奴を探してみるか。ええ、勿論 多値を返す関数のシグネチャで、そこはかとなく利用されてるのは承知してますが。

math.complex

数(のクラスの最高峰)complexである。これなくしては、スマホも使えないと言う 重要な概念だ、とニュートン誌お得意のキャチャーなコピーである。 整数 < 有理数 < 無理数 < 複素数 と言う数のピラミッドね。詳しい事はschemeの 解説書を参照ね。昔から複素数にはコンプレックスが有るのよと言うなかれ。 電気屋なら、避けて通れない道だ。

>>> import math.complex as cx
>>> mut z := cx.complex(3.0, 4.0)
>>> z
3.000000+4.000000i
>>> z.angle()
0.9272952180016122
>>> z.abs()
5.0

これで判ったような気になってしまっては、もったいないお化け ですよ。ソースを眺めてなんぼが、OSSの醍醐味。

module complex

import math

pub struct Complex {
pub mut:
        re f64
        im f64
}

// complex returns a complex struct with the given `real` and `imaginary` values
pub fn complex(real f64, imaginary f64) Complex {
        return Complex{real, imaginary}
}

// To String method
pub fn (c Complex) str() string {
        mut out := '${c.re:.6f}'
        out += if c.im >= 0 { '+${c.im:.6f}' } else { '${c.im:.6f}' }
        out += 'i'
        return out
}

// Complex Modulus value
// mod() and abs() return the same
pub fn (c Complex) abs() f64 {
        return math.hypot(c.re, c.im)
}

// Complex Angle
pub fn (c Complex) angle() f64 {
        return math.atan2(c.im, c.re)
}
:

これ以降はオイラーさんに感謝しつつ。。。

m8p

上で作成したm8をストリーム対応にしてみる。ストリームって格好いい呼び名を しちゃったけど、単にパイプに対応するだけだ。stdin/stdoutが、入出力だ。

main()をちょこっと改造する だけで良い。肝は、 read_lines() の変わりに get_lines() を利用するだけ。 ファイルに出力する必要も無いので、むしろ簡単になっている。

import os
import strconv
import math.stats

fn main() {
        mut datin := []f64{}
        for s in os.get_lines() {
                datin << strconv.atof64(s)!
        }

        ul, ll := get_limit(datin)!
        seld := datin.filter(it < ul && it > ll)

        for v in seld {
                println(v)
        }
}

fn get_limit(org []f64) !(f64, f64) {
        mut tm := org.clone()
        tm.sort()
        q1 := stats.quantile(tm, 0.25)!
        q3 := stats.quantile(tm, 0.75)!
        iqr := q3 - q1
        return q3 + iqr * 1.5, q1 - iqr * 1.5
}

実行例

まずは、パイプの一員になれるか確認。
sakae@lu:m8$ cat scall | ./m8p
50.0
48.0
:

そして、その応用例

sakae@lu:m8$ cat scall | ./m8p | gnuplot -e 'p "-" w boxplot; pause 5'

gnuplotのコマンドを -e で与え、データは標準入力から頂くとした。 pause 5は、グラフを5秒間表示してねって指示だ。 本当の所、これがやりたかった事です。

README

昭和人間のトリセツ なんて本を読んだ。前期昭和時代人に相当するオイーには うってつけの本だ。昭和時代も長かったので、昭和46年生まれを境に、前期/後期と 便宜的に分けている。これからして、もう化石を扱かう学問っぽいな。

昭和度を認識して貰うための10の質問が冒頭に用意されてた。家電ありますか? とか セクハラやパワハラは厳しすぎて弊害が有ると思ってる? とか、YOASOBIのメンバーの 人数や男女比を知らない等等。

文句無く10点満点を獲得したぞ、エヘッン!


This year's Index

Home