はじめに
Erg言語で一部の外部ライブラリが使えるようになった
具体的には以下の通りである
- matplotlib
- numpy
- pandas
- requests
- setuptools
- tqdm
- urllib3
完全には型付け完了していないため、一部の機能には限定されてはいるがこれらライブラリを使えるようになっている
例えばNumpyだとこのように型付けがされている
# TODO: dependent (static shaped)
.NDArray = 'ndarray': (T: Type) -> ClassType
.NDArray(T) <: Output T
.NDArray(_) <: Num
.NDArray.
shape: [Nat; _]
ndim: Nat
dtype: Type
size: Nat
.abs: |T|(object: .NDArray(T),) -> .NDArray(T)
.add: |T|(object: .NDArray(T), other: .NDArray(T)) -> .NDArray(T)
.all: |T <: Num|(object: .NDArray(T),) -> Bool
.any: |T <: Num|(object: .NDArray(T),) -> Bool
.arange: |T <: Num|(start: T, stop := T, step := T) -> .NDArray(T)
.array: |T|(object: Iterable(T),) -> .NDArray(T)
.linspace: |T <: Num|(start: T, stop: T, num := Nat, endpoint := Bool, retstep := Bool, dtype := Type, axis := Nat) -> .NDArray(T)
.max: |T <: Num|(object: .NDArray(T),) -> T
.mean: |T <: Num|(object: .NDArray(T),) -> T
.min: |T <: Num|(object: .NDArray(T),) -> T
.ones: |T|(shape: Nat or [Nat; _], dtype := Type) -> .NDArray(T)
# タイポが現在2023/06/ddであるのでreshapceからreshapeに直す
.reshape: |T|(object: .NDArray(T), shape: [Nat; _]) -> .NDArray(T)
.std: |T <: Num|(object: .NDArray(T),) -> T
.sum: |T|(object: .NDArray(T),) -> T
.sqrt: |T|(object: .NDArray(T),) -> .NDArray(T)
.transpose: |T|(object: .NDArray(T), axes := [Nat; _]) -> .NDArray(T)
型付けに関する概要はこことかを読めば大まかにはわかる
.d.er
ファイルを作成してPythonに対して型をつければ基本的にはPythonのライブラリ(モジュール)は使えるようになる
私自身いろいろとPRをためてしまっているので、この型付け作業に関してはまだできていないため、詳しい解説ができないがPandas
やscikit-learn
の型付け作業はしたい
最近の主要ライブラリは型注釈をつけるのが一般的となってきている
そのため、Pylyzer
を使い、型注釈を参考にすれば主要なライブラリの多くは型付けができる
$ pylyzer --dump-decl test.py
Start checking: test.py
All checks OK: test.py
$ ls
test.py test.d.er
Numpyのinit.d.er
に対して、reshape
だったりmax
メソッドをつけようとしたら以下のように書くだけで、できるようになる
# TODO: dependent (static shaped)
.NDArray = 'ndarray': (T: Type) -> ClassType
.NDArray(T) <: Output T
.NDArray(_) <: Num
.NDArray.
shape: [Nat; _]
ndim: Nat
dtype: Type
size: Nat
+ .reshape: |T|(shape: [Nat; _]) -> .NDArray(T)
+ .max: |T <: Num|() -> T
.abs: |T|(object: .NDArray(T),) -> .NDArray(T)
.add: |T|(object: .NDArray(T), other: .NDArray(T)) -> .NDArray(T)
.all: |T <: Num|(object: .NDArray(T),) -> Bool
.any: |T <: Num|(object: .NDArray(T),) -> Bool
.arange: |T <: Num|(start: T, stop := T, step := T) -> .NDArray(T)
.array: |T|(object: Iterable(T),) -> .NDArray(T)
.linspace: |T <: Num|(start: T, stop: T, num := Nat, endpoint := Bool, retstep := Bool, dtype := Type, axis := Nat) -> .NDArray(T)
.max: |T <: Num|(object: .NDArray(T),) -> T
.mean: |T <: Num|(object: .NDArray(T),) -> T
.min: |T <: Num|(object: .NDArray(T),) -> T
.ones: |T|(shape: Nat or [Nat; _], dtype := Type) -> .NDArray(T)
.reshape: |T|(object: .NDArray(T), shape: [Nat; _]) -> .NDArray(T)
.std: |T <: Num|(object: .NDArray(T),) -> T
.sum: |T|(object: .NDArray(T),) -> T
.sqrt: |T|(object: .NDArray(T),) -> .NDArray(T)
.transpose: |T|(object: .NDArray(T), axes := [Nat; _]) -> .NDArray(T)
このような.d.er
ファイルの作成や修正を作ってもらえれば既存のPython資産を既に扱える状況にある
型を持って推論もしてくれるLightなRust/RobustなPythonを書けるようになる
型付けが非常に大変な作業ではあるが、DjangoのようなWebフレームワークから、Pytorchのような機械学習ライブラリまで幅広くPythonの資産を使える前段階までは来た
もし気になった人がいれば、上記みたいなな小さいのでも良いのでライブラリの型付けをPRとして作ってもらえると嬉しい
特にこのような型付けや修正は割とgood first issue
になるので、GithubでPRを作ったことがない人にもおすすめできる
Github上でもDiscord上でも良いので質問や話しをしながらながらでもIssueやPRを作ってくれると良い経験になると思う
可能なら型付けしたその道筋をドキュメントとして残してくれるとありがたい
今回はどのように資産が扱えるかを軽く紹介しながら、Ergのコードを眺めてもらうことを目的としている
環境構築
現状はまだ、Ergで環境構築する方法はない
一応パッケージマネージャーの計画はやパッケージシステムなどがある
ErgのコンパイラをダウンロードしてPythonの環境のみを構築すれば良い
Erg releaseから自分の環境に合わせてダウンロードする
Rustがある人はcargo install erg
でもできる
クローンをしても良い
git clone https://github.com/erg-lang/erg.git
cd erg
cargo install --path . --features "full japanese" # --features以降はなくても良い
Pythonはとりあえずvenv
を使う
以下のはWindowsでの環境構築なので、MacやLinuxの人は適度に変える
python -m venv erg_venv # 名前は適当なので注意
. .\Scripts\Activate.ps1
python -m pip install -U pip matplotlib numpy tqdm
コードを書く際にはVSCodeを使うことをお勧めする
els(LSP)が既にあるため、基本的な機能は揃っている
- 型表示(2023/06/dd現在、かなり冗長なのでIssueを作っている。あると便利)
- 定義移動、実装移動
- 自動補完、自動補完候補の選択
- etc
ただ、頻繁にクラッシュするためまだ安定性が足りない
また、開発が途中だがJpyter Notebook用のカーネルも既にあるため、Ergをnotebook上で使うこともできる
Windowsではネイティブでは使えないが、WSL上では動作することは確認できている
ライブラリ(モジュール)のインポート
Ergのスクリプトを読み込むときにはimport
を使用し、Pythonのスクリプトを読み込むときにはpyimport
を使用する
# import numpy as np
np = pyimport "numpy"
読み込んだ内容は全て変数に代入するため、as
を使ったエイリアスのようなことをせずにそのまま変数名で名前を決める
np = pyimport "numpy"
a = np.array([1, 2, 3, 4.3])
print! a
# array([1. , 2. , 3. , 4.5])
他のメソッドもこのように使える
one_2d = np.ones([5, 5])
print! one_2d
ag = np.arange(15)
print! np.reshape(ag, [3, 5])
# メソッドのreshapeを使うには上記の`init.d.er`でメソッドの定義が必要
rh_one = np.arange(25).reshape([5, 5])
print! rh_one
ls = np.linspace(0, 1, 10)
print! ls
注意して欲しい点はErgでは変数のシャドーイングや再代入の原則的な禁止である
よく慣らしで同じ変数名(a
とかb
、arr
)を使って再代入してしまう
しかしこれはエラーとして検出されていしまうので注意が必要になる
また、built-inモジュールのtime
などは既に実装済みなので、これとtqdm
を使った場合はこのような感じになる
{sleep!;} = pyimport "time"
tqdm = pyimport "tqdm"
for! tqdm.Tqdm!(0..4, total:=100), _ =>
sleep!(0.25)
{sleep!;}
などの文法はレコードや可視性に詳細が書かれている
データの読み込み
私はデータ分析でPythonを使うので、データの読み込みができないとほぼ仕事にはならない
将来的にはPandas(Polar)が使えるようになるので、ここの章は丸ごと省略できるがまだできないので書いておく
データは/data/data.csv
に保存してある
1,2,3,4,5
6,7,8,9,10
11,12,13,14,15
まだ、strip()
メソッドがないので、データに余計な空白や改行があると以下のコードは使えない
date型やfloat型に対応するにはもう一工夫が必要になる
data = with! open!("./data/data.csv", mode:="r", encoding:="utf_8"), f =>
arr = ![]
for! f.read!().split("\n"), line =>
lines = ![]
for! line.split(","), e =>
lines.push!(e.to_int())
arr.push!(lines)
arr
print! data
単純な数値データだとこのようにできる
ここでのポイントとしてはwith!
とopen!
のプロシージャと、ブロックによるスコープの厳密さがある
基本的には副作用がある関数は全て!
をつけたプロシージャとしてErgでは特別扱いされるようになっている
ただ、これのおかげで代入される値がその後にエラーとして検出されたりした場合には、ここの副作用が悪さをしているからエラーとして検出されているなど目算をつけやすい
次にスコープの厳密さに関しては以下のようなことがPythonだとできてしまう
>>> with open("./data/data.csv", 'r') as f:
... data = f.read()
...
>>> print(data)
1,2,3,4,5
6,7,8,9,10
11,12,13,14,15
>>> for i in range(10):
... a = i
...
>>> a
9
ブロックがありそのなかでdata
やa
のような変数を作っても、それをブロックの外でも使用できる
このようにスコープの外に変数が漏れ出るのは意図しない挙動を生む
Ergではこれを厳密にチェックし、ブロック内で変数の寿命(lifetime)が終わるようになっている
Rustで有名になった所有権もErgでも一応存在するので使用した変数は所有権が移動したりして使えなくなったりなど制限がある
データの可視化
先程の読み込んだデータはMatplotlib
で可視化することができる
matplotlib = pyimport "matplotlib"
plot! = matplotlib.pyplot.plot!
show! = matplotlib.pyplot.show!
data = with! open!("./data/data.csv", mode:="r", encoding:="utf_8"), f =>
arr = ![]
for! f.read!().split("\n"), line =>
lines = ![]
for! line.split(","), e =>
lines.push!(e.to_int())
arr.push!(lines)
arr
_ = plot!(data)
_ = matplotlib.pyplot.xlabel!("x label")
_ = matplotlib.pyplot.ylabel!("y label")
discard matplotlib.pyplot.title!("fig title here")
discard matplotlib.pyplot.legend!(["line00", "line01", "line02"])
show!()
恐らくだがplt = pyimport "matplotlib.pyplot"
はできない
plt = matplotlib.pyplot
もできない
なので、プロシージャを変数に代入して使っている
現状使えるのは以下の6つだけ
- show!
- plot!
- xlabel!
- ylabel!
- title!
- legend!
show!
以外は返り値があるため、その返り値を使わないとwarningが出てしまう
なので、_(プレースホルダ)
やdiscard
を使用する(どちらでも良いはず)
コンパイル時のwarningはまだ消せないはず
よくあるsin波は以下のように書ける
{sin;} = pyimport "math"
matplotlib = pyimport "matplotlib"
plot! = matplotlib.pyplot.plot!
show! = matplotlib.pyplot.show!
x = 0..10
y = ![]
for! x, i =>
y.push! sin i
discard plot!(x, y)
show!()
x = np.arange(-5, 5, -0.1)
をx = 0..10
の代わりに使いたがったが、まだndarray
がイテレートできなかった
終わりに
徐々にではあるがErgの基礎的な機能だけではなく、Pythonの資産も使えるようになってきた
後はひたすら型を付ける作業をするだけで基本的にライブラリは使えるようになる
まだ使える機能が少なかったり冗長な部分が多いが、すぐに書けるという点ではRustよりはるかに書きやすかった
環境構築に関しては現状はvenv
でPythonの環境を作るだけでErgが認識してくれるので問題はなかった
外部のライブラリを使うという点ではまだ機能(型付け作業)が少ないのと、Ergでどのように書くのが良いのかの知見が足りなかったので上手く表現しきれていなかった
ただ、大まかなErgの書き方と、どのように資産を使うのかの概要は伝えられた思う
全体的に粗削りな部分が多いがこれから少しずつ改善されていくと思うし、私自身改善していくつもりである(興味を持ってくれたらIssueやPRを作ってください)