Javascript 像单子一样处理流
考虑以下流程:Javascript 像单子一样处理流,javascript,haskell,rxjs5,Javascript,Haskell,Rxjs5,考虑以下流程: const oEmailValid$ = oEmailInput$ .map(_catchInputCtrlValue) .map(_isStringNotEmpty) .map(...) .map(...) .map(...) .map(...) .subscribe((predicate) => console.log(predicate)) 假设_isstringnotetry返
const oEmailValid$ = oEmailInput$
.map(_catchInputCtrlValue)
.map(_isStringNotEmpty)
.map(...)
.map(...)
.map(...)
.map(...)
.subscribe((predicate) => console.log(predicate))
假设_isstringnotetry返回false,因此我不想继续流,但仍然希望在subscribe函数上接收_isstringnotetry的返回值,在本例中为false
如何得到它
为了澄清,我的意思是,考虑下面的Haskell代码:
结果我什么也得不到,因为第四次计算什么也不返回。你可以尝试这样破解它:
const NOTHING = {};
const wrapMaybe =
fn =>
x =>
(x!==undefined && typeof x.then === "function")
?x.then(wrapMaybe(fn))
:(x === NOTHING)?NOTHING:fn(x)
const source = Rx.Observable.from([1,2,3,4,5]);
//wrap each map function in a maybe
const example = source
.map(wrapMaybe(val => console.log("map:",1,val) || val + 10))
.map(wrapMaybe(val => console.log("map:",2,val) || Promise.resolve(NOTHING)))
.map(wrapMaybe(val => console.log("map:",3,val) || val + 10))
;
//wrap the source in an object that has mm (maybe map) function
const wrapSource =
source => {
source.mm =
function(fn){
var ret = this.map(wrapMaybe(fn));
ret.mm = this.mm;
return ret;
}
;
return source;
}
;
const example2 = wrapSource(source)
.mm(val => console.log("mm:",1,val) || val + 20)
.mm(val => console.log("mm:",2,val) || NOTHING)
.mm(val => console.log("mm:",3,val) || val + 20)
;
example.subscribe(val => console.log(val));
example2.subscribe(val => console.log(val));
JavaScript没有管道或组合运算符,因此组合函数的语法可以是:
compose(arrayOfFunctions);
下面是compose的一些示例:
const NOTHING = {};
//see if x is like a promise (has a "then" method)
const promiseLike =
x =>
(x!==undefined && typeof x.then === "function")
;
/**
*
* takes a function fn and returns a function that takes x
* and calls fn(x) if x does not equal NOTHING
* if x does equal NOTHING it returns NOTHING
*/
const wrapMaybe =
fn =>
x =>
(promiseLike(x))
?x.then(wrapMaybe(fn))
:(x === NOTHING)?NOTHING:fn(x)
;
/**
*
* takes 2 functions and turns it into:
* fn2(fn1(x)) when a value x is provided
* if x is a promse it will turn it into:
* x.then(x => fn2(fn1(x)))
* if fn1(x) is a promise it will turn it into:
* fn1(x).then(x => fn2(x))
* if both x and fn1(x) are promises:
* x.then(x => fn1(x)).then(x => fn2(x))
*/
const compose2 =
fn1=>
fn2=>
x => {
//if the current value is a promise
if(promiseLike(x)){
return x.then(x => compose2(fn1)(fn2))
}
const res1 = fn1(x);
if(promiseLike(res1)){
//result of fn1(x) is a promise
// invoke fn2 with the promise resolved value
return res1.then(x => fn2(x))
}
//no promise, invoke fn2 with result of fn1(x)
return fn2(res1);
}
;
/**
* turns an array of functions [fn1,fn2,fn3] into:
* fn3(fn2(fn3(x)))
* both x or any of the results of the functions can be a promise
* If it is a promse then the next function will be called with
* the resolve value of the promise.
* If the promse is rejected the next function is not called
* the handler for reject is called later down the promise chain
* for example fn2 returns a rejected promise:
* fn1(x)
* .then(x => fn2(x))
* .then(notCalled => fn3(notcalled))
* .then(undefined,called)
*/
const compose =
fns =>
fns.reduce(
(acc,fn) => compose2(acc)(fn)
,x=>x//id function
)
;
/**
* Turns an array of functions into compose(arrOfFunctions)
* but maps the functions to wrapMaybe(function):
* fn turns into wrapMaybe(fn)
*/
const composeWithMaybe =
fns =>
compose(
fns.map(fn=>wrapMaybe(fn))
)
;
const source = Rx.Observable.from([1,2,3,4,5]);
const mapHandlers =
[
val => console.log("map:",1,"value:",val) || val + 10
// you can return a promise in the function(s)
// from then on all results will be promises but next
// function is not called with the promise but it's resolve value
// ,val => console.log("map:",2,"value:",val) || Promise.resolve(NOTHING)
,val => console.log("map:",2,"value:",val) || NOTHING
// instead of Some or None you could return a rejected promise
// this basically gets you the same thing, none of the other
// functions are called, the result is a promise value that
// will invoke it's reject handler
// ,val => console.log("map:",2,"value:",val) || Promise.reject("Rejected reason")
,val => console.error("map should not be executed:",3,"value:",val) || val + 10
]
;
const example = source
.map(
composeWithMaybe(
mapHandlers
)
)
;
//synch example
example.subscribe(val => console.log(val));
// asynch example, you need to return a promise in one of the funcitons
// example.subscribe(
// val => val.then(
// val => console.log(val)
// ,reject => console.warn(reject)
// )
// );
如果要使用自定义运算符,可以使用编译代码。这些自定义运算符在您的IDE中显示为语法错误,因此如果您想编写函数,那么可以查看或。因为您只使用map,这意味着所有验证都是同步进行的,所以我只需编写一个函数来一次处理所有验证
function validate(validators) {
return input =>
validators.every(validator => validator(input));
}
const oEmailValid$ = oEmailInput$
.map(validate([
_catchInputCtrlValue,
_isStringNotEmpty,
...
]))
.subscribe((predicate) => console.log(predicate));
我相信,尽可能多地编写纯函数,并且只有在您真正需要时才使用流函数或类似Monad的东西是很好的,就像在Haskell中一样。民间故事有一个很好的Monad。
对于这个案例,我想到了几个解决方案。然而,如果您真的在寻找类似Haskell的maybe then Folktale,正如用户7128475所建议的,或者有maybe实现 第一个需要使用.filter和.defaultIfEmpty,但是这仅在oEmailInput$是0-1流时有效
const oEmailValid$ = oEmailInput$
.map(_catchInputCtrlValue)
.map(_isStringNotEmpty)
.filter(isValid => isValid) // or .filter(Boolean)
.map(...)
.map(...)
.map(...)
.map(...)
.defaultIfEmpty(false)
.subscribe((predicate) => console.log(predicate))
如果您想将此解决方案应用于0-*流,那么它也可以在flatMap内部工作
const oEmailValid$ = oEmailInput$
.map(_catchInputCtrlValue)
.map(_isStringNotEmpty)
.flatMap(isValid => Observable.of(isValid)
.filter(Boolean)
.map(...)
.map(...)
.map(...)
.map(...)
.defaultIfEmpty(isValid)
)
.subscribe((predicate) => console.log(predicate))
一种更通用的解决方案是再次使用flatMap,它也适用于0-*流:
const oEmailValid$ = oEmailInput$
.map(_catchInputCtrlValue)
.flatMap(input => _isStringNotEmpty(input)
? Observable.of(input)
.map(...)
.map(...)
.map(...)
.map(...)
: Observable.of(false)
)
.subscribe((predicate) => console.log(predicate))
但就我个人而言,我不认为流是这类问题的最佳代表。Streams best model actions\ behavior具有单个行为目标,目前我不清楚您原始示例的目的,并且似乎有单独但相等的分支。流通常应该建模数据如何通过一系列期望的操作来构成单个行为。这就是我怀疑Kagawa Shuhei想要达到的目的。例如,我使用一个表示表单的流,该表单执行以下操作:
onFormSubmit$
.map(_validateData)
.tap(console.log) // along the lines of your subscribe function
.tap(showErrors)
.tap(updateFormState)
.filter(form => form.isValid)
.subscribe(_postData)
_validateData的结果是一个一般形状,如:
{
isValid: true | false,
fields: {
email: string, // original input text
},
errors: [
{ name: "email", message: "Please enter a valid email address." },
],
}
通过这种方式,像淋浴ROR这样的步骤可以映射到错误数组,并且在它为空或需要条件测试时不会中断。评论Haskell,它实际上应该只有3>>只有4>>只有7>>没有任何>>。。。等等,因为>>被定义为a>>b=a>>=\\\->b。
onFormSubmit$
.map(_validateData)
.tap(console.log) // along the lines of your subscribe function
.tap(showErrors)
.tap(updateFormState)
.filter(form => form.isValid)
.subscribe(_postData)
{
isValid: true | false,
fields: {
email: string, // original input text
},
errors: [
{ name: "email", message: "Please enter a valid email address." },
],
}