Tambourine作業メモ

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

Elixirで遊んでみる(6)

9章は2ページしかない。Elixirには型みたいなものが2つのレベルである。 プリミティブなデータ型と、モジュールとそれが提供する関数が規定するレベルでの型だ。 あるモジュールはプリミティブなデータ型に加えてある構造を要求する。

例えば、ListもKeywordも実体としてのデータの型は単なるリストだ。 しかし、「アトムと値のペアからなるタプルのリスト」という追加の構造を持つことでKeywordモジュールの関数は成り立っていて、 利用するプログラマはそれを含めてKeywordをListとは違う型のように受け取っている。 つまり、OOP言語で言えば、プリミティブな文法レベルの上に標準クラスライブラリの話がはじまるが、 それに当たるAPIの話がこの後に来て、プログラマとしてはまさにそれに習得することが重要なのだろう。

というわけで10章はコレクションAPIの話になる。

最初はEnum。だいたい、RubyのEnumerableにありそうな関数がある。mapとかfilterとかsortとか。joinもここにある。 all?とかempty?みたいな?が関数名につくのもRuby style。ただし、include?はmember?になってる。 当然、reduceもここにある。

さて、P.102には練習問題がある。all?を実装してみろと。挑戦してみよう。こんな感じかな?

defmodule MyEnum do
  def all?([], _), do: true
  def all?([first|rest], f) when not f.(first), do: false
  def all?([_|rest], f), do: all?(rest, f)
end

えっ?ダメなの?

iex(117)> c "ch10ex5.exs"

== Compilation error in file ch10ex5.exs ==
** (CompileError) ch10ex5.exs:3: invalid expression in guard, anonymous call is not allowed in guards. To learn more about guards, visit: https://hexdocs.pm/elixir/patterns-and-guards.html
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
** (CompileError)  compile error
    (iex 1.11.2) lib/iex/helpers.ex:200: IEx.Helpers.c/2

ガード節の中で無名関数は使えないと。えー、マジカヨ。

defmodule MyEnum do
  def all?([], _), do: true
  def all?([first|rest], f) do
    if f.(first) do
      all?(rest, f)
    else
      false
    end
  end
end

ダサい。猛烈にダサい。何かがおかしい。コレジャナイハズ。うーむ・・・

次は、Stream。遅延評価したいときはEnumじゃなくてStreamを使う。

自分でストリームを作ることも出来る。以下はcycle, repeatedly, iterateの例。

iex> Stream.cycle([1, 2, 3]) |> Enum.take(5)
[1, 2, 3, 1, 2]
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(5)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987, 0.5014907142064751,
 0.311326754804393]
iex> Stream.iterate(1, &(&1 * -1)) |> Enum.take(5)
[1, -1, 1, -1, 1]

これはわかりやすい。

Stream.unfoldは、前のイテレーションのステータスによって次の値が作られるようなものだ。 何に使うんだろう。とりあえず、本にはフィボナッチの例が出ている。

iex> Stream.unfold({0,1}, fn {f1, f2} -> {f1, {f2, f1+f2}} end) |> Enum.take(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Stream.resourceはやりたいことはわかるけど、なかなか難しい。 3つの関数を与える。初期化、値の取得、終了処理の3つ。そうすると、値が取り出せるストリームが作られる。 例えば、ファイルを1行ずつ読むとしたら、(1)ファイルのオープン(2)1行リード(3)ファイルのクローズ だ。 実際、これを作ることになったらかなり悩みそう。

この章の最後は、内包表現。理解できるかなあ・・・。こんな感じだ。

iex> first8 = Enum.to_list 1..8
[1, 2, 3, 4, 5, 6, 7, 8]
iex> for x <- first8, y <- first8, x >= y, rem(x*y, 10) == 0, do: {x, y}
[{5, 2}, {5, 4}, {6, 5}, {8, 5}]

forの後ろに複数のパターンマッチを書いてよい。その後ろにフィルタが来る。パターンマッチやフィルタでは、それ以前のマッチの結果をつかっていい。

内包表現の返り値はリストに限らない。into: で変更できる。

iex> for x <- ~w{ USA Japan Luxembourg }, into: %{}, do: {x, String.length(x)}
%{"Japan" => 5, "Luxembourg" => 10, "USA" => 3}

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は凄いなって。うむ。