float と big decimalをなんとなくベンチした

rubyで
3.14 * 10
=> 31.400000000000002
ってなるよなーって思ってbig decimalベンチしてみた。

手元のthinkpad x1 carbon 2016 にubuntu 16.04

$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

$ cat float-vs-big-decimal.rb
require 'bigdecimal'
require 'benchmark'

n = 1000000
Benchmark.bm do |r|
  r.report "float" do
    n.times do
      3.14 * 10
    end
  end
  r.report "big decimal" do
    n.times do
      BigDecimal("3.14") * BigDecimal("10")
    end
  end
end
(rc->0)
$ ruby float-vs-big-decimal.rb 
       user     system      total        real
float  0.060000   0.000000   0.060000 (  0.052816)
big decimal  0.960000   0.000000   0.960000 (  0.967326)

生成コストでフェアじゃない?気もするけど 最後人間に読めるようにto_f するとか細かいこと気にするなってことで。16倍、思ったほどではなかった もうbigdecimal常用でいいのかな。もちろん用途によるんだけど。
キャッシュするとかベンチになってないとかあるかも。あんまりわかってないから。

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
$ ruby float-vs-big-decimal.rb                                                  
       user     system      total        real
float  0.060000   0.000000   0.060000 (  0.055150)
big decimal  1.020000   0.000000   1.020000 (  1.020329)

手元のrubyははじめ謎のバージョンで止まってたままだった。けど2.4にあげたら20倍が16倍になってなるほどはやくなってるんだ。

あとBenchmark.bm の後ろの数字繰り返し回数かと思って Benchmark.bm 1000000 do |r| ってやってすごい文字数空白出てきた。はてなマーク出しながら10回ぐらいやっておかしくねって気づいた。ラベルの幅て。

Update dependencies

json gemが1.xだとruby2.4で動かなくて、2.xだとruby1.9で動かない。
みたいなので結構おっくうになってたのだけど、ちょっとずつバージョンを上げ始めた。

http://rubies.travis-ci.org/ みながらここにあげる

- 2.0.0
- 2.1.10
- 2.2.6
- 2.3.3
- 2.4.0

今上げてるのが、nodejsにかぶれたあとのライブラリ群なので、まだそんなに苦労はしていない。まだ。

いまのところで引っかかるのは、
– rubocopがどこかのバージョンから1.9サポートをdropしている
– public_suffixがどこかのバージョンから1.9サポートをdropしている

このへん。
rubocopはgemspecではなくGemfileで入れてることが多いので、gem 'rubocop' if RUBY_VERSION >= 2.0.0みたいにした。
octokit -> sawyer -> addressable -> public_suffix
を使っているライブラリはもうどうしようもないので、1.9サポートをdropした。

1.9なんてもういいだろって思うけど、travis-ciって便利サイトがデフォルトruby1.9で、他の言語で使って欲しいツール・ライブラリでは1.9もサポートしたいのよね。出来る限り。
で、出来ない限りになってしまったので、1.9サポートdropはしかたない。
あとはmac os xっていう便利プラットフォームもsystem rubyが2.0とかいう化石使ってるので、2.0サポートも出来る限りしたい。
こういう小さいツール・ライブラリを作るのにrubyを使うのは向かない、ってことになってしまうのがなんともはや。
golangでやろう、って方向へいきますね。golangまだ全然思うように書けないけど。

Rubyのハテナってbool返すんじゃないの?

rubyのNumeric#nonzero?ってself | nilを返すんだ。ハテナってbool返すんじゃないの?
って聞かれた。
自分もこの前までそう思ってたんだけど、たしかそうじゃない便利ケースがAPIデザインケーススタディに出てきた、
ってうろ覚え知識を披露した。なにの時便利だったかは覚えてなかったので、ふわふわ。
なので調べた。

instance method Numeric#nonzero? (Ruby 2.3.0) http://docs.ruby-lang.org/ja/2.3.0/method/Numeric/i/nonzero=3f.html

Rubyではnilとfalseが偽、それ以外の値は全て真とみなされるので、このInteger#nonzero?の振る舞いは、0に対して偽、
それ以外に対して真を返すものと言えます。つまり名前に反する動作というわけではありません。
(snip)
意外な動作は学習コストを上げるので、理由がなければ避けるべき
(snip)
Enumerable#sort メソッドに与えるブロックの中の記述を簡単にするため。
APIデザインケーススタディ 田中哲

enum.sort {|a, b| c = a.x <=> b.x; c != 0 ? c : a.y <=> b.y }
が
enum.sort {|a, b| (a.x <=> b.x).nonzero? || a.y <=> b.y }

と記述できる。

具体的には自分でハテナつきメソッドを書くとき、気を使うときは、
わざわざtrue/false を返すように、最後絶対bool返すぞ!って foo ? true : falseって書くことはある。
どこかで何かを読んで、しかも最近、必ずしもそうしなくて良くて、
truthy/falsy 返すだけで名前に反するわけではない、と何かで読んだ。
そのなにかがAPIデザインケーススタディだった。

別のものを返すことで使いやすいケースだったりそういう使い方しかないケースでは、bool以外が返ることがある。
だったはずだから、ドキュメントにあるはず。

documentみていく。
ObjectやEnumerableにはbool返すのしか生えてなかった。

Numeric#nonzero? -> self | nil
Encoding.compatible?(obj1, obj2) -> Encoding | nil
Kernel.#autoload?(const_name) -> String | nil

だんだん見ていくとだいたいboolなんだけど、いくつかboolじゃないものもあった。
上記は全部ではなく、ぱっと見て出てきたもの。

ほぼだいたいboolだがそうでもないのもいるから、truthy/falsyで判別すればびっくりしなそう。

APIデザインケーススタディ読んだ

ようやく積んでた本を読んだ。読んだというか、途中まで読んでてきつくなったのでパラパラ眺めてどうにか一周終わらせた、ってかんじ。話の内容は多分面白くて書いてある中身もきっと面白いんだけど、対象に今の自分があまり興味がなかったから仕方ない。

APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方 | Gihyo Digital Publishing … 技術評論社の電子書籍

第1章─I/O, 第2章─ソケット, 第3章─プロセス, 第4章─時刻, 第5章─数,文字列 とあって、要は自分が一番好きなプレゼンの使いやすいライブラリ API デザイン. 日本Rubyカンファレンス2006 コレと近い内容なんだけど、I/O, ソケット、プロセス、時刻まであんまり興味がないので、その、あれ。

URI.encode_www_form出てきてやっと興味あるとこキタ! と思ったらそれが最後でしょんぼり。

なので、あとは思い出したことを並べる。

使いやすいライブラリAPIデザインから、自分は強烈な印象を受けていて、過去の自分の名作 https://github.com/sanemat/slip/blob/b49f8a609741c29054cacf0fe84a2a6d377bba53/slip.rb#L14 に色濃く出ている。

脱線すると、httpライブラリの好みの変遷としては、何使ってたか覚えていない -> 感銘を受けて open-uriに -> httparty で楽して楽しく -> REST厨なのでrest-client -> faraday でインターフェースを統一するんだ -> net-http やっぱり標準ライブラリだね、あとeasy, simpleだとsimpleよりのがいい イマココ

コレも好き。matz を説得する方法. RubyKaigi2008

さがしてまわっちゃったけどここにあった。Publications of Tanaka Akira

正の整数の文字列か判定するNaturalNumberString gem書いた

正の整数の文字列か判定するNaturalNumberString gem書いた。

APIはこんな感じです。

NaturalNumberString.positive_integer_string?(value) -&gt; boolean
NaturalNumberString.zero_or_positive_integer_string?(value) -&gt; boolean

詳細: yard docs

環境変数を取得してあれこれするライブラリを書いている。

if ENV['FOO']
  # do something
end

これでいいかと思ったら、あるCIサービスでは、falsyなときにENV['FOO']がないのだが、別のCIサービスでは、falsyなときはENV['FOO'] == ''なことがある。

if ENV['FOO'] &amp;&amp; !ENV['FOO'].empty?
  # do something
end

これでいいかと思ったら、

TRAVIS_PULL_REQUEST the pull request number if the current job is a pull request, or “false” if it’s not a pull request.
http://docs.travis-ci.com/user/environment-variables/#Convenience-Variables

文字列’false’… いや文字列しか使えないから仕方ないのは知ってるけど。なのでカッとなって書いた。

NaturalNumberString.positive_integer_string?('1') #=&gt; true
NaturalNumberString.positive_integer_string?('100000000000') #=&gt; true

NaturalNumberString.positive_integer_string?(nil) #=&gt; false
NaturalNumberString.positive_integer_string?('') #=&gt; false
NaturalNumberString.positive_integer_string?(1) #=&gt; false
NaturalNumberString.positive_integer_string?('1.1') #=&gt; false
NaturalNumberString.positive_integer_string?('-1') #=&gt; false
NaturalNumberString.positive_integer_string?('0') #=&gt; false

NaturalNumberString.zero_or_positive_integer_string?('0') #=&gt; true

文字列’0’の扱いだけ変えるmethodもある。

なおnodejsでも似たような発想のことやってるの前に作った。sanemat/node-boolify-string

*.gemspec ファイルをいい感じにparseするgem書いた

うっとなったので *.gemspec ファイルをいい感じにRuby Hash objectにするgem parse_gemspec と、
*.gemspec ファイルをいい感じにJSON にする CLIツールのgem parse_gemspec-cli かいた。

それぞれこう使う。

Ruby

require 'parse_gemspec'
require 'pp'

pp ParseGemspec::Specification.load('parse_gemspec.gemspec').to_hash_object
{:name=&gt;"parse_gemspec",
 :version=&gt;"0.5.0",
 :authors=&gt;["sanemat"],
 :description=&gt;"Parse *.gemspec file. Convert to Ruby Hash object.",
 :email=&gt;["o.gata.ken@gmail.com"],
 :homepage=&gt;"https://github.com/packsaddle/ruby-parse_gemspec",
 :licenses=&gt;["MIT"],
 :metadata=&gt;{},
 :summary=&gt;"Parse *.gemspec file. Convert to Ruby Hash object."}

CLI

$ parse-gemspec-cli parse_gemspec.gemspec | jq .
{
  "name": "parse_gemspec",
  "version": "0.5.0",
  "authors": [
    "sanemat"
  ],
  "description": "Parse *.gemspec file. Convert to Ruby Hash object.",
  "email": [
    "o.gata.ken@gmail.com"
  ],
  "homepage": "https://github.com/packsaddle/ruby-parse_gemspec",
  "licenses": [
    "MIT"
  ],
  "metadata": {},
  "summary": "Parse *.gemspec file. Convert to Ruby Hash object."
}

filesとdependenciesが未対応。

filesは最近のだとgit ls-files使ってる場合が多いので、どうしようかな。いい方法が思いつかない。
specification: files, many gems use git ls-files -z

dependenciesはPlain Old Ruby Objectでどう表現しようかな、json表現どうしようかな、というところで止めてる。
specification: dependencies’ PORO format and json format

具体的な利用方法はこんな感じ。
conventional-changelog(npm)をRuby pruductから使う | 實松アウトプット

conventional-changelog(npm)をRuby pruductから使う

conventional-changelogにactiveなメンテナーが増えて、v0.1.0に上がって以降(2015-09-25 時点でv0.4.3)だいぶ使いやすくなっている。
Ruby productから使う時の使い方をまとめた。

下記エントリーの続き。

bin/changelogを作って使う、と以前書いていたけど、設定ファイルだけ書いて、あとはコマンドラインのconventional-changelogから使えるようになった。実際に使っている例 parse_gemspec-cli

具体的にはこんなpackage.json書いて、npm run changelogする。

{
  "devDependencies": {
    "conventional-changelog": "0.4.3",
    "urijs": "^1.16.1"
  },
  "scripts": {
    "changelog": "conventional-changelog -i changelog.md --overwrite --preset angular --context .conventional-changelog.context.js"
  }
}

contextの指定で、無指定だとpackage.json読んでバージョンやリポジトリのurl取得するところを、Rubyプロダクト用に書き換えてやれば良い。
設定すべき値はconventional-changelog-writer のオプション。これを.conventional-changelog.context.jsにかく。ファイル名はどうでもいい。

'use strict';
var execSync = require('child_process').execSync;
var URI = require('urijs');

var gemspec = JSON.parse(execSync('bundle exec parse-gemspec-cli parse_gemspec-cli.gemspec'));
var homepageUrl = gemspec.homepage;
var url = new URI(homepageUrl);
var host = url.protocol() + '://' + url.authority();
var owner = url.pathname().split('/')[1];
var repository = url.pathname().split('/')[2];

module.exports = {
  version: gemspec.version,
  host: host,
  owner: owner,
  repository: repository
};

gemspec を読んで json を吐き出す parse_gemspec-cli gemを作った。パッケージはparse_gemspec-cli、コマンドは parse-gemspec-cli。今のところ name, version, homepage しか取り出せないけど、changelogの用途にはこれで良い。

これでgemを書くときにもchangelog環境が快適になった。

$ conventional-changelog --help
Options
(snip)
    -c, --context             A filepath of a javascript that is used to define template variables
    --git-raw-commits-opts    A filepath of a javascript that is used to define git-raw-commits options
    --parser-opts             A filepath of a javascript that is used to define conventional-commits-parser options
    --writer-opts             A filepath of a javascript that is used to define conventional-changelog-writer options

conventional-changelogのコマンドが、細かくmoduleに分割したあとにも、使うmoduleの設定書いたうえでCLIツールからそのまま使えるの、よく出来ている。

changelogとrubygem

changelog, nodeのmoduleを書くときはajoslin/conventional-changelogを使う。ただ、version bumpを誰がするのか、開発中は v1.2.3.beta とbetaとか付けるか、pushするのは誰か、releaseするのは誰か、がそんなに自明でないので、ややこしい。

Perlの場合

perlの人に言うと、「それMinillaで」と言われる。チュートリアル眺める限り、良さそうに見える、があんまり深入りしない。

Minilla::Tutorial – Minilla チュートリアルドキュメント – perldoc.jp

Rubyの場合

releaseはbundler付属の bundle exec rake release使うのが標準(だと思う)。

pull_request-create 0.1.0 built to pkg/pull_request-create-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed pull_request-create 0.1.0 to rubygems.org.

packageをbuildして、git tagして、commitとtagを(githubに)pushして、packageをrubygems.orgにpushする。

なので、changelogをどうにかして作る、手動でversionをbumpして、commit, rake releaseとなる。

いくつか見たchange logツールが、既に存在するtagに対して(あるいはgithubにpush済みのtagに対して)、処理するので、しっくりこない。

Git tagとGitHub ReleasesとCHANGELOG.mdの自動化について | Web Scratch
https://github.com/skywinder/Github-Changelog-Generator/wiki/Alternatives
試してみる。

Edited: 2015-03-21 16:44
conventional-changelogがrubyから使いづらいのでどうにかしたい | 實松アウトプット
続き書いた

アプリケーションの機能自体には直接関係ないlintのgemの管理

tl;dr

管理したい。

変更したファイルにrubocopやjscsを実行して pull requestに自動でコメントする – Saddler – checkstyle to anywhere
定期的にライブラリの依存関係をアップデートしてPull Requestする – Saddler – checkstyle to anywhere

このへんのエントリでは話のスコープから外した。
スクリプトの序盤で

gem install --no-document git_httpsable-push pull_request-create

とさらっとすませている。

好みではない方法

このやり方はそんなに好みではない。
lintに使うgemだろうとなんだろうと、入る環境を固定したい。
入るバージョンを固定したい。たとえlocalでも。
ちょっと結果がおかしいのをローカルで確認したい、時にとても困る。CircleCIみたいにsshで入れれば多少はマシ。多少。
cleanなciサーバー上ではグローバルに入れて使い捨てていいけど、ローカルでグローバルに入れるのはちょっと…

とぼんやり考えている。
普通に使いたい人(使って欲しい人)にはイミフすぎるので、話のスコープから外している。

好みの方法

rubyプロジェクトの場合は普通にアプリのGemfileのdevelopmentに追加するのがいい。

もしくは

lint.gemfile
lint.gemfile.lock

にしておいて、

bundle --gemfile lint.gemfile --path vendor/lint-bundle

にする。別gemfileを指定して、別pathにinstall。

bundle installはgemfile指定できるけど、
bundle updateやbundle execはgemfile指定できないので、
こうしている間は、元のアプリのbundle exec出来なくて、元のアプリのするときは再度

bundle --path vendor/bundle

なりして、.bundle/config のpathの向き先変えてからbundle execする必要がある。
スクリプト一枚ごとに小分けにしていくのは、これはこれでbundle updateなどメンテが辛くなるので、
ある程度まとめたい、ただある程度まとめると、結局、というループ。

golang自分はまだ書けないんだけど、golangならこの問題に関しては解決するんだよなあ。

compare_linkerにcliつけたcompare_linker_wrapper作った

cli、ただしくはcompare_linkerにフィードバックすべきなんだろうけど、いろいろ煮詰めてないのと、(おそらく)思想的にやりたいことがちょっとずれるので、wrapperのgemにした。

compare_linker とは
(GitHubのhookを受けて) Gemfile.lockにdiffがあったら、diffとGitHubのtagを比較して、GitHubのcompare viewのlinkを作り、commentにpostする。
compare_linkercompare linker example

wrapperでは、内部をガチャガチャっと変更してる。

  • 外からオプションでformatter指定できるなどのcli機能、出力は標準出力
  • 変更前/変更後のdiffをGitHub APIから取ってくるところを localのgit repositoryから取るようにした
    ここが思想的に違うところで、compare_linkerはherokuないしjenkinsでhookを受けて起動する意図で作っているのに対し、wrapperはciサーバーのpost testで実行するイメージ。いかにcloneせずにapi操作だけで実現するか vs CIサーバーならコードはもうあるじゃない
  • 名前はGemfile.lock 決め打ちじゃない(オマケ), –fileオプションか引数にpathを渡す
compare-linker-wrapper 'example/Gemfile.lock' \
  --base origin/master \
  --formatter CompareLinker::Formatter::Markdown

* [octokit](https://github.com/octokit/octokit.rb): 3.8.0 => 3.8.1

compare-linker-wrapper 'example/Gemfile.lock' \
  --base origin/master \
  --formatter CompareLinker::Formatter::Text

octokit: https://github.com/octokit/octokit.rb 3.8.0 => 3.8.1

こんな出力になる。

実際の使い方は こんなふうにしようかなあと。

git diff --name-only origin/master \
 | grep ".*[gG]emfile.lock$" || RETURN_CODE=$?

case "$RETURN_CODE" in
 "" ) echo "found" ;;
 "1" )
   echo "not found"
   exit 0 ;;
 * )
   echo "Error"
   exit $RETURN_CODE ;;
esac

git diff --name-only origin/master \
 | grep ".*[gG]emfile.lock$" \
 | xargs compare-linker-wrapper --base origin/master \
    --formatter CompareLinker::Formatter::Markdown \
 | text-to-checkstyle \
 | saddler report \
    --require saddler/reporter/github \
    --reporter Saddler::Reporter::Github::PullRequestComment

text-to-checkstyle はこれから作る。
Edited: 2015-03-17 3:37 text_to_checkstyle 作ってリリースした。