NSString 連結を利用して heredoc 風に定数を記述する

2016年12月6日 0 開発 , , ,

これまで Objective-C による iOS アプリ開発で FMDB を使用するとき、SQL 文は define directive で定義していた。

#define SQL_READ @"SELECT user_id, name, age FROM users WHERE 20 <= age;"

この方法による定数は pre-process で単純なコードに対する置換として動作する。そのため内容が構文エラーであっても置換された結果が構文として正しいなら許容される。C 言語や C++ ではこれを利用して関数名の一部を pre-process で書き換えるなどのハックが横行していたものだ。

define directive のもう一つの特徴として、複数定義された場合は後勝ちになる。例えば

#define SQL_READ @"SELECT user_id, name, age FROM users WHERE 20 <= age;"
#define SQL_READ @""

と定義した場合、後に定義されたものが置換対象となる。この動作は思わぬ事故を招くので ifdefifndef directive により定義状態で条件分岐するものだが、いかにも冗長である。

なお、このような定義を見つけたら Xcode 8.1 は

'SQL_READ' macro redefined

と警告してくれるのだが、それでも後勝ちの定数はそのまま使用されてしまう。これを防ぐためにコンパイラーの警告レベルをあげてエラーにする手もあるが、そうした運用による対策なしに標準でエラーにしたいところ。

また Xcode 4 時代に Cocoa が提供する標準型の定数書式が改善されて NSArray や NSDictionary などを定数化しやすくなった。これらと一緒に定義するとき、文字列だけ define directive なのは違和感がある。

というわけで前述の定数を

NSString * const kSQLRead = @"SELECT user_id, name, age FROM users WHERE 20 <= age;";

と書き換える。グローバル定数にするなら .h で以下のように宣言し、

extern NSString * const kSQLRead";

.m で値を定義する。

NSString * const kSQLRead = @"SELECT user_id, name, age FROM users WHERE 20 <= age;";

ただし NSObject 系を定数として宣言する際はポインターの示すものに注意すること。この辺の話は objective c – "sending ‘const NSString *’ to parameter of type ‘NSString *’ discards qualifiers" warning – Stack Overflow の議論が分かりやすい。Win32 API でプログラミングしていた頃はこうした宣言で事故らないように typedef で抽象化していたが、Objective-C だとこの方法を見かけないので素で書いている。

さて NSString 定数を宣言したところまではよかったが、SQL 文のように長くて可読性を求められるものを単一行に定義するのはつらい。構文に基づいてインデントしたくなる。こうしたとき heredoc を使えると便利。

はたして Objective-C は heredoc をサポートしているのだろうか?もしこの機能がなければ C 言語でおなじみの back slash による連結を利用する手もあるけど、これも色々と問題のある記法なので避けたいところ。

というわけで調べてみたら How to split a string literal across multiple lines in C / Objective-C? – Stack Overflow を見つけた。ここには C 言語と Objective-C 独自の方法が紹介されている。できれば一次情報にあたりたかったのだけど Stack Overflow にもリンクはなく、 Objective-C の言語仕様の日本語訳を見ると

「コンパイラのディレクティブ」に、定数文字列を連結するための 言語サポートについて文書化しました。

こういう更新履歴はあるものの当該項目は見当たらなかった。この記事を読まれた方で情報をお持ちの方はコメ欄や Twitter などで指摘していただけると助かります。

とはいえ紹介されている Objective-C の方法はちゃんとコンパイルできて期待どおり動く。試しに

NSString * const kSQLRead = @""
"SELECT "
  "user_id, name, age "
"FROM "
  "users "
"WHERE 20 <= age;";

と定義した SQL 文が FMDatabase – executeQuery で動作することを確認できた。NSLog に渡すと連結された結果がちゃんと出力される。

連結により文字列の定義における自由度は格段に向上した。好きなようにインデントできて嬉しい。ただし以下の機能が不足しているため heredoc と呼ぶのは語弊があるだろう。

  • 改行位置への改行コード自動挿入
  • 変数の展開

よって控え目に heredoc 風としておく。それともうひとつ注意点がある。この機能は単に複数の文字列を連結しているだけなので、

NSString * const kSQLRead = @""
"SELECT"
  "user_id, name, age"
"FROM"
  "users"
"WHERE 20 <= age;";

のように各行末から空白を取り除いてしまうとそのまま

NSString * const kSQLRead = @"SELECTuser_id, name, ageFROMusersWHERE 20 <= age;";

こんな感じに連結されてしまい SQL の構文エラーとなる。そのため結果を意識しながら定義すること。SQL 文のインデントを目的とするなら、

  • Syntax とそれにぶら下がる単位で分割
  • 各行の末尾に空白を入れる

とするのがよいだろう。空白の重複は無視されるので「行末へ空白を入れる」ぐらいの単純化されたルールでもよい。

そんなわけで今後 SQL 文を定義するときは heredoc 風で書くことにした。あと Objective-C で本物の heredoc がサポートされることを願っている。

Rails 5.1 のフロントエンド環境改善について

2016年12月5日 0 雑感 , ,

Rails5.1に向けてフロントエンド周りで起こっている革命まとめ – Qiita という記事のはてブにこうコメントした。

npm と npm-scripts を標準にして「yarn や webpack も選べます」としたほうがよいのでは。普及していても拡張は拡張なので衰退も視野に入れるべきと考える。

私は Web フロントエンド開発者であり npm-scripts で Web フロントエンド開発を管理する に書いたとおり特定のタスクランナー依存を危惧している立場なので、その旨をコメントしてみた。

元記事にも引用があるとおり Rails は jQuery 依存をやめようとしているのだが、ならば yarn や webpack 依存を増やすのも慎重になったほうがよい。これらは素晴らしいプロダクトだが jQuery がそうであったように支配的ともいえるシェアを持ってもなお、廃れることは十分にありえる。

ならば Node や npm-scripts のように処理系の標準を Rails も標準とし、yarn や webpack はオプションにすることでプロダクト依存を緩和したほうがよいと考えている。

という意図だったのだが、同ブコメで id:mrkn 氏 の書いた

ここで「〇〇でいいのか?」とか「webpack は心配」とか言ってないで、識者の方々は Rails の現場で発言してくださいな。

というコメントにハッとさせられたので、元記事に紹介されている issue へ私の見解をコメントしてみた。

Rails については Redmine ユーザーとして間接的に関わっているだけで識者と呼べるような見識や経験はない。また議論の流れすべてに目を通せたわけではなく英語の拙さもあって外しているかもしれない。それでも興味の対象へ公式に言及する手段が提供されているなら、物怖じせず意見したほうがよいと判断した。

反応があるか、それがよいものとなるかは分からないけど、外野コメントだけではもったいない。議論の場は開かれているし僅かにでも影響を与えられるのなら参加したほうが得なはず。今後もこういう機会があれば積極的に提案してゆきたい。

技術の流行についてゆくこと

2016年11月4日 0 雑感

以下の記事とはてブを受けての所感。

JavaScript による Web フロントエンド開発環境について総括する記事があると、はてブでは拒否反応が多く見られる。特にフレームワークやライブラリの乱立や JavaScript/CSS の Transpiler 周りに忌避感があるようだ。

確かに複雑である。しかし「それが何の問題を解決しているのか?」に注目すれば単純な要素技術の集合であることが理解できるはず。

例えば Browserify、webpack、Babel などの Transpiler 系と ES2015 や TypeScript の関係は、JVM や .NET Framework のような共通ランタイムとその上で動く言語を当てはめてみればよい。Web ブラウザで動作する JavaScript という共通ランタイムがあり、これらのツールや言語はランタイムがサポートしていない構文や機能を提供してくれる。好きな言語で書いて、それを共通ランタイムたる JavaScript に変換しているだけだ。

npm も単なるパッケージ管理システムである。いまやどのプラットフォームでも当たり前のように使用されている。こういうのに縁遠そうな Microsoft ですら NuGet を提供している。

こうやって「XXX は YYY の ZZZ だ」と当てはめてゆけば案外、簡単に理解できるものだ。適合するものがなければそれは「新しい問題を解決している」のであり、もしそれに価値があるなら、いずれ他のプラットフォームにもやってくる。例えば最近だと .NET で流行した Reactive Extensions 実装が RxJava や RxJS として移植されている。

なにか大きく複雑なものを前にすると怯んでしまう。それは大きく複雑な問題をそのまま捉えようとすることがよくないのであって、小さく分割して各個撃破すれば大したことない。

私は 2 年前、久しぶりに Web フロントエンド開発へ戻ってきたが、この考えにもとづいて既知の概念とそうでないものをより分けながら理解した。その過程で Web という難物へ取り組んでいるわりに、意外と常識的な技術や仕様策定プロセスを採用していることに感心した。これは HTML5 前夜の混乱を踏まえた反省もあるのだろうけど。

要素技術の趨勢が移り変わるはやさを危惧するとして、他のプラットフォームでも先端をながめたら同じこと。Web フロントエンド開発については、2015 年に ES2015 と以降の道筋が示されたこと、npm によるパッケージ管理が根付いたことから環境面の素地は固まったと認識している。これらが核であり、その周辺は好みによる選択でしかない。

jQuery に慣れていてプラグイン資産に魅力を感じるなら使い続けてもいい。私も以前、ES2015 で書きつつ View 部分を jQuery で組んでいた。たくさん並んだ新興技術も要素と認識すれば、このように好きなものだけ部分導入できる。

そんなわけで、未知の概念がたくさん登場して面食らったら、ひとつずつ既知の概念に変換してゆこう。これが技術の流行についてゆくコツだと思う。もしそれでも未知が多ければ、それは対象とする分野そのものが新しいか経験不足のいずれかである。

もうひとつ。

冒頭で取り上げた記事は最近の Web フロントエンド開発の複雑さに対する皮肉だが、こういうものに乗っかった批難や愚痴は不毛である。共感するにしても苦笑いで済ませるようなものであり、言及するならば否定を超えて批評を目指したい。

言及する程度に興味があるのなら批評は可能なはずである。