Quantcast
Channel: rana_kualuの記事 - Qiita
Viewing all 345 articles
Browse latest View live

一度入れたJavaScriptライブラリは二度とアップデートされることはない

$
0
0

以下はCloudflareによるレポート、JavaScript Libraries Are Almost Never Updated Once Installedの日本語訳です。

JavaScript Libraries Are Almost Never Updated Once Installed

Cloudflareは、WebページにJavaScriptやその他のフロントエンドリソースを配置するための一般的な方法である、CDNJSを支援しています。
今回我々は、CDNJSチームの許可を得て、CDNJSへのリクエストから匿名化・集約されたデータを収集し、インターネットでWebサイトがどのように構築されているかを分析することにしました。
今回のエントリでは、ひとつの疑問に焦点を当てています。
すなわち、いちどJavaScriptライブラリをサイトにインストールしたあとで、ライブラリは更新されるでしょうか?

まずは地球上でもっとも人気のあるライブラリ、jQueryから始めましょう。
このチャートは、過去12か月間の、jQueryのバージョンごとのリクエスト数を表しています。

01.png

2019年5月2日のリリース以降、バージョン3.4.1のリクエスト数は着実に増加しています。
しかし、古いバージョンが減少しているということはありません。
バージョン3.2.1は、チャートのはじめには一日あたり3600万のリクエストがありましたが、一年の終わりには2900万リクエストとなり、20%減少しています。
しかしこれは、平均的なWebサイトは2~4年しか保たないというリサーチの結果と一致しています。
バージョン3.4.1は着実に普及しつつありますが、しかしそれに伴って古いバージョンが減少するような傾向は全くありません。

ちなみにCDNJSに入っているjQueryの最も古いバージョンは、2013年5月25日にリリースされた1.10.0です。
これはいまだに一日平均10万件のリクエストがあり、それらを使っているサイトの人気も依然として衰えていないどころか、さらに高まっています。

02.png

jQueryだけの特異な現象でないことを示すために、別のプロジェクトTweenMaxも見てみましょう。

03.png

このパッケージは利用者がjQueryほど多くないため、データは一週間ごとの平均で平滑化されています。

バージョン1.20.4は1800万件のリクエストで始まり、1400万件で終わりました。
Webサイトの消滅割合と概ね同じ23%の減少です。
2.1.3の大きな成長は同時に、新バージョンのリリースが旧バージョンの人気にほとんど影響を与えないという明白な証拠を示しています。
2.1.3が2900万リクエストまで増加しても、旧バージョンへのリクエスト数は全く変わっていません。

04.png

結果として言えることは、一度公開されたライブラリを使うWebサイトは永久に存在し続けるということです。
従って、Web基盤プラットフォームは、あらゆるWebをサポートするためには、どれほど古いライブラリでも無期限に提供し続けなければなりません。

Cloudflareは、最新のWebにコントリビュートすることに非常に興味を持っています。
ぜひコメントで提案してください。

コメント欄

「CDNJS依存関係を自動でバージョンアップするRenovate Botってのを作ってる。」
「古いライブラリ使ってる人に通知するとか。」
「Wappalyzerみたいなかんじでバージョンが古いのを一目でわかるようにするとか。」
@majorみたいなセマンティックバージョンを提供するとか。」「マイナーバージョンで互換性なくすアプデしてくるライブラリを腐るほど見たからまあ無理だろ。」
「CDNが落ちてた時にローカルのライブラリを読ませてるんだけど、バージョン揃えるのが面倒。自動更新とかできない?」「ASP.NETだけどそういうのはある。」「jQueryにもあった気がする。」

感想

要するに、継続的なんちゃらだとかリーンなんちゃらだとか言ってるのは業界の極一部でしかなく、大半のサイトは一度作ったらそれっきりということです。
実際そんな最前線を走り続けなければならないサイトなんてほとんどありませんからね。
GAFAMでも配信サイトでもない一般人のサイトが最新技術を駆使してます!なんて言われてもどうでもよくない?

コメントで言っているセマンティックバージョンはこれのことです。
ざっくり言うと『互換性の無いバージョンアップはメジャーバージョンを上げろ』ですが、これが守られているライブラリなんて存在しないことは周知のとおりです。

といってもさすがに今どきjQuery1は、さすがに古すぎますね。
jQuery2が出たのが2013年4月。
当時小学校に入った子供が既に中学生になっているほどの昔ですよ。

あと今回見せてるのはjQueryとTweenMaxだけなので、VueとかReactとか最近のやつもちょっと見てみたかったですね。


PHPでFizzbuzz最短43バイト

$
0
0

今回は解決できてませんし、解決の糸口すら掴めていません。

PHPのFizzBuzz最短は長らく56バイトでした。

<?while($i++<100)echo$i%3?!$$i=$i:Fizz,$i%5?$$i:Buzz,~;<?for(;$i++<100;)echo$i%3?!$$i=$i:Fizz,$i%5?$$i:Buzz,~;

ところで先日なんとなくshinh.orgを見てみたんですよ。

01.png

43?????????

他の回答者がみな56バイトで横並びになっているところに突然3/4の長さですよ。
しかもここ、もはや古代語と言って差し支えないPHP5.3.8ですからね。
一体全体どうなってんだ?
完全に理解の範疇を超えています。

さらに目を引くのが0B / 30B / 13Bという文字の使用割合です。
これは基準が公開されていて、順にバイナリ0バイト、英数30バイト、記号13バイトを使っているという意味になります。

56バイト達成者は全員1B / 26B / 29Bとか1B / 28B / 27Bのような割合になっているので、43バイト達成者は記号が圧倒的に少ないです。
さらに改行を表すchr(245)のバイナリを使っていません。

しかしですねここまでくると、最低限必要そうな記号を上げていくだけで、
・PHP開始タグ<?
・ループwhile($i++<100)
・改行および末尾"\n";

これだけで既に記号を12文字使ってるのですよ。
記号の削減は、小手先の変更でどうにかなるレベルではありません。
どうやら根本的に考え方を変えなければならないようです。

が、正直何一つ思いつきません。

私にはもう、<?=file_get_contents("path/to/fizzbuzz");みたいな何らかの回避ルートを見つけ出したとしか考えられません。
まあ、最近の他のコンテストを見てみると見事に何かされた形跡があるので、それが正解なのかもしれませんけどね。

ちょっと私のレベルではどうにもできなかったので、誰か答えよろ。

C#でMySQLからSELECTした結果を取り出したい

$
0
0

C#は素人なのと、あとコードは再現なので不適切なコードになっている可能性が高いです。
おそらくもっといい解法があるはずですが、調べた限りではよくわかりませんでした。

課題

テーブルAからSELECTする、テーブルBからSELECTする、その後ふたつのデータを色々やって最後にテーブルCにインサートする、みたいなことがやりたかったわけですよ。
コード側で色々と処理を行う必要があるため、一度コード側にデータを引き取るのが前提です。

問題

ベストプラクティスがわからない。
適切なサンプルコードが見付からない。

信頼できるドキュメントはMicrosoft公式くらいしかないわけですが、そこに載ってるサンプルコードはどうにも役に立ちません。

https://docs.microsoft.com/ja-jp/azure/mysql/connect-csharp
https://docs.microsoft.com/ja-jp/dotnet/api/overview/azure/mysql?view=azure-dotnet

公式サンプルの例
using(MySqlConnectionconn=newMySqlConnection(connectionString)){conn.Open();// SQL発行MySqlCommandselectCommand=newMySqlCommand("SELECT * FROM MyTable",conn);MySqlDataReaderresults=selectCommand.ExecuteReader();// 行ごとにループwhile(results.Read()){Console.WriteLine("Column 0: {0} Column 1: {1}",results[0],results[1]);}}

ループ中でログ出力する例しか載ってない。
そんな役に立たないコードじゃなくてもっとこう、取得結果をreturnして他所で使い回す方法はないんですかね。

しかもresults[0]とか、今どき列番号で取ってくるなんて有り得ないじゃろ。
列名で取ってきてくれよ。

やってみた

// メインpublicstaticvoidMain{MySqlConnectionmySqlConnection=newMySqlConnection("ConnectionString");mySqlConnection.Open();vartableA=getTable("tableA");vartableB=getTable("tableB");}// 取得publicasyncTask<DataReader>getTable(stringtableName){using(varcommand=mySqlConnection.CreateCommand()){command.CommandText=$"SELECT * FROM {tableName}";using(varreader=command.ExecuteReaderAsync()){returnreader;}}}

できた。

はい、これThere is already an open DataReader associated with this Connection which must be closed firstとか言われて死にます。
どうもDataReaderは同時にひとつしか開けないらしい。
二つ目のSQLを投げる前に、一つ目のSQLはCloseしなくてはなりません。
Closeすると当然データが取れなくなるので、Closeする前に値を取り出しておかねばならないということです。

少し変更した

DataReaderから値を取得して、Dictionaryに突っ込んで返すようにしました。

staticMySqlConnectionmySqlConnection;publicstaticvoidMain{mySqlConnection=newMySqlConnection("ConnectionString");mySqlConnection.Open();vartableA=getTable("tableA");vartableB=getTable("tableB");}// 取得publicDictionary<string,Dictionary<string,string>>getTable(stringtableName){using(varcommand=mySqlConnection.CreateCommand()){command.CommandText=$"SELECT * FROM {tableName}";using(varreader=command.ExecuteReader()){if(reader.HasRows){while(reader.Read()){JObjectline=newJObject();for(inti=0;i<reader.FieldCount;i++){line.Add(reader.GetName(i),reader.GetString(i));}ret.Add(reader.GetString("id"),line);}}}}returnret;}

これでできた!
と思いきや、何故かこれでもalready an open DataReaderが発生することがあります。
なんで?returnする前にusingの外に出てるやろ?
原因がさっぱりわからなかったので諦めました。

しかも、どうやらその行の列を全て取得するみたいなメソッドがどうも存在しないっぽい。
いちいちFieldCountで列数を調べて全列をループで回して取り出しています。
SQL発行する度に毎回行×列のループを回さねばならないとか、本当かこれ???

そもそも最初からfetchAllがあれば解決なのですよ。
どうしてこの程度のメソッドが用意されてないのですかね?

とりあえず完成

mySqlConnectionを毎回開けばええんや。

publicstaticvoidMain{vartableA=getTable("tableA");vartableB=getTable("tableB");}// 取得publicDictionary<string,Dictionary<string,string>>getTable(stringtableName){using(MySqlConnectionmySqlConnection=newMySqlConnection("ConnectionString")){mySqlConnection.Open();using(varcommand=mySqlConnection.CreateCommand()){command.CommandText=$"SELECT * FROM {tableName}";using(varreader=command.ExecuteReader()){if(reader.HasRows){while(reader.Read()){varline=newDictionary<string,string>();for(inti=0;i<reader.FieldCount;i++){line.Add(reader.GetName(i),reader.GetString(i));}ret.Add(reader.GetString("id"),line);}}}}}returnret;}

いや……一応これで動いたんだけどさあ、どう考えてもおかしいだろ、これ。
SQLを発行するたび毎回MySqlConnectionをnewしてるってことは、PHPで言うと毎回new PDOってしてるってことですよね。
そんな書き方ありえなーい。
そもそも高々SQL発行するだけなのに何重に括らせる気なんだよ。

なんかもっとこう、ちゃんとした正しい書き方ってのがあるよね?
あると思うのですよ。
間違いなく存在するでしょう。

実はありまぁす

DataTableってのがあった。

publicstaticvoidMain{vartableA=getTable("tableA");vartableB=getTable("tableB");}publicDataTablegetTable(stringtableName){DataTabletbl=newDataTable();using(MySqlConnectionmySqlConnection=newMySqlConnection(builder.ConnectionString)){mySqlConnection.Open();using(varcommand=mySqlConnection.CreateCommand()){command.CommandText=$"SELECT * FROM {tableName}";using(varreader=command.ExecuteReader()){tbl.Load(reader);}}}returntbl;}

これでDataTableに全データが入ってくるようで、MySqlConnectionを閉じた後でも問題なく参照することができました。

どう考えても真っ先にこれを紹介すべきなのに、普通に調べたら全然出てこないとかいう。
ここまで調べるのにめっちゃ時間かかった。

なおMySqlConnectionを閉じないとエラーになるのは相変わらずです。
どうやったら使い回せるんですかね?
それ以前に、そもそも本当にコレが最終解なんですかね?

その他

C#では特に目立つ気がするのですが、Stackoverflowを機械訳したようなゴミURLばっかり引っかかってくるのはどうにかならないんですかね?

CNAMEトラッキングってなあに?

$
0
0

先日、というにはだいぶ前ですが、ChromeがサードパーティCookieを禁止すると発表しました。

そこらへんを見ていたところCNAMEトラッキングとかいう楽しいワードを発見したので、自分の理解のためにまとめたものです。
従って正確性を欠いている部分が多々あります。
正しい解説は既に色々別なところが出しているので、そこらへんを見た方がいいと思います。

Cookieの概要

CookieはFQDN単位で発行されます。
たとえばqiita.comで発行したCookieは、次回以降qiita.comにアクセスした場合に自動的に送信されます。
blog.qiita.comteams.qiita.comにアクセスした場合は、本来は送信されません。

サブドメインが異なるだけでも別サイトと判定されるので、たとえばニコニコ動画にログインしてからニコニコ生放送に行ってもニコニコ生放送ではログアウト状態になってしまいます。
これでは使い勝手が悪すぎるので、Domain属性を使ってCookieを送信する範囲を指定することが可能です。

QiitaのCookieはdomain=.qiita.com;という指定がされていますが、これを指定すると*.qiita.comの全てのドメインに同じCookieが送られるようになります。
これによって、zine.qiita.comjobs.qiita.comなどのサブドメインに進んでも同じCookieが送信されるようになるため、ログイン状態を継続することができるようになります。

この指定はサブドメインより上には行けません。
すなわち、domain=.com;と書いてあらゆる.comにCookieを送信するとか、*.ebis.ne.jpとか書いて全然関係ないサイトにCookieを送信するといったことはできない、ということです。
安全性を考えると当然ですね。

サードパーティCookieとは

ざっくり言うと、サイトAのCookieを別のサイトBから読み書きできる、というものです。
こんなものトラッキング以外の何に使えというのですかね。

サイトAのindex.html
<script src="サイトB/tracker.js"></script>
サイトBのtracker.js
    document.cookie = xxx;

tracker.jsが発行したCookieは、サイトAのものになります。

主体となるサイトAが、能動的にサイトBのJSを読み込む必要があります。
従ってサイトAが何らかの目的でJSを仕込むという使用法が主な使い方であり、サイトBが無差別に他所のCookieを読むようなことはできません。

これを何に使うかというと、まずはアクセス分析ですね。
CookieからセッションIDを取り出せば、誰がどのページをいつ見ていたかといった内容が集計できます。
そのようなものを個々のサイトでいちいち構築・管理するのは大変なので、Googleアナリティクスのような分析サービスが登場しました。
サードパーティCookieを使えばscriptタグをひとつ置くだけで簡単に集計できるので、多くのサイトが使用しています。

もちろんアクセス分析だけではなく、広告にも使われています。
どのようなページを多く見ているかを調査し、その人の性癖に最適な広告を表示するわけです。

サードパーティCookieの対応状況

Firefoxはデフォルトで禁止すると明言しています。
SafariもEdgeも、意外なところでは旧IEも、サードパーティCookieは2010年代前半あたりまでにデフォルト禁止になっていました。
最近までデフォルト許可のまま残っていたのはChromeだけだったということです。

2020年にようやくChromeがデフォルト禁止への変更を発表したことで、ついにサードパーティCookieが滅びます。
めでたしめでたし。

サードパーティCookieの歴史

2011年あたりまでの歴史はサードパーティCookieの歴史と現状を読めば解決。
当時の結論はデフォルトブロックは無理、DNT使えという風潮だったようですが、DNTなんて筋の悪い方法が有効に機能するわけもなく、ほとんどのブラウザはその後デフォルトブロックに舵を切りました。

ファーストパーティCookieとは

同じドメイン、もしくはサブドメインのCookieです。

サイトAのindex.html
<script src="サイトAのサブドメイン/tracker.js"></script>
サイトAのサブドメインのtracker.js
    document.cookie = xxx;

要するに普通のCookieです。
Domain属性を適切に設定すれば、サブドメインにもCookieが飛ぶのは上に書いたとおりです。

この仕組みを悪用したものがCNAMEトラッキングです。

CNAMEトラッキングとは

DNSレコードを以下のように設定します。

www IN A 192.168.0.1
ac IN CNAME tracker.ebis.ne.jp.

どうなるかというと、www.example.comへのリクエストは192.168.0.1に飛びますが、ac.example.comへのリクエストはtracker.ebis.ne.jpに飛ぶようになります。
見た目はac.example.comであり、example.comのサブドメインになっているのでCookieも飛びます。

見事にサードパーティCookieの制限を突破して、別のサーバでCookieを受け取ることができるようになりました。
サブドメインなので、サードパーティCookieのようにユーザ側で止める方法もありません。
いやあ斬新な回避策もあったものです。

問題点のひとつとしては、自社の管理するドメイン名の一部を他社に明け渡すことになるので、もしそちらで不祥事が起きたらこっちのせいになるということでしょうか。
いや違うんだ、ac.example.comで起きたんじゃないんだ、tracker.ebis.ne.jpが起こしたんだ、とか言ってもわかりませんしね。

CNAMEトラッキングへの対策

CNAMEトラッキングについて、ユーザ側ではわりとどうしようもありません。
しかし一部のブラウザはこれも止めてしまうことを考えているようです。

Intelligent Tracking Prevention

Intelligent Tracking Prevention、通称ITPは、Safariが実装しているトラッキング対策です。
元々はサードパーティCookie向けの機能だったようですが、最近は機能が拡張しています。
https://example.com/foo/bar/bazから別サイトに飛ぶとリファラをhttps://example.com/に切り詰めたり、怪しいサイトからローカルストレージへのアクセスを禁止したりといった機能があるようです。

そして最近では、別のドメインなのにひとつのIPアドレスであればトラッキングと判断してNGする機能が追加されました。
CNAMEトラッキングを殺しにきた仕様ですね。

とにかくトラッキングは皆殺しだ、という発想です。
まあAppleなら、わざわざブラウザ経由で情報を抜かなくてもiPhone自体から抜き放題ですからね。
無関係な他社にまで情報を渡す必要は全くありません。

プライバシーサンドボックス

ハードウェアを売れるAppleと違って、Googleは広告が打てなくなると死ぬので広告規制には慎重です。
最近の潮流には逆らえず規制を強化しようとはしていますが、その方向性はプライバシーを侵害せずに広告を打てるというものです。

まあ、いちユーザの感想としては『絶滅させろ』なんですけどね。

今後はどうなる?

今後も様々な手段によってトラッキングがなされ、トラッキングがブロックされ、が繰り返されていくことでしょう。
CNAMEトラッキングも寿命はさほど長くなさそうです。

しかしひとつだけ、絶対にブロックされないトラッキングがあります。
すなわち、メインコンテンツと全く同じドメインで全く同じIPアドレスであればよいということです。

ということで、次世代のトラッキングはこれだ!

・サーバにはプログラムファイルをひとつ置くだけ。言語はサーバサイド言語であればなんでもいい。
・中身はトラッキングサーバからJavaScriptを取ってきて出力するだけ。
・クライアントからはscriptタグでそれを読み込む。

PHPのサンプル
header('Content-Type: application/javascript; charset=utf-8');echofile_get_contents(sprintf('https://tracking.example.com/?phpsessid=%s&referer=%s',session_id(),$_SERVER['HTTP_REFERER']));

このJavaScriptは同じドメインにあるので、サードパーティCookieやらサブドメインやらの制限で止めることはできません。
なにしろ見た目は完全に正規のリクエストですからね、さすがにこれをブラウザが勝手に止めてしまうと大問題でしょう。

うむ、なんか大昔に見たことがあるようなシステムだな。

ともかくこれで止めようのないトラッキングが完成しました。
HTMLにscriptタグを埋め込むだけ、というお手軽さからは少しだけ遠のいてしまいましたが、それでも手間が少し増えるだけです。
めでたしめでたし(めでたくない)

まあそれでも、素人の私には思いつかない方法で止めてくるかもしれませんけどね。

【PHP8】もう`strpos($haystack, $needle)!==false`って書かなくていいんだ

$
0
0

ある文字列中に特定の文字列が存在するかを調べる方法としてstrposが存在します。

しかし、そもそもstrposは『ある文字列中で特定の文字列が何文字目に出てくるか』を調べる関数であり、第一に使用目的が異なる関数です。
そしてこちらも有名な話ですが、先頭が一致すると0が返ってくるので、緩やかな比較ではfalseと区別されません。
PHPのよくある落とし穴のひとつです。

if(strpos('放課後アトリエといろ','アトリエ')){echo'"放課後アトリエといろ"には"アトリエ"が含まれる';// 表示される}if(strpos('放課後アトリエといろ','放課後')){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示されない!!}if(strpos('放課後アトリエといろ','放課後')!==false){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示される}

マニュアルにも大きく警告が出されています。
01.png

この状況がついにPHP8で変わります

PHP RFC: str_contains

Introduction

str_containsは、ある文字列に特定の文字列が含まれているかをチェックし、見つかったか否かによってtrue/falseいずれかのbooleanを返します。

ある文字列に特定の文字列が含まれているかをチェックするために、一般的にはstrposstrstrが使用されます。
str_containsは凡そあらゆるプロジェクトで非常によく使われるユースケースなので、独自の関数を追加するに値するはずです。
strposstrstrには、いくつかの欠点が存在します。

・あまり直感的ではない
・間違いをしやすい(特に!==の場合)
・新規PHP開発者が覚えにくい

このため、多くのPHPフレームワークは独自のヘルパ関数を提供しています。
このことこそがstr_containsの重要性や必要性をよく示しています。

Proposal

このRFCは、新しい関数str_containsを提唱します。

str_contains(string$haystack,string$needle):bool

引数$haystack$needleを取り、$haystackの中に$needleが存在するか否かをbooleanで返します。

str_contains("abc","a");// truestr_contains("abc","d");// false// 空文字列はtrueになるstr_contains("abc","");// truestr_contains("","");// true

空文字列の扱いについて

As of PHP 8, behavior of '' in string search functions is well defined, and we consider '' to occur at every position in the string, including one past the end. As such, both of these will (or at least should) return true. The empty string is contained in every string.

PHP8において、文字列検索関数において''の動作は明確に定義されており、終端を含む文字列全ての位置にマッチします。そのため、これらは両方ともtrueを返すべきです。空文字はあらゆる文字列に含まれています。 - Nikita Popov

Case-insensitivity and multibyte strings

internalメーリングリストなどの議論において、この関数のマルチバイト対応版(mb_str_contains)は必要ないという結論に達しました。
理由として、この関数のマルチバイト版は非マルチバイト版と全く同じ動作になります。
文字列の見付かった位置によって動作が異なる場合は、マルチバイト版は異なる動作になります。
この関数は見付かった位置によって動作は変わらないので、マルチバイト版の必要はありません。

大文字と小文字を区別しない版については、需要が大文字と小文字を区別する版よりはるかに低いため、このRFCには含まれません。
区別しない版を取り込むと、有効なバリアントはstr_contains/mb_str_icontainsだけになります。
このように中途半端なバリアントをいきなり提供するとPHP開発者が混乱する可能性があるので、最初は小さく始めた方がよいでしょう。

Backward Incompatible Changes

PHP自体に後方互換性のない変更はありません。

ユーザランドに同じ関数が実装されている場合、非互換の可能性があります。
しかし、そのようなグローバル関数はアプリケーションの起動プロセスのかなり早い段階で追加されるので、開発者はこの問題にすぐ気付くでしょう。

Proposed PHP Version(s)

PHP 8

Implementation

str_contains関数は、このプルリクで実装されています。
https://github.com/php/php-src/pull/5179

投票

投票は2020/03/16まで、受理には2/3の賛成が必要です。
2020/03/09時点では賛成43反対5で、まず間違いなく受理されます。

感想

よく考えたらずっと前からあってもおかしくない関数なのに、これまで存在してなかったのは不思議ですね。

実際のところ、文字数としてはわずか2字の差にすぎません。

if(strpos($heystack,$needle)!==false){echo'$heystackに$needleが含まれている';}// 同じif(str_contains($heystack,$needle)){echo'$heystackに$needleが含まれている';}

しかし、読みやすさ、理解のしやすさという点では圧倒的にstr_containsに分がありますね。

以下はマルチバイト対応関数が用意されない点の補足です。
マルチバイト対応strposとしてmb_strposがありますが、文字が存在するか否かをチェックするだけであれば実はstrposでも問題ありません。

echostrpos('あい','い');// 3echomb_strpos('あい','い');// 1echostrpos('あい','あ');// 0echomb_strpos('あい','あ');// 0echostrpos('あい','う');// falseechomb_strpos('あい','う');// false

値が変わるのは『存在したときの文字数の数え方』だけです。
文字列の有無を確認するだけであれば、存在しない場合は常にfalse、存在する場合は常にint型となって差が出ないわけですね。

同様にstr_containsも、存在しない場合は常にfalse、存在する場合は常にtrueなるため、あえてmb_str_containsを用意する必要はないというわけです。

【MDN】非同期JavaScriptの紹介

$
0
0

MDNに非同期 JavaScriptという学習項目があるのですが、目次以外は日本語がありません。
ということで以下はIntroducing asynchronous JavaScript項目の日本語訳です。

Introducing asynchronous JavaScript

この記事では、同期JavaScriptにまつわる問題点を簡潔に要約します。
そして非同期JavaScriptのテクニックの幾つかを紹介し、それぞれが問題点の解決にどのように役立つかを示します。

前提条件:基礎的なコンピュータリテラシー、JavaScriptの基礎をある程度理解していること。
目的:非同期JavaScriptとは何か、同期JavaScriptとは何が違うのか、どのようなユースケースが存在するか、ということを理解する。

同期JavaScript

非同期JavaScriptが何であるのかを理解するためには、まず同期JavaScriptが何であるかを理解することから始めなければなりません。
このセクションでは、前回の記事で現れた情報の一部を要約します。

これまでに学習してきた多くの機能は、そのほとんどが同期です。
幾つかのコードを実行してみると、ブラウザはそれを実行すると同時に結果を返します。
簡単な例を見てみましょう。
実際の動作はこちらで、ソースはこちらで見ることができます。

constbtn=document.querySelector('button');btn.addEventListener('click',()=>{alert('You clicked me!');letpElem=document.createElement('p');pElem.textContent='This is a newly-added paragraph.';document.body.appendChild(pElem);});

このブロックは、処理が上から順番に実行されます。

  1. 利用可能な要素をDOMから探し出す。
  2. クリックしたときに発火するclickイベントリスナーを設定する。
    1. alert()でメッセージボックスを出す。
    2. アラートが消えたら<p>要素を生成する。
    3. そこにテキストを登録する。
    4. 最後にドキュメントボディに要素を追加する。

それぞれの処理が行われている間、他には何も起こりません。
レンダリングは一時停止されます。
これは、前回の記事で述べたように、JavaScriptがシングルスレッドであるためです。
ひとつのスレッドで一度に発生させることができるイベントはひとつだけであり、他のすべてはイベントが終了するまでブロックされます。

従って上記の例では、ボタンをクリックすると、その後アラートでOKボタンを押すまで、次の段落は表示されません。
実際に以下で試してみることができます。
https://codepen.io/pen/?&editable=true
https://jsfiddle.net/api/mdn/

注意:alert()は同期によるブロッキングを示すためのデモンストレーションとして使うのには最適ですが、実アプリで使用するととても残念なことになるので注意が必要です。

非同期JavaScript

前述のブロッキングといった問題を回避するため、多くのWeb APIは非同期コードを使って実行されるようになりました。
特に何らかの外部デバイスにアクセスしたりフェッチしたりするもの、たとえばネットワーク経由でファイルを取得する、データベースにアクセスしてデータを返す、Webカメラからビデオストリームにアクセスする、VRヘッドセットに出力をブロードキャストする、といったものです。

同期JavaScriptでこれらを実現するのが難しいのは何故でしょうか。
以下に簡単な例を見てみましょう。
サーバから画像を取得する場合、即座に結果を得ることはできません。
すなわち、以下の擬似コードはおそらく動作しないだろう、ということです。

varresponse=fetch('myImage.png');varblob=response.blob();// 画像を何処かのUIに表示する

理由は、画像のダウンロードにかかる時間がわからないためです。
時間がかかった場合、2行目に辿り着いた時点でresponseはまだ使用できません。
従って、時々あるいは毎回、2行目でエラーが発生することでしょう。
これを回避するため、responseを使用するときはresponseが使用可能になるまで待機しておく必要があります。

JavaScriptで扱える非同期コードのスタイルは主に2種類が存在します。
古いスタイルのコールバック形式と、Promiseを使った新しい形式のコードです。
以下のセクションでは、それぞれを順に解説します。

非同期コールバック

非同期コールバックは、バックグラウンドで動作するコードを呼び出すときにパラメータとして指定する関数です。
バックグラウンドコードは、実行が終了するとコールバック関数を呼び出して、自分が終了したことを知らせます。
あるいは特に注意すべきことが発生した際に通知します。

非同期コールバックは少々時代遅れになりつつありますが、いま一般に使われている歴史の長いAPIではまだまだ使用されています。
以下は非同期コールバック関数をaddEventListener()の第二引数に与える例です。

btn.addEventListener('click',()=>{alert('You clicked me!');letpElem=document.createElement('p');pElem.textContent='This is a newly-added paragraph.';document.body.appendChild(pElem);});

第一引数には監視するイベントのタイプを指定し、第二引数がイベントが発生したときに呼び出されるコールバック関数です。
コールバック関数をパラメータとして別の関数に渡す際、別の関数を実行した時点でコールバック関数まで実行されることはありません。
別の関数内部の何処かで非同期的にコールバックされます(これが名前の由来です)。
別の関数は、対象のイベントが発生したときにコールバック関数を実行します。

コールバック関数を含む関数は独自に実装することが簡単にできます。
ここではXMLHttpRequest APIを使った例を見てみましょう。
実際の動作はこちらで、ソースはこちらで見ることができます。

functionloadAsset(url,type,callback){letxhr=newXMLHttpRequest();xhr.open('GET',url);xhr.responseType=type;xhr.onload=function(){callback(xhr.response);};xhr.send();}functiondisplayImage(blob){letobjectURL=URL.createObjectURL(blob);letimage=document.createElement('img');image.src=objectURL;document.body.appendChild(image);}loadAsset('coffee.jpg','blob',displayImage);

引数のobject URLを表示するimgタグを作ってドキュメントのbodyに追加する関数displayImage()を作りました。
次いで、URLとコンテンツタイプ、そしてコールバックを引数として受け取るloadAsset()関数を作ります。
この関数は、XMLHttpRequest(よくXHRと略される)を使って指定されたURLのリソースをフェッチし、返ってきたレスポンスをコールバック関数に渡します。
loadAsset()関数にコールバックとして渡されたdisplayImage()関数はすぐに動作するのではなく、XHRによるリソースのダウンロードが完了するまで待機します。
待機はonloadイベントハンドラで実現されています。

コールバックは汎用性があります。
関数の実行順序や関数間で渡すデータを制御できるだけではなく、状況に応じて異なる関数を呼び出すこともできます。
レスポンスの内容によってprocessJSON()関数を実行したり、displayText()関数を実行したりといった様々なアクションを持たせることができます。

全てのコールバックが非同期であるわけではないことに注意してください。
以下はArray.prototype.forEach()を用いて配列項目をループする例です。
実際の動作はこちらで、ソースはこちらで見ることができます。

constgods=['Apollo','Artemis','Ares','Zeus'];gods.forEach(function(eachName,index){console.log(index+'. '+eachName);});

この例は、ギリシャの神々の配列をループし、インデックスと値をコンソールに出力するものです。
foreach()が期待するパラメータは配列インデックスと値の2値を引数として持つコールバック関数です。
ただし、このコールバック関数は一切待機せずに即座に実行されます。

Promises

Promiseは、モダンなWebAPIで使用される、新しいスタイルの非同期コードです。
よくある例はfetch() APIで、これは要するにXMLHttpRequestの現代版スタイルのようなものです。
サーバからのデータ取得から簡単な例を見てみましょう。

fetch('products.json').then(function(response){returnresponse.json();}).then(function(json){products=json;initialize();}).catch(function(err){console.log('Fetch problem: '+err.message);});

実際の動作はこちらで、ソースはこちらで見ることができます。

fetch()は取得したいリソースのURLひとつだけを引数に取り、そしてPromiseオブジェクトを返します。
Promiseは最終的に非同期操作が完了したかもしくは失敗したかのステータスを持つオブジェクトで、それまではどちらでもない中間状態になっています。
『わかり次第すぐに結果を返すことを約束する』という概念であるため、"promise"という名前が付いています。

この概念に慣れるのは少し練習が必要です。
これはシュレディンガーの猫に似ていると言えるかもしれません。
成功失敗どちらの結果もまだ発生していない状態では、fetch()はまだ実行を完了せず、次の操作を保留します。
結果がわかったときに、fetch()の次にある3つのコードブロックが呼び出されます。

・ふたつあるthen()ブロックは、いずれもその前の操作が成功した場合に呼び出されるコールバック関数です。
各コールバックは、その前の操作が成功した場合に返した結果を入力として受け取ります。
then()ブロックはそれぞれ別のpromiseを返すため、then()ブロックをいくつも連ねて、複数の非同期操作を順番に実行することができます。

catch()ブロックは、どこかのthen()ブロックが失敗したときに実行されます。
同期のtry...catchブロックと似たようなもので、catch()ブロックはエラーオブジェクトを受け取ります。
これは発生したエラーの種類を判断するために使用できます。
後ほど解説しますが、同期try...catchpromise内で使用することはできません。
async/awaitを使っている場合は使用可能です。

注:promiseについては後から詳しく解説するので、まだ完全に理解できてなくても大丈夫です。

The event queue

Promiseなどの非同期操作はイベントキューに入れられ、メインスレッドの処理が完了した後で実行されるため、後続のJavaScriptコードの実行をブロックしません。
キューに入れられた処理は、なるべく早めに処理が行われ、結果がJavaScriptに返されます。

Promises versus callbacks

Pormiseは、古い形式のコールバックと多少の類似点があります。
これらはコールバック関数を呼び出すオブジェクトであって、コールバックそのものを関数に渡す必要はありません。

しかしながら、Promiseは非同期処理を容易にするために設計されており、古い形式のコールバックよりも多くの利点が存在します。

・複数の非同期操作を.then()を使って連結し、最初の操作の返り値を次の操作の入力に渡すことができます。
これをコールバックで行うことは困難で、しばしばコールバック地獄と呼ばれる厄介なピラミッドになります。
・Promiseのコールバックは、必ずイベントキューに積まれた順番に処理されます。
・エラー処理は旧形式コールバックより遙かに優れています。ピラミッドの各レベルに個別のエラー処理を書く必要がなく、どのレベルでエラーが発生しても最後にあるひとつの.catch()で処理を受け取ることができます。
・コールバックを渡した時点で制御する権利を失う旧形式コールバックと異なり、Promiseは制御の反転を避けることができます。

The nature of asynchronous code

非同期コードの性質をさらに詳しく見てみましょう。
非同期コードの挙動を理解しないまま、非同期コードを同期コードと同じように記述した場合にどのような問題が起こるかを確認します。

以下のコードは、上で出てきたものと似ています。
異なるところは、コードの実行される順番を調べるためにいくつかのconsole.log()が埋め込まれていることです。
実際の動作はこちらで、ソースはこちらで見ることができます。

console.log('Starting');letimage;fetch('coffee.jpg').then((response)=>{console.log('It worked :)')returnresponse.blob();}).then((myBlob)=>{letobjectURL=URL.createObjectURL(myBlob);image=document.createElement('img');image.src=objectURL;document.body.appendChild(image);}).catch((error)=>{console.log('There has been a problem with your fetch operation: '+error.message);});console.log('All done!');

ブラウザがコードを実行すると、まず最初のconsole.log()Startingを表示し、次にimage変数をセットします。

その後は次の行に進んでfetch()ブロックの実行をはじめますが、fetch()は非同期実行されるため、メイン処理はPromise関連のコードより先に一番最後のconsole.log()まで辿り着き、All done!を出力します。

fetch()ブロックはファイルのフェッチ処理が終了すると、次の.then()ブロックに進み、It worked :)console.log()に出力します。
そのため、このメッセージはあなたが思っていたのと異なる順番で表示されることでしょう。

・Starting
・All done!
・It worked :)

よくわからない場合は、次の小さな例を考えてみてください。

console.log("registering click handler");button.addEventListener('click',()=>{console.log("get click");});console.log("all done");

これは、先ほどの例とよく似た動作をします。
最初と最後のconsole.log()メッセージは即座に表示されますが、2番目のget clickメッセージは、マウスのボタンをクリックするまで表示されません。
Promiseの例では、クリックされるのを待つかわりにリソースの取得が終わるまで待つということになります。

これらのトリビアルな例が示すように、非同期コードは処理順を認識しておかなければ問題を引き起こす可能性があります。
非同期コードブロックは、後で同期コードで使用するために結果を返すようなことはできません。
ブラウザが同期ブロックを処理するより前に、非同期コードが値を返すことを保証しません。

この動作を確認するために、最初の例の3番目のconsole.log()返り値を入れてみましょう

console.log('All done! '+image.src+'displayed.');

コンソールには、メッセージではなくエラーが表示されるはずです。

TypeError:imageisundefined;can't access its "src" property

ブラウザが3個目のconsole.log()を実行しようとした時点では、まだfetch()ブロックの動作が完了していないため、imageがまだ定義されていないからです。

注:セキュリティ上の理由から、ローカルのファイルに対してfetch()、もしくは類似の操作を行うことはできません。
上記の例をローカルで再現するためには、ローカルWebサーバを介して実行する必要があります。

Active learning: make it all async!

問題のあるfetch()の例を修正し、3個のconsole.log()が想定したとおりに実行されるためには、3番目のconsole.log()も非同期に実行する必要があります。
これは、2番目のブロックが終わった後に実行されるもうひとつの.then()ブロックを追加するか、あるいは単純に2つめの.then()ブロックの末尾に移動すれば修正できます。
今すぐ試してみてください。

注:行き詰まった場合は、こちらで答えを確認することができます。
あるいはライブに確認できます。
また、後のほうで出てくるGraceful asynchronous programming with Promisesにおいて、Promiseに関するより詳しい情報を知ることができます。

Conclusion

最も基礎的な部分では、JavaScriptは同期・ブロッキング・シングルスレッド言語であり、一度にひとつの動作しか実行することができません。
しかし、Webブラウザは同期実行されない関数を登録する関数およびAPIを提供しています。
そのかわり、これらは何らかのイベントが発生したとき(一定時間経過、ユーザ操作やマウス入力、ネットワーク経由のデータ到着など)に、非同期で呼び出す必要があります。
これらを使うことで、メインスレッドを停止したりブロッキングしたりすることなく、複数の処理を同時に実行させることができるようになります。

コードを同期的に実行するか非同期的に実行するかは、その目的によって決定すべきものです。

対象をすぐにロードして実行したい場合があります。
たとえばユーザ定義スタイルをWebページに適用したい場合、できるかぎり早くスタイルを取り込む必要があるでしょう。

しかし、データベースへのクエリやその結果を使ったテンプレートの作成など、時間のかかる操作を実行する場合は、これをメインスレッドから分離して、非同期にタスクを実行した方がよいでしょう。
時が経つにつれ、この場合は同期ではなく非同期を選択した方が理に叶っていたということがわかります。

In this module

一般的な非同期プログラミングの概念
非同期JavaScriptの紹介
協調型非同期JavaScript:タイムアウトとインターバル
Promiseによる洗練された非同期プログラミング
async/awaitによる簡単な非同期プログラミング
適切な技術を選択する

感想

MDNは、有象無象の氾濫するフロントエンド界隈において、最も信頼できる情報源のひとつです。
わけのわからない変なブログを見るくらいなら、MDNを見た方がよっぽど役に立つことが多いです。
まあ『最も』とか言っちゃうとソースやらW3Cやら見るのが最も適切だって話になるのですが、あんなもの一般人は読んでられませんしね。
あと、たまにMSDN(自動翻訳の混沌)と間違える。

本当はこの記事もMDNにコミットするつもりだったんだけど、構文の変換が想像以上に面倒だったので諦めました。
誰かかわりに送ってやって。

それにしても、ほぼ公式みたいなMDNが未だに訳されてないというのはびっくりですね。

【PHP8.0】gettypeとget_classの悪魔合体

$
0
0

ワレハget_debug_type、コンゴトモヨロシク…

PHPにはプリミティブ型名を取得するgettypeと、オブジェクトのクラス名を返すget_classという関数が存在します。
_があったりなかったりと命名の不統一も気になりますが、それよりgettypeはオブジェクトに使うとobjectしか返さず、get_classをプリミティブ型に使うとE_WARNINGが発生します。
いや、プリミティブ型であればintとかの型が欲しいし、オブジェクトならPDOとかの型が欲しいんだ、という問題に対する答えはありませんでした。

というわけで両者を合体させたget_debug_typeというRFCが提出されました。

PHP RFC: get_debug_type

proposal

このRFCは、指定された変数の型を返す型らしい関数、get_debug_typeを追加する提案です。

これは、配列で来る値など、変数の型に基づいた既存のチェック方法では対応できないパターンを置き換えるためのものです。

$bar=$arr['key'];if(!($barinstanceofFoo)){// もっとも単純な例。しかしgettypeは"integer"を返すので、正しい型にしたいなら"int"に変換するなどが必要。thrownewTypeError('Expected '.Foo::class.' got '.(is_object($bar)?get_class($bar):gettype($bar)));}// 今後はこう書けるif(!($barinstanceofFoo)){thrownewTypeError('Expected '.Foo::class.' got '.get_debug_type($bar));}$bar->someFooMethod();

この関数は、正しい型名を返すという点でgettypeと異なります。
"integer"ではなく"int"を返し、クラスもクラス名に変換します。
次の表は、いくつかの値に対してgettypeget_debug_typeが返す値を比較したものです。

get_debug_type()gettype()
0intinteger
0.1floatdouble
trueboolboolean
falseboolboolean
"hello"string
[]array
nullnullNULL
Foo\BarFoo\Barobject
無名クラスclass@anonymousobject
リソースresource (xxx)resource
閉じたリソースresource (closed)

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

Implementation

https://github.com/php/php-src/pull/5143

投票

投票は2020/03/26まで、2/3の賛成で受理されます。
このRFCは賛成42、反対3で受理されました。

感想

Mark Randallは最初はgettypeがクラス名も返すようにしようとしたものの、Nikitaから「新しい関数にしてくれ」と言われてget_debug_typeを作ったようです。
まあ、これまでobjectとしか言わなかったgettypeがいきなり色々な型を喋り出したら困るところも出そうですからね。

ということで、今後は型の取得はget_debug_typeに一本化できそうです。

手間を省くための定型処理を言語機能に取り込むことは、他の言語でも多々起きていることです。
たとえばJavaScriptのasync/awaitPromiseの糖衣にすぎず、async/awaitができることは全てPromiseでもできるので、究極的にはasync/awaitは不要です。
しかし非同期処理を楽に書けるようにするために言語仕様に取り込まれました。
糖衣構文の取り込み自体は、このようにさほど珍しいことでもありません。

しかし、str_containsとかis_countableとか、他言語であれば「自分で書け」と言われそうな極端に簡単な構文まで言語仕様に取り込んでしまう言語は、PHP以外にはそうそう無いのいではないかと思います。

LGTMについて思うこと

$
0
0

文字と数値がくっついていて、大きさが似ていて、色合いも同じで、著者名やタグの長さによって配置される位置が異なります。
結果として、ぱっと見で数値が全く読み取れません。

01.png

前は色つきの記号だったので自然にそこで分割することができていたのですが、意味のある文字にしたせいで文字と数値が脳内でくっついてしまい、パースに詰まります。

もっと文字と数値を無意識に分てるようにUIを考えてほしいところです。

これがタグページになると、何故かタグがLGTMの右側に移動してくるため、文字列の間に埋没してしまい益々読みにくくなります。

02.png

個人ページ新着ページになると、LGTMが右端の固定位置に寄るため、他のページに比べるとずっと見やすくなります。

03.png

それでも、かつての👍よりは圧倒的に見難いですけどね。

全体としてデザインがまとまっていればまだマシなのかもしれませんが、見てのとおり何故かページごとに配置がバラバラです。
LGTMの位置を把握するだけでも一苦労です。
Qiitaにはデザインリーダー的な人物はいないのか??

まあここの運営、何度もいいねを消したりしてるので、最終的には全部消したいだけなのかもしれませんけどね。


個人ページの情報量が激減した

$
0
0

この記事を書いた瞬間個人ページのLGTMを左側に持っていきやがった。
あえて視認性を悪くする工夫を盛り込む。

さらに、個人ページのレイアウトが格段に劣化した。
・1ページあたり僅か5件しか記事を表示しなくなった。
・コメント数を潰す。
・自分のLGTM履歴やコメント履歴を確認することができなくなっている。
 ※プロフィール右上にある小さな『…』を押すとそこから見れる。極めてわかりにくい。

04.png

Contributionsページに至っては、上部にあった日々のContribution数履歴は消えるし、記事の累計LGTM数もコメント数も一切何も表示されなっている。

05.png

ほんと情報量を減らす改悪ばっかり実行するよな、ここ。

タグページの動作がまたおかしくなってる

$
0
0

地味に、タグページのブラウザバック動作がまたおかしくなっている。

タグページを表示
を選んで2ページ、3ページ、4ページと進む
・ブラウザバックすると、URLは変わるのに表示内容が変わらずにそのまま。

マイページは正常にブラウザバックする。
Contributionコメントページ新着一覧などは元よりページ遷移型なので正常にブラウザバックする。

一応日本最大の技術サイトでありながら、この有様はいったい何なんですかね?
なんつうか、チーフデザイナー的な人物とテスト管理者的な人物を採用すべきでしょう。
あとリーガルアドバイザーもね。
正式に雇うまでいかなくても、ちょっと食事代出すだけで自発的にやってくれそうな人はいっぱいいると思うのですが。

2000円のガラケーで快適にWebを表示する技術

$
0
0

PWA?SPA?WebAssembly?
うん、そうだね、よかったね。
それで、そのページは携帯で見れるのかい?

以下はAddy Osmani( Twitter / GitHub / Facebook / HP )による記事、Loading web pages fast on a $20 feature phoneの日本語訳です。
ちなみにこの人はGoogleのエンジニアで、Chromeの開発者のひとりです。

Loading web pages fast on a $20 feature phone

ヒント:高速なWeb基盤を構築することが、低価格のフィーチャーフォンにも、最新のハイエンドスマートフォンにも、全ての人々に良い体験を提供します。

Introduction

20~25ドル未満で購入できるフィーチャーフォンは、発展途上国において数億人が使用しているローエンドデバイスです。
それらはスマートフォンの低性能バージョンとも言えます。

低価格フィーチャーフォンは、CPUの性能が低く(ハイエンドスマホより6倍以上遅い)、RAMが少なく(通常4GB未満で、256-512MBも普通)、ストレージも少なく(4G)、そして大抵はタッチスクリーンもついておらず、ナビゲーションにはキーパッドや十字キーなどを使います。
たとえば以下のようなものです。
01.jpg

これらのデバイスは、ハイエンドスマホのようにリッチJavaScriptやメディアを処理することができません。
そのため、これらに送信するペイロードには特別な注意が必要です。

02.png

上記図はGeekbenchによる、2019年上半期に最も売れたスマートフォンのCPUベンチマークです。
人気のフィーチャーフォンであるNokia 3110のパフォーマンスを強調表示しています。

JavaScriptはシングルコアのパフォーマンスが重要であり(Webの他の技術と違い、JavaScriptは本質的にシングルスレッドです)、その性能はCPU性能に依存します
発展途上国について考えるときは、このデバイス特性に留意する必要があることを意味します。

この記事では、デバイスの性能にかかわらず誰もが利用できるサイトを構築することで、これらの問題に対処していきます。

Background

フィーチャーフォンは、スマートフォンが登場する前、2000年代半ばまで人気のあった端末であることを覚えている人もいるかもしれませんし、覚えていない人もいるかもしれません。
タッチスクリーンではなく小さなキーパッドを備えていて、電話、テキストメッセージ、テキスト中心のシンプルなWebブラウジングなど、かなり基本的な機能でできていました。
スマートフォンの登場後、これらの電話は先進国ではあまり見られなくなりました。

発展途上国では、誰もが4Gネットワークの無制限データプランでスマートフォンを利用できるわけではありません。
この市場は、スマートフォンから最低限の機能を抽出し、ハードウェア性能と価格の妥協の産物、スマートフィーチャーフォンによって成り立っています。

03.png

スマートフィーチャーフォン市場は2017年から伸びに伸びており、2019年には世界で4億台ものスマートフィーチャーフォンが販売されると想定されています

フィーチャーフォンの成長は、3110や8110(Paul Kinlanがデバッグガイドを公開しています)といった往年の名機をNokiaが復活させたことに支えられています。
インドでは、Reliance Jioが外出先でWebにアクセスするための、安価で最新のフィーチャーフォンを提供しています。
JiojはLinuxベースのフィーチャーフォン向けOSであるKaiOSを推進してきました。

フィーチャーフォン市場の成長により、効率的に動作するサイトが必要となってきており、そのためには注意すべき制約がいくつも存在します。

04.jpg

これはGoogle Images LiteとFacebook mBasicのサンプルです。
これらはいずれもフィーチャーフォンで快適に動作するようつくられており、クライアント側のスクリプト依存は最小限です。
ゲームであるProxxはスクリプトに多く依存しますが、フィーチャーフォンでも高速にロードするため積極的なコード分割設計を行っています。

Feature phone constraints

発展途上国のユーザは、3つの要因によって制限されています。
・低価格かつ高性能のデバイスはほとんどない
・高品質なネットワークがない
・手頃な価格のモバイルデータ通信がない

フィーチャーフォン向けサイトが必要であるならば、これらの制約に注意してください。

  1. ハードウェア
    フィーチャーフォンは大抵、低速(1.1GHz程度)のシングル・デュアルコアCPUと、512MB未満のRAMでできています。
    この制約が意味するところにおいては、8コアCPUと4GBのRAMでできているiPhone XSと比較してみるとよいでしょう。

  2. 通信量
    データプランは安くなりつつありますが、フィーチャーフォンが人気のある地域では依然として大きな制限があります。
    ページが高速に読み込まれ、費用が多くならないように、できるかぎりペイロードを小さくしましょう。

  3. スクリーンサイズ
    フィーチャーフォンの画面サイズは通常、スマートフォンのそれよりはるかに小さいです。
    2.4インチの大きさで、ほんの少ししか情報を出すことができません。
    必要なリソースをビューポート内のコンテンツにできるだけ早くロードすることを第一に考えてください。

  4. タッチスクリーン
    タッチスクリーンがないので、画面上の各機能、アクションボタン、リンクへはキーパッドから簡単にアクセスできる必要があります。
    必要以上にショートカットキーを埋め込む必要はありません。

  5. キーパッド
    フィーチャーフォンのキーパッドは、我々が使い慣れているQWERTYキーボードとは全く異なっています。
    およそ15個のボタンでできていて、多くの文字はボタンを何度も押さなければなりません。
    従って、タイピング量をできるだけ減らすUXを考える必要があります。

日本においても、データプランの上限がユーザエクスペリエンスに影響を与える可能性があります。

Development Guidelines

以下のTIPSは、フィーチャーフォン向けWebサイトに高速なエクスペリエンスを提供するのに役立つでしょう。
全体的に、ユーザが要求したもの以外のために待たせないでください。
JavaScriptのダウンロード時間と実行時間を可能な限り抑えてください。

Set performance budgets for your initial payloads

良好なパフォーマンスを確保するために開発チームが従わなければならない制限を決める、Performance budgetsという考え方があります。
超過することを許さない制限です。
開発を始める前に定量化可能なメトリックを定義し、新しい機能を追加しても総量が制限を超えないようにします。

Performance budgetsの例としては、JavaScriptのバンドルサイズ、画像のサイズ、HTTPリクエスト数などがあります。
ユーザエクスペリエンスを図るための指標として、First Contentful PaintLargest Contentful PaintFirst Input Delayなどを採用可能です。
対象ユーザに応じて、これらのメトリックごとに閾値を決めていきましょう。

Performance budgetsはアプリケーションロジック、vendorやcommonsのバンドル、そしてその他の項目ごとに設定可能です。
Lighthouseを使ったビルドプロセスや、継続的インテグレーションにおいて強制させることができます。

PRPL-30 - a JavaScript budget for feature phones

Chrome開発チームは、低速ネットワーク上のローエンドデバイスでもなるべく早く操作可能にするため、コード量を細かく制御するPRPLパターンを推奨しています。

PRPLは、ページを表示するために必要な最小限のスクリプトのみをプリロードし、それ以外はあとから遅延ロードし、そしてService Workerで今後のナビゲーション用のデータをキャッシュしておくことを勧めています。

PRPL-50は、イニシャルリソースを50KBに制限するという、Performance budgetsを発展させた考え方です。
フィーチャーフォンはCPUによる制約も受けるため、JavaScriptにさらに厳しい制限を課す必要があります。
フィーチャーフォンを対象とするWebサイトは、PRPL-30(30キロバイト)がよいと考えています。

05.png

この仮定においては、SSLネゴシエーションが終わった後、適切なCDNからレスポンスの最初のバイトが返ってくるまでの時間はおよそ2秒です。
従って、初回のペイロードがダウンロードされ、レンダリングされ、ユーザが画面の操作をできるまでの時間は約3秒となります。
すなわち、JavaScript中心のWebサイトの場合は、minifyとgzip圧縮を行った後のバンドルサイズを30キロバイト未満に縮小しなければならないということです。

06.jpg

待って待って。
30キロバイトだって?
ふざけてんの?
Reactのライブラリすら入れることができないんですけど!

厳しい制約のあるデバイス向けのサイトを構築するのであれば、ユーザエクスペリエンスを追求するために難しいトレードオフを行う必要があります。
たとえばReactをサーバ側にしか使わない、あるいはアプリケーションロジックを最小限にして遅延ロードを行わせる、Preactのようなソリューションを使う、などです。
これらのトレードオフについては後で解説します。

PRPL-30を守ることのできるアプリケーションの例としては、たとえば25KBのファイルサイズと5秒未満のインタラクション時間を持つProxxです。
独自の指標を目的とする際は、パフォーマンス予測ツールが役に立つでしょう。

遅延ロードするファイルのサイズも35KB未満が目安です。
30-35KBのチャンクサイズは、V8スクリプトストリーミングが並列処理を行うのに十分な大きさです。

Be Frugal with JavaScript

tl;dr:可能なかぎりスクリプトへの依存を最小限に抑え、静的レンダリングもしくはサーバサイドレンダリングを採用する。
クライアント側レンダリングやハイブリッドレンダリングが必須であれば、必要最小限のスクリプトのみをできるだけ少ない回数リクエストする。
プログレッシブリハイドレーションのようなテクニックも検討する。

07.png

JS is the #1 bottleneck on feature phones

フィーチャーフォン向けにインタラクティブな体験を提供する場合、JavaScriptが最大のボトルネックになる可能性が高いことに気を付けてください。
ページをレンダリングする技術の選択によって、ユーザが実際にページを操作できるようになるための時間が遅くなる可能性があるため、この選定は重要です。
サーバサイドレンダリング静的レンダリングを選んだ場合は、インタラクティブなペイロードはできるかぎり小さくしてください。

08.png

JavaScriptにはダウンロード時間および実行時間という、ふたつの大きなコストがあります。
ダウンロード時間は遅いネットワーク(3Gコネクションなど)によって伸び、遅いCPUは実行時間に遅延をもたらします。
下の図は、人気はあるが重いJavaScriptのあるサイトについて、CPUの種類による処理速度の違いを可視化したものです。
ハイエンドのスマートフォンに比べて、ローエンドのスマートフォンでは6倍もの実行時間がかかっていることがわかります。

09.png

これは即ち、レンダリングやインタラクティブ性を大きなJavaScriptバンドルに依存しているのであれば、フィーチャーフォンではユーザがUIを操作可能になるまで30から60秒待たされることすらあるということを意味します。

JavaScriptに必要なダウンロード時間と処理時間を最小限に収めるため、開発者はそれらのリソースをできるかぎり節約する必要があり、ユーザが必要とする可能性のあるルートやコンポーネントのためのJavaScriptだけを、それらが必要になったときにロードしなければなりません。

Keep interactive payloads lean

ペイロードを無駄にしないためにも、以下のことを守るべきです。

・画面外にあるコンポーネントやリソース、あるいはファーストビュー内でもクリティカルではないコンテンツについては、できるかぎり遅延ロードします。
・JavaScriptのコードを分割し、ファーストビューで必要なものだけを最初にロードするようにします。これによりダウンロードするスクリプトの量が減り、ページの読み込みが早くなります。
・バンドルから未使用コードを削除し、できるかぎり無駄のない状態にします。そのためにはバンドルを分析し、使用されていないライブラリや一部しか使われていないライブラリを差し替える必要があります。また、最初に使わないライブラリは遅延ロードさせる必要があります。
差分ロードを用いて、必要な機能のみをブラウザに提供するようにし、過剰なトランスパイルや過剰なポリフィルを避ける必要があります。モダンブラウザに送られるレガシーコードの量を減らすことで、読み込みのパフォーマンスを上げることができます。
・レンダリングや、ファーストビューのUIのためにJavaScriptを使っているのであれば、それらをプリロードしてください。以下のように記述することでそれが重要であることをブラウザに伝えることができ、ブラウザはできるだけ早くロードします。

<linkrel="preload"as="script"href="critical.js">

Choose your stack wisely

10.png

サードパーティ製ライブラリは開発を高速化し、複雑なタスクを容易に実現することに役立ちますが、その対価として重くなりがちです。
フィーチャーフォン向けに開発する場合は使用に注意しなければなりません。
以下のガイダンスを参考にしてください。

・フィーチャーフォンはリソースに大きな制約があるため、可能ならばJavaScriptフレームワークの使用そのものを避けるか、できるかぎり制限してください。
JavaScriptフレームワークは多大なオーバーヘッドを要求します。
Reactを使うのであればサーバサイドレンダリングするか、あるいはビルド時にPreact compatを使ってPreactに入れ替えて30キロバイト削減する、などの代替を検討してください。
Sveltelit-html、そしてVanilla JSなどはバンドルを軽くするための良い選択肢です。

・サードパーティライブラリへの依存をできるかぎり減らし、初期ロードに必要なバンドルサイズを削減してください。たとえばMoment.jsのかわりにdate-fnsluxonを使うなどです。bundlephobia.comなどでサイズのチェックができます。

・状態管理のためにReduxやStoreを使う場合は厳重な注意が必要です。
StateがしばしばHTMLにインライン展開され、レスポンスサイズが非常に大きくなってしまうことがあります。

Adapt to avoid loading heavy resources on slow connections

ヒント:このトピックの詳細については、adaptive loading - improving web performance on low-end devicesを参照してください。

11.png

Adaptive Loadingとは、提供するリソースをeffectiveTypeに基づいて変更する技術で、ブラウザからはNetwork Information APIで利用可能です。
Adaptive servingを使うことで、低速なユーザにも低機能であってもひとまずのエクスペリエンスを提供することが可能になります。

console.log(navigator.connection.effectiveType);// 3G

注意:たとえeffectiveTypeが"4G"であっても、スタバやカンファレンス会場のWifiのように実速度は低速である可能性は十分にあります。

Adaptive servingを使用すべき具体的な例としては、製品紹介ページなどがあります。
接続速度の遅いユーザには圧縮された商品画像だけを、高速なユーザには高品質な画像とJavaScriptによる強化された演出(画像のズームインや、別製品との比較を表示する機能など)を提供したりすることができるでしょう。

フィーチャーフォンにおいては、低速な回線が必ずしも最大の障害であるとは限りません。
遅いCPUや少ないメモリは、回線種別より多くの影響をユーザ体験に与えます。
Client Hintsは、CPUの性能にこそアクセスできないものの、デバイスのメモリ、Viewportの幅、ピクセルレート、ネットワーク情報、そしてその他の情報を集めることで、より詳細なサービス戦略を立てるために使うことができます。

Be respectful of users data plans with the Save-Data header

Android版Chromeにはライトモード(Data Saver)と呼ばれる機能があり、通信量を気にするユーザは、ブラウザがリソースを最適化してページの読み込み速度を向上させるオプションを選ぶことができます。
最適化には、画像を圧縮したり、重要でないリソースを後回しにしたり、ページをサーバ側でレンダリングすることなどが含まれています。
詳しくはChrome Lite Pagesをご覧ください。

対応しているブラウザでユーザがデータ節約モードを選択すると、あらゆるHTTP/HTTPSリクエストにSave-Dataリクエストヘッダ
アプリケーション開発者は、このヘッダがあれば重い機能をオフにするなど、データ節約モードをオンにしているユーザに最適化された体験を提供することができます。
JavaScriptでは以下のスニペットで確認可能です。

if("connection"innavigator){if(navigator.connection.saveData===true){// Implement data saving operations here.}}

12.jpg

注意:お使いのフィーチャーフォンはChromeをサポートしているかもしれませんが、お使いのChromeがライトモードをサポートしているとは限りません。

Offload costly app logic and state handling to Web Workers

SurmaによるTechniques to make a web app load fast, even on a feature phoneを読んでください。
素晴らしい投稿です。

ブラウザのメインスレッドでは、JavaScript以外にもページレイアウト、ピクセルの描画、ユーザインタラクションの追跡など多くの機能が動いています。
複雑で時間のかかるJavaScriptは、これら他のタスクをブロックしてしまう可能性があります。

Web Workersは、メインスレッドをブロックせずにバックグラウンドでJavaScriptを実行できる技術です。
複雑なアプリロジックや状態管理サービスなど、コストのかかるJavaScriptのオーバーヘッドをメインスレッドから切り離すことができます。
メインスレッドとワーカースレッドは、postMessage()onmessageハンドラでやりとりできます。
postMessageを使って、任意の値やオブジェクトを含むひとつの値を送り付けることができます。
実装の困難はComlinkのようなライブラリで軽減することができます。

ワーカースレッドを使った場合と使わなかった場合でProxxの差を調べた、Surmaのケーススタディは読みごたえがあります。
Nokia2(RAM1GB、1.3GHzクアッドコア)では、ワーカースレッドを使わなければ6.6秒もの間アプリがフリーズしました。
しかし、ワーカースレッドを使った場合は、応答するまでの時間は48ミリ秒でした。
CPUをごりごり使う処理を書いているのであれば、それをワーカースレッドに移植する価値は十分にあると言っていいでしょう。

Optimize Images

画像は多くのデータ量を消費します。
低価格デバイスでは特に、デコードにも時間がかかります。
従ってフィーチャーフォンに画像を配信する際は、適切に画像のサイズと圧縮方法を選ぶことが大事です。

Imageminのようなツールを使って、品質を下げずに画像のサイズを削減します。
アニメーションGIFは動画に変換しましょう。ロードがはるかに速くなります。しかしその前に、ローエンドデバイスがそんなに重いメディアファイルを必要としているか考えてください。
・可能なら画像を遅延ロードしますが、遅延ロードさせるJavaScriptライブラリが重くては意味がありません。ネイティブのloading属性が役立つでしょう。
・同じ画像を複数のサイズで用意しておき、ユーザのviewportに最も適したものを選択して提供するレスポンシブイメージが役立ちます。
画面に合った画像を提供します。低解像度のローエンドデバイスにはそれなりの画像を送ることで、より高速にデコードを行うことができます。

Detecting screen-size

現在は多くのスマホがQVGAで、解像度は320px * 240pxとなっています。
機能のオンオフやアダプティブローディングを画面サイズによって切り替えたい場合、以下のようなスニペットを使うことができます。

constisFeaturePhone=Math.min(screen.width,screen.height)<=240;

これは、Porxxが採用しているアプローチに似た方法です。

Emulate a feature phone in Chrome DevTools

低価格フィーチャーフォン向けサイトを構築するのであれば、格安端末の実物を入手することを強くお勧めします。

Chrome DevToolsでフィーチャーフォンをエミュレートするのであれば、以下の手順で可能です。

・Chrome DevToolsを開く
・Toggle Device Toolbarを選択
・デバイスのドロップダウンからEdit→Add custom deviceを選択
・名前をKaiOS(もしくは必要な対象名)にする
・幅240、高さ320を指定
・UAをMozilla/5.0 (Mobile; LYF/F90M/LYF-F90M-000-02-23-181217; Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5 YouTube/1.66.51.Jにする
・保存する
・(オプション)CPU性能をカスタマイズすることもできますが、これはあまり正確ではありません

14.png

Conclusion

何処にいる誰にでも、全てのユーザに楽しい体験を提供することは可能です。
ただし、全てのユーザのハードウェアが同じではないので、その点には注意が必要です。

端末の価格が手頃であるほど、CPUの性能も同等に低い可能性が高まります。
JavaScriptのパフォーマンスがダウンロード時間と実行時間に依存していることを考慮し、適切なエクスペリエンスを提供することを考えてください。

これはスマートフォンでも注意すべき点ですが、フィーチャーフォンではさらに重要になります。

コメント欄

「すごい有意義な投稿だった!」
「よく見たらChromeの中の人じゃん。有益な情報をありがとう」
「Svelteのパフォーマンスが高いと感じていたので言及してくれてうれしい。Preact試してみる!」「SvelteとSapperが大好き!」「Svelteや類似のフレームワークが早く普及してほしいね」
「バンドルサイズを抑える最も簡単な方法?簡単さ、JavaScriptを使わなければいい。」

感想

JavaScriptなんてほぼ動かないからHTML全部べた書きな、CSSもまともに効かないからfontタグを使え、とかそういうレベルの話かと思っていたら全然違っていて、わりと最近のAndroidフィーチャーフォンの話だった。
JavaScriptもCSSもきちんと認識してくれるけど、単に性能が低くてネットワークも遅いから、そういうところでも見れるように出力をカスタマイズしろ、という話ですね。

日本ではローエンドと言われているデバイス、安価な通信回線でも一般的なWebサイトを見るには十分な能力を持っているため、日本向けのWebサイトであれば正直そこまで気にする必要はありません。
しかし、たとえば日本では完全に無名のTranssionは、アフリカに特化したフィーチャーフォンを作って2000円で毎年1億台売っています。
アフリカは次の中国と期待される未知の市場です。
2000円の超ローエンドデバイスでも快適に見られるWebサイト作りが、世界に目を向けるならば必要となってくることでしょう。

ともあれ、ローエンドだろうがハイエンドだろうが無駄にアニメーションとスクロールを要求するサイトは滅ぶべきである。

【PHP8.0】throw文がthrow式になる

$
0
0

throw expressionというRFCが投票中です。

最初のアイデアは2019/12/06のSebastiaan Andewegによるツイート

それに対して2020/03/19にCarusoが反応し、そしてその日のうちにiluuu1994が最初のプルリクを出しました
はえーよ。

throw expression

Introduction

PHPのthrowは文であるため、アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げることができません。
このRFCでは、それらを可能にするためthrow文を式に変更することを提案します。

Proposal

式を記述可能なあらゆるコンテキストでthrowが利用可能になります。
以下は思いついた便利そうな例です。

// アロー関数$callable=fn()=>thrownewException();// nullチェック$value=$nullableValue??thrownewInvalidArgumentException();// trueっぽいチェック$value=$falsableValue?:thrownewInvalidArgumentException();// 空ではない配列チェック$value=!empty($array)?reset($array):thrownewInvalidArgumentException();

他にも、議論の余地のある使用方法があります。
このRFCでは、以下のような記述も許可されています。

// ifを使った方が意図が明確になる$condition&&thrownewException();$condition||thrownewException();$conditionandthrownewException();$conditionorthrownewException();

Operator precedence

throwが式になると、優先順位を決める必要があります。
以下は現時点で有効な書式の例です。

throw$this->createNotFoundException();// こうなるthrow($this->createNotFoundException());// こうではない(throw$this)->createNotFoundException();throwstatic::createNotFoundException();// こうなるthrow(static::createNotFoundException());// こうではない(throwstatic)::createNotFoundException();throw$userIsAuthorized?newForbiddenException():newUnauthorizedException();// こうなるthrow($userIsAuthorized?newForbiddenException():newUnauthorizedException());// こうではない(throw$userIsAuthorized)?newForbiddenException():newUnauthorizedException();throw$maybeNullException??newException();// こうなるthrow($maybeNullException??newException());// こうではない(throw$maybeNullException)??newException();throw$exception=newException();// こうなるthrow($exception=newException());// こうではない(throw$exception)=newException();throw$exception??=newException();// こうなるthrow($exception??=newException());// こうではない(throw$exception)??=newException();throw$condition1&&$condition2?newException1():newException2();// こうなるthrow($condition1&&$condition2?newException1():newException2());// こうではない(throw$condition1)&&$condition2?newException1():newException2();

共通して言えるのは、全てがthrowキーワードより高い優先順位を持つということです。
このため、このRFCではthrowキーワードの優先順位を可能な限り低くすることを提案します。
現在有効なコードは、たとえ直感に反する動作だったとしても、今後も同じ動作をし続けます。
なぜなら、一般的にthrowは最後に使用するべき演算子であり、それ以降に記述した式は評価されないからです。

低い優先順位の唯一の欠点は、短絡評価のために括弧が必須になることです。

$condition||thrownewException('$condition must be truthy')&&$condition2||thrownewException('$condition2 must be truthy');// こうなる$condition||(thrownewException('$condition must be truthy')&&$condition2||(thrownewException('$condition2 must be truthy')));// こうではない$condition||(thrownewException('$condition must be truthy'))&&$condition2||(thrownewException('$condition2 must be truthy'));

もっとも、こんなコードはほぼ使われていないでしょう。

Backward Incompatible Changes

後方互換性のない変更はありません。

Other languages

C#では同じ文法が2017年に実装されました。

このような言語は他にはあまりありません。
ECMAScriptにプロポーザルがありますが、これは同じ問題を抱えているからです。

Proposed PHP Version(s)

PHP8。

投票

投票は2020/04/19まで、2/3の賛成で受理されます。
2020/04/06時点では賛成14、反対1で、受理される見込みです。

過去のML

9年前とか15年前にも同じ発想があったようですが、そのときは立ち消えになりました。
当時とはPHPのおかれた環境やRFCの出し方などがだいぶ異なることと、そしてなにより実物のプルリクがあるというのは大きいでしょう。

感想

ややこしいよね文と式。
全てが式になればいいのに。

というわけで、今後はもっと気軽にthrowすることができるようになります。
それどころかアロー関数で引数によって値を返したり例外Aを出したり例外Bを出したりすることもできちゃいますよ。
まあ正直、throwを出すようなややこしい式をアロー関数に書くんじゃないよと思ったりはするわけですが。

【PHP8.0?】PHPに名前付き引数が実装されるかもしれない

$
0
0

PHPのソースを眺めていたら、先日2020/04/07にNikitaがなんか面白そうなプルリクを出していました。
Named Parametersという2013年に提出されたまま忘れ去られたRFCがあるのですが、その機能を実装したものです。

どういう機能ってこういうのです。

functionhoge($foo,$bar){echo"foo=$foo,  bar=$bar";}hoge(bar=1,foo=2);// foo=2, bar=1

PythonCSharpなんかで実装されてるやつですね。

Nikita本人は機能が幾つも不足しているよと言っているのですが、不足の内容はOpcache対応や引数アンパックといった周辺機能で、基本的な機能は既に実装されているみたいです。

PHP RFC: Named Parameters

State of this RFC

これは名前付き引数についての準備的なRFCです。
このRFCの目的は、次のPHPバージョンで名前付き引数をサポートすべきか、またサポートするときはどのように実装すべきかを確認することです。
ここで解説している構文や動作は最終的なものではなく、詳細を詰めていく必要があります。

このRFCの実装はまだ完全なものではありません。
これは非常に複雑な機能なので、この機能が必要でなかったのに時間をかけて実装したくはありません。

Update 22-05-2014

私は他のことで忙しく、このRFCには取り組めていません。
このRFCはPHP6に間に合うように復活させるつもりです。
それまでの間、未解決の問題に対する議論の結果の一部をここにまとめておきます。

・名前付き引数のコンセンサスはまだ得られていません。
・名前付き引数のアンパックと、名前のない引数のアンパックは...構文に統合されます。アンパックのRFCは既に決定しているため、ここについて選択肢はあまりありません。
・継承時のパラメータ名チェックは強制されません。これはあまりにも大きなBC breakになるとはんだんされました。

主な未解決の実装上の問題は"Patch"セクションにリストアップされています。

What are named arguments?

名前付き引数とは、パラメータの順番ではなくパラメータ名を使って関数に引数を渡す方法です。

// 通常の引数array_fill(0,100,42);// 名前付き引数array_fill(start_index=>0,num=>100,value=>42);

名前付き引数に渡す引数の順番は自由です。
上記例では関数シグネチャと同じ順番で引数を渡していましたが、異なる順番で渡すこともできます。

array_fill(value=>42,num=>100,start_index=>0);

名前付き引数と名前のない引数を組み合わせることも可能であり、オプション引数の一部のみを名前付き引数で渡すことも可能になります。

htmlspecialchars($string,double_encode=>false);// ↓と同じhtmlspecialchars($string,ENT_COMPAT|ENT_HTML401,'UTF-8',false);

What are the benefits of named arguments?

名前付き引数の明白な利点の一つは、上のコードサンプルを見ればわかります。
変更したい引数までの間にある引数に対して、それぞれデフォルト値を指定する必要から解放されます。
名前付き引数があれば、変更したい引数だけを直接指定することができます。

これはデフォルト引数のRFCでも可能ですが、名前付き引数を使えば意図がより明白になります。
構文を比較してみてください。

htmlspecialchars($string,default,default,false);// vshtmlspecialchars($string,double_encode=>false);

ひとつめのコードを見ても、たまたまhtmlspecialcharsの引数を丸暗記していないかぎりfalseが何を意味するのかは分からないでしょう。

コードの自己文書化の利点は、オプション引数をスキップしないときにおいても明らかです。

$str->contains("foo",true);// vs$str->contains("foo",caseInsensitive=>true);

名前付き引数を使うことで、新しい方法で関数を使うことができるようにbなります。
引数を順番に並んだリストとしてだけではなく、キーと値のペアのリストとしても扱えるということです。
以下のような使い方が考えられます。

// 現在の構文$db->query('SELECT * from users where firstName = ? AND lastName = ? AND age > ?',$firstName,$lastName,$minAge);// 名前付き引数$db->query('SELECT * from users where firstName = :firstName AND lastName = :lastName AND age > :minAge',firstName=>$firstName,lastName=>$lastName,minAge=>$minAge);

Implementation

Internally

名前付き引数は、他の引数と同じくVM stackを通して渡されます。
これらの引数の違いは、位置引数は常にスタックの先頭に渡されるのに対し、名前付き引数は任意の順番でスタックに挿入することができるということです。
使用されないスタックの位置にはNULLが入り、引数カウントはNULLも数えます。

Errors

位置引数と名前付き引数を混在させることが可能ですが、名前付き引数は位置引数の後に配置しなければなりません。
そうしなければコンパイルエラーが発生します。

strpos(haystack=>"foobar","bar");// Fatal error: Cannot pass positional arguments after named arguments

可変長引数ではない関数に存在しない引数名を渡した場合、致命的エラーが発生します。

strpos(hasytack=>"foobar",needle=>"bar");// Fatal error: Unknown named argument $hasytack

同じ名前の引数を複数回渡した場合、新しい方で古い方が上書きされ、警告が発生します。

functiontest($a,$b){var_dump($a,$b);}test(1,1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)test(a=>1,b=>1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)

Collecting unknown named arguments

可変長引数の...$args構文を使った場合、余った名前付き引数は$argsに集められます。
名前付き引数は常に位置引数より後ろとなり、渡された順番が保持されます。

functiontest(...$args){var_dump($args);}test(1,2,3,a=>'a',b=>'b');// [1, 2, 3, "a" => "a", "b" => "b"]

使用例としては前述の$db->query()があります。

これはPythonで**kwargsと呼ばれている機能です。

Unpacking named arguments

引数アンパックのRFCは名前付き引数のアンパックにも対応します。

$params=['needle'=>'bar','haystack'=>'barfoobar','offset'=>3];strpos(...$params);// int(6)

文字列キーを持つ任意の値は、名前付きパラメータとして展開されます。
それ以外のキーは通常の位置引数として扱われます。

位置引数と名前付き引数をひとつの配列にまとめることも可能ですが、その場合でも引数の順番は守らなければなりません。
名前付き引数より後に位置引数が出てきた場合は警告がスローされ、アンパックは中止されます。

func_* and call_user_func_array

名前付き引数を使って、スタックから引数としてNULLが渡ってきた場合、func_*関数の挙動は以下のようになります。

func_num_args()はNULLを含んだ引数の個数を返す。
func_get_arg($n)はデフォルト値を返す。デフォルト値がない場合はNULL。
func_get_args()はデフォルト値を返す。デフォルト値がない場合はNULL。

3関数とも、未定義の引数名は無視します。
func_get_argsは値を返さず、func_num_argsはカウントに含めません。

call_user_func_arrayは名前付き引数をサポートしません。
文字列キーを持つ配列を渡すコードが壊れるからです。

Open questions

Syntax

現在の実装および提案では、名前付き引数について以下2種類の構文をサポートしています。

test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");

ふたつめの構文は、引数名が予約語である場合のためにサポートされています。

test(array=>[1,2,3]);// syntax errortest("array"=>[1,2,3]);// works

この構文の選択は恣意的なもので、特に深く考えずに採用しました。
以下に、いくつか代替構文の提案があります(ほとんどはPhil Sturgeonによる提案です)。

// currently implemented:test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");// キーワードを使えるtest($foo=>"oof",$bar=>"rab");test(:foo=>"oof",:bar=>"rab");test($foo:"oof",$bar:"rab");// キーワードを使えないtest(foo="oof",bar="rab");test(foo:"oof",bar:"rab");// 既に有効なコードなので不可test($foo="oof",$bar="rab");

どの構文で決定するかは議論次第です。

Collection of unknown named args into ...$opts

現在のRFCでは、位置引数と名前付き引数の両方がまとめて可変長引数の...$optsに入ってきます。
Pythonでは前者を*argsに、後者を**kwargsに入れるというアプローチをとっています。

Pros:PHPでは、Pythonではできない配列と辞書の混在ができます。
Cons:位置引数と名前付き引数を別にすることで、意図がより明確になります。必ずしも両方の引数をサポートしたいとはかぎらず、片方だけを強制したいかもしれません。

どのように扱うのが適切か、意見や議論を歓迎します。

Unpacking named args

引数アンパックについても同じ疑問が出てきます。
...$fooは位置引数と名前付き引数を一緒にできますが、*$foo**$fooに分けるべきでしょうか。

この決定は、可変長引数と同じに揃えるべきでしょう。

Signature validation allows changing parameter names

現在のところ、引数名はシグネチャに含まれていません。
位置引数しか使わない場合、これは合理的です。
引数名は関数の呼び出しに関係ないからです。

名前付き引数はこの動作を変更します。
継承先クラスが引数名を変更した場合、名前付き引数を使った呼び出しは失敗し、LSPに違反します。

interfaceA{publicfunctiontest($foo,$bar);}classBimplementsA{publicfunctiontest($a,$b){}}$obj=newB;// Pass params according to A::test() contract$obj->test(foo=>"foo",bar=>"bar");// ERROR!

名前付き引数が導入された場合、シグネチャの検証において引数名の変更にエラーを出さなければなりません。
通常の場合、インターフェイスと実装クラスの不一致は致命的エラーを発生させますが、名前付き引数において致命的エラーを出すとBC breakが大きくなりすぎてしまいます。
かわりに、より低いエラータイプ(WARNING / NOTICE / STRICT)を出すことを検討します。

これに関する具体的な議論のポイントをひとつ挙げておきます。
PHPは、実行時の動作を変更するini設定を導入する習慣を過去に置いてきました。
従って、この挙動をiniで制御できるようにすることは、私の選択肢にはありません。

Patch

差分がこちらにありますが、このパッチは不完全で、ダーティで、既知のバグがあります。

やるべき作業はまだまだ残っています。

・"Open questions"の結果を実装する。
・内部関数の全てのarginfosをドキュメントと一致するように更新する。現在のarginfo構造体は絶望的に時代遅れで、引数割り当てなどは自動的にできるようにしたい。
・内部関数において引数がスキップされたときに適切に動作するようにする。ほとんどの場合は自動的に動作するはずだが、手動調整が必要になる関数もかなりあるだろう。

感想

2014年とかPHP6とか出てくることからわかるように、このRFCはだいぶ昔に書かれてそのまんまです。
当時作成されたパッチはもはや使い物にならないため、新たにプルリクを作ってきたようです。
パッチにしろその他の内容にしろ、当時のPHPと今のPHPはだいぶ異なったものになっているので、なんにしろRFCのリファインは必要になるでしょう。

このプルリクについても、とりあえず提出されただけで何の展開もありませんし、メーリングリストでの議論も特に進んでいるわけではありません。
従って、このプルリクも今後どうなるかはわからず、再びこのまま忘れ去られるかもしれません。
しかしNikitaのことですから、いきなり完動品のプルリクが送られてきて第一線に躍り出る、なんてことがあっても驚きはないですね。

混沌の時代に実装された関数などでは特に、同じ内容の関数でも引数の順番が異なったりしていて大変なのですが、この機能が実装されたら、そのあたりを楽に処理できるようになります。
また却下されたデフォルト引数のRFCも、この名前付き引数があれば不要になります。
絶対にないといけないというほどでもないですが、存在すれば純粋に便利になる、そんな良い機能だと思います。

あとここからは完全に妄想ですが、このプルリクはジェネリクスへの足掛かりなのではないかと感じています。
Nikitaはどうもジェネリクス大好きっ子みたいですから、このプルリクを元に引数まわりに手を付けて、ついでにジェネリクスまでできるようにちゃったぜみたいなことを考えているのではないでしょうか。

【PHP7.4.5】バグレポ出したらPHP本体に取り込まれた

$
0
0

2020/03/10 23:38

先日なんとなく、bugs.php.netバグレポを提出しました。
中身は前書いた配列要素を削除するとインデックスが残ったり残らなかったりするの記事、というかそこに付いた @crhgさんのコメントそのまんまです。

Description

Copy array.

When arrays next index=2 and max key=2, next key is both 2.
When arrays next index=2 and max key=1, next key is both 2.
When arrays next index=2 and max key=0, next key is 0 or 2.

Test script

$a=[1,2];$b=$a;$a[]=3;$b[]=4;echo"When next index=2 and max key=2, next key is both 2.\n";var_dump($a,$b);// $a = [0=>1, 1=>2, 2=>3]  $b = [0=>1, 1=>2, 2=>4]$a=[1,2];unset($a[1]);$b=$a;$a[]=3;$b[]=4;echo"\nWhen next index=2 and max key=1, next key is both 2. \n";var_dump($a,$b);// $a = [0=>1, 2=>3]  $b = [0=>1, 2=>4]$a=[1,2];unset($a[1],$a[0]);$b=$a;$a[]=3;$b[]=4;echo"\nWhen next index=2 and max key=0, next key is 0 or 2.";var_dump($a,$b);// $a = [0=>3]  $b = [2=>4]  what?$a=[1,2];unset($a[1],$a[0]);$b=$a;$b[]=4;$a[]=3;echo"\nIf reverse variable, result is reverse too.\n";var_dump($a,$b);// $a = [2=>3]  $b = [0=>4]  what??$a=[1,2];unset($a[1],$a[0]);$b=$c=$d=$a;$a[]=3;$b[]=4;$d[]=6;$c[]=5;echo"\nWhen increase variables, only the last one take over key..\n";var_dump($a,$b,$c,$d);// $c's key is 2, others are 0

Expected result

Always same key whth $a and $b.

Actual result

Same key when array is not empty.
Not same key when array is empty.

2020/03/11 00:17

cmb69氏から修正のプルリクが提出されました。
バグレポ提出からの時間、なんと40分。
はえーよ。

ちなみにコミット内容は追加22行変更1行ですが、うち追加22行はテストコードなので、実質的な変更は1行だけです。
変更内容は要素数が0のときもnNextFreeElementを引き継ぐようにするというもので、これまた @crhgさんのコメントのとおりです。

cmb69氏はRFCにはあまり出てこないので一見目立ちませんが、githubbugs.php.netでは見ない日がないんじゃないかってくらい、バグフィックスやソースの整理などで大活躍しているメイン開発者の一人です。

2020/03/11 00:35

Nikitaから物言いがつく。

本当にインデックス継続でいいの?
リセットする方が正しいんじゃないか?
継続することにメリットはある?

などと言いつつApproveしています。ツンデレかよ。

Nikitaは最近最も仕事しているPHP開発者で、そしてガッチガチstrictぺちぱーの急先鋒です。

2020/03/11 17:13

このあたりでmasterにマージされました

2020/04/16

この修正が取り込まれたPHP7.4.5がリリースされました

ChangeLogに記載されました
めでたし。

感想

実際にプルリクを出したりコードが採用されたりしたわけではないので、PHP本体に貢献できた!と堂々と主張できるかというと微妙ですが、自分の提案が影響を与えたかと思うとちょっとだけ嬉しいですね。

バグレポを出そう

Qiitaではどうもプルリクを出してようやく一人前、プルリクを出さずんば開発者に非ず、みたいな風潮がありますが、そこまでやらなくてもバグレポ程度でも役には立ちますよ、という趣旨の記事でした。
みんなもおかしなところを見つけたらバグレポを出しましょう。
もっとも、おかしいな、これはバグかな、って思った原因の99%は自分のコードが間違ってるからなので、確認と見極めが大切です。

まあ、私がプルリクを出さなかったのはひとえに面倒だったからというだけなので、プルリクを出せるようなら出した方がもっといいとは思いますけどね。

バグレポを出すにあたって気を付けること

最低限絶対必要なものは、『再現するコード』です。

今回私のバグレポが速攻で対応されたのも、再現するコードを3v4lで出したからというのが大きいと思います。
バグレポを見てのとおり私の英語力はとってもアレなうえ、"When arrays next index=2 and max key=2, next key is both 2"のところとか数値も間違っているというぐだぐだなバグレポだったのですが、しかし出力が明白なコードを付けたおかげで、意図した問題点は汲み取ってもらえました。

次いで『期待する結果』『実際の結果』も必要です。
これがないと、どう修正すればいいかわかりませんからね。
今回私はAlways same key whth $a and $b.と"揃えろ"だけ書いていて、"〇〇に揃えろ"のように具体的な値を示していません。
これは0にしたほうがいいのか2にしたほうがいいのかわからなかったからです。
いわゆるひとつのお任せします(責任転嫁)というやつです。

あとは『再現環境』です。
今回みたいに3v4lで再現するなら特に気にする必要もないと思いますが、CLIでしか発生しないとか、Windows環境でしか発生しないとか、そういう場合は記載が必要となるでしょう。

このように『再現するコード』『期待する結果』『実際の結果』『再現環境』をきちんと揃えることで、デバッガの人も対応しやすくなります。
その程度当然じゃないかと思うかもしれませんが、意外とできていない人が多いのは某QAサイトとか見ていてもよくわかるとおりです。
さすがにbugs.php.netにはそういう人はあまりいませんが、決して全くいないわけではなく、そしてそういう人の書いたバグレポは数年にわたって放置されます。

そんなわけで、注意点に気をつけつつバグレポをどんどん出しましょう。

【PHP8.0】PHPでアトリビュート/アノテーション/デコレータが書けるようになる

$
0
0

Attributes v2というRFCが投票中です。
投票期間は2020/05/04まで、投票者の2/3の賛成で受理されます。
2020/04/27時点では賛成48反対1で、ほぼ間違いなく可決されます。

Attributes v2

Introduction

このRFCは、クラス/プロパティ/関数/メソッド/引数/定数の宣言に、構造化されたアトリビュートをメタデータとして記述できるようにする提案です。
アトリビュートは、コードの宣言に直接設定ディレクティブを埋め込むことで定義されます。

同じような概念としてJavaのAnnotation、C#/C++/Rust/HackにおけるAttribute、Python/JavascriptにおけるDecoratorが存在します。

これまで、PHPではこのようなメタデータとしては非構造的であるdoc-commentsしか存在しませんでした。
しかしdoc-commentsはただの文字列であり、言語によって解釈されることはありません。
構造化された情報を保持するために、PHPの様々なコミュニティにおいて@ベースの疑似メタデータが考案されてきました。

ユーザランドでの使用例に加え、拡張機能などではコンパイルや構文解析、コード生成、実行時の挙動に影響を与えるようなアトリビュートの使用例も多く存在します。
下のほうに例を示します。

ユーザランドのdoc-commentsが多く使われていることから、この機能がコミュニティから強く求められていることがわかります。

Proposal

Attribute Syntax

アトリビュートは、既存の構文トークンT_SLT_SR、すなわち<<>>を使った特別なフォーマットのテキストです。

アトリビュートは言語内の多くの対象に適用されます。
・関数 (クロージャやアロー関数含む)
・クラス (無名クラス含む)、インターフェイス、トレイト
・クラス定数
・プロパティ
・メソッド
・関数/メソッド引数

以下は例です。

<<ExampleAttribute>>classFoo{<<ExampleAttribute>>publicconstFOO='foo';<<ExampleAttribute>>public$x;<<ExampleAttribute>>publicfunctionfoo(<<ExampleAttribute>>$bar){}}$object=new<<ExampleAttribute>>class(){};<<ExampleAttribute>>functionf1(){}$f2=<<ExampleAttribute>>function(){};$f3=<<ExampleAttribute>>fn()=>1;

アトリビュートはdoc-blockコメントと同様に、それが属する宣言の直前に記載します。
doc-blockコメントとどちらが先かといえば、コメントの前にも後ろにも書くことができます。

<<ExampleAttribute>>/** docblock */<<AnotherExampleAttribute>>functionfoo(){}

関数、クラス、メソッド、プロパティ、パラメータはひとつ以上のアトリビュートを持つことができます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

ひとつの対象に同じアトリビュート名を複数回指定することが可能です。

アトリビュートは1行で複数回宣言できます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

<<>>は式の接頭辞として使用されており、他言語で使用されているジェネリクスの一般的な構文<T>がもし今後導入されることになったとしても、それがアトリビュート構文と衝突することはありません。

構文についてはこのRFCで最も議論されてきたポイントであり、@:として表される新たな構文トークンT_ATTRIBUTEを導入する代替案も考えられました。
構文の選択はRFCの二次投票となります。

@:WithoutArgument@:SingleArgument(0)@:FewArguments('Hello','World')functionfoo(){}

この構文は、doc-blockコメントで一般的に見られる@を使用します。
欠点としては、空白を許可するとアトリビュートの終端を判別できなくなってしまうので、空白を禁止しなければならないことです。

最も要望されるであろう@[]が使えない理由については、下にあるAlternative Syntaxの議論を参照してください。

Attribute Names Resolve to Classes

アトリビュートは、コンパイル中にその時点でインポートされている全てのシンボルについて解決されます。
これはアトリビュートに名前空間を許容するためであり、別のライブラリやアプリケーションで同じアトリビュートが誤って再利用されるのを避けるためです。

useMy\Attributes\SingleArgument;useMy\Attributes\Another;<<SingleArgument("Hello")>><<Another\SingleArgument("World")>><<\My\Attributes\FewArguments("foo","bar")>>functionfoo(){}

このことはクラスにアトリビュートを宣言する際にも利点があります。
・リフレクションが解析しやすい。後述。
・静的解析ツールによる検証が容易になる。
・IDEがオートコンプリートや引数に対応することができる。

クラスアトリビュートを指定する例は以下のようになります。

namespaceMy\Attributes;usePhpAttribute;<<PhpAttribute>>classSingleArgument{public$value;publicfunction__construct(string$value){$this->value=$value;}}

Compiler and Userland Attributes

このRFCは、2種類のアトリビュートを区別しています。
・コンパイラアトリビュート:コンパイル時に検証される
・ユーザランドアトリビュート:リフレクションで検証される

コンパイラアトリビュートは、PhpCompilerAttributeに属する内部クラスです。

ユーザランドアトリビュートは、PhpAttributeに属するユーザランドのクラスです。

コンパイル時にコンパイラアトリビュートが見つかると、実行エンジンはアトリビュートに紐付けられているバリデーションのコールバックを呼び出します。
たとえばこのパッチにはPhpCompilerAttributeのバリデーションコールバックが含まれており、ユーザがPhpCompilerAttributeを使うのを防いでいます。

#include "zend_attributes.h"
voidzend_attribute_validate_phpcompilerattribute(zval*attribute,inttarget){if(target!=ZEND_ATTRIBUTE_TARGET_CLASS){zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used on class declarations and only on internal classes");}else{zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead");}}INIT_CLASS_ENTRY(ce,"PhpCompilerAttribute",NULL);zend_ce_php_compiler_attribute=zend_register_internal_class(&ce);zend_compiler_attribute_register(zend_ce_php_compiler_attribute,zend_attribute_validate_phpcompilerattribute);

引数zvalは渡された全ての引数を含み、targetはアトリビュートが正しく宣言されているかを検証する為の定数です。
ユーザランドクラスはPhpCompilerAttributeを使用することができません。
使おうとするとエラーが発生します。

<?php<<PhpCompilerAttribute>>classMyAttribute{}// Fatal error: The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead

アトリビュートをクラスツールにマッピングすることで、エディタやIDEは、アトリビュートの構文やコンテキスト情報を開発者に提供することができます。
この方法の欠点は、コンパイラアトリビュートがユーザランドアトリビュートであると誤って分類されてしまうことです。

Constant Expressions in Attribute Arguments

アトリビュートは定数AST式として評価されますが、これは引数を許可することを意味します。

<<SingleArgument(1+1)>><<FewArguments(PDO::class,PHP_VERSION_ID)>>

この主な使用例は、定数/クラス定数を参照することです。
定数を参照することで、既に定数として存在する情報を再定義する重複を避けることができます。
もうひとつの利点として、ツールやIDEによる静的解析でアトリビュートを検証できるということです。

定数ASTは、リフレクションを通してアクセスする際は値に解決されます。
これはかつて提出されたアトリビュートRFCとは意図的に異なる挙動です。

またパーサは、ビットシフト演算子とアトリビュート構文を区別できます。

<<BitShiftExample(4>>1,4<<1)>>functionfoo(){}

Reflection

ReflectionクラスにgetAttributes()メソッドが追加され、ReflectionAttributeインスタンスの配列を返します。

functionReflectionFunction::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClass::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionProperty::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClassConstant::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];

引数$nameがあれば指定したアトリビュート、もしくはそのサブクラスを含めたものを返します。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\SingleArgument::class);

引数$flagが未指定の場合、getAttributes()メソッドは名前が完全一致したアトリビュートだけを返し、この動作がデフォルトです。
ReflectionAttribute::IS_INSTANCEOFを指定すると、instanceofを通過する全てのアトリビュートを返すようになります。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\MyAbstractAttribute::class,\ReflectionAttribute::IS_INSTANCEOF);

ReflectionAttributeクラスは以下のようになります。

classReflectionAttribute{publicfunctiongetName():stringpublicfunctiongetArguments():arraypublicfunctionnewInstance():object}

アトリビュートの検証はReflectionAttribute::newInstance()でのみ行われるので、実は必ずしもアトリビュート名に対応したクラスを定義する必要はありません。
アトリビュート名と引数は直接ReflectionAttributeから取って来れます。

以下は完全な例です。

namespaceMy\Attributes{<<PhpAttribute>>classSingleArgument{public$argumentValue;publicfunction__construct($argumentValue){$this->argumentValue=$argumentValue;}}}namespace{<<SingleArgument("Hello World")>>classFoo{}$reflectionClass=new\ReflectionClass(Foo::class);$attributes=$reflectionClass->getAttributes();var_dump($attributes[0]->getName());var_dump($attributes[0]->getArguments());var_dump($attributes[0]->newInstance());}/**
string(28) "My\Attributes\SingleArgument"
array(1) {
  [0]=>
  string(11) "Hello World"
}
object(My\Attributes\SingleArgument)#1 (1) {
  ["argumentValue"]=>
  string(11) "Hello World"
}
**/

この使い方では、getAttributes()は決して例外をスローしません。
これにより、異なるライブラリが同じ名前のアトリビュートを定義していた際の問題を回避することができます。

Use Cases

Use Cases for PHP Extensions

アトリビュートの主な使用先は、PHPコアと拡張モジュールになるでしょう。

HashTablesへのアトリビュートは、全てのzend_class_entry/op_array/zend_property_info/zend_class_constantで使用可能です。

PHPコアや拡張モジュールは、ある定義にアトリビュートがあるかどうかチェックしたくなることがあるでしょう。
例としてOpcache JITに対する@jitのチェックなどです。
これは、関数やメソッドを常に最適化するようJITに指示します。

アトリビュートが実装されれば、拡張モジュールでは以下のように書けるようになります。

staticintzend_needs_manual_jit(constzend_op_array*op_array)returnop_array->attributes&&zend_hash_str_exists(op_array->attributes,"opcache\\jit",sizeof("opcache\\jit")-1));

開発者は、doc-commentのかわりにアトリビュートを使うことができます。

useOpcache\Jit;<<Jit>>functionfoo(){}

Other potential core and extensions use cases/ideas

以下はアトリビュートの使用法のアイデアです。
RFCの一部ではないことに注意してください。

関数/メソッドの非推奨。
アトリビュートを持つほぼ全ての言語にこの機能が組み込まれています。
PHPにこれがあれば、クラスやプロパティ、定数を非推奨にすることができます。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deprecated;<<Deprecated("Use bar() instead")>>functionfoo(){}

非推奨アトリビュートは、今のところtrigger_errorを使うことができません。

classFoo{<<Deprecated()>>constBAR='BAR';}echoFoo::BAR;// PHP Deprecated:  Constant Foo::BAR is deprecated in test.php on line 7

Reclassify Engine WarningsSupport Rewinding GeneratorsのRFCのようなレガシー動作を、オプトインで変更します。
Rustが似たような機能を持っています。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deny;usePhp\Attributes\Allow;<<Allow("rewind_generator")>>functionbar(){yield1;}<<Deny("undeclared_variables")>>functionfoo(){echo$foo;// PHP Fatal error:  Uncaught TypeError: Access to undeclared variable $foo}<<Deny("dynamic_properties")>>classFoo{}$foo->bar;// PHP Fatal error:  Uncaught Error: Invalid access to dynamic property Foo::$bar

Rustっぽいマクロの一部は、旧バージョンのPHPでのみPolyfillを読み込んだりするときに便利かもしれません。
ライブラリがOpcacheやpreloadingなどを条件付きで宣言するときに役立つでしょう。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\ConditionalDeclare;usePhp\Attributes\IgnoreRedeclaration;<<ConditionalDeclare(PHP_VERSION_ID<70000)>>// PHP7.0以上ならASTによって削除される<<IgnoreRedeclaration>>// 重複時はエラーを出さず単に無視するfunctionintdiv(int$numerator,int$divisor){}

最終的には、あるアトリビュートの引数を返すAPIや、全アトリビュートの一覧を返すAPIが含まれる予定です。
これによって拡張機能の作者は、最小限の労力でアトリビュートを使うことができるようになります。
以下は草案です。

/* アトリビュート名から引数一覧を返す */HashTable*zend_attribute_get(HashTable*attributes,char*name,size_tname_len);/* アトリビュートを返す */zval*zend_attribute_all(HashTable*attributes,char*name,size_tname_len);

Userland Use-Case: Declaring Event Listener Hooks on Objects

ユーザランドにおいて、アトリビュートは宣言に対する追加設定を宣言のすぐ傍に置くことができるという利点があります。
以下はSymfonyのEventSubscribersをアトリビュートを使ってリファクタリングする例です。
EventSubscriberInterfaceは、イベントをどのクラスのどのメソッドで処理するかをgetSubscribedEvents()で宣言する必要があります。

// 現在のコードclassRequestSubscriberimplementsEventSubscriberInterface{publicstaticfunctiongetSubscribedEvents():array{return[RequestEvent::class=>'onKernelRequest'];}publicfunctiononKernelRequest(RequestEvent$event){}}// リファクタした<<PhpAttribute>>classListener{public$event;publicfunction__construct(string$event){$this->event=$event;}}classRequestSubscriber{<<Listener(RequestEvent::class)>>publicfunctiononKernelRequest(RequestEvent$event){}}// アトリビュートを使ったイベントディスパッチャclassEventDispatcher{private$listeners=[];publicfunctionaddSubscriber(object$subscriber){$reflection=newReflectionObject($subscriber);foreach($reflection->getMethods()as$method){// Listenerアトリビュートを取得$attributes=$method->getAttributes(Listener::class);foreach($attributesas$listenerAttribute){/** @var $listener Listener */$listener=$listenerAttribute->newInstance();// $listener->eventはcallable$this->listeners[$listener->event][]=[$subscriber,$method->getName()];}}}publicfunctiondispatch($event,$args...){foreach($this->listeners[$event]as$listener){// 呼び出し$listener(...$args);}}}$dispatcher=newEventDispatcher();$dispatcher->addSubscriber(newRequestSubscriber());$dispatcher->dispatch(RequestEvent::class,$payload);

Userland Use-Case: Migrating Doctrine Annotations from Docblocks to Attributes

アトリビュートのRFCが考慮した主要なケースのひとつが、広く普及しているDoctrine Annotationsライブラリからの移行可能性です。

PHPコアがアトリビュートをサポートすることで、ユーザがDoctrine Annotationsからアトリビュートへ移行するための基盤を確保することができます。

このためこのRFCは、名前空間を使ったアトリビュートの操作が主な要件となっています。

Doctrineおよび任意のユーザランドライブラリは、親クラスの名前フィルタを利用して、興味のあるアトリビュートだけを抽出することができます。
提案のリフレクションAPIを使用し、独自のロジックを追加することで、より厳格なアトリビュートの使用を強制することができるようになります。

以下に、Doctrineのアノテーションと、このRFCのアトリビュートで同じことを実装した複雑なオブジェクトの例を示します。

<?phpuseDoctrine\ORM\AttributesasORM;useSymfony\Component\Validator\ConstraintsasAssert;<<ORM\Entity>>/** @ORM\Entity */classUser{/** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */<<ORM\Id>><<ORM\Column("integer")>><<ORM\GeneratedValue>>private$id;/**
     * @ORM\Column(type="string", unique=true)
     * @Assert\Email(message="The email '{{ value }}' is not a valid email.")
     */<<ORM\Column("string",ORM\Column::UNIQUE)>><<Assert\Email(array("message"=>"The email '{{ value }}' is not a valid email."))>>private$email;/**
     * @ORM\Column(type="integer")
     * @Assert\Range(
     *      min = 120,
     *      max = 180,
     *      minMessage = "You must be at least {{ limit }}cm tall to enter",
     *      maxMessage = "You cannot be taller than {{ limit }}cm to enter"
     * )
     */<<Assert\Range(["min"=>120,"max"=>180,"minMessage"=>"You must be at least {{ limit }}cm tall to enter"])>><<ORM\Column(ORM\Column::T_INTEGER)>>protected$height;/**
     * @ORM\ManyToMany(targetEntity="Phonenumber")
     * @ORM\JoinTable(name="users_phonenumbers",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
     *      )
     */<<ORM\ManyToMany(Phonenumber::class)>><<ORM\JoinTable("users_phonenumbers")>><<ORM\JoinColumn("user_id","id")>><<ORM\InverseJoinColumn("phonenumber_id","id",JoinColumn::UNIQUE)>>private$phonenumbers;}

アトリビュートは名前付きパラメータをサポートしていないため、少し制限があります。
これがアトリビュートが関数呼び出しのような構文を利用している理由ですが、もし名前付きパラメータがサポートされれば、自動的にこのRFCもその恩恵を受けることになります。

ユーザランドのアトリビュートへの移行には、Rectorのようなツールが役立ちます。

Criticism and Alternative Approaches

Alternative Syntax: Why not use @ or [] like other languages?

どうして@[]ではないの?

構文として<<>>を使用する理由は、コードのこの場所でまだ使用できる数少ない構文のひとつであり、その中でも自然に見えるものだからです。
接頭辞演算子としてまだ使われていない他の記号を使うことも可能ですが、実質的に使えそうなものは'%'くらいです。
他に使える記号というのは|=/などです。

@[]は、エラー抑制演算子と配列短縮構文に競合するため使用することができません。
以下のような構文は、現在既に有効なPHPコードです。

[[@SingleArgument("Hello")]]

この構文が配列宣言かアトリビュートかを決定するためには、無制限に先のトークンを調べなければならなくなる可能性があります。

Why not extending Doc Comments?

Doc Commentsをそのまま拡張するのではだめなの?

アトリビュートはDoc Commentsより優れています。

・名前空間により、同じDoc Commentsを使用している複数のライブラリによる競合を防ぎます。
・アトリビュートの存在チェックはo(1)のハッシュキーチェックであり、strstrやDoc Commentsのパースより高性能です。
・アトリビュートをクラスにマッピングすることで、アトリビュートが正しい構文であることを保証し、Doc Commentsの書き間違いによるバグの発生を減らします。
・アノテーションは非常に多くのツールやコミュニティで一般に使われているので大きな需要があります。しかし初心者がコメントと見誤ると混乱を招くことになるでしょう。また/*/**の違いもバグの原因になります。

PHPで既存のDoc Commentsを解析して構造化することは可能かもしれませんが、Doc Commentsの方言ごとに追加のパーサを呼び出す必要があります。
Doc Commentsは正しい文法になっていない可能性があるため、文法エラーの取り扱いを決めなければなりません。
最終的に、これはPHP内部に別の言語が存在するということになりそうです。
この方法は、アトリビュートを導入するより遙かに複雑となり、望ましくありません。

Why not always map attributes to simple arrays instead for simplicity?

シンプルに、アトリビュートは常に配列にマッピングするようにすればよくない?

アトリビュートのクラス名解決が何故重要なのか、その利点を前のセクションで解説しました。
アトリビュートが正しいかどうかの検証は、文法が正しいかどうかを検証できないDoc Commentsよりも大きなメリットがあります。

Why not a stricter solution like Doctrine Annotations?

Doctrine Annotationsのようにより厳密なソリューションは導入しないの?

このRFCは、PHPの基本的なアトリビュート機能のみを提案しています。
一般的なソリューションを全て解決するためには様々なユースケースを考慮しなければなりませんが、大抵の場合はDoctrineほど細かなシステムは必要ありません。

Why are nested attributes not allowed?

アトリビュートのネストが許可されないのは何故?

アトリビュートのネストとは、あるアトリビュートを他のアトリビュートの引数として定義するということを意味します。
これは引数でアトリビュートを宣言できるということなので、意図的に禁止されています。

Naming (attributes or annotations)

この機能の"アトリビュート"という名前は、既に使われているアノテーションとの混同を避けるために付けられました。
これによってDoctrine AnnotationsはPHP7ではDoc Commentsで実装され、PHP8ではアトリビュートで実装されている、といったことがおこります。

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

RFC Impact

To Core

トークン、ASTノード、zend_class_entry、zend_class_constant、zend_op_array、zend_property_infoの全てにアトリビュートを追加する必要があります。

To SAPIs

なし。

To Existing Extensions

なし。

JITは@JITではなくOpcache\Jitを、@nojitのかわりにOpcache\Nojitを使うことになる予定ですが、まだ未定です。

To Opcache

パッチに含まれており、100%の互換ではない可能性があります。

New Constants

なし。

php.ini Defaults

なし。

Open Issues

なし。

Future Scope

・名前付きパラメータとの統合
・下位互換性を壊すことなく、既存関数を新しい動作で拡張させる。
・関数/メソッドが呼ばれたとき、プロパティ/定数にアクセスしたときに非推奨を通知する<<Deprecated>>
・型付きプロパティとアトリビュートで、JSON/XMLからオブジェクトへの直列化がPHPコアでできるようになる。
<<«SingleArgument("foo"), MultiArgument("bar", "baz")>>を簡単に書けるような短縮構文。

Voting

2020/04/27時点で、導入には賛成48反対1で、ほぼ導入決定です。

構文は<<>>が40人、@:が10人で、<<>>に決まると思われます。

Patches and Tests

https://github.com/beberlei/php-src/pull/2<<>>
https://github.com/kooldev/php-src/pull/2@:

References

他言語でのアトリビュート/アノテーション/デコレータ。

Rust Attributes
C# Attributes
Java Annotation
TypeScript/ECMAScript Decorators
C++ Attributes
Go Tags
Attributes in Hack

かつて却下されたり放棄されたRFC。

Attributes v1
Annotations v2
Reflection Annotations using the Doc-Comment
Simple Annotations
Annotations in DocBlock RFC
Class Metadata RFC

感想

ほぼ全員賛成というのがちょっと信じがたいんだけど。
個人的にはどちらかというと賛成ですが、正直もっと意見が割れてもよさそうな提案ですよね。

ということで、PHP8からはアトリビュートが使えるようになります。
例を見るに、AttributeInterfeceをimplementsしたりとかも不要で、普通にクラスを書いたらいきなりアトリビュート名として使えるんですかねこれ。
ちょっとユーザレベルでの使い方がいまいちよくわかりませんでした。

というか正直、全体的に意味のよくわからないところが多々ありました。
私は普段ソースまで追ってないので、いきなりAST構文木とかzend_class_entryとか言われても知らんがな!ってかんじですよ。
きっと誰かが補足してくれるはず。

あと、それでは具体的にデフォルトでどんなアトリビュートが用意されてるの、ってのもRFCには書かれていません。
複雑なアトリビュートを定義するにはどうすればいいの、というのもRFCではちょっとよくわかりません。
このあたりは今後ドキュメントの追加をおねがいしたいところですね。
まあPHPのマニュアルは親切すぎて困るくらい丁寧なので、そのうち充実してくるとは思いますが。


【PHP8.0】StartsWith/EndsWithがPHP本体に実装される

$
0
0

先日PHP8でstr_containsが導入されることが決まったばかりですが、さらにもっと直接的な『〇〇で始まる』『〇〇で終わる』関数までも導入されることになりました。

Add str_starts_with() and str_ends_with() functionsというRFCが投票中です。
2020/05/04時点では賛成50反対4で、ほぼ導入確定です。

PHP RFC: Add str_starts_with() and str_ends_with() functions

Introduction

str_starts_withは、文字列が指定の文字列で始まるか否かをチェックし、結果をbool値で返します。
str_ends_withは、文字列が指定の文字列で終わるか否かをチェックし、結果をbool値で返します。

これらの機能は既存の文字列関数、たとえばsubstrstrpos/strrpos、そしてstrncmpsubstr_compare、あまつさえstrlenなどを駆使して実装されてきました。
これらユーザランドの実装には、様々な問題点があります。

str_starts_withとstr_ends_withの需要は高く、SymfonyLaravelYiiFuelPHP、そしてPhalconと、あらゆるフレームワークによってサポートされています。

文字列の始めと終わりをチェックすることは非常に一般的なタスクであり、簡単に行えるべきです。
多くのフレームワークがこのタスクを実装しているということは、このタスクを実行することが簡単ではないことを意味しています。
JavaScript/Java/Haskell/Matlabといった多くの高水準言語が標準でこの機能を実装している理由でもあります。
文字列の開始と終了をチェックすることは、これだけのためにフレームワークを導入したり、ユーザランドで最適ではない(どころかバグが入るかもしれない)実装を行ったりする必要のあるべき作業ではありません。

Downsides of Common Userland Approaches

この機能のアドホックな実装は、専用関数に比べると直感的ではありません。
PHPのニュービーや、他言語と同時開発する開発者にとっては特にそうです。
また、特に===を含む場合、実装を簡単に間違えます。
さらに多くのユーザランド実装はパフォーマンス上の問題があります。

注意:以下の実装には、E_WARNINGを防ぐために$needle === "" ||strlen($needle) <= strlen($haystack) &&のようなガードを入れましょう。

str_starts_with

substr($haystack,0,strlen($needle))===$needle

$haystackの無駄なコピーが発生するため、メモリ効率が良くありません。

strpos($haystack,$needle)===0

$needleが見つからなかった場合に$haystackを最後まで調べてしまうため、CPU効率が悪くなります。

strncmp($haystack,$needle,strlen($needle))===0// genericstrncmp($subject,"prefix",6)===0// ad hoc

これは効率的ですが、$needleの文字数を別に渡す必要があり冗長です。

str_ends_with

substr($haystack,-strlen($needle))===$needle

str_starts_with同様、メモリ効率がよくありません。

strpos(strrev($haystack),strrev($needle))===0

str_starts_with同様CPU効率が悪いだけでなく、文字列反転処理まで入るので、さらに非効率です。

strrpos($haystack,$needle)===strlen($haystack)-strlen($needle)

冗長であり、CPURL効率が悪くなることがあります。

substr_compare($haystack,$needle,-strlen($needle))===0// genericsubstr_compare($subject,"suffix",-6)===0// ad hoc

効率的ですが、冗長です。

Proposal

2つの関数、str_starts_with()str_ends_with()を導入します。

str_starts_with(string$haystack,string$needle):boolstr_ends_with(string$haystack,string$needle):bool

str_starts_with()は、$haystack$needleで始まるかどうかを調べます。
strlen($needle) > strlen($haystack)であれば即座にfalseを返し、そうでなければ両文字列を比較し、先頭一致すればtrueを、一致しなければfalseを返します。

str_ends_with()も同じですが、後方一致です。

以下に例を示します。

$str="beginningMiddleEnd";if(str_starts_with($str,"beg"))echo"printed\n";// trueif(str_starts_with($str,"Beg"))echo"not printed\n";// falseif(str_ends_with($str,"End"))echo"printed\n";// trueif(str_ends_with($str,"end"))echo"not printed\n";// false// 空文字if(str_starts_with("a",""))echo"printed\n";// trueif(str_starts_with("",""))echo"printed\n";// trueif(str_starts_with("","a"))echo"not printed\n";// falseif(str_ends_with("a",""))echo"printed\n";// trueif(str_ends_with("",""))echo"printed\n";// trueif(str_ends_with("","a"))echo"not printed\n";// false

空文字に関しては、受理済のstr_containsのRFCの挙動に従います。
これはJavaやPythonなどと共通の動作です。

Backward Incompatible Changes

ユーザランドに同名の関数がある場合は競合します。

Proposed PHP Version(s)

PHP8

RFC Impact

・SAPI:全てのPHP環境に関数が追加されます
・エクステンション:無し
:Opcache:無し
・New Constants:無し
・php.ini Defaults:無し

Votes

投票は2020/05/04まで。
投票者の2/3+1の賛成で受理されます。

Patches and Tests

https://github.com/php/php-src/pull/5300

References

他言語の類似機能
・JavaScript: String#startsWith() / String#endsWith()
・Python: str#startswith() / str#endswith()
・Java: String#startsWith() / String#endsWith()
・Ruby: String#start_with?() / String#end_with?()
・Go: strings.HasPrefix() / strings.HasSuffix()
・Haskell: Data.String.Utils.startswith / Data.String.Utils.endswith
・MATLAB: startsWith()) / endsWith()

bugs.php.net
bug #50434 / bug #60630 / bug #67035 / bug #74449

過去のRFC
PHP RFC: rfc:add_str_begin_and_end_functions

Rejected Features

大文字小文字を区別しない版とマルチバイト版は、以前のRFCには含まれていましたが、このRFCでは廃止されました。
理由はstr_containsを参照してください。

感想

PHPの文字列関数ってやたら大量に用意されてるわりに意外と基本的なところが抜けていたのですが、PHP8でstr_continsとこの関数が追加されたことによって、テキスト処理に必要なものは出揃うことになったのではないでしょうか。

他に必要なのって何かありますかね。デフォルト関数の命名規則とか?

【PHP8.0】なんでもあり型が書けるようになる

$
0
0

ジェネリクスではない…ジェネリクスではないのだよ………

ざっくり言うとvar_dump()の型引数です。
var_dumpにはプリミティブ値にオブジェクトにリソース型にと、どんな値でも渡すことができるのですが、PHP7.4時点の型システムではvar_dumpの引数の型を表すことができません。
PHP8.0で導入予定のunion型を使うとarray|bool|callable|int|float|null|object|resource|stringとなるのですが、実はresource型はPHP8.0でもまだ使えないので、mixed型を完全に再現することはできません。

ということでMixed Type v2のRFCが提出されました。
投票は2020/05/21まで、受理には2/3+1の賛成が必要です。
が2020/05/11時点では賛成35反対6で、おそらく受理されます。

Introduction

PHP7のスカラー型、7.1のnull許容型、7.2のobject型、そして最新8.0のUNION型とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。

しかし、PHPは常に型をサポートしてきたわけではありません。
そしてこれは、型情報が欠落している際にその意味が曖昧になってしまうという問題に繋がります。

・特定の型に決まっているが、プログラマが宣言を忘れてしまった
・特定の型に決まっているが、古いバージョンのPHPと互換を保つためにあえて省略している
・現在のPHPの型システムでは表現できない型である

明示的なmixed型を用意することで、引数や返り値、プロパティに型を追加して、型情報を忘れていたわけではなく、正確に指定できなかったりあえて広げているのだという主張をを示すことができます。

現在のところmixed型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocでmixed型が使用されている顕著な例としては、PHP標準ライブラリ関数の返り値などがあります。
ネイティブにmixed型があれば、これらをより正確に表現することができるでしょう。

またmixed型はPHPマニュアルにおいても広く使用されています。

var_dump(mixed$expression[,mixed$...]):void

Proposal

PHPの型システムにmixed型を追加します。
これはarray|bool|callable|int|float|null|object|resource|stringと等価です。
これはPHPの継承時の型検査の実装に適合する正しい動作です。

LSP, Covariance and Contravariance

このProposalは、リスコフの置換原則に準拠しています。

PHP7.4以降、PHPは共変戻り値と反変パラメータに対応しています。

PHPではLSP原則に従うように、パラメータの拡大を許容しています。
サブクラスにおいて、親クラスより広い、特殊でない型を使用することができます。

PHPではLSP原則に従うように、返り値の縮小を許容しています。
サブクラスにおいて、親クラスより狭い、特殊な型を使用することができます。

Parameter types are contravariant

引数は、特定の型からmixed型に拡大することができます。

// 正しい例classA{publicfunctionfoo(int$value){}}classBextendsA{// intからmixedに拡大は許可publicfunctionfoo(mixed$value){}}

引数の縮小は、LSP原則に違反するため許可されません。

// 不正な例classA{publicfunctionfoo(mixed$value){}}classBextendsA{// mixedからintに縮小は不可// Fatal errorが出るpublicfunctionfoo(int$value){}}### Return types are covariant返り値は`mixed`型から特定の型に縮小することができます```php// 正しい例classA{publicfunctionbar():mixed{}}classBextendsA{// mixedからintに縮小は許可publicfunctionbar():int{}}

返り値の拡大は、LSP原則に違反するため許可されません。

// 不正な例classC{publicfunctionbar():int{}}classDextendsC{// intからmixedに拡大は不可// Fatal errorが出るpublicfunctionbar():mixed{}}

Property types are invariant

プロパティ型指定のRFCに従い、プロパティの型は不変です。

// 不正な例classA{publicmixed$foo;publicint$bar;public$baz;}classBextendsA{// プロパティ型は縮小不可// Fatal errorが出るpublicint$foo;}classCextendsA{// プロパティ型は拡大不可// Fatal errorが出るpublicmixed$bar;}classDextendsA{// 未指定にmixed型を追加するのも駄目// Fatal errorが出るpublicmixed$baz;}classEextendsA{// 型指定の削除も駄目// Fatal errorが出るpublic$foo;}

Void return type

void型の返り値については、LSPに適合していたとしても拡張は許可されません。

classA{publicfunctionbar():void{}}classBextendsA{// Fatal error: Declaration of B::bar(): int must be compatible with A::bar(): voidpublicfunctionbar():int{}}

このRFCは、既存の振る舞いに従います。
すなわち、void型をmixed型に広げることはできません。

Signature checking of function when no parameter type present

引数に型が存在しない場合の型チェックは、mixed型が指定されたかのように動作します。

classA{// 引数の型がないのでmixedとみなすpublicfunctionfoo($value){}}classBextendsA{// mixed型を追加したが、親クラスと動きは同じpublicfunctionfoo(mixed$value){}}classCextendsB{// mixed型を削除したが、親クラスと動きは同じpublicfunctionfoo($value){}}classDextendsB{publicfunctionfoo(mixed$value=null){}}

現在のところ、これはクラスの継承に限った動作です。

PHPで型を定義できるようになれば、他のところでも動くようになるかもしれません。

Signature checking of function when no return type present

返り値に型が指定されていない場合の型チェックは、mixed|void型が指定されたかのように動作します。

サブクラスでオーバーロードする際は、返り値を未指定にするか、void型にするか、mixed型およびそのサブタイプの何れかを指定しなければなりません。
そして、一度変更したあとの型を未指定に戻すことはできません。

classA{// 返り値に型がないのでmixed|voidとみなすpublicfunctionfoo(){}}classBextendsA{// mixedを指定した。voidは禁止になるpublicfunctionfoo():mixed{}}classCextendsB{// mixed|voidはmixedより広いのでNG// Fatal errorが出るpublicfunctionfoo(){}}classDextendsB{// voidはmixedのサブタイプではないのでNG// Fatal errorが出るpublicfunctionfoo():void{}}

The mixed|void union type

このRFCは、mixed|voidのUNION型は必要ないので許可しないという立場です。
今後ユースケースが見つかれば許可される可能性はあります。

Nullability

mixed型にはnullが含まれます。
従ってmixed型のnull許容型は情報の重複となります。

このRFCは、mixed型のnull許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。

// NG、既にnull許容なのでfunctionfoo(?mixed$arg){}// NG、既にnull許容なのでfunctionbar():?mixed{}

Explicit returns

返り値にmixed型を使用する場合、明示的にreturnを記述する必要があります。
さもなければTypeErrorが発生します。

functionfoo():mixed{}foo();// Uncaught TypeError: Return value of foo() must be of the type mixed, none returned

既存のnull許容型と同じ動作です。

functionbar():?int{}bar();// Uncaught TypeError: Return value of bar() must be of the type int or null, none returned

Resource 'type'

PHPではresource型の値を変数に割り当てることができますが、ユーザランドでは引数、返り値、プロパティの型としてresource型を使用することができません。
このRFCの立場としては、resource型はresource型チェックをパスすべきである、というものです。

Mixed vs any

PHPではマニュアルやPHPStanなどの静的解析ツールで広くmixed型が使われているため、mixedになりました。
またmixedはPHP7以降弱い予約語とされていますが、anyは予約語に含まれません。

RFC Impact

Proposed PHP Version(s)

PHP8.0

Backward Incompatible Changes

クラス名としてのmixedが禁止されますが、PHP7.0以降mixedは弱い予約語です。

To SAPIs

特になし。

To Existing Extensions

特になし。

To Opcache

特になし。

Vote

2020/05/07に投票開始、2020/05/21に投票終了。
受理には2/3+1の賛成が必要です。

Patches and Tests

GitHub Pull request #5313

References

PHP RFC: Reserve Even More Types in PHP 7
phpDocumentor type reference

感想

これ、UNION型のRFCの将来の展望にある型宣言そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。

ということでPHP8からmixed型が使えるようになります。

var_dumpのような、仕様としてあらゆる値を受け取る必要のある関数のためにこれが必要なことは確かでしょう。

しかし、ユーザランドで何も考えず適当にこれを使って大惨事、という未来が目に見えますね。
まあでも、そんな人はそもそも型引数を書かないだろうから問題ないかな?

これがなくては生きていけないVS Codeエクステンション10選

$
0
0

VisualStudioCodeは大人気なだけあって、有能なエクステンションが次から次へと出てきますね。
色々とっかえひっかえ試して遊んでいる人も多いのではないでしょうか。
なんかMinecraftのMOD環境構築と同じ空気を感じますね。
(環境ができたところで飽きて遊ばなくなる)

まあ一番手っ取り早いのは、既に評価の固まっている手堅いエクステンションを導入することです。
ということで以下はSahil Bondre( Twitter / GitHub / LinkedIn / Instagram / 個人HP )による記事、💡 10 VS Code extensions I can't code withoutの日本語訳です。

💡 10 VS Code extensions I can't code without

VSCodeは私のお気に入りのエディタです。
存在するコードエディタの中でも最も拡張性の高く、人気のあるエディタです。
そして驚いたことに、これはMicrosoftが作っています。
個人的には、他のエディタはVSCodeの機能の半分にも達しているものはないと感じています。
VSCodeをこれほど確固たる地位に押し上げた理由が、そのエクステンションのシステムです。
これにより、おおよそあらゆるユースケースに対応した拡張を書くことができます。
以下で私的エクステンション、トップ10を紹介します。

1. Beautify

ext install HookyQR.beautify

HTML、JS、CSS、JSON、そしてSASSを自分好みのスタイルでフォーマットすることができます。
内部のjs-beautifyを拡張して、自分だけのスタイルにカスタマイズすることもできます。

2. Better Comments

02.png

ext install aaron-bond.better-comments

コメントにセマンティクスを追加することができます。
コメントをアラート、クエリ、TODO、ハイライトのように分類してくれます。

021.png

3. Bookmarks

03.png

何百行ものコードの中で迷子になってしまったときに、このエクステンションが救世主となるでしょう。
コードの任意の行をブックマークすることができます。
ブックマークの一覧から選択するだけで、該当の場所に戻ってくることができます。

031.png

4. Bracket Pair Colorizer 2

04.png

ext install CoenraadS.bracket-pair-colorizer-2

この機能は、その名のとおりです。
括弧を多重に使用した際にペアごとに色分けします。
コードの奥深くに幾つものスコープがあるときに役立つでしょう。

041.png

5. Format in Context Menus

05.png

ext install lacroixdavid1.vscode-format-context-menu

サイドバーから選択するだけで、全てのファイルをフォーマットすることができます。
大量のファイルがあり、そしてあなたの環境がFormatterやLinterをサポートしていない場合に特に有用です。

051.gif

6. Git Graph

06.png

ext install mhutchie.git-graph

リポジトリのGitグラフを表示し、グラフからGitアクションを素早く実行することができます。
高度な設定が可能で、多くの機能を備えています。
このエクステンションの素晴らしさを布教するため、私はきっとまた別の記事を書くことになるでしょう。

061.gif

7. Git Lens

07.png

ext install eamodio.gitlens

GitLensは、blameとCodeLensによってコードのオーサリングを手助けしてくれます。
Gitリポジトリをシームレスにナビゲートしてエクスクローラーしたり、有能な比較コマンドを用いて知見を得たり、その他多くのことが可能です。

071.png

8. indent-rainbow

08.png

ext install oderwat.indent-rainbow

コードのインデントを深さごとに着色してくれます。
これは深いインデントのあるPythonのような言語で特に有用です。

9. Path Intellisense

09.png

ext install christian-kohler.path-intellisense

ファイルパスを自動補完してくれるようになります。
VSCodeはデフォルトでも自動補完をサポートしてますが、それはHTML/CSS/JavaScriptファイルに限定されています。
このエクステンションであらゆる言語とファイルタイプに拡張されます。

091.gif

10. Total Lines

10.png

ext install praveencrony.total-lines

ステータスバーに現在開いているファイルの行数を表示するだけの、小さなエクステンションです。
あなたのエクステンションのコレクションに追加されるべき良いものです。

これが私のお気に入りVSCodeエクステンションです。
あなたのお気に入り拡張機能をコメントでおしえてくれませんか?
ところでBashを学びたくありませんか?
シェルスクリプトのいいクラッシュコースありますよ

🌟 いくつかチートシート作ってるよ。
🚀 Instagram | Github | Twitter | Websiteでストーキングしてくれていいのよ。
😄 素敵な一日を過ごせますように!

コメント欄

「VSC半年使ってたのにBookmarks知らんかった。いったいどれだけ時間を無駄にしたのだろう。」「Codeletsでブックマーク。こいつはいいぞ最高だぞ。」
「Bookmarksさっそくインストールして、コードを後で参考にするために使ってる。」
「Bracket pair colorizer気に入った。ありがとう。」
「未知のエクステンションばっかりだった、さっそく試してみる。」
「HTML、CSS、JS、Java、Node、Ruby、他いろいろエクステンションを入れてたらあまりに巨大になりすぎた。どうにかしなければならないのではないか。」「対象に合わせて拡張パックを作って、それらの有効/無効を切り替えるってやってる。」「それはいいことを聞いた。」「よかったら私の拡張パックを紹介するよ。」

感想

私の環境では既に半分インストール済でした。
星の数ほどエクステンションがあるとは言っても、やはり汎用的に有用なものとなるとある程度数は限られてきますね。

コメント欄ではBookmarksがやたら高評価でした。
気軽に多くの付箋を置いておいて、後からすぐにそこに戻れるので、なかなか便利です。
私のように見終わったタブをついうっかり閉じてしまう癖のある人には、より有用だと思われます。

あとGit Graphも入れてなかったので入れてみたのですが、これ便利だな。
これまでわざわざ別ウィンドウでTortoiseGitなりGit bashなりを立ち上げていたのですが、VSCode上でGitツリーを表示して、ツリー上で各種操作を簡単に行うことができます。
これは使い方を調べてみる価値がありそうです。

とまあこんなかんじでエクステンションを入れまくっていたら、今何が入っててバージョンがどうなってるとかの管理がさっぱりになります。
既になっています。
言語や開発対象ごとに有効無効を切り替えたいときもあるでしょう。
そんなときのためにエクステンションを管理するエクステンションがほしいところですね。

JavaScriptのアロー関数が好きではない

$
0
0

JavaScriptのアロー関数が好きではない。

constf=()=>{a++;};leta=1;f();console.log(a);// 2 ←

こんな仕様当然みんなからフルボッコのWTF案件だろうと思いきや、批判以前にそもそもこの仕様について言及している記事自体がほとんど見当たらない。
MDNにすらはっきりとは書かれていない。
仕様書まで追えば当然載ってはいるんだけど、こんなところまでいちいち読んでられませんよね。

まあスコープチェーンというJavaScriptデフォルトの仕様のせいであるわけですが、しかしわざわざ最近追加された仕組みなのだからこんなクソ仕様は断ち切ってほしかったところですね。
アロー関数の仕様を決めるときに誰も何も言わなかったのか?

【PHP8.0】例外をcatchしたいけど何もしたくない

$
0
0

例外をcatchしたいけど何もしたくない。

try{foo();}catch(Throwable$e){// 何もしない}

何もしないのにわざわざ変数に受け取るのって無駄じゃありませんか?

というわけでnon-capturing catchesというRFCが提出されました。

PHP RFC: non-capturing catches

Introduction

今のところ、PHPは例外を明示的に変数でキャプチャする必要があります。

try{foo();}catch(SomeException$ex){die($ex->getMessage());}

しかしながら、ときにはその変数を使わない場合もあります。

try{changeImportantData();}catch(PermissionException$ex){echo"You don't have permission to do this";}

これを見た人は、プログラマが意図してこの変数を使わなかったのか、あるいはバグなのかがわかりません。

Proposal

例外を変数に受け取らずcatchできるようにします。

try{changeImportantData();}catch(PermissionException){// catchした変数を使わないという意図が明白echo"You don't have permission to do this";}

Prior art

7年前にあった似たようなRFCは、以下のように例外名も省略可能にするという理由で否定意見が多数でした。

try{foo();}catch{bar();}

いっぽう、このRFCは肯定的な反応が多く、再検討の余地があります。

Backward Incompatible Changes

後方互換性を壊す変更はありません。

Proposed PHP Version(s)

PHP8.0

RFC Impact

特になし。

Vote

投票は2020/05/24まで、投票者の2/3の賛成で受理されます。
このRFCは賛成48反対1の圧倒的多数で受理されました。

Patches and Tests

https://github.com/php/php-src/pull/5345

References

メーリングリスト

感想

このRFCが意図しているところは決して例外の握り潰しなどではなく、例外処理のロジックに例外の中身を使わないときに省略できるというものです。
変数に受け取る処理がなくなるので速度も速まることでしょう。

しかしですな、こんな機能があったら絶対にこんなコードを書く奴が出てくるわけですよ。

PHP8
try{foo();}catch(Exception){}

いやまあ、こんなの書いてしまう人は今でもやってると思いますけどね。

PHP7
try{foo();}catch(Exception$e){}

なら別にあってもいいか。

劇的に便利になるというわけではないですが、ちょっと気が利く書き方ができるようになりますね。

最後にもう一度言いますが、冒頭のコードは悪い例なので決して真似してはいけません。

Viewing all 345 articles
Browse latest View live