xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

Basic Scala #180

Open xxleyi opened 4 years ago

xxleyi commented 4 years ago

何为 Scala?

Scala 是一门最终编译为 Java 字节码并运行在 JVM 之上的静态强类型语言

与之对应的,Python 是一门最终编译为 Python 字节码并运行在各种版本的 Python 解释器之上的动态强类型语言,而 JS 是动态弱类型,C、C++ 是静态弱类型。下面是一张总结图。

还有一点:静态语言并非无法解释执行。Scala 就有自己的解释器。

动态与静态的区别是编译期间报错,还是运行期间报错,而非是否能够解释执行。

学习一门新语言的切入点:

xxleyi commented 4 years ago

在 Scala 中,「显式的使用 return,特别是使用多个 return」 是 bad style 的强烈信号

在 scala 中,定义函数使用 =,这释放了明确的信号:函数整体被视为一个表达式对待。

def fun(b: Boolean) = {
    if (b) println("do it")
    else println("dont do it")
}

上述函数的返回类型是 Unit,这在 scala 中表示无返回值。

WHY? 可参照一个 SO 上回答,且不要只看 ✅

Return in Scala - Stack Overflow

再来几个实例感受一下:

// 单表达式函数可以省略 {},和 if else 如出一辙
def isGood(s: Int) = s >= 88

def isNotGood(s: Int) = !isGood(s)

// lambda expression function
List(1, 2, 3, 4).map(_ * 6).map(x => x * 8)

// Repeated arguments
def addAllLength(str: String*) = str.map(_.length).sum
xxleyi commented 4 years ago

Nothings in Scala: Null, null, Nil, Nothing, None, and Unit

首先,第一点:只有 null 是首字母小写,其余都是首字母大写。

然后,在 Scala 中,我们应该学着在合适的场景使用合适的「空值」。

最后是详细介绍:A Post About Nothing « Matt Malone's Old-Fashioned Software Development Blog

Null and null

Null is a trait, and there exists exactly one instance of Null, and that is null. null is an instance and the only instance of Null and a value, which can only be the value of a reference that is not referring to any object. Null is a trait, is the type of null. If you write a method that takes a parameter of type Null, you can only pass in two things: null itself or a reference of type Null.

Nil

Nil is an easy one. Nil is an object that extends List[Nothing]. It's an empty list. It’s basically a constant encapsulating an empty list of anything. It’s has zero length.

Nothing

Nothing is another trait. It extends class Any. Any is the root type of the entire Scala type system. There are no instances of Nothing, but (here’s the tricky bit) Nothing is a subtype of everything. Nothing is a subtype of List, it’s a subtype of String, it’s a subtype of Int, it’s a subtype of YourOwnCustomClass.

Any collection of Nothing must necessarily be empty.

One other use of Nothing is as a return type for methods that never return. Nothing is at the bottom of Scala's type hierarchy (it is even a subtype of Null). Raising an exception is returning Nothing.

None

When you’re writing a function in Java and run into a situation where you don’t have a useful value to return, what do you do? There are a few ways to handle it. You could return null, but this causes problems. If the caller isn’t expecting to get a null, he could be faced with a NullPointerException when he tries to use it, or else the caller must check for null.

上述这个问题非常普遍,不仅在 Java 中,在我熟悉的 Python、JS 中也是如此。Scala 尝试通过引入 Option[T] 来解决这个问题:var optionInt: Option[Int] = Some(33) or None

Option[Int] 表示可能为整数,也可能为空,也就是None。这既可以用来指定变量类型,也可以用来函数的返回类型。

不得不说,这种处理方式要比突兀的 None 或 null 好很多。

Unit

Unit is the type of a method that doesn’t return a value of any sort. It’s like a void return type in Java.

Unit 的返回类型代表函数正常返回,只是没有返回任何实体。Nothing 代表函数根本不会正常返回,会以抛错结束函数体的执行。

xxleyi commented 4 years ago

Type Hierarchy in Scala

There are no primitive types in Scala(unlike java). All data types in Scala are objects that have methods to operate on their data. All of Scala’s types exist as part of a type hierarchy.

image

Scala Type Hierarchy - GeeksforGeeks

Any is the superclass of all classes, also called the top class. It defines certain universal methods such as equals, hashCode, and toString. Any has two direct subclasses:

AnyVal represents value classes. All value classes are predefined; they correspond to the primitive types of Java-like languages.

There are nine predefined value types and they are non-null able: Double, Float, Long, Int, Short, Byte, Char, Unit, and Boolean.

AnyRef represents reference classes. All non-value types are defined as reference types. User-defined classes define reference types by default; i.e. they always (indirectly) subclass scala.AnyRef. scala.AnyRef in java programming corresponds to java.lang.Object.

Nothing and Null:

Nothing is a subclassify of all value types, it is also called the bottom type. Type Nothing That has no value. we can use Nothing to signal non-termination such as a thrown exception, program exit, or an infinite loop .

Null is a subclassify of all reference types. the keyword literal null can identify a single value. Null is provided mostly for interoperability with other JVM languages.

xxleyi commented 4 years ago

Option[A] in Scala

Scala Standard Library 2.13.1 - scala.Option

Option in Scala is a companion object:

Represents optional values. Instances of Option are either an instance of scala.Some or the object None.

The most idiomatic way to use an scala.Option instance is to treat it as a collection or monad and use map,flatMapfilter, or foreach:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

Note that this is equivalent to

val upper = for {
  name <- request getParameter "name"
  trimmed <- Some(name.trim)
  upper <- Some(trimmed.toUpperCase) if trimmed.length != 0
} yield upper
println(upper getOrElse "")
xxleyi commented 4 years ago

Try[A] in Scala

Scala Standard Library 2.13.1 - scala.util.Try

Companion object Try:

The Try type represents a computation that may either result in an exception, or return a successfully computed value. It's similar to, but semantically different from the scala.util.Either type.

Instances of Try[T], are either an instance of scala.util.Success[T] or scala.util.Failure[T].

For example, Try can be used to perform division on a user-defined input, without the need to do explicit exception-handling in all of the places that an exception might occur.

import scala.io.StdIn
import scala.util.{Try, Success, Failure}

def divide: Try[Int] = {
  val dividend = Try(StdIn.readLine("Enter an Int that you'd like to divide:\n").toInt)
  val divisor = Try(StdIn.readLine("Enter an Int that you'd like to divide by:\n").toInt)
  val problem = dividend.flatMap(x => divisor.map(y => x/y))
  problem match {
    case Success(v) =>
      println("Result of " + dividend.get + "/"+ divisor.get +" is: " + v)
      Success(v)
    case Failure(e) =>
      println("You must've divided by zero or entered something that's not an Int. Try again!")
      println("Info from the exception: " + e.getMessage)
      divide
  }
}

An important property of Try shown in the above example is its ability to pipeline, or chain, operations, catching exceptions along the way. The flatMapand map combinators in the above example each essentially pass off either their successfully completed value, wrapped in the Success type for it to be further operated upon by the next combinator in the chain, or the exception wrapped in the Failure type usually to be simply passed on down the chain. Combinators such as recover and recoverWith are designed to provide some type of default behavior in the case of failure.

Note: only non-fatal exceptions are caught by the combinators on Try (see scala.util.control.NonFatal). Serious system errors, on the other hand, will be thrown.

Note:: all Try combinators will catch exceptions and return failure unless otherwise specified in the documentation.

Try comes to the Scala standard library after years of use as an integral part of Twitter's stack.

xxleyi commented 4 years ago

Either[A, B] in Scala

Scala Standard Library 2.13.1 - scala.util.Either

Companion object Either:

Represents a value of one of two possible types (a disjoint union). An instance of Either is an instance of either scala.util.Left or scala.util.Right.

A common use of Either is as an alternative to scala.Option for dealing with possibly missing values. In this usage, scala.None is replaced with a scala.util.Left which can contain useful information. scala.util.Right takes the place of scala.Some. Convention dictates that Left is used for failure and Right is used for success.

For example, you could use Either[String, Int] to indicate whether a received input is a String or an Int.

import scala.io.StdIn._
val in = readLine("Type Either a string or an Int: ")
val result: Either[String,Int] =
  try Right(in.toInt)
  catch {
    case e: NumberFormatException => Left(in)
  }

result match {
  case Right(x) => s"You passed me the Int: $x, which I will increment. $x + 1 = ${x+1}"
  case Left(x)  => s"You passed me the String: $x"
}

Either is right-biased, which means that Right is assumed to be the default case to operate on. If it is Left, operations like map and flatMap return the Left value unchanged:

def doubled(i: Int) = i * 2
Right(42).map(doubled) // Right(84)
Left(42).map(doubled)  // Left(42)
xxleyi commented 4 years ago

Pattern Matching in Scala: apply the unapply

pattern match 与 algebraic data types 的关系很微妙:

Pattern Matching | Tour of Scala | Scala Documentation

Appendix: Algebraic Data Types in Scala | alvinalexander.com

Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the switch statement is Java and it can likewise be used in place of a series of if/else statements.

Syntax

A match expression has a value, the match keyword, and at least one case clause.

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}

The val x above is a random integer between 0 and 10. x becomes the left operand of the match operator and on the right is an expression with four cases. The last case _ is a "catch all" case for any other possible Int values. Cases are also called alternatives.

Match expressions have a value.

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
matchTest(3)  // other
matchTest(1)  // one

This match expression has a type String because all of the cases return String. Therefore, the function matchTest returns a String.

Matching on case classes

Case classes are especially useful for pattern matching.(where ADTs can behave an important role)

Pattern guards

Pattern guards are simply boolean expressions which are used to make cases more specific. Just add if <boolean expression> after the pattern.

Matching on type only

abstract class Device
case class Phone(model: String) extends Device {
  def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
  def screenSaverOn = "Turning screen saver on..."
}

def goIdle(device: Device) = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}

Sealed classes

Traits and classes can be marked sealed which means all subtypes must be declared in the same file. This assures that all subtypes are known.

sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture

def findPlaceToSit(piece: Furniture): String = piece match {
  case a: Couch => "Lie on the couch"
  case b: Chair => "Sit on the chair"
}

This is useful for pattern matching because we don’t need a “catch all” case.

Notes

Scala’s pattern matching statement is most useful for matching on algebraic types expressed via case classes. Scala also allows the definition of patterns independently of case classes, using unapply methods in extractor objects.

xxleyi commented 4 years ago

Future and Promise in Scala

Scala Standard Library 2.13.1 - scala.concurrent.Future

Futures and Promises | Scala Documentation

A Future represents a value which may or may not currently be available, but will be available at some point, or an exception if that value could not be made available.

Asynchronous computations that yield futures are created with the Future.apply call and are computed using a supplied ExecutionContext, which can be backed by a Thread pool.

import ExecutionContext.Implicits.global
val s = "Hello"
val f: Future[String] = Future {
  s + " future!"
}
f foreach {
  msg => println(msg)
}
xxleyi commented 4 years ago

PartialFunction and Partially Applied Functions in Scala

Partial & Partially Applied Functions in Scala - Thejas Babu - Medium

Scala 对函数式编程有着近乎完美的支持,高阶函数自不在话下。

但初次接触 PartialFunction 的我还是有点晕。原因最终也很清晰:在我熟知的 Python 和 JS 中,有 partial function 的概念,但和 Scala 中的 PartialFunction 完全是两个东西,虽然名字几乎一样。

在 Scala 中,PartialFunction 代表的是函数参数定义域的 partial,比如平方根函数:

val squareRoot: PartialFunction[Double, Double] = {
  case x if x >= 0 => Math.sqrt(x)
}

而在 Scala 中真正与 Python 中 partial function 相对应的概念是 Partially Applied Function,表示将部分参数应用到既有函数产生新的函数。用 Python 代码表示如下:

def func(a, b, c):
    return a, b, c

functools.partial(func, a = 3)
functools.partial(func, b = 4)
functools.partial(func, c = 5)

functools.partial(func, b = 4, c = 5)

最后,在某些场景下,偏应用函数确实可以与柯里化函数产生几乎一致的效果,但它们本质上又确实是两个东西:

f(a, b, c, d) => g = f(3, b, c, d) => g(4, 5, 6) => 偏应用
f(a, b, c, d) => g = curry(f)   => g(3)(4)(5)(6) => 柯里化
xxleyi commented 4 years ago

Type Classes in Scala

Type classes in Scala - Functional Programming - Ad-hoc polymorphism

Type classes are a powerful and flexible concept that adds ad-hoc polymorphism to Scala. They are not a first-class citizen in the language, but other built-in mechanisms allow to writing them in Scala.

Idea

Type classes were introduced first in Haskell as a new approach to ad-hoc polymorphism. Philip Wadler and Stephen Blott described it in How to make ad-hoc polymorphism less ad hoc

Type class is a class (group) of types, which satisfies some contract-defined trait, additionally, such functionality (trait and implementation) can be added without any changes to the original code.

There is no special syntax in Scala to express a type class, but the same functionality can be achieved using constructs that already exist in the language. That’s what makes it a little difficult for newcomers to spot a type class in code. A typical implementation of a type class uses some syntactic sugar as well, which also doesn’t make it clear right away what we are dealing with.

Implementation

trait Show[A] {
  def show(a: A): String
}

object Show {
  val intCanShow: Show[Int] =
    new Show[Int] {
      def show(int: Int): String = s"int $int"
    }
}

Then use it in a weird way:

Show.intCanShow.show(33)
// int 33

The next step is to write the show function, in Show’s companion object, to avoid calling intCanShow explicitly.

trait Show[A] {
  def show(a: A): String
}

object Show {

  def show[A](a: A)(implicit sh: Show[A]) = sh.show(a)

  implicit val intCanShow: Show[Int] =
    new Show[Int] {
      def show(int: Int): String = s"int $int"
    }
}

The show function takes some parameter of type A and an implementation of the Show trait for that type A. Marking the intCanShow value as implicit allows the compiler to find this implementation of Show[A] when there is a call to:

println(Show.show(20))

That is basically a type class. We have a trait that describes the functionality and implementations for each type we care about. There is also a function which applies an implicit instance’s function to the given parameter.

之后还有一系列迭代和优化。。。

xxleyi commented 4 years ago

Path-Dependent Types in Scala

The Neophyte's Guide to Scala Part 13: Path-dependent types

Using path-dependent types is one powerful way to help the compiler prevent you from introducing bugs, as it places logic that is usually only available at runtime into types.

Path-dependent types and dependent method types play a crucial role for attempts to encode information into types that is typically only known at runtime, for instance heterogenous lists, type-level representations of natural numbers and collections that carry their size in their type. Miles Sabin is exploring the limits of Scala's type system in this respect in his excellent library Shapeless.

xxleyi commented 4 years ago

for-comprehension in Scala

For Comprehensions | Tour of Scala | Scala Documentation

Scala: comprehending the for-comprehension - Wix Engineering - Medium

Scala offers a lightweight notation for expressing sequence comprehensions. Comprehensions have the form for (enumerators) yield e, where enumerators refers to a semicolon-separated list of enumerators. An enumerator is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body e for each binding generated by the enumerators and returns a sequence of these values.

xxleyi commented 4 years ago

Algebraic Data Types in Scala

Algebraic Data Types in Scala - Shannon Barnes - Medium

这一篇介绍很好,其中很关键的部分:

Having an algebraic data type in Scala simply means two things are involved:

  1. sum type consisting of various subtypes of other sum and product types.
  2. The ability to analyze the values of the algebraic data with pattern matching.

Case classes are special because Scala automatically creates a companion object for them: a singleton object that contains not only an apply method for creating new instances of the case class, but also an unapply method – the method that needs to be implemented by an object in order for it to be an extractor.

Scala Best Practices - Algebraic Data Types

这第二个更像是介绍 algebraic structures.

xxleyi commented 4 years ago

Algebraic Structures in Scala

Adventures in Abstract Algebra Part I: Implementing Algebraic Structures in Scala · An Abstract Plane

这个「代数结构」其实和 Scala 没有任何直接的关联,但了解用 Scala 提供的构建对象和类型的机制,具体实现一个「代数结构」的过程,有助于具像化理解「代数结构」这一「函数式编程」中的最最基本、又非常抽象的概念。

此处最关键的是了解「代数结构」的基本定义和几种可能的性质。

定义:

An algebraic structure consists of a set A and a binary operation ∗ on A, and is written <A,∗>. The binary operation ∗ must be some total function that is closed over A.

∗ is a total function iff (if and only if) it is defined for any a,b∈A.

∗ is closed over A iff for any a,b∈A, (a∗b)∈A.

性质:

The first property we’ll look at is associativity:

An algebraic structure with an associative operation is called a semi-group.

Next, an algebraic structure can have an identity element:

A semi-group with an identity element is called a monoid

A monoid for which every element is invertible is called a group. But what does it mean for an element a to be invertible? It means that there exists an inverse of a, normally written a​−1​​.

We will go one step further and consider commutativity:

A group with a commutative operation forms an abelian or commutative group.

  Associativity Identity Element Invertible Commutativity
Semi-Group X      
Monoid X X    
Group X X X  
Abelian Group X X X X

Scala Implementation Let’s begin with the simplest possible algebraic structure. As you’ll recall, all we need is a set A (possibly infinite) and a binary operation that is total and closed over A.

  trait AlgStruct[A] {
    def op(a: A, b: A): A
    def is(a: A, b: A): Boolean = (a == b)
  }
xxleyi commented 4 years ago

Shapeless in Scala

Getting started with Shapeless

shapeless 是一个优秀的 Scala 第三方库,值得学习和掌握。但是此库的特点是文档极少,想要真正使用和掌握,必须学会如何阅读源代码。上述引文是一个很好的切入口。

在对 Scala 的基础有一个基本理解和把握之后,进阶阶段就是拿下此第三方库。

⛽️

xxleyi commented 4 years ago

极佳的 Scala 入门材料,精炼且不失深度,需要至少掌握一门编程语言,有一些理论基础更佳

The Neophyte's Guide to Scala