Pythonにパイプライン演算子（のようなもの）+α


## パイプライン演算子とは

```
g(f(e(d(c(b(a(x)))))))
```

これを

```
x |> a |> b |> c |> d |> e |> f |> g
```

ないし

```
x |> a
  |> b
  |> c
  |> d
  |> e
  |> f
  |> g
```

と書けるようにする演算子のこと。F#などで使われる。

最初の記法が処理の順番と読む順番が逆であるのに対し、パイプライン演算子を使うと処理の順番と読む順番を揃えられる点が便利。

これをPythonでも使いたいので実装を考えてみる。ついでに、パイプライン演算子を使う上で欲しい機能も実装していく。

## 関数として実装

とりあえず関数として実装する。


```python
import functools as fts

def pipe(x, *fs):
    return fts.reduce(lambda x, y: y(x), fs, x)
```


```python
def add2(x):
    return x + 2

def mul2(x):
    return x * 2
```


```python
pipe(3,
     mul2,
     add2,
     add2)
# out:
# 10
```

## 演算子で実装

先人[pipetools](http://0101.github.io/pipetools/)のようにちゃんと演算子を作る場合。

演算子として実装するのであれば既存の演算子をオーバーロードするしかない。オーバーロードするにはクラスにラップするしかない。


```python
from functools import partial


pout = object()


class PipeBase:
    def __mod__(self, f):
        # 多重ディスパッチが欲しいところだ
        if f is pout:
            return self.out()
        else:
            return Pipe(self, f)


class Pipe(PipeBase):
    def __init__(self, previous, f):
        self._previous = previous
        self._f = f
    
    def out(self):
        return self._f(self._previous.out())

    
class FirstPipe(PipeBase):
    def __init__(self, first):
        self._first = first
    
    def out(self):
        return self._first

    
class InPipe:
    def out(self):
        return None
    
    def __mod__(self, first):
        if first is pout:
            return self.out()
        else:
            return FirstPipe(first)

        
pin = InPipe()
```


```python
(pin %
    3 %
    mul2 %
    add2 %
    add2 %
    pout)
# out:
# 10
```

```python
# 遅延評価もできる

lazy_pipe = (pin %
    3 %
    mul2 %
    add2 %
    add2)

lazy_pipe.out()
# out:
# 10
```

\>とか\|を使わなかった理由は後で説明する。

これはこれで悪くないと思う。複数行にわたるときにカッコを付けないといけないのと、pin/poutが面倒だが･･･

## メソッド呼び出しもパイプに入れる

関数だけでなくメソッド呼び出しもしたいとしたらどうするか。

組み込み関数methodcallerを使うと、メソッド呼び出しを関数化できる。


```python
from operator import methodcaller

pipe(3,
     add2,
     add2,
     mul2,
     add2,
     mul2,
     add2,
     mul2,
     mul2,
     mul2,
     str, # "272"
     methodcaller("count", "2"), # "272".count("2")
     add2
)
# out:
# 4
```

でもわざわざ文字列で書くのが面倒。なのでこんなクラスを作るともう少しすっきり書ける。


```python
class MethodCaller:
    def __getattribute__(self, name):
        return partial(methodcaller, name)


call = MethodCaller()
```


```python
pipe(3,
     add2,
     add2,
     mul2,
     add2,
     mul2,
     add2,
     mul2,
     mul2,
     mul2,
     str,
     call.count("2"),
     add2
)
# out:
# 4
```

## 属性呼び出しもパイプに入れる

属性呼び出しを関数にするならattrgetterを使う。


```python
from collections import namedtuple
from operator import attrgetter


Point = namedtuple("Point", ["x", "y"])


pipe(Point(3, 2),
    attrgetter("y"),
    add2)
# out:
# 4
```

これもすっきりさせたかったらこんなクラスを定義すればいい。

```python
class AttrGetter:
    def __getattribute__(self, name):
        return attrgetter(name)


get = AttrGetter()
```


```python
pipe(Point(3, 2),
    get.y,
    add2)
# out:
# 4
```

## 部分適用もほしい

```
h(g(3, f(x)))
```

みたいなのを扱う場合はどうするか？

この場合は、関数gに「3だけ適用した」ような関数（これを**部分適用**という）を作った上で

```
x |> f |> (gに3だけ適用した関数) |> h
```

と書けばよい。

pythonの場合はpartialという部分適用のための関数が用意されているから、これを使えば書ける。


```python
pipe(range(9),
    partial(filter, lambda x: x%2 == 0), # 偶数だけ取って
    partial(map, lambda x: x*2), # 二倍して
    partial(fts.reduce, lambda x, y: x+y)) # 足し算
# out:
# 40
```

partialっていちいち書くのが面倒ならpとか別名を付けてしまえばよい。実用的にはこれでいいと思う。

パイプ演算子とラッパークラスを定義している場合なら、部分適用のための演算子を追加することも出来る。


```python
class Pipe(PipeBase):
    def __init__(self, previous, f):
        self._previous = previous
        self._f = f
    
    def out(self):
        return self._f(self._previous.out())
    
    def __truediv__(self, arg):
        return Pipe(self._previous, partial(self._f, arg))
```


```python
(pin %
    range(9) %
    filter / (lambda x: x%2 == 0) %
    map / (lambda x: x*2) %
    fts.reduce / (lambda x, y: x+y) %
    pout)
# out:
# 40
```

演算子の優先順位の関係で、ラムダ式にいちいちカッコを付けなきゃいけないのが悲しいところだ。あと、キーワード引数への部分適用はできない。

ちなみに上のコードはパイプ演算子(%)と部分適用演算子(/)が同じ優先順位を持つことを利用している。\>や\|を使わなかったのは、同じ優先順位を持つ演算子がないからだ。

`partial(filter, lambda x: x%2 ==0)`じゃなくて`filter(lambda x: x%2 == 0)`って書きたい！！！F#ちゃんとかHaskellちゃんは買ってもらってるもん！！うちはなんでだめなの！！みたいに駄々をこね出すとなかなか面倒なことになる。

メソッド呼び出しと同じやり方で解決しようと思うと関数の名前から関数を復元しないといけなくなるのだが、これはパイプ演算子を使っているコードの側の名前空間にアクセスしないといけない。黒魔術が必要。


```python
from inspect import stack


class FirstPartialFunc:
    def __getattribute__(self, name):
        # スタックを遡り呼び出し元の名前空間を再現する
        frame = stack()[1].frame
        caller_namespace = {
            **frame.f_builtins,
            **frame.f_globals,
            **frame.f_locals
        }
        return PartialFunc(caller_namespace[name])

    
class PartialFunc:
    def __init__(self, namespace_or_func=None):
        self._namespace_or_func = namespace_or_func
    
    def __getattr__(self, name):
        # モジュールを介した呼び出しの場合もhandleできるようにする
        return PartialFunc(getattr(self._namespace_or_func, name))
    
    def __call__(self, *args, **kwargs):
        return partial(self._namespace_or_func, *args, **kwargs)


part = FirstPartialFunc()
```


```python
pipe(range(9),
    part.filter(lambda x: x%2 == 0),
    part.map(lambda x: x*2),
    part.fts.reduce(lambda x, y: x+y))
# out:
# 40
```

多分これでいいと思うんだけどあんまり自信ない。

## 副作用も起こしたい

途中でprintしたい、みたいな話。

何にも考えないでpipeに突っ込むと戻り値がNoneになったりで計算が止まる。

値は素通しするような高階関数を定義してやればできる。


```python
def do(f):
    def _do(x):
        f(x)
        return x
    return _do
```


```python
pipe(range(9),
    partial(filter, lambda x: x%2 == 0),
    list,
    do(print),
    partial(map, lambda x: x*2),
    list,
    do(print),
    partial(fts.reduce, lambda x, y: x+y))
# [0, 2, 4, 6, 8]
# [0, 4, 8, 12, 16]
# out:
# 40
```

もちろん演算子でやることもできる。記号がいろいろ出てきて楽しい。

```python
class Pipe(PipeBase):
    def __init__(self, previous, f):
        self._previous = previous
        self._f = f
    
    def out(self):
        return self._f(self._previous.out())
    
    def __truediv__(self, arg):
        return Pipe(self._previous, partial(self._f, arg))
    
    def __matmul__(self, f):
        return Pipe(self._previous, do(self._f)) % f
```


```python
(pin %
    range(9) %
    filter / (lambda x: x%2 == 0) %
    list %
    print @
    map / (lambda x: x*2) %
    list %
    print @
    fts.reduce / (lambda x, y: x+y) %
    pout)
# [0, 2, 4, 6, 8]
# [0, 4, 8, 12, 16]
# out:
# 40
```

## 実用的には

pipe関数+partial+methodcaller+attrgetter+doで書くくらいならまあ特に変なコードでは無いと思う。Pythonicかは別として、コレクション処理を連続しなきゃいけないケースなんかではかなり簡潔にコードが書けるだろう。

演算子のオーバーロードとかマジックメソッドの実装とかスタックフレームのさかのぼりとかだんだん趣味の世界に入っていく。