Archive for the ‘書いた’ Category

* Scala – 暗黙の型変換の喜び

Posted on May 22nd, 2010 by tomoya. Filed under 書いた.


くまくま〜のエントリに毒されて、家族や職場のことを考えずして、1週間ほどScalaで遊んでみることにした。
Rubyから入っていく人が多いようだが、実際にテキストを読みつつコードを書いてみると、かなりとっつきやすい。
短く、簡潔に、必要なところだけを書けば、コードが動くというRubyの美点は、Scalaにも見られる。これはたまらない。

Rubyのようなダイナミックな言語に許された大きな特徴の1つは、すでに(どこか別の場所で)定義されているクラスを”開いて”拡張できること。
たとえば、基本クラスであるStringに対して、

class String
  def to_aster
     '*' * self.size
  end
end
 
"password".to_aster #=> "********"

といったメソッド追加が簡単にできる。こんなちょっとした変更だけのために、Stringの派生クラスを作らなくてもいいので、これはおいしい。

ところで、静的な型を持ち、コンパイルされるScalaでも、実はこれと同等のことが実現できる。ただし、方法はもう少しだけ込み入っている。

scala > val hidden = "password".toAster
hidden: java.lang.String = ********

ScalaでのStringは、java.lang.Stringであり、すでに定義済みのこのクラスを拡張できることはさすがにできない。代わりに、「暗黙の型変換」という仕組みを利用する。

class AsterString(str:String) {
  def toAster = str.map(_ => '*')
}
implicit def str2asterStr(str:String) = new AsterString(str)

と拡張したクラスを作り、implicitキーワードをつけて、変換を行うメソッドを定義しておくと、

"aaaaaa".toAster
res58: String = ******

と、新しい魔法を使うことができるようになる。

でも、このためだけに拡張クラスを作るのは面倒くさいかな。はい、そうですよね。
でしたら、無名クラスで代用することも可能になっております。

implicit def toAster(str:String) = new {
  def toAster = str.map(_=>'*')
}
toAster: (str: String)java.lang.Object{def toAter: String}
 
"aaaaaa".toAster
res00: String = ******

うん、同じく、魔法がきいてる。

そして、魔法を、一気に黒魔法へと変えることができる。

implicit def toAster(d:Double) = d.toString.toAster        
toAster: (d: Double)String
val pi:String = 3.14159d
pi: String = *********

Double -> Stringの型変換に、toAsterメソッドが使われるようになった。
このため、toStringを使わずに、Stringで宣言された変数にDoubleの値を代入にしても、コンパイラからtype mismatchとは言われなくなる。
定義したあなたがこの変換をいつまでも記憶の何処かにとどめておくことができたならいいけれど、数ヶ月が経ち、自分で定義した暗黙の型変換メソッドをすっかり忘れてしまっていたなら、きっと何が起こったのかわからなくて唖然とすることになるだろう。

それでもよく考え抜き、「適切に」使用されるならば、これまでコンパイラを黙らせるためだけに書いてきたtoString()やInteger.parseInt()などの紋切り型のメソッドやキャストを書く必要がなくなるばかりか、データベースから取り出した値をScalaオブジェクトの自動的に変換させるORMの機能の実装にすこぶる役に立つものになるだろう。

Scalaさんは、はじめてRubyを触ったときを思い出すほど、わくわくさせてくれる言語である。
こんなDSLを量産したくなりますね。

  val phrase = onC(4 / 4).with7th { 
    val melody = Melody('F * 2', G * 2 , 'C * 4)
    melody.withHarmony(1 octaveUp) 
  } 
  tempo(120).play(phrase)

むふふ、これはおいしい。

Tags: .



* Ruby Facets(Core)でおしゃれRuby!

Posted on December 24th, 2009 by tomoya. Filed under 書いた.


(この記事はRuby Advent Calendar jp: 2009の24日目です. 前日はursmさん、明日はtechnohippyさんです。
普段、技術的なことはwww.nicecabbage.comで書いていますが、今回は企画ものなので日本語で書いてみます。)

メリークリスマスイブ!
こんな聖なる夜に、他人のブログを読んでいる人なんていないと知りながら、投稿します。

この記事では、「Ruby Facets」ライブラリを紹介したいと思います。

Rubyは簡単に基本クラスを拡張できることから、より便利に、あるいは暗黒的に使うためのライブラリがたくさんありますね。”2.days.ago”で一躍評判となったRailsのActiveSupportや、”require :merb / :core_ext / :lib”でおなじみのMerbのextlibが有名です。

Ruby Facetsも、そんなライブラリのひとつ。Rubyをほんのりおしゃれに、ちょっぴりスパイシーにしちゃいます。

どんなことができるのか、つまみ食い形式でご紹介しましょう。

Array

Arrayの積を求める(デカルト積というらしい)

%w(Chapman Cleese Gilliam) ** %w(ham egg spam)
#=>
[["Chapman", "ham"], ["Chapman", "egg"], ["Chapman", "spam"],
["Cleese", "ham"], ["Cleese", "egg"], ["Cleese", "spam"],
["Gilliam", "ham"], ["Gilliam", "egg"], ["Gilliam", "spam"]]

組み合わせを求める

%w(Chapman Cleese Gilliam).combination(2).to_a
#=> [["Chapman", "Cleese"], ["Chapman", "Gilliam"], ["Cleese", "Gilliam"]]

Arrayのおしりに詰め物をする

%w(k t k r).pad(10, "!")
#=> ["k", "t", "k", "r", "!", "!", "!", "!", "!", "!"]

セパレータを複数指定できるjoin

["Crosby", "Stils", "Nash", "Young"].conjoin(', ', ' & ')
#=> "Crosby, Stils, Nash & Young"

Enumerable

EnumerableをごにょごにょするときのECOモード「Denumerable」
(select, mapなどはフィルタのように働き、中間のオブジェクトを生成しない)

arr = (1..100).to_a
class << arr
  include Denumerable
end
puts arr.select{|n| n%10==0}.map{|n| n**2}.to_a

グループ化

(1970..1990).group_by{|y| y < 1989 ? 'showa' : 'heisei' }
#=>
{"heisei"=>[1989, 1990],
"showa"=>[1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988]}

MapしてHashしたいから、Mash!

[:year, :month, :day].mash{|sym| [sym, Date.today.send(sym)]}
#=> {:day=>24, :month=>12, :year=>2009}

最頻出要素(modeは統計用語だそうです)

%w(ham egg spam egg spam egg).mode #=> ["egg"]

出現する確率

%w(ham egg spam egg spam egg).probability
#=> {"egg"=>0.5, "spam"=>0.333333333333333, "ham"=>0.166666666666667}

出現する個数

%w(ham egg spam egg spam egg).frequency
 #=> {"egg"=>3, "spam"=>2, "ham"=>1}

エントロピー増大の法則

%w(ham egg ham egg ham egg).entropy #=> 1.0
%w(ham egg ham egg ham spam).entropy #=> 1.45914791702724
%w(ham egg spam! egg ham spam).entropy #=> 1.91829583405449
%w(ham egg spam! spam!! ham spam).entropy #=> 2.25162916738782
%w(ham egg spam! spam!! spam!!! spam).entropy #=> 2.58496250072116
%w(spam!!!! egg spam! spam!! spam!!! spam).entropy #=> 2.58496250072116
%w(spam!!!! spam!!!!! spam! spam!! spam!!! spam).entropy #=> 2.58496250072116

Hash

Hashだって、時にはArrayのように振る舞わせたいものです。

{ :john=>'lennon'} + { :ono=>'yoko'} #=> {:john=>"lennon", :ono=>"yoko"}
{ :john=>'lennon', :ono=>'yoko'} - { :john=>'lennon'} #=> {:ono=>yoko}
{ :john=>'lennon', :ono=>'yoko'}.join(' ', ', ') #=> "john lennon, ono yoko"

Object

Object#newのProc化

class Monty
  def initialize(name); @name = name end
end
%w(Chapman Cleese Gilliam).map &Monty
 
 #=> [#<Monty:0x1254a6c @name="Chapman">,
 #<Monty:0x1254a30 @name="Cleese">,
 #<Monty:0x12549f4 @name="Gilliam">]

Kernel

ファイル名、行番号つきデバッグ ‘d’(’g'とか’n'とかありますが、’d'もありますよ、ということで)

d variable
#=> [["Chapman", "Cleese", "Gilliam", "Idle", "Jones", "Palin"]] d.rb:5

仲間に入ってる?

me = "tomoya55"
monties = ["Chapman", "Cleese", "Gilliam", "Idle", "Jones", "Palin"]
me.in? monties #=> false

困ったらユーザに聞くといい

ask "Oops, I encoutered unexpected problems. Do you want to debug me?"

とまらないかも。。しれない。

loop do
  break if maybe
end

maybeはtrueになる確率を指定することができる。

# Christmas Eve by Tasturo Yamashita
maybe(0.01) ? 'she will come' : 'she will not'

だまれ!

silently do
  warn "It is a Chiristmas, stupid!"
  puts "We wish a Merry Chiristmas, We wish a Merry Chiristmas"
  STDERR.puts "****, explode!"
end
alias devnull silently

などしてみるのもいいかもしれません

Stackable

文字列をスタックのように扱う

str = "0123456"
class << str
  include Stackable
end
str.pop #=> 6
str.push("9") #=> "0123459"

Time

時の経過をはかる

Time.elapse{require 'active_support'} #=> 0.695170879364014
Time.elapse{require 'extlib'} #=> 0.185311079025269

1年前

Time.now.ago(1, :years)

1年後(引数のパターンが違うのがいやですね。こちらのCREDITはActiveSupportチームらしいです)

Time.now.advance(:years=>1)

Module

ラッパー(いわゆるデコレーター)

class ChristmasObject
  wrap(:inspect) do |orig, *args|
    "<We Wish>" + orig.call + "< a Merry Christmas!!>"
  end
end
p ChristmasObject.new #=> <We Wish>#<ChristmasObject:0x12535cc>< a Merry Christmas!!>

Symbol

~ではじまるSymbolは否定語

:friend.not? #=> false
:'~friend'.not? #=> true

String

Stringにはわさわさとメソッドが追加されています。使いやすそうなものだけ。

"ero".bracket('(') #=> "(ero)"
"ero".bracket('x') #=> "xerox"
"santa".chars #=> ["s", "a", "n", "t", "a"]
"santa clause".cleave #=> ["santa", "clause"] cleaveは「割く」という意味ぽい
 
%q(/usr/bin/ruby).quote(:b) #=> "`/usr/bin/ruby`"
%q(/usr/bin/ruby).quote(:s) #=> "'/usr/bin/ruby'"
%q(/usr/bin/ruby).quote(:d) #=> ""/usr/bin/ruby""
%q("/usr/bin/ruby").dequote #=> "/usr/bin/ruby"
 
"ham spam".rewrite([[/am/, "i"],[/\s/,'-']]) #=> "hi-spi"

まだまだありますがこのへんで、ふう〜

あまりここでは紹介していないけれど、メタプログラミングのための拡張もずいぶんなされているので、Rubyで黒魔術やるのが好きな向きには一見の価値ありだと思います。

ところで、Facets(Core)自体は、目新しい機能は何も用意しません。当たり前に使いたいメソッドを簡潔に実装したものの集まりにすぎません。たいていのメソッドの実装は数行程度で、多くても20行程度。
おそらくRubyプログラマなら、似た振る舞いをするメソッドを実装した経験も数多くあるでしょう。

このライブラリを使用する目的は、

  • 車輪の再発明なしに
  • 簡潔に
  • 正しいメソッド名で

書けることができることにつきます。つまり、コードが読みやすくなり、メンテナンスしやすくなるのですね。

正しいメソッド名とというのは、かなり重要です。
われわれ、へっぽこ日本語プログラマは、痛いクラス名、痛いメソッド名を量産してしまいがちなので、こういったライブラリから適切な英単語をパクる術を身につけておきましょう!

どれだけRuby文法的にきれいにコードを書いても、命名規則がアレげじゃあ、台無しです。
かっこいいメソッド名を使って、おしゃれRubyにしちゃいましょう。

また、実装自体が短いので、Ruby Wayなコードの書き方の参考にもなるなるです。
(ただし、テストがRSpecじゃないのが残念!)

RDocを見て、気になるメソッドがあればソースもぜひ覗いてください。
今夜さっそく気になるあの子を落とすのに役立つトリビアが待ってるかもしれません。

最後に、、、個人的な趣味で、コード例にPython的な用語を盗用してしまってごめんなさい。

.