✌️

V言語でproconioライブラリを作ってみた

2023/02/26に公開

どうもAtCoderが言語アップデートをするそうです。
https://docs.google.com/spreadsheets/d/1HXyOXt5bKwhKWXruzUvfMFHQtBxfZQ0047W7VVObnXI/edit
こんな記事を書いている手前V言語を推さないわけにはいかんので、ちゃっかり登録してきました。前回のアップデート時にはあまりにも時期尚早すぎるために切られましたから。順調そうで何よりです。

そんでもって、V言語には今のところ競プロ向きのええかんじの入力方法があまり無い[1]わけですから、これはライブラリを作るしかないとなったわけです。

proconioとは?

proconioとは元々Rustに向けたライブラリで、Rustのマクロとかのええかんじの機能を使って、構造体ライクな宣言を書くだけで変数の宣言や、前に入力した数値からその長さの配列を読み込むなんてことを自動でやってくれます。
https://crates.io/crates/proconio

こう書くだけでaとbとcが宣言されて、文字列と数値と長さbの数値のタプルが入力できます。なんて便利なんでしょ。うちにも分けていただきたい。

use proconio::input!;

fn main() {
	input! {
		a: String,
		b: u64,
		c: [(u64, u64); b],
	}
	println!("{a} {b} {c:?}");
}

できたものを紹介

てなわけでうちの子にしました
https://github.com/lemoncmd/proconio

構造体を宣言してジェネリクスでinput関数に渡してやると自動的に入力が為されます。長さbの配列を入れることも勿論できます。

import proconio

struct Vec2 {
	x u64
	y u64
}
struct Input {
	a string
	b u64
	c []Vec2 [b] // フィールドの属性に配列の長さを入れる
}

fn main() {
	i := proconio.input[Input]()
	println('${i.a} ${i.b} ${i.c}')
}

仕組み

V言語のCompile-time Reflectionを利用しました。
https://github.com/vlang/v/blob/master/doc/docs.md#compile-time-reflection

Compile-time Reflectionを用いると、コンパイル時に構造体やメソッド、あるいはそれらに付けられた属性などの情報を取得することができます。

[attribute1]
struct Foo {
	a string [attribute2]
}

[attribute3]
fn (f Foo) do_something() {}

fn main() {
	$for attr in Foo.attributes {
		println('${attr.name}') // attribute1
	}
	$for field in Foo.fields {
		$if field.typ is string {
			println('${field.name}') // a
		}
		for attr in field.attrs {
			println('${attr}') // attribute2
		}
	}
	$for method in Foo.methods {
		for attr in method.attrs {
			println('${attr}') // attribute3
		}
	}
}

例えば上のコードであれば、型にattributesfields、またはmethodsを付けてCompile-time for文を回すとええかんじに取りたい情報が取れます。因みにどんな情報が取れるかはvlibのbuiltin/builtin.vに型定義が書いてあります。

これをジェネリクスで再帰すると任意の型について何かしらの処理をできるようになります。

fn encode[T](value T, indent int) {
	print('\t'.repeat(indent))
	$if T is u64 {
		println('u64 ${value}')
	} $else $if T is string {
		println('string ${value}')
	} $else $if T is $Struct {
		println(typeof[T]().name)
		$for field in T.fields {
			encode(value.$(field.name), indent + 1)
		}
	}
}

struct Foo {
	x u64
	y string
}
struct Bar {
	a string
	b Foo
}
fn main() {
	encode(Bar{'some', Foo{7, 'things'}}, 0)
	// Bar
	//     string some
	//     Foo
	//         u64 7
	//         string things
}

後はこれを標準入力に応用すればよいだけです。やった~!

終わりに

というわけでもしAtCoderにV言語が入ったらよろしくお願いします!サラっと色々書けてしかも実行速度は爆速なのでかなり競プロ向きの言語です。

脚注
  1. 勿論入力を改行スペースで割って数値に変換することはできる……が、問題を解く度に各問題の入力に向けたコードを手入力するわけにはいかんわけです ↩︎

Discussion