1 ポイント 投稿者 GN⁺ 1 시간 전 | 1件のコメント | WhatsAppで共有
  • zig fmt は、ファイル内にすでにある構文の形を反映して、同じコードでも複数のレイアウトに配置できる操縦可能なフォーマッタとして使える
  • 関数呼び出しでは trailing comma の有無が結果を変え、カンマがなければ1行にまとまり、カンマがあれば引数が各行に配置される
  • 実際の流れは、望むコード配置を先に決めてカンマをいくつか追加し、その後フォーマット用ショートカットを押して zig fmt に残りを処理させる方式
  • 配列では trailing comma だけでなく、最初の改行位置 も反映され、最初の改行が3番目の項目の後にあると、項目が3つずつそろうように整列される
  • ++ 配列連結を慎重に使えば、行ごとに項目数を変えて配置でき、subprocess に --keyvalue の組を渡すときは固定引数配列とオプション組配列を連結して整列できる

zig fmt を操縦する方法

  • zig fmt は、現在のファイルにすでにある構文の形を見て、同じ構文でも複数の方法で配置できるため、操縦可能なフォーマッタとして使える
  • 関数呼び出しでは trailing comma の有無がレイアウトを変える
    f(1, 2,
          3);
    
    // -> zig fmt ->
    
        f(1, 2, 3);
    
    f(1, 2,
          3,);
    
    // -> zig fmt ->
    
        f(
            1,
            2,
            3,
        );
    
  • 実際の使い方の流れは、望むコード配置を先に決めて , をいくつか追加し、その後フォーマット用ショートカットを押して zig fmt に残りを処理させる方式
  • フォーマッタにレイアウトを推測させるより、ユーザーが重要な選択を直接残す方法のほうが合っているかもしれない
  • 良いフォーマットの90%は、論理ブロック間の空行と適切な中間変数の選択にかかっているため、こうした選択をなくすより活用するほうがよいという結論

配列の列揃えレイアウト

  • 配列では trailing comma だけで1行に1つずつ配置されるのではなく、最初の改行位置zig fmt が反映する
    .{ 1, 2, 3,
          4, 5, 6, 7, 8, 9, 10, 11,  };
    
  • 最初の改行が3番目の項目の後にあると、結果も項目3つずつそろうように整列される
    .{
            1,  2,  3,
            4,  5,  6,
            7,  8,  9,
            10, 11,
        };
    
  • ++ 配列連結を慎重に使えば、行ごとに項目数を変えて配置できる
  • subprocess に --keyvalue の組を渡す場合は、固定引数配列とオプション組配列を連結して次のように整列できる
    try run(&(.{ "aws", "s3", "sync", path, url } ++ .{
        "--include",            "*.html",
        "--include",            "*.xml",
        "--metadata-directive", "REPLACE",
        "--cache-control",      "max-age=0",
    }));
    

1件のコメント

 
GN⁺ 1 시간 전
Lobste.rsの意見
  • gofmtにも似たような書式誘導の動作があった気がするし、その手のフォーマッタのほうがrustfmtより好き
    それでも、フォーマッタがまったくないよりは、どんな形でも書式の自動化があるほうがいいと思う

    • 「フォーマッタがないより何かあるほうがマシ」という言い方は、簡単には受け流せない
      自動フォーマッタは平凡さを強制し、書式をうまく扱えない人を底上げする一方で、うまく扱える人も引き下げる
      協業で他の人たちが望んでいたり、彼らの個人的な書式ルールを信頼しにくかったりするなら使うが、一人で作業するときは絶対に使わない
      自分の好みもあればフォーマッタの好みもあって、その二つは和解不能なくらい違う
      ただ、この記事で示されている内容自体は印象的だ
      その一文を読むと、Fiddler on the Roofで仲人のイェンテが「悪い夫でも――神よお守りください――夫がいないよりはまし!」と言う台詞を思い出す
    • 記憶ではElmフォーマッタも似たように動いていて、元の書式を考慮しないフォーマッタよりかなり好ましく感じた
    • 一部のC++プロジェクトでclang-formatを使っているけどひどい
      バージョン間の安定性が低すぎて、clang-formatのアップグレードがコードの全行に触れる書式コミットにつながる
      フォーマッタがないよりマシなのか本当に自信がない
    • 昔は「どんなフォーマッタでもないよりはマシ」と長いこと思っていたが、最近は完全に考えが変わった
      自動フォーマッタは主にプルリクエストでのバイクシェディングという人間の問題を解決する
      でも今はエージェント型開発に移りつつあり、その問題はだんだん重要ではなくなってきている
      いま複数のプロジェクトで機械が作業の大半をやっているので、そうなるとフォーマッタを回さないほうがいい気がしてくる
  • Pythonでコマンドライン引数を組み立てるときは、タプルをリストにスプラットする書き方が好きなので、記事の最後の例はこう書くと思う

    [  
      "aws",  
      "s3",  
      "sync",  
      path,  
      url,  
            *("--include", "*.html"),  
      *("--include", "*.xml"),  
      *("--metadata-directive", "REPLACE"),  
      *("--cache-control", "max-age=0"),  
    ]  
    
  • 前に見たとき、zig fmtには80桁制限を100桁制限の代わりに使う設定方法がなかったが、まだそうなのだろうか?
    1日に何時間も作業するので、目が疲れにくいようにターミナルのフォントサイズを大きくしているのだが、80桁と100桁の違いはvimの分割2枚とnerd treeを横に並べられるかどうかを左右する

    • zig fmtには桁数制限はない
  • フォーマッタがまったくなかったチームにrigid formatterを導入した者として、たまには手動で書式に影響を与えられる能力が恋しくなる
    その点で、Zigが柔軟なのは本当にすばらしい

  • すばらしい!
    こういうTS/JSフォーマッタはあるだろうか?
    maplibre-glを使うプロジェクトがあるのだが、スタイル仕様の式がときどき過剰に整形されて、何も見えなくなる
    今はフォーマッタの使用をやめているが、デバッグやコピーやコメントアウトをしているうちにコードが散らかってきている
    もしかするとZigフォーマッタを他の言語もフォーマットできるようにできるかもしれない :)

    • Prettierにも似た機能はあるが、具体的にはオブジェクトリテラルに限られ、「全部1行」と「各要素を別々の行」のどちらかしか選べない
      たとえば1行に4要素ずつ置けとフォーマッタに指示する方法はない
      また、オブジェクトリテラルが長くなりすぎると、入力テキストがどうであれ、Prettierは結局「各要素を別々の行」の形式にしてしまう
  • 軽量なタイプのフォーマッタ、実質的に改行しかしないフォーマッタにはがっかりしたことがあるので、もっと厳格な例の中でこうした柔軟性があるという発想はうらやましいし気に入っている :p
    最近fediで、プロポーショナルフォントでLispを書いて整形することについての質問に答えたとき、意味のある空白を使うs-expressionの変種、つまりwisp、Readable/Sweet expressions、SRFI 119と110に触れた
    この文法ファミリーは、任意の中置記法拡張を活用することで改行に対する制御をある程度取り戻している、という観察も添えた

  • 興味深い設計だが、気に入るかどうかはよくわからない
    自分のフォーマッタでは違うやり方をしている。フォーマッタが末尾カンマを無視して書式を決め、その後で複数行になったら必ず末尾カンマを追加し、1行なら必ず末尾カンマを削除する
    だから「誘導」はできないが、f(1, 2, 3)は末尾カンマの有無やトークン間の空白の量や種類に関係なく一貫してフォーマットされる
    ある程度の誘導は必要だ
    たとえば長いリストリテラル[<expr1>, <expr2>, ..., <expr100>]があると、たいていのフォーマッタは各式を1行に1つずつ置くだろうが、できるだけ多くを1行に詰め込みたいこともある
    これを末尾カンマで決めるのは変だと思うし、一般には選択肢は2つではなくN個ありうる
    こういう目的には属性のほうが向いていると思う
    たとえば、すでにあるかもしれないが、文の前に#[rustfmt::list_layout(flow)]のようなものを付けて、その文の中のリストリテラルの書式に影響を与える、といったことができる
    誘導が多すぎると、エコシステム全体でコードの書式を一貫させ、コードレビューをしやすくするというフォーマッタの目的を損なうので、限定的なケースにとどめるべきだ
    長いリストリテラルは本当に必要な例だと思う
    自分のプロジェクトにも、テスト期待値のレビューで書式が役に立つ例があり、ここがそれだ
    Dartフォーマッタにも別の「誘導」動作を1つ思い出した。長いリストリテラルでコメント行を追加して、行をグループ化できる
    たとえば[1, 2, 3, ..., 1000]なら各要素を1行に1つずつ置くが、手動でこうグループ化できる

    [1, 2, 3, 4, 5,  //  
     6, 7, 8, 9, 10, //  
     ...]  
    

    これが意図的に入れられた機能なのか、コメント処理のやり方から生じた副産物なのかはわからない

    • そのやり方はまさにrustfmtの挙動そのもので、それが腹立たしい
      ときには行長制限を超えてでも関数呼び出しを分割しないほうが読みやすい場合があり、そこに自分の判断を反映できるといいのにと思う
      ひとつ思い浮かぶ例はOpenGLだ
      ふつうは1つのリソースを変更したり使ったりしながら、たとえばテクスチャ初期化のようにgl.*呼び出しを連続してたくさん書くことになるのだが、rustfmtは「行が長すぎる。分割しなければならない」というロボット的な目的以外の感覚なしに押し進めてしまう
      この例は動作を示すための作為的なもので、実際のrustfmtの挙動と完全に同じではない
      行もそこまで長くはない
      今はスマホで書いているので、100%正確な例を作る手段がない
      gl.bind_texture(gl::TEXTURE_2D, tex);  
      gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST);  
      gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
      
      // -->
      
      gl.bind_texture(gl::TEXTURE_2D, tex);  
      gl.tex_parameteri(  
          gl::TEXTURE_2D,  
          gl::TEXTURE_MIN_FILTER,  
          gl::NEAREST,  
      );  
      gl.tex_parameteri(  
          gl::TEXTURE_2D,  
          gl::TEXTURE_MAG_FILTER,  
          gl::NEAREST,  
      );  
      
      このように連続するgl.tex_parameteri呼び出しを複数行に分割するのだが、実際には各呼び出しを1行に完全に展開しておくほうがよい
      桁がそろっていれば、2行の違いをずっと簡単に見比べられるからだ
      分割した版は視覚的な近接性が低く、読むのが難しい
      2行を目で簡単に比較できなくなる
      また、文字数制限内に収めて整形できないと完全に失敗するという滑稽なことも起きる
      コンパイラのコードで文字列リテラルの診断メッセージを書くときによく起きることで、メッセージがかなり長くなることがある
      rustfmtはそれをどう分割すればよいかわからず、あきらめてその文全体のフォーマットをしない
      よくあるのはこんな形だ
      match something {  
          // ... match arms above this one ...  
          _ => emit_diagnostic(&mut state, "This is a very long message to try and illustrate the problem. Help: please consult a doctor.")  
      }  
      
      ここでemit_diagnostic呼び出しが単なる式でしかないという理由で、match文全体のフォーマットをあきらめるのは、ただただ愚かだ
      自分のコードを最大100桁に押し込もうとしなければ、全部避けられたはずだ
  • 最後のほうのコメントを見て、自分のように調べる必要があった人のために書いておくと、++配列連結演算子
    なので配列を2つに分ければ、別々にフォーマットできる