Julia入門メモ

# モチベーション

Clojureの実行速度だと困ることが多々あるので、Juliaを試している。

Pythonのnumpyで遅すぎるということはないが、オブジェクト指向が起因でデータ構造が入り乱れる (リストとpandasとnumpyのように) のが好みでないし、REPLをより高速に回せるのであればそれに越したことはない。Common Lispのリストならば単一のデータ構造で何でもできるが、Clojureと同様にやはり遅くなってしまう[^1]。

# メモ
- 文字列はダブルクオートで、シングルクオートは不可。
- `map`をゴリゴリ利用しても速い。Python/numpyでもここまで速くはないし、こうも自然に`map`は利用できないはず。
  - 「①抽象データ型 (シーケンス) と②シーケンス操作関数だけで何でもやろう」「自作変数も自作関数も少なくて短い方が良い」というClojureのシンプル精神は、Julia上でもある程度までは実現可能に思う。イミュータブル・遅延評価は再現が難しい[^clj]けれど、無くても実用上は[^2]そこまで問題にならず、速度とのトレードオフとしては良い選択。
  - バクを取るためにコードを書いているわけではないように、最適化をするためにコードを書いているわけではない。
  - `map`/`filter`はClojureと同じ感覚で使える。`apply`を使いたい場合はリスト内包表記でごまかす。
  - `map`内で`if`文を使いたいときは、[`do`構文](https://mnru.github.io/julia-doc-ja-v1.0/manual/functions.html#%E9%96%A2%E6%95%B0%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8BDo%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E6%A7%8B%E6%96%87-1)を利用して見た目を整える。
      - `do`は無名関数を`map`の第一引数に渡す。`filter`にも使用できる。
    ```julia
    map([A, B, C]) do x
        if x < 0 && iseven(x)
            return 0
        elseif x == 0
            return 1
        else
            return x
        end
    end
    ```
  - 無名関数は`x -> x + 3`で`(fn [x] (+ x 3))`。
  - 引数を1つしか取らない関数については、スレッド的なことをするパイプ演算子がある`12 |> x -> x + 3 #=> 15`。
  - 無名関数を使えば、複数の引数が必要な関数についてもパイプ演算子が使える。
    ```julia
    10 |> x -> Dict(:ten => x)
    # Dict{Symbol, Int64} with 1 entry:
    #   :ten => 10 
    ```

- 関数を閉じるために`end`を使う言語に忌避感を持っていたが、コード内で関数をまとめておけば、あまり気にならない。
- デフォルト引数は、名前を書いて渡せない。Pythonと違うところ。
    ```julia
    function hoge(a, b=2; c=3)
        println(a, b, c)
    end
    hoge(1, 2) # 123 期待通り
    hoge(1, 3) # 133 期待通りでない
    hoge(1, 2, 3) # ERROR (キーワード引数cへ、名前を書かずに渡している)
    hoge(1, b=2) # ERROR (デフォルト引数bへ、名前を書いて渡している)
    hoge(1, c=3) # 123 期待通り
    hoge(1, 2, c=3) # 123 期待通り
    hoge(1, b=2, 3) # ERROR (デフォルト引数bへ、名前を書いて渡している)
    hoge(1, b=2, c=3) # ERROR (キーワード引数cへ、名前を書かずに渡している)
    hoge(1, b=2; 3) # ERROR (いずれにしろ、セミコロンより後ろは名前を書いて渡す必要がある)
    ```
- 関数へのデータ渡しは、基本的に参照渡し ([共有渡し](https://mnru.github.io/julia-doc-ja-v1.0/manual/functions.html#:~:text=Julia%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AF%22%E5%85%B1%E6%9C%89,%E5%81%B4%E3%81%8B%E3%82%89%E3%82%82%E8%A6%8B%E3%81%88%E3%81%BE%E3%81%99%E3%80%82))。
- `ifelse`文は便利だが、分岐に関わらず`true`節・`false`節の両方が評価されてしまう。
- `or`/`and`ではなく、`||`/`&&`。
- `while`内でカウンタを進める際、配列は1始まりなので注意。
    ```julia
    a = [1, 2, 3, 4, 5]

    # こうすることが多いはず
    i = 1
    while i < stop
        print(a[i])
        i += 1
    end

    # これはエラー
    i = 0
    while i < stop
        print(a[i])
        i += 1
    end
    ```
- 関数内で`return`を書かなくても、最後に評価される値が返される。関数型言語的。
- 三項演算子は連結できる `false ? 1 : false ? 2 : 3 #=> 3`。
- データ型は下記 ([参考](http://www.nct9.ne.jp/m_hiroi/light/juliaa01.html#abc02))。
  - 数字 (整数、小数、複素数、分数)
  - Bool (`true`/`false`)
  - 文字 ('a', 'A')
  - 文字列 ("string"): `*`で文字列結合、`$(var)`で変数展開
      - 文字列→小数: `parse(Float64, "3.14") #=> 3.14`
      - 部分文字列: `chop("string", head=1, tail=2) #=> "tri" `
  - タブル: Pythonと同じ。
  - 辞書: `Dict(key=>val)`で生成。
      - Pythonの`:`と異なり、`=>`と2文字も打つのが手間。辞書のキーに文字列を使うと、ダブルクオートを打つのが手間。良いマナーかは調べが付かなかったが、シンボルをキーとしたほうが楽。
        ```julia
        d = Dict(:name => "Julia", :version => 1.8)
        d[:name] # "Julia"
        ```
  - 集合: Pythonと同じ。
  - 範囲オブジェクト: `start:stop`か`start:step:stop`の形式。
  - 配列
      - `[1 2 3]` (1×3 Matrix{Int64}) でも、`[1; 2; 3]` (3-element Vector{Int64})でも、`[1, 2, 3]` (3-element Vector{Int64})でも生成できる。
          - Vectorは Nx1 Matrix の別称。なので`[1, 2] + [1, 2] == [2, 4]`だが、`[1 2] + [1, 2]`は行列計算ができずにエラー。
      - 行列の積は`[1 2] * [3, 4] == 11`。行列の要素ごとの積 (アダマール積) は`[1, 2] .* [3, 4] == [3, 8]`のように`.`を付ける。
      - Pythonと異なり、スライスの終わりを行列サイズ以上にはできない。`a = [1, 2, 3]; a[1:100]`はエラー。
      - 配列は1始まりなので、Pythonと異なり`stop-start`の値が行列のサイズとは**等しくならない**。`a[2:4]`には、値が3個入っている。
      - 例えば1次元と2次元どちらかの配列を列方向で半分に区切るような関数は、`if`文を使わず`size`を使い、常に2次元を扱うような切り出し方をする。
          - Pythonのように`int(1.5)`でint型にはできない。Juliaで`Int`を使う際は、中の小数はx.0になっている必要がある。
          - このやり方をすると、Vector型を分けたときにMatrix Nx1型で返ってくる。実行速度に差異はあるのだろうが、今の所、実用上は問題を感じていない[^speed]。Vector型しか受け付けないような関数があった場合、`vec()`関数でVector型に戻すこともできる。
        ```julia
        function half(a)
            l = size(a)[1] / 2
            a[1:Int(ceil(l)), :], a[Int(ceil(l))+1:end, :]
        end
        half([1, 2, 3, 4, 5]) # ([1; 2; 3;;], [4; 5;;])
        half([1 2; 3 4; 5 6]) # ([1 2; 3 4], [5 6])
        half([1, 2, 3, 4, 5])[1] + [1, 1, 1]
        # 3×1 Matrix{Int64}:
        #  2
        #  3
        #  4
        ```
      - Pythonと異なり、配列スライスの終わりのインデックスが結果に含まれる[^3]。

        ```python
        # Python
        a = [1, 2, 3, 4, 5]
        a[0:3], a[3:] # ([1, 2, 3], [4, 5])
        a[1:1] # []
        ```
        ```julia
        # Julia
        a = [1, 2, 3, 4, 5]
        a[1:3], a[4:end] # ([1, 2, 3], [4, 5])
        a[1:1] # 1
        ```
    - 配列が入った配列・タプル`a = [[1, 2, 3], [4, 5, 6]]`を二次元配列`[1 2 3; 4 5 6]`にするには、`stack(a, dims=1)`→[参考 (英語)](https://discourse.julialang.org/t/how-to-convert-vector-of-vectors-to-matrix/72609/26)。
        - `map`は結果を引数に合わせて配列かタプルに入れて返すので、処理した複数のベクタを`stack`でくっつけられる。
            ```julia
            map(x -> [x, x, x], [1, 2, 3])
            # 3-element Vector{Vector{Int64}}:
            #  [1, 1, 1]
            #  [2, 2, 2]
            #  [3, 3, 3]
            using DataFrames # あるいは、Julia 1.9からは標準で使えるらしい
            stack(map(x -> [x, x, x], [1, 2, 3]), dims=1)
            # 3×3 Matrix{Int64}:
            #  1  1  1
            #  2  2  2
            #  3  3  3
            ```
    - 配列の列ごと・行ごとの値の合計は、`sum(a, dims=n)`。`n=1`で行の和、`n=2`で列の和。
    - 配列の最大値は`maximum`。`max`ではない。



[^1]: [スタイルガイド](http://lisphub.jp/doc/google-common-lisp-style-guide/lispguide.xml#%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E4%B9%B1%E7%94%A8%E3%81%AF%E3%81%97%E3%81%AA%E3%81%84)でも、『リストの乱用はしない』ことが推奨されている。
[^2]: 哲学的には大問題かもしれない。
[^3]: そもそもPythonとJuliaで配列の数え方が違うのでこの言い方はおかしいけれど、こう言いたくなる。
[^clj]: タプルとジェネレータ式で模倣できるように思われる。それにミュータブルがもたらす混乱については、破壊的な関数に`!`を付ける慣習だけで、(小規模なコードであれば) 対応できる。
[^speed]: 『パフォーマンスのためにベクトル化する必要がない。ベクトル化しないコードも速い』, Julia 1.0 ドキュメント, https://mnru.github.io/julia-doc-ja-v1.0/index.html