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 首先支持泛型類赞辩,不過我們經(jīng)常也會(huì)遇到泛型的 trait,對(duì)于泛型的trait 在我們使用 map flatmap操作集合的時(shí)候 中非常常見螟蝙,對(duì)于出現(xiàn) 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

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

Repr 是內(nèi)部用來保存元素的集合類型咒锻,B是函數(shù)f 穿件的元素類型许蓖。 That 是我們想要?jiǎng)?chuàng)建的目標(biāo)集合的類型參數(shù),它可能與輸入的原始集合相同投蝉,也可能不同。

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"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末征堪,一起剝皮案震驚了整個(gè)濱河市墓拜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌请契,老刑警劉巖咳榜,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夏醉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涌韩,警方通過查閱死者的電腦和手機(jī)畔柔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臣樱,“玉大人靶擦,你說我怎么就攤上這事」秃粒” “怎么了玄捕?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棚放。 經(jīng)常有香客問我枚粘,道長(zhǎng),這世上最難降的妖魔是什么飘蚯? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任馍迄,我火速辦了婚禮,結(jié)果婚禮上局骤,老公的妹妹穿的比我還像新娘攀圈。我一直安慰自己,他們只是感情好峦甩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布赘来。 她就那樣靜靜地躺著,像睡著了一般凯傲。 火紅的嫁衣襯著肌膚如雪犬辰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天泣洞,我揣著相機(jī)與錄音忧风,去河邊找鬼默色。 笑死球凰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腿宰。 我是一名探鬼主播呕诉,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吃度!你這毒婦竟也來了甩挫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤椿每,失蹤者是張志新(化名)和其女友劉穎伊者,沒想到半個(gè)月后英遭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亦渗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年挖诸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片法精。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡多律,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搂蜓,到底是詐尸還是另有隱情狼荞,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布帮碰,位于F島的核電站相味,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏收毫。R本人自食惡果不足惜攻走,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望此再。 院中可真熱鬧昔搂,春花似錦、人聲如沸输拇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽策吠。三九已至逛裤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猴抹,已是汗流浹背带族。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蟀给,地道東北人蝙砌。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像跋理,于是被迫代替她去往敵國(guó)和親择克。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,446評(píng)論 0 13
  • NAME dnsmasq - A lightweight DHCP and caching DNS server....
    ximitc閱讀 2,810評(píng)論 0 0
  • 筆破蒼穹擎碧空前普, 鐵扇簇簇托蓮燈肚邢。 冰雕玉鏤沁香遠(yuǎn), 賞花何須顧春深拭卿。
    泗四坊方閱讀 1,258評(píng)論 27 29
  • 尋找我骡湖,是為了尋找心中真正認(rèn)可的自己贱纠。 ——題記 夜空正中,如紗般的薄云掩住了月响蕴,于是暗淡的夜色里并巍,唯一能尋到的月...
    2020級(jí)1班閱讀 91評(píng)論 0 0
  • 六年級(jí)七班祝全文 快樂學(xué)習(xí),這是個(gè)我們必須面對(duì)的問題换途,人的本性就是尋求快樂懊渡,避免痛苦。如果我們一...
    誠(chéng)信裝飾祝希信閱讀 147評(píng)論 0 2