timzaak / blog

8 stars 1 forks source link

Scala 3 #51

Closed timzaak closed 2 years ago

timzaak commented 4 years ago

Dotty 要在 2020年底 发布 M1 or PR,我现在的工作环境还在 scala2.12.8 版本,本想着 scala2.13.2 版本出来后,将自己的整个工作栈从 scala2.12 + java 8 升级到 Scala 2.13. 2 + java 11,现在反而没有多少动力,即使升级了,立马又会面临下一次升级,怎么滴 scala2.12 + java 8 我至少使用了3年+吧,现在升级,估计用不到一年,又得大规模升级。

Dotty 的语法,学了一遍官方文档,与其说是 Scala 的增强语言,还不如说是一门新语言。私认为比 Scala 更凝练以及写起来会更顺滑,立马就想丢弃掉老语法。

IDE 目前官方支持 VS code,Idea 并不行,所以需要耐心等待。我大概率不会去学一套 VS code 的快捷键。

由于 Akka 2.6 版本的 typesafe actor 的稳定以及后续 roadmap 以及 Scala 语法的大升,两年后,我先前积累或维护的 Scala 库,估计都要经历代码超90%的重写。越来越想将以后依赖的第三方库全部使用 JAVA,稳定大于一切!

不知道 Scala 3 的语法能稳定多长时间。期望是20年吧!

timzaak commented 3 years ago

idea 使用 Scala3 木问题。 目前跑通了 akka 和 playframework, 还欠缺 scala3 enum json 序列化,数据库 orm 选型(scalikejdbc 使用了macro)。大概率会奔着 ZIO、circe、quill 去。

如果不考虑可能存在的性能回退问题(runtime/compile), 可以用来做常规的 web 开发了。

timzaak commented 3 years ago

Scala 3 复杂度比 Scala 2 高出一级。 虽然目前自己写的一些小项目没用到多少新特性。 记录下当前遇到的问题:

  1. playframework 的 路由DSL q q_o 挂了,因为使用了macro,自己尝试了一下 scala inline 来重写,能替代实现,就是没测性能,也不知道是否用到 compileTime。
  2. scalikejdbc ,挂掉。目前社区在准备跟进 scala 3 中。 至于替代品,scala 世界里的所有 orm 都有用到 macro 去实现 DSL,目前能找到明确跟进信息的就只有 quill 和 scalikejdbc。slick 都找不到。
  3. playframework 的 sbt 插件目前我无法和 scala 3 集成,用 embeded play server 来组织代码,相关依赖库需要自己缺啥补啥。目前还没有打包上线,大概率老的方式行不通。
  4. 以前用 git submodule 的方式,引用 Scala 2 项目库,全炸了,需要把他们各自升级到3,以后有时间再搞。
  5. json4s 对 scala 3的 enum 支持,还不够好(大概率是我不会用)。

总的来说,以前写的第三方库拓展,全部废掉。要想再用起来,要花费不少的功夫。当前阶段,需要把一些基础库切到 Scala3 友好的库上(基本上都纯函数式库)。

Scala 2的一堆 macro 库, 想要迁移到 scala 3, 需要比较高级的编程能力和学习能力,预计40%的库今年底是迁移不过去,20%的库要破坏一些兼容性。很多scala库 become dead.

附录一个跑 playframework 的配置文件

import sbt._

object Dependencies {
  val playVersion = "2.8.7"
  val logbackVersion = "1.2.3"
}
import Dependencies._

val scala2Version = "2.13.4"
val scala3Version = "3.0.0-RC1"

lazy val root = project
  .in(file("."))
  .settings(
    name := "demo",
    version := "0.1.0",
    libraryDependencies ++= Seq(
        ("com.typesafe.play" %% "play" % playVersion).withDottyCompat(scalaVersion.value),
        ("com.typesafe.play" %% "play-akka-http-server" % playVersion).withDottyCompat(scalaVersion.value),
        ("com.typesafe.play" %% "filters-helpers" % playVersion).withDottyCompat(scalaVersion.value),
        ("com.typesafe.play" %% "play-logback" % playVersion).withDottyCompat(scalaVersion.value),
        "ch.qos.logback" % "logback-core" % logbackVersion,
        "ch.qos.logback" % "logback-classic" % logbackVersion,
       // ("org.scalaj" %% "scalaj-http" % "2.4.2").withDottyCompat(scalaVersion.value),
       // ("org.json4s" %% "json4s-jackson" % "3.6.10").withDottyCompat(scalaVersion.value),

        "com.novocode" % "junit-interface" % "0.11" % "test"
    ),

    // To make the default compiler and REPL use Dotty
    scalaVersion := scala3Version,

    // To cross compile with Dotty and Scala 2
    crossScalaVersions := Seq(scala3Version, scala2Version)
  )

目前, 不建议用Scala3做Web开发

timzaak commented 3 years ago

花了两天的时间,想看看如何用 dotty 实现 json4s 的自动序列化/反序列化。研究的过程极其类似当年Scala2刚出 Macro 的时候。大部分时间都在找资料。

使用 derived 特性能实现 case class/enum 序列化成 json,用法也能和 json4s 当前的API 差不多,enum 能序列化成 String 类型,但是反序列化就不行了,derived 只提供了Mirror.Product 的实例化,enum/ sealed trait 之类的Mirror.Sum 就没有,需要用到 Macro 来实例化。

Macro 的使用介绍,官方文档有点滞后,而且API还老是在变,0.25、3.0.0-M2、3.0.0-RC1 三个版本各不相同。预估即使 3.0.0 版本发布了,也还有可能变。这就很头疼。

还是老老实实的,手工序列化/反序列化。 json4s 的 scala 3 适配,再等等。

参考资料:

PS: sealed trait 可以通过 macro 找出具体的类型,然后继续序列化。 enum A{ case B,C} case object D 这两种的,尚未找到解决方案。

timzaak commented 3 years ago

Update

Scala 3.3 的 Macro API 做了大量的升级,以下都已过期,去看 https://github.com/lampepfl/dotty-macro-examples 更好一些。

Macro 基本概念

Tree 是 scala 语言的抽象语法树, 目前 IDEA,不支持 Tree 的提示,需要各种扒反射文档。自己构造起来,也特费劲。

Symbol: 记录各种信息, 包括其半生类的信息。可与 Tree 相互转换。 多用来获取判定信息,例如是否是 sealed trait,以及获取内部代码的Symbol,并转换 Tree,进行代码构造 。

Expr[_] Type[_] 是在 Tree 上提供更高级的API,可以与 Term/TypeTree 相互转化。 其 show 方法,可用来开生成的代码,对调试来讲,及其有用。

TypeRepr 可以通过 ClassOf[_] 、 TypeTree 、 Type[_] 获得,记载类型相关信息,主要用于关联类型的信息获取,例如父类型、类型别名,以及更细粒度的类型判定。

quotes.reflect.xxxModule 提供API,构造语法树。

Quotation & Splice

Quotation is expressed as '{...} for expressions and as '[...] for types. Splicing is expressed as ${ ... }

${'{e}} = e

e:代码表达 '{e}: 代码表达 => 语法树, ${'{e}}:代码表达=> 语法树 => 代码表达

'{${e}} = e

e: 语法树, ${e}: 语法树 =>代码表达 '{${e}}: 语法树 => 代码表达 => 语法树

常规用法

import scala.quoted._
// 创建 case class 实例
inline def createCaseClass[T]:T = ${createCaseClassImpl[T]}

def createCaseClassImpl[T](using quotes:Quotes)(using t:Type[T]): Expr[T] = {
  import quotes.reflect._
  // 通过 typeTree 的的 symbol 能拿到关于此 类型 的所有相关信息。它会提供一些简便方法
  val params = TypeTree.of[T].symbol.caseFields.map{ field =>
    val ValDef(name, typeTree, _) =field.tree
    // 用 TypeRepr 进行类型构造,方便其它 API 构造语法树。
    val typeBinderTpe = TypeRepr.of[TypeBinder].appliedTo(typeTree.tpe)
   // Implicts.search(TypeRepr) / Expr.summon[Type[_]]  用来获取隐式函数引用。
    val d = Implicits.search(typeBinderTpe) match {
      case result:ImplicitSearchSuccess =>
        Apply(Select.unique(result.tree, "apply"), Expr(name).asTerm::Nil)
      case _ => report.throwError(s"could not find implicit of TypeBinder[${typeTree.show}]")
    }
    NamedArg(name, d)
  }
  // Scala 会有一些内部构造函数,所以需要通过  println((inline codeSplice).asTerm) 反推代码的语法树是什么
  Apply(Select.unique(New(TypeTree.of[T]), "<init>"), params).asExprOf[T]
}
//`quotes.reflect.SymbolModule` 可以获取代码注入的上下文信息, 并开放了代码更改的API(尚未尝试)。例如你想拿构造 This/super 等。

This(Symbol.spliceOwner.owner.owner)

given 隐式转换

隐式转换这个词,在 Scala3 里,更像是编译期进行 Type+Context => InstanceOfType,然后生成代码/插入引用到用户指定位置。

ScalaMeta

ScalaMeta 也是 Scala 的 metaprogramming library,看它 issue,快要支持所有的 Scala3 的所有特性,看基于它做的工具,应该是同时支持 scala2 和 scala3。我还不确定这个 metaprogram library 和 scala3 的 macro 是替代关系,还是API 增强?

timzaak commented 3 years ago

ZIO

想着基于 Caliban 做个 grahpql 的微服务,研究了以下比较全面的 Demo dumpsterfireproject/caliban-example,总结来讲,有点晕。

ZIO 应该是受 Scalaz 的启发,风格是纯函数式。他们的团队基于ZIO,做了很多常用 web 开发组件的轮子或者封装。Caliban算是一个Graphql 的轮子。某种意义上很感谢他们对 Scala纯函数式 编程的探索,甚至他们已经快构建完毕ZIO web 开发生态(就是文档各种匮乏)。

dumpsterfireproject/caliban-example 这个 DEMO 应该是一个标准的 ZIO 项目,写法很正规。

但是,但是,但是,存在很多逻辑重复的代码。研究了一番,这些重复是为了使用 ZLayer、ZRuntime 的注入,以 ZIO 的方式组织代码。而 Caliban 除了支撑 ZIO 生态,还要兼容常规的 Scala 生态,这就导致 Caliban 需要以常规的方式注入 ZIO 代码片段。

这就有点难受了,我的代码风格是: the less, the better。思前想后,要么restful API,要么 Sangrid,使用 Scala 2.13 最为稳妥, Scala 3 放弃。

timzaak commented 2 years ago

刚刚看了 https://www.techempower.com/benchmarks/ 关于web框架的benchmark,嗯,不容乐观。甚至综合得分 play2 比不过 spring。虽说一切不能以分数作为衡量标准,但至少说明这俩在同一个等级上的,我花费时间学习做相同事情的的不同API上,是否他娘的就是浪费时间!

timzaak commented 2 years ago

playframework may be dead.

现在这个框架貌似变成单人维护,其背后的公司没有人花精力在这上面,想想真是可怕。 现在有两个选择,要么用 cask 这种在 undertow 等稳定Java 库上薄薄封装一层的项目,要么就花时间自行维护 Scala3的库。

还是要选择后面有赚钱业务公司的东西,业务驱动技术。

虽然是单人,但已经有基金会+sponsor 公司。节奏上虽然慢,但好歹还是在继续前进!

timzaak commented 2 years ago

我该用 Scala 3 填补哪方面的空白?

目前没看到,Rust 的生产力越来越高,我用 Rust 开发效率上,没有弱到只有 Scala 一半的程度。而项目的运行低开销越发重要。我曾设想用 GraalVM + Scala3 来解决这个问题,但驱动力不大,其库选型反而会受到无法预期的限制。后面可能尝试用GraalVM 来做 ClientSide 的编程。至于 Web端,目前出了 Quarkus, 但性能上、稳定性上 Native 比不上 JVM。

Kotlin 的生态除了大数据还没法和Scala比, 其他大有反超之势。LiHaoyi 不知道为什么貌似也不再出新 Scala 库了,往Scala 3迁移的迁移也特别缓慢。

GraalVM 探索

目前的结论是还不行。 主要是 Lazy val 的问题。

import io.circe.syntax._
import io.circe.generic.auto._

case class Person(name: String)
case class Greeting(salutation: String, person: Person, exclamationMarks: Int)

@main def hello: Unit =
  println(Greeting("Hey", Person("Chris"), 3).asJson)

需要配置 reflect-config.json 成这样:

[
  {
    "name":"sun.misc.Unsafe",
    "allDeclaredFields":true
  },
  {
    "name":"io.circe.Encoder$",
    "fields":[{"name":"0bitmap$1"}]
  },
  {
    "name":"io.circe.Decoder$",
    "fields":[{"name":"0bitmap$1"}]
  },
  {
    "name": "Main$package$$anon$1",
    "fields":[{"name":"0bitmap$2"}]
  },
  {
    "name": "Main$package$$anon$2",
    "fields":[{"name":"0bitmap$1"}]
  }
]

虽然是第一次的缘故,但写这个配置花了超1小时,在没有自动化的解决方案之前,对于大型项目来讲,Scala 的 NativeImage 基本不可用。需要等待 https://github.com/lampepfl/dotty/pull/14545 解决,看规划,貌似要等到 3.2.0-RC1 方能解决。现在延迟到 3.3.0-RC1

timzaak commented 1 year ago

IDE 从 IDEA 切换到了 VS Code + Metals, IDEA在搞Scala 3 的时候,很小的项目也会让风扇呼呼得响,跳转时会卡顿 、import 提示也要等10秒。VS Code + Metals 目前体验十分优秀,除了无法跑单独的测试用例,还没发现有什么大问题。

timzaak commented 1 year ago

Update 一下信息: 语言: 3.3.0 版本已经出现,第一个 LTS 版本。成熟度上已经稳定。 IDE: IDEA 很好用。 Web: 我基于 scalatra,封装了 web-sugar, ORM 现在用 protoquill,json 用 zio-json。用着还行。就是不确定 Async 相关的需求该如何解决,Websocket 也尚未搞起来。 GraalVM: https://github.com/lampepfl/dotty/issues/13985 已经 Fix Lazy Val, 后续可以尝试一下复杂的 NativeImage 项目了。 ZIO:核心成员之一跳槽去搞 Rust 了。