Julia - 関数の broadcast ( f.(arg...) ) をオーバーロードする方法

どこで使うと便利なのかわからないけれど、一応できたのでメモ。
公式ドキュメントだけでこの方法を思いつく人は相当訓練された Julian だと思う(思いたい)。

Julia 1.0 でのやり方

f.(arg...) の挙動を変えたい場合、broadcast でなく、broadcastedオーバーロードすれば良い。

julia> f(x) = println("Normal")
f (generic function with 1 method)

julia> f(1)
Normal

julia> f.(1)
Normal

# 第一引数を `::typeof(挙動を変えたい関数)` とするのがミソ
julia> Base.Broadcast.broadcasted(::typeof(f), x) = println("Broadcast")

julia> f(1)
Normal

julia> f.(1)
Broadcast

julia> f.([1,2])
Broadcast

上手くいかない方法

ドキュメントに

A special syntax exists for broadcasting: f.(args...) is equivalent to broadcast(f, args...)

とあるけど、broadcastオーバーロードすればいいわけではない。

Arrays · The Julia Language

julia> f(x) = println("Normal")
f (generic function with 1 method)

julia> f(1)
Normal

julia> f.(1)
Normal

julia> f.(x::Int) = println("Broadcast")
ERROR: syntax: invalid syntax "f.(x::Int) = ..."

julia> Base.broadcast(f, x::Int)= println("Broadcast")

julia> f(1)
Normal

julia> f.(1)
Normal

julia> Base.broadcast(::typeof(f), x::Int)= println("Broadcast")

julia> f.(1)
Normal

julia> f.([1,2])
Normal
Normal
2-element Array{Nothing,1}:
 nothing
 nothing

Julia 0.6 のときは broadcastオーバーロードすればよかった。

julia> f(x) = println("Default")
f (generic function with 1 method)

julia> f(1)
Default

julia> f.(1)
Default

julia> Base.broadcast(::typeof(f), x) = println("Broadcast")

julia> f(1)
Default

julia> f.(1) # ここの挙動が Julia 1.0 と違う
Default

julia> f.([1, 2])
Broadcast

参考

x.*x not customizable · Issue #22053 · JuliaLang/julia · GitHub

Interfaces · The Julia Language

Julia - 定義した method を消す方法

環境

Julia 1.0

無駄な method を消す

Julia といえば多重ディスパッチが有名ですよね*1

julia> f(x::Int64) = println("Intだよ")
f (generic function with 1 method)

julia> f(x::Float64) = println("Floatだよ")
f (generic function with 2 methods)

julia> f(Int64(1))
Intだよ

julia> f(Float64(1))
Floatだよ

こんな感じに同じ関数でも引数の型を指定して定義することで、型に応じて動作を変えることができるので便利です。

しかし、ここで一つ問題があります。 上記の例では引数が Int64 だったら Intだよ と出力するようにしましたが、後々になって Integer の subtype 全体に対して 整数だよ と出力したくなったとしましょう。

素直に考えたら次のように定義すると思います。

julia> f(x::Integer) = println("整数だよ")
f (generic function with 3 methods)

しかし、この定義の仕方だと Int64 の数値を入れると一番初めに定義した method が呼び出されてしまいます。

julia> f(Int32(1))
整数だよ

julia> f(Int64(1))
Intだよ

julia> methods(f)
# 3 methods for generic function "f":
[1] f(x::Float64) in Main at REPL[2]:1
[2] f(x::Int64) in Main at REPL[1]:1
[3] f(x::Integer) in Main at REPL[5]:1

単純な対処法は f(x::Int64) を上書きしてしまうことでしょう。

f(x::Int64) = println("整数だよ")

しかし、また気が変わって Integer の subtype 全体に対して Intだよ と出力したくなった場合、f(x::Int64)f(x::Integer) の2つを上書きしなければなりません。これはどう考えても面倒です。

f(x::Int64) = println("Intだよ")
f(x::Integer) = println("Intだよ")

今は Integer の subtype ならば全て同じ動作にしたいので f(x::Int64) が邪魔です。いっそなかったことにしたいです。

そんな時に便利なのが Base.delete_method です。2018/9/24 現在公式ドキュメントに記述は一切ありません。 which と組み合わせて以下のように使います。

julia> f(Int64(1))
Intだよ

julia> Base.delete_method(@which f(Int64(1)))

julia> f(Int64(1))
整数だよ

julia> methods(f)
# 2 methods for generic function "f":
[1] f(x::Float64) in Main at REPL[2]:1
[2] f(x::Integer) in Main at REPL[5]:1

見事に f(x::Int64) の method が消えました。@which マクロを使う場合は消したい method の具体的な値を入れてください。which を使う場合、

Base.delete_method( which(f, (Int64, ) ) )

と書きます。具体的な値を入れてしまう方が楽だとは思いますが、「この型の method を消してるんだぞ!」と強調したいときは which 使うと良いのではないでしょうか。

とまぁ、無事に不必要な method を消すことができた訳ですが、正直に言ってこの程度のことだったら一度プロセスを切ってもう一度 Julia を起動し直したほうが断然楽ですね。

ものすごく読み込みが遅いパッケージを使っている最中ならば Base.delete_method を使ったほうが楽かもしれませんが、大抵の場合は再起動したほうが早いでしょう。

上記の例ではあまりメリットを感じないので、もう少し実用的なものを考えてみましょう。私は pretty-print 好きなので pretty-print に応用しましょう*2

pretty-print

ここでは複素数極形式で表示するようにしてみましょう。

julia> 1.0 + im
1.0 + 1.0im

julia> function Base.show(io::IO, z::Complex{Float64})
           r = round(abs(z), digits=3)
           θ = round(atan(z.im, z.re), digits=3)
           print(io, r, " * ( cos( ", θ, " ),  sin( ", θ, " ) )")
       end

julia> 1.0 + im
1.414 * ( cos( 0.785 ),  sin( 0.785 ) )

定義した method を消します。

julia> Base.delete_method(which(Base.show, (IO, Complex{Float64})))

julia> 1.0 + im
1.0 + 1.0im

どうですか!今までは pretty-print を自分で定義した場合、元の書式に戻すには再起動するしかなかったのに Base.delete_method を使うことで再起動を回避できたではありませんか!!!

とはいえ、これでは切り替えが面倒なので簡単に切り替えできるように関数を定義しましょう。

julia> 1.0 + im
1.0 + 1.0im

julia> POLAR = false
false

julia> function polarform(x::Bool)
           if !POLAR && x
               eval(quote
                  function Base.show(io::IO, z::Complex{Float64})
                      r = round(abs(z), digits=3)
                      θ = round(atan(z.im, z.re), digits=3)
                      print(io, r, " * ( cos( ", θ, " ),  sin( ", θ, " ) )")
                  end
              end)
              global POLAR = true
              return 
          end

          if POLAR && !x
              Base.delete_method(which(Base.show, (IO, Complex{Float64})))
              global POLAR = false
              return
          end
       end
polarform (generic function with 1 method)

julia> polarform(true)

julia> 1.0 + im
1.414 * ( cos( 0.785 ),  sin( 0.785 ) )

julia> polarform(false)

julia> 1.0 + im
1.0 + 1.0im

polarform(true) にすると極形式で、polarform(false) にすると Cartesian Form*3 で表示することに成功しました!

ついに私は pretty-print の切替方法を確立してしまった。 今後のパッケージ作りに役に立ちそうだ。

落とし穴

先程の pretty-print の例では Base.delete_method を使えばいつでもデフォルトの pretty-print に戻せるように感じたかもしれませんが、そんなことは全くもってありません。

複素数の pretty-print に関しては Complex{Float64} の supertype である Complex にのみ定義されているから先程の例は上手くいっただけで、普通はこんなに上手くいかないので注意してください。

export されてもいなければ、公式ドキュメントにすら書かれていない、エンドユーザーが使うことを想定してない関数がそんなにおいしいわけないですね。

julia> 1.0+im
1.0 + 1.0im

julia> Complex{Float64} <: Complex
true

julia> @which show(stdout, 1.0+im)
show(io::IO, z::Complex) in Base at complex.jl:183

julia> Base.delete_method(@which Base.show(stdout, 1.0+im))

julia> 1.0+im
Complex{Float64}(1.0, 1.0)

julia> @which show(stdout, 1.0+im)
show(io::IO, x) in Base at show.jl:315

julia> Base.delete_method(@which Base.show(stdout, 1.0+im))

julia> @which show(stdout, 1.0+im)
ERROR: no unique matching method found for the specified argument types
Stacktrace:
 [1] which(::Any, ::Any) at ./reflection.jl:922
 [2] top-level scope at none:0

julia> 1.0+im
Error showing value of type Complex{Float64}:
ERROR: MethodError: no method matching display(::Complex{Float64})
Closest candidates are:
  display(::Any) at multimedia.jl:284
  display(::AbstractDisplay, ::AbstractString, ::Any) at multimedia.jl:178
  display(::AbstractString, ::Any) at multimedia.jl:179
  ...
Stacktrace:
 [1] display(::Complex{Float64}) at ./multimedia.jl:294

参考

Limitations · Revise.jl

LICENSE

  • Copyright 2018 goropikari
  • Licensed under MIT:

The MIT License | Open Source Initiative

*1:私はいまいち理解しきれていませんが

*2:むしろ pretty-print のためにこの method の消去方法を覚えました

*3:日本語で何ていうの?

Julia - 野良パッケージをパッケージ名だけで追加できるようにする: Custom General Repository の作り方

Julia でパッケージを追加する場合、pkg mode で

(v1.0) pkg> add Example

などとすれば良いですよね、公式パッケージ*1の場合は。

野良パッケージの場合は次の例のように github の URL を打ち込まないといけません。

(v1.0) pkg> add https://github.com/goropikari/MyPackage.jl

パッケージ名だけならともかく URL を打ち込まないといけないというのは面倒です。公式パッケージ同様パッケージ名だけで追加したいと思うのは当然の願いでしょう。

この記事では Custom General Repository を作り、公式パッケージ同様 野良パッケージもパッケージ名だけで追加できるようにする方法を紹介します。

f:id:goropikarikun:20180922212714p:plain 上手くいくとこんな感じで野良パッケージがパッケージ名だけで追加することができるようになります。

目次

環境

  • OS: ArchLinux
    OS 依存する内容ではありませんが、Linux コマンドを使っているので適宜脳内変換してください。
  • Julia 1.0

Custom General Repository

Custom General Repository とは?

Custom General Repository とは何か?と普通の人なら思うでしょう。無理もありません、便宜上私がそう呼んでいるだけですから。

Julia 0.x 時代のパッケージマネージャーはパッケージの情報を METADATA から参照していましたが、Julia 1.0 からは METADATA に変わり General を参照するようになりました。

それで、これから我々がやろうとしていることを Julia 0.x 時代は Custom METADATA Repository と呼称していたので Custom General Repository としただけです。

github.com

github.com

Custom General Repository を作るメリット

現状の Julia では、パッケージ名で追加できるパッケージ公式パッケージ は同値です。

しかし、以前の記事にも書きましたが、公式パッケージにするためには README を英語で書いて、やりとりも英語でしなければならないので、非ネイティブな我々日本人にとってはハードルが高いものになっていることも事実でしょう。 おまけにレビュー付きなので「ちょっと練習がてら空のパッケージ登録をしてみよ」なんてことも出来ません。

goropikari.hatenablog.com

また、ローカルのコミュニティでしか使わないパッケージを運用したい場合や、" 田中コレクション " といった独自のパッケージ群を提供したい場合なども公式パッケージに登録することは適しているとは言えないでしょう。

そんな時に便利なのが Custom General Repository です。これを作ることによって自分たちの裁量によって野良パッケージを自分の General に登録することができ、公式パッケージ同様にパッケージ名だけでパッケージを追加することができるようになります。

Youtuber の 「チャンネル登録はこちら」ではありませんが、「私のパッケージ群を使いたい場合はこの URL を git clone♪」とするだけで他の人たちは気軽にあなたのパッケージを使えるようになります。

Custom General Repository を作る

以下の内容は私が実験しながら模索した方法であり、公式のやり方ではありません。そもそも、公式ドキュメントに独自の General を作るやり方は 2018/9/22 現在書いてありません*2

先にどんなものなのか試したい人は MyGeneral を配置する を読んでください。

まずはご自身の github にパッケージを管理するためのリポジトリを作ってください。ローカルネットワーク内のディレクトリなどでも良いのですが、わかりやすさのため今回は github で作ります。リポジトリの名前は何でもよいですが、ここでは MyGeneral としました。

以下はこの MyGeneral に私の野良パッケージ GameOfLife.jlMyPackage.jl を登録するまでの流れです。先頭が $ から始まるものは bash での操作を表します。

作ったリポジトリをクローンしてきて必要なファイルを作っておきます。

$ git clone https://github.com/goropikari/MyGeneral.git
$ cd MyGenaral
$ touch Registry.toml

$ mkdir GameOfLife
$ cd GameOfLife
$ touch Compat.toml Deps.toml Versions.toml Package.toml

Registry.toml の中身はこんな感じ。

name = "MyGeneral"
uuid = "0d136408-be1b-11e8-146b-1b5bc37d4128"
repo = "https://github.com/goropikari/MyGeneral.git"

[packages]
b53c8852-a93e-11e8-3341-5bdef8841476 = { name = "GameOfLife", path = "GameOfLife" }

name を作ったリポジトリMyGeneral にする。

uuid は適当に generate MyGeneral して出てきたものを使いました。

(v1.0) pkg> generate MyGeneral
Generating project MyGeneral:
    MyGeneral/Project.toml
    MyGeneral/src/MyGeneral.jl

shell> cat MyGeneral/Project.toml
authors = ["goropikari <goropikari56@gmail.com>"]
name = "MyGeneral"
uuid = "0d136408-be1b-11e8-146b-1b5bc37d4128"
version = "0.1.0"

[deps]

repo にはリポジトリがある URL を入れてください。

続く [packages] の項目ですが、初めの謎の文字列は GameOfLife.jlProject.toml に記載されている uuid です。path には GameOfLife.jl の情報が書かれているファイル*3がある場所を指します。今は MyGeneral の直下に置いたので ./GameOfLife となっています。

現在のディレクトリ構成

MyGeneral/
├── GameOfLife
│   ├── Compat.toml
│   ├── Deps.toml
│   ├── Package.toml
│   └── Versions.toml
└── Registry.toml

続いて Compat.toml, Deps.toml, Versions.toml, Package.toml に必要な情報を書いていきます。

Deps.toml

GameOfLife.jlProject.toml[deps] に書いてあったものをそのまま書きます。 最初の ["0.1"]GameOfLife.jl のバージョン 0.1.0 の意味です。 要するに GameOfLife.jlのバージョン x.x.x はこれらのパッケージに依存してますよ〜と書かれているのが Deps.tomlです。

["0.1"]
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

Versions.toml

GameOfLife.jl の tree オブジェクト の SHA-1ハッシュ を書きます。

["0.1.0"]
git-tree-sha1 = "0ea24bdd852ad9505eab96815e6f2c4dadffc018"

ハッシュ値は GameOfLife.jl パッケージ本体*4ディレクトリ上で

$ git cat-file -p HEAD
tree 0ea24bdd852ad9505eab96815e6f2c4dadffc018
parent 2f5c85c856f67d2db3152801507f30b640a83849
author goropikari <goropikari56@gmail.com> 1535299189 +0900
committer goropikari <goropikari56@gmail.com> 1535299189 +0900

とすると調べることができます。

Compat.toml

対応している Julia のバージョンなどを書きます。Pkg.jl のドキュメント に書いてある方法に従えば良いと思いますが、当面は以下のように書いておけばよいでしょう。

["0.1"]
julia = "0.7-1.1"

Package.toml

パッケージの情報を書きます。uuid は Project.toml に書いてあるものを書きます。 repo にはパッケージが置いてある github URL を書いておきます。

name = "GameOfLife"
uuid = "b53c8852-a93e-11e8-3341-5bdef8841476"
repo = "https://github.com/goropikari/GameOfLife.jl.git"

push する

MyPackage.jl の方も同様にして追加し、github に push します。

$ git add -A
$ git commit -m 'first commit'
$ git push

MyGeneral を配置する

作った MyGeneral~/.julia/registries に配置します。

$ julia -e 'using Pkg; Pkg.update()'
$ cd ~/.julia/registries
$ git clone https://github.com/goropikari/MyGeneral.git

Julia を起動し、パッケージ情報をアップデートするといつもの https://github.com/JuliaRegistries/General.git に加えて https://github.com/goropikari/MyGeneral.gitも読みにいっていることがわかります。

(v1.0) pkg> up
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
  Updating registry at `~/.julia/registries/MyGeneral`
  Updating git-repo `https://github.com/goropikari/MyGeneral.git`
 Resolving package versions...

(v1.0) pkg> add GameOfLife
 Resolving package versions...
 Installed GameOfLife ─ v0.1.0
  Updating `~/.julia/environments/v1.0/Project.toml`
  [b53c8852] + GameOfLife v0.1.0
  Updating `~/.julia/environments/v1.0/Manifest.toml`
  [b53c8852] + GameOfLife v0.1.0
  [2a0f44e3] + Base64 
  [b77e0a4c] + InteractiveUtils 
  [8f399da3] + Libdl 
  [37e2e46d] + LinearAlgebra 
  [d6f4376e] + Markdown 
  [3fa0cd96] + REPL 
  [6462fe0b] + Sockets 

(v1.0) pkg> add MyPackage
 Resolving package versions...
 Installed MyPackage ─ v0.1.0
  Updating `~/.julia/environments/v1.0/Project.toml`
  [f41f8282] + MyPackage v0.1.0
  Updating `~/.julia/environments/v1.0/Manifest.toml`
  [f41f8282] + MyPackage v0.1.0
  [9a3f8284] + Random 
  [9e88b42a] + Serialization

julia> using MyPackage
[ Info: Precompiling MyPackage [f41f8282-a16f-11e8-2120-1f4619698387]

julia> MyPackage.greet()
Hello World!

無事に野良パッケージをパッケージ名だけで追加することに成功しました。 他の人の Genaral も ~/.julia/registries にクローンしてくれば同様にパッケージを追加することが出来ます。また、初回の git clone 以降はパッケージマネージャーが pull してきてくれます。

ちなみに

公式パッケージの情報が入っている ~/.julia/registries/General を消すと、パッケージマネージャーは自分のリポジトリしか見に行かなくなるので、世間から切り離された孤独感を味わうことが出来ます。

$ rm -rf ~/.julia/registries/General
$ julia -q
(v1.0) pkg> up
  Updating registry at `~/.julia/registries/MyGeneral`
  Updating git-repo `https://github.com/goropikari/MyGeneral.git`
 Resolving package versions...
  Updating `~/.julia/environments/v1.0/Project.toml`
 [no changes]
  Updating `~/.julia/environments/v1.0/Manifest.toml`
 [no changes]

おわりに

以上 Custom General Repository の作り方の紹介でした。お疲れ様でした。

この記事の内容を使えば、公式パッケージにしなくてもパッケージ名だけで追加できるようになりますが、みなさんには是非公式パッケージ作りにチャレンジしていただきたいです。

Julia 1.0 がリリースされてから、Julia を「使ってみた」といったコメントは目にするようになりましたが、「パッケージを作った」という報告は全然目にしない気がします。野良パッケージですらその状況なので公式パッケージではなおのことです。
私も他の方のパッケージ作りに興味がありますし、まだまだわからないことも多いので情報を共有していただけると嬉しく思います。

この記事で使った MyGeneral 等は以下です。必要ならばご自由にご覧ください。*5

github.com

github.com

github.com

*1:ここでは METADATAに登録されているものを公式パッケージ、されていないものを野良パッケージと呼ぶこととします。

*2:多分

*3:Compat.toml, Deps.toml, Versions.toml, Package.toml があるディレクトリ。

*4:MyPackage 中のディレクトリでない

*5:人に胸を張って見せられるようなコードではありませんが。

Julia - 野良パッケージを公式パッケージにする方法

先日、DiracNotation.jl という拙作が METADATA.jl にマージされ、野良パッケージから公式パッケージ*1へと昇格しました。 Julia 1.0 が出て以降では初めてパッケージを新規登録したので不安もあったのですが、とりあえず無事にマージされてほっとしています。

github.com

最近、METADATA.jl を覗いていると新規のパッケージ登録も多く見られるので、自分のパッケージも公式パッケージにしたくてウズウズしている Japanese Julian*2 もきっと多いことでしょう。

しかし、Julia 1.0 になって公式ドキュメントから公式パッケージへの登録方法が消え、またパッケージの管理方法も変わったので、どうしたら公式パッケージとして登録できるのか分からず困っている Julian も多いのではないでしょうか。

そこで今回は野良パッケージを公式パッケージに登録するまでの方法を紹介します。

(注) Julia 1.0 がリリースされてから1ヶ月以上経ちましたが、既存のパッケージの Julia 1.0 への対応だったり、Bug fix に追われているせいか、パッケージの作り方・登録の仕方に関しては後回しになって曖昧な状態になっていると感じます。 パッケージのテンプレートを作ってくれる PkgDev.jl もまだ Julia 1.0 に対応していませんし。
そのため、ここで書いたことが1週間後、1ヶ月後にも通用する保証はありません。

目次

環境

  • Julia 1.0

対象

本記事は以下の項目全てを満たす方を対象としています。

  • METADATA に登録をしたことがない Julian
  • Julia 1.0 向けのパッケージを作る Julian
  • Linux or Mac でも動くパッケージを作る Julian *3

必要なもの

  • 英語で質問された時に下手でも良いから伝えようとする気持ち
  • 自分のパッケージを公式パッケージにしても良いだろうという根拠のない自信
  • METADATA.jl に PR を投げる勇気

野良パッケージ作り

野良パッケージを作っていない人は、まずは野良パッケージを作ってください。大雑把な作り方に関しては過去の記事をご覧ください。

goropikari.hatenablog.com

基本的なパッケージ構成

以降では今回私が作った DiracNotation.jl を元に具体的に説明していきます。

パッケージの最小構成は以下のようになっています。

DiracNotation.jl/
├── .git
├── .gitignore
├── LICENSE.md
├── README.md
├── REQUIRE
├── src
│   └── DiracNotation.jl
├── test
│   └── runtests.jl
└── .travis.yml

Julia 1.0 が出る前に PkgDev.jl を使ってパッケージを作ったことがある人ならお気付きかもしれませんが、Julia 0.6 時代の構成と変わりません。

野良パッケージの時は必要なかった REQUIRE は必須で、野良パッケージの時に必須の Project.toml はなくても構いません。
(むしろ Project を JuliaCIBot は扱えないから入れないでと私より前に PR を投げていた人は言われていました*4。そんなこととは露知らず私は Project 込みで PR 投げましたが JuliaCIBot のテストも通ったし、特に何か指示されることもありませんでした。 なので結局 Project を入れて良いのか、入れないほうが良いのかよくわかりません。)

以前紹介したパッケージの作り方 に則ってパッケージを作った場合、LICENSE.md, README.md, REQUIRE, .travis.ymlが足りないので追加します。

LICENSE

ライセンスは適宜決めてください。Julia のパッケージの場合 MIT LICENSE を採用しているものが多いです。特にこだわりがない場合は MIT LICENSE にしておけば良いと思います。

ライセンスファイルは Github
Create new file --> ファイル名に LICENSE と打つと Choose a license template と出るのでクリック --> ライセンスを選ぶ
とすると簡単に作れます。

f:id:goropikarikun:20180920011848p:plain

f:id:goropikarikun:20180920011855p:plain

f:id:goropikarikun:20180920011902p:plain

README

作ったパッケージがどんなパッケージなのか README に軽く書きましょう。

Julia では、公式パッケージに登録する場合レビューが入るので README にどんなパッケージなのかの説明がないとレビュアーが困ってしまいます。

README に使い方を書くか、別枠で使い方のドキュメントをちゃんと作りましょう。 「ドキュメントがないと悲しくなる」と Udacity の人も言っていたので、ドキュメント大事。 www.udacity.com

Travis CI

f:id:goropikarikun:20180920014226p:plain

Julia の公式パッケージに登録する場合は JuliaCIBot *5 のテストは通せなくても Travis を通すのは必須です。 Travis でのテストが失敗すると「テスト失敗してるよ」とツッコミが入ります。

.travis.yml は外部ライブラリ*6に依存していない限り、大体こんな感じに書いておけば良いと思います。

language: julia
os:
  - linux
  - osx
julia:
  - 0.7
  - 1.0
  - nightly
matrix:
  allow_failures:
    - julia: nightly
notifications:
  email: false

テストできる OS は Linux または Mac です。必ずどちらかは入れてください。ここで Windows は使えません。

Julia の項目はパッケージが対応している Julia のバージョンを明示的に書いてください。release と書くと「明示的に書いて」とレビュアーから言われます。

REQUIRE

現状で一番面倒くさいことになっているのが REQUIRE です。Julia 1.0 向けのパッケージなのに Julia 0.x の作法に則らなければなりません。

野良パッケージ作成時に作った Project.toml のようなものなんですが少し違います。 以前の記事では例え標準ライブラリだろうとも Project.toml に書かなければならないと書きましたが、REQUIRE では逆に標準ライブラリを書いてはいけません。

依存している外部パッケージのみ REQUIRE自力で書いてください。Project.tomlと違って自動で作ってはくれません。 ここいう外部パッケージは公式パッケージに限られます。

今回私が作った DiracNotation.jlREQUIRE はこんな感じです。

julia 0.7
Requires

julia 0.7 と書くと Julia v0.7 以降の Julia を対象としているという意味になります。Julia 1.0 向けのパッケージだったら v0.7 を対応に入れなくても良いのですが、まだ Julia 1.0 に完全移行できていない人もいると思うので Julia 0.7 も対応に入れておくと良心的かなと思います。v0.7 = v1.0 + deprecation warning なので v0.7 を入れても問題ないはずですし。

外部パッケージの方にも適宜対応しているバージョンを書いてください。

私が初めて METADATA にパッケージを登録した時は「対応している外部パッケージのバージョンも書いて」と言われたのですが、最近はあまり言われていない印象です。レビュアーによってその辺のさじ加減は違うのかなという気はします。 ただ、メジャーバージョンが上がる前のパッケージでしか動かないなどという場合はちゃんと REQUIRE にバージョンも書きましょう。そうしないとそもそも travis で失敗すると思いますが。

対象とするバージョンの詳しい書き方は v0.6 時代のドキュメントを読んでください。

Packages · The Julia Language

パッケージの名前

公式パッケージにする場合、パッケージの名前にも注意を払う必要が有ります。

とりあえず、

  • 略語を入れない
  • パッケージ名に Julia という単語を入れない
  • アッパーキャメルケースにする (私のパッケージの場合、Diracnotation でなく DiracNotation にする)

ということを守っておけばパッケージ名に関しては何も言われないのではと思います*7

詳しいことは v0.6 時代のドキュメントを読んでください。

Packages · The Julia Language

METADATA に PR を投げる

全ての準備は整いました。あとは PR を投げるだけです。 自力で PR を投げる場合、METADATA.jl を fork して編集して・・・ とやるのですが、今は attobot という便利なツールがあるのでこれを使いましょう。むしろ自力で PR を投げると「次回からはattobot使ってね」と言われるので初めから attobot 使いましょう。 attobot を使うと Github 上でバージョンタグを付けると自動で METADATA へ PR を投げてくれます。

github.com

https://github.com/integration/attobot にアクセスすると、attobot をまだインストールしていない場合、Install 画面が出ると思うのでインストールしてください。

そのあと、Configure -> Select repositories -> METADATA に PR 投げたいパッケージを選ぶ -> Save

f:id:goropikarikun:20180920092203p:plain

f:id:goropikarikun:20180920092211p:plain

attobot を入れたら Github 上でバージョン番号でタグ付けします。バージョン番号はセマンティック バージョニング に従ってください。以前は初回リリース時は v0.0.1 と付けるのが普通でしたが、最近は v0.1.0 から始めるようです。

タグ付けすると自動的に METADATA.jl へ PR が投げられます。私のような OSS 開発素人は一番緊張するところです。

semver.org

f:id:goropikarikun:20180920093313p:plain

f:id:goropikarikun:20180920093322p:plain

f:id:goropikarikun:20180920093331p:plain

Publish release をクリックしたら自動的に METADATA.jl に PR が投げられます。こんな感じ。

f:id:goropikarikun:20180920093758p:plain

30秒経っても PR が作られない場合、「PR が作られないのだけれど。。。」と attobot のリポジトリに issue を立ててください。中の人が対応してくれます。

私は一度だけ PR が出来なかったことがあるのですが、その時は Travis が終了する前に PR を投げたのでそのせいなのかなぁと思いました。真相は結局わかっていませんが 。

あとは静かに3日待ちます。投げた PR に3日間コメントがないと自動的にその PR はマージされるらしいです*8 *9*10

あなたの PR よりも後に出した PR の方が先にマージされることがありますが文句を言ってはいけません。すでに公式パッケージになっているもののバージョン上げの場合、速攻マージされます。新参者は静かに待ちましょう。

特に問題がなければ以下のようにマージされます。☆-ヽ(*´∀`)八(´∀`*)ノイエーイ

f:id:goropikarikun:20180920095554p:plain

パッケージ名だけで追加できちゃう(σ・ω・)σYO♪
公式パッケージのみに許された特権だ (σ≧∀≦)σчоッ☆ f:id:goropikarikun:20180920104805p:plain

おわりに

いかがだったでしょうか?公式パッケージにするまでの方法の雰囲気はつかめましたでしょうか?

Julia の公式パッケージ作りは全て英語でやりとりしないといけないので、私のような英語が苦手な人にとってはなかなか大変ですが、苦労した分 公式パッケージとしてリリースできたときの達成感は何ものにも代え難いです。

私のような素人 Julian でも出来るのですから、皆さんも気楽に Let's try!!!

パッケージ作りでわからないことがあったら公式の Discourse か Slack で聞いてください。

Julia Community

余談

パッケージを作った当初は公式パッケージにするつもりはなかったのですが、QuantumOptics.jl の gitter にて JuliaDiffEq で有名な Rackauckas さんに私の野良パッケージへのリンクを貼っていただき、まわりの反応もそんなに悪くなさそうだったので公式パッケージにしてもいいかなぁという気になりました。

f:id:goropikarikun:20180920110430p:plain

また、前々から外部パッケージに依存しないパッケージを作りたいなぁと思っていたのと*11、パッケージを Julia 1.0 でも動くように直したら「公式パッケージにしたいなぁ」という謎の衝動に駆られたので公式パッケージとしてリリースすることにしました。

正直に言って pretty-print は自己満足の世界ですし、なくても困らないのでこのパッケージが使われることは殆どないと思いますが、パッケージのリリースアナウンスツイートが Julia 創始者の一人である Viral B. Shah さんからリツイート & いいね をもらえたのでそれだけで私は満足です。

*1:ここでは METADATA.jl に登録されているパッケージを公式パッケージ、登録されていないパッケージを野良パッケージと呼ぶこととします。

*2:Julian = Julia user

*3:Linux だけ、 または Mac だけで動くパッケージの場合はこの記事の内容で大丈夫ですが、Windowsだけで動くものを登録したい場合はこの記事の内容が通用しません。

*4:Register new package Catsay.jl v0.1.0 by attobot · Pull Request #17687 · JuliaLang/METADATA.jl · GitHub

*5:METADATA に PR 投げた時のみに走るテスト

*6:Juliaのパッケージでなく、C 言語のライブラリとかのこと

*7:代替案を出されることも有ります。

*8:Tag Reinforce.jl v0.1.0 by attobot · Pull Request #18098 · JuliaLang/METADATA.jl · GitHub

*9:Register new package DrawSimpleGraphs.jl v0.1.0 by attobot · Pull Request #18043 · JuliaLang/METADATA.jl · GitHub

*10:私は、この「3日で自動的にマージする」という規則を知らなかったので、「回りの人たちにはレスポンスがあるのに何故私のところには何もないの。。。自分でも謎のパッケージ作ったなという自覚はあるけど、コメントするのも憚られるほど!?」と不安になったのですが、コメントがあると自動マージ機能が無効になるというコメントを見て、「むしろ私のパッケージ優等生じゃん!」と開き直れました。

*11:依存があるとパッケージの読み込みが遅くなるし、メンテナンスが面倒

NiZ Plum 75 を無刻印化

格安静電容量無接点式キーボード NiZ Plum 75 を購入してから2ヶ月弱経ちましたが概ね満足しています。 しかし、一つ問題があります。それはキートップに印字があること!

単純に無刻印に憧れがあったということもありますが、私は Dvorak 配列を使っているので印字にほとんど意味がありません。 というか下手に書いてあるせいでたまにキーボードみると書いてあることに引っ張られます。 そんなわけで、米Amazonから無刻印のキーキャップを買って換装しました。

注文から到着まで

購入したのは以下の黒色です。送料込みで $23.49 でした。手数料込みで3000円弱ですかね。

www.amazon.com

9月10日に注文し、到着予定日は10月4日〜10月26日。
1ヶ月以上も掛かるのかぁと思っていたら配送激遅で有名な China Post じゃありませんか。そりゃ遅い。 前 China Post から送られてきたものは到着予定日過ぎて2日後くらいに届いたので、11月初めまで待つ感じになることも覚悟したのですが、結果的に8日で届きました。

f:id:goropikarikun:20180918124003p:plain

f:id:goropikarikun:20180918124634j:plain 箱はべこべこです。

f:id:goropikarikun:20180918124653j:plain 箱がこれなので、中身もスーパーの袋みたいなのにごちゃごちゃに入っているのかと思いましたが、ちゃんと一列ずつ分かれている袋に入れられていました。

f:id:goropikarikun:20180918131532j:plain 左Ctrlの比較。元々付いていたキーキャップよりも気持ち高い。 キーの触り心地は元々付いていたものとあまり変わりませんでした。

キーキャップ換装

元のキーキャップを取っていきましょう。私はFilcoのキーキャップ外しを使用しました。 全換装した後に思い出しましたが、NiZ Plum 75 には キーキャップ外しが標準で付いてくるので買う必要はありません。ただ、スペースキーのように長いものは標準でついてくる工具の方が外しやすいですが、その他のキーに関しては Filco のタイプの方が外しやすいと思います。

f:id:goropikarikun:20180918125123j:plain

NiZ のキーボードちょいちょいバックライトが光る部分があるのですが、どうやら Esc, Caps Lock, 左Ctrl, F10, F11, F12 が光るようです。未だに光る条件がわかりませんが。特に最近 Esc がどういうわけかよく光ります。

f:id:goropikarikun:20180918130745j:plain

後は単純に挿していくだけです。キーの抜き差しは元々のキーよりも重くなりました。 それとスペースキーの滑りがよくありません。力入れて差し込むとキーが下がったまま戻ってきません。若干引き戻すと一応戻ってくるようになりました。だけど動きが渋いです。一応隣のキーと干渉しているわけではなさそうなのでスペースキーのサイズに問題はないと思うのですが。スタビライザーについていたグリスをベタベタ触ってしまったのでグリスが落ちてしまったのかもしれません。

f:id:goropikarikun:20180918131355j:plain

全換装終了。なんだかんだで40分ほどかかりました。 キーキャップが若干高くなった分打ち心地が変わると思ったのですが、ほとんど感じませんでした、スペースキー以外は。。。 私は SandS (Shift and Space) を使っているので普通の人よりスペースキーの使用頻度はものすごく高いです。そのためスペースキーの違和感は結構ネックになってきます。

f:id:goropikarikun:20180918131425j:plain

試しに元のスペースキーに戻したら、動きもスムーズだし打ちやすかったのでスペースキーだけ元のものにしました。一応無刻印は保っているので良しとしましょう。*1

見た目は正直言ってダサいですね。キーの汚れが気になると思って黒色にしましたが、外枠が白なのでそれに合わせて真っ白にしたほうがよかったかなぁとは思います。

キーボード本体と今回買ったキーキャップを合わせても1万円ちょっとで無刻印の静電容量無接点方式のキーボードが買えたのだから贅沢は言えません。

*1:サイドにNiZ Plum のロゴはありますが。