scala 理解 trait 中 泛型 变量 F[_]

This very abstract syntax comes up all the time in Scala, I will try to give you an intuition of what it means and how to use it.
scala 首先支持泛型类,不过我们经常也会遇到泛型的 trait,对于泛型的trait 在我们使用 map flatmap操作集合的时候 中非常常见,对于出现 trait map[A,F[_]]

trait Functor[A, F[_]] {
  def map[B](f: A => B): F[B]
  def flatMap[B](f: A => F[B]): F[B]
}

/** Higher Functor Trait */
trait HigherFunctor[A, M[+_], F[_[_]]] {
  def map[B](f: A => B): F[M]
  def flatMap[B](f: A => M[B]): F[M]
}

/** Multi-Functor Trait */
trait MultiFunctor[A, State, F[_, _]] extends Functor[A, ({type λ[α] = F[α, State]})#λ] {
  def map[B](f: A => B): F[B, State]
  def flatMap[B](f: A => F[B, State]): F[B, State]
}

/** Higher Multi-Func

其实 F[_] 代表的就是泛型集合,例如 List[Int] Seq[Long],
对于 def mapB,that(
implicit bf :CanBuildFrom[Repr, B, That]) :That =(...)
)

Repr 是内部用来保存元素的集合类型,B是函数f 穿件的元素类型。 That 是我们想要创建的目标集合的类型参数,它可能与输入的原始集合相同,也可能不同。

Traits are used to share interfaces and fields between classes. They are similar to Java 8’s interfaces. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters.

Defining a trait
A minimal trait is simply the keyword trait and an identifier:

trait HairColor
Traits become especially useful as generic types and with abstract methods.

trait Iterator[A] {
  def hasNext: Boolean
  def next(): A
}
Extending the trait Iterator[A] requires a type A and implementations of the methods hasNext and next.

Using traits
Use the extends keyword to extend a trait. Then implement any abstract members of the trait using the override keyword:

trait Iterator[A] {
  def hasNext: Boolean
  def next(): A
}

class IntIterator(to: Int) extends Iterator[Int] {
  private var current = 0
  override def hasNext: Boolean = current < to
  override def next(): Int = {
    if (hasNext) {
      val t = current
      current += 1
      t
    } else 0
  }
}

val iterator = new IntIterator(10)
iterator.next() // returns 0
iterator.next() // returns 1
This IntIterator class takes a parameter to as an upper bound. It extends Iterator[Int] which means that the next method must return an Int.

Subtyping
Where a given trait is required, a subtype of the trait can be used instead.

import scala.collection.mutable.ArrayBuffer

trait Pet {
  val name: String
}

class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet

val dog = new Dog("Harry")
val cat = new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))  // Prints Harry Sally

The goal of this post is to understand this syntax and why you would need it. In order to do so we will gradually climb the ladder of abstractions and answer the following questions :

  • What is a value ?
  • What is a proper type ?
  • What is a first-order type ?
  • What abstracts over a first-order type ?
  • Why do I need F[_] ?

Values represent raw data. They have the lowest level of abstraction and are the simplest concept that we need to deal with.

Take a look at the right hand side of these examples — it’s just data and it’s trivial to understand.

If a child asks you what your funky BigPanda tshirt costs and you answer $12 then they’ll understand what you mean. They’ll certainly understand the value in your answer (2). But if they ask you what a dollar is then suddenly things get more complicated. Explaining money and currencies is a bit more tricky. This takes us to types.

Look at the information the REPL spits out. It keeps telling you about types: String, List[Int] etc. These are all proper types.

Proper types are a higher level concept than values. Let’s talk about how they are related: types can be instantiated to produce a value and values are a specific instance of a type.

String can produce all the string literals you can think up ("a", "ab", "algorithmic service operations" etc). If we go back to our pricing example, can be instantiated to2, 3, [49,000,000](https://bigpanda.io/resources/bigpanda-expands-series-b-funding-49-million/)...) or any other amount.

Moving from values to proper types took us up a level of abstraction. What do we get if we go one higher?

In the previous example, we said that List[Int] is a proper type, but what isList?

This doesn’t compile. The compiler won’t let us say that a value is a List. It wants us to say that it is a list of something, a List[_].

There is a slot there. If we want the compiler to give us a type we need to put something in the slot. It’s like a parameter to a function that returns a type. There’s a name for this special kind of function: a type constructor.

You’ve probably met other type constructors: Option[_], Array[_], Map[_,_] and friends. Notice that Map is a little different; it needs a type for the key and for the value. It has 2 slots for 2 parameters.

First-order types are just types (List, Map, Array) that have type constructors (List[_], Map[_, _]) that take proper types and produce proper types (List[Int], Map[String, Int]).

Going from proper types to first-order types took us up a layer of abstraction. In most programming languages you can’t abstract any further. However, Scala let you go a step further. Let’s take that last step and see where it takes us.

Every step we’ve taken so far has added an abstraction over the previous abstraction:

In Scala you can abstract over a first-order type with this syntax:

Let’s forget about WithMap for a second to focus on the F[_] syntax. F[_] represents a first-order type with one slot. For example List[_] or Option[_].

Now the million dollar question: What is WithMap?

Answer: A second-order type

It’s a type which abstracts over types which abstract over types!!!

[图片上传失败...(image-a8a96c-1580368866563)]

Feel like inception, right? Hopefully, you followed until here and everything is starting to fall into place.

Let’s introduce one more piece of terminology, and then try and clarify how everything fits together.

A type with a type constructor (ie. a type with [_]) is called a higher kinded type. A type constructor is just a function that takes a type and returns a type.

Let’s do a quick analogy between types and functions :

  • A type constructor List[_] is just a function of type

<pre style="box-sizing: inherit; color: rgb(54, 54, 54); font-weight: 400; line-height: 1.5; margin: 0px 0px 1.2em; word-break: break-all; font-family: Consolas, Monaco, "Andale Mono", "Source Code Pro", "Liberation Mono", Courier, monospace; display: block; padding: 15px; overflow-wrap: break-word; white-space: pre; overflow: auto; font-size: 1.1rem; background-color: rgb(247, 247, 247); border: none; border-radius: 3px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">T => List[T]
</pre>

For example:

<pre style="box-sizing: inherit; color: rgb(54, 54, 54); font-weight: 400; line-height: 1.5; margin: 0px 0px 1.2em; word-break: break-all; font-family: Consolas, Monaco, "Andale Mono", "Source Code Pro", "Liberation Mono", Courier, monospace; display: block; padding: 15px; overflow-wrap: break-word; white-space: pre; overflow: auto; font-size: 1.1rem; background-color: rgb(247, 247, 247); border: none; border-radius: 3px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">String => List[String]
</pre>

Given a proper type it will return another proper type you can think about it as a function that works at the type level, a type level function.

But wait we returned only a proper type, what if we return another first order type :

<pre style="box-sizing: inherit; color: rgb(54, 54, 54); font-weight: 400; line-height: 1.5; margin: 0px 0px 1.2em; word-break: break-all; font-family: Consolas, Monaco, "Andale Mono", "Source Code Pro", "Liberation Mono", Courier, monospace; display: block; padding: 15px; overflow-wrap: break-word; white-space: pre; overflow: auto; font-size: 1.1rem; background-color: rgb(247, 247, 247); border: none; border-radius: 3px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">List[] => WithMap[List[]]
</pre>

Or generalized to any one-hole type

<pre style="box-sizing: inherit; color: rgb(54, 54, 54); font-weight: 400; line-height: 1.5; margin: 0px 0px 1.2em; word-break: break-all; font-family: Consolas, Monaco, "Andale Mono", "Source Code Pro", "Liberation Mono", Courier, monospace; display: block; padding: 15px; overflow-wrap: break-word; white-space: pre; overflow: auto; font-size: 1.1rem; background-color: rgb(247, 247, 247); border: none; border-radius: 3px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">F[] => WithMap[F[]]
</pre>

Give a type level function we return another type level function.

Higher order functions are functions that returns functions at the value level , you can see the analogy here at the type level

The type of a type is called kind and uses * as notation to communicate what order they are.

  • String is of kind * and is Order 0
  • List[_] is of kind * -> * (takes one type and produce a proper type, Order 1) takes a String and produce a List[String]
  • Map[_,_] is of kind: * -> * -> * (takes two Order-0 types and produce a proper type, Order 1) takes a String,Int and produce a Map[String,Int]
  • WithMap[F[_]] of kind : (* -> *) -> * (take a Order 1 type (* -> *) and produce a proper type, Order 2)

This gives a visual way to talk about the type of types.

We abstracted over all the first order types with one hole, we can now define common functions between all of them for example :

<pre style="box-sizing: inherit; color: rgb(54, 54, 54); font-weight: 400; line-height: 1.5; margin: 0px 0px 1.2em; word-break: break-all; font-family: Consolas, Monaco, "Andale Mono", "Source Code Pro", "Liberation Mono", Courier, monospace; display: block; padding: 15px; overflow-wrap: break-word; white-space: pre; overflow: auto; font-size: 1.1rem; background-color: rgb(247, 247, 247); border: none; border-radius: 3px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">trait WithMap[F[_]] {def map[A,B](fa: F[A])(f: A => B): F[B]}
</pre>

You can mentally replace F by List or Option or any other first-order types. This allows us to define a map function over all first-order types.

Yes, that’s it, it allows us to define functions across a lot of different types in a concise way, this is very powerful but is not in the scope of this post. Just remember that now you have a way to talk about a range of types based on how many holes they have and not on what they represent (Option, List)

  • 1, "a", List(1,2,3) are values
  • Int, String, List[Int]are proper types
  • List[_], Option[_] are type constructors, takes a type and construct a new type, can be generalized with this syntax F[_]
  • G[F[_]] is a type constructor that takes another type constructor like, Functor[F[_]], can be tough of higher order function at the type level

TOUR OF SCALA

HIGHER-ORDER FUNCTIONS

Language

Higher order functions take other functions as parameters or return a function as a result. This is possible because functions are first-class values in Scala. The terminology can get a bit confusing at this point, and we use the phrase “higher order function” for both methods and functions that take functions as parameters or that return a function.

In a pure Object Oriented world a good practice is to avoid exposing methods parameterized with functions that might leak object’s internal state. Leaking internal state might break the invariants of the object itself thus violating encapsulation.

One of the most common examples is the higher-order function map which is available for collections in Scala.

val salaries = Seq(20000, 70000, 40000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)

doubleSalary is a function which takes a single Int, x, and returns x * 2. In general, the tuple on the left of the arrow => is a parameter list and the value of the expression on the right is what gets returned. On line 3, the function doubleSalary gets applied to each element in the list of salaries.

To shrink the code, we could make the function anonymous and pass it directly as an argument to map:

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)

Notice how x is not declared as an Int in the above example. That’s because the compiler can infer the type based on the type of function map expects. An even more idiomatic way to write the same piece of code would be:

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(_ * 2)

Since the Scala compiler already knows the type of the parameters (a single Int), you just need to provide the right side of the function. The only caveat is that you need to use _ in place of a parameter name (it was x in the previous example).

Coercing methods into functions

It is also possible to pass methods as arguments to higher-order functions because the Scala compiler will coerce the method into a function.

case class WeeklyWeatherForecast(temperatures: Seq[Double]) {

  private def convertCtoF(temp: Double) = temp * 1.8 + 32

  def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
}

Here the method convertCtoF is passed to the higher order function map. This is possible because the compiler coerces convertCtoF to the function x => convertCtoF(x) (note: x will be a generated name which is guaranteed to be unique within its scope).

Functions that accept functions

One reason to use higher-order functions is to reduce redundant code. Let’s say you wanted some methods that could raise someone’s salaries by various factors. Without creating a higher-order function, it might look something like this:

object SalaryRaiser {

  def smallPromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * 1.1)

  def greatPromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * math.log(salary))

  def hugePromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * salary)
}

Notice how each of the three methods vary only by the multiplication factor. To simplify, you can extract the repeated code into a higher-order function like so:

object SalaryRaiser {

  private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
    salaries.map(promotionFunction)

  def smallPromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * 1.1)

  def greatPromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * math.log(salary))

  def hugePromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * salary)
}

The new method, promotion, takes the salaries plus a function of type Double => Double (i.e. a function that takes a Double and returns a Double) and returns the product.

Methods and functions usually express behaviours or data transformations, therefore having functions that compose based on other functions can help building generic mechanisms. Those generic operations defer to lock down the entire operation behaviour giving clients a way to control or further customize parts of the operation itself.

Functions that return functions

There are certain cases where you want to generate a function. Here’s an example of a method that returns a function.

def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
  val schema = if (ssl) "https://" else "http://"
  (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
}

val domainName = "www.example.com"
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String

Notice the return type of urlBuilder (String, String) => String. This means that the returned anonymous function takes two Strings and returns a String. In this case, the returned anonymous function is (endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,453评论 0 13
  • NAME dnsmasq - A lightweight DHCP and caching DNS server....
    ximitc阅读 2,856评论 0 0
  • 笔破苍穹擎碧空, 铁扇簇簇托莲灯。 冰雕玉镂沁香远, 赏花何须顾春深。
    泗四坊方阅读 1,275评论 27 29
  • 寻找我,是为了寻找心中真正认可的自己。 ——题记 夜空正中,如纱般的薄云掩住了月,于是暗淡的夜色里,唯一能寻到的月...
    2020级1班阅读 97评论 0 0
  • 六年级七班祝全文 快乐学习,这是个我们必须面对的问题,人的本性就是寻求快乐,避免痛苦。如果我们一...
    诚信装饰祝希信阅读 155评论 0 2