`ask-for' : 使用例


例。自分はこんな関数を使っています。外部のソルバを時間・メモリ制限付きで呼び出す関数です。

```cl
@export
(defcached plan-task (task)
  "Calls build-component-problem, make a plan with FD, then parse the results.
returns a PDDL-PLAN. The call to this function is cached and memoized, so be
careful if you measure the elapsed time. When you measure the time, run
 (clear-plan-task-cache) to clear the cache."
  (let* ((*problem* (build-component-problem task))
         (*domain* (domain *problem*)))
    (mapcar ...
            (multiple-value-match (test-problem
                                   (write-problem *problem*)
                                   (path *domain*)
                                   :time-limit 1
                                   :hard-time-limit (* 60 5)
                                   :memory (floor (/ (sb-ext:dynamic-space-size) 1000)) ;; 15GB * 0.8 = 120
                                   ;; :options "--search astar(lmcut())"
                                   )
              ((plans t-time p-time s-time t-memory p-memory s-memory)
               (signal 'evaluation-signal
                       :usage (list t-time p-time s-time t-memory p-memory s-memory))
               plans)))))
```

これを

```cl
(defun plan-task-with-retry (t1)
  (handler-bind ...
    (restart-bind ...
      (plan-task t1))
```

でラップし、さらに


```cl
@export
(defun task-plan-equal (t1 t2)
  "Computes plan-wise compatibility. It returns true if any of the component plan
mapped from t1 to t2 is a valid plan of t2."
  (signal 'comparison-signal)
  ...
  (plan-task-with-retry t1))))
```

みたいに比較関数の中で使います。
さらにさらに、この比較関数 `task-plan-equal` を

```cl
(defun categorize-problem (problem seed)
  (log:info "~&Categorizing problem ~a with seed ~a" (name problem) seed)
  ...
    (let ((tasks/plan
           (map-reduce (lambda (bucket)
                         (coerce (categorize-by-equality
                                  bucket
                                  #'task-plan-equal
                                  :transitive t)
                                 'list))
                       #'append
                       tasks/structure
                       :initial-value nil)))
       ....)))
```

のような形で使い、最後に、実際の実験スクリプトで、
これを数十個のproblem/seed のペアに対して実行してログをとったりします。

さて、ではいま

```cl
:time-limit 1
:hard-time-limit (* 60 5)
:memory (floor (/ (sb-ext:dynamic-space-size) 1000)) ;; 15GB * 0.8 = 120
```

の部分 **だけ** を変更して実験自体を繰り返したいという場合、
しかもあとで壊さないようにスペシャル変数を用いずに使いたいという場合どうしましょう。
途中の関数 (4個) に引数を追加するのは嫌です。
特に、`plan-task-equal`のような単なる二項関係の比較関数に対して、ごちゃごちゃと追加の引数があるのは自分は嫌です。

ここで`ask-for`を使ってみます。まず、一番内側のコードだけを以下のように入れ替えます。

```cl
:time-limit (ask-for time-limit 1)
:hard-time-limit (ask-for hard-time-limit (* 60 5))
:memory (ask-for memory (floor (/ (sb-ext:dynamic-space-size) 1000))) ;; 15GB * 0.8 = 120
```

`:in` は絞り込むためのオプションに過ぎないので必要ありません。
また、 `(ask-for time-limit)` などの `time-limit` もただのタグ付けなので、
実際の変数名である必要もありませんし、好きなシンボルを選んで良いです。
で、実験コードのほうで

```cl
(in-reply-to ((time-limit 4) (hard-time-limit 10) (memory 200000))
  (do-experiment))
```

のようにしてやればよいことになります。
面白いのは、in-reply-to はシグナルハンドラなので、
そこから好きなリスタートを呼べることです。例えば

```cl
(in-reply-to ((time-limit
               (invoke-restart 'increase-time) ;; = 7
               )
              (hard-time-limit 10)
              (memory 200000))
  (do-experiment))

;; ...中間コードで...

(let ((timelimit 2))
 (restart-bind ((increase-time
                  (lambda (c)
                    (+ timelimit 5)
                    ;; do not handle the condition
                    )))
   ...)
```

リスタート increase-time は局所脱出しないコードなので、
`(invoke-restart 'increase-time)` は普通に戻り、増えた 2+5=7 を返します。
そののち、`in-reply-to` が `use-value` リスタートを使って `ask-for` に値を返します。