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