[WIP]ErgでPythonの資産が少し使えるようになった

## はじめに

Erg言語で一部の外部ライブラリが使えるようになった

具体的には以下の通りである

- matplotlib
- numpy
- pandas
- requests
- setuptools
- tqdm
- urllib3

完全には型付け完了していないため、一部の機能には限定されてはいるがこれらライブラリを使えるようになっている

例えばNumpyだと:point_right:[この](https://github.com/erg-lang/erg/blob/main/crates/erg_compiler/lib/external/numpy.d/__init__.d.er)ように型付けがされている

```python
# 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)
```

型付けに関する概要は:point_right:[ここ](https://zenn.dev/mtshiba/articles/2848e416e4bcca#%E9%96%8B%E7%99%BA%E3%81%AE%E3%81%8D%E3%81%A3%E3%81%8B%E3%81%91)とかを読めば大まかにはわかる

`.d.er`ファイルを作成してPythonに対して型をつければ基本的にはPythonのライブラリ(モジュール)は使えるようになる

私自身いろいろとPRをためてしまっているので、この型付け作業に関してはまだできていないため、詳しい解説ができないが`Pandas`や`scikit-learn`の型付け作業はしたい

最近の主要ライブラリは型注釈をつけるのが一般的となってきている

そのため、`Pylyzer`を使い、型注釈を参考にすれば主要なライブラリの多くは型付けができる

:point_right: [Pylyzer](https://github.com/mtshiba/pylyzer/blob/main/docs/source/options.md)

```shell
$ 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`メソッドをつけようとしたら以下のように書くだけで、できるようになる

```diff
# 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で環境構築する方法はない

一応[パッケージマネージャー](https://github.com/erg-lang/erg/blob/main/doc/JA/tools/pack.md)の計画はや[パッケージシステム](https://github.com/erg-lang/erg/blob/main/doc/JA/syntax/35_package_system.md)などがある

ErgのコンパイラをダウンロードしてPythonの環境のみを構築すれば良い

[Erg release](https://github.com/erg-lang/erg/releases)から自分の環境に合わせてダウンロードする

Rustがある人は`cargo install erg`でもできる

クローンをしても良い

```shell
git clone https://github.com/erg-lang/erg.git
cd erg
cargo install --path . --features "full japanese" # --features以降はなくても良い
```

Pythonはとりあえず`venv`を使う

以下のはWindowsでの環境構築なので、MacやLinuxの人は適度に変える

```shell
python -m venv erg_venv # 名前は適当なので注意
. .\Scripts\Activate.ps1
python -m pip install -U pip matplotlib numpy tqdm
```

---

コードを書く際にはVSCodeを使うことをお勧めする

els(LSP)が既にあるため、基本的な機能は揃っている

- 型表示(2023/06/dd現在、かなり冗長なので[Issue](https://github.com/erg-lang/erg/issues/368)を作っている。あると便利)
- 定義移動、実装移動
- 自動補完、自動補完候補の選択
- etc

ただ、頻繁にクラッシュするためまだ安定性が足りない

また、開発が途中だがJpyter Notebook用の[カーネル](https://github.com/erg-lang/jupyter-erg)も既にあるため、Ergをnotebook上で使うこともできる

Windowsではネイティブでは使えないが、WSL上では動作することは確認できている

## ライブラリ(モジュール)のインポート

Ergのスクリプトを読み込むときには`import`を使用し、Pythonのスクリプトを読み込むときには`pyimport`を使用する

```python
# import numpy as np
np = pyimport "numpy"
```

読み込んだ内容は全て変数に代入するため、`as`を使ったエイリアスのようなことをせずにそのまま変数名で名前を決める

```python
np = pyimport "numpy"
a = np.array([1, 2, 3, 4.3])
print! a
# array([1. , 2. , 3. , 4.5])
```

他のメソッドもこのように使える

```python
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`を使った場合はこのような感じになる

```python
{sleep!;} = pyimport "time"
tqdm = pyimport "tqdm"
for! tqdm.Tqdm!(0..4, total:=100), _ =>
    sleep!(0.25)
```

![スクリーンショット 2023-06-07 094929.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/baccc86e-00e3-802e-0a82-57a26be15fd5.png)

`{sleep!;}`などの文法は[レコード](https://github.com/erg-lang/erg/blob/main/doc/JA/syntax/14_record.md#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%88%86%E8%A7%A3)や[可視性](https://github.com/erg-lang/erg/blob/main/doc/JA/syntax/21_visibility.md#%E5%A4%96%E9%83%A8%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A7%E3%81%AE%E5%8F%AF%E8%A6%96%E6%80%A7)に詳細が書かれている

## データの読み込み

私はデータ分析でPythonを使うので、データの読み込みができないとほぼ仕事にはならない

将来的にはPandas(Polar)が使えるようになるので、ここの章は丸ごと省略できるがまだできないので書いておく

データは`/data/data.csv`に保存してある

```csv
1,2,3,4,5
6,7,8,9,10
11,12,13,14,15
```

まだ、`strip()`メソッドがないので、データに余計な空白や改行があると以下のコードは使えない

date型やfloat型に対応するにはもう一工夫が必要になる

```python
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だとできてしまう

```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で有名になった[所有権](https://doc.rust-jp.rs/book-ja/ch04-01-what-is-ownership.html)もErgでも一応[存在](https://github.com/erg-lang/erg/blob/main/doc/JA/syntax/20_ownership.md)するので使用した変数は所有権が移動したりして使えなくなったりなど制限がある

## データの可視化

先程の読み込んだデータは`Matplotlib`で可視化することができる

```python
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!()
```

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/98e3cca5-c68f-c09c-1391-aff07c1d9593.png)

恐らくだが`plt = pyimport "matplotlib.pyplot"`はできない

`plt = matplotlib.pyplot`もできない

なので、プロシージャを変数に代入して使っている

現状使えるのは以下の6つだけ

- show!
- plot!
- xlabel!
- ylabel!
- title!
- legend!

`show!`以外は返り値があるため、その返り値を使わないとwarningが出てしまう

なので、`_(プレースホルダ)`や`discard`を使用する(どちらでも良いはず)

コンパイル時のwarningはまだ消せないはず

よくあるsin波は以下のように書ける

```python
{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!()
```

![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/e427b064-fc37-183b-ce76-1997026dcc8a.png)

`x = np.arange(-5, 5, -0.1)`を`x = 0..10`の代わりに使いたがったが、まだ`ndarray`がイテレートできなかった


## 終わりに

徐々にではあるがErgの基礎的な機能だけではなく、Pythonの資産も使えるようになってきた

後はひたすら型を付ける作業をするだけで基本的にライブラリは使えるようになる

まだ使える機能が少なかったり冗長な部分が多いが、すぐに書けるという点ではRustよりはるかに書きやすかった

環境構築に関しては現状は`venv`でPythonの環境を作るだけでErgが認識してくれるので問題はなかった

外部のライブラリを使うという点ではまだ機能(型付け作業)が少ないのと、Ergでどのように書くのが良いのかの知見が足りなかったので上手く表現しきれていなかった

ただ、大まかなErgの書き方と、どのように資産を使うのかの概要は伝えられた思う

全体的に粗削りな部分が多いがこれから少しずつ改善されていくと思うし、私自身改善していくつもりである(興味を持ってくれたらIssueやPRを作ってください)