バッシュパイプが欲しい


TLドクター


シェルスクリプトからそれらを知っているパイプラインは強力です
機能性を構成する機構
この同じ便宜はほとんどのプログラミングで達成できる
言語.このポストでは、どのように導入することができます
お気に入りのプログラミング環境にパイプ.

何?


プラットフォームの多くは、小さなブロックからプログラムを構築する方法が付属しています.多くの文脈では、この機能的組成を行うための便利な方法がある.我々はこれらのパイプラインを呼び出すことができます.
このポストはbashのパイプに関する議論の後であった(理由についてのわずかに論争の的な分析).しかし、ドメインはそれよりずっと大きい.
これはパイプに接続された小さなコマンドで構成されるbashスクリプトです.
cat urls.txt | sed -e "s-http://--" -e "s/\\.com$//g"  | grep ".com$"
パイプの概念は、異なるドメインにも存在します-サウンド処理は良い例ですgstreamer website )

組成は、ほとんどのプログラミング言語の主な駆動目標です.関数を通して何らかの値を追跡し、戻り値を他の関数の入力として使用します.
std::vector<std::string> report;
for(const auto &url: read_lines("urls.txt")){
    const auto [protocol, domain, path] = explode(url);
    if (domain.ends_with(".com")){
        report.push_back(domain);
    }
}
明らかに、bashパイプラインの構文は、書き込みと読み込みの両方により簡単です.

もし何か


パイプの構文のシンプルさを使用できます.
機能的プログラミングにおいて、そして、数学では、この概念は点自由関数合成として知られています.関数のパイプラインを有効に定義します.
read_lines("urls.txt") \
    | explode_url \
    | tuple_get(1) \
    | filter(ends_with(".com"))

でも。どうやって?


私はこの簡潔さのためだけにPythonを使用します.私はC++の例を後で追加することができます- C++は、関数のオーバーロードの解像度を持つだけで、より構文の砂糖を追加することができます.
私はどのような流暢なインターフェイスを説明を開始します.混合物にオペレーターオーバーロードを加えて、我々は素晴らしいパイプライン建設構文で終わります.

流暢なインタフェース


ほとんどのプログラミング言語では、ビルドする方法がありますfluent interfaces , 連鎖操作をオブジェクトに一緒に許可する
ServerBuilder()\
  .on_address([localhost])\
  .on_port(1050)\
  .with_database(that_db)\
  .that_handles("rq1", handle_rq1)\
  .that_quits_on("quit")\
  .build()
これは再びビルダーを返すメソッドを作成することによって動作します.

演算子


もしあなたの言語があなたが同様に演算子を上書きすることができるならば、あなたはGoldenPipeline と余分な関数でパイプラインインスタンスを拡張するパイプ演算子.
class Pipeline:
    def __init__(self, functions = tuple()):
        self.functions = functions

    def __or__(self, f):
        return Pipeline(self.functions + (f,))

    def __call__(self, arg):
        return functools.reduce(
            lambda r, f: f(r),  # function
            self.functions,  # iterable
            arg)  # initializer

"""pipeline starting element"""
ID = Pipeline()

テスト


そして、それはすべてのようです.
これは簡単に検証できます.pytest . この記事のために、仮定しましょうinc and double 値をそれぞれ増加させて倍増する関数.
def test_everything_starts_with_the_identity_function():
    assert all(ID(x) == x for x in (1, 2, "abcd", None))

def test_pipeline_steps_are_applied_in_order():
    pipeline = ID | inc | double
    assert pipeline(0) == (0+1) * 2
    assert pipeline(3) == (3+1) * 2

しかし、まだ.どうやって?


さあ、一歩一歩説明しましょう.

パイプラインの建設


The Pipeline クラスは関数のコンテナーです.これをタプルに格納することによって行うself.functions ). (として)tuple むしろlist その不変性のために
モジュールはまた、我々の構築の出発点として使うことができる非常に最初のオブジェクトを加えます-パイプライン帽子は、それが受ける同じ要素を返すだけです.呼称ID , 機能的なプログラミングの世界と同じように.
今、私たちのクラスは、この特別なメンバーを持っています__or__(self, f) . その唯一の目的はシェルスクリプトから知っている'パイプ'構文を提供することです.p | inc | double ; そしてPythonでは、operator overloading .
私たちは、同じ機能を達成するためにカスタム名を作成することができました
    def and_then(self, f):
      return Pipeline(self.functions + (f,))

...
ID.and_then(double).and_then(inc)
でも選ぶ__or__ メンバー名がPythonに通知するとき、我々はPipeline オブジェクトは、関数へのedまたはedです.

パイプラインを呼び出す


再び、もう一つのスペシャルメンバー__call__ 関数.おそらくそれは推測されますが、これはオブジェクトが関数のように振る舞うものです.
私はそれを使用してfunctools.reduce , しかし、最初の引数をTNEの最初の関数、次の関数への戻り値などを渡すループを手にすることができます.
ここでは、私たちはそれを何か他のものと呼びましたinvoke_with . 非特殊なメンバパイプラインは次のようになります.
ID.and_then(inc).and_then(double).invoke_with(10)
でも選ぶ__call__ このメソッドをブレースを使用しているときにPythonに指示します.
(ID | inc | double)(10)

最初の引数を注入する


本当に書きたいことは何かです.

twentytwo = echo(10) | inc | double
fourty_eight = echo("0x18") >> from_hex | double
それで、我々は、ものを逆にするもう一つのトリックヘルパークラスを必要とします:
class WithArg:
    def __init__(self, value):
        self.value = value
    def __call__(self, p: Pipeline):
        return p(self.value)
今すぐ書くことができます
WithArg(10)(ID | inc | double) == (10+1) * 2
我々があきらめる気があるならば| 演算子は、括弧をドロップすることもできます.これは演算子の優先順位によるものです.
assert 1 + 2*3 == 7
assert 1 + 2 * 3 == 1 + (2*3)
assert True | False&True == False
assert True | False&True == True | (False&True)
そこで、例えば、乗算演算子を使用して、引数の注入のために右シフトを使用できます.
class Pipeline:
    ...
    def __mul__(self, f):
        return self | f

class WithArg:
    ...
    def __rshift__(self, p: Pipeline):
        return p(self.value)

assert WithArg(10) >> ID * inc * double == (10+1) * 2

制限


この記事のコードでビルドできるパイプは良いですが、シェルで使用するものの重要な側面はまだありません.それは破壊構造と関係がある.
見るthis pipeline :
function top5 {
   grep "page" | awk '{print $5 " " $3 " " $9}' | sort -n | tail -5
}
我々がここで見るものは、ラインベースですgrep and awk . でも、sort . これはどう違うの?出力を生成する前に、すべての入力を許可します.
Pythonパイプラインは、ロックステップでデータを受け入れて返す関数を作成することができます.ID | grep("page") | print_elements(5, 3, 9) 単一の引数を処理して単一の値を生成します.どのように我々はそれから解放するつもりですか?パイプの一部は、1つの出力(またはストリーム)を生成するために入力をバッファリングすることができなければなりませんを返します.
実際には、テキストベースの処理には2種類のイベントがあります.実際、これらのコマンドラインストリーム処理ツールは全てバッファリング/チャンキング部とチャンク処理部から構成される.我々は、ちょうど我々のパイプラインをよりスマートにするために、この知識を使うかもしれません.しかし、このポストででなく.

結論


言語を使用すると、これらの日の構文の独自のセットを構築することができます.我々は既知のドメインから記法を模倣するためにこれを利用することができます.
言語は完璧ではありませんが、我々は十分に近くに役立つことができます.