Tambourine作業メモ

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

w3mをインストールしたら、opensslがインストールされて・・・あとでみる

ちょっと必要があってw3mをhomebrewでインストールした。 途中でopensslがインストールされてCaveatsがいっぱいでてるけど、 後で読む。そのためにとりあえずここに貼っときます。

> brew install w3m
==> Installing dependencies for w3m: bdw-gc and openssl
==> Installing w3m dependency: bdw-gc
==> Downloading https://homebrew.bintray.com/bottles/bdw-gc-8.0.4.mojave.bottle.
==> Downloading from https://akamai.bintray.com/05/05219d7d030791e3c3e3751b36a60
######################################################################## 100.0%
==> Pouring bdw-gc-8.0.4.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/bdw-gc/8.0.4: 69 files, 1.5MB
==> Installing w3m dependency: openssl
==> Downloading https://homebrew.bintray.com/bottles/openssl-1.0.2s.mojave.bottl
==> Downloading from https://akamai.bintray.com/c4/c4a762d719c2be74ac686f1aafabb
######################################################################## 100.0%
==> Pouring openssl-1.0.2s.mojave.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
  /usr/local/etc/openssl/certs

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

openssl is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries.

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

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

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

==> Summary
🍺  /usr/local/Cellar/openssl/1.0.2s: 1,795 files, 12.0MB
==> Installing w3m
==> Downloading https://homebrew.bintray.com/bottles/w3m-0.5.3_6.mojave.bottle.t
==> Downloading from https://akamai.bintray.com/57/571d0562f50fb42eab8fc7efd03e7
######################################################################## 100.0%
==> Pouring w3m-0.5.3_6.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/w3m/0.5.3_6: 28 files, 1.8MB
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /Users/tambara/Library/Caches/Homebrew/heroku--7.24.3.tar.xz... (6.5MB)
Removing: /usr/local/Cellar/openssl/1.0.2r... (1,795 files, 12.1MB)
Removing: /Users/tambara/Library/Caches/Homebrew/openssl--1.0.2r.mojave.bottle.tar.gz... (3.7MB)
Removing: /Users/tambara/Library/Caches/Homebrew/pandoc--2.7.2.mojave.bottle.tar.gz... (13.4MB)
Removing: /Users/tambara/Library/Caches/Homebrew/pyenv--1.2.11.mojave.bottle.tar.gz... (623.7KB)
Removing: /Users/tambara/Library/Caches/Homebrew/ruby-build--20190423.tar.gz... (60.7KB)
Removing: /Users/tambara/Library/Logs/Homebrew/pyenv... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/pkg-config... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/tree... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/pandoc... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/heroku... (114B)
Removing: /Users/tambara/Library/Logs/Homebrew/rbenv-communal-gems... (115B)
Removing: /Users/tambara/Library/Logs/Homebrew/readline... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/ruby-build... (2 files, 179B)
Removing: /Users/tambara/Library/Logs/Homebrew/nodebrew... (104B)
Removing: /Users/tambara/Library/Logs/Homebrew/heroku-node... (119B)
Removing: /Users/tambara/Library/Logs/Homebrew/autoconf... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/pcre2... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/openssl... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/fish... (64B)
Removing: /Users/tambara/Library/Logs/Homebrew/rbenv... (64B)
Pruned 0 symbolic links and 2 directories from /usr/local
==> Caveats
==> openssl
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
  /usr/local/etc/openssl/certs

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

openssl is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries.

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

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

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

C++でも遊んでみたい

2019年にもなって、C++を勉強しなくてはいけなくなった。嘆かわしい。

C++は大変に歴史があり、かつ、あり得ないぐらいに幅広く使われているので、大量の歴史的経緯を抱えている。 そのため、実体がよくわからない。私が最後にC++を勉強したのは20世紀のことなので、2019年モデルへの アップデートが必要である。まあ、ぶっちゃけ何にもおぼえてないので、位置から勉強する。

まずは教科書選びが必要。どれが良いのかはさっぱりわからないが、ここはC++の作者であるStroustrupさん自らが書いた、わりと新しめの教科書があるので、それを使ってみよう。もう一冊、いわゆるバイブル(言語作者自らが書いた本)があるようだけど、1万円ぐらいするのでこっちにしとく。

C++によるプログラミングの原則と実践

C++によるプログラミングの原則と実践

この本は、C++の本というよりもC++でプログラミングを学ぶ本だ。これはこれでStroustrupさんの思想信条が伝わってくる気がするのでいいと思っているけども、流石に大学1年生ではないので、すごい勢いで飛ばし読みすることになる。なんせ1200ページもあるのだ。基本、ざっと読んで、練習問題をやっていく感じになる。

まずは、第2章のドリルから。お約束のHello, Worldである。最大の難関だ。

以下のコードを、ビルドして実行せねばならない。

#include "std_lib_facilities.h"

int main()
{
    cout << "Hello, World\n";
    keep_window_open();
    return 0;
}

付録に環境構築の方法があるからそこを見て頑張れと書いてあるが、 そこに書いてあるのはVisual Studio のExpress版のインストール方法である。おまいがー。

とはいえ、Windowsと違って私のMacGCCが入っていなければロクに動くはずもない。 コマンドを打てばどうにかなるはずだ。打たずにどうにかしたいのだ。ぶっちゃけcargo runしたいのだ。ワガママだ。

とりあえず、Visual Studio Codeを立ち上げて、さっきのコードをhello.cppとして保存したところ 「ちょ、おまっ。お前、C++書いたな?書いたんだな?じゃあ、このプラグイン入れるか?入れるよな?」(超訳)と VS Codeが仰いますので、おとなしくプラグインを入れる。 入れて立ち上げ直して、メニューの[Run Build Task...]を選ぶと、clangを使うかg++をつかうかリストで選ばされる。 特に理由もなくg++を選ぶ。もちろんエラーである。

> Executing task: /usr/bin/g++ -g /Users/tambara/study/cpp_study/hello.cpp -o /Users/tambara/study/cpp_study/hello <

/Users/tambara/study/cpp_study/hello.cpp:1:10: fatal error: 'std_lib_facilities.h' file not found
#include "std_lib_facilities.h"
         ^~~~~~~~~~~~~~~~~~~~~~
1 error generated.
The terminal process terminated with exit code: 1

Terminal will be reused by tasks, press any key to close it.

とりあえず、コマンドを打たなくてもg++コマンドを実行してくれたことが驚きだ。世の中は進歩している。 しかしながら、エラーだ。「std_lib_facilities.hってなんすか?」と聞かれている。

うん、私も同感だ。

どうやらこれは学習用にStroustrup先生が用意してくれたヘッダファイルのようだ。 ダウンロードして使えばいいらしい。これを使わないのならば、代わりに

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
inline void keep_window_open() { char ch; cin >> ch; }

と書けとしてある。なるほど、教育的である。ちなみに最後のkeep_window_open()は何かというと、 WindowsでVSからビルドして実行したときにコマンドプロンプトの画面が一瞬で閉じてしまうと なにが起きたかわからないので用意された関数である。教育的すぎて凄い。

先生のサイトからダウンロードし、cppファイルと同じディレクトリにおいてみる。

再度、ビルドしてみよう。

> Executing task: /usr/bin/g++ -g /Users/tambara/study/cpp_study/hello.cpp -o /Users/tambara/study/cpp_study/hello <

In file included from /Users/tambara/study/cpp_study/hello.cpp:1:
In file included from /Users/tambara/study/cpp_study/std_lib_facilities.h:34:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ext/hash_map:213:5: warning: Use of the header
      <ext/hash_map> is deprecated. Migrate to <unordered_map> [-W#warnings]
#   warning Use of the header <ext/hash_map> is deprecated.  Migrate to <unordered_map>
    ^
In file included from /Users/tambara/study/cpp_study/hello.cpp:1:
/Users/tambara/study/cpp_study/std_lib_facilities.h:43:20: error: no matching function for call to object of type 'hash<char *>'
            return hash<char*>()(s.c_str());
                   ^~~~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ext/__hash:38:12: note: candidate function not
      viable: 1st argument ('const std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::value_type *'
      (aka 'const char *')) would lose const qualifier
    size_t operator()(char *__c) const _NOEXCEPT
           ^
1 warning and 1 error generated.
The terminal process terminated with exit code: 1

Terminal will be reused by tasks, press any key to close it.

warningはdeprecateだよと言われているだけなのでいいかな。

エラーの方はさっぱりわからないが、ググったらまったく同じようにこの本を使って勉強している人がStack Overflowに書き込みをしている。

stackoverflow.com

なるほど、わからん。

でも、constを付ければ良いようだ。その通りに修正してみる。

> Executing task: /usr/bin/g++ -g /Users/tambara/study/cpp_study/hello.cpp -o /Users/tambara/study/cpp_study/hello <

In file included from /Users/tambara/study/cpp_study/hello.cpp:1:
In file included from /Users/tambara/study/cpp_study/std_lib_facilities.h:34:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ext/hash_map:213:5: warning: Use of the header
      <ext/hash_map> is deprecated. Migrate to <unordered_map> [-W#warnings]
#   warning Use of the header <ext/hash_map> is deprecated.  Migrate to <unordered_map>
    ^
1 warning generated.

Terminal will be reused by tasks, press any key to close it.

おー、ちゃんと実行ファイルができた。

> ./hello
Hello, World
Please enter a character to exit

a

attr_accessorを自分で実装する

自作クラスでデータを保持しようとしていて半角カナが入ってきて発狂し、絶叫し、半角カナ絶対殺すマンが覚醒した。

といっても、Rubyで半角カナを全角カナに変換するのはとても簡単。NKFを使う。

> irb
irb(main):001:0> require 'nkf'
=> true
irb(main):002:0> a = "いささかプギャーな気分"
=> "いささかプギャーな気分"
irb(main):003:0> NKF.nkf("-Xw", a)
=> "いささかプギャーな気分"

これをセッターに組み込めばいい

require 'nkf'

class MyRecord
  @rec = ""
  
  attr_reader :rec
  
  def rec=(str)
    @rec = NKF.nkf("-Xw", str)
  end
end

r = MyRecord.new

r.rec = "いささかプギャーな気分"

p r.rec # => "いささかプギャーな気分"

問題ない。しかし、フィールドは1つではないかもしれない。 3つあったらこうなるだろう。

class MyRecord
  @rec1 = ""
  @rec2 = ""
  @rec3 = ""
  
  attr_reader :rec1, :rec2, :rec3
  
  def rec1=(str)
    @rec1 = NKF.nkf("-Xw", str)
  end
  
  def rec2=(str)
    @rec2 = NKF.nkf("-Xw", str)
  end
  
  def rec3=(str)
    @rec3 = NKF.nkf("-Xw", str)
  end
end

半角カナ絶対殺すマンは転生し、絶対DRY星人として蘇った。

さて、どうするか。こんな感じに出来ればいいのだろう。

class MyRecord
  @rec1 = ""
  @rec2 = ""
  @rec3 = ""
  
  my_attr :rec1, :rec2, :rec3
end

my_attrはMyRedordクラスのクラスメソッドとして定義してやればいい。 Rubyist人生で初めてinstance_variable_setを使った。

require 'nkf'

class MyRecord
  def self.my_attr(*name)
    attr_reader *name
    name.each do |n|
      define_method("#{n.to_s}=") do |str|
        instance_variable_set(
          "@#{n.to_s}".to_sym,
          NKF.nkf("-Xw", str)
        )
      end
    end
  end
  
  @rec1 = ""
  @rec2 = ""
  @rec3 = ""
    
  my_attr :rec1, :rec2, :rec3
end

r = MyRecord.new

r.rec3 = "いささかプギャーな気分"

p r.rec3 # => "いささかプギャーな気分"

treeコマンド

treeコマンドは、たまーに欲しくなる。 プロジェクトの共有ディレクトリの使い方を説明するととかに。

Macにはない。Homebrewで入れれば良いらしい

> brew install tree
==> Downloading https://homebrew.bintray.com/bottles/tree-1.8.0.mojave.bottle.ta
######################################################################## 100.0%
==> Pouring tree-1.8.0.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/tree/1.8.0: 8 files, 117KB

試して見る

> ls
納品      課題      成果物
> tree
.
├── ?\215?\223\201
├── 課?\214
└── ?\210\220?\236\234?\211?

3 directories, 0 files

おおぅ。-Nが必要らしい

> tree -N
.
├── 納品
├── 課題
└── 成果物

3 directories, 0 files

絶対忘れるので、aliasを設定しておく

> alias tree "tree -N"
> tree 
.
├── 納品
├── 課題
└── 成果物

3 directories, 0 files

fishのaliasは、実体としては関数定義。なので、関数を保存する必要がある。

> funcsave tree

確認してみよう

> functions
., :, N_, abbr, alias, bg, cd, cdh, contains_seq, delete-or-exit, dirh, dirs,
disown, down-or-search, edit_command_buffer, eval, export, fg,
fish_breakpoint_prompt, fish_clipboard_copy, fish_clipboard_paste, fish_config,
fish_default_key_bindings, fish_default_mode_prompt, fish_fallback_prompt,
fish_hybrid_key_bindings, fish_indent, fish_key_reader, fish_md5,
fish_mode_prompt, fish_opt, fish_print_hg_root, fish_prompt,
fish_sigtrap_handler, fish_title, fish_update_completions, fish_vi_cursor,
fish_vi_key_bindings, fish_vi_mode, funced, funcsave, grep, help, history,
hostname, isatty, kill, la, ll, ls, man, nextd, nextd-or-forward-word, open,
popd, prevd, prevd-or-backward-word, prompt_hostname, prompt_pwd, psub, pushd,
pyenv, rbenv, realpath, seq, setenv, string, suspend, trap, tree, type, umask,
up-or-search, vared, wait,
> functions tree
# Defined in - @ line 1
function tree --description 'alias tree tree -N'
    command tree -N $argv;
end
> 

OCamlでコマンドライン引数を処理する

さて、次はファイル名や表示行数を指定できるように修正しよう。

引数は、Cのargv相当がSys.argvに入ってくるが、自分でパースするのはめんどうだ。 もちろん、標準ライブラリでパースできる。Argモジュールを使う。

以下の手順を踏む。

  1. 引数で指定された情報を格納する参照を定義する
  2. どんな引数がくるのか、定義を作る
  3. パースする

詳しいことは、 http://caml.inria.fr/pub/docs/manual-ocaml/libref/Arg.html を読む。 手順2のanon_funに何を指定するのかわかりにくいが、要するにフラグ無しの引数の扱いを指定すればいい。

tail -n 5 filename に対応したかったら、

let n = ref 5 (* 表示する行数 *)
let inputfile = ref ""

let spec = [("-n", Arg.Set_int n, "Number of lines")]
let () = Arg.parse spec (fun s -> inputfile := s) ""


(* ファイルを全行読み込む *)
let fi = open_in !inputfile

let lines = 
  let lst = ref [] in
  let eof = ref false in 
  while not !eof do
    try lst := !lst @ [(input_line fi)] with End_of_file -> eof := true
  done;
  !lst

let () = close_in fi


(* 行のリストの最後n行だけ取り出す*)
let rec tail lst = match lst with
    [] -> [] 
  | first :: rest -> 
    let last = tail rest in
    if List.length last > !n - 1  then last else first :: last 

let () = List.iter print_endline (tail lines)

これでOK。以外とバリバリrefを使うのである(笑)。

OCamlでナイーブなtailを作る

プログラミングの基礎 ((Computer Science Library))を読み終わった。それなりに面白かった。正直、赤黒木とかちゃんと理解するのは普通に難しいので飛ばし読みしたけど。昔、一度理解はしたけど、実装しろと言われたらもう一度勉強し直さないとどうにもならないだろうなあ。

さて、OCamlをざっと理解した気になったので、Goと同じプログラムをOCamlで作ってみることにしよう。先ずは、tailが作れないことには話にならない。

とはいえ、教科書にファイルの読み書きは出てこないのだった(笑)。副作用のある処理はオマケ扱いだ。

標準出力への書込が出来ないと何が正しいのかさっぱりわからないので、Hello Worldから行こう。

> cat tail.ml
let () = print_endline "Hello, OCaml!"
> ocamlopt -o my_tail tail.ml 
> ./my_tail 
Hello, OCaml!

ocamloptコマンドは、ネイティブバイナリを作ってくれる。

さて、ファイルを読み込んでみよう。コマンドライン引数の処理方法をまだ知らないので、読み込むファイルは固定にする。

open_inでファイルを開き、input_lineで読む。 EOFまで読むと例外になるので、それをキャッチして読込を止める。とても命令型な処理になる。 教科書が何にも役に立たない(笑)。

let fi = open_in "./sample.memo"

let lines = 
  let lst = ref [] in
  let eof = ref false in 
  while not !eof do
    try lst := !lst @ [(input_line fi)] with End_of_file -> eof := true
  done;
  !lst

let () = close_in fi

let () = List.iter print_endline lines

whileを使ってファイルを読むところは、mutableなlstやモードを持つeofが出てきて全然functionalじゃない。がっかりだ。

とりあえず、これでcatコマンド相当だ。

tailにするには、listの後ろを取り出す。実は後ろを取り出すのは、再帰だと簡単だ。 こんな関数で出来る。

let n = 5
let rec tail lst = match lst with
    [] -> [] 
  | first :: rest -> 
    let last = tail rest in
    if List.length last > n - 1  then last else first :: last 

これで後ろの5行が取り出せることになる。

GoでMarkdownをHTMLにする

OCamlに浮気していたけど、golang版の議事録作成ツールの最後のピースとして、Makdownの変換部分について調べてみる。

Markdownの変換は標準ライブラリでは出来ない。外からライブラリを持ってくるときにはGOPATHが大事になるらしいが、 それも過去のことらしい。良くわからない。

最新のやり方に従うため、Software-Design 2019/05の特集記事を参考に、go mod initする。

> go mod init fmt_session_memo
go: creating new go.mod: module fmt_session_memo

fmt_session_memo というのはこれから作ろうとしているコマンドの名前である。

これをすると、go.modというファイルが作られる。

> cat go.mod
module fmt_session_memo

go 1.12

Markdownのライブラリはblackfridayというのを使うことにする。go getすればいいらしい。 Markdown記法で書かれたものをHTMLに変換するGo言語コード を参考にした。

> go get github.com/russross/blackfriday
go: finding github.com/russross/blackfriday v2.0.0+incompatible
go: downloading github.com/russross/blackfriday v2.0.0+incompatible
go: extracting github.com/russross/blackfriday v2.0.0+incompatible
go: finding github.com/shurcooL/sanitized_anchor_name v1.0.0
go: downloading github.com/shurcooL/sanitized_anchor_name v1.0.0
go: extracting github.com/shurcooL/sanitized_anchor_name v1.0.0

go.modが修正された。

> cat go.mod
module fmt_session_memo

go 1.12

require (
    github.com/russross/blackfriday v2.0.0+incompatible // indirect
    github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
)

go.sumってのも出来た。

> cat go.sum
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=

チェックサムが入ってる

後はblackfriday.MarkdownBasic()を使えばいいらしい・・・と思ったら、そんな関数ないと言われる。 GitHubを観に行くと、V2はRunを使えと書いてある。

str := string(blackfriday.Run(md))

これで出来た。