数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 MCMC 采样
- 机器学习方法概论
统计学习
深度学习
- 深度学习简介
- 深度前馈网络
- 反向传播算法
- 正则化
- 深度学习中的最优化问题
- 卷积神经网络
- CNN:图像分类
- 循环神经网络 RNN
- Transformer
- 一、Transformer [2017]
- 二、Universal Transformer [2018]
- 三、Transformer-XL [2019]
- 四、GPT1 [2018]
- 五、GPT2 [2019]
- 六、GPT3 [2020]
- 七、OPT [2022]
- 八、BERT [2018]
- 九、XLNet [2019]
- 十、RoBERTa [2019]
- 十一、ERNIE 1.0 [2019]
- 十二、ERNIE 2.0 [2019]
- 十三、ERNIE 3.0 [2021]
- 十四、ERNIE-Huawei [2019]
- 十五、MT-DNN [2019]
- 十六、BART [2019]
- 十七、mBART [2020]
- 十八、SpanBERT [2019]
- 十九、ALBERT [2019]
- 二十、UniLM [2019]
- 二十一、MASS [2019]
- 二十二、MacBERT [2019]
- 二十三、Fine-Tuning Language Models from Human Preferences [2019]
- 二十四 Learning to summarize from human feedback [2020]
- 二十五、InstructGPT [2022]
- 二十六、T5 [2020]
- 二十七、mT5 [2020]
- 二十八、ExT5 [2021]
- 二十九、Muppet [2021]
- 三十、Self-Attention with Relative Position Representations [2018]
- 三十一、USE [2018]
- 三十二、Sentence-BERT [2019]
- 三十三、SimCSE [2021]
- 三十四、BERT-Flow [2020]
- 三十五、BERT-Whitening [2021]
- 三十六、Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings [2019]
- 三十七、CERT [2020]
- 三十八、DeCLUTR [2020]
- 三十九、CLEAR [2020]
- 四十、ConSERT [2021]
- 四十一、Sentence-T5 [2021]
- 四十二、ULMFiT [2018]
- 四十三、Scaling Laws for Neural Language Models [2020]
- 四十四、Chinchilla [2022]
- 四十七、GLM-130B [2022]
- 四十八、GPT-NeoX-20B [2022]
- 四十九、Bloom [2022]
- 五十、PaLM [2022] (粗读)
- 五十一、PaLM2 [2023](粗读)
- 五十二、Self-Instruct [2022]
- 句子向量
- 词向量
- 传统CTR 预估模型
- CTR 预估模型
- 一、DSSM [2013]
- 二、FNN [2016]
- 三、PNN [2016]
- 四、DeepCrossing [2016]
- 五、Wide 和 Deep [2016]
- 六、DCN [2017]
- 七、DeepFM [2017]
- 八、NFM [2017]
- 九、AFM [2017]
- 十、xDeepFM [2018]
- 十一、ESMM [2018]
- 十二、DIN [2017]
- 十三、DIEN [2019]
- 十四、DSIN [2019]
- 十五、DICM [2017]
- 十六、DeepMCP [2019]
- 十七、MIMN [2019]
- 十八、DMR [2020]
- 十九、MiNet [2020]
- 二十、DSTN [2019]
- 二十一、BST [2019]
- 二十二、SIM [2020]
- 二十三、ESM2 [2019]
- 二十四、MV-DNN [2015]
- 二十五、CAN [2020]
- 二十六、AutoInt [2018]
- 二十七、Fi-GNN [2019]
- 二十八、FwFM [2018]
- 二十九、FM2 [2021]
- 三十、FiBiNET [2019]
- 三十一、AutoFIS [2020]
- 三十三、AFN [2020]
- 三十四、FGCNN [2019]
- 三十五、AutoCross [2019]
- 三十六、InterHAt [2020]
- 三十七、xDeepInt [2023]
- 三十九、AutoDis [2021]
- 四十、MDE [2020]
- 四十一、NIS [2020]
- 四十二、AutoEmb [2020]
- 四十三、AutoDim [2021]
- 四十四、PEP [2021]
- 四十五、DeepLight [2021]
- 图的表达
- 一、DeepWalk [2014]
- 二、LINE [2015]
- 三、GraRep [2015]
- 四、TADW [2015]
- 五、DNGR [2016]
- 六、Node2Vec [2016]
- 七、WALKLETS [2016]
- 八、SDNE [2016]
- 九、CANE [2017]
- 十、EOE [2017]
- 十一、metapath2vec [2017]
- 十二、GraphGAN [2018]
- 十三、struc2vec [2017]
- 十四、GraphWave [2018]
- 十五、NetMF [2017]
- 十六、NetSMF [2019]
- 十七、PTE [2015]
- 十八、HNE [2015]
- 十九、AANE [2017]
- 二十、LANE [2017]
- 二十一、MVE [2017]
- 二十二、PMNE [2017]
- 二十三、ANRL [2018]
- 二十四、DANE [2018]
- 二十五、HERec [2018]
- 二十六、GATNE [2019]
- 二十七、MNE [2018]
- 二十八、MVN2VEC [2018]
- 二十九、SNE [2018]
- 三十、ProNE [2019]
- Graph Embedding 综述
- 图神经网络
- 一、GNN [2009]
- 二、Spectral Networks 和 Deep Locally Connected Networks [2013]
- 三、Fast Localized Spectral Filtering On Graph [2016]
- 四、GCN [2016]
- 五、神经图指纹 [2015]
- 六、GGS-NN [2016]
- 七、PATCHY-SAN [2016]
- 八、GraphSAGE [2017]
- 九、GAT [2017]
- 十、R-GCN [2017]
- 十一、 AGCN [2018]
- 十二、FastGCN [2018]
- 十三、PinSage [2018]
- 十四、GCMC [2017]
- 十五、JK-Net [2018]
- 十六、PPNP [2018]
- 十七、VRGCN [2017]
- 十八、ClusterGCN [2019]
- 十九、LDS-GNN [2019]
- 二十、DIAL-GNN [2019]
- 二十一、HAN [2019]
- 二十二、HetGNN [2019]
- 二十三、HGT [2020]
- 二十四、GPT-GNN [2020]
- 二十五、Geom-GCN [2020]
- 二十六、Graph Network [2018]
- 二十七、GIN [2019]
- 二十八、MPNN [2017]
- 二十九、UniMP [2020]
- 三十、Correct and Smooth [2020]
- 三十一、LGCN [2018]
- 三十二、DGCNN [2018]
- 三十三、AS-GCN
- 三十四、DGI [2018]
- 三十五、DIFFPOLL [2018]
- 三十六、DCNN [2016]
- 三十七、IN [2016]
- 图神经网络 2
- 图神经网络 3
- 推荐算法(传统方法)
- 一、Tapestry [1992]
- 二、GroupLens [1994]
- 三、ItemBased CF [2001]
- 四、Amazon I-2-I CF [2003]
- 五、Slope One Rating-Based CF [2005]
- 六、Bipartite Network Projection [2007]
- 七、Implicit Feedback CF [2008]
- 八、PMF [2008]
- 九、SVD++ [2008]
- 十、MMMF 扩展 [2008]
- 十一、OCCF [2008]
- 十二、BPR [2009]
- 十三、MF for RS [2009]
- 十四、 Netflix BellKor Solution [2009]
- 推荐算法(神经网络方法 1)
- 一、MIND [2019](用于召回)
- 二、DNN For YouTube [2016]
- 三、Recommending What Video to Watch Next [2019]
- 四、ESAM [2020]
- 五、Facebook Embedding Based Retrieval [2020](用于检索)
- 六、Airbnb Search Ranking [2018]
- 七、MOBIUS [2019](用于召回)
- 八、TDM [2018](用于检索)
- 九、DR [2020](用于检索)
- 十、JTM [2019](用于检索)
- 十一、Pinterest Recommender System [2017]
- 十二、DLRM [2019]
- 十三、Applying Deep Learning To Airbnb Search [2018]
- 十四、Improving Deep Learning For Airbnb Search [2020]
- 十五、HOP-Rec [2018]
- 十六、NCF [2017]
- 十七、NGCF [2019]
- 十八、LightGCN [2020]
- 十九、Sampling-Bias-Corrected Neural Modeling [2019](检索)
- 二十、EGES [2018](Matching 阶段)
- 二十一、SDM [2019](Matching 阶段)
- 二十二、COLD [2020 ] (Pre-Ranking 模型)
- 二十三、ComiRec [2020](https://www.wenjiangs.com/doc/0b4e1736-ac78)
- 二十四、EdgeRec [2020]
- 二十五、DPSR [2020](检索)
- 二十六、PDN [2021](mathcing)
- 二十七、时空周期兴趣学习网络ST-PIL [2021]
- 推荐算法之序列推荐
- 一、FPMC [2010]
- 二、GRU4Rec [2015]
- 三、HRM [2015]
- 四、DREAM [2016]
- 五、Improved GRU4Rec [2016]
- 六、NARM [2017]
- 七、HRNN [2017]
- 八、RRN [2017]
- 九、Caser [2018]
- 十、p-RNN [2016]
- 十一、GRU4Rec Top-k Gains [2018]
- 十二、SASRec [2018]
- 十三、RUM [2018]
- 十四、SHAN [2018]
- 十五、Phased LSTM [2016]
- 十六、Time-LSTM [2017]
- 十七、STAMP [2018]
- 十八、Latent Cross [2018]
- 十九、CSRM [2019]
- 二十、SR-GNN [2019]
- 二十一、GC-SAN [2019]
- 二十二、BERT4Rec [2019]
- 二十三、MCPRN [2019]
- 二十四、RepeatNet [2019]
- 二十五、LINet(2019)
- 二十六、NextItNet [2019]
- 二十七、GCE-GNN [2020]
- 二十八、LESSR [2020]
- 二十九、HyperRec [2020]
- 三十、DHCN [2021]
- 三十一、TiSASRec [2020]
- 推荐算法(综述)
- 多任务学习
- 系统架构
- 实践方法论
- 深度强化学习 1
- 自动代码生成
工具
- CRF
- lightgbm
- xgboost
- scikit-learn
- spark
- numpy
- matplotlib
- pandas
- huggingface_transformer
- 一、Tokenizer
- 二、Datasets
- 三、Model
- 四、Trainer
- 五、Evaluator
- 六、Pipeline
- 七、Accelerate
- 八、Autoclass
- 九、应用
- 十、Gradio
Scala
- 环境搭建
- 基础知识
- 函数
- 类
- 样例类和模式匹配
- 测试和注解
- 集合 collection(一)
- 集合collection(二)
- 集成 Java
- 并发
一、函数定义
函数定义:以
def
开始,然后是函数名,然后是圆括号()
和圆括号包围的、以逗号,
分隔的参数列表,然后是冒号:
,然后是返回类型,然后是等号=
,最后是花括号{}
包围的函数体。def max(x:Int,y:Int): Int = { if (x>y) x else y }
参数列表中的每个参数都必须加上冒号
:
开始的类型标注,因为编译器无法推断函数参数的类型。如果函数不接收任何参数,则参数列表为空,此时使用空的圆括号
()
,表示不接收任何参数。圆括号包围的参数列表之后是返回结果的类型标注。
如果函数没有返回结果,则结果类型标注为
:Unit
。Unit
结果类型类似与Java
的void
类型,表示函数并不会返回任何有实际意义的结果。函数体之前的等号也有特别的含义,表示在函数式编程中,函数定义是一个表达式。
一个返回结果类型为
Unit
的函数通常带有副作用(如:修改某个变量,或者输入/输出),这样的函数也被称作过程procedure
。- 每个有用的
preocedure
都必须有某种形式的副作用,否则它对于外部世界没有任何价值。 - 函数式编程倾向于使用没有副作用的函数,这鼓励你设计的代码副作用尽可能小。其好处是:更容易测试。
- 每个有用的
一旦定义好函数,则可以通过按函数名来调用:
max(3,5)
。函数定义中的参数列表x,y
称作形参,函数调用中的值3,5
称作实参。Scala
函数的参数都是val
而不是var
,这意味着不能在函数体内部对入参重新赋值:xxxxxxxxxx
def add(b:Byte):Unit ={ // b = 1; 错误:b 是一个val println(b) }在没有任何显式的
return
语句时,Scala
函数返回的是该函数计算出的最后一个表达式的值。Scala
推荐的风格是:尽量避免使用任何显式的return
语句,尤其是多个return
语句。这样可以将每个函数视作一个最终提交某个值的表达式。这种哲学鼓励你编写短小的函数,将大的函数拆解成小的函数。当一个函数只会计算一个返回结果的表达式时,可以不写花括号。如果这个表达式很短,它甚至可以被放置在
def
的同一行。为了极致的精简,还可以省略掉结果类型,
Scala
会帮你推断出来。但是为了代码的可读性,推荐对类中声明的公有方法(它是函数的一种)显式的给出结果类型。
1.1 定义缩略
有时候没必要采用完整的函数定义,
Scala
编译器可以帮助我们做推断。如果函数的返回结果的类型是已知的,则函数定义中可以省略掉结果类型部分。
- 如果函数是递归的,则必须显式给出函数的结果类型。
- 通常建议显式的给出函数的结果类型,虽然编译器不会强求,但是这会让代码更容易阅读。
如果函数体只有一条语句,则可以省略掉花括号
{}
。如:
xxxxxxxxxx
def max(x:Int,y:Int) = if (x>y) x else y如果可以推断参数类型信息,则可以省略参数类型声明。
xxxxxxxxxx
val intList = List[Int]() intList.filter((x) => x > 0 )其中
intList
是一个整数列表。由于Scala
编译器知道x
必定是个整数,因此(x:Int)
可以简写为(x)
。这被称作目标类型
target typing
,因为一个表达式的目标使用场景会影响该表达式的类型。该机制原理细节不必掌握,你可以不需指定任何参数类型,直接使用函数字面量,当编译器报错时再添加类型声明。如果省略了参数类型,则参数两侧的圆括号也可以省略。
xxxxxxxxxx
intList.filter(x => x > 0 )为了让函数字面量更精简,还可以使用下划线作为占位符,用于表示一个或者多个参数,只需要满足每个参数只在函数字面量中只出现一次即可。
xxxxxxxxxx
intList.filter(_ > 0 )可以将下划线当作是表达式中的需要被“填”的“空”。函数每次被调用时,该“空”会被入参填上。
事实上,当
_
表示一个参数,且该参数恰好也是一个函数时,其语法会比较怪异:xxxxxxxxxx
actionList.foreach(_ ())其中
actionList
是一个列表,列表中每个元素都是一个函数。这些函数的参数列表都为空。因此上式等于:xxxxxxxxxx
actionList.foreach(f => f())有时候使用
_
作为参数占位符时,编译器可能没有足够多的信息来推断缺失的参数类型。此时可以通过冒号来显式给出参数类型。xxxxxxxxxx
val f = _ + _ // 错误:无法知道参数类型 val f = (_:Int) + (_:Int) // 正确,知道参数类型Scala
编译器将_ + _
展开为一个接收两个参数的函数字面量。多个下划线意味着多个参数,而不是一个参数的多次使用:第一个下划线代表第一个参数,第二个下划线代表第二个参数,依次类推。
下划线
_
不仅可以替换掉单个参数,它甚至可以替换掉整个参数列表。如:xxxxxxxxxx
intList.foreach(println _)这等价于
intList.foreach(x => println(x))
。这里的下划线_
是整个参数列表的占位符。注意:需要保留println
和_
之间的空格。这种语法是部分应用函数
partially applied function
。在Scala
中,当你调用某个函数,传入任何需要的参数时,你实际上是apply
那个函数到这些参数上。
1.2 部分应用函数
部分应用函数是一个表达式,在这个表达式中,并不需要函数给出所有的参数,而是部分给出甚至完全不给出。如:
xxxxxxxxxx
def sum(a:Int,b:Int,c:Int) = a + b +c val i = sum(1,2,3) // 函数调用 val a = sum _ // partially applied function println(a(1,2,3)) // 输出:6部分应用函数将返回一个新的函数。背后的原理是:
Scala
编译器根据partially applied function
创建一个类,并实例化一个值函数,并将这个新的值函数的引用赋值给变量a
。该类有一个接收
3
个参数的apply
方法,这是因为表达式sum _
缺失的参数个数为3
。然后编译器将表达式
a(1,2,3)
翻译成对值函数apply
方法的调用:a.apply(1,2,3)
。这个
apply
方法只是简单的将三个缺失的参数转发给sum
,然后返回结果。
你也可以通过给出一些实参来表达一个部分应用的函数。如:
xxxxxxxxxx
val a = sum(1,_:Int,3) // partially applied function由于只有一个参数缺失,因此
Scala
编译器生成的新的函数类,这个函数类的apply
方法接收一个参数。如果你的部分应用函数表达式并不给出任何参数,则可以简化成:
sum _
,甚至连下划线也不用写。如果什么都不写,则要求在明确需要函数的地方,否则会引起编译错误:
xxxxxxxxxx
intList.foreach(println) // foreach 的参数要求是个函数
1.3 可变长度参数列表
Scala
允许你标识出函数的最后一个参数可以被重复,这使得调用方可以传入一个可变长度的参数列表。可变长度参数列表的语法为:在参数的类型之后加上一个星号
*
。xxxxxxxxxx
def echo(args : String*) = for (arg <- args) println(arg)在函数内部,该重复参数的类型是一个所声明的参数类型的
Array
。因此在echo
内部,args
的类型其实是Array[String]
。如果你有一个
Array[String]
变量,则它不能作为echo
的实参。因为echo
的参数必须都是String
类型。你可以为数组实参
arr
的后面加上一个冒号和一个_*
符号,这种表示法告诉编译器:将数组实参arr
的每个元素作为参数传给函数,而不是将数组实参arr
作为单个参数传入。xxxxxxxxxx
def echo(args : String*) = for (arg <- args) println(arg) val arr = Array("Hello","World") echo(arr) // 编译失败,参数不匹配 echo(arr : _*) // OK
1.4 带名字的参数
在一个普通的函数调用中,实参是根据被调用的函数的参数定义,逐一匹配起来的。
Scala
支持带名字的参数,使得调用方可以用不同的顺序将实参传给函数。其语法是:在每个实参之前加上参数名和等号。xxxxxxxxxx
def getArea(width:Float,height:Float) = width * height println(getArea(3.0,4.0)) // width = 3.0, height = 4.0 println(getArea(height=4.0,width=3.0)) // width = 3.0, height = 4.0通过带名字的参数,实参可以在不改变含义的情况下交换位置。
可以混合使用按位置的参数和带名字的参数,此时按位置的参数需要放在前面。
带名字的参数最常用的场合是跟默认参数一起使用。
1.5 默认参数
Scala
允许你给函数参数指定默认值。这些带有默认值的参数可以不出现在函数调用中,此时这些参数被填充为默认值。其语法是:在函数定义时,形参类型之后使用= 默认值
的格式。xxxxxxxxxx
def printTime(out: java.io.PrintStream = Console.out) = { out.println("time = " + System.currentTimeMillis()) }
1.6 高阶函数
高阶函数
higher-order function
:接收函数作为参数的函数。xxxxxxxxxx
def filesMatching(query : String, matcher : (String,String) => Boolean) = { // 这里是函数定义 }其中形参
matcher
是一个函数,因此类型声明中有个=>
符号。这个函数接收两个字符串类型的参数并返回一个布尔值。高阶函数的优势:
高阶函数用于创建减少重复代码的控制抽象。
如:返回指定模式的文件名:
xxxxxxxxxx
def filesMatching(query : String, matcher : (String,String) => Boolean) = { // 这里是函数定义 }这种方式避免了重复定义一些函数:
xxxxxxxxxx
def files_begin(query : String)= { // 这里是函数定义 } def files_end(query : String)= { // 这里是函数定义 } ...在
API
中采用高阶函数,从而使得调用代码更精简。如:列表的
exists
方法就是高阶函数:xxxxxxxxxx
val has_neg = intList.exists(_ < 0) // 列表是否包含负数由于
exists
是Scala collection API
中的公共函数,因此它减少了API
使用方的代码重复。大量的循环逻辑都被抽象到了exists
方法里了。
1.7 柯里化curring
常规的函数定义包含一个参数列表,而一个柯里化的函数定义包含多个参数列表。
柯里化函数调用时,每个参数列表都需要提供对应的实参。
x def regular_f(x:Int,y:Int,z:Int) = x + y + z // 常规函数定义 def currying_f(x:Int)(y:Int)(z:Int) = x + y + z // 柯里化函数定义 println(regular_f(1,2,3)) // 常规函数调用 println(currying_f(1)(2)(3)) // 柯里化函数调用
当调用
currying_f
时,实际上是连续进行了三次传统的函数调用:- 第一次调用接收了一个名为
x
的Int
参数。 - 第二次调用接收了一个名为
y
的Int
参数。 - 第三次调用接收了一个名为
z
的Int
参数。
- 第一次调用接收了一个名为
可以通过占位符表示法来获取柯里化函数内部的“传统函数”的引用。
xxxxxxxxxx
def f(x:Int)(y:Int)(z:Int) = x + y + z // 柯里化函数定义无法通过下面的方式获取,会引发编译失败:
xxxxxxxxxx
val f1 = f(1) // 编译失败 val f2 = f(1)(2) // 编译失败可以通过括号外的
_
来获取。xxxxxxxxxx
val f1 = f(1)_ // 指向一个函数的引用,类型:Int => Int => Int // (参数为 Int,返回值为一个函数) val f2 = f(1)(2)_ // 指向一个函数的引用,类型:Int => Int理论上
_
之前要放置空格,但是由于这里_
之前是圆括号,因此不必放置空格。如println _
就必须放置空格,因为println_
是另一个合法的标识符。也可以通过括号内的
_
来获取,但是此时需要给定参数类型。xxxxxxxxxx
val f1 = f(_:Int)(_:Int)(_:Int) // 指向一个函数的引用,类型:(Int, Int, Int) => Int val f2 = f(1)(_:Int)(_:Int) // 指向一个函数的引用,类型:(Int, Int) => Int val f3 = f(1)(2)(_) // 指向一个函数的引用,类型:Int => Int
1.8 传名参数
当函数的参数是另一个函数,且该参数函数的参数列表为空时,参数函数可以退化为传名参数
by-name parameter
:xxxxxxxxxx
def filter_line1(file:File,op:() => Boolean) = { // 函数体 } def filter_line2(file:File,op: => Boolean) = { // 退化为传名参数 // 函数体 }传名参数是一个以
=>
开头的类型声明的参数,而不是() =>
开头。传名参数比常规的参数函数的优点在于:调用时可以去掉空的参数列表。
xxxxxxxxxx
filter_line1(new File("data.txt"),() => 5>3 ) // 常规调用,必须带上空的参数列表 filter_line2(new File("data.txt"), 5>3 ) // 传名参数调用,不用带上空的参数列表传名参数是相对于传值参数
by-value parameter
来说的。xxxxxxxxxx
def filter_line3(file:File,op: Boolean) = { // 传值参数 // 函数体 } filter_line3(new File("data.txt"), 5>3 ) // 传值参数调用传名参数和传值参数在调用时完全相同;在定义时,传名参数的类型声明多了一个
=>
。传名参数本质上是一个值函数对象,因此
5>3
转换成一个返回Boolean
值的值函数。因此:
对于传值参数,表达式
5>3
首先被求值。对于传名参数,表达式
5>3
并不会立即求值,而是在这个值函数对象apply
方法被应用时才会求值。如果该值函数的
apply
方法从未被调用,则表达式5>3
始终不会被求值。
传名参数和传值参数的典型比较:
xxxxxxxxxx
def assert_by_p_name(enable_assert:Boolean,predicate: => Boolean)={ if (enable_assert && ! predicate()) throw new AssertionError } // 传名参数 def assert_by_p_value(enable_assert:Boolean,predicate: Boolean)={ if (enable_assert && ! predicate) throw new AssertionError } // 传值参数 assert_by_p_name(false,5/0 == 0 ) // 正常执行,因为 5/0 不会被求值 assert_by_p_value(false,5/0 == 0 ) // 抛出异常,因为 5/0 被求值了
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论