Tambourine作業メモ

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

Goでナイーブなtailを作る

私が日頃作るツールの大抵はフィルタなので、まずはcatコマンドが実装出来ることが必要。 とりあえず、ファイルをいったん全部読み込んで、最後の5行だけ書き出す、ナイーブな実装のtailコマンドを作ってみる。

パッケージのリストを眺めるとio/ioutil.ReadFileが良さそう。

サンプルをそのまま引き写す

package main

import (
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    content, err := ioutil.ReadFile("./sample.memo")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", content)
}

ReadFile()の戻りは(byte, error)である。 byteとstringの関係を知る必要がある。公式ドキュメントのstring型の説明を読むと、

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

てけとーな訳

string型は、8bitのbyteのあらゆる並びの集合です。伝統的には、UTF-8エンコードされたテキストを表現するものですが、 必ずしもそうである必要はありません。string型は空かもしれませんが、nilにはなりません。string型の値は、immutable(不変)です。

という説明なので、UTF-8のシーケンスであれば、byteと大差は無いのだろう。

とりあえず、byteはPrintf()を使えば文字列として表示できることはわかった。 これをPrintln()にすると、255以下の数字が並ぶことになる。 単純に型変換したらどうなるのだろう。fmt.Println(string(content))としたら、これでもちゃんと表示された。よしよし。

さて、これを各行に分解したい。すると、bufio.Scannerというのがあるみたい。 なるほど、いわゆるバッファドIOは、ioじゃなくて、bufioを使うのね。 ファイルを1行ずつ読み込むのはこれで良さそう。

ただし、今回は一挙に全部読み込みたいので、読み込んだものをsplitして各行にしたい。

regexpパッケージにSplitがあるので、たぶんこれでよいだろう。

main関数の中はこんな感じにした。regexpのimportは追加してある。

func main() {
    content, err := ioutil.ReadFile("./sample.memo")
    if err != nil {
        log.Fatal(err)
    }

    re := regexp.MustCompile(`\r?\n`)
    lines := re.Split(string(content), -1)
    noLines := len(lines)
    startidx := noLines - 5
    for i, v := range lines[startidx:] {
        fmt.Println(i+startidx+1, " : ", v)
    }
}

うん。ちゃんとラスト5行が取り出せている。おけ、おけ。

Goのチュートリアルをやる(4)

Goroutine

GoのGoたる所以だと、風の噂に聞いた。

  • 関数をgo付けて呼ぶと、Goroutineで動く
  • 通信するにはチャネル(型名はchan)を渡す
    • チャネルはmake(chan int, 255)のようにしてバッファに出来る。
    • チャネルはclose(ch)でクローズ出来る。しまっていることはv, ok := <-chで確認できる。
  • Mutexもある

Goのチュートリアルをやる(3)

メソッド

メソッドはレシーバーの型を指定した関数。その型を定義したパッケージでしか定義できない。

オブジェクト指向言語だとthisを使うようなところで、レシーバーの仮引数があるのが変わってるかもしれない。

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

thisを変更したい場合には、レシーバでポインタを受け取るようにする。

インターフェース

  • Rustでいうところのトレイト。
  • ただし、JavaやRustみたいに型が実装しているかを宣言はしない。ジェネリクスがないから。実装していない型に対して呼んじゃったら単に実行時エラー。
  • 値と型のタプルのように考えればいいらしい・・・どういう意味かよくわからない。
  • 空のインターフェースinterface{}を、Cのvoidのように使える

アサーションは、Cでいうキャスト。

var i interface{} = "hello"
t := i.(string)

とすると、tはstring型になる。

キャストに失敗するとパニックだが、第2の戻り値を取るとパニックにならない。

f := i.(float64) // => panic
f, ok := i.(float64) // 0, false
f, _ = i.(float64) // 0

Goのチュートリアルをやる(2)

More types

スライス

スライスが出てきた。スライスの概念はRustで初めて知ったけど、Goも大体同じ

  • 長さと容量を別に持ってる
  • スライスリテラル[]int{5, 4, 3, 2, 1}の様に作る。
  • スライスに要素を追加するには、append()を使う。容量を増やさないといけないときは、ギリギリじゃなくて余裕を持たせて(たぶん基本は倍)増やす。

構造体のスライスのリテラル

宣言と初期化をいっぺんにするのアリ。

s := []struct {
    i int
    b bool
}{
    {2, true},
    {3, false},
    {5, true},
    {7, true},
    {11, false},
    {13, true},
}

Map

キーに対して値をもつかどうかを2つめの戻り値でチェックできる

v, ok := m["key"]

いらなければ、

v := m["key"]

でよい。v, _で受けなくてよいのがGoのスタイルなのかな。

関数

  • 関数を変数に入れる場合の型の指定はfuncを使う。
  • 変数に入った関数のcallは普通に()をつける感じ。
  • Goの関数はクロージャ。環境をバインドする。
func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

Goのチュートリアルをやる(1)

仕事でMarkdownっぽいフォーマットで議事録を書くと、ダサいフォーマットのHTMLに変換するスクリプトRubyで書いたら、gem installがsudoでしか出来ないと言われ、悲しんだ。

実行ファイルを作って配るのがやはり楽かもしれない。Rustでやろうかとも思ったけど、これを機にちょっとGoを触ってみることにする。 インストールはいつものようにbrew install goで。GOPATHやGOROOTは特に設定しろと言われなかったので、このままいってみる。

では、さっそくチュートリアル。日本語もあって嬉しい。

tour.golang.org

最初にページに書いてあるHello Worldを書いてみる。しかし、コンパイル方法が書いてない(笑)。

> pwd
/Users/tambara/study/go_study
> ls
hello.go
> cat hello.go 
package main

import "fmt"

func main(){
    fmt.Println("Hello, 世界")
}

goとだけ打つとUsageが出るので、それをみて、適当にgo buildしてみる

> go build
> ls -l
total 4128
-rwxr-xr-x  1 tambara  staff  2108040  3 31 09:52 go_study
-rw-r--r--@ 1 tambara  staff       76  3 31 08:54 hello.go
> ./go_study
Hello, 世界

実行ファイルできた。

> go clean
> ls 
hello.go

cleanで消えた。

そういえば、Rustではcargo runしたなと思い出し、runしてみる。

> go run
go run: no go files listed

なんか設定ファイルっぽいものが必要な様子。まあ、わからんでもない。気にせず進むことにする。

  • welcome
  • basics
  • flowcontrol

は特に問題なく進んだ。以下、ちょっと面白かったところ。

変数宣言

初期化しない変数宣言にはvarがいる。初期化がある場合には、

var i int = 1
var j = 2

としてもいいけど

i := 1

が簡潔で好まれるみたい。

制御構造

switch with no condition

switchで、条件を省略すると暗黙のtrueが置かれるというのは面白い。 というか、その場合は条件と値が逆の取扱になる。 かけ算の順序に厳しい小学校では烈火のごとく怒られそうだ(笑)。

以下のようにすると、最初にtrueになるcondの部分が実行される

switch {
case cond1:
    // cond1がtrueなら実行
case cond2:
    // cond1がfalse, cond2がtrueなら実行
default:
    // どちらもfalseなら実行
}

そういえば、何が真偽値になるのかの話はまだ読んでなかった。

Defer

関数の遅延評価。引数は渡した時点で評価されるが、関数の中身はreturnするまで評価されない。

var gs = "ぐろーばる"

func hoge(s string) {
    fmt.Println(gs + s)
}

func main(){
    s := "ろーかる"
    defer hoge(s)

    gs = "グローバル"
    s = "ローカル"
}

"グローバルろーかる"が返る。複数回deferするとスタックに積まれる。

Rails チュートリアルをやってみる(11) 5.1 テンプレートまわり

5章に入る。

5.1

5.1.1でまずはHTMLにdivタグとclass設定で構造を入れる。この時点ではCSSを当ててないので、しょっぱい。演習はcurlコマンドを使ってみろとかなので省略。

5.1.2でBootstrapを適用する。まずはgemをインストール

> vi Gemfile
> git diff Gemfile
diff --git a/Gemfile b/Gemfile
index ad03f0d..384a66b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,7 @@
 source 'https://rubygems.org'
 
 gem 'rails',        '5.1.6'
+gem 'bootstrap-sass','3.3.7'
 gem 'puma',         '3.9.1'
 gem 'sass-rails',   '5.0.6'
 gem 'uglifier',     '3.2.0'
> bundle install
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 12.3.1
Using concurrent-ruby 1.1.3
Using i18n 1.1.1
Using minitest 5.10.3
Using thread_safe 0.3.6
Using tzinfo 1.2.5
Using activesupport 5.1.6
Using builder 3.2.3
Using erubi 1.7.1
Using mini_portile2 2.3.0
Using nokogiri 1.8.5
Using rails-dom-testing 2.0.3
Using crass 1.0.4
Using loofah 2.2.3
Using rails-html-sanitizer 1.0.4
Using actionview 5.1.6
Using rack 2.0.6
Using rack-test 1.1.0
Using actionpack 5.1.6
Using nio4r 2.3.1
Using websocket-extensions 0.1.3
Using websocket-driver 0.6.5
Using actioncable 5.1.6
Using globalid 0.4.1
Using activejob 5.1.6
Using mini_mime 1.0.1
Using mail 2.7.1
Using actionmailer 5.1.6
Using activemodel 5.1.6
Using arel 8.0.0
Using activerecord 5.1.6
Using ansi 1.5.0
Using execjs 2.7.0
Fetching autoprefixer-rails 9.3.1
Installing autoprefixer-rails 9.3.1
Using bindex 0.5.0
Using rb-fsevent 0.10.3
Using ffi 1.9.25
Using rb-inotify 0.9.10
Using sass-listen 4.0.0
Using sass 3.7.2
Fetching bootstrap-sass 3.3.7
Installing bootstrap-sass 3.3.7
Using bundler 1.17.1
Using byebug 9.0.6
Using coderay 1.1.2
Using coffee-script-source 1.12.2
Using coffee-script 2.4.1
Using method_source 0.9.2
Using thor 0.20.3
Using railties 5.1.6
Using coffee-rails 4.2.2
Using formatador 0.2.5
Using ruby_dep 1.5.0
Using listen 3.1.5
Using lumberjack 1.0.13
Using nenv 0.3.0
Using shellany 0.0.1
Using notiffany 0.1.1
Using pry 0.12.2
Using guard 2.13.0
Using guard-compat 1.2.1
Using guard-minitest 2.4.4
Using multi_json 1.13.1
Using jbuilder 2.7.0
Using jquery-rails 4.3.1
Using ruby-progressbar 1.10.0
Using minitest-reporters 1.1.14
Using puma 3.9.1
Using sprockets 3.7.2
Using sprockets-rails 3.2.1
Using rails 5.1.6
Using rails-controller-testing 1.0.2
Using tilt 2.0.8
Using sass-rails 5.0.6
Using spring 2.0.2
Using spring-watcher-listen 2.0.1
Using sqlite3 1.3.13
Using turbolinks-source 5.2.0
Using turbolinks 5.0.1
Using uglifier 3.2.0
Using web-console 3.5.1
Bundle complete! 21 Gemfile dependencies, 80 gems now installed.
Gems in the group production were not installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

カスタムCSSファイルを作る。

> ls
application.css   static_pages.scss
> touch custom.scss
> ls
application.css   custom.scss       static_pages.scss
> vi custom.scss 
> cat custom.scss 
@import "bootstrap-sprockets";
@import "bootstrap";

これで、bootstrapのCSSが適応されるらしい。ほうほう。

f:id:Tambourine:20181127113811p:plain

こんなしょっぱい画面がこうなる・・・と

f:id:Tambourine:20181127113742p:plain

あり?

<ul class="nav navbar-nav navbar-right">
  <li><a href="#">Home</a></li>
  <li><a href="#">Help</a></li>
  <li><a href="#">Log in</a></li>
</ul>

FireFoxの開発ツールでみると、navbar-rightが当たってないように見える。うーむ・・・

CSSを開発ツールで確認してみると、

@media (min-width: 768px) {
  .navbar-right .dropdown-menu {
    right: 0;
    left: auto;
  }
}

あー、ブラウザの幅が狭すぎただけだ。

f:id:Tambourine:20181127155120p:plain

広げたらちゃんと収まりました。

custom.scssにいろいろいれて遊んでみる演習はパス。

5.1.3はpartial。要するに、HTMLテンプレートのモジュール化で、renderコマンドでインポートするようなイメージだと。ふむふむ

Rails チュートリアルをやってみる(10) 4章 Rubyについて

Rails風味のRuby」とのこと。味わってみよう。

4.1

リスト4.1で例として、app/views/layouts/application.html.erbの以下のコードを取り上げている。

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

Rubyを知らなければ、この表記は戸惑うんだろうなあ。Ruby的には

stylesheet_link_tag( 'application', media: 'all', {'data-turbolinks-track': 'reload' })

なので、引数3つのメソッド呼び出しである。

ちなみに、キーワード引数がなかった昔のRubyではこれは

stylesheet_link_tag( 'application', {:media => 'all', 'data-turbolinks-track' => 'reload'} )

だった。そして、実はあんまりキーワード引数の仕様にくわしくないので、この辺りの私の理解は曖昧だ。

それはいいとして、これを「Railsの組込関数」と表現されると悩ましい。 Rubyではすべてはメソッドで、「Rubyの組込関数」とはKernelモジュールのメソッドのことだからだ。

Rails APIのリファレンスへのリンクが示されているのでそこを見ると、このメソッドは ActionView::Helpers::AssetTagHelperのメソッドらしい。 ということは、このERBが評価されるコンテキストではAssetTagHelperがincludeされているということだ。 しかし、「なんだこのメソッド?」と思ったときに所属しているクラスを突き止めるのは大変そうだな。

・・・などと思うんだけど、それはたぶん先を読めば書いてある(笑)。先に進もう。

4.1.2はカスタムヘルパーだ。置き場所としてapp/helpers/application_helper.rbが最初から作られてるので、 そこにメソッドを作ってみようとのこと。ふむふむ。

楽しみのために仕様を理解して、リスト4.2を見ないで作ってみる。

module ApplicationHelper
    def full_title(page_title = '')
        out = ["Ruby on Rails Tutorial Sample App"]
        out.unshift page_title unless page_title.empty?
        out.join(" | ")
    end
end

テストがないと不安だな。これをレイアウトに適用してみる。うん、ちゃんと動いているようだ。

あとは4.2からこの章の最後まで、バージョン1.4から付き合っているRubyistとしては特に新しい知識はなかった。