Open xxleyi opened 4 years ago
在 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
首先,第一点:只有 null 是首字母小写,其余都是首字母大写。
然后,在 Scala 中,我们应该学着在合适的场景使用合适的「空值」。
最后是详细介绍:A Post About Nothing « Matt Malone's Old-Fashioned Software Development Blog
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
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
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.
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
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
代表函数根本不会正常返回,会以抛错结束函数体的执行。
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.
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
AnyRef
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.
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 objectNone
.The most idiomatic way to use an scala.Option instance is to treat it as a collection or monad and use
map
,flatMap
,filter
, orforeach
:
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 "")
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. TheflatMap
andmap
combinators in the above example each essentially pass off either their successfully completed value, wrapped in theSuccess
type for it to be further operated upon by the next combinator in the chain, or the exception wrapped in theFailure
type usually to be simply passed on down the chain. Combinators such asrecover
andrecoverWith
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.
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 thatLeft
is used for failure andRight
is used for success.For example, you could use
Either[String, Int]
to indicate whether a received input is aString
or anInt
.
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)
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.
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.
Case classes are especially useful for pattern matching.(where ADTs can behave an important role)
Pattern guards are simply boolean expressions which are used to make cases more specific. Just add if <boolean expression>
after the pattern.
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
}
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.
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.
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)
}
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) => 柯里化
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.
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.
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.
之后还有一系列迭代和优化。。。
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.
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.
Algebraic Data Types in Scala - Shannon Barnes - Medium
这一篇介绍很好,其中很关键的部分:
Having an algebraic data type in Scala simply means two things are involved:
- A sum type consisting of various subtypes of other sum and product types.
- 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.
这个「代数结构」其实和 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)
}
Getting started with Shapeless
shapeless 是一个优秀的 Scala 第三方库,值得学习和掌握。但是此库的特点是文档极少,想要真正使用和掌握,必须学会如何阅读源代码。上述引文是一个很好的切入口。
在对 Scala 的基础有一个基本理解和把握之后,进阶阶段就是拿下此第三方库。
⛽️
极佳的 Scala 入门材料,精炼且不失深度,需要至少掌握一门编程语言,有一些理论基础更佳
何为 Scala?
Scala 是一门最终编译为 Java 字节码并运行在 JVM 之上的静态强类型语言。
与之对应的,Python 是一门最终编译为 Python 字节码并运行在各种版本的 Python 解释器之上的动态强类型语言,而 JS 是动态弱类型,C、C++ 是静态弱类型。下面是一张总结图。
还有一点:静态语言并非无法解释执行。Scala 就有自己的解释器。
动态与静态的区别是编译期间报错,还是运行期间报错,而非是否能够解释执行。
学习一门新语言的切入点: