JavaScript/TypeScript でお手軽パイプライン で再帰でパイプができると教えていただいたので、関数合成の方もあわせてやってみました。
あわせて、途中で計算が失敗したらそれ以降は計算しないで失敗の値をそのまま返し、関数適用時に何かエラーを投げたら、それも計算の失敗とみなしてnullを返すようにしてみました。
// パイプライン:
const pipe = x => f =>
!isFunction(f) ? x
: pipe( applyIfSucceed(f)(x) )
// 関数合成:
const innerComp = fs => g =>
!isFunction(g) ? fs.reduceRight(
(acc, f) => applyIfSucceed(f)(acc)
, g
)
: innerComp([...fs, g])
const compose = innerComp([])
// 共通の補助関数:
const isFunction = f =>
typeof f === "function"
const applyIfSucceed = f => x => {
if( isFailure(x) ) { return x }
try { return f(x) }
catch(e) { return null }
}
const isFailure = x =>
x === null || x === undefined || Number.isNaN(x)
// 使い方の例:
pipe(2)(x=>x*5)(x=>x+3)(); // 13
compose(x=>x*5)(x=>x+3)(2); // 25
// 途中で失敗したらその値を返す:
pipe(2)(x=>NaN)(x=>3)(); // NaN
compose(x=>5)(x=>null)(2); // null
ちなみに、関数合成の方は本当には合成してなくて、逆向きのパイプラインになってます。
関数を配列に貯め込んで、値が来たら後ろの関数から順番に適用、ってしてます。
マジ関数合成しちゃった場合、途中で失敗というのが検知できなくてこうしました。あしからず(追記:あとで考えたらできました。末尾の追記参照)。
ちなみに、そのマジ関数合成しちゃう版はこちら:
const compose = f => g =>
!isFunction(g) ? f(g)
: compose( x => f(g(x)) )
// 使い方の例:
compose(x=>x*5)(x=>x+3)(2); // 25
// 途中で失敗しても教えてくれない...:
compose(x=>5)(x=>null)(2); // 5
追記 :マジ関数合成でもできました。
const compose2 = f => g => x =>
f(g(x))
const id = x =>
x
const innerComp = f => g =>
! isFunction(g) ? f(g)
: innerComp( compose2(f)( applyIfSucceed(g) ) )
const compose = innerComp(id)
// 使い方の例:
;
compose(x=>x*5)(x=>x+3)(2) // 25
compose(x=>5)(x=>null)(2) // null
最初の f には 恒等関数 id を持ってきて、 g を合成するかわりに applyIfSucceed(g) を合成していけばよい。