V言語でアレする(アプリ作成)
QRコードの奇跡
なんて本を読んだ。今やこれなしでは世の中が成り立たないぐらい普及してる。携帯屋は、電装へ、お礼参りをかかすなよ。
開発秘話が丁寧に解説されてた。トヨタ自動車の電装部分を作ってる会社が、トヨタの有名な『カンバン』生産方式を採用(させられた)。殿様の意向には逆らえぬ。下請け(そんな言い回しすると怒られるぞ)は、その為多数の伝票発行を余儀なくされる。
電子化したいってんで、独自のバーコード、NDコードを発明。油にまみれたカンバンを読み取るため、並々ならぬ苦労が有ったそうな。でも、苦労のかいがあって、殿様の所でも採用された。
そのうちに、自動車のBTO(要するにカスタマイズ仕様)が当たり前になり、バーコードでは桁数不足で対応出来なくなった。そうだ、バーコードを2次元に拡げよう。ってんで、開発したのがQRコード。
名前の由来は、クィック・レスポンスから取った(素早く反応するって意味)。NDコードは特許で固めていたので、残念ながら世に拡がらなかった。
この反省を踏まえてQRコードは、無償で公開した。特許でガチガチに固めてしまったら、ここまで拡がる事は無かったでしょう。過去には同様事例に、フィリップスが発明したカセットテープがある。無償で使えるがゆえ、爆発的に拡がった。
ソフトウェア業界では、アドビ発のPDFが有るな。PDF普及の為、閲覧ツールであるリーダーを無償で公開してた。作成ツールはアドビがしっかり握っていたな。そのうちに仕様を頼りに、独自実装の作成ツールが出てきた。今では、ちょっと気が利いたアプリなら、PDFで出力出来ますってなってる。これから格闘していくgnuplotもしかりだ。簡単に表示したグラフをPDFに落とせる。これで、みんなが幸せだ。
V言語の同士達
今までV語に付いて2回も記事を書いてしまったけど、まだ一行もコードを書いていない。そこで少し反省して、アレを書いてみたいんだけど、その前に先行者達の思いを少し調べてみた。
良さげなクロスプラットフォーム開発言語《v》のメモ(Go,Nim,Rustと少し比較)
他にも探してみたけど、深入りしてる人は余りいないようだ。まだ新しすぎて、本格的に手を出すのを躊躇されているのかな?
アレする
アレってのは、新しい言語に遭遇した時にやってみる儀式である。Rustの時にもやった。血圧表示アプリである。
今回は、一から書くのではなくて、goで書かれたコードをV語のソースと偽って、エラーまみれになりながら、もぐらたたきしてみたい。きっとエラーが無くなれば、V語で走るアプリが完成している事でしょう(ゲームセットって言うのかな)。
こういう方針を取るのは、上の先輩たちがgoに似ているよ。80%は一緒だよと言う発言を受けての事だ。まあ、こういうの方言が激しいscheme界隈で、違うschemeに乗り換える時、差分を書いて乗り切るのと、根が似ているな。
適当にプロジェクトを作る。プロジェクトのdir内で、そんなのオイラーには関係無いって言う、.git族を消しておく(rm -rf .git*)。それから、前回適当にでっちあげたMakefileも用意しておく。
goのソースを、プロジェクト名.v にコピペする。これで準備完了。原本は、 血圧グラフ(nbld.go) を参照。
いきなり、コンパイルしてみる。
make -k v nbldv.v nbldv.v:198:1073: error: invalid character literal (more than one character) use quotes for strings, backticks for characters 196 | 197 | func main() { 198 | gsrc :=`# blood graph for gnuplot | ^
M-g n (or p)でエラー行へ飛んで行けるのだな。Compilationモード を見ながらね。実は、emacsでこういう使い方するの初めてだったりします。
巨大な文字列をシングルクォーテーションで括ったら、中にシングルクォーテーションが廃いていたよ。その結果、こんなエラー行が。(後で、文法書を調べたら、ルーン文字列はGo式で指定するってなってた。オイラーの勘違い?)
make -k v nbldv.v nbldv.v:221:13: error: invalid character `起` 219 | set multiplot layout 2,1 220 | 221 | set title '起床時: ' . amh . aml . when | ^
シングルクォーテーションの前に、バックスラッシュを置いて、エスケープしたよ。文字列を指定する第三の記号が欲しいな (goの場合は、バッククォート文字がそれになってる)。
sed風に
オイラーの希望は、sedがとっくの昔に実現してたぞ。 例えば、/usr/local/bin って文字列を /opt/bin に変更したい場合
sed -e 's/\/usr\/local\/bin/\/opt\/bin/'
こんな具合に書くのかな。s/old/new/ が指定方法で、/が区切りになってる。だから、文字列の中にある/はエスケープが必要。こんな時、sedでは
sed -e 's!/usr/local/bin!/opt/bin!' or sed -e 's#/usr/local/bin#/opt/bin#'
のように、指定出来る。要するにsに続く文字を区切り記号に採用しますって言う決断。昔のsedで実現出来てる事を、最新式の言語が取り入れ無いって、遅れていると思うぞ。
まだまだモグラ叩きは続きます
nbldv.v:3:9: error: unexpected name `main` 1 | // blood PDF by gnuplot 2 | // Using gnuplot script topdf.plt 3 | package main | ~~~~
はあ、packageじゃなくて、module なのか。v/cmd/toolsに有る、実例を見て気が付いたぞ。
make -k v nbldv.v nbldv.v:5:8: error: `import()` has been deprecated, use `import x` instead 3 | module main 4 | 5 | import ( | ^
カッコで括って展開する、数学は苦手なんだな。展開して書けって事か。
make -k v nbldv.v nbldv.v:5:8: error: unexpected string `os`, expecting name 3 | module main 4 | 5 | import "os" | ~~~~
名前なんだからダブルクォーテーションで括りたいけどそれは駄目。文字列じゃなくて名前って、シンボル扱いして、コンパイルスピードを稼ぎたいんだな。
Go vs Rust vs V
SEO対策で、こんな項目を作っておきます(笑い)。今やってるのは、Go家からV家に嫁いだ来たお嬢様にV家の作法を覚えて頂く作業だ。 ここまでエラーにまみれてみて、一つ気が付いた事がある。
それは、Vの場合一回の動作(コンパイル)で、一つしかエラーを指摘しない事。Rustは、無茶苦茶エラーだとか警告を発して来るんだ。言うなれば、非常に恐い姑さんって事。これじゃ、普通の人は、Rust家をお暇させて頂きますってなるわな。 あれ、その点、本家のGoはどうだったかな? 遠い昔の事で、忘れてしまったぞ。
Vは、当たりが柔らかい。エラーの出方一つ取っても、優しいと思うのはオイラーだけかな?
nbldv.v:10:10: error: unexpected token `[`, expecting `=` 8 | const dbfile = "current.csv" 9 | 10 | type AAy [][4]int // main data type | ^
= が足りませんよと、親切なご指摘。素直に従いましょう(しおらしくね)。
nbldv.v:11:5: error: unexpected name `bld` 9 | 10 | type AAy = [][4]int // main data type 11 | var bld AAy // blood data | ~~~
Go家の作法では、AAyって型を持った変数bldを宣言しますって意味だけどな。V語ではbldそんな名前は許しませんとな。 ここはV家の作法書、 https://github.com/vlang/v/blob/master/doc/docs.md か、その翻訳である、 V プログラミング言語 を見ておけ。
ここからが、大幅に書き換えが出て来るんだな。 その前に、V家の作法を身に着けているであろう、小姑ことexampleの中から、これはと思えるものを選んで、観察してみる。
from example
余り長いのは疲れるので、なるべく短いやつって事で。
dump_factorial.v
ob$ v run dump_factorial.v [dump_factorial.v:2] n <= 1: false [dump_factorial.v:2] n <= 1: false [dump_factorial.v:2] n <= 1: false [dump_factorial.v:2] n <= 1: false [dump_factorial.v:2] n <= 1: true [dump_factorial.v:3] 1: 1 [dump_factorial.v:5] n * factorial(n - 1): 2 [dump_factorial.v:5] n * factorial(n - 1): 6 [dump_factorial.v:5] n * factorial(n - 1): 24 [dump_factorial.v:5] n * factorial(n - 1): 120 120
dumpって関数で式を包むと、式と結果が表示される。Rustにあったdbg!マクロ機能だな。何かの時のために、覚えておこう。
process/execve.v
名前からして、子プロセスを起動するサンプルっぽい。
ob$ v run execve.v V panic: No such file or directory /tmp/v/execve.421045109077557346.tmp.c:5895: at v_panic: Backtrace /tmp/v/execve.421045109077557346.tmp.c:11904: by main__exec /tmp/v/execve.421045109077557346.tmp.c:11909: by main__main /tmp/v/execve.421045109077557346.tmp.c:11997: by main
いや、違った。パニックの実例になってる。期待してたのと違うぞ、小姑の腹の中を探ってみると、
fn exec(args []string) { os.execve('/bin/bash', args, []) or { // eprintln(err) panic(err) } }
panicの呼び出し方がGoとは違いますってのが見て取れる。そこが今回の注目点ではなく、小姑のいやがらせ、/bin/bash と言うのが、姑息な手段である。曰く、何処にでも有ると思うな、bashである。全く、リナ系しか知らない弊害が出てるな。/bin/shが万国共通ISOだぞ。標準に従いましょう。
枕に書いたQRコードの開発物語。何でも読めるようにしちゃうデンソーさん。ある時から、そのサポートを止めたとか。理由は、ちゃんとしたQRコードの印刷基準が公開されてるのに、それを守らないユーザーから泣きつかれる事例が多くあった為。涙を飲んで、そういうサービスは止めたそうだ。
V語の作法
vldの定義部分は、取り合えずコメントにして、先に進みます(進んだ先で、またモグラが出現するでしょう)。
nbldv.v:15:2: error: unexpected name `low`, expecting `=` 13 | ymdh = iota 14 | hi 15 | low | ~~~
constではカッコが使えるけど、中の名前は、それぞれ=で定義する必要が有ります。これらの名前は配列のインデックス番号を名前で呼びたかったから定義してみただけです。
nbldv.v:19:6: error: unexpected name `failOnError` 17 | ) 18 | 19 | func failOnError(err error) { | ~~~~~~~~~~~
名前が悪いと言ってるけど、本当に悪いのはfuncってキーワード。V語ではものぐさのfnってのが正解。
nbldv.v:19:4: error: function names cannot contain uppercase letters, use snake_case instead 17 | ) 18 | 19 | fn failOnError(err error) { | ~~~~~~~~~~~
関数名は、蛇のようにょろにょろと書けとな。アンダーバー書くの面倒。lispみたいに平気でダッシュで繋ぎたいのが本音。
nbldv.v:28:15: error: unexpected name `fd`, expecting `{` 26 | fd, err := os.Open(datafile) 27 | fail_on_error(err) 28 | defer fd.Close() | ~~
{ … } で、囲んでくれとな。
nbldv.v:30:8: error: redefinition of `err` 28 | defer { fd.Close() } 29 | r := bufio.NewReader(fd) 30 | line, err := r.ReadString('\n') | ~~~
再定義してるってな。そもそも多値で返ってこないんだな。errはC語で言うerrnoみたいだな。 processの所に出てきたように処理をすれば良いのか。(事前学習が役にたった)
nbldv.v:32:37: error: invalid expression: unexpected token `:` 30 | line := r.ReadString('\n') or { 31 | fail_on_error(err) } 32 | return fmt.Sprintf("when=%s", line[:6]) | ^
スライスの書き方が、.. 派なんだな。コロンで区切るのはgo,python,octave系、作者さんの趣味? それとも、コロンは別の用途に使う? おぼろげながら、ラベルの宣言かな。
nbldv.v:36:6: error: unexpected name `rows` 34 | 35 | fn mycsv(csvfile string) { 36 | var rows [4]int | ~~~~
これ、V語にはvarなんてキーワードが無い。だからvarを変数名と認識したのかな。すると次に続くのは=か。そのチェックの前にrowって括りを見つけて、これはおかしいぞとなったんだな。先に=を期待ってやってもよかったんだろうけど、何かの都合でrowsをエラーに引っかけたんだろうね。
mut rows := [4]int{}
こうするのが正しい。mutは、書き換え可能の指定。intの後ろの{}は無くても許されるみたいだけど、警告が出るんで付けておいた。
nbldv.v:42:21: error: unexpected name `all`, expecting `;` 40 | all := reader.ReadAll() or { 41 | fail_on_error(err) } 42 | for _, el := range all { | ~~~
V語のループにはforしか無い。その代わり多彩な書き方が許されている。その一つにC語風ってのが有る。それを適用しようとして、ドツボにはまり、セミコロンを提案したのだろう。 付け加えておくと、V語にはrangeってのも無い。
配列を(インデックス番号付きで)舐める使い方なんで、
for _, el in all {
こんな形になるかな。
nbldv.v:54:28: error: unexpected token `,`, expecting `)` 51 | fn savecsv(csf string) { 52 | buf, _ := ioutil.ReadFile(csf) 53 | _ = ioutil.WriteFile("Backup.csv", buf, 0666) 54 | fd, err := os.OpenFile(csf, os.O_CREATE|os.O_WRONLY, 0666) | ^
51行目はエラー提示では出てこないので、追加表示してる。で、ここら辺からGo語をV語に無理やり書き換えると言う無謀な事が露呈し始めている。
今まで見て見ぬふりをしてたけど、関数名は小文字で始まるスネーク方式にV語はなってる。ここは、コンパイラーの意志を尊重して先に進んでみる。(もぐらが増殖してる事請け合いますよ)
nbldv.v:73:6: error: unexpected name `a` 71 | 72 | fn ire(ym int) { 73 | var a, b, c, d string | ^
変数宣言だけなので、削除。初期化してる部分が有ったので、
mut a, b, c, d := "", "", "", ""
こんな風に、一気に宣言しちゃった。後に根過を残さなければ良いのですが。
nbldv.v:100:19: error: unexpected name `string`, expecting `(` 98 | } 99 | 100 | fn (ds AAy) pp(fn string) { | ~~~~~~
これは特殊事例かも知れないな。関数の引数名がキーワードと被っている。それでコンパイラーは、関数定義が始まったと誤認してるんだな。fnじゃなくてvfnって変数名に変更しといた。
nbldv.v:135:6: error: unexpected name `rs` 133 | 134 | fn (ds AAy) pm() AAy { 135 | var rs AAy | ~~
空の配列の宣言なんだな。
rs := []AAy{}
こんな風にしろとな。
nbldv.v:207:12: error: expression evaluated but not used 205 | mksf("topdf.plt", gsrc) 206 | exec.Command("gnuplot", "-e", ag("am.dat"), "topdf.plt").Run() 207 | os.Remove("am.dat") | ~~~~~~~~~~~~~~~~
これ、エラー提示を素直に解釈すれば式を評価したけど、それを使っていない(無駄な事は、スピード低下になる)から、何か足りないって言ってるんだな。
Removeがガンなのか。vilib/osの下をremoveで探してみると os_c.v
に、こんなのが見つかった。
// rm removes file in `path`. pub fn rm(path string) ? { mut rc := 0 $if windows { rc = C._wremove(path.to_wide()) } $else { rc = C.remove(&char(path.str)) } if rc == -1 { return error('Failed to remove "$path": ' + posix_get_error_msg\(C.errno)) } // C.unlink(path.cstr()) }
これを使えば良いのだな。冒頭に付いているpubがその証拠。無料公開してるから、何方でもご自由に利用出来ます。中身は、もろにC語(≒ OS)を呼び出している。返値はrcで、失敗した理由が載ってる。
一方、Webに公開されてるvlibの説明では、
fn rm(path string) ?
のように、返値に所が?になってる。これはエラーがあったら載せてくるからねって事らしい。 処理しないと、上位に伝搬されて、最後はpanicに落ちるそうだ。
os.rm(file_name) or { panic(err) }
良い実例を探すには、 xxx_test.v
を漁るのが手っ取り早いぞ。これ、生活の知恵、試してガッテンですよ。ああ、帝国放送から著作権侵害で訴えられそう。
それにしても、Webが3ペインになってて、右側のペインにスライダーが付いていないって、どういう事よ。意味無いじゃん。人手不足で、そこまで手をかけられないんですって、スタートアップ企業らしいな。生暖かく見守ってあげましょう。
それまでは、下記のようにして、ソースから直接探しましょう。
grep 'pub fn' *.v
emacsからgrepしてるなら、結果の所をづらづら見て行って、RTNを叩けば該当部分に飛んでくれる。
2周目突入
こうして、ソースファイルの最後まで、文法チェックが済んだみたい。継続してコンパイルを行うと、今度は、
nbldv.v:6:1: builder error: cannot import module "csv" (not found) 4 | 5 | import os 6 | import csv | ~~~~~~~~~~~
ソースの頭から、またチェックが始まったよ。そして、エラーだ。どうもV語の環境にマッチしてるか系のチェックって思える。このcsvは文法的には合ってる、実体と一致しないよとな。 対応者も、ここからは視点を変える必要があるな。
今、勝手に2周目と書いちゃったけど、コンパイルのフェーズできちんと区分け出来るの。以前やった、ハロワでちょっと確認。
debian:hoge$ v -show-timings hoge.v 38.173 ms SCAN 73.466 ms PARSE 37.305 ms CHECK 68.733 ms C GEN 1319.305 ms C cc
改めて段階を確認すると、5段階を経て、バイナリーが作成されるんだな。
debian:hoge$ v -show-timings hoge.v 38.189 ms SCAN 74.041 ms PARSE 37.198 ms CHECK hoge.v:4:2: error: unknown function: zzzprintln 2 | 3 | fn main() { 4 | zzzprintln('Hello World!') | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | }
わざとエラーになるようにソースを改変してみると、4段階目のC語作成で失敗してる。
数十年前に、車の免許を取った時、1段階、2段階と続いて、確か4段階目で、路上教習だったように覚えている。
今の段階は、どんあ具合なんだろう? 確認してみるか。
debian:nbldv$ v -show-timings nbldv.v 50.161 ms SCAN 98.032 ms PARSE 59.399 ms CHECK nbldv.v:19:1: error: unknown type `error` 17 | ) 18 | 19 | fn fail_on_error(err error) { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | if err != nil { 21 | panic(err) nbldv.v:20:12: error: undefined ident: `nil` 18 | 19 | fn fail_on_error(err error) { 20 | if err != nil { | ~~~ :
C語の作成段階まで、漕ぎつけているな。そして、この段階だと、バンバンと複数のエラーが挙がってくる。もうV語の基礎は出来てるはずだから、スパルタ方式に切り替えたよとな。 これが、本当の山場になるんだな。
これを通過すれば、合格間違い無し! 大学の医学部で国家試験の医師免許合格率が高いのは、 予め合格出来そうな連中だけを入学させているからって事だけど、それと一緒だ。
随分長く伸びてきたので、次回に続く。。。。
何と言っても、75個もエラーを検出してますからね! どうだ、凄いだろう(って、そんなの自慢になるか)。
いや、自慢していいよ。兎角コンパイルエラーは忌み嫌われるけど、初めての言語に取り組む時は、エラーのシャワーをたっぷりと浴びておいた方が良い。
並々ならぬ努力の末の勲章、それを信じてね。