「『出現確率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]