見出し画像

Retro-gaming and so on

RE: プログラミング日記・随時追記 2021/10/28

星田さんの記事へのコメント。

MapとFor-eachの違いという話なんだけど、For-eachは副作用を目的とする・・?データの改変を行うってことだよな・・

はい。
他にも副作用目的の関数には出力がありますね。Pythonでもprintってのは副作用目的の関数です。

これ、ちょっとプログラミング初めて間もないと意味が掴めないかも。
ちとPythonで見てみますが、その前に。

「関数」ってのは「値を返す」のが「作用」ですよね。何らかの値を結果returnする。そしてそのreturnした値を「使える」わけです。変数に代入したり出来ますよね。

と言う事はPythonのprintも「結果を代入出来る」筈です。

>>> x = print("Hello, World!")
Hello, World!
>>> x
>>>

Pythonでこうやってprintの返り値を代入したら何も表示されません。でも代入自体は出来ている。と言う事は「何らかの値」が返されてる、って事です。
つまり、Pythonの場合、printの返り値はNoneで、printの「作用」は「Noneを返す」事なんです。それが主作用。「関数である以上」そうなる。
一方、インタプリタに「Hello, World!」って印字するのは作用じゃない。
いや、プログラミングする側にとっては「印字させるのが目的で作用ですけど?」とか思うんだけど(笑)、「関数の仕組み」で考えるとそうじゃない。印字させる事「自体」はプログラミング言語の「関数」側に取ると「副作用」なんです。
(逆に言うと「印字」が主作用なら、xに「印字させる」と言う効果が代入されてないとならない)

Schemeのfor-eachもそのテの副作用目的、主には「破壊的変更を伴う」仕事、あるいは「出力」ですね、そういったモノを扱う為の高階関数です。

> (for-each display '("Hello" " " "World" "!" "\n"))
Hello World!
>


じゃあMapでやったらどうなんのかと思ったら・・むっ!?なんだこれは・・まあ、一応合計は出とるが・・

いい実験です。
実の事言えばmapfor-eachも副作用に対してやるこたぁ変わんない。
要は「mapは返り値としてリストを返す」んだけど、for-eachの方はリストを返さない。
つまり、for-each自体が返り値がないんで、これは実は「高階関数」と言うよりは「高階プロシージャ」あるいは「高階手続き」なんですね。
#<void>ってのはRacket上で「返り値がない」事を示すサインです。言い換えると、インタプリタの内部的にはset!の「返り値みたいなモノ」って事になります。

> (define a #f)
> (define b (set! a #t))
> a
#t
> b
>

上の実行結果を見れば分かるでしょうが、「aを破壊的変更した」返り値はありません。だからbには何も代入されてないんですが、一応内部的には #<void>が渡されています。
言い換えると、Pythonで言うトコのNoneみたいなモノが#<void>ですね。

駄目だ~。もう良いやw

いい、挑戦ですね。
正解は「ラムダ式を使う」です。

(define (sa ls1 ls2)
 (map (lambda (x y)
  (abs (- x y))) ls1 ls2))

> (sa '(7 8 0 4) '(5 10 5 6))
'(2 2 5 2)
>

あるいはmap二段階適用、とかね。

(define (sa ls1 ls2)
 (map abs (map - ls1 ls2)))

Filterの問題で偶数だけを返すというの。割った余りを出す計算が%じゃないんだな・・と探すのに苦労する。

ああ、Pythonだと剰余使いますからね・・・・・・。
Schemeには実は偶数を判定するeven?と言う組み込み関数と、奇数を判定するodd?と言う関数があります。
だから望ましい正解はこんなカンジ。

(define (gusu ls)
 (filter even? ls))

> (gusu '(2 5 8 10 19 22))
'(2 8 10 22)


なんですが・・・・・・ん?


言語がR5RS(※)になっとる・・・・・・。

紫藤さんのページってR6RS対応なんで星田さんが今使ってるバージョンはそれより古い形式で使ってるんですよね・・・・・・。
(結果、filterがビルトインじゃない)
わざとそうしたのかしらん。


上の写真の赤丸のトコ、要するにDrRacket本体の左下ね。
そこ開くと言語選択ウィンドウが出ると思うんだけど。


それで一番上の「The Racket Language」を選べば良いです。

実はRacket自体は結構巨大で、上の画面みたら分かるけど、実験用の言語とか教科書用言語とか色々突っ込まれてるんですよ。
R5RSはもはやレガシーなので、あまり使わんですね。・・・いまだポピュラーな仕様ではあるんですけどね(主流は二分されてて、R5RSとR7RS-smallの二つ)。

と言うわけで、The Racket Languageに直しておけば、filterもビルトインで使い放題です。

と言うか日本語のSchemeページって少ないんですねぇ・・(^_^;)

そうですね。本と同様・・・。
っつーか資料ページはそこそこ見つかるけど、まとまったサイトってのは多分、かなり古いけど独習Scheme3週間お気楽Schemeプログラミング入門くらいしかないんじゃないですか?後者はGaucheに特化してますし(だからWindowsだとツラい)。

本日のハイライト!銀行口座を作る。こ、これは・・!たったこんだけの記述で!?これが高階関数の威力か・・・感動した!

そこにも書いてますが、それがオブジェクト指向の原理です。

練習問題。問題はBeginだ・・。説明では別の命令をまとめて書くということだったんだけど・・この例では後にはSet!しかないよな・・Amountは違うし、くくる必要がないじゃないか

例えばこんなカンジ。

(define (make-bank-account amount)
 (lambda (n)
  (if (and (negative? n) (> (abs n) amount))
   (error "残高が足りません" n)
   (begin
    (set! amount (+ amount n))
    amount))))

「くくる必要がある」ってのはifthen節else節も、他の言語で言うトコの「単文」しか取れないから、です。
ところが、このケースだとelse節が複文

(set! amount (+ amount n))
    amount

を取らないとなんない。
だからそこにbeginが必要なんです。

ちなみに、condを使えばbeginは要らない。

(define (make-bank-account amount)
 (lambda (n)
  (let ((m (+ amount n)))
   (cond ((negative? m) (error "残高が足りません" amount))
      (else (set! amount m)
      amount)))))

condcaseには「暗黙のbegin」と言うものが含まれていて、実行部分にはいくつ式を置いてもO.K.だ、と言うのがifと違う辺りです。
逆に言うと、ifはミニマリスティックな機能しかない、って事ですね。そして現代的なLispでは、condifを用いてマクロとして定義されている、って考えて、まぁ間違っていません。
紫藤さんのこのページに、マクロとしてのcondの定義が載っています。

※: RnRSと言うのがSchemeの仕様書。R5RSは改定第5版、R6RSと言うのは改定第6版を意味する。
なお、現在の最新仕様はR7RS、つまり改定第7版だが、これは仕様が二つに分かれてて、ミニマムなR7RS-smallと大掛かりなR7RS-largeとなってて、かつsmallがlargeの基盤になってる、っつーわけでもない。そしてまた、R7RS-largeはいまだ決定されていない(筈)。
旧PLT-Scheme/MzSchemeはR6RSの後、独自路線を選択し、Racketとなる。よって(かなりの確率で機能を共有してはいるが)、RacketはSchemeである事を止めた。

  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: プログラミング日記」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事