先に結論
TODOの最新30を取得し、優先度 (A,B,C) をアルファベット順、日付を降順(新しい順)にする例。
(記事の後半に別解も紹介。)
#+BEGIN_QUERY
{:title "TODO (直近30)"
:query (todo TODO)
:result-transform (fn [result]
(let [rev-pri (fn [p] (case p "A" "Z" "B" "Y" "C" "X" "Z" "B" "A"))]
(->> result
(sort-by (fn [r]
[
(rev-pri (get r :block/priority "Z"))
(get (get r :block/page) :block/journal-day 19000101)
]
))
reverse
(take 30)
;; 日付デバッグ用
;; (map (fn [m] (update m :block/properties
;; (fn [u]
;; (assoc u
;; :journal-day (get (get m :block/page) :block/journal-day)
;; )
;; ))))
)))
:breadcrumb-show? false
}
#+END_QUERY
解説
まず、->>
はThread-Lastマクロと呼ばれていて、関数を順番に実行して適用するという意味。Thread-Firstマクロ (->
) との違いは、引数が関数の最後に渡されるか最初に渡されるかの違い。
わかりやすくいえば次のように置き換えられる。
(->> result fn_a fn_b fn_c)
; 上と下は同じ意味
(fn_c (fn_b (fn_a result)))
もう少し具体的な例をClojureDocから引用しておく。
;; An example of using the "thread-last" macro to get
;; the sum of the first 10 even squares.
user=> (->> (range)
(map #(* % %))
(filter even?)
(take 10)
(reduce +))
1140
;; This expands to:
user=> (reduce +
(take 10
(filter even?
(map #(* % %)
(range)))))
1140
これを踏まえると、:result-transform
の中身は次のように分けて考えられる。
(fn [result]
; 先に使いたい関数を定義
(let [rev-pri (fn [p] (case p "A" "Z" "B" "Y" "C" "X" "Z" "B" "A"))]
; 得られた結果を、
(->> result
; まずソートして
(sort-by (fn [r]
[
(rev-pri (get r :block/priority "Z"))
(get (get r :block/page) :block/journal-day 19000101)
]
))
; 逆順にして
reverse
; 30個とってきて、
(take 30)
; ついでに表示データに :journal-day を加える (※デバッグ用で、必須ではない。)
(map (fn [m] (update m :block/properties
(fn [u]
(assoc u
:journal-day (get (get m :block/page) :block/journal-day)
)
))))
)))
わかりやすく、ソートの部分だけを取り出しておく。
(sort-by (fn [r]
[
(rev-pri (get r :block/priority "Z"))
(get (get r :block/page) :block/journal-day 19000101)
]
))
ここでは、複数のソート条件を配列で渡している。(ちなみにget
関数の最後の引数にあるZ
や19000101
は、フォールバックというもので、見つからなかったときにその文字列で間に合わせるというやつ。)
ちなみに、sort-by
やreverse
を2回やってしまうと、結果全体が新規に書き換わってしまうのでうまくいかない。Logseqフォーラムにもあるように、juxt
等も同様の理由で今回は役に立たない。
そこで、ここでは作為的に、rev-pri
なる、優先順位のアルファベットを逆転させる関数を先にlet
で定義して、A
->Z
, B
->Y
... と先に置き換えておくことで、逆順ソートを実現している。
Thread-Lastマクロの最後で、結果を逆転=reverse
しているので、最終結果としては、優先度 (A,B,C) がアルファベット順、日付が降順(新しい順) になるようにソートされる。
なお、今回は「優先度」を配列の先に置いているので、「優先度」が優先されてソートされるが、逆にすれば日付が優先される。これは例えばDONE
などの終わったものを順に表示させるときに良いと思う。
ちなみに今回はsort-by
に配列を渡したけれど、配列ではなくて文字列結合(str
)でも可。こちらのほうが内部で何が起きているかわかりやすいと思う。
別解
journal-day
が20241001
のような数値で返ってくることを活かして、
(sort-by (fn [r]
[
(get r :block/priority "Z")
(- (get (get r :block/page) :block/journal-day 19000101))
]
))
として reverse
を取り除いても良い。
ただこの方法はマイナス値として扱えるような数値 (つまり整数) が来た場合に限る。
余談
複合検索とか、逆順検索みたいなのは、標準機能としてあってくれてもいいかなーと若干思う。
ただ、アルファベットを数値にする方法がわかれば1、例えば日付の逆順なんかはマイナス値にすれば良いので、より汎用的な方法はありそう。(ただ、数値とかアルファベット1文字とかじゃない、2024-10-01
みたいな非数値の文字列が現れたときはたぶん悩む。)
(追記)LogseqフォーラムにFeature Requestも一応出してみた。もしかしたらより良い方法があって教えてもらえるかも。2