Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Elixirで遊んでみる(5)

7章は「リスト」。いわゆるconsリストについて説明している。まあ、いいでしょう。理解はしてる。使えるかはアレだけど。

8章は「マップ、キーワードリスト、セット、構造体」。盛りだくさんだ。

マップのパターンマッチの仕方は慣れる必要がありそうだ。

iex> person = %{name: "Tambara", age: 45}
%{age: 45, name: "Tambara"}
iex> person.name
"Tambara"
iex> %{name: n} = person
%{age: 45, name: "Tambara"}
iex> n
"Tambara"

:nameの値を取り出すだけならperson.nameでいいわけだけど、パターンマッチでも取り出せる。

で、ここで内包表現の例が出てくる。Pythonでよくわからないアレだ。 いや、ネストしなければわからなくはないんだけど。やってみよう。forを使っているところがそれらしい。

力士 = [
    %{ 四股名: "白鵬", 番付: "横綱", 部屋: "宮城野", 体重: 151.0 },
    %{ 四股名: "朝乃山", 番付: "大関", 部屋: "高砂", 体重: 174.0 },
    %{ 四股名: "照ノ富士", 番付: "関脇", 部屋: "伊勢ヶ濱", 体重: 173.0 },
    %{ 四股名: "隆の勝", 番付: "関脇", 部屋: "常盤山", 体重: 163.0 },
    %{ 四股名: "御嶽海", 番付: "小結", 部屋: "出羽海", 体重: 172.0 },
]

IO.inspect(for i = %{体重: h} <- 力士, h < 170.0, do: i)

イマドキの言語なので、UTF-8の並びならなんでも識別子になる。

実行すると

iex> c "query.exs"
[
  %{体重: 151.0, 四股名: "白鵬", 番付: "横綱", 部屋: "宮城野"},
  %{
    体重: 163.0,
    四股名: "隆の勝",
    番付: "関脇",
    部屋: "常盤山"
  }
]
[]

こんな感じ。なんで改行が入ったのかはよくわからない。

i = %{体重: h} <- 力士は、パターンマッチがネストになっている。まず、<-の方がマッチされる (ここが=じゃないのは、力士リストに軽量力士がいない可能性もあるからだ。=だと例外がでちゃう)。 で、マッチしたものが、iに束縛されている。なので、Pythonのようにループ変数を指定する必要がないみたい。 その後ろに、ガードっぽいものが書いてあって最初のマッチで束縛されたhがここで使われている。 けど、単に,で並べればいいのかな。 どんな式なら並べて良いのかわからないので、釈然としない。

さて、Elixirなのでマップもイミュータブルだ。えっ?そうなの?

リストはconsリストの構造や、スライスになじみがあるからリストがイミュータブルですと言われても、「そうですね」という気がする。 でも、マップに新しいキーと値のペアを追加したら新しいマップが生成されると聞くと、なんかビックリする。 リストと同じように出来るものなの?

マップの変更はこんな感じ

iex> m = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
iex> m1 = %{ m | b: "two", c: "three" }
%{a: 1, b: "two", c: "three"}
iex> m
%{a: 1, b: 2, c: 3}
iex> m1
%{a: 1, b: "two", c: "three"}

キーの追加はこの構文では出来ない。Map.put_newを使う。

iex> m2 = %{m | d: 4}
** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3}
    (stdlib 3.14) :maps.update(:d, 4, %{a: 1, b: 2, c: 3})
    (stdlib 3.14) erl_eval.erl:259: anonymous fn/2 in :erl_eval.expr/5
    (stdlib 3.14) lists.erl:1267: :lists.foldl/3
iex> m2 = Map.put_new m, :d, 4
%{a: 1, b: 2, c: 3, d: 4}

次は、構造体。構造体はモジュールの中で定義する。 というか、モジュールとして名前がついたキーが固定されたマップである。 オブジェクトはすべてハッシュというJSに近い感じがする。

定義はこんな感じ

defmodule SumoFighter do
  defstruct 四股名: "", 部屋: "", 番付: "", 体重: 0.0
end

マップと同じように作る。更新もおんなじ感じ。

iex(109)> shodai = %SumoFighter{ 四股名: "正代", 部屋: "時津風", 番付: "関脇", 体重: 170.0}
%SumoFighter{
  体重: 170.0,
  四股名: "正代",
  番付: "関脇",
  部屋: "時津風"
}
iex(110)> shodai2 = %SumoFighter{ shodai | 番付: "大関" }
%SumoFighter{
  体重: 170.0,
  四股名: "正代",
  番付: "大関",
  部屋: "時津風"
}

大関に昇進させたいけど、イミュータブルなので別のマップになります(笑)

キーを固定できるだけじゃなくて、モジュールに関数を定義することで オブジェクトっぽくできる。

その力士が三役力士かどうかを判定する関数を作ってみる。 もちろん、ifで判定するんじゃなくて、パターンマッチで判定するのだ。

defmodule SumoFighter do
  defstruct 四股名: "", 部屋: "", 番付: "", 体重: 0.0
  def is_三役?(%SumoFighter{番付: "横綱"}) , do: true;
  def is_三役?(%SumoFighter{番付: "大関"}) , do: true;
  def is_三役?(%SumoFighter{番付: "関脇"}) , do: true;
  def is_三役?(%SumoFighter{番付: "小結"}) , do: true;
  def is_三役?(%SumoFighter{番付: _}) , do: false;
end

ダサい。猛烈にダサい。でも、今の私のElixir力ではここからどうして良いのかはわからない。うむー。

まあ、これでなんとか判定は出来る。

iex> SumoFighter.is_三役?(shodai)
true
iex> okinoumi = %SumoFighter{ 四股名: "隠岐の海", 部屋: "八角", 番付: "前頭", 体重: 160.0 }
%SumoFighter{
  体重: 160.0,
  四股名: "隠岐の海",
  番付: "前頭",
  部屋: "八角"
}
iex> SumoFighter.is_三役?(okinoumi)                                                        
false

でもなあ・・・。

そして、構造体を作ると、当然、ネストすることもある。 つまり、構造体のあるキーの値がまた別の構造体ってこと。 すると、更新が面倒くさくなるので、マクロと関数が用意されている。

しかし、本のP092にある

authors = [
  %{ name: "José", language: "Elixir" },
  %{ name: "Matz", language: "Ruby" },
  %{ name: "Larry", language: "Perl" }
]

languages_with_an_r = fn (:get, collection, next_fn) ->
  for row <- collection do
    if String.contains?(row.language, "r") do
      next_fn.(row)
    end
  end
end

IO.inspect get_in(authors, [languages_with_an_r, :name])
#=> [ "José", nil, "Larry" ]

は完全に理解できない。なんだこりゃ?

Elixirで遊んでみる(4)

6章は「モジュールと名前付き関数」である。

ここでやっと普通の関数定義の説明がある。

で、Rubyっぽいこんな感じだ。でも、Rubyならdoはいらない

defmodule Times do
  def double(n) do
    n * 2
  end
end

しかし、do...endはシンタックスシュガーで、do: (...)が基本的な構文らしい。

つまり、def はキーワード引数で:doにブロックを取るような関数だと。ほー・・・。

上の定義はこう書いてもいいらしい(が、「頼むからやめてくれ」と書いてあるw)

defmodule Times, do: (def double(n), do: n * 2)

なるほど。

関数の引数でパターンマッチしたい場合は、単に関数を複数書く。 例えば、階乗の計算はこんな感じ。上からマッチが試されるので、順番大事。

defmodule Factorial do
  def of(0), do: 1
  def of(n), do: n * of(n-1)
end

パターンマッチではなく、引数の型や値の評価で実行する関数を区別したい場合はガード節を付ける。こんな感じ。

defmodule Factorial do
  def of(0), do: 1
  def of(n) when is_integer(n) and n > 0 do
    n * of(n-1)
  end
  def of(_), do: :error
endend

OCamlと比べると、型によるパターンマッチが出来ないのは面倒くさく感じなくもない。 型それ自体が面倒くさいという意見もあると思うけど(笑)。

次に出てくるのは|>で書く、パイプライン。メソッドチェーンの関数言語版だ。 昔、Rubyにも提案されていて、trunkにサクッと入ってたことはあるけど、どうも上手く行かなかったらしい。

それはともかく、例えば、「1から10までの数字を2倍して、5より大きいもの」を表現するのに、パイプラインがないと

iex> Enum.filter(Enum.map(1..10, &(&1 * 2)), &(&1 > 5))   
[6, 8, 10, 12, 14, 16, 18, 20]

こうなっちゃうけど、パイプラインがあれば、

iex> (1..10) |> Enum.map(&(&1 * 2)) |> Enum.filter(&(&1 > 5))
[6, 8, 10, 12, 14, 16, 18, 20]

うん。明らかにこっちの方が見やすい。

ちなみにRubyでは

irb> (1..10).map{_1 * 2}.filter{_1 > 5}
=> [6, 8, 10, 12, 14, 16, 18, 20]

パイプラインの必要性がわからない。まあ、だから無くなったんだと思うけど。

後は、モジュールについて。属性の説明が面白い。

Elixirで遊んでみる(3)

5章は無名関数。普通の関数やってないのに、無名関数からやるという(笑)

無名関数は変数に束縛して使う。関数呼び出しの時に.がいるのがポイント

iex> sum = fn (a, b) -> a + b end
#Function<43.79398840/2 in :erl_eval.expr/5>
iex> sum.(1, 3)
4
iex> sum.(1.4, 3.2)
4.6

整数でも小数でも実行出来てしまうところが、OCamlと違うところやね。

引数なしの関数はこんな感じ。ん?なんか:okが返ってる。

iex> greet = fn -> IO.puts "Hello" end
#Function<45.79398840/0 in :erl_eval.expr/5>
iex> greet.() 
Hello
:ok

P.045には練習問題としてパターンマッチを使ったFizzBuzzを作れという問題がある。 こんな感じかな?

f = fn
  0, 0, _ -> "FizzBuzz"
  0, _, _ -> "Fizz"
  _, 0, _ -> "Buzz"
  _, _, i -> i
end

fizzbuzz = fn i -> f.(rem(i, 3), rem(i, 5), i) end

IO.puts fizzbuzz.(10)
IO.puts fizzbuzz.(11)
IO.puts fizzbuzz.(12)
IO.puts fizzbuzz.(13)
IO.puts fizzbuzz.(14)
IO.puts fizzbuzz.(15)
IO.puts fizzbuzz.(16)
iex> c "ch5ex2.exs"
Buzz
11
Fizz
13
14
FizzBuzz
16
[]

最後に空のリストが表示されるのはなんだろう・・・

さて、関数を返す関数を作るのももちろんOK

iex> times = fn i -> fn n -> n * i end end
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> twice = times.(2)
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> twice.(3)
6

高階関数ももちろんOK

iex(61)> Enum.map [1, 2, 3], fn i -> i * i end
[1, 4, 9]

fn... endは冗長なので、&記法というのがある。

iex> times = fn i -> &(&1 * i) end
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> ten_times = times.(10)
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> ten_times.(3)
30
iex> Enum.map [1, 2, 3], &(&1 * 11)
[11, 22, 33]

&は後ろの式を関数にするので、別に()が必須というわけじゃないらしい

iex> f = &"#{&1}, #{&1}"   
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> f.("hoge")
"hoge, hoge"

iex> f = &{"#{&1}", "#{String.reverse &1}"}
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> f.("hoge")
{"hoge", "egoh"}

iex> f = &~r/#{&1}#{&1}/
#Function<44.79398840/1 in :erl_eval.expr/5>
iex> "make" =~ f.("e")
false
iex> "street" =~ f.("e")
true

Elixirで遊んでみる(2)

本を先に進んでみよう。

2章はパターンマッチ。未だに慣れない。とりあえず=でマッチ出来る。 左側に変数があると、マッチすると束縛が起きる。 左側の変数の値をマッチに使いたい場合は^を付ける。

iex> a = 1
1
iex> [^a, b] = [2, 3]
** (MatchError) no match of right hand side value: [2, 3]
iex> [a, b] = [2, 3]
[2, 3]
iex> a
2

ふむふむ。それ以外は、割と自然だった。

3章は不変性。まあ、関数型だからネ。

4章はElixirの基礎。まずは、組み込みの型。

整数は整数。浮動小数点数はIEEE754 倍精度。アトムはRubyでいうところのシンボルかな。シンタックスも同じだし。範囲もRubyのRange。 正規表現~r{...}optsRubyの%リテラルと同じように{ }は別の記号でもよい。オプションは見慣れないのがあるな・・・。

iex> Regex.replace ~r/,/, "a, b, c", "|" 
"a| b| c"

~r/..../のように/を使うのはノスタルジーだと書かれている(笑)。

システム型として、PIDとポート、リファレンスがあるらしいが後回しにされてる。

コレクションとしては、タプルとリスト、マップとバイナリ。

タプルは、まあ、タプル。RubyならArray。何を入れてもいい。

リストは、いわゆるconsリスト。後で詳しく出てくると思う。あと、キーワードリストという特別扱いされる形式がある。

iex(17)> [a, b] = [name: "Taro", age: 5]
[name: "Taro", age: 5]
iex(18)> a
{:name, "Taro"}

Rubyのハッシュの記法みたいだけど、実体はタプルのリスト。関数呼び出しの最後の引数がキーワードリストの場合、角括弧を省略できる。 つまり、キーワード引数はこう実装されていると。

マップはRubyでいうところのハッシュ。記法も似てる。キーがアトムのときに%{ :key => value}%{key: value}と書いていいのも似てる。 取り出す時は、map1[:key]と書いても良いし、map1.keyでもいい。 こっちをキーワード引数に使わなかったのは、キーワードリストはキーの重複が許されるかららしい。

バイナリはビットストリームを扱う。面白い。

日付型。こんな感じ。リテラル表現の仕方が面白い。シジルっていうらしい。

iex> {:ok, d1} = Date.new(2020, 12, 28)
{:ok, ~D[2020-12-28]}
iex> d1
~D[2020-12-28]
iex> d2 = ~D[2020-12-28]               
~D[2020-12-28]
iex> d1 == d2
true
iex> Date.day_of_week(d1)
1
iex> Date.add(d1, 4)
~D[2021-01-01]
iex> inspect d1, structs: false
"%{__struct__: Date, calendar: Calendar.ISO, day: 28, month: 12, year: 2020}"

時刻型。1秒以下の値をどう持つかがちょっと難しい。「マイクロ秒と有効桁のタプルを持つ」と説明されているけど、ようわからん。

iex> {:ok, t1} = Time.new(12, 23, 34)
{:ok, ~T[12:23:34]}
iex> {:ok, t2} = Time.new(12, 23, 34.56)
** (FunctionClauseError) no function clause matching in Time.new/5    
    
    The following arguments were given to Time.new/5:
    
        # 1
        12
    
        # 2
        23
    
        # 3
        34.56
    
        # 4
        {0, 0}
    
        # 5
        Calendar.ISO
    
    Attempted function clauses (showing 2 out of 2):
    
        def new(hour, minute, second, microsecond, calendar) when is_integer(microsecond)
        def new(hour, minute, second, {microsecond, precision}, calendar) when is_integer(hour) and is_integer(minute) and is_integer(second) and is_integer(microsecond) and is_integer(precision)
    
    (elixir 1.11.2) lib/calendar/time.ex:119: Time.new/5

このように秒に小数は与えられない。

これはOKみたい。

iex> {:ok, t2} = Time.new(12, 23, 34, 56)              
{:ok, ~T[12:23:34.000056]}

リテラルとしては、小数の秒を持てる。が、扱いが難しい。

iex> t1 = ~T[12:23:34.0]
~T[12:23:34.0]
iex> t2 = ~T[12:23:34.00]
~T[12:23:34.00]
iex> t1 == t2
false

これが同じにならないのは、有効桁が違うから、らしい。

DateTimeはタイムゾーンを持つ。タイムゾーンを持たないNaiveDateTimeもある。

真偽値は、true, false, nilの3種類。Rubyと同じかな。falseとnil以外の値が真と扱われることが多いのもRubyと同じ。 ちなみにtrueは:trueとおなじもの。

比較演算子===Rubyと違って厳密な同値を取る。つまり、1 == 1.0はtrue, 1 === 1.0はfalse。 ちなみに、Rubyの===は・・・なんて呼ぶんだろう。むしろ、より緩い比較になる。case式で使う目的で、いろんなクラスで良い感じに定義してある。

irb> /hoge/ === "hogefuga"
=> true
irb> (2..4) === 3
=> true

ブール演算子はandとorのペアと、&&と||のペアがある。andとorは左側(= 1つ目の引数)にtrueかfalseが来ることを期待する。&&はfalseとnil以外はtrueと解釈する。つまり、Perlor dieをしたければ||を使う・・・ということなのか?

コレクションにアイテムが含まれるかどうかはin演算子で確かめる。マップはキーと値のタプルが必要。

iex> 1 in [1, 2, 3]
true
iex> {key1: "value1"} in %{key1: "value1", key2: "value2"}
** (SyntaxError) iex:38:2: syntax error before: key1

iex> {:key1, "value1"} in %{key1: "value1", key2: "value2"}
true
iex> {:key2, "value1"} in %{key1: "value1", key2: "value2"}
false

次に、変数のスコープ・・・といっても特に特殊なことはなくて、普通のレキシカルスコープ。

変わった例として、with式がある。Pythonのwith構文みたいにブロックの中から外に漏れて欲しくないリソースを扱える。 withでパターンマッチして、取り出したものは、doブロックの中だけで使える。

iex> a = [1, 2, 3]
[1, 2, 3]
iex> x = with [_, 2, i] = a do
...(42)> i * 3
...(42)> end
9

パターンマッチに失敗すると=では例外が出るが、<-を使うとパターンマッチの右側がそのまま返る。

iex> x = with [_, 1, i] = a do
...(44)> i * 3
...(44)> end
** (MatchError) no match of right hand side value: [1, 2, 3]

iex> x = with [_, 1, i] <- a do
...(44)> i * 3
...(44)> end
[1, 2, 3]

doのショートカットというのもある。Pythonっぽい?

iex> x = with [_, 1, i] <- a, 
...(45)> do: i * 3
[1, 2, 3]
iex> x = with [_, 2, i] <- a do: i * 3
** (CompileError) iex:46: undefined function a/1
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
iex> x = with [_, 2, i] <- a, do: i * 3
9

ちょっと混乱するね

Elixirで遊んでみる(1)

年末年始、「プログラミングElixir」の第2版を買ったので、ちょっと遊んでみよう

ElixirのインタラクティブシェルIExを実行する。イマドキはDockerだよねと。P4を写経する。

> docker run -it --rm elixir
Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 3 + 4
7
iex(2)> String.reverse "hogefuta"
"atufegoh"
iex(3)> 5 * 
...(3)> 6
30
iex(4)>  
User switch command
 --> q

IExを終了するにはCtrl-Gしてq。ちなみに、本の例では"madamimadam"をString.reverseしてるんだけど、 回文をreverseするのは明らかにサンプルとして不適当なのではないのだろうかw

伝統に則って、Hello, worldを出力するスクリプトを作って実行してみる。

> cat hello.exs 
IO.puts "Hello, World!"⏎     
> docker run -it --rm -v (pwd):/work elixir
Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> cd "/work"
/work
iex(2)> ls     
hello.exs     
iex(3)> c "hello.exs"
Hello, World!
[]
iex(4)> 

これがいいやり方なのかはわからない。 そのうち、homebrewでインストールしてみるかも知れないけど、 Dockerに慣れるためにも、しばらくこんな感じで遊んでみる。

Re:viewを試してみたい

ちょっと事情があって、C言語講座っぽいものを書いた ->

GitHub - tmbrms/c-lang-world

けっこうな量書いたんだけど、これは書籍で言うと何ページぐらいなんだろうということが知りたいし、あわよくば技術書典とかに出したいと思い、技術書向け製本システムのRe:VIEWを試してみたい。

とりあえず、下の記事を参考に、Dockerを使ってビルドしてみた。

blog.kozakana.net

こんな感じになった。すっごーい

f:id:Tambourine:20201214110319p:plain

ただし、会話と地の文が入り交じる形式なので、このままのフォーマットではダメっぽい。フォーマットをいじるならけっこう踏み込む必要がありそうだなあ。TeXの知識も要るだろうし。私のLaTeXスキルは修論を書いた時点から止まっているのでもうどうしていいかわからない。

ともあれ、やはりLaTeXの環境構築はすごい面倒くさかった記憶しかないので、Dockerは凄いなって。うむ。

MacへSubversionを入れたい

2020年にもなって、svnを使っているプロジェクトに関わることになった。まあ、だからどうと言うことはない。 どうと言うことはないんだけども、Macでのsvnには苦い思い出がある。 かなり前のことだけど、WindowsなどとUnicodeの正規化の扱いが違い、濁点が含まれたファイルが2つに分裂するなど、悲しいことが結構起きた。 特別なパッチを当てたsvnをインストールする必要があったを記憶している。

詳細はあんまりわかってないんだけど、APFSでこの扱いが変更されたので今は問題なくなっているのかなーと推測しているが、詳細は全然わかってない。 ググってみたんだけど、まあ、もうみんなsvnなんか使っていないのか、これと言った情報が引っかからない。 そして、さらにOS標準のsvnはCatalinaでなくなっちゃったんだと。あれま。

とりあえず、brewでインストールしてみる

> brew info svn
subversion: stable 1.13.0 (bottled), HEAD
Version control system designed to be a better CVS
https://subversion.apache.org/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/subversion.rb
==> Dependencies
Build: openjdk ✔, pkg-config ✘, scons ✘, swig@3 ✘
Required: apr ✘, apr-util ✘, gettext ✘, lz4 ✘, openssl@1.1 ✘, perl ✘, sqlite ✘, utf8proc ✘
==> Requirements
Required: macOS is required ✔
==> Options
--HEAD
    Install HEAD version
==> Caveats
svntools have been installed to:
  /usr/local/opt/subversion/libexec

The perl bindings are located in various subdirectories of:
  /usr/local/opt/subversion/lib/perl5

You may need to link the Java bindings into the Java Extensions folder:
  sudo mkdir -p /Library/Java/Extensions
  sudo ln -s /usr/local/lib/libsvnjavahl-1.dylib /Library/Java/Extensions/libsvnjavahl-1.dylib
==> Analytics
install: 8,763 (30 days), 34,364 (90 days), 108,450 (365 days)
install-on-request: 7,790 (30 days), 29,555 (90 days), 83,898 (365 days)
build-error: 0 (30 days)

> brew install svn
Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/apr-1.7.0.catalina.bottle.t
==> Downloading from https://akamai.bintray.com/27/277c42fcf2f5ca298a14279d1325f
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/openssl%401.1-1.1.1g.catali
==> Downloading from https://akamai.bintray.com/19/1926679569c6af5337de812d86f4d
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/apr-util-1.6.1_3.catalina.b
==> Downloading from https://akamai.bintray.com/42/425955a21c3fec8e78f365cd7fc4c
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/gettext-0.20.2_1.catalina.b
==> Downloading from https://akamai.bintray.com/71/71f4ded03e8258b5e6896eebb00d2
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/lz4-1.9.2.catalina.bottle.t
==> Downloading from https://akamai.bintray.com/7d/7de6165d86c7a7ae01d254a5d0ea0
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/perl-5.30.2_1.catalina.bott
==> Downloading from https://akamai.bintray.com/b2/b25dbfa43f3fea68a3acdf7f59e18
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/readline-8.0.4.catalina.bot
==> Downloading from https://akamai.bintray.com/6a/6ae1c8e7c783f32bd22c6085caa4d
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/sqlite-3.31.1.catalina.bott
==> Downloading from https://akamai.bintray.com/e0/e09e8c96db88178e4f47b0cdab647
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/utf8proc-2.5.0.catalina.bot
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/subversion-1.13.0_5.catalin
==> Downloading from https://akamai.bintray.com/0c/0c131c339c9d452563aeda9dffc0a
######################################################################## 100.0%
==> Installing dependencies for subversion: apr, openssl@1.1, apr-util, gettext, lz4, perl, readline, sqlite and utf8proc
==> Installing subversion dependency: apr
==> Pouring apr-1.7.0.catalina.bottle.tar.gz
==> Caveats
apr is keg-only, which means it was not symlinked into /usr/local,
because Apple's CLT provides apr.

If you need to have apr first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/apr/bin" $fish_user_paths' >> ~/.config/fish/config.fish

==> Summary
🍺  /usr/local/Cellar/apr/1.7.0: 59 files, 1.4MB
==> Installing subversion dependency: openssl@1.1
==> Pouring openssl@1.1-1.1.1g.catalina.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/openssl@1.1/bin" $fish_user_paths' >> ~/.config/fish/config.fish

For compilers to find openssl@1.1 you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/openssl@1.1/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Summary
🍺  /usr/local/Cellar/openssl@1.1/1.1.1g: 8,059 files, 18MB
==> Installing subversion dependency: apr-util
==> Pouring apr-util-1.6.1_3.catalina.bottle.tar.gz
==> Caveats
apr-util is keg-only, which means it was not symlinked into /usr/local,
because Apple's CLT provides apr (but not apr-util).

If you need to have apr-util first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/apr-util/bin" $fish_user_paths' >> ~/.config/fish/config.fish

==> Summary
🍺  /usr/local/Cellar/apr-util/1.6.1_3: 54 files, 785.7KB
==> Installing subversion dependency: gettext
==> Pouring gettext-0.20.2_1.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/gettext/0.20.2_1: 1,923 files, 18.6MB
==> Installing subversion dependency: lz4
==> Pouring lz4-1.9.2.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/lz4/1.9.2: 22 files, 589.5KB
==> Installing subversion dependency: perl
==> Pouring perl-5.30.2_1.catalina.bottle.tar.gz
==> Caveats
By default non-brewed cpan modules are installed to the Cellar. If you wish
for your modules to persist across updates we recommend using `local::lib`.

You can set that up like this:
  PERL_MM_OPT="INSTALL_BASE=$HOME/perl5" cpan local::lib
  echo 'eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib=$HOME/perl5)"' >> ~/.config/fish/config.fish
==> Summary
🍺  /usr/local/Cellar/perl/5.30.2_1: 2,444 files, 62MB
==> Installing subversion dependency: readline
==> Pouring readline-8.0.4.catalina.bottle.tar.gz
==> Caveats
readline is keg-only, which means it was not symlinked into /usr/local,
because macOS provides BSD libedit.

For compilers to find readline you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/readline/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/readline/include"

For pkg-config to find readline you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/readline/lib/pkgconfig"

==> Summary
🍺  /usr/local/Cellar/readline/8.0.4: 48 files, 1.5MB
==> Installing subversion dependency: sqlite
==> Pouring sqlite-3.31.1.catalina.bottle.tar.gz
==> Caveats
sqlite is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have sqlite first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/sqlite/bin" $fish_user_paths' >> ~/.config/fish/config.fish

For compilers to find sqlite you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/sqlite/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/sqlite/include"

For pkg-config to find sqlite you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/sqlite/lib/pkgconfig"

==> Summary
🍺  /usr/local/Cellar/sqlite/3.31.1: 11 files, 4MB
==> Installing subversion dependency: utf8proc
==> Pouring utf8proc-2.5.0.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/utf8proc/2.5.0: 10 files, 650.2KB
==> Installing subversion
==> Pouring subversion-1.13.0_5.catalina.bottle.tar.gz
==> Caveats
svntools have been installed to:
  /usr/local/opt/subversion/libexec

The perl bindings are located in various subdirectories of:
  /usr/local/opt/subversion/lib/perl5

You may need to link the Java bindings into the Java Extensions folder:
  sudo mkdir -p /Library/Java/Extensions
  sudo ln -s /usr/local/lib/libsvnjavahl-1.dylib /Library/Java/Extensions/libsvnjavahl-1.dylib

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
==> Summary
🍺  /usr/local/Cellar/subversion/1.13.0_5: 234 files, 30.4MB
==> Caveats
==> apr
apr is keg-only, which means it was not symlinked into /usr/local,
because Apple's CLT provides apr.

If you need to have apr first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/apr/bin" $fish_user_paths' >> ~/.config/fish/config.fish

==> openssl@1.1
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/openssl@1.1/bin" $fish_user_paths' >> ~/.config/fish/config.fish

For compilers to find openssl@1.1 you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/openssl@1.1/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> apr-util
apr-util is keg-only, which means it was not symlinked into /usr/local,
because Apple's CLT provides apr (but not apr-util).

If you need to have apr-util first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/apr-util/bin" $fish_user_paths' >> ~/.config/fish/config.fish

==> perl
By default non-brewed cpan modules are installed to the Cellar. If you wish
for your modules to persist across updates we recommend using `local::lib`.

You can set that up like this:
  PERL_MM_OPT="INSTALL_BASE=$HOME/perl5" cpan local::lib
  echo 'eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib=$HOME/perl5)"' >> ~/.config/fish/config.fish
==> readline
readline is keg-only, which means it was not symlinked into /usr/local,
because macOS provides BSD libedit.

For compilers to find readline you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/readline/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/readline/include"

For pkg-config to find readline you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/readline/lib/pkgconfig"

==> sqlite
sqlite is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have sqlite first in your PATH run:
  echo 'set -g fish_user_paths "/usr/local/opt/sqlite/bin" $fish_user_paths' >> ~/.config/fish/config.fish

For compilers to find sqlite you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/sqlite/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/sqlite/include"

For pkg-config to find sqlite you may need to set:
  set -gx PKG_CONFIG_PATH "/usr/local/opt/sqlite/lib/pkgconfig"

==> subversion
svntools have been installed to:
  /usr/local/opt/subversion/libexec

The perl bindings are located in various subdirectories of:
  /usr/local/opt/subversion/lib/perl5

You may need to link the Java bindings into the Java Extensions folder:
  sudo mkdir -p /Library/Java/Extensions
  sudo ln -s /usr/local/lib/libsvnjavahl-1.dylib /Library/Java/Extensions/libsvnjavahl-1.dylib

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

さて、これでプロジェクトの適当なドキュメントをチェックアウトし、ファイル名に濁点が入っている(「ベースライン」という言葉が含まれていた)Excelファイルを開き、シートを1つ増やして保存。svn statusしてみた。ちゃんと変更が捉えられていた。昔はこれが、ファイルが2つに増えて、1つは新規と認識されていたのだ。つまり、ファイル名が「へ゛ースライン」と「ベースライン」の2つになっちゃったわけだ。見た目は表示するときに「へ゛」を「べ」にするからおんなじなんだけど。

とりあえず、大丈夫そうで良かった。