1

在尝试以函数样式编写代码时,我经常会遇到这样的场景,我想要使用两个函数将一个集合或流映射到另一个:一个函数应用于传递给出谓词和应用于失败元素的另一个函数。这两个函数产生一个公共输出,应该收集这些输出以供进一步处理。使用谓词和两个函数映射集合

下面是Java中一个人为的例子。

Stream.of("foo", "bar", "baz", "qux") 
     .map(it -> it.startsWith("ba") ? it.toUpperCase() : new StringBuilder(it).reverse()) 
     .forEach(System.out::println); 

和Groovy中的例子一样。

["foo", "bar", "baz", "qux"] 
     .collect{ it.startsWith("ba") ? it.toUpperCase() : it.reverse() } 
     .each{ println(it) } 

我的问题是关于用于实现此映射的条件(三元)语句。有没有一种功能方式来实现这样的映射,而不诉诸分支逻辑?

+1

您正在将过滤和映射组合在相同的步骤中,这使得看到抽象更难一些。我有一个[关于换能器的几个答案](https://stackoverflow.com/search?q=user%3A633183+transducer),你可能有兴趣阅读 - 理解它们之后,你将能够过滤,地图,*和*甚至只是迭代通过你的集合打印*一个*时间^ _^ – naomik

+0

我的理解是,链接操作的效率与语言有关,例如Java流被懒惰地评估,所以每个_terminal_操作只有一次迭代('filter'和'map'是中间操作)。我在这里更感兴趣的是链接一个过滤器,谓词的两端映射到不同的函数。换句话说,它是一个过滤器,它不会除去任何东西,而只是将元素分开以进行单独处理,然后再将它们重新组合为单个转换后的集合。 – jaco0646

回答

1

你总是可以用另一个函数抽象表达式。既然你标记你的问题语言无关,我编码我用javascript,可执行例如在你喜欢的浏览器 - 我希望这是好的:

// select :: (a -> Boolean, a -> b, a -> b) -> a -> b 
 
const select = (p, f, g) => x => p(x) ? f(x) : g(x); 
 

 
// mapSelect :: (a -> Boolean, a -> b, a -> b) -> [a] -> [b] 
 
const mapSelect = (p, f, g) => xs => xs.map(select(p, f, g)); 
 

 
const reverse = s => s.split("").reverse().join(""); 
 
const toUpperCase = s => s.toUpperCase(); 
 
const startsWith = s => t => t.indexOf(s) === 0 
 

 
const xs = ["foo", "bar", "baz", "qux"]; 
 

 
console.log(
 
    mapSelect(startsWith("ba"), toUpperCase, reverse) (xs) 
 
);

显然,mapSelect不保存你来自条件运算符。它只是另一层抽象。由于它不像map那样普遍,所以你特别增加了同事的认知负担而没有太多。

结论:我会坚持使用明确的条件运算符而不是伪装分支逻辑。

+1

您可能希望将'select'重命名为'ifElse';实际上,[ramda实现它几乎完全一样](http://ramdajs.com/0.21.0/docs/#ifElse)。 'mapSelect'抽象有点尴尬,'map(ifElse(startsWith('ba'),toUpperCase,reverse))'可能是足够声明的 - 否则很好的答案 – naomik