Smalltalkのtは小文字です

id:sumim:about

オブジェクト指向の“モヤッと”の正体を知りたくなったら、id:sumim:20080415:p1id:sumim:20040525:p1 がお役に立つかも…。


 

2014-05-27

[] Squeak Smalltalk で 配列を交互に分配




Squeak Smalltalk でならどう書くかなぁ…と、いろいろ考えてみたのですが、いまひとつしっくりくるものがないので、いくつか思いついた書き方をざっくばらんに晒しておきます。


お題は、#(x0 y0 x1 y1 x2 y2) を #( (x0 x1 x2) (y0 y1 y2) ) のように分配してまとめるというもの(元はリストなのですが Smalltalk なので配列に読み替えます)。


オーソドックスに #( (x0 y0) (x1 y1) (x2 y2) ) という中間生成物を介して作る方法だとこう書けます。

(#(x0 y0 x1 y1 x2 y2) groupsOf: 2 atATimeCollect: [:pair | pair])
   inject: #(() ()) into: [:sum :each |
      sum with: each collect: [:a :b | a, {b}]]  "=> #((x0 x1 x2) (y0 y1 y2)) "

こういうときは、入れ子の配列を二次元配列に見立てて、行と列を入れ替える #transpose とか #transposed みたいな節操のないメソッドが欲しくなります。

SequenceableCollection >> transposed
   | colns |
   colns := (self species new: self first size) collect: [:idx | self first species new].
   self do: [:each | colns := colns with: each collect: [:coln :elem | coln copyWith: elem]].
   ^colns
#('abc' 'def') transposed  "=> #('ad' 'be' 'cf') "
(#(x0 y0 x1 y1 x2 y2) groupsOf: 2 atATimeCollect: [:xs | xs]) transposed  "=> #((x0 x1 x2) (y0 y1 y2)) "

余談ですが、ここで引数を返すだけのブロック(無名関数)である [:xs | xs] をシンボル #yourself に置き換えられないのは、メソッド #groupsOf:atATimeCollect: が第二引数(通常はブロック)に対して numArgs をして 1 かそれ以外で条件分岐をしているというありがた迷惑な実装のためです。

SequenceableCollection  >> groupsOf: n atATimeCollect: aBlock 
   | passArray |
   passArray := aBlock numArgs = 1.
   ^(n to: self size by: n)
      collect: [:index | 
         | args |
         args := (self copyFrom: index - n + 1 to: index) asArray.
         passArray
            ifTrue: [aBlock value: args]
            ifFalse: [aBlock valueWithArguments: args]]

このため、ブロックはグループを配列としてまるごとを受け取る処理も、その要素を個別にブロック引数に受け取る処理としても書けるようになっていて便利ではあるのですが、あいにくブロックの代わりにシンボルを渡すと破綻します(なお、エラーにならないことからわかるようにシンボルも numArgs への応答はできているので、破綻しないように細工することは簡単です)。個人的にはこういう一見便利そうなカラクリにはなるほどっ!と思うよりはイラッ!とさせられることのほうが多いです。


ありがた迷惑といえば、最近の String>>#, の実装もそうで、引数を asString するようになってしまったため、文字列以外の物を文字列として結合するのには便利なのですが、

'abc', 123 "=> 'abc123' "

しかし、バイト列に見立てた配列を連結するとか以前の実装に依存したちょっと変態的なコードを書こうとしたときにそれが妨げられて脱力します。まあ難しいところです。

'abc', #(65 66 67)  "=> 'abc#(65 66 67)'。'abcABC' を期待 "

閑話休題。


Smalltalk の配列は要素の追加ができませんが、OrderedCollection や WriteStream のインスタンスを使えば破壊的に順次追加する処理を書け、さらに #atWrap: を使ってひとひねりできます。

| colns |
colns := #(() ()) collect: #asOrderedCollection.
#(x0 y0 x1 y1 x2 y2) doWithIndex: [:each :idx | (colns atWrap: idx) add: each].
^colns collect: #asArray  "=> #((x0 x1 x2) (y0 y1 y2)) "
| strms |
strms := #(() ()) collect: #writeStream.
#(x0 y0 x1 y1 x2 y2) doWithIndex: [:each :idx | (strms atWrap: idx) nextPut: each].
^strms collect: #contents  "=> #((x0 x1 x2) (y0 y1 y2)) "

もうひとひねりして、追加先のコレクションやストリームをローテートする書き方もできますが、これはちょっとやり過ぎでしょうね。^^;

| colns |
colns := #(() ()) asOrderedCollection collect: #asOrderedCollection.
#(x0 y0 x1 y1 x2 y2) do: [:each | (colns add: colns removeFirst) add: each].
^colns asArray collect: #asArray  "=> #((x0 x1 x2) (y0 y1 y2)) "

と、いいつつさらに悪のり。

| strm colns |
strm := #(x0 y0 x1 y1 x2 y2) readStream.
colns := #(() ()) asOrderedCollection collect: #asOrderedCollection.
[strm atEnd] whileFalse: [(colns add: colns removeFirst) add: strm next].
^colns asArray collect: #asArray  "=> #((x0 x1 x2) (y0 y1 y2)) "

目先を変えて Matrix のインスタンスを使う手もあります。が、Array2D の代替として導入されたわりに、いまいち頼りにならない Matrix のことなので、これを使って書ける処理もそれなりです。

| mat |
mat := Matrix rows: 3 columns: 2 contents: #(x0 y0 x1 y1 x2 y2).
^(1 to: mat columnCount) collect: [:idx | mat atColumn: idx]   "=> #((x0 x1 x2) (y0 y1 y2)) "

総じて、スマートに書くことができず残念な結果に終わりましたが、いろいろと考えたり考えさせられたりして楽しかったのでよしとしましょう。

2014-02-06

[] 誰得かわからないSqueak Smalltalkを使った関数型プログラミングっぽい話



当初、誰向けかわからないCommon Lispでの関数型プログラミング入門とその未来 - 八発白中 に例にあがっているコードを Squeak Smalltalk ならどう書けるかというような、少しは有益なことを書こうかと思っていたら、のっけからネタに走ってしまって失敗しました。ごめんなさい。



▼関数の定義とコール(ネタ)

Smalltalk ではまずクラスを作り(その際、インスタンス変数などを宣言し)メソッドを登録することで、そのクラスのインスタンスであるオブジェクトの振る舞いを決める、というやりかたをします。しかし「メソッド」などといったところで、クラスに属しているだけでただの関数です。

通常であればこの“関数”は、オブジェクトに「メッセージ」と称される「関数名(セレクターとも言う)と引数」を組にした情報*1を送ることで動的にコールされますが、もし静的コール*2もできればもっと普通に関数として運用可能なはずです。

処理系ごとに方法が異なりますが(可不可も含め)、実は Smalltalk でも関数の静的コールは可能だったりします。たとえば Squeak Smalltalk であれば、メソッドの定義(とクラスへの登録)とその実体の取得、静的コールはそれぞれ次のように書いて実行できます。

"関数の定義とクラスへの登録 = 関数を定義したいクラスにコード文字列をコンパイルさせる"
Number compile: 'foo ^self' "<= レシーバーを返す関数 #foo を定義し、仮に Number に登録 ".

"関数の取得"
(Number >> #foo) "=> コンパイルされた関数の実体の取得。なお #〜 はシンボルのリテラル式 ".

"関数の静的なコール"
(Number >> #foo) valueWithReceiver: 'string' arguments: #() "=> #foo を静的にコール ".

静的なコールなのでいわゆるオブジェクト指向プログラミングの文脈からは外れ、クラス(この場合Number クラス)は単なる関数の置き場所に過ぎません。実際、Number とは縁もゆかりもない文字列である 'string' をレシーバー(もちろんこの呼び名も今や意味をなしておらず、「第一引数に」と読み替えてよい)にして #foo メソッドをコールできている点に注目してください。お手元に処理系があれば、上の最初の式を評価する(alt/cmd + d か、右クリックメニューから do it )かクラスブラウザを使って Number>>#foo を定義後、最後の式を評価(alt/cmd + p か、右クリックメニューから print it )すると、レシーバーとして与えた 'string'(実行コンテキスト内では self )がちゃんと返ってきます。

もし 'string' に対して foo というメッセージを送って、#foo を通常通り動的にコールしようとしても、Smalltalk で唯一のエラーともいえる「そんなメッセージ投げられても、何すりゃいいのかわかんねーぞ!」エラーを吐きます。そもそも 'string' のクラスやそのスーパークラス群は Number にある関数 #foo のことなど何一つ知るはずもないので、当たり前といえば当たり前ですね。

'string' foo  "=> MessageNotUnderstood: ByteString>>foo "

試しに階乗を求める #fact も定義してみましょう。どうせ静的にコールするため、関数の置き場所はどこでもよいので、今度は String に登録してみます。

String compile: 'fact
   self < 2 ifTrue: [^self].
   ^self * (String >> #fact valueWithReceiver: self-1 arguments: #())'.
(String >> #fact) valueWithReceiver: 10 arguments: #().  "=> 3628800 "

ちゃんと答えが返ってきますね。ただ、ここまで書いていまさらですが、関数のコールに valueWithReceiver: 10 arguments: #(). とかいちいち長たらしくていけません。



▼関数の定義とコール(仕切りなおし)

そんなわけもあってかなくてか(←ないです)、Smalltalk では通常、関数を単体で定義するにはブロックという無名関数オブジェクトを用います。定義はゼロ個以上の式を [ ] で括るだけです。名前を付けたいときは、別途、変数を宣言してそれを束縛してください。

| func |
func := [:a :b | a + b].
func value: 3 value: 4  "=> 7 "

引数がなければそのまま関数本体を記述し、引数があるときは「:引数名」を列挙して最後に | で区切って関数本体の記述を続けます。

引数のない関数では「value」を、引数がある関数では「value: 引数 」を引数の数だけ繰り返して構成したメッセージを関数に送ることでコールできます*3。value とか value: 3 value: 4 とか通常の言語の関数コールの記述より長たらしくていけませんが、(Integer >> #+) valueWithReceiver: 3 arguments: #(4) とかいちいち書くよりは少しはマシなので我慢しましょう。


再帰も書けます。が、再帰的呼び出しのために変数名が未定義であると評価するたびにコンパイラに指摘されるのがウザイです。

| fact |
fact := [:n | n < 2 ifTrue: [n] ifFalse: [n * (fact value: n-1)]].
   "=> fact appears to be undefined at this point Proceed anyway? "
fact value: 10  "=> yes を選べば、普通に 3628800 を返してくる "

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


表示されたポップアップでメニューで yes を選べば、警告を無視して(他にエラーがなければ)コンパイルは完了し、普通に走らせることはできるのですが、毎回それをするのは面倒なので、再帰関数を定義したいときは、いったん関数名にしたい変数を nil などに明示的に束縛しておき、直後に改めて関数本体を再代入するとよいようです。

| fact |
fact := nil.
fact := [:n | n < 2 ifTrue: [n] ifFalse: [n * (fact value: n-1)]].
fact value: 10  "=> 3628800 "

なお、末尾再帰最適化とかはないです。が、コールスタックはメモリの許す限り積めるので、メモリを食いつぶしたな…と気付く時点(メモリ不足のアラートが出るか、VM ごと落ちる)で、きっとそれはなにか意図しないことが起こっているか、意図したとおりに動いていたとしても現実的な時間では終わらないコードであることが多いです。


ブロックの返値としては最後の式の結果が使われます。メソッドと違い、ブロックでは ^ を用いて関数の途中で抜けることができません。オーソドックスには、この階乗の例のように条件分岐などをネストする必要があります。

Squeak Smalltalk や VisualWorks(Cincom Smalltalk)では BlockClosure>>#valueWithExit が使えるので、この手のカラクリを活用するのもよいでしょう。条件分岐だけでは対応しにくいネストしたループ内からの脱出とかも可能です。ただデフォルトの #valueWithExit の定義のままでは値を持って抜けることができないので、返値を持って回る変数を用意するなどの工夫が必要です。

| func |
func := [:stop |
   | result |
   [:exit |
      1 to: Float infinity do: [:m |
         m = stop ifTrue: [result := m. exit value]
      ]
   ] valueWithExit.
   result].
func value: 100 "=> 100 "


高階関数

よくある map の役割は Smalltalk では #collect: というメソッドが担当します。

#(1 2 3 4 5) collect: [:x | x + 1]  "=> #(2 3 4 5 6) "

[:x | x + 1] は無名関数でこれは第一級オブジェクトなので、変数に代入したり、あらためてそれを関数コール時に引数として渡すこともできます。*4

| plusOne |
plusOne := [:x | x + 1].
#(1 2 3 4 5) collect: plusOne  "=> #(2 3 4 5 6) "

さらに Squeak や Pharo では、単項メッセージのセレクター(シンボル)を渡すこともできます。

3 squared  "=> 9 "
#(1 2 3 4 5) collect: #squared  "=> #(1 4 9 16 25) "

内部的にはシンボルを同名の単項メッセージとして各要素に送ってその返値を得ているだけなので、Lisp などでシャープクオートマクロを使って関数を得ているのとは意味が違いますが、見た目はそれっぽくてかっこいいです。


あと、高階関数の話からは逸れてしまいますが、Squeak や Pharo の場合さらに、たとえば #squared のように Collection に定義されているメソッドならそのまま配列などのコレクションオブジェクトに対してメッセージとして送信、同名メソッドを動的にコールすることも可能です。

#(1 2 3 4 5) squared  "=> #(1 4 9 16 25) "

参考まで、同様のことができるメソッド名(セレクター)を列挙するコードとその結果を示します。

#('math functions' 'arithmetic')
   inject: #() into: [:acc :cat | acc, (Collection allMethodsInCategory: cat)]
#(abs arcCos arcSin arcTan average ceiling cos degreeCos degreeSin exp floor 
ln log max median min minMax negated range reciprocal roundTo: rounded sign 
sin sqrt squared sum tan truncated * + - / // \\ raisedTo:)

Smalltalk では reduce は #inject:into: を使います。

#(1 2 3) inject: 0 into: [:sum :x | sum + x]  "=> 6 "

もちろんこの場合は #+ でもOKです。

#(1 2 3) inject: 0 into: #+  "=> 6 "

最近のバージョンの Squeak や Pharo には、inject: キーワードの引数として初期値を与える代わりに、レシーバーの第一要素を用いるバリエーションとして #reduce: が用意されています。

#(1 2 3) reduce: #+  "=> 6 "

これまた余談ですが、Squeak や Pharo には Collection>>#sum があるので reduce: #+ に限っては無用です。

#(1 2 3) sum "=> 6 "
{Color red. Color green. Color blue} sum "=> Color white "


無名関数や #reduce: を(あえて)使って sum や factorial を定義すると例えばこんなふうに書けます。

| sum |
sum := [:xs | xs reduce: #+].
sum value: #(1 2 3 4). "=> 10 "
sum value: (1 to: 100). "=> 5050 "
sum value: {Color cyan. Color magenta. Color yellow}. "=> Color white "
| factorial |
factorial := [:x | (1 to: x) reduce: #*].
factorial value: 10. "=> 3628800 "

ところで Common Lisp の mapcar は複数のリストをとってこんなことができます。

(mapcar #'list '(1 2 3) '(a b c) '(松 竹 梅))  ;=> ((1 A 松) (2 B 竹) (3 C 梅))

が、Smalltalk の #collect: はこうした凝ったことはできません。そもそも、Smalltalk には文法の制約から可変長引数というしくみ自体がありません。

もはや関数型ぜんぜん関係なくなっちゃいますが、上の Common Lisp のコードの動きを想像して Squeak や Pharo で手続き的に書くとこうなります。

(#((1 2 3) (a b c) (松 竹 梅)) collect: #readStream) in: [:ss | ss collect: [:x | ss collect: #next]]
=> #(#(1 #a #'松') #(2 #b #'竹') #(3 #c #'梅')) 


▼関数合成

| compose plusOne |
compose :=[:fs | fs reduce: [:g :f | [:arg | f value: (g value: arg)]]].
plusOne := [:x | x + 1].
#(1 2 3 4 5) collect: (compose value: {plusOne. #sin})
=> #(0.909297426825682 0.1411200080598672 -0.756802495307928 -0.958924274663138 -0.279415498198926)



▼conjoin & disjoin

| cojoin |
cojoin := [:fs | fs reduce: [:g :f | [:arg | (f value: arg) and: [g value: arg]]]].
(cojoin value: #(isZero isInteger)) value: 0  "=> true ".
(cojoin value: #(isZero isInteger)) value: 0.0  "=> false ".
| disjoin positive |
disjoin := [:fs | fs reduce: [:g :f | [:arg | (f value: arg) or: [g value: arg]]]].
positive := [:x | x > 0].
(disjoin value: {positive. #negative}) value: 100  "=> true ".
(disjoin value: {positive. #negative}) value: 0  "=> false ".
| complement |
complement := [:f | [:x | (f value: x) not]].
(complement value: #isZero) value: 0  "=> false ".


▼まとめ

  • Smalltalk のメソッドは、通常は「メッセージ」を介して動的にコールされるが、静的にもコールできる
  • Smalltalk のメソッドの実体を手繰って静的にコールすれば普通に関数として使えなくはないが、いろいろめんどくさい
  • Smalltalk で関数を使いたければ、メソッド実体より普通にブロック(無名関数)を使うほうがいい つーか、使うべき
  • Smalltalk のブロックリテラル [〜] は Commpn Lisp の (lambda 〜) より lambda と書かなくていいぶん短くすっきり
  • でも呼び出しは value とか value: 3 value: 4 とかの長ったらしいメッセージの送信なのでプラマイゼロ、むしろマイ
  • 素の Smalltalk のやり方にも、関数型プログラミングっぽい側面はある
  • Squeak や Pharo にある、ブロックの代わりにシンボルを渡せる機能は、さらに関数型っぽさを醸し出す
  • かなり gdgd になったが気にしない

*1:たとえば 3 + 4 なら 3 へ + 4 というメッセージの送信を意味し、+ がセレクタ、4 が引数です。4 between: 3 and: 5 なら between: 3 and: 5 がメッセージ、between:and: がセレクタ、3 と 5 が引数になります。メソッド名はコロンも含まれ、そのコロンの直後で分断してそこに引数を挿入してメッセージを構成するところが、通常であれば 4.between:and:(3,5) のように記述するであろう他の言語と Smalltalk のメッセージ式とが違うところです。

*2:ここでは呼びたいメソッドを定め、ずばりそれをコールするという意味で使っています。

*3:内部的には、#value: #value:value: #value:value:value: はそれぞれ独立した別個のメソッドとして定義されています。#value:value:value:value: まで用意されていて、それ以上の引数が必要な場合は #valueWithArguments: を使い、引数は配列の要素として渡します。

*4:ただし条件分岐(たとえば #ifTrue:ifFalse:)など一部のメソッドはコンパイル時にインライン展開されるため、変数を介してブロックを間接的に渡すことができないこともあります。

2014-02-01

[] 1からnまでの配列をシャッフルして偶然昇順に戻るまでをシミュレート

358 :デフォルトの名無しさん:2014/01/31(金) 05:31:38.44
まず、ユーザに1以上30以下の整数をひとつ、入力してもらう。これをnとする。
1〜nの整数を並べる。これをLとする。
※Lをシャッフルする。
Lが偶然にも大きいものから小さいものへの順に並んでいたら、何回シャッフルしたかをユーザに伝え、終了する。
順に並んでいなければ※に戻り、成功するまで何度でも繰り返す。

これをグラフィカルにプログラミングすると、どうなるん?

no title

スレ主の趣旨には沿わないかもしれませんが、スクラッチの勉強にちょうどよさげなお題だったので書いてみました。



参考まで、Squeak Smalltalk だとこんな感じに書けます。

| n L k |
n := (FillInTheBlank request: '30以下の整数') asInteger ifNil: [3].
(n between: 1 and: 30) ifFalse: [^self].
L := (1 to: n) asArray.
k := 0.
[k := k+1. L shuffled isSorted] whileFalse.
'1から{1}まで、{2}回シャッフルで昇順に' format: {n. k}
"=> '1から8まで、46285回シャッフルで昇順に' "
=> '1から8まで、46285回シャッフルで昇順に'

2014-01-30

[] SequenceableCollection >> #after: を使ってジャンケンの勝敗判定



Prologの話をしよう - CROSS2012・言語CROSS のコードが簡潔でよかったので、Squeak Smalltalk でも何か珍しい方法で簡潔に書けないかと考えて、いぜん見かけて使う機会がその後なかった #after:(実際には #after:ifAbsent: )というのを思い出したので、それを使って書いてみる試み。

| jankens player1 player2 judge |
jankens := #('グー' 'チョキ' 'パー').
player1 := jankens atRandom.
player2 := jankens atRandom.
judge := player1 = player2 ifTrue: ['引き分け'] ifFalse: [
   ((player2 = (jankens after: player1 ifAbsent: [jankens first]))
      ifTrue: [player1] ifFalse: [player2]
   ), 'の勝ち'].
^{player1. player2} -> judge  "=> #('グー' 'パー')->'パーの勝ち' "

#after: にも #atWrap: 的な機能が欲しいですね。ちなみに #atWrap: で上のを書くとこんな感じになります。

| jankens player1 player2 judge |
jankens := #('グー' 'チョキ' 'パー').
player1 := jankens atRandom.
player2 := jankens atRandom.
judge := player1 = player2 ifTrue: ['引き分け'] ifFalse: [
   ((player2 = (jankens atWrap: (jankens indexOf: player1) + 1))
      ifTrue: [player1] ifFalse: [player2]
   ), 'の勝ち'].
^{player1. player2} -> judge  "=> #('チョキ' 'グー')->'グーの勝ち' "

いや。#atWrap: みたいな動きは、むしろ通常の #at: に対してコレクションのほうが「環状である」という性質として持つほうが好ましいか。

2013-12-31

[] 「1から10までの整数を、ランダムに100個生成して、その標準偏差を求める」プログラムを Squeak Smalltalk


昔のCマガジンに、プログラミング言語Jを用いると「1から10までの整数を、ランダムに100個生成して、その標準偏差を求める」プログラムは

0.5^~(+/%#)2^~(-(+/%#))1+?100$10

という1行だけで計算できることが誇らしげに書いてあった記事がありました。ループも変数も使っていないとのこと。

プログラミング言語J - 丸井綜研

ループは使わなくても大丈夫ですが、変数は使わないとダメでした。^^;

((1 to: 100) collect: [:idx | 10 atRandom]) in: [:Xs | (Xs - Xs average) squared average sqrt]

もっとも Smalltalk 処理系の中でも Squeak/Pharo に特徴的な、配列などをそのまま演算できる機能は APL の影響を受けてのことなので、できて当たり前ではありますね。

 
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 |

最近のコメント

1. 08/07 sumim
2. 08/07 squeaker
3. 06/26 squeaker
4. 06/26 sumim
5. 06/26 infoarchitect

最近のトラックバック

1. 01/30 no_orz_no_life - Erlangとジャンケン
2. 12/31 檜山正幸のキマイラ飼育記 - J言語、だってぇー?
3. 09/04 Twitter / @atsushifx
4. 07/06 みねこあ - オブジェクト指向 と FizzBuzz
5. 08/07 大島芳樹のカリフォルニア日記 - ランダムな文字列の中に隠された...

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