Ruby と Crystal で違うところ

gazel を書いてみた時に、Ruby と違うなーって感じたところを書いてみる。

型付き

わぁお! 型が付いてる!!

Tuple が追加された

Crystal では Tuple が追加されたので、引数のリストは実際 Tuple になったよ。

除け者にされた then

私は Ruby では then を付ける派だったんだけど、Crystal では then 付けなくてもいいんだね。

        if File.exists? gazel_file then                # then 付けちゃダメ!
          others = Dir.glob gazel_file

          return others.size == 1 ? others.first : gazel_file
        end

Dir.pwd が無い!

Dir.pwd の替わりに Dir.current を使おう!

    def find(options)
      here = Dir.current

      until filename = have_gazel_file?
        Dir.cd ".."

        return { "", here } if Dir.current == here || options[:no_search]?

        here = Dir.current
      end
      { filename, here }
    end

ʕ•͡ω•ʔ Dir.chdir もないけどな。

Hash の has_key? が無い

替わりに []? を使おう。

String -> は Proc(A, B).new では表記できない?

3 記事連続で Crystal。

#onblock の型が String -> なんだけど、戻り値の型が不明?なので、Proc(A, B).new { |x| ... } という Proc の表記では書けないっぽい………。

こういう風に書きたいけど、エラーになる:

proc = Proc(String, ).new { |x| hoge = x }             # エラー!!

C 言語的に考えれば、Void 型にすれば、値を返さない Proc になるはずだが……:

proc = Proc(String, Void).new { |x| hoge = x }         # proc の型は String -> Void になる……

この proc の型は String -> Void になる。

Why Crystal People???!!!!!!!!!!!!!!!

Crystal では何でもかんでも配列にしてあれこれするのは良くないぜ!

Rake のコードを Crystal に移植していたら、配列を関数の引数として展開できないせいでまずいことが起こった。

def standard_gazel_options(options)
  [
    [  "-A", "--all",
       "Show all tasks, even uncommented ones (in combination with -T or -D)",
       ->(value : String) { options.show_all_tasks = value }
    ], [
      "--backtrace=[OUT]",
      "Enable full backtrace.  OUT can be stderr (default) or stdout",
      ->(value : String) {
        options.backtrace = true
        #select_trace_output options, "backtrace", value
        value
      }
    ], [
      "-B", "--build-all",
      "Build all prerequisites,  including those which are up-to-date.",
      ->(value : String) {
        options.build_all = true
        value
      }
    ]#,
    #  []
  ]
end

parse = OptionParser.new do |opts|
  opts.banner = "gazel [-f <gazel file>] {options} targets..."
  opts.separator ""
  opts.separator "Options are ..."

  opts.on "-h", "--help", "Display this help message" do
    puts opts
    exit
  end

  standard_gazel_options(options).each do |args|
    opts.on *args                                   # エラー!!!
                                                    # Array を引数として展開することはできない!
  end
end

Crystal には Tuple があるので、それだったら展開できるんだけど、Array(T) から Tuple に変換する便利な関数が無いっぽい。
それに Crystal では、standard_gazel_options 関数が返す配列の型は Array(Array(String | String -> String)) なので、
要素が String なのか、String -> String なのかは確かめる必要がある。

それは激しくめんどくさいので、こういう場合は、Struct を作って、それの配列として standard_gazel_options 関数が返すことにしよう。
こんな風に、OptionDefine という構造体(Struct)を定義して:

struct OptionDefine
  property short_flag
  property long_flag
  property description
  property block

  def initialize(@short_flag, @long_flag, @description, @block)
  end
end

standard_gazel_options 関数をこういう風に書き換えよう:

def standard_gazel_options(options)
  [
    OptionDefine.new( "-A", "--all",
                      "Show all tasks, even uncommented ones (in combination with -T or -D)",
                      ->(value : String) { options.show_all_tasks = value } ),
    OptionDefine.new( nil, "--backtrace=[OUT]",
                      "Enable full backtrace.  OUT can be stderr (default) or stdout",
                      ->(value : String) {
                        options.backtrace = true
                        #select_trace_output options, "backtrace", value
                        value
                      } ),
    OptionDefine.new(  "-B", "--build-all",
                       "Build all prerequisites,  including those which are up-to-date.",
                       ->(value : String) {
                         options.build_all = true
                         value
                       } )
  ]
end

OptionDefine のメンバーを渡すところはこういう風に書くかもしれない:

  standard_gazel_options(options).each do |args|
    if args.short_flag.nil? then
      opts.on                  args.long_flag, args.description, &args.block
    else
      opts.on args.short_flag, args.long_flag, args.description, &args.block
    end
  end

(๑´ڡ`๑) いいかも

Crystal の Proc

こういう関数があって:

def greeting(&block)
  yield "Hello, World!"
end

こんな風に渡すことができるよ:

#
# 通常通りにブロックを渡す。
greeting { |v| puts v }

#
# puts を Proc にして渡す。
greeting &->puts(String)

#
# puts を Proc にしたものをブロックとして渡す。
proc = ->puts(String)
# greeting proc               # これはエラー!
greeting &proc

Crystal ではオーバーロードができるので、ブロック無しの greeting を呼び出したい場合は、
ブロック無しの greeting を作ろう?

YAML でターゲット書くビルドツール gazel とかどうかな

だいぶ前から YAML でビルドスクリプト(?)書いて、ビルドしてくれるツールのことを考えていた。

なんか今日、これだっていうのが浮かんだので書いてみる。
gazel は bazel と似てるけど、名前を忘れていてそれに想起されて思いついた。

とはいえ、例によって使ったことがないので、gazel は YAML を読み込んで依存を解決するだけの似非 Make みたくなっている。
まずは 1 つのソースファイルを実行ファイルにするケースを考えてみよう。
こういう YAML ファイルがあって:

- target:
    src: hoge.c
    dst: hoge

ファイル名は Gazelfile でも、build.yml でも build.gazel でも多分おk。

% gazel

と打つと、[Gg]azelfile、build.yml、build.gazel を探してそれを読む。
依存関係をあれやこれやして、多分ビルドしてくれる。

dst が無い場合:

- target:
    src: hoge.c

この名前がないターゲットは default という名前になって、gazel はこのターゲットの依存関係を解決しようと試みる。
規定の phony パターン、.c.o によって gazel は hoge.c から何らかの方法で*1 hoge.o を生成する。

(๑´ڡ`๑) おしまい

*1:gcc を使って

構造化プログラミングと脳みそクラッシャー

COBOL は構造化プログラミングが可能な言語として名前が挙げられることが多い。
でも、COBOL が構造化プログラミングできる言語だというのはまったくもって真実ではない。
COBOL の構造化の考え方はこうだ:

COBOL には3つの構造化文がある!  
曰く、「順次」、「反復」、「分岐」だ!
これを持っているから、COBOL は構造化プログラミングをサポートした言語なんだ!!!

えーっと、それで?
それは、食パンには 3 つの要素があって、ふかふかの白いところ、茶色い耳だ!*1
というのとなんら変わりがない。 白くて茶色というのは食パン以外のパンにも当てはまる興味深い点で、それって特徴にもなっていないんじゃないの?

私が思うに、構造化というのは IF 文や FOR 文、その他の文のような要素のことを指すのではなくて、それを使って更に巨大な構造を作ることを指すんだと思う。
っていうか COBOL にはその点がまったく抜けていて、構造化プログラミングができないできそこないの言語だと言えるんじゃないだろうか?

今となっては構造化というのは、ビルを作るときのコンクリートを混ぜ合わせるときのコツのようなありふれた知識になってしまっているような感じがあるけれど、 そのことについてあまり理解されていないというのは本当に嘆かわしいことだと思う。

*1:3 つめを考えるのがめんどくさかった

わんおぶ!

こんな風に書くのめんどいよね。

(or (equal? x #\a)
    (equal? x #\b)
    (equal? x #\c))

just を書いて……

(define* (just x #:optional (judge? equal?))
  (lambda (other)
    (judge? x other)))

one-of をこういう風に書いたら、

(define (one-of . args)
  (lambda (x)
    (any (just x) args)))

こんな風に書けるよ!

(define one-two-three? (one-of 1 2 3))

(one-two-three? 1)

ちょまどデバッグ ちゃぷたー1

なんでこれを書きたくなったかはめんどくさいので省きます。
便利プロパティを知らない人が多いっぽい感じなので、少しでも便利プロパティを知って楽にコードを書きましょう。

1 日の中で何秒経過したか知りたい!

.NET では、DateTime オブジェクトから DateTime オブジェクトを引くと TimeSpan オブジェクトが返ってきます。
また、DateTime#Date プロパティはレシーバの年月日部分を取得することができます。

ということは、DateTime.Now から DateTime.Now.Date を引けば、今日の零時からの経過時間を求めることができますね。

var elapsedTimeSpanOfDay = today - today.Date;

さて、秒で知りたいということなので、上記の elapsedTimeSpanOfDay では不完全ですね。
ということで、TimeSpan#TotalSeconds プロパティを使えば経過時間を秒単位に変換したものを取得することができます。

以下の elapsedSecondsOfDay が答えです。

var elapsedSecondsOfDay = ( today - today.Date ).TotalSeconds;

1 年の中で、何日と何秒経過したか知りたい!!

gist の中でこのように書いたわけですが:

var daysOfYear = ( today.DayOfYear - 1 ) + elapsedSecondsOfDay / SecondsPerDay;

これも TimeSpan で求められる気がしませんか?

var firstDay = new DateTime( today.Year, 1, 1 );

var daysOfYear = ( today - firstDay ).TotalDays;

(๑´ڡ`๑) よさそう

Wercker で Scheme なモジュールのテストした

全国七千万の自動テストファンのみなさんこんばんは! 昨日ね、wercker でテストしたのでそんな感じのことを書くよ。

wercker.yml はこんな感じ:

# This references a standard debian container from the
# Docker Hub https://registry.hub.docker.com/_/debian/
# Read more about containers on our dev center
# http://devcenter.wercker.com/docs/containers/index.html
box: debian
# You can also use services such as databases. Read more on our dev center:
# http://devcenter.wercker.com/docs/services/index.html
# services:
    # - postgres
    # http://devcenter.wercker.com/docs/services/postgresql.html

    # - mongodb
    # http://devcenter.wercker.com/docs/services/mongodb.html

# This is the build pipeline. Pipelines are the core of wercker
# Read more about pipelines on our dev center
# http://devcenter.wercker.com/docs/pipelines/index.html
build:
    # Steps make up the actions in your pipeline
    # Read more about steps on our dev center:
    # http://devcenter.wercker.com/docs/steps/index.html
  steps:
    - script:
        name: update apt
        code: |
          sudo apt-get update
    - script: 
        name: install Make
        code: |
          sudo apt-get install -y make
    - script:
        name: install Guile
        code: |
          sudo apt-get install -y guile-2.0
    - script:
        name: perform test
        code: |
          make check

makeguile も無い状態なので、いったん apt-get update した後、それぞれインストールする。
2 つ同時にインストールしても多分大丈夫そう:

% sudo apt-get install make guile-2.0

気をつけなきゃいけないのが -y というオプション。
これが無いと、永久にインストールが終わらないので、自動で y してくれるこのオプションを忘れないようにしよう。

いっちゃん上の Makefile はこう:

# $(srcdir)/Makefile
check:
    make -C ./test

test/Makefile はこうなっている:

# $(srcdir)/test/Makefile
all: check

check: 01-simple

01-simple:
    guile -L ../lib -s ./01-simple.scm

それで srfi-64 ではテストが失敗しても、$? は必ず 0 なので、失敗したかどうかわからない。
応急処置としてとりあえず、失敗したテストの個数を返すようにした:

(let ((runner (test-runner-current)))
  (exit (test-runner-fail-count runner)))

そしたら、失敗した。

モジュールのこと

Scheme の S6RS や S7RS では モジュールの名前を (shell command) みたいにリストで表記する。
私が使っている Guile ではそういう風に表記するので、調べてみたら R6RS や R7RS のあれだった。

R6RS でも R7RS でもモジュールじゃなくてライブラリというらしい。
Guile のモジュールはこのように書ける:

(define-module (std io)
  #:export (print println))

(define* (print #:rest args)
  (define (recur xs)
    (if (null? xs)
        #f
        (begin
          (display (car xs))
          (recur (cdr xs)))))
  (recur args))

(define* (println #:rest args)
  (define (recur xs)
    (if (null? xs)
        (newline)
        (begin
          (display (car xs))
          (recur (cdr xs)))))
  (recur args))

Guile には printprintln も無いので書いてみた。
どちらも可変引数を取って、それを出力する。
この 2 つの違いは println が最後に改行することくらいだ。

次は R6RS で同じライブラリを書いたらどうなるか。さあ、どうだ:

(library (std io)
  (import (rnrs (6)))
  (export print println))

(define* (print #:rest args)
  (define (recur xs)
    (if (null? xs)
        #f
        (begin
          (display (car xs))
          (recur (cdr xs)))))
  (recur args))

(define* (println #:rest args)
  (define (recur xs)
    (if (null? xs)
        (newline)
        (begin
          (display (car xs))
          (recur (cdr xs)))))
  (recur args))

これでいけるはずなんだが、syntax error になってしまった。

(library (std io)
  (export print println)
  (import (rnrs base (6)))

  (define (print #:rest args)
    (let recur ((xs args))
      (cond ((null? xs)   #f)
            (else         (display (car xs))
                          (recur   (cdr xs))))))

  (define (println #:rest args)
    (let recur ((xs args))
      (cond ((null? xs)   (newline))
            (else         (display (car xs))
                          (recur   (cdr xs)))))))

名前付き let にしたけどやっぱりだめっぽい。
なんかよくわからん………。
define* が無いって表示されるけど、define にしたらダメだし……。