「大人の教養・知識・気付き」を伸ばすブログ

一流の大人(ビジネスマン、政治家、リーダー…)として知っておきたい、教養・社会動向を意外なところから取り上げ学ぶことで“気付く力”を伸ばすブログです。データ分析・語学に力点を置いています。 →現在、コンサルタントの雛になるべく、少しずつ勉強中です(※2024年1月21日改訂)。

MENU

Juliaを使うときのTipsをまとめる(その01/X)

 \mathrm{Julia}を使うのに、今一度

で使い方を整理する。

前回

power-of-awareness.com

注意

 参照文献はかなり古い(2019年)ため、現在のバージョンでは動作しない関数などが多いとの評判がある。そこでそういった齟齬があった場合は随時コメントする。なお筆者の環境は、

アプリケーション バージョン
\mathrm{Julia} 1.8.0
\mathrm{Jupyter} \mathrm{Notebook} 6.5.2

である。

0. まえがき

 \mathrm{Julia}は動的かつ高性能なプログラミング言語である。これからは

  • \mathrm{Julia}の強力なツールやデータ構造、パッケージ群を用いる。
  • 数値計算、分散処理、高性能計算を行うレシピを扱う。
  • データサイエンス用のプログラムを並列処理やメモリ使用最適化で高速化する方法を扱う。
  • メタプログラミング関数型プログラミングについても扱う。
  • データベースの使い方やデータ処理を行う方法なども扱う。

0.1 Juliaの特徴

 \mathrm{Julia}は「科学技術計算を指向した動的な言語」である。すなわち

  • 動的言語の特徴である書きやすさ、試行のしやすさを維持しつつ、
  • 性的言語と同程度の性能を得ようとする

言語である。
 \mathrm{Julia}は以下のような特徴を持つ:

  • \mathrm{JIT}コンパイルされ、最適化された機械語が実行される実行機構

     \mathrm{Julia}は従来のコンパイル言語とは異なり、関数が実際に良い出されたときにその場でコンパイルする。このため実際に呼び出されたときの情報を用いることができ、静的な型付き言語よりも有利な条件で機械コードを生成可能である。
  • 配列の配列とは異なる多次元配列

     \mathrm{Julia}は多次元配列をサポートしている。他言語とは異なり、\mathrm{Julia}には多次元配列しかない。

     他方で\mathrm{Julia}\mathrm{Python}などとは異なり、インデックスが1から始まる1オリジンである。

     また行列はメモリ上、列方向優先で格納されている。

     更に\mathrm{Python}でのリストと同じような内包記法で配列を作成できる。

     \mathrm{Juali}にはブロードキャストという機能があり、配列の全要素に任意の関数を適用し、結果を再度配列として収集できる。

     大規模な配列をコピーしないでメモリを節約しつつ扱える\mathrm{view}という機能がある。
  • 柔軟な関数の利用

     関数は\mathrm{function}キーワードを用いて定義する(使わなくても定義は可能)。\mathrm{return}文は不要である。また\mathrm{Python}\mathrm{lambda}文に相当する無名関数も定義できる。

     更に取得した資源を確実に解放させるために、ブロック部分を関数として関数名を呼び出す\mathrm{do}機能がある。

     またパイプ演算子(引数 |> 関数名)により複数の処理を連続して行う際に見通しをよくすることができる。
  • 構造体と総称関数によるオブジェクト指向プログラミング

     \mathrm{Julia}には\mathrm{C}++や\mathrm{Python}にあるクラスが無く、構造体と総称関数によりオブジェクト指向的なプログラミングを実現している。

     更に、1つの関数名に対して実体がいくつも定義され、引数の型に応じて実行される実体が選択される総称関数が実装されている。

     こうして構造体と総称関数を用いることでオブジェクト指向を実現している。
  • ジェネリクス

     \mathrm{Julia}では、型を定義する際にその一部を型パラメータとして残し、実際に用いるときに具体的な型を指定するパラメータ化型(他言語でジェネリクスと呼ばれる機能)を持つ。
  • 共用型

     「複数の型のいずれか」を意味する共用型\mathrm{Union}\mathrm{Julia}に用意されている。

0.2. 配列

0.2.1 多次元配列

 \mathrm{Julia}は多次元配列しかない。

# 配列の例
a = [1, 2]
println(a)
0.2.2 1オリジン

 \mathrm{Julia}のインデックスは1から始まる。

########################
### Juliaは1オリジン ###
########################

a = [1,2,3]

try
    println(a[0])
catch
    println("インデックスは1から始まります。0はダメです")
end
0.2.3 カラムメジャー

 \mathrm{Julia}では、行列を表現する際の1次元目は列である。

m = [1 2; 3 4]

println(m)
println("$(m[1]),$(m[2]),$(m[3]),$(m[4])")
0.2.4 配列の内包表記

 基本的には\mathrm{Python}のリストの内包表記と同じ方法で1次元配列を内包表記できる。

list1 = [num for num in collect(1:1:100) if num % 2 == 0]
println(list1)

# 前置ifはelseを指定するときにあると良さそう: Falseだと自動でnothingを返す
list2 = [(if num % 2 ==0 num end) for num in collect(1:1:100)]
println(list2)

0.3 関数とその使い方

 \mathrm{Julia}では関数を\mathrm{function}キーワードを用いて定義する。

##################
### 関数の定義 ###
##################

# 関数の最後の文の値が暗黙の返り値になる。途中で抜ける場合以外はreturn文は不要
function add1(x, y)
    x + y
end

println(add1(2,3))

# 簡便型
add2(x, y) = x + y

println(add2(2,3))

# 無名関数
(x, y) -> x + y

list = map(x -> 2x, 1:3) # 無名関数は他の関数の引数として用いるのが普通
println(list)
0.3.1 doブロック構文
map(x->begin
           if x < 0 && iseven(x)
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end,
    [A, B, C])

# doを用いると以下のようにより簡潔に書ける
map([A, B, C]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end
0.3.2 パイプ演算子

 パイプ演算子 |> は、関数と引数の順番を入れ替える。すなわち

  • 引数 |> 関数
  • 関数(引数)

は等価である。これは複数の処理を連続して行う際に見通しを良くする。

####################
### パイプ演算子 ###
####################

# パイプ演算子の方が見通しが良くなる
result1 = unique(sort([1,4,2,3,2]))

result2 = [1,4,2,3,2] |> sort |> unique

println(result1 == result2)
println(result1)
println(result2)

0.4 Juliaによる「オブジェクト指向」プログラミング

 \mathrm{Julia}では構造体と総称関数によってオブジェクト指向的なプログラミングを実現する。

0.4.1 構造体

 \mathrm{Julia}の構造体は\mathrm{C}のものとほぼ同じである。

# デフォルトではimmutable
struct Coordinate1
    x::Float64
    y::Float64
end

# mutableキーワードを付けるとmutableになる
mutable struct Coordinate2
    x::Float64
    y::Float64
end
0.4.2 総称関数と多重ディスパッチ

 総称関数とは、1つの関数名に対して実体がいくつも定義されている関数で、引数の型に応じて実行される実体が選択される関数である。

####################
### 総称関数の例 ###
####################

# メソッド(個々の型に対する関数定義)を定義する
double(a::Number) = a * 2
double(a::String) = a ^ 2

println(methods(double))

println(double(2))
println(double("aa"))


############################
### 構造体と総称関数の例 ###
############################

mutable struct Vector2D
    x::Float64
    y::Float64
end

function add(v1::Vector2D, v2::Vector2D)
    Vector2D(v1.x + v2.x, v1.y + v2.y)
end

v1 = Vector2D(2, 4)
v2 = Vector2D(3, 5)

println(add(v1, v2))

0.5 パラメータ化型

0.5.1 パラメータ化形と型パラメータ

 \mathrm{Julia}では、型を定義する際にその一部を型パラメータとして残しておき、実際に利用する際に具体的な型を指定することができる。

##########################
### 型パラメータの指定 ###
##########################

# 型をReal型にする
mutable struct Vectors2D{T <:Real}
    x::T
    y::T
end

Vectors2D{Int32}(0,1)
0.5.2 共変と不変

 ある型\mathrm{A,B}の間にサブタイプの関係がある場合に、これらを用いて作成した何らかの型\mathrm{X\{A\}},\mathrm{X\{B\}}にでき得るサブタイプ関係を変位という。

言語要素 変位
タプル 共変
配列 非変
名前付き

タプル
非変
構造体 非変
############
### 変位 ###
############

abstract type A end
struct   B <: A end # BがAのサブタイプであることを B<: Aと書く

struct C{T}
    a::T
end

# 変位の確認
true_or_false1_1 = (B(),) isa Tuple{A}                        # タプルか?
true_or_false2_1 = [B()] isa Vector{A}                        # 配列か?
true_or_false3_1 = (x = B(),) isa NamedTuple{(:x,),Tuple{A}} # 名前付きタプルか?
true_or_false4_1 = C(B()) isa C{A}                            # 構造体か?

println(true_or_false1_1)
println(true_or_false2_1)
println(true_or_false3_1)
println(true_or_false4_1)

println("")
println("*****************")
println("")

# <:を用いると共変にできる
true_or_false1_2 = (B(),) isa Tuple{A}                        # タプルか?
true_or_false2_2 = [B()] isa Vector{<:A}                        # 配列か?
true_or_false3_2 = (x = B(),) isa NamedTuple{(:x,),Tuple{T}} where T <: A # 名前付きタプルか?
true_or_false4_2 = C(B()) isa C{<:A}                            # 構造体か?

println(true_or_false1_2)
println(true_or_false2_2)
println(true_or_false3_2)
println(true_or_false4_2)

0.6 共用型

 複数の型のいずれかを意味する共用型(\mathrm{Union})という抽象的な抽象型が存在する。

# 共用型の例
IntOrString = Unoon{Int, String}

0.7 マクロと文字列マクロ

0.7.1 マクロ
##################
### マクロの例 ###
##################

import Dates

macro mytime(expr)
    esc(quote
            now = Dates.now()
            tmp = $expr
            println(Dates.now() - now)
            tmp
        end
    )
end

@mytime(sum(rand(1000000)))
0.7.2 文字列マクロ

 文字列マクロは\mathrm{xxx}_\mathrm{str}(\mathrm{xxx}は適当な名称)の形をした名前を持つ特殊なマクロである。

0.8 その他の言語機能

0.8.1 タプル

 タプルは任意の型の値を複数保持できる。複数の値を返す関数の返り値を格納するのにも用いられる。

typeof((1, "test"))
a, b = f()

 配列をタプルに変換するには関数\mathrm{tuple}を用いる。また関数\mathrm{ntuple}を用いることもできる。第1引数にインデックス番号から値を作る関数を、第2引数にタプルの要素数を取る。

ntuple(i -> j, 10)
0.8.2 Symbol

 シンボルは\mathrm{String}のように文字列を表す。こちらの方がメモリ効率が良く、高速である。

0.8.3 Dict

 \mathrm{Julia}には辞書型を欠くための特別なリテラル記法はなく、\mathrm{Dict}関数を用いて作成する。

Dict(:a => 1, :b => 2)
ans[:a]
0.8.4 ショートカット構文
  • 条件||実行文:条件が偽のときのみ実行文が実行される
  • 条件 &&実行文:条件が真のときのみ実行文が実行される
0.8.5 文字列補間

 $を使って文字列に値を埋め込むことができる。これを文字列補間(\mathrm{string\ interpolation})という。

x = 1.0
println("x = $x")
println("3x = $(3 * x)")

# 細かく数値の指定をしたければ、printf,sprintfマクロを用いる
using Printf
@printf("%0.2f", π)
0.8.6 レンジ

 \mathrm{Julia}には連続した数字の列を作成する機能が用意されている。開始点と終了点を:で区切る*1

for i in 1:10
    println("i = $(i)")
end

for i in collect(1:2:10) # collect(引数1,引数2,引数3):引数1から引数2おきに引数3までの配列
    println("i = $(i)")
end
0.8.7 nothingとmissing

 \mathrm{Julia}には他言語の\mathrm{none}\mathrm{null}に相当する値として、\mathrm{nothing}\mathrm{missing}の2つが用意されている。
 いずれも値がない事を意味するが、\mathrm{nothing}は値が無いことが異常である場合に、\mathrm{missing}は値が無いことが正常である場合に用いる。このため\mathrm{nothing}に対して演算を施すとエラーを起こす一方で、\mathrm{missing}に対して演算を施すと\mathrm{missing}が伝搬していくのみである。

########################
### nothingとmissing ###
########################

err_val1 = nothing
err_val2 = missing

try
    println(err_val1 + 1)
catch
    println("$(err_val1)に演算を施すとエラーになります。")
end

println("$(err_val2)に演算を施すと$(err_val2 + 1)を返します(エラーを起こしません)。")
0.8.8 数式の表現

 \mathrm{Julia}はプログラムの字面を可能な限り数式に近づけられるように設計されており、任意の\mathrm{unicode}をプログラム中から利用できるようになっている。たとえば\piは\piと入力した後に[Tab]キーを押すことで変換される。

docs.julialang.org

 更に演算子にも様々な文字を利用できる。たとえば整数除算は÷、排他的論理和\veebarを用いる。

 また\mathrm{Julia}では数値リテラルと変数の掛け算の差異に掛け算記号を省略できる。たとえば2*\piではなくて2\piと書けばよい。

0.8.9 イテレータ

 \mathrm{Julia}にもイテレータが存在する。
 ユーザーが定義した型でイテレータを定義するには\mathrm{iterate}関数を定義する。

Base.iterate(iter [, state]) -> Union{Nothing, Tuple{Any, Any}}
### サンプル
x = [5,2,3,1,2,3,1,1]

sumx = 0
#iterate関数により最初の要素と次のindexを取得
next = iterate(x)
println(next)

#iteratorの要素がなくなるまで繰り返す
while next !== nothing
    #現在の要素をi, 次のindexをstateに格納
    (i, state) = next
    sumx += i
    #次の計算に使用する要素と次の次のindexをnextに格納
    next = iterate(x, state)
end

@show(sumx)

*1:\mathrm{R}と似た方法である。]

プライバシーポリシー お問い合わせ