📘 hkob-astro-notion-blog

これまではてなブログにて情報発信をしていましたが、令和5年3月22日より、こちらでの情報発信を始めました。2019年以前の古い記事は過去の Middleman 時代のものなので、情報が古いです。記録のためだけに残しています。

Formula 2.0 関数探求(2): map編(リスト解説含む)

1. はじめに

第一回目は parseDate を説明しました。今回は map 関数を取り上げようと思います。ただ、この関数を説明するためには、Formula 2.0 から取り入れられたリストについて理解する必要があります。今回はまず、リストの概念を説明しその上でその一つの応用である map について説明します。私が書いた map 関数のマニュアルはこちらになります。
https://hkob.notion.site/map-6f16d3b139c34acdabd3b6a5137fa984?pvs=4

2. Formula 2.0 における型表現

2.1 Formula 1.0 までの型

Notion の Formula で扱えるデータは型を持っています。それぞれのプロパティから取り出された値はいずれかの型に相当します。Formula 1.0 で取り扱えたデータ型は以下の表のようになります。Boolean は Formula 1.0 の時代は Checkbox と呼んでいましたが、ここでは Formula 2.0 に合わせて Boolean と呼ぶことにします。

内容 対応するプロパティ
Text テキスト Title, Text, Select, Multi-Select, Email, Person, Created by, Last Edited by, Relation, Rollup, URL, Phone number, ID
Number 数値 Number, Rollup (Number)
Date 日付・日付範囲 Date, Created Time, Last Edited Time, Rollup (Date)
Boolean true か false Checkbox

ほとんどのプロパティは全てテキストに変換されてしまっていたのがわかります。このため、ロールアップなどで取得した文字列の「,」の数を数えるなど、怪しい Tips などが大量に作成されていました。また一部の例外を覗き、異なる型同士の演算などは許可されていなかったため、それぞれの型を変換する関数なども多く用意されておりました。

変換前\変換後 Text Number Date
Text - toNumber() なし
Number format() - fromTimestamp
Date formatDate() timestamp() -
Boolean なし toNumber()またはunaryPlus() なし
2.2 Formula 2.0 からの型

Formula 2.0 になり、型の種類が一気に増えました。基本的な型だけでなく、People や Page などの Notion 自体が持っているオブジェクトもテキストに変換されることなく、オブジェクトとして管理できるようにっています。このため、People であれば .email().name() など、Page であれば .id.prop("プロパティ名") ように内部データにアクセスできるようになっています。

内容 対応するプロパティ
Text テキスト Title, Text, Select, Email, URL, Phone number, ID
Number 数値 Number, Rollup (Number)
Date 日付・日付範囲 Date, Created Time, Last Edited Time, Rollup (Date)
People Created by, Last Edited by
Boolean true か false Checkbox
Page ページ
List (array) 上のものの配列 Multi-Select (List of Text),
Person (List of People),
Relation (List of Page),
Rollup (List of any type)

この中で Formula 2.0 で一番影響が大きいものが最後のリストになります。リストは中に複数個の基本要素を並べたダンボール箱のようなものです。リストの中には異なる型のものを無理矢理入れることはできるのですが、その後の型変換などを正しく理解していないと難しいので、初心者は避けた方がいいでしょう。今回説明予定の map などを利用したときにうまく動かないことがあります

💡
注: 実は Formula 2.0 からはいい感じで数値から文字列への型変換などを自動でやってくれるようになっており、2.1 で示した format() はほとんど出番がなくなっています。

リスト自体は Formula 内で作成したり、関数の戻り値として返却されたりするだけでなく、Notion 内で複数情報が存在するプロパティの表現にも使われています。表内に書かれているように以下のものがリストとして活用できます。

  1. マルチセレクト: 複数個のオプション(Text)のリスト
  2. ユーザー: 複数個のアカウント(People)のリスト
  3. リレーション: 関連先ページ(Page)のリスト
  4. ロールアップ: 「オリジナルを表示する」もしくは「一意の値を表示する」を選んだ場合は、選択したプロパティの型のリスト

3. リストの表現

フォーミュラ内でリストを作成するには [] を使います。ここでも「混ぜるな危険」ということで、同じ型のものだけを入れてみました。

Number のリスト
Text のリスト
Date のリスト
Boolean のリスト

少し高度な表現になりますが、リストの中にリストを含むこともできます。このように直接リスト内リストを作成することは多くはありませんが、内部表現がこんな形になることもあります。ただし、結果の表示はすべてフラットにした形で表示されます。

リスト内リスト

また、上で示したプロパティがリストになっていることも確認してみます。ユーザの部分は他の人を呼べないので一人になっていますが、これらは複数個のオブジェクトを持つことになるため、リストになっていることがわかります。

マルチセレクト(Text のリスト)
ユーザー (People のリスト)
リレーション (Page のリスト)
リレーション先のマルチセレクトのロールアップ (Text のリスト)

4. map 関数

map 関数はリストを第一引数に持ち、リストの一つずつの要素に対して、第二引数の式を展開した中身に変換する関数です。メソッド形式の場合には、リストに対して map メソッドを呼び、第一引数の式を展開します。基本的にはメソッド形式のものは、関数の第一引数が前に来るだけと思えばいいだけです。

map(リスト,)
関数形式
リスト.map()
メソッド形式

なお、リスト内の一つずつの要素は current という一時変数として、0から始まる要素の番号は index という一時変数として渡されます。

map の仕組みを知るためだけの意味のない Formula ですが、いくつかサンプルを挙げてみましょう。

  1. リストの中身を2倍にします。1, 2, 3 のそれぞれの値が 2 倍された新しいリストが返ってくることがわかります。
  2. リストの要素番号を5倍します。5倍にはあまり意味はありません。
  3. map 内の式でリストを作るとリスト内リストが生成できます。ここでは index と current が入ったリストを要素にもつリストが生成されます。
  4. 内部で作成したリストに対して演算をすることもできます。ここでは上で示した要素番号と文字列を : を区切りにした join メソッドで連結しています。
  5. 最後はお遊びです。まだ解説していない repeatsplit などが使われていますが、興味のある人はなぜこのような出力が出るかを考えてみてください。ここで数値プロパティには 5 という数字が入っています。

5. 具体的な応用例

逆引き Formula で map を使っているものをいくつか紹介しましょう。

5.1 年、月、日の3つの数値プロパティから日付を作成するには?

この記事の URL はこちらです。

https://hkob.notion.site/3-fcf89d94d7964597a266f579eb681452?pvs=4

こちらで紹介している Formula はこちらです。年・月・日のプロパティからもらった三つの数値から、前回紹介した parseDate に渡すための基本方式の8桁の数値を作成しています。

/* 3つのプロパティからリストを作成 */
[prop("年"), prop("月"), prop("日")]
	/* 年を 10000 倍, 月を 100倍, 日を1倍する */
	.map(current * 100^(2-index))
	/* これらを合計 */
	.sum()
	/* 日付に変換 */
	.parseDate()

この3つのプロパティに 2023, 9, 15 の三つの数値が渡されたことを考えてみましょう。最初の行で [2023, 9, 15] というリストが生成されます。これらに対して map がどのような処理をするのか考えてみます。

current index 100^(2-index) () の中身
1つ目 2023 0 10000 20230000
2つ目 9 1 100 900
3つ目 15 2 1 15

この結果、 [20230000, 900, 15] というマッピングが行われたことになります。これを .sum() することで、 20230915 という8桁の数値が得られます。最後にそれを .parseDate() で日付に変換します。

5.2 子タスク・孫タスク・曽孫タスクのステータスをかき集めるには?

この記事の URL はこちらです。

https://hkob.notion.site/6f4c9cdf3d4840ebb67252b5fe905a7f?pvs=4

こちらで紹介している Formula はこちらです。子タスク (Sub tasks) が存在しなければ自分の Status をリストの形で返し、存在する場合には小タスクで同じ処理を再帰的に続けていきます。map 関数の返り値はリストのリストになってしまうので、 .flat() でフラットなリストに変換します。この結果、そのタスクよりも下位のステータスのみを全てかき集めることができています。

/* 1階層目が子供を持つか? */
prop("Sub tasks").empty() ?
/* 持たなければ自分(1階層目)のステータスをリストで返す */
[prop("Status")] :
/* 持つ場合は2階層目の全てのページを処理 */
prop("Sub tasks").map(
	/* 2階層目が子供を持つか?? */
	current.prop("Sub tasks").empty() ?
	/* 持たなければ自分(2階層目)のステータスをリストで返す */
	[current.prop("Status")] :
	/* 持つ場合は3階層目の全てのページを処理 */
	current.prop("Sub tasks").map(
		/* 3階層目が子供を持つか?? */
		current.prop("Sub tasks").empty() ?
		/* 持たなければ自分(3階層目)のステータスをリストで返す */
		[current.prop("Status")] :
		/* 持つ場合は4階層目のステータスを回収 (5階層目はチェックしない) */
		current.prop("Sub tasks").map(current.prop("Status"))
	)
	/* 3階層目の結果はリストが返るので2階層目のデータとフラットに並べる */
	.flat()
)
/* 2階層目の結果はリストが返るので1階層目のデータとフラットに並べる */
.flat()

6. おわりに

今回は、map 関数の説明だけでなく、Formula 2.0 から導入されたリストについて解説したため、少し長くなってしまいました。次回以降はもう少しコンパクトな記事になるかと思います。不定期での連載になりますが、また読んでいただけると幸いです。