
最近看到了一篇文章,Approximating Named Arguments in Java,讲的是怎么在 Java 里面模拟类似 Python 的命名参数,整体来说不算太出乎我的意料吧,主要还是原地改状态和 builder 模式,还有个 with 的写法虽然看起来有点意思,但总感觉缺点什么。
不过看到这个风格的写法的时候,突然想起了之前自己写的一个项目里面用 lambda 来模拟模式匹配的写法。
kMeans(X, nClusters, opts -> opts.maxIter = 1000); String guess = new Match<Shape, String>(new Circle()) .is(Circle.class, (circle) -> "我是一个圆") .is(Square.class, (square) -> "我有点方") .otherwise((Shape shape) -> "你看不见我") .get(); 然后突然想,是不是可以用一个 lambda 参数来当占位符,来变相实现命名参数。大概是(maxIter -> 1000)这样的感觉。
但仔细想的话,Java 里面没有 currying 和 uncurrying 的概念,换句话说,本来我构想的在 Java 里应该用的是 Supplier 接口,但现在我手头上的是 Function<T, U>而且其中的 T 无法被忽略掉。似乎是一个很致命的问题。
看起来就卡死了……
然后想了想,其实我需要的是一个能把 Function<T, U>消掉的东西,回忆了一下最近 code review 从隔壁 senior 学来的 static class 的一些奇怪的写法,然后又想了想自己最近在搞的 Ref<T>,然后折腾出了一坨这玩意:
static final class KMeans { static final class Options { int maxIter = 300; double tol = 1e-4; boolean verbose = false; // ... } @FunctionalInterface interface Param { void apply(Options o); } static Param maxIter(IntSupplier s) { return o -> o.maxIter = s.getAsInt(); } static Param tol(DoubleSupplier s) { return o -> o.tol = s.getAsDouble(); } static Param verbose(BooleanSupplier s) { return o -> o.verbose = s.getAsBoolean(); } static Output kMeans(Data X, int nClusters, int maxIter, boolean verbose, double tol, ...) { ... } // 这里是具体的代码逻辑 static Output kMeans(Data X, int nClusters, Param... params) { var optiOns= new Options(); Arrays.stream(params).forEach(applier -> applier.apply(options)); // 然后就可以继续实现具体的代码逻辑了 } } psvm 里面可能长这样:
var X = new Data(...); var res1 = KMeans.kMeans(X, 3, 1000, 1e-8, ...); // 常见的写法 var res2 = KMeans.kMeans(X, 3, KMeans.verbose(() -> true), KMeans.tol(() -> 1e-6), KMeans.maxIter(() -> 1000), ...); // 现在可以这么写了,而且顺序也可以换来换去 // 如果不用担心 import 会变得乱七八糟的话还可以这样 var res3 = kMeans(X, 3, maxIter(() -> 1000), tol(() -> 1e-6), verbose(() -> true), ...); 感觉看起来有点像 Python 的命名语法,也不至于像 builder 模式那么嗦了。
但是总感觉好像还是有点嗦(
然后还可以稍微再改改。
interface Constant { static Param maxIter(int maxIter) { return o -> o.maxIter = maxIter; } static Param tol(double tol) { return o -> o.tol = tol; } } // 用法 IntSupplier someOtherCalc = () -> { return ... ; } // 假设它会返回 2048 var res4 = kMeans(X, 3, maxIter(someOtherCalc) Constant.tol(1e-6), Constant.verbose(true), ...); 但似乎显得更怪异了,而且这么写代码感觉会被人打(
1 yazinnnn0 102 天前 为啥不用 kotlin 或者 scala |
2 xtreme1 102 天前 vavr 里有类似的东西 用多了在 idea 中编辑时巨卡 |
3 chendy 102 天前 一个劣化版本 …public static void main(String[] args) { Demo student = new Demo( d -> d.setId(1L), d -> d.setAge(24), d -> d.setName("Student") ); } @Data public static class Demo { private long id; private String name; private int age; public Demo() { } public Demo(Consumer<Demo>... consumers) { for (Consumer<Demo> consumer : consumers) { consumer.accept(this); } } } |
4 guyeu 102 天前 Java 没有默认参数、关键字参数这些功能,对命名参数也没有啥需求啊。。 |
5 Ketteiron 101 天前 语言的语法是用来增强表达的能力,你这个设计不太好,增加了阅读/编写的复杂度,但是收益无法抵消额外开销,在我看来是负优化,想在 java 里搞奇技淫巧的应该直接转 kotlin/scala ,别指望能在垃圾语言上屎上雕花,只能屎上雕屎。 命名参数这玩意,是为了把参数从水平转成垂直,就像浏览器的普通标签页 vs 垂直标签页,语法上想实现这种功能,一定要支持默认参数/可选参数,所以 java 只能洗洗睡了。 而且命名参数也不是最好的设计,js 不支持命名参数,但是 es6 的参数解构远比命名参数好用。 |
6 cloudzhou 101 天前 在业务开发方面,脚本语言几乎式微,一个原因就是维护性不好,写的开心维护的累 Perl 更是誉为 readonly 语言 So ,builder 已经足够可以了 你这个模式是 Golang 的 Option 模式,用于某些选项类,不是通用参数 |
7 netabare OP |
8 xuanbg 100 天前 好玩,但没什么用 |