The King's Museum

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

Scheme 手習い(7)

第8章:究極の lambda

rember-f

(define (rember-f test? a l)
  (cond [(null? l) '()]
        [(test? (car l) a) (cdr l)]
        [else (cons (car l) (rember-f test? a (cdr l)))]))

リストから要素を削除する rember の派生版。 要素の一致を判定する関数を引数で与えることができる。

eq-c?

(define eq-c?
  (lambda (a)
    (lambda (x)
      (eq? a x))))

引数 a を与えると、その引数と eq? をとる関数を返す関数。 これはカリー化と呼ばれている。

rember-f

(define rember-f
  (lambda (test?)
    (lambda (a l)
      (cond [(null? l) '()]
            [(test? (car l) a) (cdr l)]
            [else (cons (car l) ((rember-f test?) a (cdr l)))]))))

さきほどと同じ rember-f だが、「関数を適用すると関数が返ってくる」点が異なっている。 この関数を使うためには次のようにする。

((rember-f eq?) 1 '(1 2 3))

rember-f に eq? を与えると test? を eq? で束縛した a と l をとる関数返ってくる。 それに対し、1 と '(1 2 3) を引数として関数を呼び出す。

insertL-f/insertR-f

(define insertL-f
  (lambda (test?)
    (lambda (new old l)
      (cond [(null? l) '()]
            [(test? (car l) old) (cons new l)]
            [else (cons (car l) ((insertL-f test?) new old (cdr l)))]))))
(define insertR-f
  (lambda (test?)
    (lambda (new old l)
      (cond [(null? l) '()]
            [(test? (car l) old)
             (cons old (cons new (cdr l)))]
            [else (cons (car l) ((insertR-f test?) new old (cdr l)))])))) 

リストの特定の要素の左か右に要素を挿入する関数の派生版。 比較関数を引数として与えられるようにしている。

insert-g

(define (seqL new old l)
  (cons new (cons old l)))
  
(define (seqR new old l)
  (cons old (cons new l)))

(define insert-g
  (lambda (seq)
    (lambda (new old l)
      (cond [(null? l) '()]
            [(eq? (car l) old)
             (seq new old (cdr l))]
            [else (cons (car l) ((insert-g seq) new old (cdr l)))]))))  

insertL と insertR を抽象化して、特定の要素のどちら側に要素を挿入するかを関数で与えられるようにしている。

(define insertL
  (insert-g seqL))

(define insertR
  (insert-g seqR))

insert-g に seqL と seqR を渡すと insertL と insertR を得ることができる。

なお、insertL と insertR は seqL と seqR を用意しなくても次のように定義できる。

(deifne insertL
  (insert-g
    (lambda (new old l)
      (cons new (cons old l)))))

(deifne insertR
  (insert-g
    (lambda (new old l)
      (cons old (cons new l)))))

また、要素を違う要素に置き換える subst も次のように定義できる。

(define subst
  (insert-g
    (lambda (new old l)
      (cons new l))))

さらに、なじみの深い rember も次のように定義できる。

(define rember
  (insert-g
    (lambda (new old l) l)))

第9の戒律

【第9の戒律】

新しき関数においては共通のパターンを抽象化すべし。

multirember-f

(define multirember-f
  (lambda (test?)
    (lambda (a lat)
      (cond [(null? lat) '()]
            [(test? (car lat) a)
             ((multirember-f test?) a (cdr lat))]
            [else (cons (car lat)
                        ((multirember-f test?) a (cdr lat)))]))))

multirember の比較関数を引数で与えられるようにしたバージョン。

multirember-T

(define (multirember-T test? lat)
  (cond [(null? lat) '()]
        [(test? (car lat))
         (multirember-T test? (cdr lat))]
        [else (cons (car lat)
                    (multirember-T test? (cdr lat)))]))

multirember に特定の値と比較する比較関数を与えられるようにしたバージョン。 次のような関数を定義し、multirember-T に与える。

(define (eq?-tuna k)
  (eq? 'tuna k))

(multirember-T eq?-tuna '(tuna shrimp salad))

multirember&co

(define (multirember&co a lat col)
  (cond [(null? lat) (col '() '())]
        [(eq? (car lat) a)
         (multirember&co a (cdr lat)
           (lambda (newlat seen)
             (col newlat (cons a seen))))]
        [else
         (mutltirember&co a (cdr lat)
           (lambda (newlat seen)
             (col (cons (car lat) newlat) seen)))]))          

multirember を継続渡しスタイル (continuation passing style) と呼ばれるやり方に変更したバージョン。

ちなみにこの例では継続を使って、さらに再帰しているので非常に分かりづらい。 継続を説明せずにいきなりこの例を出されるのはけっこう辛いものがある。

multiinsertLR

(define (multiinsertLR oldL oldR new lat)
  (cond [(null? lat) '()]
        [(eq? oldL (car lat))
         (cons new (cons oldL
                    (multiinsertLR oldL oldR new (cdr lat))))]
        [(eq? oldR (car lat))
         (cons oldR (cons new
                    (multiinsertLR oldL oldR new (cdr lat))))]
        [else (cons (car lat)
                    (multiinsertLR oldL oldR new (cdr lat)))]))

oldL の要素の左に new を、oldR の要素の右に new を挿入する関数。 これ自体はそれほど難しくない。

multiinsertLR&co

(define (multiinsertLR&co oldL oldR new lat col)
  (cond [(null? lat) (col 0 0 '())]
        [(eq? oldL (car lat))
         (multiinsertLR&co oldL oldR new (cdr lat)
           (lambda (l r newlat)
             (col (+ 1 l) r (cons new (cons oldL newlat)))))]
        [(eq? oldR (car lat))
         (multiinsertLR&co oldL oldR new (cdr lat)
           (lambda (l r newlat)
             (col l (+ r 1) (cons oldR (cons new newlat)))))]
        [else
         (multiinsertLR&co oldL oldR new (cdr lat)
           (lambda (l r newlat)
             (col l r (cons (car l) newlat))))]))             

multiinsertLR を継続渡しスタイルにしたバージョン。 この例はぎりぎり理解できている(気がする)。

外側からどんどん関数を適用(ひも解いていく)するイメージかな…。

evens-only*

(define (evens-only* lat)
  (cond [(null? lat) '()]
        [(atom? (car lat))
         (cond [(even? (car lat)) (cons (car lat) (evens-only (cdr lat)))]
               [else (evens-only (cdr lat))])]
        [else (cons (evens-only (car lat)) (evens-only (cdr lat)))]))                

入れ子になったリストから奇数を除去する関数。 入れ子を走査するために、(car lat) が atom? かどうかで分岐しているのがポイント。

evens-only*&co

(define (evens-only*&co lat col)
  (cond [(null? lat) (col '() 1 0)]
        [(atom? (car lat))
         (cond [(even? (car lat))
                (evens-only*&co (cdr lat)
                  (lambda (newlat p s)
                    (col (cons (car lat) newlat) (* p (car lat)) s)))]                    
               [else
                (evens-only*&co (cdr lat)
                  (lambda (newlat p s)
                    (col newlat p (+ (car lat) s))))])]
        [else
         (evens-only*&co (car lat)
           (lambda (newlat p s)
             (evens-only*&co (cdr lat)
               (lambda (dnewlat dp ds)
                 (col (cons newlat dnewlat) (* dp p) (+ ds s))))))]))

evens-only* を継続渡しスタイルにしたバージョン。 ここで自分の理解能力の限界を超えた。

(evens-only*&co (car lat)
  (lambda (newlat p s)
    (evens-only*&co (cdr lat)
      (lambda (dnewlat dp ds)
        (col (cons newlat dnewlat) (* dp p) (+ ds s))))))

この部分がなぜ、こうなるのかまだきちんと理解できていない。

本には、

ひえー。頭がこんがらがりそうですね。

と書かれていたが、ほんとにその通り。

自分の理解のために次の章に進む前に継続について一つの記事を書こうかな。

道路を舗装する

仕事をしていると、アウトプットがあった日とそうでない日がある。

プログラマのアウトプットはコードだと思っている。 だから、コードが書けない日はアウトプットがなかったと感じる。 もちろん、コードの「量」だけがアウトプットだとは思っていない。 書いたコードの影響力とか、そのコードが与えたインパクトの大きさが大事だ。

ただ、やはりコードがアウトプットであることには変わりはないと思う。

コードを書かない日

コードを書かない日は何をしてるかというと、打ち合わせをしたり、メールをしたり、チケットにコメントを書いたり、開発環境を整えるのに苦労したりしている。 こういう日が増えてくると、目に見えるアウトプットが減っていき、仕事での達成感を失っていく。

ただ、少し見方を変えてみると違う結果が得られる。

事前に打ち合わせやメールで仕様を調整する。 インタフェースについて合意を取るためにチケットにコメントを書く。 開発環境の構築に時間を費やす。

一見、コードを書くことと関係なさそうに見えるこれらの仕事はうまくいっているのならコードを書くための準備運動だとみることもできる (ただ、「うまくいっているのなら」が重要なポイントで、意味のない打ち合わせなどは本当に何も生み出さないので注意)。 そう考えるとこれらの活動は、コード、すなわちアウトプットを直接生み出してないようにみえて、実はそれを生み出すための下地を整えているともいえる。

これを自分は「道路を舗装する」と呼んでいる。

アウトプットのために

快適にそして全速力でコーディングするために道路を舗装してるんだと思うと、打ち合わせやメール、めげそうになる Yak Shaving も少しだけポジティブに取り組むことができる。アウトプットを感じられなかった日も、自分は明日のために道路を舗装してたんだと思えばいくらか気分はましになる。

もちろん、もとから道路が舗装されていればベストだし、他人が舗装してくれることを期待してもよいだろう。 ただ、この職を続けていれば自分が舗装しなければならない場面に必ず遭遇するし、職位が上がると自分だけでなくチームのために道路を舗装しなければならないことも増えてくる。

そういう時に無駄に達成感をなくしてしまうのではなく、解釈を変えることで自分のモチベーションをうまく維持することができる。 そして、こういう自分を扱い方というものは身につけるべき一種のテクニックだなぁ感じた職業プログラマー10年目の春でした。

『イノベーション・オブ・ライフ』を読んで

今年は毎月一冊本を読もうと思い立って三ヶ月。

今月は『イノベーション・オブ・ライフ』を読んだ。

『イノベーションのジレンマ』で有名なクレイトン・クリステンセンが幸せな人生を送るための理論について語った本。

この本では企業経営に用いる様々なマネジメント理論を人生に応用する。 「〜をすれば幸せな人生を送れる」という知識を提供するのではなく、幸せな人生を分析するための理論を提供することを目指している。 言ってみれば「魚を与えるのではなく魚の釣り方を教える」というところだろう。理論によって対象を分析することをクリステンセンは『理論のレンズを通して見る』と表現する。

人生を理論のレンズを通して見てみよう、というわけだ。

キャリアについて

キャリアでありがちなミスとして報酬や地位をゴールにしてしまうことを挙げている。

報酬や地位は<衛生要因>であり、それらを満たすことで不満をなくすことはできるが仕事の満足には繋がらない。 「衛生状態が悪ければ健康を害するが、衛生状態が良くても健康を増進するわけではない」ことから付けられた名前だ。 「仕事に不満がある」の反対は「仕事に満足している」ではなく、単に「仕事に不満がない」という状態である、と述べている。

これは「動機付け理論」という理論のレンズを通して見ることで明らかになる。

また、キャリアについてのすべてをあらかじめ計画しておくことはできないし、そうするべきではないと説く。 事前に計画した戦略を意図的戦略と呼ぶが、企業経営と同様に物事が計画通りいくことはほぼありえないし、予期されないチャンスを逃す可能性が高い。 予期されないチャンスに対して扉をオープンにしておくことを創発的戦略と呼び、企業経営と同じくキャリアについても意図的戦略と創発的戦略のミックスが必要だという。

その他にも様々な理論を用い、そのレンズを通してみることでキャリアについて分析している。

感想

キャリアについては自分にもあてはまるなぁ、と思う部分も多々あり、考え方が整理されてとてもよかった。

一方、本書の残りの半分以上が人生の『プライベート』の部分(家族や地域コミュニティなど)に関して書かれているのだが、正直いまいちピンとこなかった。 どちらかといえばこの『プライベート』の部分が本書の主軸のような気もするけど、悪くいえば少し説教くさかったかし、賛同できない点もあった。

でも、キャリアについてはとても役に立ったのでその部分だけでも読むことをおすすめしたい。 今回、友達がおすすめしてくれたのでこの本を読んだのだけど、紹介してくれた友達には感謝したい。

ちなみに、原題は "How will you measure your life?" なんだけど、邦題に無理矢理「イノベーション」をいれているあたりはお約束といった感じだろうか。 「人生にイノベーションを!」みたいなイケイケな本ではないことは付け加えておきたい。

そういえば『イノベーションのジレンマ』は数年前にブログ記事にしたなと思っていたが、もう5年以上も前だった…。

www.thekingsmuseum.info

『プロフェッショナルの条件』を読んで

今年二冊目の本。ドラッカーの『プロフェッショナルの条件』。

ドラッカーが組織や社会ではなく個人に焦点を置いて書いた本。

より正確には「書いた本」ではなく「まとめた本」かな。

ドラッカーの過去の著作や論文から抜粋・加筆・修正してまとめあげたのがこの本。 そのせいか、全体的にまとまりがなかったように思う。 話があっちにいったりこっちにいったりする。

個人に焦点に書かれた本だから仕事術とか自己啓発的な部分があるわけだけど、ちょっと抽象的だし説明がまわりくどい。 今はもっと実践的でシンプルに説明された本がたくさんあるので、そちらを読む方が身につくことは多いかも。 きっと、それらの本の元ネタがドラッカーだったりするのだろうけど。

個人の寿命が所属する組織の寿命よりも長くなった、というのは読んでみて改めて再認識したところかな。 自分は今の会社で定年を迎えるなんてぜんぜん考えてないし、そういう時代は終わったと思っていたけど、ドラッカー先生が言っているとやはり説得力がある。

ポスト資本主義とか新しい社会がどうなるか、という話は正直よく分からなかった。 「知識労働者が〜」とか「組織社会が〜」とか。 個々の話はなんとなく「なるほどね〜」って気持ちになったけど、結局全体として何が言いたかったのかは「よくわからんわ・・・」って感じ。

人生で初めてドラッカーを読んだけど、正直なところ得られたものは少なかった。 どうせ読むならこういう抜粋形式の本ではなく、ちゃんとしたドラッカーの本を一つ読んだ方がよかったかもしれない。

題名に「はじめて読むドラッカー」って書かれてるけど、はじめて読む人にはあまり向いてないと思う。 すでにドラッカーを一通り読んでいて「ふむふむ、やっぱりそういうことだよね」って再認識するための本なんだと思う。

Amazon Aurora について

業務で Amazon Aurora を使っているが、ほとんど何も意識せず MySQL の代替として使ってる。

何が違うのか。 せっかく使っているのだから、少し勉強したほうがよさそう。 ということで、Aurora についてまとめられた資料を読んだ。

www.slideshare.net

ストレージがマスターとスレーブ(レプリカ)で共通のものを使っているところがアーキテクチャとしては特徴的。 このおかげでレプリケーションが速かったり、いろいろとメリットがあるようだ。

また、書き込みエンドポイント・読み取りエンドポイントが存在し、読み取りエンドポイントは各レプリカインスタンスに負荷分散する。

あとは、教科書通りのメリット(パフォーマンスや運用上のメリット)がずらりと。

余談

スライドにはデータベースの実装としての特徴がいくつも書いてあったけど、正直よく分からず。完全な背景知識不足。

こういう時に、分からない部分をどこまで追うかはいつも迷う。

技術者としては、こういう機会を生かして知識を蓄える必要があると思うのだが、適度に割り切って目の前の仕事を進める必要もある。 そのバランスをどうするかはいつも迷うが、最近は知識の蓄えの方をサボりがちかもしれない。

Scheme 手習い(6)

第7章:友達と親類

set?

(define (set? lat)
  (cond [(null? lat) #t]
        [(member? (car lat) (cdr lat)) #f]
        [else (set? (cdr lat))]))

lat が重複要素を持たない(= セットかどうか)かを調べる関数。 前章までですでに定義した手続き member? を利用する。

makeset

(define (makeset lat)
  (cond [(null? lat) '()]
        [(member? (car lat) (cdr lat)) (makeset (cdr lat))]
        [else (cons (car lat) (makeset (cdr lat)))]))

(define (makeset lat)
  (cond [(null? lat) '()]
        [else (cons (car lat)
                    (makeset (multirember (car lat) (cdr lat))))]))

makeset の2つのバリエーション。 member? を使うか multirember を使うかでリスト内の要素の順序が変わる。 機能は同じだけど実装が異なるという話。

subset?

(define (subset? set1 set2)
  (cond [(null? set1) #t]
        [else (and (member? (car set1) set2)
                   (subset? (cdr set1) set2))]))

set1 が set2 の部分集合かどうかを調べる関数。 and をうまく使うと短く書ける。

eqset?

(define (eqset? set1 set2)
  (and (subset? set1 set2) (subset? set2 set1)))

set1 と set2 が等しい集合かどうかを調べる関数。 subset? を使うとうまく簡潔に書ける。

intersect?

(define (intersect? set1 set2)
  (cond [(null? set1) #f]
        [else (or (member? (car set1) set2)
                  (intersect? (cdr set1) set2))]))

二つの集合に共通部分があるかどうかを調べる関数。 set1 の要素が1つでも set2 にあったら true となる。

intersect

(define (intersect set1 set2)
  (cond [(null? set1) '()]
        [(member? (car set1) set2)
         (cons (car set1) (intersect (cdr set1) set2))]
        [else (intersect (cdr set1) set2)]))

二つの集合の積集合を得る関数。

union

(define (union set1 set2)
  (cond [(null? set1) set2]
        [(member? (car set1) set2)
         (union (cdr set1) set2)]
        [else (cons (car set1) (union (cdr set1) set2))]))

二つの集合の和集合を得る関数。 実装としてはちょうど intersect の反対のような処理を行う。

intersectall

(define (intersectall l-set)
  (cond [(null? (cdr l-set)) (car l-set)]
        [(intersect (car l-set) (intersectall (cdr l-set)))]))

集合のリストからすべての集合の積集合を得るための関数。 リスト内のそれぞれの集合に intersect を再帰的に適用する。 もし、集合 A、B、C、D があったら、

(intersect A (intersect B (intersect C (intersect D))))

と適用していくイメージ。((intersect D) = D である)

a-pair?

(define (a-pair? x)
  (cond [(atom? x) #f]
        [(null? x) #f]
        [(null? (cdr x)) #f]
        [(null? (cdr (cdr x))) #t]
        [else #f]))

対象がペアかどうかを調べる関数。 ペアとは Lisp における対ではなくて、要素がふたつのリストという意味。

fun?

(define (fun? rel)
  (cond [(null? rel) #t]
        [else (set? (firsts rel))]))

対象の関係(rel)が関数であるかどうかを調べる。 関数であるためには、入力(第一要素)が重複してないことが条件。

revrel

(define (revrel rel)
  (cond [(null? rel) '()]
        [else (cons
               (build (second (car rel))
                      (first (car rel)))
               (revrel (cdr rel)))]))

対象の関係(rel)を逆にする関数。 第一要素と第二要素を入れ替えて、リストを構築し直す。

fulfun?

(define (fullfun? rel)
  (cond [(null? rel) #t]
        [else (set? (seconds rel))]))

第二要素に重複がないかどうかを調べる関数。

one-to-one?

(define (one-to-one? rel)
  (fun? (revrel fun)))

full-fun? を fun? と revrel を使って書き直した関数。 one-to-one は単射の意味(だと思う)。

感想

集合の操作がメイン。難しいところはそんなになかったかな。

最近、進捗があまりよくないなぁ。

次章からが本番な気がする。

『HIGH OUTPUT MANAGEMENT』を読んで

去年はあまり本を読めなかったので、今年はせめて毎月 1 冊は何かしらの本を読んでいこうと思う。

一冊目はインテル CEO だったアンドリュー・グローブの『HIGH OUTPUT MANEGEMENT』。過去に何度か読んだことがある本だけど、また読みたくなったので再読。

何度も読んでいるのでさすがに感銘を受けることは少なかったけど、『いかにして自分の組織のアウトプットを高めるか?』という一点に注力して書かれているところに改めて感心してしまった。会議、研修、打ち合わせ、人事考課。日々の仕事すべてを『アウトプットを高める活動か?』という視点で論じていて、姿勢にブレがない。

主に中間管理職に向けて書かれた本だけど、『今日の失敗は、過去のいつかの時点での計画の失敗である』なんてところとか、単なる仕事論としてこの本から学べることも多いのではないかと思う。一方で、二章の組織論の部分は経営層に近い人向けに書かれている気がして、今の自分に直接は関係なかったかな(それでも、やはりミドルマネジャーが重要だという論調だったけど)。

最後にこの本で気に入った一節。

私の1日が終わるのは疲れて帰宅する時であり、仕事が終わった時ではない。私の仕事は決して終わらない。

一見、すごくスパルタな一文に見えるけど、個人的には「疲れたらその日の仕事は終わりだよ」と勝手に解釈している。仕事がうまくいかずに消化不良だった日も、この一節を思い出して「今日は疲れたから仕事は終わり。ゆっくり休んでまた明日。どうせ仕事に終わりはないのだから」と考えて帰路につくようにしている。

(c) The King's Museum