diff options
Diffstat (limited to 'vhosts/blog/content/posts/2021-10-02')
7 files changed, 1239 insertions, 0 deletions
diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml new file mode 100644 index 00000000..f87d3a65 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【C++】 属性構文の属性名にはキーワードが使える</title> + <abstract> + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + </abstract> + <keywordset> + <keyword>cpp</keyword> + <keyword>cpp17</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/94090937bcf860cfa93b">https://qiita.com/nsfisis/items/94090937bcf860cfa93b</link> + </note> + <para> + タイトル落ち。まずはこのコードを見て欲しい。 + </para> + <programlisting language="cpp" linenumbering="unnumbered"> + <![CDATA[ + #include <iostream> + + [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]] + [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]] + [[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]] + [[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]] + [[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]] + [[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]] + [[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]] + [[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]] + [[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]] + [[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]] + [[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]] + [[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]] + [[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]] + // [[using]] + int main() { + std::cout << "Hello, World!" << std::endl; + } + ]]> + </programlisting> + <blockquote> + <para> + コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 + (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: + posix InstalledDir: + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin + </para> + <para> + コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp + </para> + </blockquote> + <para> + この記事から得られるものはこれ以上ないので以下は蛇足になる。 + </para> + <para> + 別件で cppreference.com の + <link xl:href="https://en.cppreference.com/w/cpp/language/identifiers">identifier + のページ</link> を読んでいた時、次の文が目に止まった。 + </para> + <blockquote> + <itemizedlist> + <listitem> + the identifiers that are keywords cannot be used for other purposes; + <itemizedlist> + <listitem> + The only place they can be used as non-keywords is in an + attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + </blockquote> + <para> + キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 + 実際にやってみる。 + </para> + <para> + 同サイトの <link xl:href="https://en.cppreference.com/w/cpp/keyword">keywords のページ</link> + から一覧を拝借し、上のコードが出来上がった (C++17 + においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown + attribute `〇〇' ignored) + がコンパイラから出力されるが、コンパイルできる。 + </para> + <para> + 上のコードでは <literal>[[using]]</literal> をコメントアウトしているが、これは <literal>using</literal> + キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 + </para> + <programlisting language="cpp" linenumbering="unnumbered"> + <![CDATA[ + // using の例 + [[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文 + ]]> + </programlisting> + <para> + C++17 の仕様も見てみる (正確には標準化前のドラフト)。 + </para> + <para> + 引用元: <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4">https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4</link> + </para> + <blockquote> + <para> + If a keyword or an alternative token that satisfies the syntactic + requirements of an identifier is contained in an attribute-token, it is + considered an identifier. + </para> + </blockquote> + <para> + 「<literal>identifier</literal> の構文上の要件を満たすキーワードまたは代替トークンが + <literal>attribute-token</literal> に含まれている場合、<literal>identifier</literal> + とみなされる」とある。どうやら間違いないようだ。 + </para> + <para> + ところで、代替トークン (alternative token) とは <literal>and</literal> (<literal>&</literal>) や <literal>bitor</literal> + (<literal>|</literal>) などのことだが、<literal>identifier</literal> + の構文上の要件を満たさないような代替トークンなどあるのか? + 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい + (参考: + <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/lex.digraph">同ドラフト</link>) + </para> + <itemizedlist> + <listitem><literal><%</literal> → <literal>{</literal></listitem> + <listitem><literal>%></literal> → <literal>}</literal></listitem> + <listitem><literal><:</literal> → <literal>[</literal></listitem> + <listitem><literal>:></literal> → <literal>]</literal></listitem> + <listitem><literal>%:</literal> → <literal>#</literal></listitem> + <listitem><literal>%:%:</literal> → <literal>##</literal></listitem> + </itemizedlist> + <para> + 「<literal>identifier</literal> + の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。 + </para> + <para> + 調べた感想: 字句解析器か構文解析器が辛そう + </para> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml new file mode 100644 index 00000000..eb8aa452 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</title> + <abstract> + Python における UnboundLocalError の理由と対処法。 + </abstract> + <keywordset> + <keyword>python</keyword> + <keyword>python3</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/5d733703afcb35bbf399">https://qiita.com/nsfisis/items/5d733703afcb35bbf399</link> + </note> + <para> + 本記事は Python 3.7.6 の動作結果を元にして書かれている。 + </para> + <para> + Python でクロージャを作ろうと、次のようなコードを書いた。 + </para> + <programlisting language="python" linenumbering="unnumbered"> + <![CDATA[ + def f(): + x = 0 + def g(): + x += 1 + g() + + f() + ]]> + </programlisting> + <para> + 関数 <literal>g</literal> から 関数 <literal>f</literal> のスコープ内で定義された変数 <literal>x</literal> を参照し、それに + 1 を足そうとしている。 これを実行すると <literal>x += 1</literal> + の箇所でエラーが発生する。 + </para> + <blockquote> + <para> + UnboundLocalError: local variable `x' referenced before assignment + </para> + </blockquote> + <para> + local変数 <literal>x</literal> が代入前に参照された、とある。これは、<literal>f</literal> の <literal>x</literal> + を参照するのではなく、新しく別の変数を <literal>g</literal> 内に作ってしまっているため。 + 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。<literal>var</literal> + を変数宣言のための構文として擬似的に利用している。 + </para> + <programlisting language="python" linenumbering="unnumbered"> + <![CDATA[ + # 注: var は正しい Python の文法ではない。上記参照のこと + def f(): + var x # f の local変数 'x' を宣言 + x = 0 # x に 0 を代入 + def g(): # f の内部関数 g を定義 + var x # g の local変数 'x' を宣言 + # たまたま f にも同じ名前の変数があるが、それとは別の変数 + x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文) + # 加算する前の値を参照しようとするが、まだ代入されていないためエラー + g() + ]]> + </programlisting> + <para> + 当初の意図を表現するには、次のように書けばよい。 + </para> + <programlisting language="python" linenumbering="unnumbered"> + <![CDATA[ + def f(): + x = 0 + def g(): + nonlocal x ## (*) + x += 1 + g() + ]]> + </programlisting> + <para> + <literal>(*)</literal> のように、<literal>nonlocal</literal> を追加する。これにより一つ外側のスコープ (<literal>g</literal> + の一つ外側 = <literal>f</literal>) で定義されている <literal>x</literal> を探しに行くようになる。 + </para> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml new file mode 100644 index 00000000..7c0c960d --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Ruby】 自身を実行している処理系の種類を判定する</title> + <abstract> + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + </abstract> + <keywordset> + <keyword>ruby</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791">https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791</link> + </note> + <para> + Ruby + という言語には複数の実装があるが、それらをスクリプト上からどのようにして + programmatically に見分ければよいだろうか。 + </para> + <para> + <literal>Object</literal> クラスに定義されている <literal>RUBY_ENGINE</literal> + という定数がこの用途に使える。 + </para> + <para> + 参考: + <link xl:href="https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html">Object::RUBY_ENGINE</link> + </para> + <para> + 上記ページの例から引用する: + </para> + <programlisting language="shell-session" linenumbering="unnumbered"> + <![CDATA[ + $ ruby-1.9.1 -ve 'p RUBY_ENGINE' + ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux] + "ruby" + $ jruby -ve 'p RUBY_ENGINE' + jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] + "jruby" + ]]> + </programlisting> + <para> + それぞれの処理系がどのような値を返すかだが、stack overflow + に良い質問と回答があった。 + </para> + <para> + <link xl:href="https://stackoverflow.com/a/9894232">What values for RUBY_ENGINE + correspond to which Ruby implementations?</link> より引用: + </para> + <blockquote> + <table> + <thead> + <tr> + <td>RUBY_ENGINE</td> + <td>Implementation</td> + </tr> + </thead> + <tbody> + <tr> + <td><undefined></td> + <td>MRI < 1.9</td> + </tr> + <tr> + <td>`ruby'</td> + <td>MRI >= 1.9 or REE</td> + </tr> + <tr> + <td>`jruby'</td> + <td>JRuby</td> + </tr> + <tr> + <td>`macruby'</td> + <td>MacRuby</td> + </tr> + <tr> + <td>`rbx'</td> + <td>Rubinius</td> + </tr> + <tr> + <td>`maglev'</td> + <td>MagLev</td> + </tr> + <tr> + <td>`ironruby'</td> + <td>IronRuby</td> + </tr> + <tr> + <td>`cardinal'</td> + <td>Cardinal</td> + </tr> + </tbody> + </table> + </blockquote> + <para> + なお、この質問・回答は + 2014年になされたものであり、値は変わっている可能性がある。MRI (aka + CRuby) については執筆時現在 (2020/12/8) も <literal>'ruby'</literal> + が返ってくることを確認済み。 + </para> + <para> + この表にない主要な処理系として、https://mruby.org[mruby] は <literal>'mruby'</literal> + を返す。 + </para> + <para> + <link xl:href="https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35">mruby + 該当部分のソース</link> より引用: + </para> + <programlisting language="c" linenumbering="unnumbered"> + <![CDATA[ + /* + * Ruby engine. + */ + #define MRUBY_RUBY_ENGINE "mruby" + ]]> + </programlisting> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml new file mode 100644 index 00000000..0a799a3b --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml @@ -0,0 +1,270 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Ruby】 then キーワードと case in</title> + <abstract> + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + </abstract> + <keywordset> + <keyword>ruby</keyword> + <keyword>ruby3</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</link> + </note> + <section xml:id="tl-dr"> + <title>TL; DR</title> + <para> + <literal>case</literal> - <literal>in</literal> によるパターンマッチング構文でも、<literal>case</literal> - <literal>when</literal> + と同じように <literal>then</literal> が使える (場合によっては使う必要がある)。 + </para> + </section> + <section xml:id="what-is-then-keyword"> + <title><literal>then</literal> とは</title> + <para> + 使われることは稀だが、Ruby では <literal>then</literal> + がキーワードになっている。次のように使う: + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + if cond then + puts "Y" + else + puts "N" + end + ]]> + </programlisting> + <para> + このキーワードが現れうる場所はいくつかあり、<literal>if</literal>、<literal>unless</literal>、<literal>rescue</literal>、<literal>case</literal> + 構文がそれに当たる。 上記のように、何か条件を書いた後 <literal>then</literal> + を置き、式がそこで終了していることを示すマーカーとして機能する。 + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + # Example: + + if x then + a + end + + unless x then + a + end + + begin + a + rescue then + b + end + + case x + when p then + a + end + ]]> + </programlisting> + </section> + <section xml:id="why-then-is-usually-unnecessary"> + <title>なぜ普段は書かなくてもよいのか</title> + <para> + 普通 Ruby のコードで <literal>then</literal> + を書くことはない。なぜか。次のコードを実行してみるとわかる。 + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + if true puts 'Hello, World!' end + ]]> + </programlisting> + <para> + 次のような構文エラーが出力される。 + </para> + <literallayout class="monospaced"> + <![CDATA[ + 20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' + if true puts 'Hello, World!' end + ^~~~ + 20:1: syntax error, unexpected `end', expecting end-of-input + ...f true puts 'Hello, World!' end + ]]> + </literallayout> + <para> + 二つ目のメッセージは無視して一つ目を読むと、<literal>then</literal> か <literal>;</literal> + か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + </para> + <para> + ポイントは改行が <literal>then</literal> (や <literal>;</literal>) の代わりとなることである。<literal>true</literal> + の後に改行を入れてみる。 + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + if true + puts 'Hello, World!' end + ]]> + </programlisting> + <para> + 無事 Hello, World! と出力されるようになった。 + </para> + </section> + <section xml:id="why-then-or-linebreak-is-needed"> + <title>なぜ <literal>then</literal> や <literal>;</literal> や改行が必要か</title> + <para> + なぜ <literal>then</literal> や <literal>;</literal> や改行 (以下 「<literal>then</literal> 等」) + が必要なのだろうか。次の例を見てほしい: + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + if a b end + ]]> + </programlisting> + <para> + <literal>then</literal> も <literal>;</literal> + も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 + この例は二通りに解釈できる。 + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + # a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 + if a then + b + end + ]]> + </programlisting> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + # a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 + # その結果が truthy なら何もしない + if a(b) then + end + ]]> + </programlisting> + <para> + <literal>then</literal> 等はこの曖昧性を排除するためにあり、条件式は <literal>if</literal> から <literal>then</literal> + 等までの間にある、ということを明確にする。 C系の <literal>if</literal> 後に来る <literal>(</literal>/<literal>)</literal> + や、Python の <literal>:</literal>、Rust/Go/Swift などの <literal>{</literal> も同じ役割を持つ。 + </para> + <para> + Ruby の場合、プログラマーが書きやすいよう改行でもって <literal>then</literal> + が代用できるので、ほとんどの場合 <literal>then</literal> は必要ない。 + </para> + </section> + <section xml:id="then-in-case-in"> + <title><literal>case</literal> - <literal>in</literal> における <literal>then</literal></title> + <para> + ようやく本題にたどり着いた。来る Ruby 3.0 では <literal>case</literal> と <literal>in</literal> + キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして + <literal>then</literal> 等が必要になる。 (現在の) Ruby には formal + な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc + の説明は省略)。 + </para> + <para> + <link xl:href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</link> + </para> + <programlisting language="yacc" linenumbering="unnumbered"> + <![CDATA[ + p_case_body : keyword_in + { + SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); + p->command_start = FALSE; + $<ctxt>1 = p->ctxt; + p->ctxt.in_kwarg = 1; + $<tbl>$ = push_pvtbl(p); + } + { + $<tbl>$ = push_pktbl(p); + } + p_top_expr then + { + pop_pktbl(p, $<tbl>3); + pop_pvtbl(p, $<tbl>2); + p->ctxt.in_kwarg = $<ctxt>1.in_kwarg; + } + compstmt + p_cases + { + /*%%%*/ + $$ = NEW_IN($4, $7, $8, &@$); + /*% %*/ + /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ + } + ; + ]]> + </programlisting> + <para> + 簡略版: + </para> + <programlisting language="yacc" linenumbering="unnumbered"> + <![CDATA[ + p_case_body : keyword_in p_top_expr then compstmt p_cases + ; + ]]> + </programlisting> + <para> + ここで、<literal>keyword_in</literal> は文字通り <literal>in</literal>、<literal>p_top_expr</literal> + はいわゆるパターン、<literal>then</literal> は <literal>then</literal> + キーワードのことではなく、この記事で <literal>then</literal> 等と呼んでいるもの、つまり + <literal>then</literal> キーワード、<literal>;</literal>、改行のいずれかである。 + </para> + <para> + これにより、<literal>case</literal> - <literal>when</literal> による従来の構文と同じように、<literal>then</literal> + 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + case x + in 1 then a + in 2 then b + in 3 then c + end + + case x + in 1 + a + in 2 + b + in 3 + c + end + + case x + in 1; a + in 2; b + in 3; c + end + ]]> + </programlisting> + <para> + ところで、<literal>p_top_expr</literal> には <literal>if</literal> による guard clause + が書けるので、その場合は <literal>if</literal> - <literal>then</literal> と似たような見た目になる。 + </para> + <programlisting language="ruby" linenumbering="unnumbered"> + <![CDATA[ + case x + in 0 then a + in n if n < 0 then b + in n then c + end + ]]> + </programlisting> + </section> + <section xml:id="outro"> + <title>まとめ</title> + <itemizedlist> + <listitem> + <literal>if</literal> や <literal>case</literal> の条件の後ろには <literal>then</literal>、<literal>;</literal>、改行のいずれかが必要 + <itemizedlist> + <listitem>通常は改行しておけばよい</listitem> + </itemizedlist> + </listitem> + <listitem>3.0 で入る予定の <literal>case</literal> - <literal>in</literal> でも <literal>then</literal> 等が必要になる</listitem> + <listitem>Ruby の構文を正確に知るには (現状) <literal>parse.y</literal> を直接読めばよい</listitem> + </itemizedlist> + </section> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml new file mode 100644 index 00000000..3aaca63f --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml @@ -0,0 +1,237 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>Rust のプリミティブ型はどこからやって来るか</title> + <abstract> + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + </abstract> + <keywordset> + <keyword>rust</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/9a429432258bbcd6c565">https://qiita.com/nsfisis/items/9a429432258bbcd6c565</link> + </note> + <section xml:id="intro"> + <title>前置き</title> + <para> + Rust + において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 + </para> + <programlisting language="rust" linenumbering="unnumbered"> + <![CDATA[ + #![allow(non_camel_case_types)] + #![allow(dead_code)] + + struct bool; + struct char; + struct i8; + struct i16; + struct i32; + struct i64; + struct i128; + struct isize; + struct u8; + struct u16; + struct u32; + struct u64; + struct u128; + struct usize; + struct f32; + struct f64; + struct str; + ]]> + </programlisting> + <para> + では、普段単に <literal>bool</literal> と書いたとき、この <literal>bool</literal> + は一体どこから来ているのか。rustc のソースを追ってみた。 + </para> + <blockquote> + <para> + 前提知識: 一般的なコンパイラの構造、用語。<literal>rustc</literal> そのものの知識は不要 + (というよりも筆者自身がよく知らない) + </para> + </blockquote> + </section> + <section xml:id="code-reading"> + <title>調査</title> + <para> + 調査に使用したソース (調査時点での最新 master) + </para> + <para> + <link xl:href="https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98">https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98</link> + </para> + <para> + どのようにして調べるか。rustc + の構造には詳しくないため、すぐに当たりをつけるのは難しい。 + </para> + <para> + 大雑把な構造としては、<literal>compiler</literal> フォルダ以下に <literal>rustc_*</literal> + という名前のクレートが数十個入っている。これがどうやら <literal>rustc</literal> + コマンドの実装部のようだ。 + </para> + <para> + <literal>rustc</literal> はセルフホストされている (= <literal>rustc</literal> 自身が Rust で書かれている) + ので、<literal>bool</literal> や <literal>char</literal> + などで適当に検索をかけてもノイズが多すぎて話にならない。 + しかし、お誂え向きなことに <literal>i128</literal>/<literal>u128</literal> + というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って + <literal>git grep</literal> してみる。 + </para> + <literallayout class="monospaced"> + <![CDATA[ + $ git grep "\bi128\b" | wc # i128 + 165 1069 15790 + + $ git grep "\bu128\b" | wc # u128 + 293 2127 26667 + + $ git grep "\bbool\b" | wc # cf. bool の結果 + 3563 23577 294659 + ]]> + </literallayout> + <para> + 165 + 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 + </para> + <literallayout class="monospaced"> + <![CDATA[ + $ git grep "\bi128\b" + ... + rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); + ... + ]]> + </literallayout> + <para> + <literal>rustc_resolve</literal> + というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 + </para> + <programlisting language="rust" linenumbering="unnumbered"> + <![CDATA[ + /// Interns the names of the primitive types. + /// + /// All other types are defined somewhere and possibly imported, but the primitive ones need + /// special handling, since they have no place of origin. + struct PrimitiveTypeTable { + primitive_types: FxHashMap<Symbol, PrimTy>, + } + + impl PrimitiveTypeTable { + fn new() -> PrimitiveTypeTable { + let mut table = FxHashMap::default(); + + table.insert(sym::bool, Bool); + table.insert(sym::char, Char); + table.insert(sym::f32, Float(FloatTy::F32)); + table.insert(sym::f64, Float(FloatTy::F64)); + table.insert(sym::isize, Int(IntTy::Isize)); + table.insert(sym::i8, Int(IntTy::I8)); + table.insert(sym::i16, Int(IntTy::I16)); + table.insert(sym::i32, Int(IntTy::I32)); + table.insert(sym::i64, Int(IntTy::I64)); + table.insert(sym::i128, Int(IntTy::I128)); + table.insert(sym::str, Str); + table.insert(sym::usize, Uint(UintTy::Usize)); + table.insert(sym::u8, Uint(UintTy::U8)); + table.insert(sym::u16, Uint(UintTy::U16)); + table.insert(sym::u32, Uint(UintTy::U32)); + table.insert(sym::u64, Uint(UintTy::U64)); + table.insert(sym::u128, Uint(UintTy::U128)); + Self { primitive_types: table } + } + } + ]]> + </programlisting> + <para> + これは初めに列挙したプリミティブ型の一覧と一致している。doc comment + にも、 + </para> + <blockquote> + <para> + All other types are defined somewhere and possibly imported, but the + primitive ones need special handling, since they have no place of + origin. + </para> + </blockquote> + <para> + とある。次はこの struct + の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 + </para> + <programlisting language="rust" linenumbering="unnumbered"> + <![CDATA[ + /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. + /// (略) + fn resolve_ident_in_lexical_scope( + &mut self, + mut ident: Ident, + ns: Namespace, + // (略) + ) -> Option<LexicalScopeBinding<'a>> { + // (略) + + if ns == TypeNS { + if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { + let binding = + (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) + .to_name_binding(self.arenas); + return Some(LexicalScopeBinding::Item(binding)); + } + } + + None + } + ]]> + </programlisting> + <para> + 関数名や doc comment が示している通り、この関数は識別子 (identifier, + ident) を現在のレキシカルスコープ内で解決 (resolve) する。 + <literal>if ns == TypeNS</literal> のブロック内では、<literal>primitive_type_table</literal> (上記の + <literal>PrimitiveTypeTable::new()</literal> で作られた変数) に含まれている識別子 + (<literal>bool</literal>、<literal>i32</literal> など) + かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 + </para> + <para> + なお、<literal>ns</literal> は「名前空間」を示す変数である。Rust + における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この + <literal>if</literal> + は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。 + </para> + <para> + 重要なのは、これが <literal>resolve_ident_in_lexical_scope()</literal> + の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 + </para> + <para> + 動作がわかったところで、例として次のコードを考える。 + </para> + <programlisting language="rust" linenumbering="unnumbered"> + <![CDATA[ + #![allow(non_camel_case_types)] + + struct bool; + + fn main() { + let _: bool = bool; + } + ]]> + </programlisting> + <para> + ここで <literal>main()</literal> の <literal>bool</literal> は <literal>struct bool</literal> + として解決される。なぜなら、プリミティブ型の判定をする前に <literal>bool</literal> + という名前の別の型が見つかるからだ。 + </para> + </section> + <section xml:id="outro"> + <title>まとめ</title> + <para> + Rust + のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 + </para> + </section> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml new file mode 100644 index 00000000..ef17ae38 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Vim】 autocmd events の BufWrite/BufWritePre の違い</title> + <abstract> + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + </abstract> + <keywordset> + <keyword>vim</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/79ab4db8564032de0b25">https://qiita.com/nsfisis/items/79ab4db8564032de0b25</link> + </note> + <section xml:id="tl-dr"> + <title>TL; DR</title> + <para> + 違いはない。ただのエイリアス。 + </para> + </section> + <section xml:id="code-reading"> + <title>調査記録</title> + <para> + Vim の autocmd events には似通った名前のものがいくつかある。大抵は + <literal>:help</literal> + に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。 + </para> + <itemizedlist> + <listitem><literal>BufRead</literal>/<literal>BufReadPost</literal></listitem> + <listitem><literal>BufWrite</literal>/<literal>BufWritePre</literal></listitem> + <listitem><literal>BufAdd</literal>/<literal>BufCreate</literal></listitem> + </itemizedlist> + <para> + このうち、<literal>BufAdd</literal>/<literal>BufCreate</literal> に関しては、<literal>:help BufCreate</literal> に + </para> + <blockquote> + <para> + The BufCreate event is for historic reasons. + </para> + </blockquote> + <para> + とあり、おそらくは <literal>BufAdd</literal> + のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため + vim と neovim のソースコードを調査した。 + </para> + <blockquote> + <para> + ソースコードへのリンク + <link xl:href="https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2">vim (調査時点での master branch)</link> + <link xl:href="https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3">neovim (上に同じ)</link> + </para> + </blockquote> + <section xml:id="code-reading--vim"> + <title>vim のソースコード</title> + <para> + 以下は、autocmd events + の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。 + </para> + <para> + <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86</link> + </para> + <programlisting language="c" linenumbering="unnumbered"> + <![CDATA[ + {"BufAdd", EVENT_BUFADD}, + {"BufCreate", EVENT_BUFADD}, + ]]> + </programlisting> + <para> + <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97</link> + </para> + <programlisting language="c" linenumbering="unnumbered"> + <![CDATA[ + {"BufRead", EVENT_BUFREADPOST}, + {"BufReadCmd", EVENT_BUFREADCMD}, + {"BufReadPost", EVENT_BUFREADPOST}, + ]]> + </programlisting> + <para> + <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105</link> + </para> + <programlisting language="c" linenumbering="unnumbered"> + <![CDATA[ + {"BufWrite", EVENT_BUFWRITEPRE}, + {"BufWritePost", EVENT_BUFWRITEPOST}, + {"BufWritePre", EVENT_BUFWRITEPRE}, + ]]> + </programlisting> + </section> + <section xml:id="code-reading--neovim"> + <title>neovim のソースコード</title> + <para> + neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua + で書かれている。以下にある通り、はっきり <literal>aliases</literal> と書かれている。 + </para> + <para> + <link xl:href="https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124">https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124</link> + </para> + <programlisting language="lua" linenumbering="unnumbered"> + <![CDATA[ + aliases = { + BufCreate = 'BufAdd', + BufRead = 'BufReadPost', + BufWrite = 'BufWritePre', + FileEncoding = 'EncodingChanged', + }, + ]]> + </programlisting> + <para> + ところで、上では取り上げなかった <literal>FileEncoding</literal> だが、これは + <literal>:help FileEncoding</literal> にしっかりと書いてある。 + </para> + <literallayout class="monospaced"> + <![CDATA[ + *FileEncoding* + FileEncoding Obsolete. It still works and is equivalent + to |EncodingChanged|. + ]]> + </literallayout> + </section> + </section> + <section xml:id="outro"> + <title>まとめ</title> + <para> + 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 + </para> + <itemizedlist> + <listitem> + <literal>BufAdd</literal>/<literal>BufCreate</literal> + <itemizedlist> + <listitem>→ <literal>BufCreate</literal> は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (<literal>BufAdd</literal>) を使う</listitem> + </itemizedlist> + </listitem> + <listitem> + <literal>BufRead</literal>/<literal>BufReadPost</literal> + <itemizedlist> + <listitem>→ <literal>BufReadPre</literal> との対称性のため、あるいは <literal>BufWritePost</literal> との対称性のため <literal>BufReadPost</literal> を使う</listitem> + </itemizedlist> + </listitem> + <listitem> + <literal>BufWrite</literal>/<literal>BufWritePre</literal> + <itemizedlist> + <listitem>→ <literal>BufWritePost</literal> との対称性のため、あるいは <literal>BufReadPre</literal> との対称性のため <literal>BufWritePre</literal> を使う</listitem> + </itemizedlist> + </listitem> + <listitem> + <literal>FileEncoding</literal>/<literal>EncodingChanged</literal> + <itemizedlist> + <listitem>→ <literal>FileEncoding</literal> は <literal>`Obsolete'' と明言されているので、`EncodingChanged</literal> を使う</listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <para> + ところでこの調査で知ったのだが、<literal>BufRead</literal> と <literal>BufWrite</literal> + は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら + <literal>Pre</literal>/<literal>Post</literal> 付きのものを使った方が分かりやすいだろう。 + </para> + </section> +</article> diff --git a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml new file mode 100644 index 00000000..c4c26ca8 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml @@ -0,0 +1,215 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>Vimで選択した行の順番を入れ替える</title> + <abstract> + Vim で選択した行の順番を入れ替える方法。 + </abstract> + <keywordset> + <keyword>vim</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <note> + この記事は Qiita から移植してきたものです。 + 元 URL: <link xl:href="https://qiita.com/nsfisis/items/4fefb361d9a693803520">https://qiita.com/nsfisis/items/4fefb361d9a693803520</link> + </note> + <section xml:id="tl-dr"> + <title>TL; DR</title> + <programlisting language="vim" linenumbering="unnumbered"> + <![CDATA[ + " License: Public Domain + + command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1 + ]]> + </programlisting> + </section> + <section xml:id="version"> + <title>バージョン情報</title> + <para> + <literal>:version</literal> の一部 + </para> + <blockquote> + <para> + VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS + version Included patches: 1-148 Huge version without GUI. + </para> + </blockquote> + </section> + <section xml:id="existing-solution"> + <title>よく紹介されている手法</title> + <section xml:id="existing-solution--external-commands"> + <title><literal>tac</literal> / <literal>tail</literal></title> + <para> + <literal>tac</literal> や <literal>tail -r</literal> などの外部コマンドを <literal>!</literal> + を使って呼び出し、置き換える。 + </para> + <blockquote> + <para> + :h v_! + </para> + </blockquote> + <para> + <literal>tac</literal> コマンドや <literal>tail</literal> の <literal>-r</literal> + オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい + </para> + </section> + <section xml:id="existing-solution--global-command"> + <title><literal>:g/^/m0</literal></title> + <para> + こちらは外部コマンドに頼らず、Vim の機能のみを使う。<literal>g</literal> は <literal>:global</literal> + コマンドの、<literal>m</literal> は <literal>:move</literal> コマンドの略 + </para> + <para> + <literal>:global</literal> コマンドは <literal>:[range]global/{pattern}/[command]</literal> + のように使い、<literal>[range]</literal> で指定された範囲の行のうち、<literal>{pattern}</literal> + で指定された検索パターンにマッチする行に対して、順番に <literal>[command]</literal> + で指定された Ex コマンドを呼び出す。 + </para> + <blockquote> + <para> + :h :global + </para> + </blockquote> + <para> + <literal>:move</literal> コマンドは <literal>[range]:move {address}</literal> のように使い、<literal>[range]</literal> + で指定された範囲の行を <literal>{address}</literal> で指定された位置に移動させる。 + </para> + <blockquote> + <para> + :h :move + </para> + </blockquote> + <para> + <literal>:g/^/m0</literal> のように組み合わせると、「すべての行を1行ずつ + 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。 + </para> + <para> + なお、<literal>:g/^/m0</literal> は全ての行を入れ替えるが、<literal>:N,Mg/^/mN-1</literal> とすることで + N行目から + M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 + </para> + <programlisting language="vim" linenumbering="unnumbered"> + <![CDATA[ + command! -bar -range=% + \ Reverse + \ <line1>,<line2>g/^/m<line1>-1 + ]]> + </programlisting> + <para> + これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 + </para> + </section> + </section> + <section xml:id="problem-of-global-command"> + <title><literal>:g/^/m0</literal> の問題点</title> + <para> + <literal>:global</literal> + コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。<literal>^</literal> + は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。<literal>'hlsearch'</literal> + オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと + <literal>n</literal> コマンドなどの際に不便である。 + </para> + <blockquote> + <para> + :h @/ + </para> + </blockquote> + </section> + <section xml:id="solution"> + <title>解決策</title> + <blockquote> + <para> + [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した + </para> + </blockquote> + <para> + 前述した <literal>:Reverse</literal> コマンドの定義を少し変えて、次のようにする: + </para> + <programlisting language="vim" linenumbering="unnumbered"> + <![CDATA[ + function! s:reverse_lines(from, to) abort + execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1) + endfunction + + command! -bar -range=% + \ Reverse + \ call <SID>reverse_lines(<line1>, <line2>) + ]]> + </programlisting> + <para> + 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 + </para> + <para> + この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが + <literal>^</literal> で上書きされることがなくなる。 + </para> + <para> + Vim のヘルプから該当箇所を引用する (強調は筆者による)。 + </para> + <blockquote> + <para> + :h autocmd-searchpat + </para> + <para> + <emphasis role="strong">Autocommands do not change the current search patterns.</emphasis> Vim saves the + current search patterns before executing autocommands then restores them + after the autocommands finish. This means that autocommands do not + affect the strings highlighted with the `hlsearch' option. + </para> + </blockquote> + <para> + これは autocommand + の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは + <literal>:nohlsearch</literal> のヘルプにある。同じく該当箇所を引用する + (強調は筆者による)。 + </para> + <blockquote> + <para> + :h :nohlsearch + </para> + <para> + (略) This command doesn’t work in an autocommand, because the + highlighting state is saved and restored when executing autocommands + |autocmd-searchpat|. <emphasis role="strong">Same thing for when invoking a user function.</emphasis> + </para> + </blockquote> + <para> + この仕様により、<literal>:g/^/m0</literal> + の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。 + </para> + </section> + <section xml:id="solution-revised"> + <title>解決策 (改訂版)</title> + <blockquote> + <para> + [2020/9/28追記] より簡潔な方法を見つけたため追記する + </para> + </blockquote> + <programlisting language="vim" linenumbering="unnumbered"> + <![CDATA[ + command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1 + ]]> + </programlisting> + <para> + まさにこのための Exコマンド、<literal>:keeppatterns</literal> + が存在する。<literal>:keeppatterns {command}</literal> + のように使い、読んで字の如く、後ろに続く + Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 + </para> + <blockquote> + <para> + :h :keeppatterns + </para> + </blockquote> + </section> +</article> |
