今更何言ってるんだ?と思う方もいらっしゃるかもしれませんが、iOS のアプリで「ショートカット」というアプリがあり、「これ神アプリじゃね?」と思ったのでオススメの記事を書いてみました。
プログラマー向けに、「ショートカット」アプリの最初に理解しづらい点や躓きやすいと思った点を説明したいと思います。
何ができるの?
名前からは想像しづらいですが、平たく言えば iPhone 上で各種 API を利用したスクリプトを組んで動かせるうえ、それを共有シートや Siri から呼び出せる アプリです。
共有シートというのは から呼び出せるやつですね。コンテキストメニューみたいな。
例えば以下のようなことができます。
- 画像、音楽、カレンダー、リマインダーなどの iOS の各種 API にアクセスして操作、編集などが可能
- 条件で絞ってデータを取得可能
- そのほか、対応しているアプリ(Evernote など)の各種 API にもアクセスして操作、編集などが可能
- 分割・結合、正規表現、Base64 エンコード・デコードなどある程度の文字列操作などが可能
- 画像の結合、重ね合わせ、トリミングなどの簡単な編集が可能
- Web からダウンロードして加工、保存が可能
-
任意の REST API を呼び出して処理が可能
- JSON の操作もある程度でき、
PUT
やDELETE
のリクエストのほか HTTP リクエストヘッダの指定も可能
- JSON の操作もある程度でき、
-
if
やfor
のような条件分岐、反復制御が可能 - 入力を求める、選択肢から選択させる、音声で入力させるなどのユーザーとの対話が可能
- 作ったショートカットを様々な場所から実行可能
- ショートカットアプリ上のボタン
- ウィジェット
- 共有シート
- Siri ショートカット(任意の音声文字列)
- ホーム画面のアイコン
- カスタム URI スキームへのアクセス
ヤバくないですか?
つまりどんなものが作れるかというと、こんなものが作れます。
- 24時間以内に撮ったスクリーンショットをボタン 1 つでまとめてブログ用に縮小して特定の Dropbox フォルダにアップロード
- 3ヵ月以内に登録された音楽の中から再生数が上位25位までの曲を抽出し、今日の日付を名前にしたプレイリストとして作成
- Home Kit 未対応の Philips hue v1 ブリッジに Siri から REST API を呼び出して照明を操作
- 画像の保存ができないサイトから、共有シートでショートカットを呼び出し、HTML をスクレイピングして画像をアルバムに保存
- Siri に声で問い合わせれば AWS の CloudWatch Alerm の状態を音声で回答させる
- お風呂で Dash Button を押したら IFTTT から iPhone に通知を送り、お風呂上りに通知をタップしたら「ボディーソープ」「シャンプー」「コンディショナー」の選択肢を選ぶだけで、最寄り駅到着時に買い物をするようにリマインダーを仕掛ける 12
- ホーム画面の推しに設定したアイコンをタップすると「おやすみモード」に設定しつつ音ゲーを起動する
みたいなことが出来ます。
元々はサードパーティー製のアプリだったものを現在では Apple が買い取って、Apple 製のアプリだからこそ出来るような機能もあります。
どうやってショートカットを作るの?
「ギャラリー」に色々サンプルがあり、中身(プログラムでいうソースコード)も見れるので、見てみるのが良いです。
ただ、私は最初、この考え方がとっつきづらく理解しづらかったので、今回はその辺りを説明してみようと思います。
私も使い始めて2日やそこらなので、「もっといいやり方があるよ!」という方はぜひコメントをください。
パラダイムは関数型言語に近い
「ショートカット」では「アクション」と呼ばれるものをつなぎ合わせてマクロのように 1 つの処理(ショートカット)を作成します。「アクション」はプログラムでいう「関数」みたいなものです。
ところで、普段「手続き型」「オブジェクト指向」な言語(C、C++、Java、C#、PHP、Ruby、Python、Kotlin、Swift、JavaScript など)をお使いの方は、ステートメント(文)を列挙してプログラムを書く考え方に慣れていると思います。
「ショートカット」はどちらかというと「関数型言語」に近く、基本的に各アクションは評価されると値を返し、それを次に渡します。
冒頭で述べたように、「つなぎ合わせ」るのは「アクション」つまり「関数」でありステートメント(文)ではないため、ここをイメージできないと躓きます。
foo(); // 文同士は独立しており、上から順に実行される
bar();
var x = piyo();
piyo(
bar( // foo の結果は bar に渡る
foo()
)
)
ここまで見て Scheme や Haskell っぽいと思った方は、まさにそんな感じだと思います。Scheme も Haskell もよく知りませんが。
もちろん、「ショートカット」アプリにおいては、順序的には上に置いたアクションから並べた順に実行されます。そのためパイプライン演算子や拡張メソッド(拡張関数)のある言語では、それを使ってメソッドチェーンにしていくようなイメージでイメージすると良いかと思います。
foo
|> bar // foo が実行され結果が bar に渡る
|> piyo
具体例を疑似コードと対応させてみる
例えばさっきのスクショで見てみましょう。
一番上の「入力からテキストを取得」とある「入力」というのはショートカット自体に外部から渡されたパラメーターのことで、実行引数みたいなものです3。これは共有シートから実行されたときに渡ってくるもので、ホーム画面などから起動したときは何もわたってきません(null
/ nil
/ undefined
みたいなものがくるみたいです)。
「入力からテキストを取得」アクション自体はキャストのようなもので、「マッチテキスト」は正規表現にマッチさせて結果を返します。「一致したテキストからグループを取得」は正規表現のマッチ結果を受け取ってグループの一部を返します。「Base64エンコード」はそのままですね。
つまり、疑似コードで表すとこんな感じです。
base64Encode(
getMatchGroup(
regexMatch(
toString(
input
)
, "data:text/html;base64,(.+)"
, caseSensitive: false)
, 1)
, decode: true)
読みづらいのでパイプラインチックな疑似コードに書き換えます。
toString input
|> regexMatch "data:text/html;base64,(.+)" false
|> getMatchGroup 1
|> base64Encode true
あれ?2 行目以降は引数の 1 つをパイプラインで受け取っているかのように書かれているのに、1 行目だけ異質ですね?
実際には input
という変数があるわけではなく、1行目の toString
も上から降ってきた値を第1引数に受け取るようなイメージになります(これが「入力からテキストを取得」の「入力」の意味するところです)。
|> toString
|> regexMatch "data:text/html;base64,(.+)" false
|> getMatchGroup 1
|> base64Encode true
つまるところ、関数型言語の関数合成の考え方に近いんですよね。Haskell なんかだと仮引数を宣言せずに関数の組み合わせだけで関数本体を書く(ポイントフリースタイル)こともありますが、そんな感じです。
逆に、入力されたものを特定の名前の変数として取り扱いたい場合には、後述する「変数を設定」アクションを使う必要があります(それ以外に「マジック変数」で取り出す方法もあります、こちらも後述)。
型とシグネチャは考えるのではなく感じろ。。。
私が「わかりづらいな」と思ったのは、先に見たように「アクションは上から渡されたものを引数の 1 つに取る」「アクションによっては『渡されるもの』に特定の『型』のようなものを想定している(正規表現のグループを取得するやつみたいに)」にも関わらず、それがドキュメントとして明記されていない点でした。
「このアクションは何を(メインの)引数に取るのか」「必要とされる型は何なのか」「戻り値で何を返すのか」「戻り値の型はなんなのか」といったこと、つまり関数のシグネチャにあたるものは、アプリの説明文上ではやんわりとしか書かれていません。
@ImplicitParam("input")
regexMatch(input: String, pattern: String, caseSensitive: Boolean): RegExp
この辺りは使っていくうちに「慣れていく」感じでした。「たぶんこうだろうな」というのが経験則でつかめてきます。
まず知っておくと良いのは、前述の通りほとんどのアクションは「メインの引数」があり、それは 1 つ上のアクションから返された値を自動で受け取るということだと思います。
例えば「URLの内容を取得」というアクションを配置すると「URL」を入れるオプション欄が無くて戸惑います。
これは「メインの引数」であるため、上のアクションから受け取ることになっているためです。どうするかというと、「URL」というアクションがあります。
これはプログラムの抽象構文木で言うところの「URLリテラル」にあたり、URLを値として宣言して下のアクションに渡す役目があります。
new Uri("https://.....azurew....")
※ URLの場合は「テキスト」アクションでも大丈夫のようです。これは文字列リテラルにあたります。動的型付け言語のように、割と柔軟に内部で自動型変換がかかります
この「暗黙的に渡される」「メインの引数」のことを、「ショートカット」アプリ上では**「(アクションの)入力」**と呼びます。また、次のアクションに渡される値(=式でいう評価値のこと)については「次のアクションに渡します」としか書かれていないため、この記事では便宜上「出力」と呼びます。
先ほどのスクショを疑似コードで書くならこういうことですね。
new Uri("https://.....azurew....") // 「URL」アクションに相当
|> sendHttpRequest Method.GET // 「URLの内容を取得」アクションに相当
「アクション」は「関数(Function)」というより「式(Expression)」と捉えるのが良さげ
単なる「URL」が「アクション」といっているのに違和感を感じる人がいるかもしれません。私もそうでした。
冒頭では「アクション」は「関数」みたいなもの、と言いましたが、実際には「式」と考えてもらうほうがより近いと思います。「式」であれば単なるリテラルがあったり関数があったりして、それぞれが値を持つことがイメージしやすいのではないでしょうか。
例えば「テキスト」アクションは文字列を次のアクションに渡しますが、以下のようなリテラルに相当します。
"foobar"
「数字」アクションはこんな感じ
1091
前述の通り、URLアクションは多くの言語だと new Uri
とか new Url
みたいなのに相当する感じでしょうか。仮に専用リテラルがあったとしたらそれに相当する、でもいいです。
new Uri("https://foobar.com/")
URL"https://foobar.com/" // もしもURLオブジェクト専用のリテラルのある言語だとこんなイメージ?
まぁ、「コンストラクタ」を関数の一種と捉えれば、そういう捉え方でもいいかもしれません(実際に Kotlin なんかは new
演算子を付けなかったりしますし。
URL("https://foobar.com/")
変数
「ショートカット」でも変数を使うことができます。
変数は「変数を設定」アクションと「変数を取得」アクションで利用します。他に「変数に追加」というのがあり、これは配列のように変数を扱うときに使います。
「変数を設定」は疑似コードでいうと以下の式です(式なのでそれ自体も値を返すことに注意)。
var hoge = input
式っぽく捉えるなら、PHP のような変数宣言不要な言語でイメージしてもらうほうがイメージしやすいかもしれません(実際、「変数の宣言」というものは「ショートカットアプリ」においては不要です)。
$hoge = input
つまり
-
アクションの入力: 設定される値(上の例では
input
) -
その他の引数: 変数名(上の例では
hoge
)4 -
アクションの出力: 設定された値(=入力がそのまま=
input
=$hoge
)
です。
「変数を取得」はそのまま変数の式に相当します。
hoge
名前(正確には変数への参照)を引数として、評価されるとその名前の値を出力として次のアクションに渡します。
ここからわかるように、一部のアクションは入力を取りません。このタイプのアクションは、入力を無視して捨てます。(逆に出力がないアクションも存在します。関数型言語でいう Unit
を返すイメージ、手続き系の言語でいう void
です)
また、「入力に渡したい」つまり「あるアクションのメイン引数に渡したい」以外の場合、つまりメインではない引数(アクションのオプション)で変数の値を使いたい場合はキーボードの上に変数を選択する画面が表示されるため、そこから選ぶだけでOKです。
大半は「マジック変数」を使うのが便利
「変数を設定」アクションと「変数を取得」アクションを使い始めると、次第にツラみが増してくると思います。
変数に値を退避し、それを他の個所で使おうとするだけで、2回もアクションを検索して配置する必要があるためです。
そこで活躍するのが「マジック変数」です。多くのアクションは「出力」がそのままマジック変数に退避されます。他の個所で使う場合、例えば「変数を取得」アクションで「マジック変数」を選択することで、自分で明示的に変数に退避していない上の方にある値を下の方で取り出すことが出来ます。
「リスト」アクションの結果をマジック変数として取り出している図
つまるところ、「変数を設定」アクションを使うのは~~「名前を付けて取得側の見た目を明確にしたいとき」や~~「マジック変数に退避されないタイミングの値を退避したいとき」ということです。
※マジック変数も名前の変更が可能でした。そのため、多くの場合はマジック変数を使ってしまうのが楽です
まとめ&知っておくべきアクション一覧
ここまでで主要なプログラミング言語の経験者がつまづきやすい(と思っている)ポイントは概ね説明してきました。
ポイントは「文を列挙する」ではなく「式が連鎖する」という考え方です。
いわゆるワンライナーのプログラム(セミコロンを使うタイプの言語経験者は、セミコロンを 1 つしか使えない制限がかかっているみたいな)を想像すればよいと思います。
慣れないうちは「こうしたいのにどうしたらいいかわからない」ということもあるかもしれませんが、考え方がわかってくるとスッと腹落ちするはずです。
最後に、この値は最初に押さえておくと良さそうですよ、というアクション一覧を紹介して終わります。(i)
アイコンから「よく使う項目」に追加できるので活用しましょう。
リテラル系
- テキスト: 文字列型のリテラルを宣言(「文字列」で検索すると見つけやすい)
- 数字: 数値型のリテラルを宣言
- リスト: リスト型の配列のような値を宣言(「リストから選択」と組み合わせてユーザーに選択肢を提示できる)
対話系
-
入力を要求: 入力ポップアップを出す。JS でいう
prompt
。文字列以外に数字、URL、日時の入力もこれ -
メニューから選択: 選択肢をユーザーに提示できる。「リストから選択」と違い、1アクションで実行できるうえ結果の分岐もくっついてくる(
switch
やmatch
のような分岐)
制御構文系
-
次の場合:
if
文。と見せかけてif
式5。式なので分岐内の最後のアクションの出力をそのまま下に渡す。 -
繰り返す:
for
文に近い。と見せかけて(ry。最後に評価された値を返す -
それぞれで繰り返す:
foreach
文 / 拡張for
文 /for of
文などなどに相当。こちらも実際には式。
値の加工系
- 日付を調整: 「現在の日付」から時間や分を切り捨てたいときに重宝
-
統計を計算: 「リスト」と組み合わせて「最大値」や「最小値」を計算させることで
Math.max
やMath.min
と同等のことが出来る。「幅」と「高さ」の「大きい方」を取得したいときとかに「次の場合」を使うより楽 - 辞書: いわゆる連想配列。ディクショナリ。「次の場合」などから複数の値を返したいときに便利6。後述の通り JSON もこの型として扱われる
夢が広がる系
-
URLの内容を取得: 名前に騙されることなかれ、
POST
やPUT
やDELETE
も可能。curl。 - 辞書の値を取得(など): JSON データのこと。先に挙げたとおり連想配列の扱い。特定のキーの値を読み書きしたりが可能
- SSH経由でスクリプトを実行: ぼくはえんじにあじゃないのでくわしくないからパス
開発系
-
クイックルック: IDE のウォッチ機能的な。デバッグは基本これを使う(
print
の代わり) - ショートカットを実行: 他のショートカットをサブルーチンとして呼び出せる