kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

本屋さんの端末

最近は多くの本屋さんに在庫が検索できる端末が設置されてますね
便利になりました

でもですね
検索するような本ってあんまり在庫はなかったりするんですよね
ああ残念、と思ってあきらめて他の本屋に行ったり通販で買ったり

まあその分の売上が上がらなかったのは仕方ないとして
こんな検索キーワードがあったよ、とか
この本の詳細確認してたよ、みたいな情報は生かされてるんですかね
あーお客さんひとり逃した、この本在庫しておこう、とか

その本は在庫ありませんけどこんな本ありますよ、とか言われると
かえって邪魔かな?

サンプルが少ないとあんまりあてにできないかもしれませんが

RacketのMacroを調べてみる (8)

次の例です
ちょっとエラー処理に凝ったりしてます

何をするかは説明するより見たほうが速いかな

> (define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))
> (hash.refs js.a.b.c)
"value"

hasheqというのはいわゆるハッシュテーブルを作る関数で、
ここではネストしたハッシュテーブルを作ってjsという名前を付けています
ネストしたハッシュテーブルから指定した要素の値を持ってくるわけですが
指定の仕方がRacketていうかlispぽくないですね?
ドット区切りになってます
ここをマクロで作ります

ちなみに生のRacketで書くと
(hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)となります

最初にヘルパー関数を定義します
こんな風に使えるもの

> (hash-refs js '(a b c))
"value"

まあこれだけでも十分便利なんですが話が終わってしまうので

定義はこう

(define/contract (hash-refs h ks [def #f])
  ((hash? (listof any/c)) (any/c) . ->* . any)
  (with-handlers ([exn:fail? (const (cond [(procedure? def) (def)]
                                          [else def]))])
    (for/fold ([h h])
              ([k (in-list ks)])
      (hash-ref h k))))

関数定義に契約がついてますね
契約はRacketのひとつの大きな売りのようですが実はまだよく知りません
そのうち勉強したいと思います
ていうかRacketってなにげにてんこ盛りな言語なんですよ
Schemeはシンプルな言語っていうイメージがありますが正反対

マクロ本体
細切れにしてコメントを付けていきます

(define-syntax (hash.refs stx)
  (syntax-case stx ()
    [(_)
     (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx #'chain)]

引数がなにも無ければエラー

    [(_ chain)
     #'(hash.refs chain #f)]

ふたつめの引数には、指定した名前の要素が見つからなかったときに
返す値を書くことができます
省略された場合は#fが指定されたふりをしてもういちどマクロを展開します

    [(_ chain default)

名前とデフォルト値が両方指定されたとき
ここが本体です

     (unless (identifier? #'chain)
       (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx #'chain))

これはfender
名前が識別子でないときはエラーにします

     (let* ([chain-str (symbol->string (syntax->datum #'chain))]

まずはドットで区切られた識別子のsyntaxをただの文字列に変換します
上の例で言うと#'js.a.b.cをまずjs.a.b.cにして、さらに"js.a.b.c"にしています

            [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                   (format-id #'chain "~a" str))])

文字列をドットのところで分割してできたひとつひとつの文字列を
同じ名前の識別子に戻します
"js.a.b.c"から(#'js #'a #'b #'c)を作ってることになるかな

       (unless (and (>= (length ids) 2)
                    (not (eq? (syntax-e (cadr ids)) '||)))
         (raise-syntax-error #f "Expected hash.key" stx #'chain))

またエラー処理
短すぎたらエラーにします
|は見慣れませんがシンボル用の引用符
||は名前のないシンボルってことになるのかなあ

       (with-syntax ([hash-table (car ids)]
                     [keys (cdr ids)])
         #'(hash-refs hash-table 'keys default)))]))

ここが本当の本体ですが(#'js #'a #'b #'c)まで作ってあるので
大したことはしてませんね
#'js(#'a #'b #'c)に分割して、(hash-refs js '(a b c) #f)
作ってます

エラーメッセージが親切になったのはいいんですが
本体のロジックとエラー処理が混じってしまって見にくいですね
syntax-parseはそのあたりをうまくやってくれるとのことです
期待

子供の習慣づけにも使えるかなあ

今のところ途切れることなく順調に続いている小さな習慣ですが
これは子供にいい習慣を身につけさせるのにも応用できるでしょうか

脳内シミュレーションしてみました

「ねえ、毎日机に座ってノートを開くだけは必ずやるってことにしない?」
「うんわかった!」
「開いたよ!」

うーんうまくいく気がしない

毎日やることが大事なのはわかってるけどそれができない、っていうケースでしか
使えないかな…

もうちょっと考えてみよう

 

達成!

RacketのMacroを調べてみる (7)

ここまでの集大成としてstructもどきを作ります
こんな風に使えるものです

> (our-struct person (name age))
> (define p (person "john" 24))
> p
'#(person "john" 24)
> (person-name p)
"john"
> (person-age p)
24

では定義です

(define-syntax (our-struct stx)
  (syntax-case stx ()
    [(_ id (fields ...))
     (for-each (lambda (x) (unless (identifier? x)
                             (raise-syntax-error #f "not an identifier" stx x)))
               (cons #'id (syntax->list #'(fields ...))))

パターンと実行部分の間に式をひとつ入れると「fender」になります
fenderが真なら普通に実行
fenderが偽なら、パターンにマッチしなかったことになって次のパターンを探しにいきます
とはいってもここではraise-syntax-errorでエラーを上げていますので
その場でコンパイルが終了します

syntax->listは、リスト全体がsyntaxになったものをばらして
リスト要素ひとつずつのsyntaxをリストにして返します

     (with-syntax ([pred-id (format-id stx "~a?" #'id)])
       #`(begin

#``のsyntax版です
`(quasiquote ...)の短縮形であるように、
#`(quasisyntax ...)の短縮形です
quasisyntaxの中に書かれた式は基本的にそのまま返されますが、
#,(unsyntax)が付いた式は評価して返されます
#,@(unsyntax-splicing)も同様ですが、リストがひと皮むけて返されます

ここからマクロ本体です
まずは構造体をvectorとして宣言する部分のテンプレート

           (define (id fields ...)
             (apply vector (cons 'id (list fields ...))))

次はid型のstructかどうかの述語を定義する部分のテンプレート

           (define (pred-id v)
             (and (vector? v)
                  (eq? (vector-ref v 0) 'id)))

最後にstructのメンバにアクセスするための関数を定義する部分のテンプレート

           #,@(for/list ([x (syntax->list #'(fields ...))]
                         [n (in-naturals 1)])

in-naturalsは自然数をひとつずつ返してくれるsequenceを返します
vectorへのインデックスとして利用します

                (with-syntax ([acc-id (format-id stx "~a-~a" #'id x)]
                              [ix n])
                  #`(define (acc-id v)
                      (unless (pred-id v)
                        (error 'acc-id "~a is not a ~a struct" v 'id))
                      (vector-ref v ix))))))]))

ここの#`は中に#,がないので#'でよさそうなんですがなんでしょうか

structねえぞ!と思ったらこうやって作れるというのが
lisp好きなひとにはたまらないんでしょうねえ

 

達成!

娘が歴女になってしまいます

ウチの娘は自分に似て読書好き
なのでどんな本を読ませようか考えるのが趣味みたいになってます

で、この娘なんですが読書好きなだけではなくて性格全般が私に似てる感じ
感じっていうか、どこまで同じなのと驚くほどです
ダンナひとりで十分イラッとするのにひとり増えた、などとヨメは申しております

まあそんな感じなので、父親に似て理系人間になるだろうから
ちょっと歴史とかそういうのに早い目に触れさせておこうかなと思って
誘導したわけです
まあ教養ってやつへのあこがれです
理系の本は勝手に読むようになるだろうと

この娘、非常に女の子意識が強くて
お姫さまとかアイドルとか洋服とかそういうのが大好きな一方
男性が主人公の話はほとんど見向きしないという傾向がありまして
作戦を考える必要がありました

ほら平安時代のお姫さまの生活の話だよ―とか言って
枕草子とか源氏物語とかをマンガにしたやつを読ませてから
日本史マンガに誘導したり
マリー・アントワネットクレオパトラの伝記だとか
ベルばらや王家の紋章だとかを読ませて
そこから世界史マンガに誘導したり

そんな試みは大成功
マンガじゃないのも読むようになってきたし女性が主役でなくても読むようになってきたし

ところがですね
理系の本を読む気配がないんです
算数・理科ができないとかそういうわけじゃないんですけど

「はじめてであうすうがくの絵本」
「たのしい!さんすうのふしぎ なぜ?どうして?1・2年生」
ドラえもん 算数おもしろ攻略」各種
「算数の呪い」
あたりはけっこう食いつきよかったんですけどね
その続きが難しい

「数の悪魔」「数学ガールの秘密ノート」あたりを棚に置いてるんですが
興味を引いてる様子なし
「ルビィのぼうけん」は何度か読んだくらい
(気にいると無限に繰り返し読むので何度か読んだくらいでは反応が薄い方)

もう少し大きくなったらこの間書いた「13歳の娘に語る」シリーズとか
「はじめまして数学」あたりを勝手に読みはじめて
いずれは「数学ガール」とかいっしょに読んだり
自分で「虚数の情緒」読んで勉強したり
父親素粒子の話するようになってほしいんですけど
このままではただの歴女になってしまいます

とりあえずそっち方向に走り出しちゃったのはしょうがないんで
そっちはそっちで走り続けてもらうつもり
幸い歴史の中には人間のすべての営みが含まれているはずなので
歴史を核にして興味の幅を広げていってくれれば

自分自身も、歴史はチョボチョボですが科学史・数学史みたいな話は大好きなので
歴史で使ったパターンを逆輸入しようと、数学者・科学者の伝記を探しています
ただ女性に絞ると普通に出てくるのはキュリー夫人くらいなんですよね
この間、エイダ・ラブレスの伝記絵本を見つけたんで買いましたが反応は今ひとつ
ちょっとわかりにくかったかな
なにかもうひとつアイデアが必要かも

今日はブックオフでまんが西洋美術史っていうのが安く売ってたんで
まとめ買いしてきました
がっつり食いついてます
まんが数学史とかあったらいいんですけどね

 

達成!

RacketのMacroを調べてみる (6)

ここらから少し応用編
適当に端折っていきます

ちゃんとしくみを理解してないと、の方針は続いていて、エラーを出したり
それを調べたりしながら順を追って関数名を作る話をしてくれます

  • syntaxの内側でないとpattern variableは参照できない
  • template内はpattern variable以外置き換えは起こらない
  • 置き換えたければsyntax-caseを入れ子にして使うことができる
  • マクロがうまく動かなくてどう展開されているのか知りたいときはMacro Stepperが便利

ここでいったんなんとか動くソース

(define-syntax (hyphen-define/ok1 stx)
  (syntax-case stx ()
    [(_ a b (args ...) body0 body ...)
     (syntax-case (datum->syntax stx
                                 (string->symbol (format "~a-~a"
                                                         (syntax->datum #'a)
                                                         (syntax->datum #'b))))
       ()
       [name #'(define (name args ...)
                 body0 body ...)])]))

(hyphen-define/ok1 foo bar () #t)を評価すると
(define (foo-bar) #t)になります

  • stx#'(hyphen-define/ok1 foo bar () #t)が入る
  • afoobbar(args ...)()body ...#tにマッチする
  • (syntax->datum #'a)fooを、(syntax->datum #'b)barを取り出す
  • format"foo-bar"を作る
  • string->symbolfoo-barを作る
  • datum-syntax#'foo-barを作る
  • 内側のsyntax-casename#'foo-barにマッチする
  • #'(define (name ...(define (foo-bar) #t)に変換される

てことをしてます
死にそうです

こういうよくある(はず)の処理は簡単にできるようになってるはず
ふたつのツールが使えます

  • (syntax-case <syntax> () [<pattern> <body>])の代わりに(with-syntax ([<pattern> <syntax>]) <body>)が使えます
  • (datum->syntax stx (string->symbol (format ... (syntax->datum #'a))))の代わりに(format-id stx ... a)が使えます

こうなりました

(define-syntax (hyphen-define/ok3 stx)
  (syntax-case stx ()
    [(_ a b (args ...) body0 body ...)
     (with-syntax ([name (format-id stx "~a-~a" #'a #'b)])
       #'(define (name args ...)
           body0 body ...))]))

これなら生きていけます

水出し茶

最近、事務所では緑茶を水出しして飲んでます
冷水だと出ないかな、と思ってましたがやってみると
色は薄くてもけっこう出てる感じ
個人的には好みの味

使ってるお茶は300グラム1000円の茎茶
お茶屋さんで売ってるお茶の中ではかなり安い部類だと
思いますけどけっこういけますよ
普通の煎茶では試してませんけど、いけるんじゃないかなと思います

時間が経って2煎め、じゃないな2杯目を入れるとかなり濃く出ます
1杯目がもう少し濃く出るとバランスがいいんじゃないかと思って
微妙なぬるさ加減とか試し中