本文档面向 Scala3 初学者,他们已经精通 Scala 语言,但对所有“implicits
` 以及代码中的参数化特征。
本文档解释了原因、方式、地点和时间 类型类别 (TC).
阅读本文档后,初学者 Scala3 开发人员将获得扎实的使用知识并深入了解 ScalaXNUMX 的源代码 很多 Scala 库并开始编写惯用的 Scala 代码。
让我们从原因开始……
表达问题
1998年, 菲利普·瓦德勒表示 “表达问题是老问题的新名称”。这是软件可扩展性的问题。根据Wadler先生的写作,表达问题的解决必须遵守以下规则:
- 规则 1:允许实施 现有行为 (想想 Scala 特性)应用于 新的表述 (想想一个案例类)
- 规则 2:允许实施 新行为 应用于 现有的交涉
- 规则3:不得危害 类型安全
- 规则 4:不必重新编译 现有代码
解决这个问题将是本文的主旨。
规则 1:在新表示上实施现有行为
任何面向对象的语言都有针对规则 1 的内置解决方案 亚型多态性。您可以安全地实施任何`trait
` 定义在对 ` 的依赖中class
` 在您自己的代码中,无需重新编译依赖项。让我们看看实际效果:
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
val eth = Lib1.Ethereum()
val btc = Lib1.Bitcoin()
val dot = Lib2.Polkadot()
在这个虚构的例子中,库`Lib1
`(第 5 行)定义了一个特征 `Blockchain
`(第 6 行)及其 2 个实现(第 9 行和第 12 行)。 `Lib1
` 在本文档的所有内容中将保持不变(执行规则 4)。
`Lib2
`(第 15 行)实现现有行为 `Blockchain
`在新班级`Polkadot
`(规则 1)以类型安全(规则 3)的方式,无需重新编译 `Lib1
`(规则 4)。
规则 2:实施应用于现有表示的新行为
让我们想象一下‘Lib2
`我们想要一种新的行为`lastBlock
` 专门针对每个 ` 实施Blockchain
`.
首先想到的是根据参数类型创建一个大开关。
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(blockchain: Blockchain): Block = blockchain match
case _:Ethereum => todo
case _:Bitcoin => todo
case _:Polkadot => todo
object Lib3:
import Lib1.*
case class Polygon() extends Blockchain:
override def getBlock(height: Height): Block = todo
import Lib1.*, Lib2.*, Lib3.*
println(lastBlock(Bitcoin()))
println(lastBlock(Ethereum()))
println(lastBlock(Polkadot()))
println(lastBlock(Polygon()))
该解决方案是基于类型的多态性的弱重新实现,该多态性已经内置于语言中!
`Lib1
` 保持不变(请记住,在本文档中强制执行规则 4)。
在`中实施的解决方案Lib2
` 是 还可以 直到另一个区块链被引入`Lib3
`。它违反了类型安全规则(规则 3),因为此代码在第 37 行运行时失败。并且修改 `Lib2
` 将违反规则 4。
另一个解决方案是使用`extension
`.
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(): Block = todo
extension (eth: Ethereum) def lastBlock(): Block = todo
extension (btc: Bitcoin) def lastBlock(): Block = todo
import Lib1.*, Lib2.*
println(Bitcoin().lastBlock())
println(Ethereum().lastBlock())
println(Polkadot().lastBlock())
def polymorphic(blockchain: Blockchain) =
// blockchain.lastBlock()
???
`Lib1
` 保持不变(在整个文档中执行规则 4)。
`Lib2
` 定义其类型的行为(第 21 行)和现有类型的“扩展”(第 23 和 25 行)。
第 28-30 行,新行为可以在每个类中使用。
但是没有办法以多态方式调用这个新行为(第 32 行)。任何这样做的尝试都会导致编译错误(第 33 行)或基于类型的切换。
第 2 条规则很棘手。我们尝试用我们自己的多态性定义和“扩展”技巧来实现它。这很奇怪。
有一个缺失的部分叫做 临时多态性: 能够根据类型安全地调度行为实现,无论行为和类型是在何处定义的。输入 类型类 格局。
类型类模式
Type Class(简称TC)模式配方有3个步骤。
- 定义新行为
- 实施行为
- 使用行为
在下面的部分中,我以最直接的方式实现 TC 模式。它冗长、笨重且不切实际。但请稍等,这些警告将在文档中进一步得到解决。
1. 定义新行为
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
`Lib1
` 再次保持原样。
新行为 is TC 由特质具体化。特征中定义的功能是应用该行为某些方面的一种方法。
参数`A
` 代表我们想要应用行为的类型,它们是 ` 的子类型Blockchain
` 在我们的例子中。
一些备注:
- 如果需要,参数化类型`
A
` 可以进一步受到 Scala 类型系统的约束。例如,我们可以强制执行`A
` 成为一个 `Blockchain
`. - 此外,TC 中还可以声明更多的函数。
- 最后,每个函数可能有更多的任意参数。
但为了可读性,让我们保持简单。
2. 实施行为
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
对于每种类型新的 `LastBlock
` 行为是预期的,有该行为的特定实例。
`Ethereum
` 实现行 22 是根据 ` 计算得出的eth
` 实例作为参数传递。
实施`LastBlock
` 为 `Bitcoin
` 第 25 行是使用非托管 IO 实现的,并且不使用其参数。
所以,`Lib2
` 实现新行为 `LastBlock
` 为 `Lib1
` 类。
3.使用行为
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A, behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2), ethereumLastBlock))
println(useLastBlock(Bitcoin(), bitcoinLastBlock))
第 30 行`useLastBlock
` 使用 ` 的实例A
` 和 `LastBlock
` 为该实例定义的行为。
第 33 行`useLastBlock
` 用 ` 的实例调用Ethereum
` 以及 ` 的实现LastBlock
` 定义于 `Lib2
`。请注意,可以传递`的任何替代实现LastBlock[A]
`(想想 依赖注入).
`useLastBlock
` 是表征(实际的 A)与其行为之间的粘合剂。数据和行为分离,这就是函数式编程所提倡的。
讨论
我们回顾一下表达式问题的规则:
- 规则 1:允许实施 现有行为 应用于 新班级
- 规则 2:允许实施 新行为 应用于 现有课程
- 规则3:不得危害 类型安全
- 规则 4:不必重新编译 现有代码
规则 1 可以通过子类型多态性立即解决。
刚刚提出的 TC 模式(参见前面的屏幕截图)解决了规则 2。它是类型安全的(规则 3),我们从未触及`Lib1
`(规则 4)。
然而,由于以下几个原因,使用它是不切实际的:
- 第 33-34 行我们必须显式地沿其实例传递行为。这是额外的开销。我们应该只写`
useLastBlock(Bitcoin())
`. - 第 31 行的语法不常见。我们宁愿写一个简洁且更面向对象的`
instance.lastBlock()
`声明。
让我们重点介绍一些用于 TC 实际使用的 Scala 功能。
增强的开发人员体验
Scala 拥有一组独特的功能和语法糖,使 TC 成为开发人员真正愉快的体验。
隐式
隐式作用域是一种在编译时解析的特殊作用域,其中只能存在给定类型的一个实例。
程序使用`将实例放入隐式作用域中given
` 关键字。或者,程序可以使用关键字“从隐式作用域中检索实例”using
`.
隐式作用域在编译时解析,有已知的方法可以在运行时动态更改它。如果程序编译,则隐式作用域被解析。在运行时,使用它们的隐式实例是不可能丢失的。唯一可能的混乱可能来自使用错误的隐式实例,但这个问题留给了椅子和键盘之间的生物。
它与全局范围不同,因为:
- 这是根据上下文解决的。程序的两个位置可以在隐式作用域中使用相同给定类型的实例,但这两个实例可能不同。
- 在幕后,代码将隐式参数函数传递给函数,直到达到隐式用法。它不使用全局内存空间。
回到类型类!让我们举一个完全相同的例子。
def todo = 42
type Height = Int
type Block = Int
def http(uri: String): Block = todo
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
`Lib1
` 与我们之前定义的未修改代码相同。
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given ethereumLastBlock:LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given bitcoinLastBlock:LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A)(using behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2)))
println(useLastBlock(Bitcoin()))
第 19 行一个新行为`LastBlock
` 被定义,就像我们之前所做的那样。
第 22 行和第 25 行,`val
` 被替换为 `given
`。 ` 的两种实现LastBlock
` 被放入隐式作用域中。
第 31 行`useLastBlock
` 声明行为 `LastBlock
` 作为隐式参数。编译器解析`的适当实例LastBlock
` 来自调用者位置上下文的隐式作用域(第 33 和 34 行)。第 28 行从 `Lib2
`,包括隐式范围。因此,编译器将第 22 行和第 25 行定义的实例作为 ` 的最后一个参数传递useLastBlock
`.
作为库用户,使用类型类比以前更容易。第 34 和 35 行开发人员只需确保该行为的实例被注入到隐式作用域中(这可以只是一个`import
`)。如果隐式不是`given
` 其中代码是 `using
` 它,编译器告诉他。
Scala 的隐式简化了传递类实例及其行为实例的任务。
隐性糖
之前代码的第22行和25行可以进一步改进!让我们迭代一下 TC 的实现。
given LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
第22行和25行,如果实例的名称不用,可以省略。
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
第22行和第25行,类型的重复可以用`替换with
` 关键字。
given LastBlock[Ethereum] = _.lastBlock
given LastBlock[Bitcoin] = _ => http("http://bitcoin/last")
因为我们使用的是包含单个函数的退化特征,所以 IDE 可能会建议使用 SAM 表达式来简化代码。尽管正确,但我认为这不是 SAM 的正确使用方式,除非您只是随意地打高尔夫球。
Scala 提供语法糖来简化语法,消除不必要的命名、声明和类型冗余。
延期
明智地使用,`extension
` 机制可以简化使用类型类的语法。
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)
def lastBlock(using tc: LastBlock[A]) = tc.lastBlock(instance)
import Lib1.*, Lib2.*
println(Ethereum(lastBlock = 2).lastBlock)
println(Bitcoin().lastBlock)
第 28-29 行通用扩展方法 `lastBlock
` 是为任何 ` 定义的A
` 带有一个 `LastBlock
` 隐式作用域中的 TC 参数。
第 33-34 行扩展利用面向对象的语法来使用 TC。
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)(using tc: LastBlock[A])
def lastBlock = tc.lastBlock(instance)
def penultimateBlock = tc.lastBlock(instance) - 1
import Lib1.*, Lib2.*
val eth = Ethereum(lastBlock = 2)
println(eth.lastBlock)
println(eth.penultimateBlock)
val btc = Bitcoin()
println(btc.lastBlock)
println(btc.penultimateBlock)
第28行,也可以为整个分机定义TC参数以避免重复。第 30 行我们在扩展中重用 TC 来定义`penultimateBlock
` (尽管它可以在 `LastBlock
` 直接特质)
当使用 TC 时,奇迹就会发生。这种表达感觉更加自然,给人一种行为“lastBlock
` 与实例合并。
带TC的通用型
import Lib1.*, Lib2.*
def useLastBlock1[A](instance: A)(using LastBlock[A]) = instance.lastBlock
def useLastBlock2[A: LastBlock](instance: A) = instance.lastBlock
val eth = Ethereum(lastBlock = 2)
assert(useLastBlock1(eth) == useLastBlock2(eth))
第 34 行该函数使用隐式 TC。请注意,如果不需要该名称,则无需为 TC 命名。
TC 模式的使用如此广泛,以至于有一种通用类型语法来表达“具有隐式行为的类型”。第 36 行语法是前一个语法(第 34 行)的更简洁的替代方案。它避免专门声明未命名的隐式 TC 参数。
开发者体验部分到此结束。我们已经看到,当使用和定义 TC 时,扩展、隐式和一些语法糖如何提供更简洁的语法。
自动推导
许多 Scala 库都使用 TC,让程序员在自己的代码库中实现它们。
例如 Circe(一个 json 反序列化库)使用 TC `Encoder[T]
`和`Decoder[T]
` 供程序员在其代码库中实现。一旦实施,库的整个范围都可以使用。
TC 的这些实现通常是 面向数据的映射器。它们不需要任何业务逻辑,编写起来很无聊,并且维护与案例类同步的负担。
在这种情况下,这些库提供了所谓的 自动 推导或 半自动 推导。例如参见喀耳刻 自动 和 半自动 推导。通过半自动派生,程序员可以使用一些次要语法来声明类型类的实例,而自动派生除了导入之外不需要任何代码修改。
在底层,在编译时,通用宏内省 类型 作为纯数据结构并为图书馆用户生成 TC[T]。
一般派生 TC 非常常见,因此 Scala 为此引入了一个完整的工具箱。尽管这是使用派生的 Scala 3 方式,但库文档并不总是宣传此方法。
object GenericLib:
trait Named[A]:
def blockchainName(instance: A): String
object Named:
import scala.deriving.*
inline final def derived[A](using inline m: Mirror.Of[A]): Named[A] =
val nameOfType: String = inline m match
case p: Mirror.ProductOf[A] => compiletime.constValue[p.MirroredLabel]
case _ => compiletime.error("Not a product")
new Named[A]:
override def blockchainName(instance: A):String = nameOfType.toLowerCase
extension[A] (instance: A)(using tc: Named[A])
def blockchainName = tc.blockchainName(instance)
import Lib1.*, GenericLib.*
case class Polkadot() derives Named
given Named[Bitcoin] = Named.derived
given Named[Ethereum] = Named.derived
println(Ethereum(lastBlock = 2).blockchainName)
println(Bitcoin().blockchainName)
println(Polkadot().blockchainName)
18号线新TC`Named
` 被引入。严格来说,这个TC与区块链业务无关。其目的是根据案例类的名称来命名区块链。
首先关注第 36-38 行的定义。派生 TC 有 2 种语法:
- 第 36 行 TC 实例可以直接在案例类上定义,使用 `
derives
` 关键字。编译器在底层生成一个给定的`Named
` 中的实例Polkadot
` 伴随对象。 - 第 37 和 38 行,类型类实例在预先存在的类上通过 ` 给出
TC.derived
`
第 31 行定义了通用扩展(请参阅前面的部分)和 `blockchainName
` 是自然使用的。
`derives
` 关键字需要一个格式为 ` 的方法inline def derived[T](using Mirror.Of[T]): TC[T] = ???
` 这是第 24 行定义的。我不会深入解释代码的作用。概括地说:
- `
inline def
` 定义一个宏 - `
Mirror
` 是用于内省类型的工具箱的一部分。镜像有不同种类,第26行代码重点是`Product
` 镜子(案例类是一种产品)。第 27 行,如果程序员尝试导出不是 ` 的东西Product
`,代码无法编译。 - `
Mirror
` 包含其他类型。其中之一,`MirrorLabel
`, 是包含类型名称的字符串。该值用于`的第 29 行的实现中。Named
` TC。
TC 作者可以使用元编程来提供一般生成给定类型的 TC 实例的函数。程序员可以使用专用库 API 或 Scala 派生工具为其代码创建实例。
无论您需要通用代码还是特定代码来实现 TC,每种情况都有一个解决方案。
所有好处的总结
- 解决了表达问题
- 新类型可以通过传统特征继承来实现现有行为
- 新的行为可以在现有类型上实现
- 关注点分离
- 该代码未被破坏且易于删除。 TC 将数据和行为分开,这是函数式编程的座右铭。
- 它是安全的
- 它是类型安全的,因为它不依赖于内省。它避免了涉及类型的大模式匹配。如果您遇到自己编写此类代码,您可能会发现 TC 模式非常适合的情况。
- 隐式机制是编译安全的!如果编译时缺少实例,则代码将无法编译。运行时并不奇怪。
- 它带来了临时多态性
- 传统的面向对象编程中通常缺少临时多态性。
- 通过临时多态性,开发人员可以为各种不相关的类型实现相同的行为,而无需使用传统的子类型(耦合代码)
- 依赖注入变得容易
- TC 实例可以根据里氏替换原则进行更改。
- 当组件依赖于 TC 时,可以轻松注入模拟的 TC 以进行测试。
计数器指示
每把锤子都是针对一系列问题而设计的。
类型类用于解决行为问题,不得用于数据继承。为此目的使用组合。
通常的子类型更简单。如果您拥有代码库并且不以可扩展性为目标,那么类型类可能有点过分了。
例如,在 Scala 核心中,有一个 `Numeric
` 类型类:
trait Numeric[T] extends Ordering[T] {
def plus(x: T, y: T): T
def minus(x: T, y: T): T
def times(x: T, y: T): T
使用这样的类型类确实很有意义,因为它不仅允许在 Scala 中嵌入的类型(Int、BigInt 等)上重用代数算法,而且还允许在用户定义的类型(a `ComplexNumber
` 例如)。
另一方面,Scala 集合的实现主要使用子类型而不是类型类。这种设计之所以有意义有几个原因:
- 集合 API 应该是完整且稳定的。它通过实现继承的特征公开常见行为。高度可扩展并不是这里的特定目标。
- 它必须简单易用。 TC 增加了最终用户程序员的心理负担。
- TC 也可能会产生少量的性能开销。这对于集合 API 可能至关重要。
- 尽管如此,集合 API 仍然可以通过第三方库定义的新 TC 进行扩展。
结论
我们已经看到,TC 是一个解决大问题的简单模式。得益于 Scala 丰富的语法,TC 模式可以通过多种方式实现和使用。 TC 模式符合函数式编程范式,是构建干净架构的绝佳工具。没有灵丹妙药,TC 模式必须在适合时应用。
希望您通过阅读本文档获得了知识。
代码可在 https://github.com/jprudent/type-class-article。如果您有任何问题或意见,请与我联系。如果需要,您可以使用存储库中的问题或代码注释。
软件工程师
- :具有
- :是
- :不是
- :在哪里
- ][p
- 1
- 10
- 11
- 12
- 14
- 15%
- 19
- 1998
- 22
- 23
- 24
- 25
- 26%
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 35%
- 36
- 7
- 8
- 9
- a
- 对,能力--
- 关于
- AC
- 根据
- 操作
- 实际
- 添加
- 倡导者
- 再次
- 瞄准
- 算法
- 所有类型
- 让
- 允许
- 沿
- 已经
- 还
- 替代
- 尽管
- 时刻
- an
- 和
- 另一个
- 任何
- API
- 应用的
- 使用
- 适当
- 架构
- 档案
- 保健
- 参数
- 刊文
- AS
- 方面
- At
- 尝试
- 作者
- 自动表
- 可使用
- 避免
- 背部
- 基地
- 基于
- BE
- 因为
- before
- 初学者
- 行为
- 行为
- 作为
- 之间
- 大
- blockchain
- Boring
- 都
- 盒子
- 带来
- 广阔
- BTC
- 负担
- 商业
- 但是
- by
- 呼叫
- 被称为
- 呼叫者
- CAN
- 案件
- 椅子
- 更改
- 变
- 程
- 类
- 清洁
- 码
- 代码库
- 代码库
- 采集
- 收藏
- 如何
- 购买的订单均
- 注释
- 相当常见
- 伴侣
- 完成
- 执行
- 元件
- 写作
- 关心
- 简洁
- 总结
- 混合的
- 混乱
- 包含
- 核心
- 正确
- 可以
- 创建信息图
- 创造
- 生物
- 危急
- data
- 资料结构
- 声明
- 专用
- 定义
- 定义
- 定义
- 定义
- 定义
- 依赖
- 深度
- 漂移
- 设计
- 设计
- 检测
- 开发商
- 开发
- DID
- 不同
- 直接
- 派遣
- 潜水
- do
- 文件
- 不
- 不会
- 别
- 动态
- 每
- 缓解
- 更容易
- 容易
- 易
- ed
- 嵌入式
- 结束
- 执行
- 强制
- 愉快的
- 输入
- 故障
- ETH
- 甚至
- 一切
- 究竟
- 例子
- 除
- 存在
- 现有
- 预期
- 预计
- 体验
- 说明
- 介绍
- 明确地
- 特快
- 表达
- 延期
- 扩展
- 额外
- 失败
- 特征
- 感觉
- 固定
- 专注焦点
- 重点
- 以下
- 针对
- 申请
- 止
- 功能
- 实用
- 功能
- 进一步
- Gain增益
- 获得
- 生成
- 产生
- GitHub上
- 特定
- 给予
- 全球
- 全球视野
- 目标
- 指南
- 锤
- 手
- 发生
- 有
- 相关信息
- 近期亮点
- 高度
- 他
- 举行
- 兜帽
- 创新中心
- HTML
- HTTP
- HTTPS
- i
- if
- 错觉
- 想像
- 实施
- 履行
- 实现
- 实施
- 器物
- 进口
- 进口
- 改善
- in
- 包含
- 继承权
- 例
- 代替
- 拟
- 成
- 介绍
- 涉及
- 问题
- 问题
- IT
- 它的
- 危害
- JSON
- 只是
- 保持
- 知道
- 知识
- 语言
- 名:
- 信息
- 离开
- 莱杰
- 左
- 减
- 杠杆
- 库
- 自学资料库
- 喜欢
- Line
- 线
- 地点
- 逻辑
- 占地
- 宏
- 制成
- 健康无副作用
- 魔法
- 保持
- 使
- 制作
- 方式
- 许多
- 匹配
- 可能..
- me
- 机制
- 内存
- 心理
- 聚体
- 元
- 方法
- 可能
- 介意
- 未成年人
- 镜面
- 失踪
- 更多
- 最先进的
- 大多
- 座右铭
- 必须
- 姓名
- 命名
- 命名
- 自然
- 需求
- 打印车票
- 决不要
- 全新
- 没有
- 注意
- 对象
- of
- 提供
- 优惠精选
- 经常
- 老
- on
- 一旦
- 一
- 仅由
- or
- 其他名称
- 我们的
- 输出
- 大纲
- 超过
- 己
- 范例
- 参数
- 参数
- 部分
- 特别
- 党
- 通过
- 通过
- 通行证
- 通过
- 模式
- 完美
- 性能
- 片
- 柏拉图
- 柏拉图数据智能
- 柏拉图数据
- 请
- 可能
- 实用
- 比较喜欢
- 呈现
- 以前
- 先前
- 原理
- 市场问题
- 问题
- 产品
- 曲目
- 程序员
- 程序员
- 代码编程
- 正确
- 提供
- 目的
- 目的
- 放
- 认沽期权
- 有疑问吗?
- 范围
- 宁
- 达到
- 达到
- 阅读
- 真
- 原因
- 概括
- 食谱
- 依靠
- 留
- 纪念
- 删除
- 更换
- 知识库
- 表示
- 代表
- 解决
- 尊重
- 重用
- 丰富
- 第
- 定位、竞价/采购和分析/优化数字媒体采购,但算法只不过是解决问题的操作和规则。
- 运行
- s
- 安全
- 安然
- 实现安全
- 清酒
- Sam
- 同
- 现场
- 范围
- 部分
- 部分
- 看到
- 看到
- 感
- 集
- 几个
- 短
- 应该
- 白银
- 简易
- 简化
- 简化
- 单
- 情况
- 小
- So
- 软件
- 固体
- 方案,
- 解决
- 一些
- 东西
- 来源
- 源代码
- 太空
- 发言
- 特别
- 具体的
- 特别是
- 稳定
- 开始
- 个人陈述
- 步
- 步骤
- 仍
- 简单的
- 精简
- 串
- 结构体
- 这样
- 糖
- 建议
- 如下
- 应该
- 肯定
- 惊
- Switch 开关
- 同步。
- 句法
- 系统
- 采取
- 任务
- 告诉
- 测试
- 比
- 谢谢
- 这
- 其
- 他们
- 那里。
- 他们
- 事
- 事
- 认为
- 第三
- Free Introduction
- 那些
- 虽然?
- 通过
- 次
- 至
- 工具
- 工具箱
- 工具
- 感动
- 传统
- 尝试
- 真正
- 尝试
- 二
- 类型
- 类型
- 罕见
- 下
- 独特
- 无名
- 不必要
- 直到
- 不变
- 未使用
- 上
- 用法
- 使用
- 用过的
- 用户
- 用户
- 使用
- 运用
- 通常
- 平时
- 折扣值
- 各个
- 谙练
- 非常
- 想
- 是
- 方法..
- 方法
- we
- 弱
- 什么是
- 什么是
- ,尤其是
- 而
- 这
- WHO
- 全
- 为什么
- 广泛
- 将
- 明智
- 也完全不需要
- 将
- 写
- 写作
- 错误
- 但
- 完全
- 您一站式解决方案
- 你自己
- 和风网