7

Scala の高階関数定義から提供された例にジャンプする方法を理解するのに苦労しています。これは、スライド 81のこのスライド ショーで提供されました。

高階関数の定義は次のとおりです。

trait X[A] { def map[B](f: A => B): X[B] }

提供されている例は次のとおりです。

(1 to 10) map { x => x * 2 } // evaluates to Vector(2, 4, ..., 20)
(1 to 10) map { _ * 2 }      // shorthand!

は?!ここにはいくつかの手順が必要です。例では、関数定義といくつかの Scala の優れた点の両方を活用している可能性があることがわかりました。私は、Scala を読んだり、関連する仮定を作成したりするのに十分な経験がありません。

私のバックグラウンドは Java OO です。現在、Scala と関数型プログラミングを学んでいます。そして、これは私が理解していないこのような最初の例ではありません. 無知に見えることを知って投稿する勇気があったと感じたのはこれが初めてです。

これについて調べてみました。最初に、Scala の「バイブル」、「Programming in Scala 2nd Edition」に行き、そこから if を理解しようとしました (165 ~ 9 ページ)。次に、StackOverflow で検索を行いました。そして、その地域について話しているいくつかのリンクを見つけました。しかし、このスライドの特定のインスタンスに対応する方法で、Scala の高階関数定義と提供された例との関係を段階的に示しているものは実際には何もありません。

StackOverflowで見つけたものは次のとおりです。

  1. Scala: ワークショップのアドバイス
  2. 一般的な Scala 関数の詳細
  3. Scala:「ジェネリック」関数パラメータを定義する方法は?

Google をスキップして、StackOverflow に直接アクセスしたことに気付きました。うーん。グーグルで適切なリンクを見つけたら、ぜひご覧ください。サルモナド、ブラストモーフィズムなどの用語を使用するすべての Google リンクをふるいにかける時間はありませんでしたが、さらに混乱し、これを理解しようとする可能性が低くなりました。

4

7 に答える 7

6

高階関数(またはメソッド)は、関数をパラメーターとして受け取るか、結果として関数を生成するか、またはその両方を行う関数/メソッドです。

この場合、それはmapリスト、配列、および他の多くの種類のコンテナなどで定義されたと呼ばれるメソッドです。1 to 10Scalaで表される1から10までの数値の範囲であるaで呼び出されると、Range[Int]それらを1つずつトラバースし、範囲内のすべての数値に関数(パラメーターとして渡されたもの)を適用します。この関数の結果は、新しいコンテナに蓄積されます。この場合は、メソッドVector[Int]の結果として返されます。map

つまり(1 to 10) map { x => x * 2 }、これはの糖衣構文であり、からの数に(1 to 10).map(x => x * 2)適用されます。これは、コールバック関数と考えることができます。次のように書くこともできます。x => x * 21 to 10

(1 to 10).map( new Function1[Int, Int] {
   override def apply(x: Int) = x * 2
})
于 2012-02-26T16:26:39.823 に答える
6

以下は、Scala のコレクション プロパティの一部を表示する目的で提供されたシグネチャの例にすぎないと思います。特に、実装を示していないため、すべての点を実際に接続することはできません。また、実際には例と一致していません...したがって、これは混乱を招く可能性があります。

trait X[A] { def map[B](f: A => B): X[B] }

X私はそれを次のように読みます: type の要素に対するコレクションクラスを考えるとA

  • 型でパラメータ化されたmap関数があるB
  • 関数は、シングルをシングルに変換する関数mapを取りますfAB
  • mapXtypeの要素に対して同じ型のコレクションを返しますB

次に、使用例にジャンプします。

(1 to 10) map { x => x * 2 }

したがって、ドットを接続します。

  • コレクションXは (1 から 10) のタイプで、ここではRange
  • f: A => Bは、 を取り、を返すx => x * 2関数として推論されます。IntInt
  • 署名を考えると、Rangeoverが返されると思うかもしれませんがInt、これは実際にはIndexedSeq.

より良い例は次のとおりです。

List(1, 2, 3).map(i => i + "!") // a List[Int]
// returns a List[String]: List("1!", "2!", "3!") 
于 2012-02-26T16:47:26.327 に答える
5

単一リンクリストである map メソッドでデータ型を定義しましょう。

sealed abstract class MyList[+A] {
  def map[B](f: A => B): MyList[B]  // higher order function declaration.
  def head: A
  def tail: MyList[A]
}
case class Cons[A](head: A, tail: MyList[A]) extends MyList[A] {
  def map[B](f: A => B): MyList[B] = Cons[B](f(head), tail.map(f))
}
case object Nil extends MyList[Nothing] {
  def map[B](f: Nothing => B): MyList[B] = this
  def head = sys.error("head on empty list")
  def tail = sys.error("tail on empty list")
}

リストは空であるか、単一の値 ( head) がリストの残りの部分 ( ) とペアになっていtailます。シールされた親クラスから拡張されたクラス階層として、2 つのケースを表しMyListます。

の実装に注意してください。最初に を使用して関数を実行し、次に への再帰呼び出しに渡すためにCons#map、パラメータを2 回使用しました。fheadtail.map

構文f(head)は の省略形でf.apply(head)、値はメソッドを定義しfたクラスのインスタンスです。Function1apply

ここまでは順調ですね。リストを作成しましょう。

val list: MyList[Int] = Cons(1, Cons(2, Nil))

ここで、各要素を変換することにより、 のリストを のInts新しいリストに変換したいと考えています。StringJava では、次のように を明示的に匿名でサブクラスFunction1化します。

// longhand:
val stringList1: MyList[String] = list.map[String](new Function1[Int, String] {
  def apply(a: Int): String = a.toString
})

これは Scala では合法ですが、信号対雑音比は良くありません。代わりに無名関数構文を使用しましょう。

val stringList2: MyList[String] = list.map[String]((a: Int) => a.toString)

さらに進んで、明示的な型注釈を省略できます。コンパイラがそれらを推測するのに十分な情報を持っている場合です。

aまず、の要素の型に基づいて、 parameter の型を推測しましょうlist

val stringList3: MyList[String] = list.map[String](a => a.toString)

このような非常に単純な関数は、プレースホルダー構文でも表現できます。パラメータを宣言するのではなく、コードを記述して_不明な数量に使用します。これを行い、 の型をstringList4推測できるようにします:

val stringList4 = list.map(_.toString)
于 2012-02-26T16:25:56.687 に答える
3

特性 X で定義されているように定義されているmapメソッドに集中しましょう

def map[B](f: A => B): X[B]

わかりました、これは 1 つのパラメーターを持つメソッドfです。f(コロンの後のビット)の型は ですA => B。これは関数型です。これは特性の省略形ですFunction1[A, B]が、私はこれらの特性をまったく考えず、単にA の特定の値に対して B を生成できるものと考えることを好みます。

したがって、関数A => Bは、 type の単一のインスタンスを取り、 typeAの単一のインスタンスを生成するものBです。それらを宣言するいくつかの例を次に示します

val f = (i: Int) => i.toString //Int => String
val g = (_ : String).length    //String => Int, using placeholder syntax

それでは、何が何でX[A]あるかを考えてみましょう。などのコレクション型である可能Listがあります。上記の aList[String]String => Int関数を所有している場合、リスト内の各要素に関数を適用し、結果を使用して新しいリストを作成することで、明らかに a in を取得できます。gList[Int]

したがって、次のように言えます。

strings map g //strings is a List[String]

しかし、Scala では関数を匿名で宣言できます。どういう意味ですか?これは、val または var として宣言するのではなく、インラインで使用する時点で宣言できることを意味します。多くの場合、これらは「ラムダ」と呼ばれます。あなたが困惑している構文は、そのような関数の 2 つのオプションです。

strings map { (x: String) => x.length }
strings map { x => x.length }
strings map { _.length }
strings map ( _.length )

これらはすべて基本的に同じものです。1 つ目は、map に渡される関数を明確に宣言します。scala には型推論があるため、このユース ケースでは関数入力の型を省略できます。プレースホルダー構文 _ は識別子の代わりに使用さxれ、入力を一度だけ参照する必要がある場合に便利です。また、マルチステートメント関数を除いて、多くの場合、中括弧の代わりに括弧を使用できます。

于 2012-02-26T17:06:29.770 に答える
2

何が得られないのか正確にはわかりませんが、例を説明するには:

trait X[A] { def map[B](f: A => B): X[B] }

Atraitは Java インターフェースのようなもので、具体的なメソッドを持つこともできます。

Xは特性名で[A]あり、型パラメーターです。Java ジェネリックを考えてみてください<A>。(多くの場合ABetc はコレクションなどの要素の型に使用されますTが、これは単なる慣例です。)

mapこの特性は、別の型パラメーターを持つメソッドであり[B]、型の関数引数を取り、A => B戻り値の型が である、というメンバーを指定しますX[B]。メソッド本体がないため、ここでは抽象的です。

あなたが見逃しているかもしれないビットは、のA => B略ですFunction1[A, B]Function11 つの引数を取る関数オブジェクトの型です。(A, B) => Cは etc の略です。Java でFunction2[A, B, C]独自の型を作成できますFunction。これは楽しい演習です。関数オブジェクトは、基本的にはapplyメソッドを持ち、いくつかの引数から結果を生成するオブジェクトです。

(1 to 10) map { x => x * 2 }    

これらにはポイントフリー表記が含まれており、a.method(b)は と書かれていa method bます。を取得してを生成するto方法も同様です。Function1 引数を取るメソッドです(関数は単なる type のオブジェクトであることを思い出してください)。RichIntIntRangemapRangeFunction1

=>は、関数自体を記述するためにも使用されます (上記の型レベルに加えて)。したがって、以下は同じで、 type のすべてのオブジェクトですInt => Int

(x: Int) => x + 1
new Function1[Int, Int] { def apply(x: Int) = x + 1 }
  // note Function1 is a trait, not a class, 
  // so this the same as `new Object with Function[Int, Int]`
new (Int => Int) { def apply(x: Int) = x + 1 }

Scala は型推論を使用するため、コンテキストから特定の関数型 (またはその他のパラメーター化された型) が期待される場合、すべての型を自分で追加する必要はありません。

val f : Int => Int  =  x => x + 1
val f : Int => Int  =  _ + 1

うまくいけば、このアンダースコア表記が何を意味するかがわかります。関数定義の RHS は LHS のパラメーターを使用する必要があるため、アンダースコアがないと常に繰り返しが発生するため、アンダースコアは便利です。String別の例として、aをその長さにマッピングする関数があります。

val f: String => Int = _.length

val の型は通常推論されるため、必要な型注釈のみを

val f = (_: String).length

構文シュガーと型推論は、同じことを記述する方法がいくつかあることを意味するため、おそらく少し混乱しますが、一度理解すると、生活を楽にし、ノイズを減らすためにそこにあることがわかります. まだ行っていない場合は、REPL でこれらを試してみてください。

于 2012-02-26T16:50:37.987 に答える
2

あなたの例は、コレクションを変換するときに最も具体的な型を生成するために、型システムの洗練された使用法を持つScala Collection Frameworkを参照しています。さて、これを可能にするメカニズムは把握するのが難しく、例とはあまり関係がありません。高階関数とは、他の関数を引数として取る (または返す) 単純関数またはメソッドです。この例は、型パラメーターを追加し、Scala Collection Frameworks の暗黙の使用法について言及していないため、多少わかりにくくなっています。

于 2012-02-26T16:41:17.517 に答える
1

     Scala:(1 to 10) map { x => x * 2 }
     英語: 1 から 10 までの値を取り、それぞれに 2 を掛けます。

注意すべき点:

  • (1 から 10) の場合、Scala はこれが整数のコレクション、具体的には Range[Int] であることを認識します。これは、別のコレクション タイプに変換できます。(1 to 10).toList

  • map は小文字です。動詞を考えて、物から別のものにマッピングします。

  • {x => x * 2}、中かっこで囲まれています。これは、名前のない関数、無名関数であることを意味します。

  • アンダーバー (_) はx => x


     Scala:trait X[A] { def map[B](f: A => B): X[B] }
     英語:
       X のクラス、タイプ A に追加できるトレイトを定義します。
       これには、値を取り、それを X の新しいクラスの別の値にマップするメソッドがあります。

ノート:

  • X[A]X[B]は同じコレクション型ですが、異なる型の要素を持つことができます。`(1 to 10).toList map { _.toSTring } は、List[Int] を List[String] にマップします。

  • f: A => B、これは map が引数として関数を取り、型 A の 1 つのパラメーターを持ち、型 B を返すことを意味します。

  • mapすべての Scala コレクション型で定義されています。通常、これを自分で定義することはありません。

于 2012-02-26T22:05:37.080 に答える