Island Life

2018/10/03

Student-led conference

らむ太は小学校の4年生から、年数回ある学校での面談のうち1回は生徒主導型の三者面談(student-led conference, SLC)だったのだが、これはわりと一般的なようで中学校では基本的に全ての三者面談がSLCになるようだ。

SLCとは、生徒が学校でやったことについて自分が進行役となってレポートし、親と教師から質問を受ける形式。中学ではラップトップが支給されてるので、WeeblyでSLC用のwebpageを自分で作ってそれを見せながらのプレゼンだった。

まあ生徒主導と言っても、目標、達成したこと、改善すべき課題、など進行のテンプレは与えられてるし、教師が適宜助け船を出すのだけれど、自分の責任で準備して、他者からフィードバックを受けるというのは良い経験だと思う。

Tag: 生活

2018/08/30

中学校

らむ太は今月から7年生になった。 米国では小学校(elementary)が5年もしくは6年で、中学校(middle school)は6-8年生。 らむ太は6年制の小学校に通ったので、中学は2年間通うことになる。

で、今日はその中学校の授業紹介的なものに行ってきた。授業参観に相当するイベントだと 思うけれど、傍観者として授業を眺めるのではなく、子供の案内で親が授業を受ける。 もちろん完全な授業ではなく、 各コマ10分でどんなことをやってるのかを教師が紹介する形だけど、 なかなかおもしろかった。 (開催時刻も5:30pmからと、仕事を持つ親が参加しやすいようになっている。)

どの教科も議論のウェイトがかなり大きいようだ。講義形式のレクチャーはあっても 割合としては70分のコマ中10-15分程度で、 議題を与えられて小グループで議論したりグループワークして、 その後全体に発表しつつフィードバック、という感じらしい。

それと、各科目で大きな「問い」から入るのがおもしろかった。 まず大きなテーマとしてジェネリックな問いが提示されていて、 それに答えるには何が必要かという視点から議論が進む。 例えば数学なら、 「現実の問題を解決するのにどう数学を使ってゆくか」ってな問いがトップレベルのテーマとして あって、各授業では具体的な現実の問題(を単純化したもの)が与えられて、 グループワークで解を見つけてから、それが有効な解であることの説明、 その根拠は何か、等の発表になる。 この形式だと多分、日本の学校ほどに幅広いトピックをカバーする時間は無さそうに思うけど、 うちの子には教科書の例題をひたすらやるよりは合ってそうではある。 英語(国語)だと、例示された授業では 「著者は効果的に伝えるためにどんな技術を使っているか」 がまず問いに設定され、短篇を読んで、それについて議論して発表する、 というようなことをやっていた。 「技法を学ぶために読み、それを自分の表現に活用する」が重視されてるようだ。

なお、科目としては数学、英語、科学、社会、アート、体育があって、 残りの時間はプロジェクト (テーマを決めて調査研究) となっている。 プロジェクトの比重は大きく、ポートフォリオを作って 卒業前にはプレゼンとディフェンス(審査)があるというからなかなか本格的だ。 このへんの教程の組み方は学校によって大きく異なるが。

あと、ノートはそのコマで得られた知見を後から参照できる形になるように指導されている模様。 板書はないから、板書を写すという行為もない。 問題と議論と得られた解決のまとめを書いているのかな。 科目によっては実験ノート的な使い方もされているようだ。 これは小学校でもそうだったが、原則ノートは教室に置いておき、持ち帰らない。 (課題が終わらずに持ち帰る場合にノート持ち出しも許可されることはあるが、 可能なら持ち帰らずに、家で別紙に書いてきて貼り付けることになっている)。

Tag: 生活

2018/08/18

子供と読む児童文学 (6年生編)

昨年度にらむ太が学校の課題図書で読んでいたもののうち、印象に残ったものまとめ。 (以前のもの: 5年生編3-4年生編)


Island of the Blue Dolphins (Scott O'Dell)

19世紀初頭、主人公Karanaはカリフォルニア沖の小島に暮らしていたネイティブアメリカン。 小島の人々は漁で自給自足を送っていたが、 ある時外からやってきたラッコの狩猟者達と争いになり多くの死者を出す。 再び狩猟者達がやってくることを恐れた島民は、伝説に聞く海の向こうの土地へと移住するが、 Karanaは幼い弟と船に乗り遅れてしまう。それから子供だけのサバイバルが始まった。 実話を元にした物語。

サバイバル生活の描写が具体的でとてもおもしろく、 限られた舞台の中で出会いや別れも盛り込んであってストーリーの牽引力も強い。


The Sign of the Beaver (Elizabeth George Speare)

18世紀、メイン州にイギリス人入植者が領地を広げていた時代。

13歳のMattは父親と共に、何日も森の中を旅して新たに獲得した開墾地にたどり着く。 森を切り開き、最低限の小屋を建ててから、父親はMattに留守を任せて家族を迎えにゆく。 しかし父親は予定を過ぎても戻って来ない。 熊に小屋を荒らされ、見知らぬ入植者に騙されて銃を奪われ、 窮地に陥ったMattを助けたのはネイティブアメリカンの長老だった。 長老は、孫Atteanに英語を教えてやってくれとMattに依頼する。 Atteanやその一族と数が月を過ごすうち、Matt自身もネイティブアメリカンの生活について学ぶことになる。

単に侵略者vs被侵略者で割り切れない機微をうまく描いている。 Mattはネイティブインディアンの文化を理解してゆくが、 白人入植者とネイティブアメリカンが最終的には共存できないことを知らなければならない。

越境者となったMattがその後どうなったか、が気になる。


Riding Freedom (Pam Muñoz Ryan)

19世紀中頃の米国。「女性は女性らしく」という規範から外れた孤児の少女Charlotteが主人公。 当時の社会規範で将来に展望が持てなかったCharlotteは孤児院から逃げだし、 名前をCharleyと変えて男のふりをして馬の世話の仕事をみつける。 やがて優れた馬の乗り手となった彼女は、ゴールドラッシュに沸くカリフォルニアを目指す。 当時の女性には閉ざされていた道を切り開いてゆくのが 歴史ドラマとして非常に面白かった。映画でも見てみたい。

Charlotte (Charley) Parkhurstは実在の人物で、 米国で女性に参政権が与えられる半世紀前に 実際にカリフォルニアで(男性として)投票した人物。


Chains (Laurie Halse Anderson)

奴隷である13歳の黒人の少女から見た、アメリカ独立戦争時のニューヨーク。 史実をたくみに混ぜ込んで、戦争と差別を背景にした手に汗握る冒険譚として読ませる。

これについては別エントリを書いた: "Chains"と差別の構造


The Witch of Blackbird Pond (Elizabeth George Speare)

1687年、カリブ海の島の裕福な家に育った16歳のKatherine (Kit)は、 庇護者であった祖父が亡くなり年配の男性との結婚話が持ち上がったのを嫌って、 コネティカットに入植した叔母夫婦を頼って旅をする。 途中で溺れそうになった子供を泳いで助けるが、そこで警告される。 女性は泳がないものだ。泳ぐ女性は魔女とみなされると。

入植地はピューリタンの街で全てが質素。子供も大人も労働にあけくれ、 規律は厳しく、召使いのいたかつての暮らしと大違い。 Kitも慣れぬ労働に苦労する。

街外れに一人住む老婆Hannahはマサチューセッツを追われたクエーカー教徒だった。 外れもの同士、KitもHannahの家では自分を出してくつろげる。二人は親しくなる。 しかし村に病気が流行し二人は魔女の疑いをかけられて…

外部者としてのKitの視点から見るピューリタンの入植地の社会構造と格差、 迫害されていたクエーカー教徒、野蛮とみなされた船乗り達、などの描写が興味深い。 当時の社会というだけでなく、現代社会の構造にも様々な対応を見出せるし、 ストーリー的にも後半の展開は楽しめる。 だが女性が主人公だとノリが悪いらむ太であった。


My Brother Sam is Dead (Christopher Collier and James Lincoln Collier)

これも独立戦争の話。Tory党主導の街で酒場を経営する一家の子供Timが主人公。 兄Samは16歳になって理想主義に燃えて独立軍へと志願する。 兄への憧れと、生々しい戦争の現実の間に挟まれるTimの成長を描く。

独立戦争は当事者にとっては内戦だったんだなあ、というのがよくわかる。 普通に暮らしていた近所の人同士が、どちらにつくかで揉めなくてはならないし、 一方で軍事力というのはどちら側であっても個人の思いなんか軽く吹っ飛ばしてしまう 圧倒的な力だ。 そして、どちらに占領されようと、住人は生活していかなきゃならない。

物語自体はぽんぽん事件が起きて話が転がるというものではなく、 むしろじわりじわりと背後に大きな動きがあるはずなのにローカルに何も起きないことがじれったい、 のだがそういうのが実際に戦争が起きている現場のリアルなのかもしれない。 良作だとは思うが、らむ太は途中で飽きていた。


Jonny Tremain (Esther Forbes)

1773年、ボストンで銀細工職人の下働きをしていたJonnyは、 いずれ職人の孫娘と結婚して店を継ぐことを当然と受け入れていたが、 事故により手が不自由になり人生が暗転する。 やさぐれかかった時にWhig党シンパの地元新聞社に拾われ、 アメリカ独立戦争へと続く騒乱に関わってゆく。

当時の一般人視点から描からた独立戦争直前のボストンの状況が非常に興味深いんだけど、 実在の人物が多く出てきて彼らがどの立場で何をしたのか知ってないと話を追うのが難しいかも。 らむ太的には主人公のスパイ的活躍とか戦闘シーン以外はあまり興味を引かれなかったようだ。


The Watsons Go to Birmingham - 1963 (Christopher Paul Curtis)

1963年。ミシガン州フリントに住む黒人の少年が主人公。 家は貧しく学校では上級生にいじめられしかし家族は陽気。 問題児である兄を母親の実家に預けるために一家は車でアラバマまで旅をする。 時は市民権運動の真っ最中。アラバマ州では人種間対立が先鋭化していた。 そして日常を揺るがす事件が起きる。

事件が何かは献辞で分かっちゃうんだけど、 むしろ前半の日常パートが面白い。 大きな公立小学校でのタフな学校生活というのは、 小さな小学校に通ったらむ太にとってはピンと来なかったかもなあ。


The Giver (Lois Lowry)

SF作品は課題図書では始めてじゃないかな。 らむ太はディストピア的なSF作品に興味を持ち始めてて、これも結構楽しんだようだ。

人間が平和と調和のうちに暮らすようになった未来。 感情は争いをもたらすものとして教育と薬物によって抑制され、 過去の記録は迷いをもたらすものとして封印されている。 子供は産まれてすぐからコミュニティの管理下で教育され、 観察された適正に基づいて12歳になった時に職業を割り当てられる。

主人公Jonasも職業の割り当てを心待ちにしていたが、 そのセレモニーで彼に告げられたのはコミュニティに一人しかいない特殊な役割、 "Receiver of Memory"だった。 コミュニティで唯一、過去の記録にアクセスし、その重荷を引き受け、 委員会が判断しかねる問題に直面した時にアドバイスをする役割。 Jonasはそれから、Giverとなった先代のReceiver of Memoryの下で、 過去の人類の歴史、その痛みや苦しみ、あるいは歓喜をひとつづつ受け取ってゆく。

Huxleyの "Brave New World" からきつすぎる毒気を抜いた感じだけど、 テーマとなるヒトの抱える矛盾についてはより純粋に蒸留されてるように思う。

Tag:

2018/07/27

Gaucheの遅延シーケンス

TuringComplete.fm #28でGaucheの遅延シーケンスの話をしたのだけれど、 Lispユーザでない人はいきなりカーだのクダーだの言われて面食らったろうし、 音声ではClojureとclosureが区別できないし、かなりわかりにくかったかも。 Gaucheの遅延シーケンスについて、実装面から書いたことはなかった気がするので、 この機会に書いておく。

リスト

Lisp系言語のリストは通常、ポインタ二つを持つ構造体 (ペア、あるいはコンスセル) でもって実装される。例えばリスト (1 2 3) は内部的にはこんなふうになっている。

+---+---+    +---+---+    +---+---+
| * | *----->| * | *----->| * | ()|
+-|-+---+    +-|-+---+    +-|-+---+
  |            |            |
  v            v            v
  1            2            3

最後の()はリスト終端を表す。

慣習的に、このペアの最初のポインタをたどる操作をcar (カー)、 二つめのポインタをたどる操作をcdr (クダー) と呼ぶ。 Cに慣れてる人にはコードの方がわかりやすいかも:

struct ScmPairRec {
    ScmObj car;
    ScmObj cdr;
};

また、carとcdrのポインタを与えて新たなペアを作る操作をconsと呼ぶ。

オブジェクトのリストを受け渡して行くような処理はLisp系言語には頻出する。 例えば、「16進数の数値が1行に一つづつ書かれたファイルを読み込み、 その中に65535を越える数値があれば最初のものの位置を返す」という処理を考えよう。

ファイルdata.datを読み込んで文字列のリストにして返すのは次のとおり:

(file->string-list "data.dat")

文字列のリスト中の各文字列を16進数としてパーズし、数値のリストとして返す処理は次のとおり (ここで、(cut number->string <> 16)(lambda (x) (number->string x 16)) と同じ):

(map (cut number->string <> 16) 文字列のリスト)

条件に合う要素の位置を返す処理は次のとおり(find-indexgauche.sequenceモジュール):

(find-index (cut > <> 65535) 数値のリスト)

全部つなげると:

(find-index (cut > <> 65535)
            (map (cut number->string <> 16)
                 (file->string-list "data.dat")))

Gaucheではこんなふうにも書ける。逆向きのパイプラインと思うとわかりやすい。

($ find-index (cut > <> 65535)
   $ map (cut number->string <> 16)
   $ file->string-list "data.dat")

この考え方はわかりやすいが、ひとつ問題がある。Schemeでは引数は関数を呼び出す前に 完全に評価されるので、まずdata.datが全て読まれて文字列のリストになり、 次にそのリスト全体が数値のリストに変換される。もしその最初の方に条件を満たす要素があれば、 それ以降は読む必要が無いのだから、無駄だ。

遅延ストリーム

遅延ストリームは、要素の列について、それぞれの要素が必要になるまでその計算を遅らせる仕組みだ。 Gaucheではutil.streamモジュールで提供される。

リストlisの各要素に手続きprocを適用して、その結果をリストとして 返す関数map1は、素直に再帰で書くとこうなる (Gauche組み込みのmapと違って リストを1つしか取らないので、区別するためにmap1とする):

(define (map1 proc lis)
  (if (null? lis)
    '()
    (cons (proc (car lis))
          (map1 proc (cdr lis)))))

lisが空なら()を返し、そうでなければ 「(car lis)procを適用したもの」と、 「リストの残り(cdr lis)map1で処理したもの」をconsする。

次の手続きtwiceは「引数をprintしてから、それを2倍したものを返す」関数だ。 引数をprintするのは評価のタイミングを知るため。

(define (twice x)
  (print "x=" x)
  (* x 2))

リスト (1 2 3)twiceをマップして、結果を変数zに束縛してみよう。

gosh> (define z (map1 twice '(1 2 3)))
x=1
x=2
x=3
z

最後のzdefineが返したもの。それ以前にtwiceが3回、 要素それぞれに対して呼ばれたことがわかる。

結果のリストの0, 1, 2番目の要素を見ると、それぞれ元の要素の2倍になっている。

gosh> (list-ref z 0)
2
gosh> (list-ref z 1)
4
gosh> (list-ref z 2)
6

同じことを遅延ストリームでやってみる。 遅延ストリームを扱う手続きは、 リスト操作手続きを対応するストリーム操作手続きに置き換え、 全体をstream-delayでくくることで書ける。空リスト() にはstream-nullが対応する。 次が遅延ストリーム版のmap1だ。

(use util.stream)

(define (stream-map1 proc strm)
  (stream-delay
    (if (stream-null? strm)
      stream-null
      (stream-cons (proc (stream-car strm))
                   (stream-map1 proc (stream-cdr strm))))))

上でリストにやったのと同じように、ストリームに対してtwiceをマップしてみよう。

gosh> (define z (stream-map1 twice (stream 1 2 3)))
z
gosh> (stream-ref z 0)
x=1
2
gosh> (stream-ref z 1)
x=2
4
gosh> (stream-ref z 2)
x=3
6

今度は、definezを返した時点で、twiceは一度も呼ばれていない。 最初の要素を見に行った時に初めて1回呼ばれる。以降も、必要なだけしかtwiceは呼ばれない。

(追記2018/09/02 09:07:13 UTC: 原則として、副作用のある式を遅延評価中で使うべきではない。 どのタイミングで評価されるかわからないからだ。今回はそのタイミングを知りたいので 副作用を使ったが、例えばたまたまcurrent-output-portが置き換わっている環境で 評価が走れば想定したポートに出力が出てこない、なんてこともありえるので注意。)

ストリームは、概念的には式をくるんだものだ。最初に作られた時には、 与えられた式が評価されずにそのまま入っている。

  +-stream-------+
  |              |
  | (expression) |
  |              |
  +--------------+

値が必要になった時点で、まずストリームの中身が評価される(forceする、という)。 一旦forceされた式は、コンスセルになるか、空リストになる:

expressionがコンスセルになる場合

  +-stream-------+
  |  +---+---+   |
  |  |   | *--------> stream
  |  +---+---+   |
  +--------------+

expressionが空リストになる場合

  +-stream-------+
  |              |
  |      ()      |
  |              |
  +--------------+

forceされた結果はstream内に記憶されるので、expresssionは1度しか評価されない。 二度目以降のforceは何もしない。

stream-* 系の関数はだいたい、streamを受け取ったらそれをforceして、 内容を取り出して何かする、というものである。

遅延ストリームはこうして「値が必要になるまで計算しない」を実現できているのだが、 いちいちリストをstreamオブジェクトに入れる必要があるため、 アクセスするのに専用の関数が必要になる。 リスト用に書いていたコードをストリーム対応にするにはほぼ全面的な書き直しになってしまう。 これはきつい。

また、ストリームを作る際にexpressionの評価を遅延するために、 expressionをlambdaでくるんでクロージャにするのだけれど、 長大なリストの1要素ごとにクロージャを作成するのは、 普通のリストアクセスに比べると重すぎる。

遅延シーケンス

Clojureではリストに限らずいろいろな「並び」をシーケンスとして統一的に扱えるうえ、 シーケンスを生成する関数はデフォルトで遅延シーケンスを作る。 これがなかなか使い勝手が良かったので、Gaucheでも実現できないかと考えた。

要件は、

  • 通常のリスト関数が、変更することなく、遅延シーケンスも受け取れること。 つまりScheme側からみて、 どうしても必要な時以外はリストと遅延シーケンスの区別が見えないようにすること。
  • 生のリストより多少遅くなるのは仕方ないとしても、なるべくオーバヘッドを減らすこと。 任意のexpressionをいちいちラップするストリームのアプローチは重すぎる。

そこでGaucheでは、次のような「遅延ペア」を導入した。遅延ペアは、そのcar部は ペアと同様にリストの要素であるオブジェクトを指しているが、 cdr部は「ジェネレータ」を指している。

  +===+===+
  | * | *----> generator
  +=|=+===+
    |
    v
   obj

ジェネレータは引数を取らない手続きで、呼ばれる度に値を生成する。

遅延ペアをforceすると、まずジェネレータが評価される。 それがEOFオブジェクトを返した場合は終端とみなし、 遅延ペアはcdrが空リストな通常のペアに「化ける」。 そうでなければ、carに返されたオブジェクト、cdrに元のジェネレータを持つ 新たな遅延ペアが作られ、元の遅延ペアはcdrが新しい遅延ペアを指す通常のペアに化ける。

generatorがEOFを返した場合:
  +---+---+
  | * | ()|
  +-|-+---+
    |
    v
   obj

generatorがEOF以外のオブジェクト(obj2)を返した場合:

  +---+---+    +===+===+
  | * | *----->| * | *----> generator
  +-|-+---+    +=|=+===+
    |            |
    v            v
   obj          obj2

遅延ペアから通常のペアへの変化は透過的かつidentity preservingである。 透過的というのは、指している先のオブジェクトが遅延ペアか通常のペアかは 普通の方法では判別できないということ。 identity preservingとは、遅延ペアが通常のペアになった時に、 オブジェクトのidentity (Gaucheの場合は、オブジェクトのメモリ上のアドレス)が 変わらないということ。

この方法だと、リストの1要素あたりのオーバヘッドは、 ジェネレータ手続きの呼び出しと新たな遅延ペアのアロケートだけである。 通常のリストでも1要素あたりペアが一つアロケートされるから、後者はある程度相殺される。 そして、Gaucheでは手続き呼び出しは軽い。

ところでtcfmで話を出したのは、透過性をどう実現するかという点で、 Schemeレベルだけならcarやcdr手続きの中で遅延ペアかどうかをチェックして処理を変えるという 手もあるのだけれど、Cレベルではcarフィールドやcdrフィールドに直接アクセスしてる。 carやcdrにアクセスする機会は非常に多いので、そこにオーバヘッドを入れたくない。 ペアの要素にアクセスするためには、その前に必ずオブジェクトがペアであるかどうかを チェックしているはずなので、 じゃあペアかどうかのチェック手続きの時点で遅延ペアをforceしてしまえば良いじゃないか、 という発想である。

実際のコードでは次のようになっている:

#define SCM_PAIRP(obj)  \
    (SCM_HPTRP(obj)&&(SCM_HTAG(obj)!=7||Scm_PairP(SCM_OBJ(obj))))

前半が通常のペアかどうかの判定で、それを満たさなかった場合に Scm_PairP()を呼び出すが、その中で遅延ペアかどうかの判定と 必要ならforceするという処理を行っている。

(Scm_PairP() を呼び出す部分はオーバヘッドになる。 が、遅延ペアはもともと少ない上に一度チェックされたら通常のペアになっちゃうんだから、 SCM_PAIRPが呼ばれるペアのほとんどは通常のペアだ。 ペアでないものに対してSCM_PAIRPを読んだ場合はペナルティがあるが、 そのほとんどの場合、続く処理はランタイム型エラーを投げるものなので問題にならない。)

なお、遅延シーケンスはリスト関数にそのまま渡せるが、上で書いたmap1に渡すと map1自身がリストを全部読んでしまうので、そこで全てがforceされてしまう。 遅延シーケンスを「返す」関数は別に書かねばならない。 上の方で書いたmap1を素直に遅延シーケンス版にするとこうなる。conslconsになるだけ。

(define (lmap1 proc lis)
  (if (null? lis)
    '()
    (lcons (proc (car lis))
           (lmap1 proc (cdr lis)))))

(この定義は要素毎にクロージャを作るので最適ではない。詳しくは後述。)

さて、これまでの説明で気づいた方もいると思うが、Gaucheの遅延シーケンスでは 遅延ペアをforceした時点で「次の要素」が計算されてしまう。 つまり常に1つ先の要素が計算されることになる:

gosh> (define z (lmap1 twice '(1 2 3)))
x=1
z
gosh> (list-ref z 0)
x=2
2
gosh> (list-ref z 1)
x=3
4
gosh> (list-ref z 2)
6

たかだか一つ余分に評価するだけなので、 Gaucheとしては性能的なペナルティは気にしない方針。 但し、遅延シーケンスの計算に「自分自身のシーケンスの前の方」を参照している場合、 扱いが複雑になる場合がある。

(なお、一つ余分に計算しなければならないのは、Gaucheでは空リストが固定の定数なので、遅延ペアを空リストに「化けさせる」ことができなかったからだ。 空リストをタグ付きオブジェクトにして、空リストのインスタンスが複数あっても良いことにすれば、余分に計算しておく必要はなくなる)

遅延シーケンスを使って、最初の例題を書き直すとこんな感じになる。 ファイルから1行づつ読む遅延シーケンスは、ファイルをオープンしてportを作ってから port->string-lseq で生成している (遅延シーケンスは最後まで読まれるとは限らないので、途中で中断してもファイルが クローズされるようにするために、全体をcall-with-input-fileで囲む):

(use gauche.lazy)

(call-with-input-file "data.dat"
  (^[port]
    ($ find-index (cut > <> 65535)
       $ lmap (cut number->string <> 16)
       $ port->string-lseq port)))

mapは遅延させるためにlmapにする必要があるが、find-indexは もともと必要なところまでしか読まないので遅延版を必要としない。

余再帰とジェネレータ

遅延ストリームを作る定石は、素直な再帰をするコードのconsを遅延cons (lazy-consと表記) で置き換えることだ:

(define (F ...)
  ...
  (lazy-cons item (F ...)))

この方法はしかし、lazy-consの引数にあるFの呼び出しを遅延するために クロージャの作成を必要としてしまう。

Gaucheではそれより、 一旦ジェネレータを作ってからそれを遅延シーケンスにする方が速い。 これについては以前のエントリがあるので参照して欲しい:

上で書いたlmap1のジェネレータ経由版は次のとおりだ。

(define (lmap1 proc arg)
  (generator->lseq (^[] (if (null? arg)
                          (eof-object)
                          (proc (pop! arg))))))

さらに詳しく知りたい方は、マニュアルを参照して欲しい。

Tags: Gauche, Clojure, Programming

2018/07/01

『打ち上げ花火、下から見るか?横から見るか?』

アニメ版のほう、ネット配信に来てたのでふと見てみた。 良作だと思った。 ただ、これは戻れない過去からかなり距離を置いたおっさんだから感じることであって、 青春真っ只中にいる視聴者には受けないのかもしれないなあとも感じた。

以下ネタバレ。

岩井俊二のドラマ版からの変更として目につくのは、 一回の「if」による単純分岐を見せるのではなく、 自らの意志で繰り返し再試行することで望ましい結果を求めてゆく という「ループもの」にした点だ。 でもただのループものはもうありふれている。 本作が巧みだと思ったのは、 そもそも再試行していないという見方ができるように作ってあること。

主人公が、最初にヒロインのなずなが親に連れ去られるのを見た後、謎の球体を投げる、 そこから先は現実ではない、という解釈が可能だ。 (そういうヒントが散りばめられている。)

この解釈を取るなら、主人公は、気になってた女の子が親友の方を誘ったことを知り、 親に連れていかれるのを何も出来ずに見て、自分の無力さを知り、そのやり切れない思いから 「もしあの時ああしていたら」という自分に都合の良い世界線をずーっと妄想していたことになる。 そういう妄想、ある程度年食った観客には身に覚えがあるのではないか。少なくとも自分にはある。 最初の巻き戻し以降を全部主人公の妄想と思って見るとこれはもう青春の痛さ全開、 黒歴史を見せられて悶えるしかない。

新学期に、学校をサボりたくなるのも無理はない。

ところで、どの時点で妄想は現実に戻ったのか。花火師が球体を打ち上げ、 フレネルレンズのドームが壊れる部分で現実に戻ったことを示唆する描写がある (風力発電の回転方向や、花火の形状)。 主人公は自分の無力さを思い知ることで友人達に先んじて子供から一歩踏み出したわけで、 友人達が花火を横から見て、主人公一人が下から見た、 という現実があったと考えるのはテーマと整合する。

なずなと主人公は海で実際に会ったのか。 自分の限界の自覚をテーマとするなら海中のシーンが現実だとするのは都合が良すぎる。 一方で、全く会わなかったとすると救いがなさすぎる。 現実には、親と和解して海に来ていたなずなとたまたま会って 言葉を交わした程度(「今度いつ会えるかな」)の現実があった、 と想像しておくくらいがちょうど良いのかもしれない。

この、現実と妄想の境界のぼかし具合に、実写ではできない表現を使っていて それはさすがだなと思ったのであった。

Tag: 映画

More entries ...