Hy threading macro の紹介

-> (or the threading macro)

Hyにはthreading macro(->, -»)が標準で用意されています。 これらを使うと、Pythonでは一時変数と再代入が必要だったコードがそれらなしできれいに書けることがあります。

Threading (tail) macro とはどういうものか

公式ドキュメントには下記のように説明されています。

The threading macro inserts each expression into the next expression’s first argument place.
(訳:スレッディングマクロは、[訳注:スレッド内の]それぞれの式を次の式の最初の引数に配置します。)

Threading tail macro (-») は、式の配置先が最初の引数でなく最後の引数である点だけが違うところです。

文字通りの意味なのですが、具体例を出した方が分かりやすいので下記に概念的なマクロ展開例を出してみます。

; (defn f [x y z] (+ x y z)) など何か適当な関数
(-> 10
  (f 1 2)
  (f 3 4))
  
(->> 10
  (f 1 2)
  (f 3 4))
  
↓↓↓

(f (f 10 1 2) 3 4)

(f 3 4 (f 1 2 10))

実用的な例

挙動は分かったとして、これだけだと有用性がピンと来ないかもしれません。ここでは私がPythonからHyに移植したコードの抜とを比較してみます。

(こちらより引用)

    def forward(self, cond_vector, truncation):
        z = self.gen_z(cond_vector)

        # We use this conversion step to be able to use TF weights:
        # TF convention on shape is [batch, height, width, channels]
        # PT convention on shape is [batch, channels, height, width]
        z = z.view(-1, 4, 4, 16 * self.config.channel_width)
        z = z.permute(0, 3, 1, 2).contiguous()

        for i, layer in enumerate(self.layers):
            if isinstance(layer, GenBlock):
                z = layer(z, cond_vector, truncation)
            else:
                z = layer(z)

        z = self.bn(z, truncation)
        z = self.relu(z)
        z = self.conv_to_rgb(z)
        z = z[:, :3, ...]
        z = self.tanh(z)
        return z
;;; threading macroをクラスインスタンスのメソッドで利用するためのマクロ
(defmacro/g! *-> [f &rest xs] `(fn [g!ob] ((. g!ob ~f) ~@xs)))

(setv ,,, Ellipsis)

(defn forward [self z truncation]
    (with [(torch.no_grad)]
      (-> (self.G.gen_z z)
          ((*-> view -1 4 4 (* 16 self.g.config.channel_width)))
          ((*-> permute 0 3 1 2))
          ((*-> contiguous))

          (layers-forward self.G.layers z truncation)

          (self.G.bn truncation)
          (self.G.relu)
          (self.G.conv_to_rgb)
          (get [(slice None None) (slice None 3) ,,,])
(self.G.tanh))))

(defn layers-forward [z* layers z truncation]
  (for [layer layers]
    (cond [(in "GenBlock" layer.__class__.__name__) (setv z* (layer z* z truncation))]
          [True (setv z* (layer z*))]))
  z*)
 

Python版では z = … を毎行書いていたのが、Hy版では一時変数と代入がforward関数中からは撲滅できています。 1引数関数の呼び出しでチェインする部分は特に見通しが良くなっているのではないでしょうか。

一時変数を使わないとネストが深くなってコードが見づらくなる場合には、-> や -» の使用は検討の価値があると思います。

本文について

2019/5現在、Hy 0.16 についての記述です。