Help us understand the problem. What is going on with this article?

ruby 2.6 が出たので触ってみて、python と比較してみた

More than 1 year has passed since last update.

※ この記事はちょー長いです。垂れ流しアウトプットです。そして割とニッチなネタが多いです。
※ けどやって良かった。頭の整理と手を動かす良い機会になった。

はじめに

どうも

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 に落ちることが多いし、得意で好きなんだけど、どうしても気にくわないことがある!

これ!!

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']

辛い!!ネストすんの辛いわ!!別に書けるけど!!読むとき目線移動大変なんだよ!!最初に読むのここだよ!?

python
#                                                                                               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
人間に見せる
print .to_s 1 1 foo bar [1, 2, 3] 人間に見せる

ppはとりあえず要らないかな、って思って略。

to_s書いても自作クラスがpで使ってくれない、って思ったけどinspectなんてのがあるのか。

とりあえず開発中はpで良さげ。
haskell のprintputStrLnputStrと似てる。

python

python は print しかないので特に迷うことはない。

改行したくなければprint x,ってやるって覚えてるとちょい便利、くらい。

組み立て

よく使うので個人的にはかなり大事なポイント。

ruby

(ここではpじゃあなくてputsを使う)

変数や特殊文字が入らないなら、'"も同じで、片方を中でそのまま使える。

ruby
puts "I'm John."           # => I'm John.
puts '{"name": "John"}'    # => {"name": "John"}

特殊文字や式展開は"じゃあないと無効。

ruby
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 作るのちょっとめんどい??

ruby
puts "{\"message\": \"I'm #{name}.\"}"    # => {"message": "I'm John."}

here document があるので、これなら良い??

ruby
puts <<EOS
{"message": "I'm #{name}."}               # => {"message": "I'm John."}
EOS

groovy と似てるかな。
式展開は素直に書けて良い感じ。

python

python は'"に違いはない。どっちかを外側にしてもう片方を中で使えるのも ruby と同じ。

python
print "I'm John."              # => I'm John.
print '{"name": "John"}'       # => {"name": "John"}

変数展開がどちらででもできるのが好きだけど、展開場所の指定は ruby の方がすっきりしてると思う。
.formatlocals()等の細かいことは略。)

python
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 だろな。

python
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行でも可能。この場合は中で"'が使える。

python
print """{"message": "I'm %s."}""" % name # => {"message": "I'm John."}

json や linux コマンドを組み立てるときに便利。
"""は地味だけど python の好きなところのひとつ。

日本語

こりゃあ ruby の方が試すまでもなく楽だろね。

ruby

japanese.rb
puts "仕様書.doc\nmain.rb\ntest.rb"

こんなスクリプトに対してコマンドラインから叩くと、当然予想通り動く。

$ ruby japanese.rb | grep rb
main.rb
test.rb

2.0 以降は標準で utf-8 らしい。

python

japanese.py
# -*- 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 の中から叩いたりしても同様。

japanese.py
# -*- coding: utf-8 -*-

import sys, codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

print u'仕様書.doc\nmain.py\ntest.py'

こうする。妙に大変。知ってた。

インタプリタ

型を調べる

ruby

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

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

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

python
>>> class Foo:
...   def name(self):
...     return 'foo'

>>> foo = Foo()

>>> dir(foo)
['__doc__', '__module__', 'name']

>>> dir(str)
['__add__', '__class__', .... 'upper', 'zfill']

typeと合わせて使う。

help

ruby

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

python
>>> 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

あんま自信ない...

ruby
lines = File.read('foo.txt').split()
p lines                                 # => ["foo", "bar", "bla bla bla", "pon"]

これ、close してないことになる?
.openしてFileオブジェクトを一度手に入れて、.readしたら.closeするか、ブロックを渡す必要があるんだよね?
ブロックを渡す方は、要はローンパターンだよね。

これで確認できるのか自信ないけど、.closeを書き換えて試してみた。

ruby
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

こうとか。

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

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するとかができる。
そうすると影響が小さくしやすくて読みやすいし消しやすくて好き。(統合開発環境とか全く使ってないのでそこら辺も手作業。)

ただ、名前が競合したりするのは気にしないのかな?

imported_one.rb
def n()
    'n (one)'
end

def one()
    'one'
end
imported_two.rb
def n()
    'n (two)'
end

def two()
    'two'
end
ruby
require './imported_one'
require './imported_two'

puts n                    # => n (two)

python

名前が被ったり、手元のローカル変数と被るのが気になるときはメソッド単位でimportするか、モジュール名を略さない。

python
from imported_one import *
from imported_two import *

print n()                 # => n (two)
python
from imported_one import n, one
from imported_two import two

print n()                 # => n (one)
python
import imported_one
import imported_two

print imported_one.n()    # => n (one)

python の方が融通が利くけど、このimport.とかfromが難しすぎる面もあるので、どっちもどっちなのかな? -> Pythonのimportについてまとめる

演算子

overload

二項演算子はどっちも大体同じ。

ruby

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 + 21.+(2)に見えていれば理解は簡単。scala と同じ。

Stringに対しても可能で、単項演算子も可能ってのは面白いなって思った。

python

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

ruby
if 1 == 1
    p 'true'     # => true
else
    p 'false'
end

これは python を同じだけど、こいつ値返すらしいぞ!

ruby
x = if 1 == 1
    'true'
else
    'false'
end

p x    # => true

こいつはすげぇ!

これを

ruby
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()

こうできる!

ruby
p Foo.new(
    if 1 == 1
        foo = 1
        bar = foo + 2
        foo + bar
    else
        pon = 1
        pon * 2
    end
).twice()    # => 8

変数大嫌いだし、ifが式なのは色々と便利で良い!
メソッドの最後に書いてもそのまま値返却にできる。式は良い。

まさかと思って適当に書いてみて良かった。scala みたいだ。

python

python
if 1 == 1:
    print 'true'
else:
    print 'false'

python は文なんだ、ちょっと、いや結構悲しい。

三項演算子

ruby

他の言語と同じ。

ruby
x = nil
p x.nil? ? 'x is nil' : x                    # => x is nil

python

特殊。他で見たことない。
英語的ってやつかしら。

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がそうだったのできっとと思ったけど、こいつも式みたい。良い!

ruby
p case x
when 'rb'
    'ruby'
when 'py'
    'python'
when 'hs'
    'haskell'
else
    'unknown'
end
# => ruby

式になってると.collectとかにすっきり収まるはず。

ruby
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

python
x = 'py'

print {
    'rb' : 'ruby',  
    'py' : 'python',
    'hs' : 'haskell'
}[x].capitalize()       # => Python

paiza みたいなのやるときはちょいちょい使う。

完全に余談だけど、python の boolean はただの01のエイリアスなのを知ってるとこんなことができる。

python
print ['false!', 'true!'][x.startswith('p')]        # => true!

print 'true!' if x.startswith('p') else 'false!'    # => true!

三項演算子より極僅かだけど短いので、コードゴルフで使う。

優先度

ruby

andより&&が強いらしい。へー。

ruby
p false && false || true     # => true
p false and false || true    # => false

上は(f && f) || tで、下はf && (f || t)に相当するから。
おもしろいけど、自分で使うかは微妙。とはいえ場面が多くて、かつ慣れたら多用すると思う。

python

python はandorのみで&&||はない。これも英語的ってやつかしら。

演算子の連結

ruby

逆に ruby はこれができない。

ruby
x = 5

p 1 < x < 10

まぁちょっと考えれば1.<(x).<(10)だけど、TrueClassが前の条件覚えた.<を持ってるわけないしね。

python

python
x = 5

print 1 < x < 10    # => True

案外ないと「んあ〜」ってなるので、できるのはうれしい。

メソッド

ここが本題。

無名関数

ruby

ruby
f = lambda{|x| x + x}

p f.call(3)    # => 6

defで作ったものと違い、f.call(x)みたいに呼ぶ。もしくはf[x]

こういう時にdeflambdaを比べたいから型が知りたくなるんだけど、これだとエラーなんだよなー。
もうちょっと慣れが必要っぽい。

ruby
def f(x); x + x; end

p f.class                    # => error

p lambda{|x| x + 2}.class    # => Proc

それは置いといて、lambdaで作った方はProcと言うらしい。2.6 のリリースノートで見たぞ。

ruby
p [1, 2, 3, 4].collect do |x|
    x + x
end

そんでここのdo ~ endをブロックというらしい。置き換えられるふいんき。

ruby
lamb = lambda{|x| x + x}

p [1, 2, 3, 4].collect(lamb)

...怒られた。どうやらブロックとlambdaは別物らしい。
ruby lambda to block とかでぐぐったら出るわ出るわ。鬼門らしい?

要約すると、「ブロックはオブジェクトではない。ブロックをオブジェクトにするなら&Procオブジェクトにする。」ってことっぽい。
proclambdaの違いはわかった気がするけど、breakreturnも使う気は全く無いので今は無視。

なので、こうだ。

ruby
lamb = lambda{|x| x + x}

p [1, 2, 3, 4].collect(&lamb)    # => [2, 4, 6, 8]

lambda->による略記があるみたい。

ruby
lamb = ->(x) {x + x}

こっちの方がf(x) {...}と同じ形だからわかりやすいでしょって書いてあったけど、x -> {...}の方がわかりやすいと思うw
java とか scala とか haskell とか js とか、大体こうじゃあない?

python

python
f = lambda x: x + x

print f(3)    # => 6

def製とlambda製に違いはない。

python
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)にできるみたい。

ruby
'foo'.include?('o')                   # => true
:include?.to_proc.call('foo', 'o')    # => true

引数がひとつならこんな感じでそのままはまる。

ruby
['ruby', '', 'python'].collect(&:empty?)    # => [false, true, false]

たまに見る&:f?っていう呪文みたいなのはこいつだったんだな。
&.to_procで、:がシンボル。?はただのメソッド名の慣習。呪文をひとつ習得。

ところで、なんで.collect(の中とかじゃあないと&で怒られるんだ?

ruby
f = :empty?.to_proc    # => #<Proc:0x00007f877b0cd960(&:empty?)>
f = &:empty?           # => unexpected &

その辺がまだわからん...

python

似た感じ。.to_procとか.callがないのでスッキリしてる。

python
'foo'.startswith('f')                 # => True
str.startswith('foo', 'f')            # => True

map(str.strip, ['foo\n', 'bar\n'])    # => ['foo', 'bar']

数年触ってて、これ先日まで知らなかったんだよね...

デフォルト引数と名前付き引数

リスト渡しとか辞書渡しは使わないので無視。必要な場面に出くわしたら調べる。

ruby

ruby
def f(a: 0, b: 0, c: 0)
    p [a, b, c]
end

f(b: 3, a: 1)      # => [1, 3, 0]

デフォルト引数がある引数は、名前指定で渡せる。

python

python
def f(a, b, c = 0):
    print [a, b, c]

f(b = 3, a = 1)    # => [1, 3, 0]

デフォルト引数がなくても、名前指定で渡せる。
これは引数が多い場合に呼ぶ側の可読性が上がったりして地味に好き。

複数引数の無名関数

ruby

ruby
p [['ruby', '2.6'], ['python', '2.7']].collect{|x| "#{x[0]}-#{x[1]}"}                   # => ["ruby-2.6", "python-2.7"]

これは多分a, b = [1, 2]みたいな代入ができるんだから、きっとこうできる。

ruby
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

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 と同じ感じ。

python
print ['%(name)s-%(version)s' % locals() for name, version in ['ruby', '2.6'], ['python', '2.7']]    # => ['ruby-2.6', 'python-2.7']

内包表記のforの部分は,でバラして受けられるのでよく使う。

カリー化

ruby

ruby
f = ->(name, version) {"#{name}-#{version}"}
python = f.curry.call('python')

p ['2.7', '3.3'].collect(&python)    # => ["python-2.7", "python-3.3"]

できるんだ!良さげ!

ただ、これが怒られるのがよくわからない。

ruby
:+.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

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 |"]

>><<、良いなぁ!

メソッド参照とカリー化も出来るってことは...

ruby
p [['ruby', '2.6'], ['python', '2.7']].collect(&join >> :capitalize.to_proc >> surround.curry['|'])    # => ["| Ruby-2.6 |", "| Python-2.7 |"]

できた!慣れてきたー!

&(f >> g)にしないで良いって事は、演算子の優先順位は&の方が弱いってこと?

python

自分で合成書けば...

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

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

んー、良い!
改行できるのがとても良い!

.joinArrayのメソッドなので、最後に書けるのがとても良い!

目線が左上から右下に素直に行くのは、とっても大事!

ちなみに関数合成ができたので、複数引数の片方しか使わない場合は、これを

ruby
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 風。

ruby
fst = proc{|x, _| x}
snd = proc{|_, x| x}

p xs
    .collect{|x| x.split('.')}
    .select(&snd >> is_p)
    .collect(&fst)
    .join(', ')                                      # => [bar, pon]

こういうのはメソッドが集まるほど強いので、個人的にはとても好き。

python

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
            )
        )
    )
)

辛い...

これくらいなら内包表記で書くかなー...

python
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が処理を持てないのかな?。だからlenxs.len()じゃあなくてlen(xs)だしmap.mapじゃあなくてmap()なのかな?dict.get()とか.items()とかあるのにな。

スライス

地味だけど重宝する。

だいたい python と同じだった。良い感じ。

ruby

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じゃあなかった。そっかー。

irb
> (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 の好きなところのひとつ。

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 くらいの長さのリストを試しに一周だけ動かすときとかに便利。

python
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との使い分けは状況次第で簡潔な方ってかんじ。

mapfilterが1回ずつ出る場合はまず内包表記でやる。(ネストするから)
mapfilterがどちらか1回の場合はlambdaってタイプしないといけないかどうかとかで決めてるなぁ。

python
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

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するけど、これは例なので。)

ruby
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したり、空リストに対して畳み込んだりする。

python
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は不要。

ruby
p ARGV

# => ruby ./command.rb 1 3
#
#    ["1", "3"]

グローバル変数だから$使うのかな?って思ってたけど、これは定数?

python

python
import sys

print sys.argv

# => python ./command.py 1 3
#
#    ['./command.py', '1', '3']

コマンドライン引数パーサ

オレオレ自作コマンドを良く作るので、良く触る。

ruby

OptionParserっていう手の込んだやつと、手っ取り早くARGVを改造するgetoptsがあるみたい。

ruby
#!/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が生成されるの?

.getoptsARGVを破壊しているの?それとも.getoptsに渡した情報がどっかに渡ってるのかな?
get しかしてないのにちょっと気持ち悪いw

python

3 系だともっとイケてるのかもしれないけど、ずっとこんな感じでやってる。
これは適当だけど、排他オプションとかもちゃんとできるので良い。

python
#!/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 とかのとは違う感じで、本来の処理の前後に任意の処理を被せられる。

python
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

ruby
res = `ls /tmp`.split()

p res    # => ["a.md", "a.py", "a.txt", "b.txt"]

すっきりしてて良いな!
groovy みたい。

一応、同期処理かも確認しておこう。

ruby
puts "foo"
res = `sleep 2s; ls /tmp`.split()
p res
puts "bar"

ちゃんとp resが終わってから bar が出た。良かった。(groovy でよくハマるw)

python

手っ取り早いのはos.systemだけど、戻り値が受け取れない。

python
import os

res = os.system('ls /tmp')
print res    # => 0

# => python command.py
#    a.md
#    a.py
#    a.txt
#    b.txt

いつもは素直にリストになってくれるcommands.getoutputを使ってる。

python
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 書いてみようかなー。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした