关于RIZAP的一切

关于RIZAP的一切

2018年做的不一定是最有价值,但一定最成功的事,就是参加了RIZAP。

我参加了三个月的课程,体脂率从26%减到18%,体重从79kg减到69kg,算是达到了预期目标。

什么是RIZAP?

RIZAP是日本的一家网红私人健身室,广告铺天盖地,广告都是一个风格:名人的Before&After。随着魔性的BGM,拖着肥肉的大叔大姐转进去,健美的身材转出来。100%迎合了现代消费者减肥的诉求。

RIZAP不仅在首都圈有大量的门店,在上海、台北、香港等城市都有分店。

为什么去RIZAP?

工作后吃得好了运动得少了自然就胖了,特别是近年升职后责任重了,睡得也不好,这些都是肥胖的原因。虽然BMI还没到肥胖线,但是很明显小腹突起,双下巴也出来了。身体检查有轻微的高血压高血脂迹象,睡眠鼾声也大了,减肥势在必行。

为什么选择RIZAP?

  • RIZAP是宣称肯定有效果,不满意可以退钱,成功例子也多。我以前也上过健身房,但是没什么成果,基本乱练。去RIZAP有点像手游的课金和USJ的Express票,社会人经不起折腾,用钱换时间。
  • 换了工作,工资有大幅提升,比起成本更注重效果。
  • RIZAP门店多,不会有预约不上的问题。RIZAP虽然贵,其他私教也不便宜。
  • RIZAP的服务质量是真的好。
  • 好奇。RIZAP在EdTech领域算是一个网红,非常好奇他们是怎样打造这个服务的。

RIZAP的缺点

就一个字:!真的是非常贵!

RIZAP是真的贵,入会费5万日元(介绍的话可以免入会费,我是推友介绍的),教学费每月15万(8节课)左右,而要是买他们家的补剂的话,就更贵,比如说30包的蛋白粉就要约3万日元。我自己是买了每月7万日元的全家桶补剂,加起来三个月约70万左右。要知道我在日本读两年master学位的学费加起来也才70万。

RIZAP的课程

课程长度

进去RIZAP之前,会有一次咨询。主要就是明确想要什么样的身体,然后RIZAP给出他们的方案。我自己的目标很明确,就是减脂,把三高的潜在风险减掉,回到健康的身体。

RIZAP风格的广告有一个误导人的地方是,那些从肥仔肥女变成型男俏女的明星参加的都是起码八个月左右的课程,他们不止减脂还有一定的增肌才有这样的身材。一般人参加的两三个月的课程其实只够减脂。我选择的也是三个月,毕竟贵啊。

饮食限制

RIZAP有一个著名的地方就是“糖质制限”,也就是低碳水化合物饮食。每天摄取的碳水化合物标准是不超过50g,其实有点接近生酮了。然而生酮虽然很有效,但其实有一定的危险性的,健身圈一般不建议超过三个月的生酮。我自己课程完了以后就把生酮换成一般的低碳水了。

RIZAP入会时会发一个手册,告诉你什么可以吃什么不可以吃,大体上跟生酮差不多。RIZAP还有个app,不太好用的app,用来跟踪会员的所有饮食。功能上类似myfitnesspal,但不如myfitnesspal好使,至少不能扫条形码,我自己是记了一个月习惯以后就只发照片了。教练会每两三天评价一次,给点饮食的建议。这些建议对于进入生酮时帮助很大,但到后期习惯就比较鸡肋。因为我每天吃的东西都自己做都差不多,怎么看都不会超糖。减脂主要靠的还是热量赤字,吃得多就是只吃蛋白质也没用。

其实RIZAP也不只限糖,对于增肌的人就变成限脂肪了。不过体脂肪率低到可以增肌的人估计都不会去RIZAP就是了。

课程内容

一周两节课,基本都是大肌群胸背腿肩,一天胸肩一天背腿,然后结束前做卷腹。教练说课程因目的是差很远的,比如男女之间就有大差,毕竟很多女生不想要胸肌。

项目基本是自由重量,器械偶尔辅助。大重量和多组数轮流做。

RIZAP的教练水平真不是盖的,至少我见到的每个男教练都有很明显的训练痕迹,据说他们每个工作日都会互练一个小时以上。我的教练宫坂也是,他还参加地方健美大赛。每个动作做示范时他都让我摸一下他的肌肉理解一下肌肉的感受度,让我这种健身小白好生羡慕。

其实两节课训练量还是不足的,我后期平台期基本都会在家里另外练两天。虽然RIZAP也有器械,预约制免费用,但还是家里舒服。其实有哑铃胸背腿肩都可以练了,就是不敢上大重量就是。家里练的项目问教练都会教,跟教练学习还是获益良多的。

收益

  • 体脂率和体重都达到了预期目标,外表也回到毕业时水平甚至更好。
  • 健身算是入门了,现在看健身方面的资料看得进去了,去自由重量区也敢操杠铃了。
  • 养成了健身和良好的饮食习惯,减少了未来的健身风险。

下一步

  • 继续刷体脂,目标是这个2019第一季度刷进13%。
  • 刷好了就增肌。
  • 把减肥心得总结一下。

2018订阅的服务

  • Grammarly
    • 英文语法检查SaaS
    • 打折后$83.97/year。标准价格非常贵,年末的黑五销售比较给力。注册后一直不升级的话自动化销售会给你coupon,相对来说比较便宜。
    • 要是公司给钱买的话就续下去。否则的话再等减价。
  • スタディサプリEnglish
    • 英文在线学习,主要是托业。
    • 16,680円/6 months
    • 内容质量非常高。不过考完Toeic就不续了。
  • dマガジン
    • 包月杂志
    • 432円/month
    • 性价比非常高,有我每个月都要读的《東洋経済》《Tarzan》等杂志。肯定续。
  • MoneyForward Me
    • 家計管理SaaS
    • 480円/month
    • 比较实用,能跟非常多的服务连接数据。可以分组,夫妇的钱都能管理。续。
  • Amazon Music Unlimited Family Plan
    • 包月听歌
    • 1480円/month
    • 本来是用的个人版,因为夫人买了个Bose就升级了。不贵,应该续。
  • Amazon Prime Family Plan
    • 不用说,肯定续
  • Netflix 4K
    • 1800円/month
    • 买了新4K电视于是升的4K,无特别感觉。续是打算续的,打算降回去。本来Netflix看的也不算多。
  • Cocoro Video
    • 夏普电视专有的VOD影视
    • 540円/month
    • 每个月给540积分,一般够看一部电影。特点是高清,而且上线比其它厂早一个月左右。观望。
  • Times Car Plus
    • 租车服务
    • 1030円/month
    • 跟CocoroVideo有点像,也是OnDemand服务的Subscription版。每个月交会费返还1000点。主要用来去Costco。续。
  • Costco
    • 会员制超市
    • 4,400円(税抜)/year
    • 便宜,而且搬家后更方便去了。续。
  • MyFitnessPal
    • 健身系SaaS
    • ¥6,000/year
    • 用来记录每天饮食的营养,年末刚开通,用起来还行。观望。

Programming in Scala [6]

读书笔记(6)

Chapter 7 Built-in Control Structures

Scala的if, for, try, match都有返回值。

Scala的if

val filename =
  if (!args.isEmpty)
    args(0)
  else
    "default.txt"
这种写法有两个优点:
  1. 更短
  2. 使用val而不是var
不带elseif也有返回值,会返回Unit类型的()。当一个值的类型转换成Unit,关于值的所有消息都会丢失。所以()不表示任何值。

Scala的while

var line = ""
do {
  line = readline
  println("read: " + line)
} while (!line.isEmpty)
Scala的whiledo-while跟Java里的差不多,然而whiledo都返回不含任何值的()。这虽然有悖纯函数语言的哲学,但是方便了用户用命令式语言风格写出更具可读性的程序,比如一些算法。而有些算法虽然用while也能写,但是用递归等函数语言风格的话可能会更清晰,比如算最大公约数的算法:
def gcd(x: Long, y: Long): Long =
  if (b == 0) a else gcd(b, a % b)
总的来说,更推荐用函数风格来写程序,因为while不返回值,需要引入var

Scala的for

最简单的用法就是循环一整个collection
val fileHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
  println(file)
这种写法能循环所有collection,不只数组(必须实现了scala.Iterable这个trait的<-方法)。比如说循环一个Range类型。
for (i <- 1 to 5)
  println("Iteration " + i)
过滤
for (file <- filesHere; if file.getName.endsWith(".scala"))
  println(file)
for (file <- filesHere)
  if (file.getName.endsWith(".scala"))
    println(file)
因为for有返回值,所以for是一种expression。 可以加入更多的判断条件。
for (
  file <- filesHere;
  if file.isFile;
  if file.getName.endsWith(".scala")
) println(file)
为了更可读,可以把()换成{}。这样就可以省略掉分号;
for {
  file <- filesHere
  if file.isFile
  if file.getName.endsWith(".scala")
} println(file)
嵌套循环
def fileLines(file: java.io.File) =
  scala.io.Source.fromFile(file).getLines
def grep(pattern: String) =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
    line <- fileLines(file)
    if line.trim.matches(pattern)
  } println(file + ": " + line.trim)
grep(".*gcd.*")
中途赋值(Mid-stream assignment)
def grep(pattern: String) =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
    line <- fileLines(file)
    trimmed = line.trim
    if trimmed.matches(pattern)
  } println(file + ": " + trimmed)
grep(".*gcd.*")
例子中的trimmed = line.trim的效果类似val,只是不用标明val。减少了一次算trim的重复开销。 生成新collection
def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
  } yield file
这里在循环体之前用了yield,生成了新的collection。 当使用yield的时候,实际上是如下的结构: for 循环判断语句 yield 循环体 yield必须放在循环体之前。(跟ruby和C#等放置的位置不同)

Scala的try

Scala的try跟其他语言的的异常处理差不多。 抛出异常
throw new NullPointerException
val half =
  if (n % 2 == 0)
    n/2
  else
    throw new Exception("n must be even")
  • 用法跟Java差不多。
  • 技术上来说,throw返回了类型Nothing。这使得上面例子的写法可以成立。
捕获异常
try {
  doSomething()
}
catch {
  case ex: IOException => println("Oops!")
  case ex: NullPointerException => println("Oops!!")
}
  • 写法跟其他语言类似。
  • 这里用了一种叫模式匹配(pattern matching)的方法,后面会提到。
  • 上面的异常处理会按顺序从上往下检查。如果无法捕获,再往外层抛出。
  • 不像Java,你无需处理已检查的异常。
finally语句 跟Java几乎一样。 生成值 try-catch-finally结构是有返回值的。比如:
val url = try {
  new URL(path)
}
catch {
  case e: MalformedURLException =>
    new URL("http://www.scala-lang.org")
}
这里有个要注意的地方,如果在Scala的finally的结构体内中途显式return返回值,这个值会覆盖之前的返回值,比如:
def f(): Int = try { return 1 } finally { return 2 }
def g(): Int = try { 1 } finally { 2 }
这里f()返回2,而g()返回1。

Scala里的match

Scala里的match,相当于其他语言的switch。不过Scala的match能让你匹配任何模式(详见Chapter 12)。
val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
  case "salt" => println("pepper")
  case "chips" => println("salsa")
  case "eggs" => println("bacon")
  case _ => println("huh?")
  • 这个例子依次把firstArg匹配"salt","chips","eggs",而通配符_则匹配默认值。
  • 不像Java里的switch,只能匹配整数类型和枚举常量,Scala的match能匹配所有常量。(虽然书里没提,实际上Java7的switch也能匹配字符串,是通过语法糖实现的,方法是先计算变量的.hashCode(),再比较常量的哈希值,最后用equals按值比较1。枚举类型本来就是语法糖,实现方法应该也类似。)
  • break不可用。(这东西本来就是语法盐。近代的语言基本都不保留了。)

没有breakcontinue也能活

有点意外的是,Scala保留了while,却非常前卫地完全去掉了breakcontinue。 书里提的例子就是用递归或者用其他函数式的方法去避免用breakcontinue,介绍得不怎么详细。有空做一下深入调查再写。
  1. http://javarevisited.blogspot.jp/2014/05/how-string-in-switch-works-in-java-7.html 

Programming in Scala [5]

读书笔记(5)

Chapter 6 Functional Objects

这章用了一个叫ChecksumCalculator的实例介绍Scala里的OOP。
  • Scala的理念里valval只是实现目的的工具,没有分轻重。
  • 使用不可改变的对象,不用关心值的改变,更容易在不同的计算间分享数据,无论是串行还是并行。
  • Scala跟Java一样,也有privateprotected
  • 跟Java固定,Scala的对象里也可用this引用自己。
  • Scala的identifiers(标识符)有如下两种:
  • alphanumeric identifer: 就是以非数字起始的,以大小字英数字与下划线组成的标识符。虽然符号$也算字符,但只供Scala编译器内部使用。虽然$也能编译,但用户的程序里不应含$,否则会跟Scala编译器生成的标识符冲突。(Java的标识符的字符集 = Scala的alphanumeric identifer的字符集 + $)。
  • operator identifier: 以一个或多个操作符字符组成。这里的操作符字符指的是7位ASCII里字母,数字,和_ ( ) [ ] { } . ; , " ' ‘中的任一个字符以外的字符。例如:->。Scala编译器会自动把:->转成合法的Java标识符:$colon$minus$greater
  • mixed identifer: 以alphanumeric identifier起始,后面加一个下划线和一个operator identifier。比如:vector_+
  • literal identifier: 被包住的任意字符串,可用于在使用跟Scala保留字相同名字的时候,比如yield是Scala的保留字,在调用Java的Thread.yield()时,可以写成Thread.‘yield‘()
  • Scala支持任意长度的标识符,这会引起小问题。比如x<-y在Java里会被识别为x < - y四个符号,而在Scala则会被识别是为x <- y三个符号。要分开它们,只要在<-间插一个空格。
  • implicit关键字告诉编译器如何进行自动类型转换。
  • 要小心设计自动类型转换,因为它会降低程序的可读性。(这种坑太多了,可看《Java Puzzlers》等,一抓一大堆。)

Programming in Scala [4]

读书笔记(4)

Chapter 4 Classes and Objects

变量作用域

  • 跟Java一样,Scala也是用花括号{}定义新的变量作用域(scope)。要在scope之外引用变量,只能通过继承/import/成员变量。
  • 一旦定义了变量,不能在同一域里用同一名字定义变量
val a = 1
val a = 2  // 编译错误
println(a)
val a = 1;  // 这里";"是必须的
{
  val a = 2  // 编译通过
  a
}
println(a) // 输出1, 因为花括号内的a跟外面的a无关。
  • 第二段代码在Scala能编译通过,然而在Java里是不允许括号内部的变量与外部的变量同名。在Scala里此括号外部的同名变量在括号内部是不可见的。
  • Scala这么做是为了创造更方便的交互环境。
  • 在interpreter里,Scala可以重复定义一个变量。
scala> val a = 1
a: Int = 1
scala> val a = 2
a: Int = 2
scala> println(a)
2
  • interpreter可以这样做的原因是它会自动为每一段语句创建一个嵌套的花括号。上面的代码相当于
val a = 1;
{
  val a = 2;
  {
    println(a)
  }
}

分号推定

  • Scala里句末的分号通常是可省略的。
  • 只有在你要在一行里写多句语句时,分号才是必要的。
  • 这个特性有时会产生有违你本意的结果,比如
x
+ y

Scala 会把这段分成两行:x+y。要两行算x+y,可用

(x
+ y)

x +
y +
z

这样的写法。

如果下面三个条件满足一个或以上,该行就不会被判断为已完结

  • 该行用.infix-operator(后述)等不合法的语法结尾。
  • 次行的开头的词不能用作开头。
  • 该行的括号()[]没完结。因为它们内部不允许多行。

单例对象

又是单例对象。

  • 单例对象与其伴生类可以互相访问私有成员。
  • 对Java程序员来说,理解单例对象最好的方法就是:它是Java里所有静态方法的集合体。用法跟Java里的也差不多。
  • 跟Java最大的不同是,单例对象不能用new初始化,所以它们的构造函数是不能带参数的。
  • 单例对象的实现方式是通过静态变量,实际上它们的初始化方式是跟Java的静态成员差不多的。单例对象只会在第一次被访问时初始化。
  • 单例对象适合用来放置工具函数。(比如之前关于List的那章,就用List.apply这个工厂函数生成新的不可修改对象)
  • 没有伴生类的standalone object还可以用来定义一个Scala程序的入口,书里的4.9小节有介绍,这里省略。

Chapter 5 基本类型和操作符

  • 虽然Scala里所有值皆是对象,但为了运行效率Scala编译器实际上会把一部分值用Java的原始类型表达。
  • Scala的基本类型:ByteShortIntLongCharStringFloatDoubleBoolean。(本来想找官方文档的,没有。书里的Table 5.1)
  • 除了Stringjava.lang包里,其他的基本类型都是scala包里。比如Int的全名是scala.Int
  • scalajava.lang会被自动引用到所有的Scala源文件里。你只用使用缩写,比如Int
  • 这些类型的取值范围跟Java的原始类型一致,这是为了能在转换成字节码时优化成Java的原始类型。
  • 这些基本类型都有全小写的别名,跟Java的原始类型一样,但请跟随社区的选择,别使用。(我用2.11.7试了下,已经不能使用了)。

Literals(符码)

  • Literal指的是我们在代码里对常量的在文面上的表达方式。
  • Scala的literals跟Java的几乎一样。
    • 整数(与Java一样)
    • 浮点小数(与Java一样)
    • 字符(与Java一样)
    • 字符串(与Java一样,唯一的不同,就是Scala支持多行字符串符码),比如:
println("""Welcome to Ultamix 3000.
             Type "HELP" for help.""")  // 没有对齐
             
println("""|Welcome to Ultamix 3000.
           |Type "HELP" for help.""".stripMargin) // 通过`stripMargin`方法对齐

用的是""",跟python与ruby类似。

操作符是方法

  • 反复说了。Java里的操作符是语法,Scala里的操作符是方法。既然是方法,自然也可重载。
  • 当不使用.()来调用一个方法时,使用的是下面三个操作符记法的其中之一:
    • prefix(前缀):操作符放于值之前,比如-7里的-
    • postfix(后缀):操作符放于值之后,比如7 toLong里的to Long
    • infix(中缀):操作符放于值之中,比如7 + 2里的+
  • 操作符与值之间的空格不是必须的,比如1+21 + 2(1).+(2)1.+(2)是完全一样的。(当然了,这只在不存在歧义时才成立)。
  • 操作符记法可适用于任何方法,比如String的indexOf,如str indexOf 'o'
  • 当你使用中缀记法来调用一个多于一个参数的方法时,要用括号,如str indexOf ('o',5),相当于str.indexOf('o', 5)
  • 与中缀记法不一样,前缀与后缀记法是unary的,也就是一元操作符。
  • 前缀记法是调用特定方法的简略写法,比如-2.0里的-,实际调用的是一个带unary_前缀的方法:unary_-。Scala会把-2.0自动转换成(2.0).unary_-
  • 只有+,-,!,~四个特殊字符能成为前缀方法。像unary_*一类的方法是不能用前缀记法调用的(但可正常调用)。
  • 后缀就比较简单了,没有任何参数的方法都可以用后缀记法调用。

Scala里的操作符

虽然本质不一样,用起来跟Java大同小异,只写比较关心的部分。

  • Scala里的浮点小数求余用的不是IEEE 754标准,要用专用的IEEEremainder方法。(Java好像也是一样的。)
  • Scala里的逻辑与和逻辑或跟Java一样,是可以短路的。由于Scala的逻辑与和逻辑或实际上是方法,所以其实Scala是可以做到选择性地不计算参数里的函数的。这是通过Scala的延迟计算特性来实现的,具体后述。

对象比较(Object equality)

  • Scala里的对象比较可以比较所有对象,不仅是基本类型。如:
scala> 1 == 2
res24: Boolean = false
scala> 1 != 2
res25: Boolean = true
scala> 2 == 2
res26: Boolean = true

scala> List(1,2,3) == List(1,2,3)
res27: Boolean = true
scala> List(1,2,3) == List(4,5,6)
res28: Boolean = false

scala> 1 == 1.0
res29: Boolean = true
scala> List(1,2,3) == "hello"
res30: Boolean = false

scala> List(1,2,3) == null
res31: Boolean = false
scala> null == List(1,2,3)
res32: Boolean = false
  • 在Java里,对于原始类型,==比较值;对于引用型变量,==比较的是两者是否引用JVM的heap里的相同对象。
  • 在Scala里,==实际是调用了equals,只要实现了基于内容比较的equals,就能比较两者内容是否一致。
  • Scala里有Java式的比较函数的实现eqne,后述。

操作符优先级(operator precedence)

  • Scala里没有操作符优先级,但有方法优先级。
  • 方法的优先级是通过方法名的首字符判断的。比如说,*开头的方法比+开头的方法优先,所以2 + 2 * 7 = 2 + (2 * 7)
  • 具体优先级如下:
  • 当优先级一样时,Scala通过方法名的未字符判断其关联性(associativity,就是判断操作数与其左侧还是右侧的操作数组合)。一般的方法是从左到右结合,当方法名以:结尾时,从右到左结合(前面关于String的一章有提过)。所以,a ::: b ::: c相当于a ::: (b ::: c),而a * b * c相当于a * (b * c)
  • 千万别为了炫耀自己对于操作符的理解而省略括号!!!你应该默认所有的程序员只知道*,/,%优先于+,-。除此之外的所有运算符都应该加括号!!!

关于这一点,我个人深有同感。我自己在给后辈做code review时,有违这个原则的,都打回去要求写括号。Scala作为一门为了简洁可以去掉句末;的语言,作者却要求明确写出括号注明优先级,可以看出这个括号是不可少的。究其原因,除了作者所述的原因外,我觉得还有一点:不同的语言的操作符优先级不一定一样。

比如说,PHP里,赋值运算符比比较运算符优先级高,而在JavaScript里,比较运算符比赋值运算符优先级高。在某些脚本语言里,逻辑与和逻辑或是同级的,如bash。更极端的甚至有语言是没有运算符优先级的,比如LISP那堆。要填上不同背景的工程师之间的鸿沟,括号是最优雅简单的选择。如果你觉得括号省去也没啥问题,很可能是你学的语言还不够多。

未完待续。

Programming in Scala [3]

读书笔记(3)

Chapter 3 Next Steps in Scala

类与单例对象

构造类

  • Scala里的构造函数比简洁。
class FancyGreeter(greeting: String) {
    def greet() = println(greeting)
}
val g = new FancyGreeter("Salutations, world")
这里的greeting是私有的不可修改量。
  • 所有的包含在花括号内的非函数或变量定义的部分,都是构造函数。称之为primary constructor
class RepeatGreeter(greeting: String, count: Int) {
    def this(greeting: String) = this(greeting, 1)
    def greet() = {
        for (i <- 1 to count)
            println(greeting)
    }
}
val g1 = new RepeatGreeter("Hello, world", 3)
g1.greet()
val g2 = new RepeatGreeter("Hi there!")
g2.greet()
  • Scala也支持多个构造函数。但是必须选定一个primary constructor,其他的是auxiliary constructor
  • auxiliary constructor的名字必须是this,必须无返回值,第一个参数必须是同一类内的其他构造函数。

单例对象

  • Scala不允许类内有静态变量或静态方法。
  • 取而代之,Scala引入了singleton object
  • singleton object不能被,也不应该用new创建实例。它会在第一次被引用时被创建,且只有一个实例。
  • singleton object的名字可以与类相同,这个singleton object被称为这个类的companion object(伴生对象)。
  • 只有object没有class的话,叫stand-alone object
实例: WorldlyGreeter.scala(非脚本,故用驼峰式命名。实际上不像Java,Scala的文件名跟内容没关系,只是推荐像Java一像用跟内容类一致的文件名。)
// The WorldlyGreeter class
class WorldlyGreeter(greeting: String) {
    def greet() {
        val worldlyGreeting = WorldlyGreeter.worldify(greeting)
        println(worldlyGreeting)
    }
}
// The WorldlyGreeter companion object
object WorldlyGreeter {
    def worldify(s: String) = s + ", world!"
}

traitmixin

  • Scala的trait可以包含非抽象方法,而Java的interface就只能包含抽象方法。所以Java里是实现interface,而Scala是扩展trait
  • Java跟Scala的类都只能继承自一个父类。
  • Java的类可以实现0~无限个interface,Scala的类可以扩展0~无限个trait
  • implements并非Scala的关键字。
  • 不同于Java,Scala里重载一个函数要有override关键字。
  • 不同于Java,Scala可以在初始化时混合trait
混合例子
trait ExclamatoryGreeter extends Friendly {
    override def greet() = super.greet() + "!"
}

val pup: Friendly = new Dog with ExclamatoryGreeter
println(pup.greet())
  • 这里创建的类叫synthetic class,意为“编译器创建的类”。(应该是编译时创建而非运行时)

Chapter 4 Classes and Objects

  • A class should be responsible for an understaandable amount of functionality. (不知道怎么译好)
  • Scala里定义一个值的时候必须初始化。
  • Java里的类变量是可以不初始化的,它们会被根据类型被自动初始化对应的值(数值就是0或0.0,布尔值是false,引用则是null,等等)。Scala里要获得同样效果可以用_,比如var x: Int = _
(文章里还介绍了一个关于unreachable的例子,不难,割爱掉。)

4.2 Mapping to Java

感觉这一小节挺重要的。
  • 如前所述,Scala的类与对象等概念跟Java并非完全一一对应的。
  • Java的所有对象的内存分配都在JVM的heap里。比如说Java里的String,不管是否本地引用,都会放在heap里。如果是在本地创建的String,这个String的引用是放在stack里,而String的本体则放在heap里。
  • 相反地,由于Java里的int不是对象,而是原始类型,所以不分配在heap。不过,Java里提供了一个不可修改的int的wrapper,也就是java.lang.Integer,跟String一样,它也是分配在heap里。Java里区分原始类型跟wrapper的原因是为了优化性能(当真???)。
  • 为了让这两个概念没这么难用,Java 5里引入了autoboxing,也就是自动装箱,类似的还有自动拆箱。(Java基础,不说了)
  • 再看回Scala。Scala里所有值都是对象,包括数字。
  • Scala里的String跟Java一样,也是放于heap里,实际上Scala的编译器会创建一个java.lang.String
  • 然而原始类型就有些复杂了。编译器会自己优化,自动选择intjava.lang.Integer。实际上Scala会频繁的自动装箱和拆箱。
  • 为免歧义,此书把所有reference(引用),定义为“只有确定会在Java运行层面里生成引用才叫引用”。String在Java里必然是引用,而Int则有可能是引用(java.lang.Integer的时候),有可能不是引用(int的时候)。
  • 同样的,本书的unreferenced,只用于“确定Java层面上没有引用”的情况(被垃圾回收)。这里会使用unreachable来表述Scala的同样情况,Scala抽象层的所有对象都可以是unreachable。(这书真严谨,好喜欢)
  • 举个例子,如果一个Scala的Int定义在本地,且用原始类型int实现。当方法返回时栈帧(stack frame,JVM基础)弹出,这个变量会变成unreachable。而实际上因为不存在Java对象,它并非unreferenced

Fields and methods

class ChecksumCalculator {
    private var sum = 0
    def add(b: Byte) { sum += b }
    def checksum: Int = ∼(sum & 0xFF) + 1
}
  • 如果方法前没有=,会返回Unit类型,因为Scala会把任何类型转换为Unit
scala> def g { "this String gets lost too" }
g: Unit

scala> def h = { "this String gets returned!" }
h: java.lang.String
未完待续。

Programming in Scala [2]

读书笔记(2)

Chapter 3 Next Steps in Scala

带类型数组

Scala里的用参数创建实例(parameterize)的方法:

val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ","
greetStrings(2) = "world! \n"

for (i <- 0 to 2)
  print(greetStrings(i))
  • 这里greetStrings的类型是Array[String]。(类似Java5引入的泛型。)
  • 使用val后,不能再修改引用,但引用的对象可以修改。
  • 跟Java不同,泛型使用方括号[],下标引用使用圆括号()
  • 0 to 2里的to是方法名,相当于(0).to(2),它返回一个Scala的包含0,1,2iterator。Scala里的+,-,*,/都是方法名。
  • 在Scala里,Array也是类。当在实例后面使用(),Scala会调用一个叫apply的方法。greetStrings(i)相当于greetStrings.apply(i)。不止Array,任何实例后面接()都会调用对应的apply
  • 类似地,greetStrings(0) = "Hello"相当于greetStrings.update(0, "Hello")
  • 不像其他语言,上述的这种变换在Scala里不会明显影响性能。Scala编译器会尽量把他们优化成Java的本地数组或者原始类型(primitive type,像int一类)。

关于List和Tuple

  • 如上例所示,Scala的Array是同一类型的值的可修改的序列。像上述的Array[String],虽然长度不可修改,但内容可修改。所以Array是可修改的。(为避免混乱,本笔记系列会把所有mutable都译成可修改,反之immutable都译成不可修改)
  • 比Array更适合函数语言的,是List,它不可修改。它也是同一类型的值的序列。跟Java的java.util.List不一样,Scala的List,也就是scala.List,它是总不可修改的。它是为函数风格编程设计的。

List的示例

val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour

val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
  • List的创建没用new,因为它调用的是List的伴生对象(companion object,后述)的apply方法。
  • 两个List相接,用:::。它用于把一个List接到另一个List的前面,也就是prepend
  • 在Scala里一般的方法是从左到右的,比如a * b相当于a.*(b)。而:结尾的方法则反之,a ::: b相当于b.:::(a),所以说是把a接于b的前面
  • :: 念“cons”。cons用来把一个元素接到一个List前面,与:::同理。
  • List是没有append的,因为它的时间复杂度是O(n)。

题外话:关于List的时间复杂度

自己总结了一下Scala的List的一些性能资料12

  • List是为后入先出(LIFO)优化的,类似Stack。如果要使用其他访问方式,别用List。
  • List只有prepend,访问链首or链尾三个操作是O(1)的,其他如随机访问,lengthappendreverse都是O(n)的。
  • Java的List的话则有两个实现ArrayListLinkedList,伪代码3如下
public ArrayList<T> {
    private Object[] array;
    private int size;
}
public LinkedList<T> {
    class Node<T> {
        T data;
        Node next;
        Node prev;
    }
    private Node<T> first;
    private Node<T> last;
    private int size;
}

可以看出来,随机访问的话,ArrayList快,增删的话,LinkedList比较好4

Scala的Tuple

  • 跟List一样,是不可修改的。
  • 跟List不同,Tuple的各个值类型是不一样的。
  • Tuple非常适合同时返回多个值。(Python等语言也可以,非常实用。Java的话只能用类似JavaBean或直接修改输入,超级恶心)
val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)
  • 可以直接用()生成。Scala会自动判断类型,这里是生成了一个可容纳两个值的Tuple2[Int, String]
  • 不可以像List那样通过像pair(1)一样的下标访问的方式使用,因为Tuple各个值类型不一样。只能通过像pair._1一样访问
  • 数据序号从1开始,Tuple最多只能到Tuple22,容纳22个值。这个数字其实是任意的。

Set与Map

  • Scala里的List总是不可修改的,Array总是可修改的。然而Set与Map则同时有可修改与不可修改两套实现。
  • 无论Set或Map,它们的可修改不可修改版都是从同一个trait继承出来的两个subtrait,然后再继承成类。

这是可修改Set的例子:

import scala.collection.mutable.HashSet

val jetSet = new HashSet[String]
jetSet += "Lear"
jetSet += ("Boeing", "Airbus")
println(jetSet.contains("Cessna"))
  • HashSet要事先import。
  • ("Boeing", "Airbus"),这不是Tuple,而是相当于jetSet.+=("Boeing", "Airbus")。要传入Tuple的话,要使用jetSet += (("Boeing", "Airbus")),双圆括号。

这是不可修改Set的例子:

val movieSet = Set("Hitch", "Shrek")
println(movieSet)
  • 这里用了scala.collection.Set的伴生对象的工厂方法,这个对象是自动import的。

这是可修改的HashMap的例子:

import scala.collection.mutable.HashMap

treasureMap += (1 -> "Go to island.")
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (3 -> "Dig.")
println(treasureMap(2))
  • 1 -> "Go to island."相当于(1).->("Go to island."),相当于生成一个两个元素的Tuple
val romanNumeral = Map(
    1 -> "I",
    2 -> "II",
    3 -> "III",
    4 -> "IV",
    5 -> "V" )
println(romanNumeral(4))
  • 跟不可修改的Set差不多,也是工厂方法。这个工厂方法就是Map(...),也就是Map.apply(...)

未完待续。

  1. http://www.scala-lang.org/api/2.11.7/index.html#scala.collection.immutable.List 
  2. http://docs.scala-lang.org/overviews/collections/performance-characteristics.html 
  3. http://stackoverflow.com/questions/10656471/performance-differences-between-arraylist-and-linkedlist 
  4. https://docs.oracle.com/javase/8/docs/api/java/util/List.html 

Programming in Scala [1]

读书笔记(1)

Chapter 1 A Scalable Language

进化的语言

  • Scala能够方便的把自定义的类型封装得像本地类型一样。如BigInt
  • Scala能够把方法封装成本地语法一样。
    • Scala里函数都是对象,可以被继承。
  • Scala面向对象,Scala比很多语言在面向对象上走得更远。
    • Scala里有traits,类似Java的interface。Scala的方法对象可以通过mixin composition构建,类似于在对象上面打补丁。(其实Ruby的对象也常用类似方法构建,Javascript里也常用mixin技巧)。
  • Scala是函数语言。
函数语言有两大中心思想。1
  • 函数是first-class变量,地位类似整数与字符串。
  • 函数语言的操作,输入与输出要能构成映射关系,不可直接修改输入的数据。
    • Java里的字符串就是不可修改(immutable)的。只考虑字符串类型的话。Java是函数语言,而Ruby不是。不可修改的数据结构是函数类型的基石。
    • 也就是说,函数语言的方法不会造成side effects(函数副作用),它们只能通过输入与输出跟外部打交道。
    • 如果程序中任意两处具有相同输入值的函数调用能够互相置换,而不影响程序的动作,那么该程序是referentially transparent(引用透明的)
    • 函数语言鼓励使用不可修改的数据和引用透明的方法,有些函数语言甚至要求必须遵守,而这在Scala里是可选的。

为何选择Scala

  • Scala是兼容的。Scala运行在JVM(其实.net也可),大量使用了Java库。
  • Scala语法间洁。
  • Scala支持类型推断。
  • Scala的语言比较高级(high-level,相对于Java)。(文中举了用一个predicates代替传统枚举的例子,predicate指的是返回Boolean型的函数)
  • Scala是强类型的。
    • 很多人觉得这是缺点,作者认为是优点。(同感)
    • 强类型可以在编译期间确认某些运行时错误不会出现。
    • 安全的引用。(Java里经常遇到的,可以方便地改掉同一属性or函数or类的名字/引用)
    • 可以方便地生成文档。
    • 虽然Scala是强类型,但没有Java那么烦。比如声明一个带泛型的HashMap,在Java的话两侧都要写上泛型的类型,Scala只要写单侧。

Scala的起源

  • 直接看维基,不总结。

Chapter 2 First Steps in Scala

关于语法,不总结,只整理有意义的内容。Step 1~5跳过。
  • Java,C++和C的那种用可变的参数实现的循环风格,叫imperative style。而Scala的循环风格更为functional style
  • Scala里也可以写imperative stylefor,其实实现方法跟Java的不一样,Scala的for为循环体的每个循环都创建了一个新的不可变的arg并初始化。
// Scala
for (arg <- args)
    println(arg)
// java
for (String arg : args) {
    System.out.println(args);
}

Chapter 3 Next Steps in Scala

  • 提到上面的循环的例子。Scala用的是val,所以是functional style。Java用的是可变的状态(相当于var),所以是imperative style
  • Scala不是纯函数语言它是imperative/functional language,就是混合的语言。
未完待续。
  1. 关于函数语言还有很多可参考的资料:http://www.jianshu.com/p/390147c78967