Scala

The name Scala comes from the word scalable, and true to that name, the Scala language is used to power busy websites and analyze huge data sets.

Scala特性:

  • 高级语言:无需处理底层概念,如指针和内存管理;

  • 面向对象和函数式编程:函数也能当成值来使用,定义匿名函数,支持高阶函数,允许嵌套多层函数;支持函数式和面向对象混合编程。

    • Functions for the logic
    • Objects for the modularity
  • 静态类型(编译时检查),具有表达性强的类型系统(可自动推测类型,而无需为变量声明固定类型);

  • Scala源代码被编译成Java字节码运行在Java虚拟机上,可以无缝与现有的Java类库交互。

  • 并发性:Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。

  • 应用:服务、大数据应用、浏览器(Scala.js)。

安装

使用Coursier

使用系统软件仓库或安装包

不推荐:不能一次性安装所有工具。

Ubuntu (scala-2.11)

apt install scala  # 包括scalac、scala、fsc、scaladoc
安装sbt
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add
sudo apt update && sudo apt install sbt

Windows

winget install scala
winget install sbt

Scala程序

交互式编程

脚本

// class definition similar to JAVA
object ExampleClass {   // object is singleton class
   def main(args: Array[String]) {
      println("Hello, world!") // 输出 Hello World
   }
}
@main def main(args:Array[String]) = {...}  // scala3

基本语法

语句可以用分号(;)结束或换行符。

package com.runoob  // java package style
class HelloWorld
package com.runoob{ // c# Namespace style
	class HelloWorld    
}

导入依赖包:使用_代表包中所有可导入内容。

import java.util.*		
import org.apache.spark.sla._

import scala.jdk.CollectionConverters.*
val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq

流行的Scala库

数据类型

Scala与Java有着相同的数据类型,scala没有java中的原生类型。空值是 scala.Null 类型。

变量

变量声明
var myVar [: String] = "Foo"  // 变量
val myVal [: String] = "Foo"  // 常量

类型推断:根据初始化值推断变量类型,而无需指定变量类型。

类型转换

IntLong不能自动类型转换(ClassCastException),需要使用强制类型转换:

v.asInstanceOf[Type]

Any类型只向其实际类型转换,不能直接转换为其他类型;必须还原实际类型后执行强制转换。

运算符
  • 算术运算符:+,-,*,/,%
  • 比较运算符:==,!=,>,<,>=,<=
  • 逻辑运算符:&&,||,!
  • 位运算符:&,|,^,~,<<,>>,>>>>>>右移补零)
  • 赋值运算符:=,+=,-=,...

Scala中的类不声明为public,一个Scala源文件中可以有多个类。

class Point(xc: Int, yc: Int)  extends baseclass(args) {
   var x: Int = xc
   var y: Int = yc
   def move(dx: Int, dy: Int) {   }
}

所有类继承自Any,其子类包括AnyVal(值类型)和AnyRef(引用类型,类似于Java)。

Traits

Traits(特征) 相当于Java的接口。与接口不同的是,它还可以定义属性和方法的实现。

使用new关键字创建对象。

数组(Array)

import Array._
var array:Array[String] = new Array[String](3)
var array = new Array[String](3)
var myMatrix = ofDim[Int](3,3)  // 二维数组
var x = range(1,10,2)  //start stop step   Range

下标访问符为(),而非[]

泛型类型声明符为[],而非<>(容器类型)。

容器(Collection)

enter image description here

不可变对象

Scala默认使用不可变对象(scala.collection.immutable)。可以导入collection.mutable包使用可变对象版本。

import scala.collection.mutable
var s = mutable.Set()

修改对象的操作返回新对象。

List

Nil表示空列表。

Seq是List继承的特征(trait)。

Set

set(val)判断值是否在集合中。

无法按下标返回元素。

Map

var A:Map[Char,Int] = Map()
A += ('I' -> 1)  // add key-value
val colors = Map("red" -> "#FF0000", "azure" -> "#F0FFFF")
println(colors("red"))
for (c <- colors){
  println(c)  // iterate c is (key,value) tuple
}

Tuple

使用t._idx访问元组的元素(idx 1-based);

目前 Scala 支持的元组最大长度为 22。

Option

Iterator

运算符

:用于声明运算符与相邻对象的结合关系。

var a = List(1,2,3), c = List(4,5,6), item = 10
val b = a:+item  // append a with item
val b = item+:a  // prepend a with item: item::a
val b = a++c     // appended c to a
val b = a++:c    // prepend a to c: also a:::c

+运算符用于字符串(String)拼接。

:++ error.

Java容器

访问修饰符

方法和函数

Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。

方法定义语法

方法声明中,可以选择指定参数类型、参数默认值、返回值类型。

def sum(x: Int, y: Int = 0)[: Int]= x + y  // expression style
@Transaction
@throws(classOf[IOException])
def methodName ([arg1:Int, arg2 Double])[: Double] = // method style
{
   statements
   return [expr]
}
def matchState(x): Int=x match{...} // methods with single match

公开方法需要声明返回类型,局部或私有方法可以省略返回类型声明(: Int)。

无返回值,则声明: Unit(类似于 void)。

类似于Java,可以声明标注(Annotations),包括抛出异常(Java使用关键字throws抛出异常)。

method style

与多数编程语言类似,使用{}代码块表示函数体。

expression style

使用=运算符连接单句表达式作为函数体(类似于Lambda表达式)。

match表达式

函数对象

函数对象保存一个匿名函数定义,=>运算符用于分隔参数列表和函数体。

val f1 = (a: Int, b: Int) => a + b
val f2 = { (a: Int, b: Int) =>
  val sum = a + b
  sum  // return sum
}
闭包

匿名函数及其引用的外部作用域中的对象组成闭包。

factor = 5
val multiplier = (i:Int) => i * factor

注意函数返回值使用=,方法返回值使用=>

Map and Reduce

传递(匿名)函数对象作为mapreduce的参数。

val l = List(2, 5, 3, 6, 4, 7)
val sum = l.reduce((x, y) => x+y)
val sqrt = l.map(x => x*x)

模式匹配(switch-case)

def matchTest(x: Any): Any = x match {
      case 1 => "one"
      case "two" => 2
      case y: Int => "scala.Int"
      case _ => "many"
   }

正则表达式

import scala.util.matching.Regex

流程控制

条件

y = x match {
  case hd :: tail => hd + sum(tail)
  case Nil => 0
}

循环

for ( x <- iterable ){
  println(x)
}

异常处理

try {
    val f = new FileReader("input.txt")
} catch {
    case ex: FileNotFoundException =>{
        println("Missing file exception")
    }
    case ex: IOException => {
        println("IO Exception")
    }
} finally {
    println("Exiting finally...")
}

输入输出

Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File)。

  1. The Scala Programming Language.

Java的标准输入输出已经导入运行环境,可以直接调用函数如printprintln,无需添加包名(System.out)。

Scala项目

sbt是首选的Scala项目管理工具,其他工具包括antgradlemaven……

sbt项目

创建项目

sbt new scala/hello-world.g8  # Scala 3 project
sbt new scala/scala3.g8       # Scala 2 project

需要下载运行环境(包括从Maven仓库下载依赖库,视网络环境可能占用一定时间)。

项目结构
project-name
├── build.sbt  # sbt项目配置
├── project
│   └── build.properties
└── src
    └── main
        └── scala
            └── Main.scala  # 入口程序

项目配置

build.sbt

name := "HelloWorld"
version := "1.0"
scalaVersion := "2.13.8"  # *

*:项目可指定Scala版本而无需使用全局Scala版本。

开发环境

Coursier管理工具

Coursier命令行工具用于安装Scala应用程序和设置开发环境1

安装Coursier

==如果安装过程无法下载数据,首先检查网络状况,可能需要设置代理。==

Linux
CS_URL=https://github.com/coursier/launchers/raw/master/cs-x86_64-pc-linux.gz
curl -fL $CS_URL | gzip -d > cs && chmod +x cs && ./cs [options] setup [options]

将安装coursier(cs)到默认位置,以及安装以下全局命令行工具和JVM(安装过程会提示是否更新用户配置文件(.profile)中的环境变量,从而使相应工具全局可用):

安装完成后可移除下载的cs文件,使用安装版本的couriser/cs进行管理。

cs setup \
   --apps sbt-launcher,ammonite \ # 安装额外的命令行工具 
   --install-dir ~/.local/scala \ # 修改安装目录*
   --yes                          # 使用默认选项,取消交互

*--install-dir仅适用于cs以及scala工具,不适用于JVM安装路径。

Windows
$Uri="https://github.com/coursier/launchers/raw/master/cs-x86_64-pc-win32.zip"
Invoke-WebRequest -Uri $Uri -OutFile "cs-x86_64-pc-win32.zip"
Expand-Archive -Path "cs-x86_64-pc-win32.zip"
.\cs-x86_64-pc-win32.exe setup
get-command cs  # 重新打开终端执行

安装完成后,将自动添加Coursier目录C:\Users\USER\AppData\Local\Coursier\data\bin到路径环境变量中(如果未添加,可手动添加;如果不需要全局启用,可通过PowerShell $Profile设置)。

$env:JAVA_HOME="C:\Users\gary\AppData\Local\Coursier\cache\arc\https\github.com\AdoptOpenJDK\openjdk8-binaries\releases\download\jdk8u292-b10\OpenJDK8U-jdk_x64_windows_hotspot_8u292b10.zip\jdk8u292-b10"
$env:PATH="$env:JAVA_HOME\bin;C:\tools\scala;$env:PATH"

配置Coursier

coursier网络代理配置

Coursier的JVM不会自动使用系统的代理配置,需要将代理配置通过Java选项传递给JVM。

coursier '-J-Dhttps.proxyHost=127.0.0.1' '-J-Dhttps.proxyPort=7890' [args] #*
sbt '-Dhttps.proxyHost=…' '-Dhttps.proxyPort=…' [args]  #**

传递给java.net.HttpURLConnection的代理属性,HTTP同理。

*:PowerShell会将-XX解释为PowerShell命令的选项,需要使用''将其视为普通参数传递。

**:在环境变量JAVA_OPTS中配置(仅适用于sbt,不适用于coursier):

export JAVA_OPTS="$JAVA_OPTS -Dhttp.proxyHost= -Dhttp.proxyPort= ..."
仓库配置

仓库列表:~/.sbt/repositories文件(不存在则手动创建)记录所有可用仓库列表。

[repositories]
local          
maven-central   

模板配置文件:delta/repositories at master · delta-io/delta (github.com)

默认仓库为:

  • ivy2local:Ivy2本地仓库,位于~/.ivy2/local
  • central:Maven中央仓库,即https://repo1.maven.org/maven2

上述默认配置可通过环境变量COURSIER_REPOSITORIES修改(使用镜像仓库代替Maven中央仓库central)。或通过命令行选项-r,--repository指定额外仓库(可指定多次以包含多个仓库)。提供--no-default将不使用默认或通过环境变量配置的仓库。

export COURSIER_REPOSITORIES="ivy2Local|central|sonatype:releases|jitpack|http://"

sbt Reference Manual — Proxy Repositories sbt Configuration

sbt Reference Manual — sbt Launcher Configuration 3. Repositories Section

相应地,可在项目配置文件(优先级更高)中添加:

resolvers += "central" at "http://maven.aliyun.com/nexus/content/groups/public/"
externalResolvers :=
 Resolver.withDefaultResolvers(resolvers.value, mavenCentral = false)

镜像配置:sbt默认使用Maven中央仓库下载依赖,因此速度很慢。~/.config/coursier/mirror.properties(Linux)中配置代理转发规则(类似于Maven配置的mirrorOf)。

central.from=https://repo1.maven.org/maven2
central.to=https://mirrors.huaweicloud.com/repository/maven  #*

*:阿里云/163中央仓库镜像缺少部分Scala插件包,腾讯云下载文件可能校验和不一致,实测华为云镜像更新安装时不会报错。

该配置文件路径可通过COURSIER_MIRRORS环境变量或命令行选项-Dcoursier.mirrors=指定。

缓存路径

缓存用于存放不经常发生变化的依赖库,默认路径~/.cache/coursier/v1(Linux),配置COURSIER_CACHE环境变量或coursier.cacheJava选项覆盖默认路径。

  • coursier使用的缓存路径:传递--cache选项覆盖上述配置;
  • sbt使用的缓存路径:其位置可在sbt交互环境中使用show csrCacheDirectory查看。通过或传递-Dsbt.coursier.home选项来更改缓存路径。

缓存声明周期:以-SNAPSHOT结尾的包、Maven元数据等具有缓存周期(TTL,默认为24小时)。

管理Scala命令行应用程序

cs install <app:version> --install-dir DIR  # -> cs setup --apps <app:version>
cs list|update|uninstall|search <app:version>

默认安装位置:~/.local/share/coursier/bin,可通过配置修改;需要将安装位置加入PATH以便通过命令名启动应用。环境变量COURSIER_BIN_DIR|COURSIER_INSTALL_DIR以及Java属性coursier.install.dir可覆盖上述默认位置(优先级由高到低)。==命令行选项--dir,--install-dir具有最高优先级==。

管理JVM
cs java --available                # 列出可用Java版本
cs java --installed                # 列出已安装Java版本
cs setup --jvm 11                  # 安装Java环境*
cs java --jvm 11 --env             # 输出设置Java环境变量的语句,可调用eval执行
cs java --jvm 11 [options] args... # 使用指定Java(不存在则自动下载安装)**
cs java --jvm VER --setup          # 设置默认Java版本
cs java-home [--jvm ver]...        # 显示指定环境的JAVA_HOME

cs会首先检查系统是否自带Java并尝试使用该版本;如果没有,则自动安装AdoptOpenJDK 1.8。通过--jvm指定的Java即使与自带版本相同(如1.8)还是会额外下载安装,如果版本仅指定数字,默认安装Adoptium发布的JDK(adoptium,与AdoptOpenJDK标识adopt不同)。安装后会提示是否更新环境变量,环境变量决定命令行工具依赖的Java版本。

如果使用指定版本Java执行命令时,该版本不存在,则cs会自动下载安装相应版本Java。

Adoptium OpenJDK是从GitHub下载,可能需要使用代理

*--jvm-dir(指定Java安装路径)不可用。JVM安装路径可通过COURSIER_JVM_CACHEcoursier.jvm.cacheJava系统属性设置。

启动Scala应用

install命令会将安装的应用存放在上述路径下,因此通过命令名启动应用程序则是最近安装的版本。

app [options] args...        # 启动默认版本

如果要启动其他版本,使用cs launch命令。

cs launch <app:version> [cs_options] -- [app_args...] # app未经安装则直接从仓库源拉取
# cs launch scala:2.12.15
cs launch org.scalameta::scalafmt-cli:2.4.2
   -M,--main-class                       # 如果指定的库找不到主类,可通过该选项指定
   -java-opt -Dfoo=bar --java-opt -Xmx2g # 传递Java参数给应用

应用程序支持接收Java运行时参数,如"-J-Xmx2g";或通过环境变量设置,两种可混合使用。

export JAVA_OPTS="-Xmx2g -Dfoo=bar"

bootstrap将从指定的依赖库构造可执行的应用程序文件。

cs bootstrap org.scalameta::scalafmt-cli:2.4.2 -o scalafmt

bootstrap · Coursier (get-coursier.io)

配置应用程序通道

默认使用主通道,即io.get-coursier:apps

cs install --contrib app  # io.get-coursier:apps-contrib*
           --channel <custom-channel>

*appio.get-coursier:apps-contrib是官方维护的仓库通道。

管理Maven依赖项

解析依赖项
cs resolve io.circe::circe-generic:0.12.3
下载依赖项
cs fetch io.circe::circe-generic:0.12.3
         --classpath    # 输出依赖包的CLASSPATH,而不是仅列出下载路径
         --source       # 下载源码包而非标准包
         --javadoc      # 下载文档而非标准包
         --default=true # 同时下载标准包
Scala依赖库和应用程序
  1. lauris/awesome-scala: A community driven list of useful Scala libraries, frameworks and software. (github.com)

  2. Scaladex (scala-lang.org)

命令行工具

编译工具

sbt     # 进入sbt交互环境
[~]run  # 使用"~"可在文件发生更改时自动重新执行, 输入exit退出
sbt run # 直接编译运行

集成开发环境

集成开发环境可使用IntelliJ或VSCode。

VSCode+Metals

开发环境设置

设置下Metals语言服务器依赖库的仓库源。

{
    "metals.customRepositories": [
        "https://maven.aliyun.com/repository/central"
    ]
}
Metal项目设置

bloop

Scala3

新特性

New in Scala 3 | Scala Documentation (scala-lang.org)

参考资料