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。
#on の block
の型が 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 を生成する。
(๑´ڡ`๑) おしまい
構造化プログラミングと脳みそクラッシャー
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
make
も guile
も無い状態なので、いったん 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 には print
も println
も無いので書いてみた。
どちらも可変引数を取って、それを出力する。
この 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
にしたらダメだし……。