The King's Museum

ソフトウェアエンジニアのブログ。

Scheme 手習い(2)

第3章:偉大なる cons

rember

(define (rember a lat)
  (cond [(null? lat) '()]
        [(eq? (car lat) a) (cdr lat)]
        [else (cons (car lat) (rember a (cdr lat)))]))

rember は remove member の略。 ラット(すべての要素がアトムのリスト)内で一致した最初のアトムを削除する関数。

第2の戒律

【第2の戒律】

リストを作るには cons を用いるべし

そのまんまなんだけど、リストを作りたいときは cons を利用する。

firsts

(define (firsts l)
  (cond [(null? l) '()]
        [else (cons (car (car l)) (firsts (cdr l)))]))

firsts はリストを含むリストの最初の要素を集めて再びリストにする関数。

第3の戒律

【第3の戒律】

リストを作らんとせしときは、最初の要素になるものを記述し、しかる後にそれを自然なる再帰に cons すべし。

cons は第一引数は要素、の第二引数は再帰、の形にする。

insertR/insertL

(deifne (insertR new old lat)
  (cond [(null? lat) '()]
        [(eq? (car lat) old) (cons (car lat) (cons new (cdr lat)))]
        [else (cons (car lat) (insertR new old (cdr lat)))]))

insertR は old の右側に new を挿入する関数。

(define (insertL new old lat)
  (cond [(null? lat) '()]
        [(eq? old (car lat)) (cons new lat)]
        [else (cons (car lat) (insertL new old (cdr lat)))]))

insertL は old の左側に new を挿入する関数。

subst/subst2

(define (subst new old lat)
  (cond [(null? lat) '()]
        [(eq? old (car lat)) (cons new (cdr lat))]
        [else (cons (car lat) (subst new old (cdr lat)))]))

old を new に置き換える関数。

(define (subst2 new o1 o2 lat)
  (cond [(null? lat) '()]
        [(or (eq? o1 (car lat)) (eq? o2 (car lat)))
         (cons new (cdr lat))]
        [else (cons (car lat) (subst2 new o1 o2 (cdr lat)))]))

o1 か o2 を new に置き換える関数。

multirember

(define (multirember a lat)
  (cond [(null?o lat) '()]
        [(eq? (car lat) a) (multirember a (cdr lat))]
        [else (cons (car lat) (multirember a (cdr lat)))]))

ラット内のすべての要素 a を削除して新しいリストを作る関数。 rember の複数要素対応ってところ。

multiinsertR/multiinsertL

(define (multiinsertR new old lat)
  (cond [(null? lat) '()]
        [(eq? (car lat) old)
         (cons old (cons new (multiinsertR new old (cdr lat))))]
        [else (cons (car lat) (multiinsertR new old (cdr lat)))]))
(define (multiinsertL new old lat)
  (cond [(null? lat) '()]
        [(eq? old (car lat))
         (cons new (cons old (multiinsertL new old (cdr lat))))]
        [else (cons (car lat) (multiinsertL new old (cdr lat)))]))

insertL と insertR の複数要素対応版。

感想

第3章は再帰の基本的な構造を学んだ。

ここら辺は SICP でやったことがあるところなので、特に難しくなかった。 でも、SICP よりパターン化してあるからこちらの方がより定着率は高いかな。

『Scheme 手習い』はじめました。

Gauche 本を進めていたけれど、なんだかあんまり身についてる気がしなかったので『Scheme 手習い』に手を出してみた

Scheme手習い

Scheme手習い

  • 作者: Daniel P. Friedman,Matthias Felleisen,元吉文男,横山晶一
  • 出版社/メーカー: オーム社
  • 発売日: 2010/10/22
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 129回
  • この商品を含むブログ (34件) を見る

第1章:おもちゃ

アトム、リスト、S 式とは

Gauche は atom? が定義されてないので、本に書いてあるように定義する。

(define atom?
  (lambda (x)
    (and (not (pair? x)) (not (null? x)))))

(atom? '0) ; => #t

「(atom? '0) が #f になることを確かめてほしい」って本に書いてあるけど、おかしい…。 本の誤植かも。

(atom? 'atom) ; => #t
(atom? 1492) ; => #t
(atom? '()) ; => #f
(atom? '(1 2 3)) ; => #f

アトムはリストではないもの。

(list? 'atom) ; => #f
(list? 1492) ; => #f
(list? '()) ; => #t
(list? '(1 2 3)) ; => #t

リストは S 式を括弧でくくったもの。

アトムは S 式。 リストもそれ自体が S 式となる。

car とは

(car '(A B C)) ; => A
(car '(A)) ; => A
(car 'hotdog) ; => 未定義
(car '()) ; => 未定義

car は空でないリストの先頭の S 式を取り出す。 アトムと空のリストに対する操作は定義されていない。

cdr とは

(cdr '(A B C)) ; => (B C)
(cdr '(A)) ; => ()
(cdr 'hotdog) ; => 未定義
(cdr '()) ; => 未定義

cdr は空でないリストの先頭以外のリストを取り出す。 アトムと空のリストに対する操作は定義されていない。 cdr は常にリストとなる。

cons とは

(cons 'a '(b c)) ; => (a b c)
(cons 'a '()) ; => (a)
(cons 'a 'b) ; => 本では扱わない

cons は任意の S 式をリストの先頭につける。 第一引数は S 式。第二引数は任意のリスト。 ドット対は扱わないようだ。

null? とは何か

(null? '()) ; => #t
(null? '(a)) ; => #f
(null? 'a) ; => 本では扱わない

null? は空リストかどうかを調べる。 アトムに対する null? は定義されていない。

eq? とは何か

(eq? 'atom 'atom) ; => #t
(eq? '(1) '(1)) ; => 本では扱わない
(eq? 7 6) ; => 本では扱わない

eq? は同じアトムであるかどうかを調べる。 eq? はどちらも数でないアトムでなければならない。

1章はこれで終わり。

第2章:一度、もう一度、さらにもう一度、またもう一度、……

lat?

(define (lat? lis)
  (cond [(null? lis) #t]
        [(atom? (car lis)) (lat? (cdr lis))]
        [else #f]))

(lat? '()) ; => #t
(lat? '(a b c)) ; => #t
(lat? '((a) b c)) ; => #f
(lat? '(() b c)) ; => #f

lat? はリスト内の S 式がすべてアトムだった場合に #t を返す関数。

or

(or (null? '()) (null? '())) ; => #t
(or (null? '()) (null? '(a b c))) ; => #t
(or (null? '(a b c)) (null? '())) ; => #t
(or (null? '(a b c)) (null? '(a b c))) ; => #t

or の確認。

member?

(define member?
  (lambda (lis a)
    (cond [(null? lis) #f]
          [(eq? (car lis) a) #t]
          [else (member? (cdr lis) a)])))

(member? '(a b) 'a) ; => #t
(member? '(a b) 'c) ; => #f
(member? '() 'a) ; => #f

与えられたアトムがリスト内にあるかどうかを調べる関数。

第1の戒律

【第1の戒律】

(仮)いかなる関数を表現するときも最初の質問はすべて null? にすべし。

感想

再帰の練習って感じ。まずはじめに null? を質問するのがコツ。 ここらへんはまだまだ簡単。

そういえば、紙の本買ったのひさしぶりだなぁ。

クラウドのもろさとは

昨日起きた AWS の障害。自分の会社も AWS を利用しているので影響を受けた。

この出来事に対してクラウドを利用することへのもろさを懸念する論調が一部であがった。

「もろさ」には二種類あると思う。個別のサービスに対するもろさと、社会全体として見たときのもろさ。

大手が提供するクラウドサービスを利用すること個別のサービスのもろさは低減されると思う。自社でサーバーを管理するよりも高度な体制で管理されているだろうし、今回のような事件があっても復旧は早い。

しかし、多くのサービスが特定の業者のクラウドに依存するようになると、社会としてのもろさは確実に上昇するだろう。オール電化のマンションが停電すると何もできないように。

ウェブサービスが載っかるインターネットは分散システムになっていることとは対照的だと感じた。

あと、このあたりの切り分けをせずにこのことを論じると、なんだか水掛け論だなぁとツイッターなどを見ていて思った。

Scheme の number? と complex?

Scheme にある number?complex? という手続き。

  • number? : 引数が数値であるかどうか
  • complex? : 引数が複素数であるかどうか

(number? x) => #t かつ (complex? x) => #f の数値ってあるのかな?と思った。

リファレンス(6.3.2 数値に関する述語)を見るとまさに解答が書かれていた。

Gaucheでは、数の集合は複素数の集合と同一であり、...(略)

「Gauche では、」ってことは他の処理系ではそういう数値があるのかなぁ?

Dart の非同期プログラミング(Future, async/await)

仕事で Flutter をちょっといじっていて、いい加減 async あたりをちゃんと理解しておこうと思ったのでメモ。

https://dart.dev/tutorials/language/futures

Dart コードはシングルスレッドで実行される

Dart は基本的にシングルスレッドで実行される。 並列処理を行いたい場合は Isolate を利用する。

Future は非同期オペレーションの結果を表す

Future は非同期オペレーションの結果を表している。 ここは Java と同じだね。

Future が完了するまで待つには async 関数で await を使う

void main() async {
  await something();
  print('all done');
}

await を使うためには関数に async を付与する必要がある。

async 関数では以下の条件のいずれかを満たすまで同期的に実行される。 以下のどれかにたどりつくと Future を返し、非同期的に実行される。

  • 関数で最初の await
  • return
  • 関数の終わり

await のあれこれ

await expressionexpression は通常 Future を返す関数である。 しかし、expression が Future を返さない場合は、自動的に Future でラップされる。

なお、await expression 文自体は Future の完了した値を返す。

import "dartd:async";

void main() async {
   // 自動的に Future にラップされ、Future の完了値 int を返す
   int i = await something();
   print(i);
}


int something() {
  return 1;
}

完了を待ちつつスレッドをブロックしない、っていうのが await で気楽に使えるのはいいなぁ。

cut 式とプログラミング Gauche 7章

cut 式

関数を部分適用したい時に使えるらしい。

例えば、

二つのパラメータを取る cons。片方のパラメータを 1 に特殊化したい。

そういう場合には、

(cut cons 1 <>)

と簡潔に書くことができる。

cut 式は実際には lambda の糖衣構文。

(lambda (x) (cons 1 x)) ; = (cut cons 1 <>)

たしかに cut 式の方が簡潔にかける。

ちなみに名前にこれぞという語源はなさそうだ *1

7章: 手続き

7章はちょっと長かったな。以下はメモ。コードはこちら

  • 評価の結果が #<...> のものは読み戻せない
  • 手続きも、数値、文字列、真偽値と同じ、値として他の変数に束縛できる
  • (define (NAME args) expr ...) = (define NAME (lambda (args) expr ...)
    • 前者は MIT 記法
  • for-each: 要素に手続きを適用して、返り値なし
  • map: 要素に手続きを適用して、返り値あり
  • map や for-each は複数のリストを同時に処理できる
  • スコープ
    • let: lambda の糖衣構文。順序は保証されない。
    • let*: let がネストしているだけ。
    • letrec: 初期化式が lambda の場合に変数を参照できる。再帰を書くために使う。
  • ロカール手続きは内部的には letrec として扱われる
  • 可変長引数
    • (define (func a b . c) ...) ⇒ a = 1, b = 2, c = (3 4 5)
    • (define (func . a) ...) ⇒ a = (1 2 3 4 5)
  • apply: リストを unpack して、手続きに適用する
  • match 構文
  • let-optionals* 構文
  • let-keywords 構文
  • cut 構文: 部分適用
    • (cut cons 1 <>) ⇒ (lambda (x) (cons 1 x))
  • 多値
    • リストでも値を返せるが、取り出しが面倒だったり、非効率だったりする
    • receive で受け取る
    • let-values や let*-values でも受け取れる
    • values で返す

プログラミングGauche

プログラミングGauche

(c) The King's Museum