Crystal言語のドキュメントを読む(Literals)

Crystalドキュメントの Literals を読んだのでメモ。

コメント

  • # による一行コメントのみ

Nil

  • nil 。普通

Bool

  • truefalse の二つの値を持つ
  • RubyではBool型は存在しなくて、それぞれ TrueClassFalseClass の唯一の値という形なので、そこがちょっと違う

Integers

  • 8bit, 16bit, 32bit, 64bit。unsignedあり。
    • リテラルの接尾辞で型を明示できる。 _i16, _u32 など。
  • 整数リテラルの型は、サイズによって signed 32bit, signed 64bit, unsigned 64bit のどれかに決まる。
  • アンダースコア区切りOK
  • 0b でbinary、 0o でoctal、 0x でhexadecimal

Floats

  • デフォルト64bit。32bitを使いたいときは接尾辞に _f32 を付ける。
  • アンダースコア区切りOK

Char

  • Unicodeコードポイントを表す32bit値
  • \uxxxx で16進数4桁、 \u{xxxxxx} で16進数6桁までのコードポイント指定

String

  • immutable
  • 引用符は " のみ
  • %(...) のようなクォートもできる。括弧は他の種類もOK
  • \u に続けて16進数でコードポイント指定(Charと同じ)
  • リテラル内で改行できる
  • ヒアドキュメントは <<-IDENT から IDENT まで
  • #{} でstring interpolation
    • string interpolationを無効にするには %q(...) で囲う

Symbol

  • :hello など。普通

Array

  • genericな型を持つ。型は構築時に決まる。
    • たとえば [1,2,3]Array(Int32)[1,"Hello",'x']Array(Int32|String|Char)
    • 空配列を作るときは型を明示する必要がある。 [] of Int32
  • Rubyと同じように、 %w(one two three) で Array(String) が作れる
  • %i(one two three) で Array(Symbol) が作れる

Hash

  • genericな型を持つ。型は構築時に決まる。
    • たとえば {1=>2, 3=>4}Hash(Int32, Int32){1=>2, 'a'=>3}Hash(Int32|Char, Int32)
    • 空ハッシュを作るときは型を明示する必要がある。 {} of Int32 => Int32

Range

  • x..y とか x...yRubyと同じ

Regex

  • スラッシュ区切りで /foo|bar/ とするか、 %rと括弧で %r(foo|bar) とするか
  • PCRE というのが文法らしい

Tuple

  • タプルがある!
  • {1, "Hello", 'x'}Tuple(Int32, String, Char) 型になる
  • 空のタプルを作るときは Tuple.new を使う
  • メモリが静的にスタックに割り付けられる。関数から複数の戻り値を返すようなときに使うと良い

NamedTuple

  • NamedTupleがある!
  • {name: "Crystal", year: 2011}NamedTuple(name: String, year: Int32) 型になる(キーの値も型に含まれるということ?)
  • tuple[:name] のようにアクセスする
  • キーはStringでもOK

Proc

  • -> で作る。 lambdaproc キーワードはない
  • ->(x : Int32, y : Int32) { (x + y).to_s }Proc(Int32, Int32, String) 型になる
  • 引数の型は基本的に指定が必須
  • メソッドをProc化できる
def increment(x)
    x + 1
end
proc = ->increment(Int32) # 引数の型を限定している
  • レシーバを指定してProc化もできる
str = "abc"
proc = ->str.count(Char)
puts proc.call('x')

Rails環境をDockerで作る

Railsチュートリアルを始めた。せっかくなので環境をDockerで作ってみた。

これをかなり参考にさせてもらった RailsアプリをDockerで開発するための手順 - Qiita

Rails入りのDocker Imageを作る

RailsのDocker Imageは Docker Hubにある のだけど、なんかDEPRECATEDと書いてあるし、勉強も兼ねて自分でRails入りのイメージを作った。

RubyのイメージにRailsをインストールするために、次のDockerfileとGemfileを作成し、空の Gemfile.lock も作成した(内容は、参考にしたリンク先のものからバージョン番号を変えたくらいでほぼそのまま)。

Dockerfile:

FROM ruby:2.4.0

ENV APP_ROOT /usr/src/workspace

WORKDIR $APP_ROOT

RUN apt-get update && \
    apt-get install -y nodejs \
                       mysql-client \
                       postgresql-client \
                       sqlite3 \
                       --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \
  bundle config --global build.nokogiri --use-system-libraries && \
  bundle config --global jobs 4 && \
  bundle install && \
  rm -rf ~/.gem

Gemfile:

source "https://rubygems.org"
gem 'rails', '5.0.1'

ここからdocker buildしてRailsが入ったイメージを作成する。

$ docker build -t tomerun/rails_5_0_1 .
(snip)
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
tomerun/rails_5_0_1            latest              afc9c9a9ef93        3 minutes ago      801 MB

apt-getするときの --no-install-recommendsrm -rf /var/lib/apt/lists/* は、ファイルサイズを削減するため。参照: http://d.hatena.ne.jp/mainyaa/20140203/p1 の10番

作成したイメージからアプリのひな形を作成

アプリケーションを作成するディレクトリへ移動し、上で使ったGemfileをコピーしてくる(これがないとrails newが失敗するのだけど、Railsの仕組みを理解していないのでなぜ必要なのかわかっていない)。 その後、Docker内で rails new してアプリケーションのひな形を作る。 途中でGemfileを上書きして良いか聞かれるので Y で進める。

$ mkdir app_dir
$ cd app_dir
$ cp ../rails_template/Gemfile .
$ docker run --rm -it -v "$PWD":/usr/src/workspace tomerun/rails_5_0_1 rails new . -BT
(snip)
$ ls
Gemfile       README.md     app/          config/       db/           log/          tmp/
Gemfile.lock  Rakefile      bin/          config.ru     lib/          public/       vendor/

ライブラリをインストールしたDocker Imageを作る

使用するgemをインストールしたイメージを作っていく。

まず、Gemfileを必要に応じて書き換える。

次のDockerfileを作って、更新したGemfileを元にbundle installを実行したdocker imageを作成する。

FROM tomerun/rails_5_0_1

ENV APP_ROOT /usr/src/workspace

WORKDIR $APP_ROOT

COPY Gemfile $APP_ROOT

RUN bundle install --without production

EXPOSE  3000
$ docker build -t tomerun/rails_toy_app .
(snip)
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
tomerun/rails_toy_app          latest              e5c44f8a1a2b        3 minutes ago       854 MB
tomerun/rails_5_0_1            latest              afc9c9a9ef93        About an hour ago   801 MB

Gemfile.lockをイメージ内からホスト側へ持ってくるために適当なことをする

$ docker run -d tomerun/rails_toy_app sleep 100
$ docker cp 1ec8e208d405:/usr/src/workspace/Gemfile.lock .
# 1ec8e208d405 はコンテナID
$ docker stop 1ec8e208d405

次のようにするとRailsが起動する。localhost:3000 にアクセスすると動いている様子が見える。

$ docker run -d -p 3000:3000 -v "$PWD":/usr/src/workspace tomerun/rails_toy_app rails server -b 0.0.0.0

-v でファイル共有しているため、ホスト側でソースコードを書き換えると自動的に反映される。

Effective Python 1章

Effective Pythonの内容を身につけるため、各項目に関連した内容を書いていく。

  • 本の内容そのまま写経ではなく、できるだけ独自に例を作る
  • Python2に固有の話は基本的に無視する

項目1:使っている Pythonのバージョンを知っておく

自分のパソコンの環境

$ python --version
Python 2.7.10
# システムデフォルトのやつ

$ python3 --version
Python 3.6.0
# homebrewで入れた。virtualenvとかの仮想環境的な仕組みは使っていない

項目2:PEP 8スタイルガイドに従う

PEP8の本家ドキュメント

SublimeTextでは、SublimeLinter-pep8を使ってスタイルチェックができる。

項目3:bytes, str, unicodeの違いを知っておく

unicodeはpython2用なので気にしない。

>>> # ascii文字値から構成されるbytesは、先頭に'b'をつけたリテラルで表現できる
>>> bytes = b'a lazy fox'
>>> bytes
b'a lazy fox'
>>> # bytesへのインデックスアクセスで得られる要素はint
>>> bytes[0].__class__
<class 'int'>
>>> list(bytes)
[97, 32, 108, 97, 122, 121, 32, 102, 111, 120]
>>> 
>>> str = 'a lazy fox'
>>> str
'a lazy fox'
>>> # strへのインデックスアクセスで得られる要素はstr
>>> str[0].__class__
<class 'str'>
>>> list(str)
['a', ' ', 'l', 'a', 'z', 'y', ' ', 'f', 'o', 'x']

項目4:複雑な式の代わりにヘルパー関数を書く

たいへん当たり前の内容なので特に何も言うことはないが、本文に登場する例について。

def get_first_int(values, key, default=0):
    found = values.get(key, [''])
    if found[0]:
        found = int(found[0])
    else:
        found = 0
    return found

変数 found に再代入しているのがイマイチと感じる。このくらいの分岐だったら個人的には条件演算子の方が好み。

Pythonの条件演算子、ちょっと独特なのでそこでの読みづらさはある

def get_first_int(values, key, default=0):
    found = values.get(key, [''])
    return int(found[0]) if found[0] else 0

項目5:シーケンスをどのようにスライスするか知っておく

スライスの範囲がシーケンスの長さをはみ出ている場合、スライスの範囲長よりも小さいサイズのシーケンスが返される。

>>> l = [0, 1, 2, 3]
>>> l[:10]
[0, 1, 2, 3]
>>> l[10:20]
[]  # 範囲をはみ出ている場合、空リスト
>>> l[3:2]
[]  # スライスの範囲が大小逆転している場合、空リスト

ちなみにRubyの場合、指定するRangeが配列の長さの範囲から完全にはみ出ている場合、空の配列ではなくてnilが返ってくることが異なる。

> a = [0, 1, 2, 3]
 => [0, 1, 2, 3] 
> a[0...10]
 => [0, 1, 2, 3] 
> a[10...20]
 => nil  # 範囲をはみ出ている場合、nil
> a[3...2]
 => [] 

インデックスアクセスは、 __getitem__()__setitem__() で実装されている。これらのメソッドを定義すると、自作クラスでインデックスアクセスを提供できる。

class IndexedObject:

    def __init__(self):
        self._prop0 = 0
        self._prop1 = 1

    def __getitem__(self, key):
        print("getitem:" + str(key))
        if key == 0:
            return self._prop0
        elif key == 1:
            return self._prop1
        else:
            return None

    def __setitem__(self, key, value):
        print("setitem:" + str(key))
        if key == 0:
            self._prop0 = value
        elif key == 1:
            self._prop1 = value
        else:
            pass
>>> o = IndexedObject()
init
>>> o[1]
getitem:1
1
>>> o[1] = 'str'
setitem:1
>>> o[1]
getitem:1
'str'

このコードでは、あくまでサンプルとして key は整数にしか対応していない。スライスを渡す形式に対応したかったら、 __getitem__()__setitem__() の中で key の型を判定して処理を分ける。

項目6:1つのスライスでは、 start, end, strideを使わない

ややこしいスライスを行おうとしてコードが複雑になる場合、それを避けるためにスライスを複数回適用すると、リストのコピーが複数回行われて効率が悪い。

>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> b = a[1:6]
>>> c = b[::2]
>>> c
[1, 3, 5]
# a[1:6:2] と同じ結果

itertools#islice() を使うと、イテレータの形で中間状態を持てるため、効率が良い。

>>> from itertools import islice
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> iter = islice(a, 1, 6)
>>> list(islice(iter, None, None, 2))
[1, 3, 5]

項目7:mapやfilterの代わりにリスト内包表記を使う

適用したい関数がはじめからある場合には、別にmapでも問題なさそう。

あと、mapだと変換したシーケンスそのものではなくてイテレータが返ってくるという違いがある。 まあ、リスト内包表記のほうもジェネレータにすればいいだけではあるが。

>>> import math
>>> a = [1, 2, 4, 8]
>>> [math.log2(x) for x in a]
[0.0, 1.0, 2.0, 3.0]
>>> list(map(math.log2, a))
[0.0, 1.0, 2.0, 3.0]

filterも一緒に実行したい場合は、map/filterだと関数のネストになってしまうのでさすがにリスト内包表記の方が読みやすい。 (とはいえ、Rubyのメソッドチェーン形式のほうがより読みやすいんだけど)

項目8:リスト内包表記には、 3つ以上の式を避ける

リスト内包表記のネストは本当に読むの厳しいのでやめた方が良いですね…

項目9:大きな内包表記にはジェネレータ式を考える

ジェネレータを使うと無限リストも扱える

>>> from datetime import *
>>> 
>>> def generate_int():
>>>    i = 0
>>>    while True:
>>>        yield i
>>>        i = i + 1
>>> 
>>> start_time = datetime.now()
>>> for v in generate_int():
>>>    if (datetime.now() - start_time).total_seconds() > 1:
>>>        print(v)
>>>        break
661670

項目10:rangeよりは enumerateにする

enumerate、要はeach_with_indexだ

初期値を指定できるところは便利

>>> for i, e in enumerate(['a', 'b', 'c'], start=1):
...     print(f'{i}:{e}')
1:a
2:b
3:c

項目11:イテレータを並列に処理するには zipを使う

zipは3つ以上の引数も扱える。

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = [7, 8, 9]
>>> for x, y, z in zip(a, b, c):
...     print(f'x:{x} y:{y} z:{z}')
x:1 y:4 z:7
x:2 y:5 z:8
x:3 y:6 z:9

項目12:forとwhileループの後の elseブロックは使うのを避ける

はい。

項目13:try/except/else/finallyの各ブロックを活用する

  • finallyブロック
    • Pythonでfinallyブロックを使った覚えがない。これまでのところだいたいwith文で事足りている気がする
  • elseブロック
    • 成功時の処理をtry本体と切り離して明示できるのはいいですね
    • 変数のスコープがtryブロック内に閉じないからこれが実現できるのか