Scala字符串插值的妙用,以及java+scala混合编程
by 伊布
一
我们在项目中遇到一个需求,输入为json字符串,如:
{
"title": "lrtest",
"alg": "LogisticRegression",
"params": {
"inputTable": "xxx.lrtest3",
"labelColumn": "label",
"featureColumns": [
{"columnName":"f0","columnType":"Double"},
..
{"columnName":"f5","columnType":"Double"}
],
"elasticNetParam": 1.0,
"fitIntercept": true,
"maxIter": 10,
..
"fraction": 0.8,
}
}
输出为该机器学习算法执行的scala代码,如:
..
val lor = new LogisticRegression()
.setFeaturesCol("features")
.setLabelCol("indexedLabel")
.setRegParam(params.regParam)
.setElasticNetParam(params.elasticNetParam)
.setMaxIter(params.maxIter)
.setTol(params.tol)
.setFitIntercept(params.fitIntercept)
..
本质上需要的是一个json到scala的编译器。最早的时候使用纯java编程,解决方法是先写好模板化的代码,其中的参数如MaxIter这种在模板代码中写为${maxIter},然后根据json串解析到的参数,通过String.replace()模板代码以生成最终代码。过程如下:
模板代码:
val lr = new LogisticRegression()
.setFeaturesCol("features")
.setLabelCol("label")
.setWeightCol("weight")
.setElasticNetParam({elasticNetParam})
.setFitIntercept({fitIntercept})
.setMaxIter({maxIter})
.setRegParam({regParam})
.setStandardization({standardization})
.setThreshold({threshold})
.setTol({tol})
replace的过程就不贴了,简单来说就是先从文件中将模板代码读取出来,然后将占位符replace实际json串的参数,脑补下好了,代码删掉了,懒得去翻git了。
这样的坏处是什么呢?
第一,由于是模板代码,决定了这个过程非常不灵活。假如有个参数是可选的,不需要在new LogisticRegression()的时候set进去,模板代码不能很自然的搞定; 第二,可维护性比较差,因为模板跟replace的过程的割裂的,互相没有约束,很难在replace的代码段中清楚的看到有没有漏replace的占位符。 第三,每次请求都要从文件中读取模板代码,性能比较差。 第四,由于java中多行字符串只能使用 “string1 “ + “string2”这种形式来编写,并且如果字符串中有引号还需要加斜杠转义,造成如果想将代码拷贝出去运行的话需要做一系列的修改。
使用scala的字符串插值就可以很简单的解决这个问题。
//todo: 什么是scala 字符串插值,请google
还是上面的例子:
val params: LogisticRegressionBody = JsonProc.toObj(jsonStr, classOf[LogisticRegressionBody])
val createMethod =
s"""
val method = new LogisticRegression()
.setFeaturesCol("features")
.setLabelCol("label")
.setElasticNetParam(${params.getElasticNetParam})
.setFitIntercept(${params.getFitIntercept})
.setMaxIter(${params.getMaxIter})
.setRegParam(${params.getRegParam})
.setStandardization(${params.getStandardization})
.setThreshold(${params.getThreshold})
.setTol(${params.getTol})
"""
params即为json解析得到的body,字符串插值可以很灵活的将json的值“插”到模板代码中,并且scala支持多行字符串,可读性比较高。
二
由于有些问题使用java来解决比较熟悉,所以这个工程是java和scala混合的,其pom.xml需要注意下。下面贴spring boot框架和scala代码混合编程的maven工程。这种写法maven会mix方式的编译java和scala代码,即使2个子包有相互依赖也没问题。
maven还可以配置javaBeforeScala或者反过来,不过我觉得意义不大,不如mix以不变应万变。官方说明等后面找到了再贴一下链接。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ieevee</groupId>
<artifactId>ml</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<properties>
<scala.version>2.10.5</scala.version>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
...
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
纯记录,凌乱请见谅。
Subscribe via RSS