Smalltalk-72で学ぶOOPの原点:組み込みの構造化エディター(ソース概説)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


組み込みの構造化エディター(定義済みアクションやクラスの編集)の続き)

“The Smalltalk(-72) Editor”

edit(やfixアクションでも同じく)で起動できるSmalltalk-72に組み込みの構造化エディター(ブートストラップ時=ALLDEFSに定義される──)は、いくつかの基本的なAPIコールこそしていますが、全体としてわずか100行ちょっとで実装されています。Smalltalk-72の記述力、恐るべし…ですね。もっとも、そのぶん難読ではありますが…^^;(それでも、個人的印象ではもっとずっと記述力は高いものの、かなり気合いを入れないと読むことができないAPL/Jはもとより、頭の中でリスト処理をしないといけないLisp族よりは幾分読みやすいかな…と)。

ソースコードをざっと眺めてみましょう。

http://squab.no-ip.com/collab/uploads/st72editor-overview.png
The Smalltalk(-72) Editorのソースコード

Rubyのinstance_eval相当の「's」に続く)

Smalltalk-72で学ぶOOPの原点:組み込みの構造化エディター(定義済みアクションやクラスの編集)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


組み込みの構造化エディター(Add、Insert、Replace、Delete、Move、Up、Push)の続き)

addtoアクションで定義済みアクションやクラスにコード片を追加する

たとえばto foo (isnew)のように、最小限の定義しかしていないクラスfooがあったとして、これだとfooインスタンスはクラスを問い合わせるis?というメッセージには答えられない(正確には、スルーされたメッセージの先頭のisがアクションとしてアクティベートされ、それが続く?メッセージを受け取ってデフォルト値であるかのように「untyped」を返す偽装をしている)という話はすでにしました

f:id:sumim:20191215230946p:plain
最少構成のクラスに属するオブジェクトはクラスの問い合わせに正しく応答できない

このクラスfooインスタンス(上の例ではxに代入)に正しく自分が属するクラスを返させるためには、fooのメソッドに∢is ⇒(ISIT eval) というパターンマッチを追加すればよいわけですが、これはaddtoアクションを使うことで簡単にできます。

f:id:sumim:20191215231848p:plain
「addto」でクラス「foo」にパターンマッチを追加

コードを( )で括って与えるtoアクションと違い、addtoアクションで追加したいコード片は配列リテラル(頭にを追加。余談ですが、より厳密にはこれもというアクションへのメッセージ送信で実現されている──)なので注意してください。

本格的にクラスやアクションのコードを編集するならeditアクション

REPLのfixと同じように、構造化エディターを使ってクラスやアクションのコードも編集できます。

f:id:sumim:20191215232835p:plain
「edit」アクションにコードを編集したいクラスをメッセージとして送ると…

f:id:sumim:20191215232940p:plain
構造化エディターが起動し、「fix」と同様にコードを編集できる

使い方は前回紹介したfixのそれとまったく同じです。

組み込みの構造化エディター(ソース概説)に続く)

Smalltalk-72で学ぶOOPの原点:組み込みの構造化エディター(Add、Insert、Replace、Delete、Move、Up、Push)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


組み込みの構造化エディター(Enter、Leave、Exit)の続き)

「Add」「Insert」「Replace」「Delete」「Move」

「Add」は最後に要素を追加するときに使います。「Add」をクリックするか、編集中の要素のどれかを2度クリックすると(うまくマウスクリックを受け付けると)「Add」が反転表示になり、プロンプト(アルトを模したアイコン)が現れます。

f:id:sumim:20191214234635p:plain
「Add」モード

この状態で追加したい要素をタイプして入力しdo-it(\キー)を押すと記入した要素を最後に追加されます。

f:id:sumim:20191214235024p:plain
追加したい「+ 6」をタイプして入力してdo-it(\キー)

f:id:sumim:20191214235213p:plain
「+ 6」が追加される

「Insert」は指定した位置への要素の挿入です。「Insert」を押して反転させた状態で、要素を挿入したい場所の要素をクリックして指定すると「Add」と同様にプロンプトが現れるので挿入したい要素をタイプして入力し、do-it(\キー)します。

f:id:sumim:20191214235643p:plain
「Insert」→クリックで挿入位置指定→挿入したい要素をタイプして入力→do-it(「\」キー)

f:id:sumim:20191214235912p:plain
指定した場所に「+ 5」が挿入される

「Replace」は指定した範囲の要素の入れ替えです。「Replace」を押して反転させた状態で、入れ替えたい範囲の最初の要素をクリック(要素の上半分が白黒反転)したあと、続けて入れ替えたい範囲の最後の要素をクリック(要素の下半分が白黒反転)してから、プロンプトで入れ替える要素を入力してdo-it(\キー)します。入れ替える要素はひとつでも構わなくて、その場合は同じ要素を二度クリックします。

f:id:sumim:20191215001039p:plain
「Replace」→範囲を二度クリックして指定→入れ替えたい要素を入力してdo-it(「\」キー)

f:id:sumim:20191215001254p:plain
「+ 5」が「* 7」に置き換わる

「Delete」は指定した範囲の要素の削除です。「Delete」を押して反転させた状態で、削除したい範囲の最初の要素をクリック(要素の上半分が白黒反転)、続けて最後の要素をクリック(下半分が白黒反転)すると、指定された範囲の要素が削除されます。

f:id:sumim:20191215001652p:plain
「Delete」→削除したい範囲をクリックして指定

f:id:sumim:20191215001807p:plain
「+ 6」が削除される

「Move」は指定した範囲の要素を任意の位置に移動します。

f:id:sumim:20191215002128p:plain
「Move」→動かしたい範囲を指定(上半分、下半分が白黒反転)→移動先をクリック

f:id:sumim:20191215002313p:plain
「+」の位置に「* 7」が移動

「Push」と「Up」

「Push」と「Up」は、括弧でくくる(新しい配列にする)のと、括弧(ネストした配列)から取り出す操作です。

「Push」を選択して範囲を指定すると、指定した範囲の要素がくくられて表示は() になります。「Enter」して要素を確認したり「Leave」で元に表示に踊れます。

f:id:sumim:20191215003018p:plain
「Push」→範囲を指定すると要素がくくられる

f:id:sumim:20191215003143p:plain
くくられた要素群は「()」表示に変わる

「Up」はネストした配列の中身を取り出す操作です。ネストした配列がひとつしか無い場合は「Up」を押すと直ちに展開されます。複数ある場合は「Up」を反転させた状態で展開したい配列を選びます。

f:id:sumim:20191215005406p:plain
ネストした配列がひとつしかないときは「Up」をクリックするとただちにそれが展開される

f:id:sumim:20191215005242p:plain
くくられていた「3 * 7」が展開される

「Exit」で編集を終了し、編集後の式が評価されます。ちなみにSmalltalk-72の演算は右結合なので3 * 7 + 43 * (7 + 4)となり、その結果の33とその後に改行(disp ← 13.)が出力されます。

f:id:sumim:20191215005804p:plain
「Exit」で編集を終了し評価結果が返される

f:id:sumim:20191215005921p:plain
結果の「33」と改行(disp ← 13.)が出力される

組み込みの構造化エディター(定義済みアクションやクラスの編集)に続く)

Smalltalk-72で学ぶOOPの原点:組み込みの構造化エディター(Enter、Leave、Exit)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


念願のクリック、's(instance_evalで使う記号)、⦂(オープンコロン)を手に入れた!の続き)

REPLの便利機能(redofix)とコード/配列エディター

Smalltalk-72のREPLでは、redoで直前に評価した式を、またredo <正数>と数値を付すことで指定した数だけ前に評価した式の再評価を行えます。

f:id:sumim:20191213125038p:plain
直前に評価した式を「redo

f:id:sumim:20191213125108p:plain
直前の評価結果に「redo」による再評価結果が追記される

一方、fixは指定した式の一部を変更して評価できます。このとき、コード(実体は配列)専用のエディターが起動します。redo 同様にどのくらい遡るかも指定できます。

f:id:sumim:20191213125609p:plain
「fix」で直前に評価した式の一部を変更して再評価する

f:id:sumim:20191213125656p:plain
組み込みのコード/配列エディターが起動

このコード/配列エディターがマウスクリック必須なので、これまでfixやアクション/クラス編集用のeditなどが使用できずたいへん不便でしたが、今後はだいぶREPLの使い勝手も改善されそうです。ただし前回紹介したちょい直し版を使っていない場合、残念ながらこのエディターでの操作はできません。escキーを押してそのまま終了してください。

「Enter」「Leave」「Exit」

このエディターは配列の要素の編集が基本的な機能です。要素内に配列があるときは()トークンに分断されないよう、実際は1文字の記号)と省略されて表示され、編集中はひとつの要素として扱われます。

配列(もしくはコード)の中にネストした配列がひとつしかないときは「Enter」コマンドをクリックするとその中身を見ることができます。複数あるときは、「Enter」コマンドがハイライトした状態で中身を見たい配列(()で表示されている)をクリックします。

f:id:sumim:20191213150459p:plain
「Enter」でネストした配列の内容を表示

元の表示に戻るには「Leave」を使います。戻る画面がないときはエディターを抜けて編集中のコードを再評価します。「Exit」はどの階層からでもエディターを直ちに抜けてやはり編集中の式を再評価します。

組み込みの構造化エディター(Add、Insert、Replace、Delete、Move、Up、Push)へ続く)

Smalltalk-72で学ぶOOPの原点:念願のクリック、's(Rubyのinstance_evalライクに使う記号)、⦂(オープンコロン)を手に入れた!

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点のカレンダー | Advent Calendar 2019 - Qiita


知らないメッセージはスルーする?の続き)

LivelyWeb版Smalltalk-72処理系のいくつかの不具合を解消してみた

1970年当時のバイナリーがそのまま動いているという点で、Webアプリ and/or 限定的なALTOエミュレーターであるがゆえの制約は少なからずあるものの、体験や学習に最適!とご紹介している件の処理系ですが、実は長らく解決できていなかった、しかしどうしても解消したい問題がいくつかありました。

ひとつはマウスクリックのイベントが拾えないこと。これが使えないと、マウスによる描画等のスクリプトが動かせないという残念さはありますが、それよりなにより開発環境として、便利な組み込みの構造化エディターが使えない(編集対象のトークンやメニューコマンドの選択のためのクリックに反応しないため──)という点で大きな障害になります。もちろん、構造化エディター無しでもコードの編集はできなくはないのですが、イントロスペクションを駆使したかなりの力業で、ちょっと気軽に触れて遊んでみたいだけの体験者には向きません。

二つ目はRubyinstance_evalに相当する機能を使うときの 's というグリフの文字、および、メッセージから評価せずにトークンを1つだけ取り込むアクションに使う(オープンコロン)が入力できないこと。これらはSmalltalk-72のコーディングには必須といってもよい記号文字なのですが、いろいろ問題があり入力できませんでした。前者はssなどの新しいメソッドとして追加登録(addtoアクションを利用)する、後者は:☞で同じことができるので代替するというワークラアラウンドは見つけてはいたのですが、それなりに面倒でした。

そんなわけで、ユーザーが自由に改変して保存もできるLivelyWebならではの特徴を活かし、このたび思い切ってこれらの不具合を解消する細工を処理系に入れてみました。本家を置き換える自信はなかったので、ユーザーフォルダー(ディレクトリー)のsumimALTO-Smalltalk-72.tweak.htmlでアクセスできるようにワールドの複製を作りました。

Chrome推奨です。以降は、このワールドをベースに進めていこうと思います。

追記:その後、本家 https://lively-web.org/users/Dan/ALTO-Smalltalk-72.html もいろいろ改良され、私も思いきって修正を入れてみたので、マウスクリックの処理、's の入力は本家でほぼ問題なく扱えます。Snippets ウインドウからの二回クリック(ダブルクリックにならないように、ゆっくり、二回)による、コードの転送などにも対応していて便利なのでぜひ本家をお使いください。なお、残念ながら Smalltalk Zoo からリンクされているインスタンスは権限が解放されておらず、修正を加えて保存することができませんでしたので、マウスクリックに関してはこちらは使用できないままです。ご注意ください。

追記 [2021-09-08]:残念ながら本家 https://lively-web.org/users/Dan/ALTO-Smalltalk-72.html が削除されて使えなくなってしまったようですので、当面は「LivelyWeb版Smalltalk-72について」にもお示しした勝手バックアップをご利用ください→https://lively-web.org/users/sumim/ALTO-Smalltalk-72.tweak2.html

追記 [2021-11-06]:削除されて使えなくなってしまっていた本家ですが復活しました。ただ、少々古いバージョンへのロールバックなのでマウスクリックが拾えないなどの諸々も戻っています。引き続き勝手バックアップをご利用ください→https://lively-web.org/users/sumim/ALTO-Smalltalk-72.tweak2.html

組み込みの構造化エディター(Enter、Leave、Exit)に続く)

Smalltalk-72で学ぶOOPの原点:知らないメッセージはスルーする?

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


継承が…ない!(「is?」の実装))からの続き)

知らないメッセージは基本スルーするSmalltalk-72

isパターン未定義時のis?の挙動の不思議はひとまず置いておくとして、Smalltalk-72でオブジェクトが知らない(パターン未定義の)メッセージを受け取ったときの挙動を確かめておきましょう。

ためしに3hogeを送ってみます。

f:id:sumim:20191211175711p:plain
「3」に「hoge」を送ってみる

すると、エラーになります。

f:id:sumim:20191211175827p:plain
エラーが出るが…

ただ「シンボル(hogeを指す)は値を持たない」というちょっと不思議なエラーになります。

ちなみに参考まで、今のSmalltalkでは「そんなメッセージ、知らない」がこういうときのエラーの基本です。

f:id:sumim:20191211180341p:plain
Pharo Smalltalkで「3」に「hoge」を送ったときのエラー

実は、Smalltalk-72では知らないメッセージは基本スルーする仕組みになっており、したがって、続くメッセージを新しい式の始まりと認識されます。つまり、続くメッセージの最初のトークンは次のメッセージ式のレシーバーになるので、hogeは変数として認識され、それに値が代入されていないという件のエラーになるわけです。

試しに、あらかじめhoge4を代入しておいてから改めて3 hoge を評価してみましょう。

f:id:sumim:20191211180735p:plain
あらかじめ「hoge」に「4」を代入して「3 hoge」を実行すると…

なるほど4が返ります(このとき3はREPLで複数の式を評価したときの一つ前の式と同じ扱いとなり、普通に破棄され結果=出力には影響を与えません)。

知らないメッセージは基本スルーというのは、悪名高きObjective-Cnilの挙動(もっとも彼はオブジェクトですらないので、知っているメッセージなど無く、すべてのメッセージをスルーするわけですが…)を彷彿とさせて、なんだかちょっと残念ですね。これについては、アラン・ケイのアイデアに忠実にした結果なのか、はたまたただ単純に実装の都合や運用の妙の類いに過ぎないのか、もう少し調べてみたいと思います。

isアクション

ということで、実はALLDEFS(ブートストラップコード)にはto is …で始まるisアクションが別に定義されていて、isメッセージに対応できないオブジェクトにis?等のメッセージが送信されたとしても、レシーバーがそれに応答する代わりにisアクションがコールされ(それに改めて?が送られることで)untypedを返すことができる、というのがこのカラクリの答でした。

f:id:sumim:20191211181311p:plain
「is」アクションの定義

念願のクリック、's(instance_evalで使う記号)、⦂(オープンコロン)を手に入れた!に続く)

Smalltalk-72で学ぶOOPの原点:継承が…ない!(「is?」の実装)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


継承が…ない!(クラス変数の活用)からの続き)

クラスを問い合わせるis?

継承がないということは、Objectに代表される“すべてのオブジェクトの振る舞いを決める基底クラス”もないわけで、つまり、クラスを問い合わせるメッセージis?のような基本操作にも、そのパターンマッチをいちいち定義しておかなければ応答がままならないことを意味します。

f:id:sumim:20191210104251p:plain
タートル「😄」、文字列「'abc'」、配列「☞(1 2 3)」は「is?」に応答できるが…

f:id:sumim:20191210120113p:plain
それは、これらのクラスで「is」のパターンマッチが明示されているから

多くのクラス(のインスタンス)がis?に応答できるのは、単純にisのパターンマッチが定義されているからなのですね。

ISITの正体

ではここでeval(評価)されているISITとは何者でしょうか? 定義はこんな感じです。

f:id:sumim:20191210121413p:plain
「ISIT」の定義

続くトークンが?(タイプして入力するときは~)なら、クラス名(TITLEプロパティ)を返し、そうでなければTITLEプロパティと続くトークン(評価せずに取り込む:☞)と等価ならそのまま、等価でなければfalseを返す…というコード片と同義の配列が、グローバル変数ISITに代入されたものとなっています。

運用時はこれにevalを送ることで評価できます。シェアしたいコード片(処理)を配列のままグローバル変数に保持し、各クラスでの記述を最小限にするという継承の無い辛みを軽減するもうひとつの工夫ですね。

f:id:sumim:20191210124028p:plain
「is」と「is?」に対する振る舞い

エラーにならないis?

クラスはisnewアクションを呼ぶアクションに過ぎないことは既に述べましたが、この最少構成であるクラス(仮にfooisのパターンマッチはあえて定義しない)のインスタンスを使ってis?にどんな応答をするか調べてみましょう。

f:id:sumim:20191210122501p:plain
パターンマッチが定義されていないのにエラーにならない「is?」

あれ? おかしいですね。fooとこそ返しませんが、untypedなどとそれっぽい答が返ってきます。これはいったいなぜでしょうか。

知らないメッセージはスルーする?に続く)