Smalltalkのtは小文字です

id:sumim:about

Smalltalk を本格的に勉強する気はないけれど、うんちく程度に知っておきたいなら→Smalltalkをちょっとかじってみたい人のための、チュートリアルまとめ - Qiita

オブジェクト指向の“モヤッと”の正体を知りたくなったらこちらの記事が役に立つかも→id:sumim:20080415:p1 とか id:sumim:20040525:p1


 

2016-07-24

[] イケてないRubyのコードのリファクタリングって奴をSmalltalkでやってみる(sumim版)


件のネタについては Ruby ではすでに言及済みですが、その後 えせはらさんが Smalltalk に書き直すかたちで



というすばらしいエントリーを公開してくださいましたので、触発されて私も二番煎じではありますが、同じ Smalltalk 処理系の Pharo を使ってやってみました。ただバージョンは、先頃リリースされたばかりの 5.0 を使用しました。あとで出てきますが、新機能のサジェスチョンも初めて体験できて面白かったです。


正直このネタに関しては、肝心のリファクタリング自体は冗長な記述をまとめるだけであっさりと終わってしまいそうなので、このエントリーでは本論から少し離れて、その前段階の Ruby のリファクタリング対象のコードとそのテストをできるだけ忠実に Pharo Smalltalk で再現して実際にテストを通すまでの作業も少し詳しく書きたいと思います、できればお手元でも Pharo をインストールし、同じ作業を試してみるなどして、Smalltalk 体験のとっかかりとなれば嬉しいです。



▼システムブラウザ(クラスブラウザ)の起動

Pharo を起動すると仮想的なデスクトップが現われるので、このデスクトップに相当する領域でクリック→ポップアップするメニューから System Browser を選択してシステムブラウザ(たんにブラウザ、あるいはクラスブラウザともいう)を起動します。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo01.png

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo02.png


通常、IDE によるサポートがデフォの Smalltalk では、このクラスブラウザを使って既存のクラスやメソッドの閲覧や修正、新規のものの追加を行ないます。

Smalltalk が Ruby などの通常の言語処理系と大きく違うのは、組み込みクラスはもちろん、コンパイラなど処理系の主要部や IDE それ自体が Smalltalk のオブジェクトで構成され(セルフホスティング)、なおかつ、それらを構成するオブジェクトをオブジェクトストア(簡易OODB。仮想イメージと呼ばれるファイルに適宜保存可能なオブジェクトメモリ)内に生きたまま永続化可能なかたちで存在させ運用しているところです。

したがって Smalltalk において、クラスやメソッドの定義はソースコードの記述というよりは、その場で「クラスやメソッドのオブジェクトを生成してオブジェクトストアに追加する(あるいは差し替える)作業」という感覚であることは、他言語とかなり違います。ここらへんは Smalltalk でこの仕組みを意識しつつ体験しないと実感できないところなので、ぜひ実際に操作してつかんでほしいところです。



▼Order クラスの定義

元の Ruby のコードでは、さくっとこのような定義があります。

class Order < OpenStruct
end

残念ながら Smalltalk には Ruby の OpenStruct のような便利な機構はないので、普通に amount と placed_at をインスタンス変数に持つ Object のサブクラスとして定義します。

いくつか方法はあるのですが、ここでは先ほど起動したクラスブラウザの上段左端の枠内をスクロールして最後にある _UnpackagedPackage を見つけてクリックします。すると、下の枠にクラス定義のテンプレートのようなものが現われるので、その中の #NameOfSubclass を #Order に、instanceVariableNames:キーワードの引数を 'amount placed_at' に、package:キーワードの引数の '_UnpackagedPackage' を 'RubyRefactoring-Example' に変更してから同じ枠内で右クリックメニューから Accept (あるいはキーボードで ctrl + s をタイプ)します。

Object subclass: #Order
	instanceVariableNames: 'amount placed_at'
	classVariableNames: ''
	package: 'RubyRefactoring-Example'

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo03.png


すると、上段左から二番目の枠内に Order が現われ、システムに当該クラスが追加されたことが示されます。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo04.png


引き続き、インスタンス変数 amount と placed_at それぞれのアクセッサーを追加しましょう。

Ruby の attr_accessor 相当のことをすればよいわけですが、ここでも Ruby とは違い Smalltalk では、スクリプト内にアクセッサーの自動生成を処理として記述するのではなく、あらかじめクラスブラウザなどで作業として済ませておく必要があります。

やはりいくつか方法はありますが、ここでは上段左から二番目の枠で Order クラスを(必要なら一度クリックして選択解除した後、改めて)右クリックしてポップアップするメニューから Refactoring → Class Referctoring → Generate Accessor → OK でアクセッサーを自動的に生成、追加します。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo05.png


このタイミングで、次のような「Author Identification」ダイアログが現わて名前の入力を促されることがあります。ここで入力した名前は、システムに改変を加えた編集者名としてメソッド定義等のバージョン管理などをするのに利用されるので適当に入力して OK してやってください。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo06.png


今さらですが、デフォルトでアンダーラインを変数名などに使えるのは、Ruby のコードを移植するときに便利ですね。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo07.png



▼OrdersReport クラスの定義

Orderクラスと同様の操作で、引き続き OrdersReport クラスとそのアクセッサーを定義します。Ruby の定義は次のようなものです。

class OrdersReport
  def initialize(orders, start_date, end_date)
    @orders = orders
    @start_date = start_date
    @end_date = end_date
  end

  def total_sales_within_date_range
    orders_within_range = []
    @orders.each do |order|
      if order.placed_at >= @start_date && order.placed_at <= @end_date
        orders_within_range << order
      end
    end

    sum = 0
    orders_within_range.each do |order|
      sum += order.amount
    end
    sum
  end
end


直前の Orderクラスおよびそのアクセッサーの追加(定義)作業の直後であれば、クラスブラウザの上段左端の枠ではすでに RubyRefactoring-Example パッケージが追加されて選択状態にあるので、今回は package:キーワードの引数はそのままで結構です。(必要なら、Orderクラスをクリックして選択解除してクラス定義のテンプレートを下の枠に呼び出してから)subclass: キーワードの引数を #OrdersReport に、instanceVariableNames: の引数を 'orders start_date end_date' に変更して Accept します。

それぞれのインスタンス変数のアクセッサーも Order 同様に Generate Accessor で自動生成・追加します。

Object subclass: #OrdersReport
	instanceVariableNames: 'orders start_date end_date'
	classVariableNames: ''
	package: 'RubyRefactoring-Example'

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo08.png


OrdersReportクラスには、加えてメインの total_sales_within_date_range メソッドも追加してあげる必要があります。クラスブラウザ上段左から三番目の枠内の accessing プロトコルをクリックして選択すると下にメソッド定義のためのテンプレートが現われるので ctrl + a などで選択後、次のコード(OrdersReport >> より後、total_sales_within_date_range 以下を使用します。以降に出てくるコードも同様です)をコピペ、あるいはタイプして入力して置き換えてください。

OrdersReport >> total_sales_within_date_range
	| orders_within_range sum |
	orders_within_range := OrderedCollection new.
	orders
		do: [ :order | 
			(order placed_at >= start_date and: [ order placed_at <= end_date ])
				ifTrue: [ orders_within_range add: order ] ].
	sum := 0.
	orders_within_range do: [ :order | sum := sum + order amount ].
	^ sum

入力が完了したら、同枠内で右クリック → Accept (コンパイルしてクラスにメソッドを追加。なお、Smalltalk ではコンパイルはメソッドごとにインクリメンタルに行なわれる)します。コンパイルが通ると上段右端の枠内に total_sales_within_date_range が現われ、以降、いつでも当該メソッドのソースコードを読んだり編集したりが可能になります。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo09.png



▼OrdersReportTest クラスの定義

最後に Ruby 版のテストを Pharo Smalltalk に移植します。Ruby では次のようなコードでした。

require 'spec_helper'

describe OrdersReport do
  describe '#total_sales_within_date_range' do
    it 'returns total sales in range' do
      order_within_range1 = Order.new(amount: 5,
                                      placed_at: Date.new(2016, 10, 10))
      order_within_range2 = Order.new(amount: 10,
                                      placed_at: Date.new(2016, 10, 15))
      order_out_of_range = Order.new(amount: 6,
                                     placed_at: Date.new(2016, 1, 1))
      orders = [order_within_range1, order_within_range2, order_out_of_range]

      start_date = Date.new(2016, 10, 1)
      end_date = Date.new(2016, 10, 31)

      expect(OrdersReport.
             new(orders, start_date, end_date).
             total_sales_within_date_range).to eq(15)
    end
  end
end

RSpec のコードですが、こちらも単純に Pharo/Squeak Smalltalk に組み込みの xUnit 系 TestCase を継承した OrdersReportTest クラスに test_total_sales_within_date_range メソッドを定義することで再現とすることにします。

(Order または OrdersReportクラスが選択状態にあるならクリックして選択を解除した状態で)Object を TestCase に、subclass: キーワードの引数を #OrdersReportTest に変更して Accept します。上段左から三番目の枠に OrdersReportTest が現われることを確認してください。

TestCase subclass: #OrdersReportTest
	instanceVariableNames: ''
	classVariableNames: ''
	package: 'RubyRefactoring-Example'

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo10.png


このクラスにはインスタンス変数はないのでアクセッサーの自動生成作業は不要です。あとはテストメソッド test_returns_total_sales_in_range を追加します。

まず、上段左から三番目の枠の no message を選択して下の枠にメソッド定義のテンプレートを呼び出します。下の枠内で ctrl + a でテンプレート全選択後、次のコードに置き換えてから Accept してコンパイルを完了します。

OrdersReportTest >> test_returns_total_sales_in_range
	| order_within_range1 order_within_range2 order_out_of_range orders start_date end_date |
	order_within_range1 := Order new amount: 5; placed_at: (Date year: 2016 month: 10 day: 10).
	order_within_range2 := Order new amount: 10; placed_at: (Date year: 2016 month: 10 day: 15).
	order_out_of_range := Order new amount: 6; placed_at: (Date year: 2016 month: 1 day: 1).
	orders := {order_within_range1. order_within_range2. order_out_of_range}.
	start_date := Date year: 2016 month: 10 day: 1.
	end_date := Date year: 2016 month: 10 day: 31.
	self
		assert:
			(OrdersReport new orders: orders; start_date: start_date; end_date: end_date)
				total_sales_within_date_range
		equals: 15

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo11.png



▼テストの実行

上段左から二番目の枠でテストクラス OrdersReportTest を、あるいは上段右端の枠で実行したいテストメソッド test_returns_total_sales_in_range を(必要ならクリックして選択解除後、改めて)右クリックしてポップアップメニューから Run tests を選ぶとテストが走ります。上記の作業がうまく終わっていればグリーンが左下に現われるはずです。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo12.png



▼リファクタリング1: a >= b and: [a <= c] の代わりに a between: b and: c を使いましょう

ようやく本題です。と、意気込みたいところですが、ここまででほぼ力尽きたので以下はあっさり目に。^^;


リファクタリングの例題ということで、あえてそうしているのでしょうが、とにかく total_sales_within_date_range というメソッドの記述内容は冗長で手続き的です。Smalltalk や Ruby、Rails に元から備わっている API をあえて無視して書かれているだけ、という言い方もできます。裏を返せば、Smalltalk や Ruby、Rails をちゃんと使えば読み下しやすくすっきり書けるということです。

まず、Pharo 5.0 から導入された下段の枠に表示されるコードのサジェスチョンに従った変更をしてみます。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo13.png


最初の "a >= b and: [a <= c]" -> "a between: b and: c" というのは特には説明は不要かと思います。Ruby にも between? がありますね。

このサジェスチョンの脇の?と×の間には「Automatically resolve the issue」ボタンが用意されているので、これを押して自動で処理してもらいましょう。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo14.png

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo15.png

OrdersReport >> total_sales_within_date_range
	| orders_within_range sum |
	orders_within_range := OrderedCollection new.
	orders
		do: [ :order | 
			(order placed_at between: start_date and: end_date)
				ifTrue: [ orders_within_range add: order ] ].
	sum := 0.
	orders_within_range do: [ :order | sum := sum + order amount ].
	^ sum

テストももちろん通ります。以後、OrdersReportTest と OrdersReport を行き来するのは面倒なので、OrdersReport を右クリックして Browse full してクラスブラウザを2つ開いておくと total_sales_within_date_range を書き換えた後のテストが楽になるのでお薦めです。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo16.png


あるいは、ブラウザ右上の Go back、Go foward ボタンを使って行き来するのでもよいでしょう。



▼リファクタリング2: do: の代わりに select: や collect: を使いましょう

こちらも Pharo からのサジェスチョンのひとつなのですが、慣れていないとちょっとイメージしにくいですね。条件に合致したものをあらかじめ用意した追加可能なコレクション(この場合 an OrderedCollection)に加えていくという処理は select:(条件に合致したものを排除するなら reject:)を使った処理に置き換えられます。

OrdersReport >> total_sales_within_date_range
	| orders_within_range sum |
	orders_within_range := orders
		select: [ :order | order placed_at between: start_date and: end_date ].
	sum := 0.
	orders_within_range do: [ :order | sum := sum + order amount ].
	^ sum


▼リファクタリング3: sum := 0. colln do: [:x | sum := sum + x ]. ^sum は inject:into: に置き換えましょう

上の select: と考え方は似ていますが、こちらはいわゆる畳み込み処理です。Smalltalk の inject:into: は Ruby の inject に相当し、第一引数として初期値を与え、それと最初の要素との何かしらの演算をし(二引数のブロックで与えられる)を次の要素との同じ演算処理に受け渡す処理を表現するのに用います。

件のコードでも sum に初期値 0 を与え、ループを回して sum に足し込んでいく様子は、inject:into: のターゲットとなる処理の代表格です。

OrdersReport >> total_sales_within_date_range
	| orders_within_range sum |
	orders_within_range := orders
		select: [ :order | order placed_at between: start_date and: end_date ].
	^ orders_within_range inject: 0 into: [ :sum :order | sum + order amount ]


▼リファクタリング4a: orders_within_range の amount の sum であることが分かる記述にする(その1)

当初の冗長さから比べればだいぶ簡潔になったのですが、inject:into: というのは select: などに比べると、読み下しのしやすさという意味からはちょっと弱いです。ここはやはり、orders_within_range の amount の sum を欲していることがわかる記述にしたいところです。

ひとつの解決策としては、組み込みの select:thenCollect: を使う方法です。

OrdersReport >> total_sales_within_date_range
	^ (orders
		select: [ :order | order placed_at between: start_date and: end_date ]
		thenCollect: #amount) sum

select:thenCollect: は読んで字の通り、select:キーワードの引数(つまり第一引数)のブロックの処理に合致する要素を選び、さらにそれらについて thenCollect:キーワードの引数(同じく第二引数)のブロックの処理をした値を集めたコレクションを返します。

なお、Squeak/Pharo では、ブロックで記述する処理が要素に対する単項メッセージ(引数を取らないメッセージ)の場合、そのメッセージのセレクタ(メソッド名。実体はシンボル)で代替可能です。この例では [:order | order amount] は #amount に置き換え可能であることをさします。


ただこの select:thenCollect: には問題があって、それは、select:thenCollect: の実装が素朴に select: した結果を collect: しているだけで工夫がないため、ひとつ前のリファクタリング前のコードより、無駄なコレクションを作成するコストがかかってしまうことです。



▼リファクタリング4b: orders_within_range の amount の sum であることが分かる記述にする(その2)

コストの問題を解決するには、inject:into: の sum に特化したエイリアスを定義する方法があります。具体的には Ruby2.4 以降の sum のように、ブロックを引数として取ることができるようにすればよいでしょう。inject:into: と同じ Collection に次の sumOf: メソッドを生やしてやります。


まず念のため inject:into: がどこに定義されているのかを調べます。文字を入力できる場所ならどこでもいいのでその中の(たとえば total_sales_within_date_range のコード入力枠の)空行におもむろに inject:into: とタイプして入力し、ctrl + m(あるいは右クリックメニューから Code search... → Implemnetors of it を選択)します。すると開いたウインドウの上のリスト枠から inject:into: が Collection と LayoutCell というふたつのクラスに定義され、Collection での定義が下のペインに呼び出されます。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo17.png


ここで、下のペインの inject:into: の定義を ctrl + a などの操作で全選択し、次のコードに置き換えたあと Accept (コンパイル)します。なお、クラスブラウザでのコード編集はメソッド名が変われば別のメソッドの追加と解釈されるので、こうした操作で inject:into: が sumOf: に置き換えられてしまう心配はありません。

Collection >> sumOf: block
	^ self inject: 0 into: [ :sum :each | sum + (block value: each) ]

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo18.png


あと、このままですと RubyRefactoring-Example パッケージをソースコード単位で扱うとき(たとえば File Out して別の仮想イメージに Load 等した場合)に sumOf: が追加されず困るので、RubyRefactoring-Example パッケージに含めておくのがよいと思います。操作は、sumOf: を選択して右クリック→ Code search... → implementors of it → Browse → 上段右端の枠で sumOf: を右クリック→ Move to package... → RubyRefactoring-Example を選択 → OK です。


以降はこの sumOf: が使えるので、total_sales_within_date_range も次のように書き換えます。

OrdersReport >> total_sales_within_date_range
	^ (orders select: [ :order | order placed_at between: start_date and: end_date ])
		sumOf: #amount

いかがでしょう。指定した期間内の order の amount の sum であることが分かりやすくなったのではないでしょうか。当初のコードと比べると一目瞭然です。

OrdersReport >> total_sales_within_date_range
	| orders_within_range sum |
	orders_within_range := OrderedCollection new.
	orders
		do: [ :order | 
			(order placed_at >= start_date and: [ order placed_at <= end_date ])
				ifTrue: [ orders_within_range add: order ] ].
	sum := 0.
	orders_within_range do: [ :order | sum := sum + order amount ].
	^ sum

なお、クラスブラウザの上段右端の枠内の total_sales_within_date_range を右クリック→ Versions で、リファクタリングしたコードの変遷をたどることができます。

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo19.png

http://squab.no-ip.com/collab/uploads/RubyRefactoringPharo20.png


Smalltalk は環境それ自体がオブジェクトストアのような性格を有していることを利用して、このようなメソッド単位でバージョン管理が可能な SCM 機構が 1980年代から組み込まれて利用されてきています。他にもいろいろと興味深いしくみがあるので、他言語ユーザーの皆さんも、処理系や IDE 等の環境が Smalltalk でどのように実現されているか、ぜひオブジェクトのスープの中に飛び込んで異世界を愉しんでみてください。

2016-07-18

[] 手続き的で冗長な Ruby のコードを Squeak/Pharo Smalltalk の類似機能を活用してよりシンプルに書き換える


こんな感じの“イケてない”と称されるコードを改善する話。

  def total_sales_within_date_range
    orders_within_range = []
    @orders.each do |order|
      if order.placed_at >= @start_date && order.placed_at <= @end_date
        orders_within_range << order
      end
    end

    sum = 0
    orders_within_range.each do |order|
      sum += order.amount
    end
    sum
  end

Rubyのリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法 - その1 - Ruby on Railsのビシバシはぁはぁ日記

元記事では、Smalltalk 由来のいわゆる「〜ect系」メソッドの導入によりコードをシンプルに書き換えていますが、もうちょっと Ruby や Rails に備わっている機能を使うことはできないのかなぁ、とリファレンスを紐解きながらこんなふうにしてみました。

  def total_sales_within_date_range
    within_date_range = ->order{ order.placed_at.between?(@start_date, @end_date) }
    @orders.select(&within_date_range).sum(&:amount)
  end

範囲に収まっているかどうかの判定は無名関数(Proc)にして名前を付け、select に渡しています。Ruby の無名関数は Smalltalk のと違い、〜ect系メソッドの引数としてはそのまま渡せないので、& を付ける必要があります。

範囲に収まっているかどうかの判定処理記述の中身についても、Date が Numeric 同様 Comparable なのを利用して簡潔な between? に置き換えています。Smalltalk にも Magnitude>>#between:and: がありますね。


map(&:amount).inject(0, :+) も冗長で意図が伝わりにくいので sum ひとつに置き換えました。ただ、ここで使った sum は Smalltalk の sum とは違って、次のような定義を想定しています。Rails や Ruby2.4 の sum はよく知らないので、こういう動きでなかったらごめんなさい。

class Array
  def sum(zero = 0, &b)
    inject(zero){ | s, e | s + (b ? b[e] : e) }
  end
end

Ruby の制約として残念だったのは、Proc の within_date_range をクエスチョンマークを使って within_date_range? としたかったのが許されなかったところ。メソッド名にすればクエスチョンマークもOKなのですが、そうすると今度は select の引数にするときに記述が面倒になるので痛し痒しですね。


2016-07-03

[] Sak 関数ベンチを Squeak/Pharo Smalltalk


絶対どっかにありそうだけど、ベンチマーク用関数 fib_m() を考えてみた。

  • fib_m(0 or 1) = 1
  • fib_m(n) = fib_m(n-1) * fib_m(n-2)

Sak 関数と呼んで下さい。

Diary - 2016 July 研究日記

これを Squeak/Pharo Smalltalk で試してみました。

"メソッド版"
Integer compile: 'fibM
    ^self caseOf: {[0]->[1]. [1]->[1]}
        otherwise: [(self-1) fibM * (self-2) fibM]'.

[40 fibM] timeToRun

"Squeak5.0 [msec] => 1987 "
"Pharo5.0 => 0:00:00:02.096 " 
"ブロック版"
| fibM |
fibM := nil.
fibM := [:n |
    n caseOf: {[0]->[1]. [1]->[1]}
        otherwise: [(fibM value: n-1) * (fibM value: n-2)]
].

[fibM value: 40] timeToRun

"Squeak5.0 [msec] => 3025 "
"Pharo5.0 => 0:00:00:02.986 "

Node.js(V8)にこそ僅差で負けていますが、Squeak/Pharo Smalltalk の Cog VM もなかなかの速度をたたき出しています。

$ gcc -o sak_bench sak_bench.c


$ time ./sak_bench
real    0m0.913s
user    0m0.890s
sys     0m0.000s
$ cat sak_bench.js
function fib_m(n){
    if(n == 0) return 1;
    if(n == 1) return 1;
    return fib_m(n-1)*fib_m(n-2);
}

fib_m(40);


$ node -v
v0.10.31


$ time node sak_bench.js
real    0m1.681s
user    0m0.000s
sys     0m0.000s


Ruby の最近のビルドは試していなかったので、ほとんどその存在を忘れかけていた rbenv で 2.4.0-dev をインストールして手元の環境で試してみたところこんな感じになりました。

$ ruby -v
ruby 2.4.0dev (2016-07-03 trunk 55566) [x86_64-cygwin]


$ time ruby sak_bench.rb
real    0m16.726s
user    0m16.133s
sys     0m0.424s

ささださんのところの環境よりちょっとだけ速い結果を出すようですが、言語によって前後するようです。

$ gosh -V
Gauche scheme shell, version 0.9.5_pre1 [utf-8,pthreads], x86_64-unknown-cygwin


$ time gosh sak_bench.scm
real    0m13.953s
user    0m13.905s
sys     0m0.000s
$ python3 -V
Python 3.4.3


$ time python3 sak_bench.py
real    0m50.436s
user    0m50.390s
sys     0m0.015s
$ python2 -V
Python 2.7.10


$ time python2 sak_bench.py
real    0m42.712s
user    0m42.390s
sys     0m0.109s

ついでに Ruby でも Proc 版も試してみます。

$ cat sak_bench_proc.rb
fib_m = ->(n){
  case n
  when 0, 1
    1
  else
    fib_m[n-1] * fib_m[n-2]
  end
}

fib_m[40]


$ time ruby sak_bench_proc.rb
real    0m49.341s
user    0m48.775s
sys     0m0.302s

2016-06-25

[] 現在の Smalltalk(すなわち、-80以降)と Smalltalk-76, -72における true, false の扱いの違いを調べてみた



元の話の発端が何かは分からなかったのですが、最近 Ruby の true, false の属するクラスについての言及


や、関連する過去のこんな記事


を見かけたので、今の Smalltalk と、その元になっている Smalltalk-80 より前に作られた Smalltalk-76、Smalltalk-72 ではどんなふうになっていたか調べてまとめてみました。



▼ 現在の Smalltalk における true, false (Smalltalk-80 以降)

先の言及にもあるように Smalltalk-80 を元にしている今の Smalltalk(Pharo、SqueakVisualWorks などの直系の子孫。GNU Smalltalk などのファンお手製の変わり種実装を含む)では、true は Trueクラス、false は Falseクラスの唯一のインスタンスで、さらに Trueクラス、Falseクラスは共通の Booleanクラスのサブクラスになっています。

true class. "=> True "
false class. "=> False "
True superclass. "=> Boolean "
False superclass. "=> Boolean "

nil class. "=> UndefinedObject "
UndefinedObject superclass. "=> Object "

Ruby と異なり、現在の Smalltalk では、if 式は true や false へのメッセージ送信として記述します。たとえば、次の式の場合、3 < 4 (これも 3 への < 4 というメッセージ送信)の結果の true に対して、ifTrue: [5] ifFalse: [6] というメッセージが送信されます。

3 < 4 ifTrue: [5] ifFalse: [6] "=> 5 "

通常のメッセージ式と同じように解釈されるならば、これは true の属する Trueクラスに定義された ifTrue:ifFalse: というメソッドを [5]、[6] という引数を伴ったコールとして機能します(実際にはコンパイル時にインライン展開されていわゆる GOTO を使ったコードに置き換えられるので、通常のコードでは ifTrue:ifFalse: などのメソッド本体がコールされることはありません。念のため)。

分かりやすく Ruby 風に(メソッド名に使えないコロンをアンダーバーに置き換えて)書き下すとこんなかんじになりますか。

(3 < 4).ifTrue_ifFalse_(->{5}, ->{6})

普段使いの Squeak4.3J で調べると、True(もしくは False)にはこんなメソッド群が定義されています。

True selectors.
"=> #(#and: #| #ifTrue:ifFalse: #not #ifFalse:ifTrue: #==> #ifTrue: #or: #ifFalse: #printOn: 
#xor: #& #asBit) "

そのスーパークラスである Boolean に定義されているメソッド群はこんなふうになります。

Boolean selectors.
"=> #(#ifTrue:ifFalse: #or:or: #and:and:and: #not #or:or:or: #and:and: #or:or:or:or:or: #& 
#veryDeepCopyWith: #and:and:and:and: #eqv: #| #or:or:or:or: #storeOn: #deepCopy #basicType 
#clone #isLiteral #ifFalse:ifTrue: #shallowCopy #==> #ifTrue: #or: #ifFalse: 
#newTileMorphRepresentative #and:) "

このうち、サブクラスの True, False が再定義している #(#ifTrue:ifFalse: #not #& #| #ifFalse:ifTrue: #==> #ifTrue: #or: #ifFalse: #and:) の中身はすべて self subclassResponsibility と記述されています。これらのメソッドはコールされることはないですし、コールされても例外があがるだけで意味をなさず定義は無用なのですが、きっと抽象データ型OOPの部分的サポートを意識した設計になっているのでしょうね。



▼Smalltalk-76 における true, false

Smalltalk は仕様のみで実装まで至らなかった Smalltalk-71 を除いて、大きく分けて 1970年代に Smalltalk-72、Smalltalk-76 という「二つの言語」が作られました(実際にはさらに -72 の高速版の -74、-76 のシュリンク版の -78 も作られています)。この「二つの言語」という言い回しは、通常の言語からすると妙に聞こえると思います。

プログラミング言語は、バージョンが上がるごとに機能が増えたり文法が拡張・変更されたりしつつも、言語としては同じ物と認識されるのが普通です。しかし Smalltalk の場合は違っていて、メッセージングによるプログラミングというコンセプトを共有することを除けば、Smalltalk-72 と -76 は言語としてはまったく別物なので注意を要します。

Smalltalk-80 とは比較的時代の近い Smalltalk-76 は、Smalltalk で見慣れたメッセージ式文法やカラムUI を採用したクラスブラウザ等の IDE関連 GUIツールの存在など Smalltalk-80 と似た部分も多いのですが、Smalltalk-80 でよく知られているメタクラスや、後にクロージャーで実装される第一級の無名関数オブジェクトが無かったり、いわゆる 〜ect 系のコレクションメソッド群を持たないなど、今の Smalltalk の特徴とされる機能を多く欠いていて興味深いです。

一方で、Smalltalk-76 には通常の言語にある if式構文が用意されています。つまり Smalltalk-80 のように true, false へのメッセージ送信では表現しないのです。これは Smalltalk に慣れ親しんだ者としてはちょっとした衝撃の事実ですね。たとえば先に書いた現在の Smalltalk の 3 < 4 ifTrue: [5] ifFalse: [6] という処理は、Smalltalk-76 では次のように記述します。

http://squab.no-ip.com/collab/uploads/st76ifthenelse.png


同様に、for, while, until といったループの式構文も用意されています。

http://squab.no-ip.com/collab/uploads/st76forwhileuntil.png


こんな Smalltalk-76 ですが、本題の true, false はどのような扱いになっているのでしょうか。それぞれが属するクラスを調べてみます。処理系は 2014年にリバイブされた Smalltalk-78 を使用しました(先のスクリーンショットも同様)。

true class "⇒ Class Object "
true hash "⇒ 2 "

false class "⇒ Class Object "
false hash "⇒ 1 "

nil class "⇒ Class Object "
nil hash "⇒ 0 "

Object new class "⇒ Class Object "
Object new hash "⇒ 4867 "

Class Object というのは単に Objectクラスのことのようです。実に面白い。なんと Smalltalk-76 では true, false そして nil は専用のクラスが用意されておらず、Object の普通のインスタンスなのです。hash が 2, 1, 0 なのも象徴的です。



▼Smalltalk-72 における true, false

Smalltalk-72 は先にも述べたとおり現在の Smalltalk はおろか、Smalltalk-76 ともまったくの別言語です。クラスは JavaScript のようにコンストラクタを兼ねた関数で、その中にメソッドがパターンマッチを用いた文法解析処理のように記述されます。クラスの継承機構はありません。したがって self subclassResponsibility に象徴されるような、現在主流の抽象データ型のOOP の汚染を受けていないので、アラン・ケイのメッセージングOOP の心を学ぶには、ぜったい外せない処理系とも言えます。

参考まで、簡単な Joe the box デモ(下のツイートを参照)程度であれば Smalltalk-78 同様、Lively-Web にリバイブされた ALTO/Smalltalk-72 エミュレーターが手軽に使えます。

さらに組み込みのエディタなども活用して Smalltalk-72 を本格的に体験したいということであれば、Squeak Smalltalk に関する知識がそれなりに必要にはなりますが Squeak3.2 で動く Smalltalk-72 エミュレーターがお薦めです。今回は後者を使います。


さて、Smalltalk-76 が Object のインスタンスで true, false, nil を表わしていたのだから、それ以前の Smalltalk-72 でも同じだろう…と安直に考えていたのですが、調べてみるとどうやら違うようです。そもそも Smalltak-72 では Smalltalk-76 と違って、true, false へのメッセージ送信による条件分岐処理に戻って(?)いますので、この時点でもう間違っています。^^;

Smalltalk-72 では「 真偽値を返す式 ⇒ (真の時に評価する式) 偽の時に評価する式 」で条件分岐を記述します。(この ⇒ は ? で、後の is? で使う ? は ~ で入力できます。! は Lively-Web版では \ 、Squeak3.2版では上方向カーソルキーです。)

http://squab.no-ip.com/collab/uploads/st72ifthenelse.png


ちなみに Smalltalk-72 では、メソッドの定義はパターンマッチで記述された文法の定義のようなものなので、if オブジェクトに対するメッセージ送信の形で ALGOL系の if-then-else も定義可能で、実際にそうした記法を可能にするコードもエミュレーターには含まれています。

http://squab.no-ip.com/collab/uploads/st72algoltypeif.png


これを踏まえて true, false ついでに nil がどのように扱いかを調べてみましょう。Smalltalk-72 ではオブジェクトに自身が属するクラスを訊ねるためには is? というメッセージを送信します。

http://squab.no-ip.com/collab/uploads/st72truefalsenil.png


true は atom と称したシンボルオブジェクトに属します。一方、false は is? メッセージに対して自身を返している、あるいは false がクラスであるかのように振る舞いますが、実際には false は falseclass のインスタンスです。

show falseclass で定義を確認すると、どうやら is や is? メッセージを受け取った際に自身を返すように、さらにご丁寧に本来 偽(false) であるはずの false is false が 真(true) を返すようなパターンマッチを用意してまで特殊な振る舞いがコードされているようです。

http://squab.no-ip.com/collab/uploads/st72showfalseclass.png


false 向けのいくつかのメソッドはプリミティブ(CODE 11)として記述されておりユーザーからは見えないのですが、is やそれに続いて ? がメッセージと送られた場合にどういう振る舞いになるかは Smalltalk-72 自身で記述されており、false is? が falseclass ではなく false を返していることがこの定義を見ると分かります。

この特殊な振る舞いの定義を削ってしまえば正しく応答してくれるはずなのですが、ただ is メソッドはクラスごとに実装する必要があるようで、これを欠いてしまうと is? メッセージに対して正しい反応ができないようです。

http://squab.no-ip.com/collab/uploads/st72addtois.png


そこで、falseclass の特殊な振る舞いの is の定義をいったん削除して他の通常のクラスのような is メソッドを定義してみました。(残念ながら、組み込みのエディタを用いたこの操作は、なぜかマウスクリックをエミュレーターが検出しない Lively-Web 版ではできません。あしからず。)

http://squab.no-ip.com/collab/uploads/st72editfalseclass01.png

http://squab.no-ip.com/collab/uploads/st72editfalseclass02.png

http://squab.no-ip.com/collab/uploads/st72editfalseclass03.png

http://squab.no-ip.com/collab/uploads/st72editfalseclass04.png


これで false も is? メッセージに嘘をつかなくなりましたので再び試してみます。

http://squab.no-ip.com/collab/uploads/st72editfalseclass05.png



▼まとめ

Smalltalk の状況に限れば、察するに true, false にメッセージを送って条件分岐をするなど必要がなければ、それぞれにクラスは無くても大丈夫で(Smalltalk-76)、もしその必要があっても、false 以外は真扱いにするのであれば、false クラスだけで用は足りる(Smalltalk-72)ということになりそうです。

さらに、true のみに真の振る舞いをさせるなら True クラスも必要で、加えて真偽値の振る舞い(メソッド)を増やしたり、その際にテンプレートメソッドパターンを活用したいとき、あるいは抽象データ型OOPを限定的にでもサポートすることを考えた場合は Boolean クラスも用意しておくのが便利(Smalltalk-80)なようです。

2016-03-15

[] 平和な動物園を作ろう!をインスタンス特異的メソッドを用いてSqueak Smalltalk


http://echo.2ch.net/test/read.cgi/tech/1444216746/361 経由で、

あなたは,さいたま動物園の園長に選ばれました.さいたま動物園には全部で10種類の動物たちがいます.あなたの園長としての初仕事は,これらの動物たちをどのオリに入れるかを決めることになりました.

さて,ここで問題なのは,

・動物たちには相性の良し悪しがある.

・相性の悪い動物たちをお互いに近いオリにいれると,みんなが暴れだしてしまう.

・動物たちの不満度が小さくなるようにオリを選んでやる必要がある.

ということです.


動物たちの不満度は,

 (各オリの間の距離) × (各動物の間の相性)の総和

で表されます.


さぁ,地図に示されたオリに動物たちをうまく割り当てて,動物たちの不満度が小さい平和な動物園を作ってください.

平和な動物園を作ろう! ―2次割当て問題って何?― 埼玉大学工学部情報システム工学科池口研究室

手抜きをすべく、Matrix で行あるいは列単位で permutationsDo: を使いたかったのですが、そもそも Matrix は SequenceableCollection のサブクラスではなかったので permutationsDo: は端から使えないことが発覚( permutationsDo: は SequenceableCollection に定義されている。為念)。そこで、配列の配列を使うことにしました。

ただし素朴にデータだけからなる配列の配列では、並べ替えた際に動物との対応が面倒になるので、key に動物名、value に配列を持たせた Association を要素にしました。

animals := {
   'ライオン' -> #(0 2 6 4 6 2 4 4 2 4).
   'ワニ' -> #(2 0 4 2 2 2 2 2 2 6).
   'ニシキヘビ' -> #(6 4 0 2 6 8 8 6 4 8).
   'オオカミ' -> #(4 2 2 0 4 2 6 6 2 6).
   'トラ' -> #(6 2 6 4 0 2 4 4 2 4).
   'スイギュウ' -> #(2 2 8 2 2 0 6 6 6 8).
   'サイ' -> #(4 2 8 6 4 6 0 6 6 4).
   'カバ' -> #(4 2 8 6 4 6 6 0 6 6).
   'インパラ' -> #(2 2 4 2 2 6 6 6 0 6).
   'ゾウ' -> #(4 6 8 6 4 8 4 6 6 0)}.

こうしておけば動物名も一緒にスワップできるので何かと便利で一件落着…かと思いきや、動物をスワップしたら、その動物との相性を記したデータの対応する位置の要素も連動してスワップさせないといけません。

うーむ、やはり permutationsDo: 相当を書くしかないのかな…と諦めかけたのですが、それだとなんか負けた気(謎)がします。


あらためて SequenceableCollection>>#permutationsDo: 内の処理を眺めてみると、size と swap:with: しか使われていないことが分かります。

SequenceableCollection >> permutationsDo: aBlock
"Repeatly value aBlock with a single copy of the receiver. Reorder the copy
so that aBlock is presented all (self size factorial) possible permutations."

"(1 to: 4) permutationsDo: [:each | Transcript cr; show: each printString]"

self shallowCopy permutationsStartingAt: 1 do: aBlock

SequenceableCollection >> permutationsStartingAt: anInteger do: aBlock
"#(1 2 3 4) permutationsDo: [:each | Transcript cr; show: each printString]"

anInteger > self size ifTrue: [^self].
anInteger = self size ifTrue: [^aBlock value: self].
anInteger to: self size do:
[:i | self swap: anInteger with: i.
self permutationsStartingAt: anInteger + 1 do: aBlock.
self swap: anInteger with: i]


つまり、animals に対して swap:with: で前述の処理(行・列要素のスワップの連動)を行なうようなんとか多態させることさえできれば、permutationsDo: を使って手を抜くという目的は果たせそうです。

とはいえ animals を permutationsDo: するためだけに swap:with: を書き換えてしまうのは、何か違う気がするので(というか、ダメ。ゼッタイ。w)、assureUniClass してインスタンス特異的クラスを作成し、インスタンス特異的メソッドとして swap:with: を再定義することにしました。


…というような腑抜けた方針で書いたのが、このコードです。



| animals cages ans |

animals := {
   'ライオン' -> #(0 2 6 4 6 2 4 4 2 4).
   'ワニ' -> #(2 0 4 2 2 2 2 2 2 6).
   'ニシキヘビ' -> #(6 4 0 2 6 8 8 6 4 8).
   'オオカミ' -> #(4 2 2 0 4 2 6 6 2 6).
   'トラ' -> #(6 2 6 4 0 2 4 4 2 4).
   'スイギュウ' -> #(2 2 8 2 2 0 6 6 6 8).
   'サイ' -> #(4 2 8 6 4 6 0 6 6 4).
   'カバ' -> #(4 2 8 6 4 6 6 0 6 6).
   'インパラ' -> #(2 2 4 2 2 6 6 6 0 6).
   'ゾウ' -> #(4 6 8 6 4 8 4 6 6 0)}.

cages := #(
   (0 3 4 5 8 10 9 6 2 4)
   (3 0 4 4 7 9 9 8 5 9)
   (4 4 0 2 4 7 5 4 4 8)
   (5 4 2 0 3 5 5 5 5 9)
   (8 7 4 3 0 3 5 6 8 12)
   (10 9 7 5 3 0 4 7 10 14)
   (9 9 5 5 5 4 0 3 8 11)
   (6 8 4 5 6 7 3 0 5 8)
   (2 5 4 5 8 10 8 5 0 4)
   (4 9 8 9 12 14 11 8 4 0)).

ans := Set new -> Float infinity.
animals assureUniClass class compile: 'swap: i with: j
   super swap: i with: j.
   self do: [:each | each value swap: i with: j]'.
animals permutationsDo: [:perm |
   | keys values sum |
   keys := perm collect: #key. "keys asString displayAt: 20@20."
   values := perm collect: #value.
   sum := (values * cages) sum sum.
   ans value = sum ifTrue: [ans key add: keys].
   ans value > sum ifTrue: [ans := (Set with: keys) -> sum]].
^ans

"=> a Set(
   an Array1('スイギュウ' 'インパラ' 'ニシキヘビ' 'カバ' 'サイ' 'オオカミ' 'トラ' 'ライオン' 'ゾウ' 'ワニ')
   an Array1('スイギュウ' 'インパラ' 'ニシキヘビ' 'カバ' 'サイ' 'オオカミ' 'ライオン' 'トラ' 'ゾウ' 'ワニ')
)->2160


その後よく考えたら、素直に書いた方がシンプルだし速かったでござるの巻。あと、パラメーターのコピペミスがあったので、結果と共に差し替えました。orz

| animals cages ans |

animals := #(
   (0 2 6 4 6 2 4 4 2 4)
   (2 0 4 2 2 2 2 2 2 6)
   (6 4 0 2 6 8 8 6 4 8)
   (4 2 2 0 4 2 6 6 2 6)
   (6 2 6 4 0 2 4 4 2 4)
   (2 2 8 2 2 0 6 6 6 8)
   (4 2 8 6 4 6 0 6 6 4)
   (4 2 8 6 4 6 6 0 6 6)
   (2 2 4 2 2 6 6 6 0 6)
   (4 6 8 6 4 8 4 6 6 0)).

cages := #(
   (0 3 4 5 8 10 9 6 2 4)
   (3 0 4 4 7 9 9 8 5 9)
   (4 4 0 2 4 7 5 4 4 8)
   (5 4 2 0 3 5 5 5 5 9)
   (8 7 4 3 0 3 5 6 8 12)
   (10 9 7 5 3 0 4 7 10 14)
   (9 9 5 5 5 4 0 3 8 11)
   (6 8 4 5 6 7 3 0 5 8)
   (2 5 4 5 8 10 8 5 0 4)
   (4 9 8 9 12 14 11 8 4 0)).

ans := Set new -> Float infinity.
(1 to: animals size) permutationsDo: [:perm |
   | sum |
   sum := 0.
   perm doWithIndex: [:pi :i |
      perm doWithIndex: [:pj :j |
         sum := ((animals at: pi) at: pj) * ((cages at: i) at: j) + sum]].
   ans value = sum ifTrue: [ans key add: perm copy].
   ans value > sum ifTrue: [ans := (Set with: perm copy) -> sum]].
ans

"=> a Set(#(6 9 3 8 7 4 5 1 10 2) #(6 9 3 8 7 4 1 5 10 2))->2160 "


さらに追記。

なんと出題の動物の相性のデータにも対称になっていないという誤りがあったみたいで、

| animals |
animals := #(
   (0 2 6 4 6 2 4 4 2 4)
   (2 0 4 2 2 2 2 2 2 6)
   (6 4 0 2 6 8 8 6 4 8)
   (4 2 2 0 4 2 6 6 2 6)
   (6 2 6 4 0 2 4 4 2 4)
   (2 2 8 2 2 0 6 6 6 8)
   (4 2 8 6 4 6 0 6 6 4)
   (4 2 8 6 4 6 6 0 6 6)
   (2 2 4 2 2 6 6 6 0 6)
   (4 6 8 6 4 8 4 6 6 0)).

animals - ((1 to: animals size) collect: [:idx | animals collect: [:each | each at: idx]])
=> #(
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 -2 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 2 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0)
   (0 0 0 0 0 0 0 0 0 0))

対称となっていない相性の値がそれぞれ 8 の場合と 6 の場合で計算し直すと、結果は次のようになりました。

8 => a Set(
   #(6 9 3 8 7 4 5 1 10 2)
   #(6 9 3 8 7 4 1 5 10 2)
)->2164
6 => a Set(
   #(6 9 3 7 1 5 4 8 10 2)
   #(6 9 3 7 5 1 4 8 10 2)
   #(6 9 3 8 7 4 1 5 10 2)
   #(6 9 3 8 7 4 5 1 10 2)
   #(6 9 7 8 4 1 5 3 10 2)
   #(6 9 7 8 4 5 1 3 10 2)
   #(6 9 8 7 4 1 5 3 10 2)
   #(6 9 8 7 4 5 1 3 10 2)
)->2156

[]「『出現確率1%のガチャを100回引いても,4割近くの人は全部はずれる。“本当の確率”を読み解いてみよう』を素直に解いてみる」をSqueak Smalltalk




「1 - 100回引いてハズす確率」ではなく、各回の当たる確率を積算して算出する計算はどうなるか、というお話にからめて、最後に添えられた Ruby 版で、Smalltalk でもおなじみの inject (Smalltalk では inject:into: )が使われていたので Squeak Smalltalk でも書いてみました。

(0 to: 99) inject: 0 into: [:r :n | r+((0.99 raisedTo: n)*0.01)] "=> 0.63396765872677 "

残念ながら Squeak には raisedTo: のエイリアスとして ** が用意されてないのと(Pharo にはあるらしい)、二項メッセージ式に優先順位がないせいで括弧が増えるのがアレですが、よく似ていますね。


なお、Smalltalk でも Squeak や Pharo に限れば、APL 譲り(…とういかワナビ?)の配列計算が使えるので、ちょっと趣を変えて同じようなことをこんなふうにも書くことができます。

((0.99 raisedTo: (0 to: 99)) * 0.01) sum "=> 0.6339676587267705 "

Squeak や Pharo の raisedTo: は、引数に配列を与えれば答えを配列で返すしくみになっています( raisedTo: の返値になぜか生じる丸め誤差が見苦しいので rounded しています)。

(3 raisedTo: (0 to: 4)) rounded "=> #(1 3 9 27 81) "

ではメッセージ raisedTo: 〜 のレシーバーが配列なら、配列の配列が返るかというとそうはならず、対応した各要素について累乗値が返ってきます。

#(3 4 5) raisedTo: #(0 1 2) "=> #(1 4 25) "

したがって、レシーバーと raisedTo: の引数の配列のサイズが違うとエラーになるので要注意です。

#(3 4 5 6) raisedTo: #(0 1 2) "=> Error: otherCollection must be the same size "

余談ですが、整数の累乗なのに配列だと Float に変換されてしまう謎も含め、なぜこのような振る舞いになるかというのは、Number>>#raisedTo: の定義をみると分かります。

Number >> raisedTo: aNumber 
"Answer the receiver raised to aNumber."

aNumber isInteger ifTrue: [
"Do the special case of integer power"
^ self raisedToInteger: aNumber].
aNumber isFraction ifTrue: [
"Special case for fraction power"
^ (self nthRoot: aNumber denominator) raisedToInteger: aNumber numerator ].
self < 0 ifTrue: [
^ ArithmeticError signal: 'Negative numbers can''t be raised to float powers.' ].
0 = aNumber ifTrue: [^ self class one]. "Special case of exponent=0"
1 = aNumber ifTrue: [^ self]. "Special case of exponent=1"
0 = self ifTrue: [ "Special case of self = 0"
aNumber < 0
ifTrue: [^ (ZeroDivide dividend: self) signal]
ifFalse: [^ self]].
^ (aNumber * self ln) exp "Otherwise use logarithms"


なお、レシーバーが配列の場合は、まず Collection>>#raisedTo: が呼ばれるので、レシーバーが整数の場合とは振る舞いが異なってきます。

Collection >> raisedTo: arg
^ arg adaptToCollection: self andSend: #raisedTo:

Collection >> adaptToCollection: rcvr andSend: selector
"If I am involved in arithmetic with another Collection, return a Collection of
the results of each element combined with the scalar in that expression."


rcvr isSequenceable & self isSequenceable ifFalse:
[self error: 'Only sequenceable collections may be combined arithmetically'].
^ rcvr with: self collect:
[:rcvrElement :myElement | rcvrElement perform: selector with: myElement]

 
2004 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 10 | 12 |
2013 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2014 | 01 | 02 | 05 | 07 | 08 | 09 | 10 | 11 |
2015 | 04 | 07 | 08 | 11 | 12 |
2016 | 02 | 03 | 06 | 07 |

最近のコメント

1. 06/25 sumim
2. 06/25 山田
3. 08/29 squeaker
4. 08/29 ardbeg1958
5. 10/16 umejava

最近のトラックバック

1. 05/25 プラグインレスでSVGを表示する「SIE」開発ブログ - メッセージをや...
2. 01/30 no_orz_no_life - Erlangとジャンケン
3. 12/31 檜山正幸のキマイラ飼育記 - J言語、だってぇー?
4. 09/04 Twitter / @atsushifx
5. 07/06 みねこあ - オブジェクト指向 と FizzBuzz

この日記のはてなブックマーク数
1583208