※ この記事はちょー長いです。垂れ流しアウトプットです。そして割とニッチなネタが多いです。
※ けどやって良かった。頭の整理と手を動かす良い機会になった。
はじめに
どうも
python 歴は 5-6 年ですが、ruby は 3 日のド素人です。
ruby ってなんかput
とか@
とか| do |
とか出てくるやつでしょ?
みたいな状況からごにょごにょと 3 時間ほど書いて、たのしい Ruby を 2 時間くらいで流し読みして、そっから 12 時間くらいでまとめました。
前から ruby が気になっていて、2.6 のリリースノートが面白そうだったので休みに一気にやってみました。
ちなみに python は新人の半ばくらいの時に vim のプラグインを作りたくて python ruby perl からひとつ選ぶってなったときに、「プログラミングなんてなーんもわからん。とりあえずなんか名前がカッコイイ。」って理由で始めました。
向き不向きがあるので、選択肢を増やしたい
ruby と python のどちらが優れている!とかやる気は毛頭なくて、「〜〜するんならこっちの方が楽そう」ってのが理解できたら良いな、と思う。
例えば僕は「何か作ろ」ってなったとき、大体こんな感じで言語を決める。
(会社の制約や集団の経験言語も考慮しているので、ひとつの例として...)
まず | ざっと | んで |
---|---|---|
大体どう作るか当たりが付いてる | 自分専用 and IO 少なめ | haskell |
他の人のローカルでも叩くことがある | python | |
思いついたクラス設計の検証をしたい | scala | |
gradle 関係 and 文字列やリスト操作多め | groovy | |
gradle 関係 and クラスや高階関数多め | java (+ javaslang + lombok) | |
会社の適当なインフラで web る | php | |
テキスト整形 and 他人がそれを再現しなくて良い | vim | |
クソネタ作って Lightning Talk する | js (+ github-pages) | |
悩みながらになりそう... | チャレンジする気分 | haskell |
ちょっと大きい | scala | |
さっさと動かしたい | python | |
else | python |
python に落ちることが多いし、得意で好きなんだけど、どうしても気にくわないことがある!
これ!!
>>> lines = ['main.py', 'main.rb', 'readme']
>>> map(lambda line: 'python' if line == 'py' else 'ruby', map(lambda line: line.split('.')[1], filter(lambda line: '.' in line, lines)))
# => ['python', 'ruby']
辛い!!ネストすんの辛いわ!!別に書けるけど!!読むとき目線移動大変なんだよ!!最初に読むのここだよ!?
# v ここ!!
>>> map(lambda line: 'python' if line == 'py' else 'ruby', map(lambda line: line.split('.')[1], filter(lambda line: '.' in line, lines)))
# ^ んで次ここ!!
そんなわけで変数に@
とか$
とかあってめんどそう、&:
みたいな呪文怖い、do |x|
?| do | x
?って思って避けてた ruby を、チェインができるのでやってみることにした。
ちなみに、自分が ruby を使う状況で使わないだろうと思ったこととかは飛ばした。
- 継承とか
- mix-in とか extend とか
- 例外
- 特異メソッドとか
- ブロックつきメソッドの自作(欲しくなったらやる)
- ファイルモジュール(これはいつも linux コマンドの発行でやっちゃう)
- 日付まわり
あ、ruby は 2.6.0 で python は 2.7.14 です。(なんとなくずっと 2.7 使ってる...)
あとはひたすら垂れ流します。
文字列
出力
まずはここから。
ruby
名前 | 特徴 | 1 | "1" | "foo\tbar" | [1, 2, 3] | 用途 |
---|---|---|---|---|---|---|
p | .inspect + 改行 | 1 | "1" | "foo\tbar" | [1, 2, 3] | デバッグ |
puts | .to_s + 改行 | 1 | 1 | foo bar | 1 2 3 |
人間に見せる |
.to_s | 1 | 1 | foo bar | [1, 2, 3] | 人間に見せる |
pp
はとりあえず要らないかな、って思って略。
to_s
書いても自作クラスがp
で使ってくれない、って思ったけどinspect
なんてのがあるのか。
とりあえず開発中はp
で良さげ。
haskell のprint
とputStrLn
とputStr
と似てる。
python
python は print しかないので特に迷うことはない。
改行したくなければprint x,
ってやるって覚えてるとちょい便利、くらい。
組み立て
よく使うので個人的にはかなり大事なポイント。
ruby
(ここではp
じゃあなくてputs
を使う)
変数や特殊文字が入らないなら、'
も"
も同じで、片方を中でそのまま使える。
puts "I'm John." # => I'm John.
puts '{"name": "John"}' # => {"name": "John"}
特殊文字や式展開は"
じゃあないと無効。
name = 'John'
puts 'win path is doc\readme.md' # => win path is doc\readme.md
puts "win path is doc\readme.md" # => eadme.md is doc
puts 'my name is #{name}.' # => my name is #{name}.
puts "my name is #{name}." # => my name is John.
ってことは... json 作るのちょっとめんどい??
puts "{\"message\": \"I'm #{name}.\"}" # => {"message": "I'm John."}
here document があるので、これなら良い??
puts <<EOS
{"message": "I'm #{name}."} # => {"message": "I'm John."}
EOS
groovy と似てるかな。
式展開は素直に書けて良い感じ。
python
python は'
と"
に違いはない。どっちかを外側にしてもう片方を中で使えるのも ruby と同じ。
print "I'm John." # => I'm John.
print '{"name": "John"}' # => {"name": "John"}
変数展開がどちらででもできるのが好きだけど、展開場所の指定は ruby の方がすっきりしてると思う。
(.format
やlocals()
等の細かいことは略。)
name = 'John'
print 'my name is %s.' % name # => my name is John.
print "my name is %s." % name # => my name is John.
ruby の'
に相当するものは、r''
(もしくはr""
)になる。raw string の r だろな。
print r'win path is doc\readme.md' # => win path is doc\readme.md
print 'win path is doc\readme.md' # => eadme.md is doc
here document は"""
で囲う。1行でも可能。この場合は中で"
と'
が使える。
print """{"message": "I'm %s."}""" % name # => {"message": "I'm John."}
json や linux コマンドを組み立てるときに便利。
"""
は地味だけど python の好きなところのひとつ。
日本語
こりゃあ ruby の方が試すまでもなく楽だろね。
ruby
puts "仕様書.doc\nmain.rb\ntest.rb"
こんなスクリプトに対してコマンドラインから叩くと、当然予想通り動く。
$ ruby japanese.rb | grep rb
main.rb
test.rb
2.0 以降は標準で utf-8 らしい。
python
# -*- coding: utf-8 -*-
print u'仕様書.doc\nmain.py\ntest.py'
$ python japanese.py
仕様書.doc
main.py
test.py
問題ないと思いきや、怒られる。
$ python japanese.py | grep py
Traceback (most recent call last):
File "japanese.py", line 6, in <module>
print u'仕様書.doc\nmain.py\ntest.py'
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
コマンドラインに出力するときと、|
に繋いだりして出力するときで挙動が違う。
あと vi の中から叩いたりしても同様。
# -*- coding: utf-8 -*-
import sys, codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
print u'仕様書.doc\nmain.py\ntest.py'
こうする。妙に大変。知ってた。
インタプリタ
型を調べる
ruby
irb(main):001:0> 'foo'.class
=> String
irb(main):004:0> 'foo'.chomp.class
=> String
irb(main):010:0> 'foo'.start_with?.class
=> FalseClass
なんだけど、どうも思ってたのとちょっと違う...?? これ評価結果に.class
してるよね...
メソッドか属性値なのか知りたくなったりしないのかな...??
python
>>> type('foo')
<type 'str'>
>>> type('foo'.strip)
<type 'builtin_function_or_method'>
>>> type(os.path)
<type 'module'>
>>> type(os.path.join)
<type 'function'>
とりあえず混乱したらtype
して一息付く感じ。これだけで大分手がかりになる。
属性を調べる
ruby
> class Foo
> def name()
> 'foo'
> end
> end
> foo = Foo.new()
> foo.methods
=> [:name, :instance_variable_defined?, .. 略.. :__send__]
> ''.methods
=> [:to_r, :encode, :encode!, :include?, :%, .. 略 ..]
これは python と似た感覚で使えそう。ちょっと長いけど。
python
>>> class Foo:
... def name(self):
... return 'foo'
>>> foo = Foo()
>>> dir(foo)
['__doc__', '__module__', 'name']
>>> dir(str)
['__add__', '__class__', ..略.. 'upper', 'zfill']
type
と合わせて使う。
help
ruby
> help String#match
(from ruby core)
------------------------------------------------------------------------
str.match(pattern) -> matchdata or nil
str.match(pattern, pos) -> matchdata or nil
------------------------------------------------------------------------
Converts pattern to a Regexp (if it isn't already one),
then invokes its match method on str. If the second parameter is
present, it specifies the position in the string to begin the search.
'hello'.match('(.)\1') #=> #<MatchData "ll" 1:"l">
'hello'.match('(.)\1')[0] #=> "ll"
'hello'.match(/(.)\1/)[0] #=> "ll"
'hello'.match(/(.)\1/, 3) #=> nil
'hello'.match('xx') #=> nil
help
でとりあえず起動してから色々探すってこともできる。
python
実は知らなくて、ruby の本読んでいて「へー、python にもあんのかな?」って思って叩いたら良いのがあったw
>>> help(map)
Help on built-in function map in module __builtin__:
map(...)
map(function, sequence[, sequence, ...]) -> list
Return a list of the results of applying the function to the items of
the argument sequence(s). If more than one sequence is given, the
function is called with an argument list consisting of the corresponding
item of each sequence, substituting None for missing values when not all
sequences have the same length. If the function is None, return a list of
the items of the sequence (or a list of tuples if more than one sequence).
良いことを知ったw
file
読み込み
ruby
あんま自信ない...
lines = File.read('foo.txt').split()
p lines # => ["foo", "bar", "bla bla bla", "pon"]
これ、close してないことになる?
.open
してFile
オブジェクトを一度手に入れて、.read
したら.close
するか、ブロックを渡す必要があるんだよね?
ブロックを渡す方は、要はローンパターンだよね。
これで確認できるのか自信ないけど、.close
を書き換えて試してみた。
class File
def close; puts 'close!!!'; end
def write(str); STDOUT.write(str); end
end
f = File.open('foo.txt')
f.close # => close!!!
File.open('foo.txt', 'r') {} # => close!!!
lines = File.read('foo.txt') {} # =>
lines = File.read('foo.txt').split # =>
lines = File.open('foo.txt', 'r') {|f| f.read.split} # => close!!!
lines = File.open('foo.txt', &:read).split # => close!!!
ちゃんと close させるにはopen
経由しないとだめなの?ならなんでFile#read
があるの?
ちょっとしたスクリプトならあんま気にしない感じなの?
python
こうとか。
with open('foo.txt', 'r') as f:
lines = f.read().splitlines()
with open('foo.txt', 'r') as f:
lines = map(str.strip, f)
下のは最近教えてもらった。
慣れるまでちょい変な感じがしたけど、シンプルだと思う。慣れたからかな?
モジュール
正直ここはそんなに興味ないので、ruby のrequire
をちょっとだけ。
読み込み
ruby
puts 'foo'
if 1 == 1
require './japanese'
require './japanese'
end
puts 'bar'
$ ruby import.rb
foo
仕様書.doc
main.rb
test.rb
bar
冒頭じゃあなくても書けるし、2度は読み込まれないっぽい。python と同じ。
冒頭じゃあなくても良いのは地味に好きで、例えばif
に落ちた時だけrequire
するとかができる。
そうすると影響が小さくしやすくて読みやすいし消しやすくて好き。(統合開発環境とか全く使ってないのでそこら辺も手作業。)
ただ、名前が競合したりするのは気にしないのかな?
def n()
'n (one)'
end
def one()
'one'
end
def n()
'n (two)'
end
def two()
'two'
end
require './imported_one'
require './imported_two'
puts n # => n (two)
python
名前が被ったり、手元のローカル変数と被るのが気になるときはメソッド単位でimport
するか、モジュール名を略さない。
from imported_one import *
from imported_two import *
print n() # => n (two)
from imported_one import n, one
from imported_two import two
print n() # => n (one)
import imported_one
import imported_two
print imported_one.n() # => n (one)
python の方が融通が利くけど、このimport
の.
とかfrom
が難しすぎる面もあるので、どっちもどっちなのかな? -> Pythonのimportについてまとめる
演算子
overload
二項演算子はどっちも大体同じ。
ruby
class Lines
attr_reader :xs
def initialize(xs)
@xs = xs
end
def +(o)
Lines.new(@xs + o.xs)
end
def -@()
Lines.new(@xs.reverse)
end
def inspect()
@xs.join(', ')
end
end
lines1 = Lines.new(['foo', 'bar'])
p lines1 + Lines.new(['pon']) # foo, bar, pon
p -lines1 # bar, foo
これは1 + 2
が1.+(2)
に見えていれば理解は簡単。scala と同じ。
String
に対しても可能で、単項演算子も可能ってのは面白いなって思った。
python
class Lines:
def __init__(self, xs):
self.xs = xs
def __add__(self, o):
return Lines(self.xs + o.xs)
def __str__(self):
return ', '.join(self.xs)
print Lines(['foo', 'bar']) + Lines(['pon']) # => foo, bar, pon
似た感じ。
条件分岐
これは正直大差ないだろと思ってたけど、かなり違った。
文か式か
ruby
if 1 == 1
p 'true' # => true
else
p 'false'
end
これは python を同じだけど、こいつ値返すらしいぞ!
x = if 1 == 1
'true'
else
'false'
end
p x # => true
こいつはすげぇ!
これを
if 1 == 1
foo = 1
bar = foo + 2
foo = Foo.new(foo + bar)
else
pon = 1
foo = Foo.new(pon * 2)
end
p foo.twice()
こうできる!
p Foo.new(
if 1 == 1
foo = 1
bar = foo + 2
foo + bar
else
pon = 1
pon * 2
end
).twice() # => 8
変数大嫌いだし、if
が式なのは色々と便利で良い!
メソッドの最後に書いてもそのまま値返却にできる。式は良い。
まさかと思って適当に書いてみて良かった。scala みたいだ。
python
if 1 == 1:
print 'true'
else:
print 'false'
python は文なんだ、ちょっと、いや結構悲しい。
三項演算子
ruby
他の言語と同じ。
x = nil
p x.nil? ? 'x is nil' : x # => x is nil
python
特殊。他で見たことない。
英語的ってやつかしら。
x = None
print x if x is not None else 'x is none' # => x is none
print x, if x is not None. else, 'x is none'.
みたいに一息つきながら読む感じ。
ちなみに ruby の if 修飾子と違ってelse
は無くせない。
switch
ruby
if
がそうだったのできっとと思ったけど、こいつも式みたい。良い!
p case x
when 'rb'
'ruby'
when 'py'
'python'
when 'hs'
'haskell'
else
'unknown'
end
# => ruby
式になってると.collect
とかにすっきり収まるはず。
xs = ['rb', 'py', 'hs']
p xs.collect {|x| case x
when 'rb'; 'ruby'
when 'py'; 'python'
when 'hs'; 'haskell'
else ; 'unknown'
end
} # => ["ruby", "python", "haskell"]
良いねー!(スタイルが ruby っぽくないのはわかってやってる。自分専用コードの時だけに留める。)
scala みたい。
python
文は大嫌いなので、式になってる scala や haskell でしか使ったことない。
まぁ python にはそもそも switch 文もないけど。
5年くらい前に使い捨ての辞書を引くって方法を自分で閃いて天才かと思ったけど、調べたら常套手段だった思い出w
x = 'py'
print {
'rb' : 'ruby',
'py' : 'python',
'hs' : 'haskell'
}[x].capitalize() # => Python
paiza みたいなのやるときはちょいちょい使う。
完全に余談だけど、python の boolean はただの0
と1
のエイリアスなのを知ってるとこんなことができる。
print ['false!', 'true!'][x.startswith('p')] # => true!
print 'true!' if x.startswith('p') else 'false!' # => true!
三項演算子より極僅かだけど短いので、コードゴルフで使う。
優先度
ruby
and
より&&
が強いらしい。へー。
p false && false || true # => true
p false and false || true # => false
上は(f && f) || t
で、下はf && (f || t)
に相当するから。
おもしろいけど、自分で使うかは微妙。とはいえ場面が多くて、かつ慣れたら多用すると思う。
python
python はand
とor
のみで&&
と||
はない。これも英語的ってやつかしら。
演算子の連結
ruby
逆に ruby はこれができない。
x = 5
p 1 < x < 10
まぁちょっと考えれば1.<(x).<(10)
だけど、TrueClass
が前の条件覚えた.<
を持ってるわけないしね。
python
x = 5
print 1 < x < 10 # => True
案外ないと「んあ〜」ってなるので、できるのはうれしい。
メソッド
ここが本題。
無名関数
ruby
f = lambda{|x| x + x}
p f.call(3) # => 6
def
で作ったものと違い、f.call(x)
みたいに呼ぶ。もしくはf[x]
。
こういう時にdef
とlambda
を比べたいから型が知りたくなるんだけど、これだとエラーなんだよなー。
もうちょっと慣れが必要っぽい。
def f(x); x + x; end
p f.class # => error
p lambda{|x| x + 2}.class # => Proc
それは置いといて、lambda
で作った方はProc
と言うらしい。2.6 のリリースノートで見たぞ。
p [1, 2, 3, 4].collect do |x|
x + x
end
そんでここのdo ~ end
をブロックというらしい。置き換えられるふいんき。
lamb = lambda{|x| x + x}
p [1, 2, 3, 4].collect(lamb)
...怒られた。どうやらブロックとlambda
は別物らしい。
ruby lambda to block とかでぐぐったら出るわ出るわ。鬼門らしい?
要約すると、「ブロックはオブジェクトではない。ブロックをオブジェクトにするなら&
でProc
オブジェクトにする。」ってことっぽい。
proc
とlambda
の違いはわかった気がするけど、break
もreturn
も使う気は全く無いので今は無視。
なので、こうだ。
lamb = lambda{|x| x + x}
p [1, 2, 3, 4].collect(&lamb) # => [2, 4, 6, 8]
lambda
は->
による略記があるみたい。
lamb = ->(x) {x + x}
こっちの方がf(x) {...}
と同じ形だからわかりやすいでしょって書いてあったけど、x -> {...}
の方がわかりやすいと思うw
java とか scala とか haskell とか js とか、大体こうじゃあない?
python
f = lambda x: x + x
print f(3) # => 6
def
製とlambda
製に違いはない。
print map(f, [1, 2, 3, 4]) # => [2, 4, 6, 8]
正直ここだけ見ると python の方がシンプル。
(f = lambda x: ...
みたいにラムダ式を変数束縛するのは PEP 8 では非推奨です。これはあくまで個人用なので好みでやってるだけです。)
メソッド参照
名前は不適切っぽいけど。なんて言うのかな。
ruby
シンボルをProc
オブジェクトにすることで、x.f(arg)
を:f.to_proc.call(x, arg)
にできるみたい。
'foo'.include?('o') # => true
:include?.to_proc.call('foo', 'o') # => true
引数がひとつならこんな感じでそのままはまる。
['ruby', '', 'python'].collect(&:empty?) # => [false, true, false]
たまに見る&:f?
っていう呪文みたいなのはこいつだったんだな。
&
は.to_proc
で、:
がシンボル。?
はただのメソッド名の慣習。呪文をひとつ習得。
ところで、なんで.collect(
の中とかじゃあないと&
で怒られるんだ?
f = :empty?.to_proc # => #<Proc:0x00007f877b0cd960(&:empty?)>
f = &:empty? # => unexpected &
その辺がまだわからん...
python
似た感じ。.to_proc
とか.call
がないのでスッキリしてる。
'foo'.startswith('f') # => True
str.startswith('foo', 'f') # => True
map(str.strip, ['foo\n', 'bar\n']) # => ['foo', 'bar']
数年触ってて、これ先日まで知らなかったんだよね...
デフォルト引数と名前付き引数
リスト渡しとか辞書渡しは使わないので無視。必要な場面に出くわしたら調べる。
ruby
def f(a: 0, b: 0, c: 0)
p [a, b, c]
end
f(b: 3, a: 1) # => [1, 3, 0]
デフォルト引数がある引数は、名前指定で渡せる。
python
def f(a, b, c = 0):
print [a, b, c]
f(b = 3, a = 1) # => [1, 3, 0]
デフォルト引数がなくても、名前指定で渡せる。
これは引数が多い場合に呼ぶ側の可読性が上がったりして地味に好き。
複数引数の無名関数
ruby
p [['ruby', '2.6'], ['python', '2.7']].collect{|x| "#{x[0]}-#{x[1]}"} # => ["ruby-2.6", "python-2.7"]
これは多分a, b = [1, 2]
みたいな代入ができるんだから、きっとこうできる。
p [['ruby', '2.6'], ['python', '2.7']].collect{|name, version| "#{lang}-#{version}"} # => ["ruby-2.6", "python-2.7"]
f = ->((name, version)) {"#{name}-#{version}"}
p [['ruby', '2.6'], ['python', '2.7']].collect(&f) # => ["ruby-2.6", "python-2.7"]
できた。
lambda
の方は、->(name, version)
だと「引数の数あってねーよ」って怒られたので適当に()
をもう一つ書いたら動いた。
あくまで1つの引数を受けて、受けた方がバラすのでこうなるってことかな。haskell みたい。
python
f = lambda (name, version): '%(name)s-%(version)s' % locals()
print map(f, [['ruby', '2.6'], ['python', '2.7']]) # => ['ruby-2.6', 'python-2.7']
ruby と同じ感じ。
print ['%(name)s-%(version)s' % locals() for name, version in ['ruby', '2.6'], ['python', '2.7']] # => ['ruby-2.6', 'python-2.7']
内包表記のfor
の部分は,
でバラして受けられるのでよく使う。
カリー化
ruby
f = ->(name, version) {"#{name}-#{version}"}
python = f.curry.call('python')
p ['2.7', '3.3'].collect(&python) # => ["python-2.7", "python-3.3"]
できるんだ!良さげ!
ただ、これが怒られるのがよくわからない。
:+.to_proc.curry('result: ').call('Ruby-2.7') # => 'result: '.+('Ruby-2.7') だから、result: Ruby-2.7 になると思った
:+.to_proc.call('result: ', 'Ruby-2.7') # => これは result: Ruby-2.7 になるのになぁ
haskell の("result:" ++)
みたいに演算子の部分適用できたらすげー便利だと思うのに...
python
できないんだよなー、残念。
関数合成
これが「良いな!」ってなって ruby を始めた。
ruby
join = ->((name, version)) { "#{name}-#{version}" }
cap = ->(s) { s.capitalize }
surround = ->(c, s) { "#{c} #{s} #{c}" }
pipe = ->(s) { surround.call('|', s) }
f = join >> cap >> pipe
p f.call(['ruby', '2.6']) # => | Ruby-2.6 |
p [['ruby', '2.6'], ['python', '2.7']].collect(&f) # => ["| Ruby-2.6 |", "| Python-2.7 |"]
>>
と<<
、良いなぁ!
メソッド参照とカリー化も出来るってことは...
p [['ruby', '2.6'], ['python', '2.7']].collect(&join >> :capitalize.to_proc >> surround.curry['|']) # => ["| Ruby-2.6 |", "| Python-2.7 |"]
できた!慣れてきたー!
&(f >> g)
にしないで良いって事は、演算子の優先順位は&
の方が弱いってこと?
python
自分で合成書けば...
join = lambda (name, version): '%(name)s-%(version)s' % locals()
cap = lambda s : s.capitalize()
surround = lambda c, s : '%(c)s %(s)s %(c)s' % locals()
pipe = lambda s : surround('|', s)
comp3 = lambda f1, f2, f3: lambda x: f3(f2(f1(x)))
f = comp3(join, cap, pipe)
print f(['ruby', '2.6']) # => | Ruby-2.6 |
print map(f, [['ruby', '2.6'], ['python', '2.7']]) # => ["| Ruby-2.6 |", "| Python-2.7 |"]
あんまり恩恵感じないかな...
リスト
連結
大半をメソッドのところでやっちゃったけど。
こいつが前置きにあった一番のポイント。
ruby
xs = ['ruby', 'python', 'php', 'js']
f = lambda{|x| x % 2 == 0 ? 'even' : 'odd'}
p xs
.select{|x| x.start_with?('p')}
.collect(&:length)
.collect(&f)
.join(', ') # => even, odd
んー、良い!
改行できるのがとても良い!
.join
もArray
のメソッドなので、最後に書けるのがとても良い!
目線が左上から右下に素直に行くのは、とっても大事!
ちなみに関数合成ができたので、複数引数の片方しか使わない場合は、これを
is_p = proc{|x| x.start_with?('p')}
xs = ['foo.ruby', 'bar.python', 'pon.php', 'kaz.js']
p xs
.collect{|x| x.split('.')}
.select{|name, ext| is_p.call(ext)}
.collect{|name, ext| name}
.join(', ') # => [bar, pon]
こんな風に書いたりできる。haskell 風。
fst = proc{|x, _| x}
snd = proc{|_, x| x}
p xs
.collect{|x| x.split('.')}
.select(&snd >> is_p)
.collect(&fst)
.join(', ') # => [bar, pon]
こういうのはメソッドが集まるほど強いので、個人的にはとても好き。
python
xs = ['ruby', 'python', 'php', 'js']
f = lambda x: 'even' if x % 2 == 0 else 'odd'
print ', '.join(map(f, map(len, filter(lambda x: x.startswith('p'), xs))))
print ', '.join(
map(
f,
map(
len,
filter(
lambda x: x.startswith('p'),
xs
)
)
)
)
辛い...
これくらいなら内包表記で書くかなー...
print ', '.join(
[f(len(x)) for x in xs if x.startswith('p')]
) # => even, odd
慣れてるのでそんなに読み書きは苦ではないけど...
xs
を探す -> if
探す -> [
の左端から読む -> 上の行のjoin
読む。ってなるので、目線がすんげーことになる。とても残念。
ところで、scala とか java とかもやったけど、sep.join(xs)
の形って珍しいよね。普通はxs.join(sep)
だよなぁ。
なんでこうなってんだろ。
list
が処理を持てないのかな?。だからlen
もxs.len()
じゃあなくてlen(xs)
だしmap
も.map
じゃあなくてmap()
なのかな?dict
は.get()
とか.items()
とかあるのにな。
スライス
地味だけど重宝する。
だいたい python と同じだった。良い感じ。
ruby
xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
p xs.slice(7..-1) # => [7, 8, 9]
p xs.slice(7..) # => [7, 8, 9] -1 の省略は 2.6 から
p xs.slice(0..0) # => [0]
p xs[-1] # => 9
p xs.slice(5..-2) # => [5, 6, 7, 8]
p xs.slice(5...-1) # => [5, 6, 7, 8]
p xs.slice((1..-1).step(2)) # => error
なんだよrange
なら良いんだろーと思ったけど、range
じゃあなかった。そっかー。
> (1..2).class
=> Range
> (1..2).step(2).class
=> Enumerator::ArithmeticSequence
> ((1..2) % 2).class # % は 2.6 から
=> Enumerator::ArithmeticSequence
python
[::]
の意味を知っていればすっきり読み書きできる。([start:end:step]
)
ちょっと含む範囲が違う。step も有効で、応用で逆順がサッとできる。
python の好きなところのひとつ。
xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print xs[7:] # => [7, 8, 9]
print xs[:1] # => [0]
print xs[-1] # => 9
print xs[5:-2] # => [5, 6, 7]
print xs[5:-2:2] # => [5, 7]
print xs[::-1] # => [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
ちなみにxs[:1]
は案外便利で、リストの先頭要素がリストのまま欲しいときに重宝する。
1000 くらいの長さのリストを試しに一周だけ動かすときとかに便利。
print [f3(f2(x)) for x in xs if f1(x)] # これを試しに1回だけ動かすのに
print f3(f2(xs[0])) if f1(xs[0]) else None # がっつり書き直さなくても
print [f3(f2(x)) for x in xs[:1] if f1(x)] # [:1] だけ付けちゃう
ruby と python どちらも[]
に対してやったら[]
が返ってくるので安全。
稀に便利なちょいテク。
内包表記
もうちょくちょく出してるけど。
ruby
ないっぽい。
まぁ.collect
とかが改行できるし、良いかな。
python
下手にfor
で一時変数に足し込むのは変数が散らかるので大嫌い。
map
との使い分けは状況次第で簡潔な方ってかんじ。
map
とfilter
が1回ずつ出る場合はまず内包表記でやる。(ネストするから)
map
かfilter
がどちらか1回の場合はlambda
ってタイプしないといけないかどうかとかで決めてるなぁ。
xs = ['python', 'ruby']
print map(len, xs) # => [6, 4]
print [len(x) for x in xs] # => [6, 4]
print map(lambda s: 'lang is %s.' % s, xs) # => ['lang is python.', 'lang is ruby.']
print ['lang is %s.' % x for x in xs] # => ['lang is python.', 'lang is ruby.']
微妙にどっちかの方がすっきりしてるw
あと新人時代に冒頭しか読めなかったエキ py に性能の話があったけど、ruby が主軸なので割愛。
flat_map
1 : *
構造が入れ子になってたりする時に使う。作るものの特性上、結構使うので気になるところ。
ruby
xs = ['main.rb', 'readme.md', 'test.rb']
read_rb = ->(path) {path.end_with?('rb') ? ["this is #{path}", 'bla bla bla...'] : []}
p xs.map(&read_rb) # => [["this is main.rb", "bla bla bla..."], [], ["this is test.rb", "bla bla bla..."]]
read_rb
は引数で受けたパスのファイルを open, read して、中の行をリストで返すような処理。(だと思って。)
.map
でやると[[]]
になっちゃう。
(例として便宜上readme.md
をスキップしたけど、空ファイルだったりする場合も同様に[]
になる。そもそも.md
を[]
にしたいなら先に.select
するけど、これは例なので。)
p xs.flat_map(&read_rb) # => ["this is main.rb", "bla bla bla...", "this is test.rb", "bla bla bla..."]
.flat_map
あるんだね。良い。
python
flatten
的なのがないので、内包表記でfor for
したり、空リストに対して畳み込んだりする。
xs = ['main.py', 'readme.md', 'test.py']
read_py = lambda path: ['this is %s' % path, 'bla bla bla...'] if path.endswith('py') else []
def flat_map(f, xs):
return sum(map(f, xs), [])
print flat_map(read_py, xs) # => ['this is main.py', 'bla bla bla...', 'this is test.py', 'bla bla bla...', 'this is readme.md', 'bla bla bla...']
他、気になってること
個人的によく書くこととかを触っておきたい。
コマンドライン引数
ruby
特にrequire
は不要。
p ARGV
# => ruby ./command.rb 1 3
#
# ["1", "3"]
グローバル変数だから$
使うのかな?って思ってたけど、これは定数?
python
import sys
print sys.argv
# => python ./command.py 1 3
#
# ['./command.py', '1', '3']
コマンドライン引数パーサ
オレオレ自作コマンドを良く作るので、良く触る。
ruby
OptionParser
っていう手の込んだやつと、手っ取り早くARGV
を改造するgetopts
があるみたい。
#!/usr/local/bin/ruby
require 'optparse'
params = ARGV.getopts('', 'tension:', 'lang:en')
puts "#{params['lang'] == 'en' ? 'foo' : 'ふー'}#{'!' * params['tension'].to_i}"
# => $ foo -h
# Usage: foo [options]
# --tension=VAL
# --lang=en
#
#
# $ foo --tension 4
# foo!!!!
#
#
# $ foo --tension 4 --lang ja
# ふー!!!!
すっきりしてて良い感じ。
ところで、どうして-h
が生成されるの?
.getopts
がARGV
を破壊しているの?それとも.getopts
に渡した情報がどっかに渡ってるのかな?
get しかしてないのにちょっと気持ち悪いw
python
3 系だともっとイケてるのかもしれないけど、ずっとこんな感じでやってる。
これは適当だけど、排他オプションとかもちゃんとできるので良い。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import argparse
parser = argparse.ArgumentParser(description = 'say foo!')
parser.add_argument('tension', type = int, help = 'number of !')
parser.add_argument('-l', '--lang', default = 'en', type = str, action = 'store', help = 'language')
args = parser.parse_args()
print '%s%s' % ('foo' if args.lang == 'en' else u'ふー', '!' * args.tension)
# => $ foo -h
# usage: foo [-h] [-l LANG] tension
#
# say foo!
#
# positional arguments:
# tension number of !
#
# optional arguments:
# -h, --help show this help message and exit
# -l LANG, --lang LANG language
#
#
# $ foo 4
# foo!!!!
#
#
# $ foo 4 --lang ja
# ふー!!!!
アノテーション
ruby
検索してみたけど、そもそも@foo
みたいなのないんだ。
TODO
等の特定のコメントを、アノテーションコメントと言うみたい。
rails はそれをかき集めるコマンドがあるからセットでひっかかるのかな。
python
python ではデコレータと言う。
java とかのとは違う感じで、本来の処理の前後に任意の処理を被せられる。
def file_testing(f):
def w(path):
print 'mkdir %s if not exists\n' % path.split('/')[0] # => 書き込み先のディレクトリがなければ作る
f(path) # => 元々呼ばれた処理を呼ぶ
print '\nremove %s' % path # => 作ったファイルを消しておく
return w
def sut(path): # 本当にやりたいこと
print 'write to %s' % path
@file_testing
def sut_test(path): # sut のテスト
sut(path)
result = sut が作ったファイルを読む
assert len(result) == 2 とかする
sut_test('out/foo.txt')
# => mkdir out if not exists
#
# write to out/foo.txt
#
# remove out/foo.txt
理解できると案外さっと作れるので、メソッド本体を汚さずにゴミ掃除をしたり実行時間を計ったりできる。(詳細は割愛。)
多用はしないけど重宝する。python の好きなところのひとつ。
os コマンド実行
これもよく使う。
ファイルモジュールで再帰的にファイル探すのとか面倒なのでfind | grep
使っちゃったり、ライブラリ経由して mysql 操作するのが面倒だからmysql -e
で適当に叩いちゃったりする。
http ライブラリ使うのも面倒なのでcurl
しちゃうし、git のオレオレラッパー作るときも、文字列でコマンド組み立てて叩いちゃう。(自分専用は、ね。)
ruby
res = `ls /tmp`.split()
p res # => ["a.md", "a.py", "a.txt", "b.txt"]
すっきりしてて良いな!
groovy みたい。
一応、同期処理かも確認しておこう。
puts "foo"
res = `sleep 2s; ls /tmp`.split()
p res
puts "bar"
ちゃんとp res
が終わってから bar が出た。良かった。(groovy でよくハマるw)
python
手っ取り早いのはos.system
だけど、戻り値が受け取れない。
import os
res = os.system('ls /tmp')
print res # => 0
# => python command.py
# a.md
# a.py
# a.txt
# b.txt
いつもは素直にリストになってくれるcommands.getoutput
を使ってる。
import commands
res = commands.getoutput('ls /tmp').split()
print res # => ['a.md', 'a.py', 'a.txt', 'b.txt']
ポリシー・思想
ruby
当然まだ全然わからないけど、ちょっと調べてわかったことだけ...
ストレスなく楽しむってのが最も重視されているらしい。
あとどうもドキュメントが後追いな感じ?なの?
「Matz(まつもと) が Python に満足していれば Ruby は生まれなかったであろう」と公式のリファレンスの用語集で言及されている。
へーww
でも正直なところ、思ってたよりずっと触りやすくて良いなーって思った。
純粋な oop 言語が良かったらしい。確かに python のlen
とかを見ると、後付け感がすごい。
ruby は触っていて統一感がある感じがした。
知ってる言語の中だと(あんまり詳しくないけど)scala に一番似てる気がした。ド素人意見。
あと python とは逆に、同じ事をやる方法が多い印象もある。
「proc
の呼び方がf.call(x)
or f[x]
of f.(x)
」とか「.map
or .collect
」とか「->
or lambda
」とか「proc
of Proc.new
」みたいな、別名が多くてどっちに慣れると良いんだろ?みたいなノイズも感じた。
python
the zen of python ってのがある。
あと印象深いのは、「同じ事をやるなら同じコードになる方が良い」とか「英語的」とかそんなフレーズ。
シンプルで読みやすくて、似るコードが美徳っぽい。
なので「if
があるからswitch
はない」とか「i += 1
があるからi++
がない」とか「&&
じゃあなくてand
」とか「x if x is not None else...
みたいな記述になる」とかなんだろな。
「map
か内包表記か」とか「いやいやimport
ムズいしfrom
とかどうすんだよ」とかも感じないわけではないけど、この考え方は好き。
(昔聞いた話で曖昧だけど、逆に perl は「同じ事をやるいろんなコードがあって良い」的な感じだった気がする。柔軟さやテクさが美徳的な。)
あった、これか
やり方はいろいろある (There's More Than One Way To Do It; TMTOWTDI)
おわりに
思ってたよりずっと満足できた。ruby にもまとめにも。集中も続いたのでガッとやってポイッてまとめて放り投げられて良かった。
あとは経験あるのみ。しばらくは ruby 書いてみようかなー。