2012-02-26 6 views
7

Scalaの高次関数定義から提供された例へのジャンプの方法を理解するのが難しいです。それはのslide 81に提供されました。ここでScala高次関数と提供された例との間の段階的な接続

は、高次関数の定義です:

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の "bible"("Programming in Scala 2nd Edition")に行って、そこから(165-9ページ)感覚を覚えようとしました。次に、StackOverflowでここで検索しました。そして、その周辺を話すいくつかのリンクを見つけました。しかし、実際には、Scalaの高次関数定義と、このスライドの特定のインスタンスにマップされる方法で提供されたサンプルとの間の接続、STEP-BY-STEPを私に示しているものはありません。

は、ここで私はStackOverflowの上で見つけたものです:私はちょうど今、私がグーグルをスキップしてStackOverflowのにまっすぐに来たことを実現しています

  1. Scala: Workshop Advice
  2. More on generic Scala functions
  3. Scala: How to define "generic" function parameters?

。うーん。あなたがグーグルで正しいリンクを見つけたら、それを見るのが大好きです。私は、猿モナド、奇形などの用語を使用するGoogleのすべてのリンクを調べるために時間を使い果たしましたが、私をさらに混乱させ、これを試してみる可能性は低いです。

+3

不思議...例はフラットアウト偽の特性の定義と矛盾します。 'X [A] .map'からの結果は、' 'X [B]であるべきであるが、' Range.map'は、実施例では、 ''ベクトル[INT]に評価なお、!これは、 'Range'の' map'が最初に型シグニチャを持たないために起こります。この例を完全に無視し、基本的なクラス定義から構築して新しい概念を説明する、@ retronymの答えを見てみることをお勧めします。 –

+0

聖なるトンネル症候群!そう多くの答え。 Tysvm!あなたの指導のためにダニエルに感謝します。 – chaotic3quilibrium

答えて

6

以下は、Scalaのコレクションプロパティの一部を表示するためのシグネチャの例です。特に、実装を示していないので、実際にすべての点を接続することはできません。 また、実際には例と一貫していません...これは混乱している可能性があります。タイプAの要素の上にコレクションクラスXに与えられた:

  • それはタイプにパラメータ化されmap機能を持っているがB
  • map関数が取るよう

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

    私はそれを読んでいました機能fシングルをAに変換すると、B

  • mapは、タイプBの要素に対して同じタイプのXのコレクションを返します。

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

    をしたがって、ドットを結ぶ:

    • コレクションXは(1〜10)の一種であり、ここで

    そしてそれが使用して示すために実施例にジャンプRange

  • f: A => BIntと戻り、を取る関数として推定されるx => x * 2であります。
  • Intを超えてRangeが返されますが、これは実際にはIndexedSeqを返します。

より良い例があったかもしれない:

List(1, 2, 3).map(i => i + "!") // a List[Int] 
// returns a List[String]: List("1!", "2!", "3!") 
+0

ありがとうございます。そして私は、その形質が提供されている例と一致していないとあなたが思い出すことが好きです。あなたの答えを通して読むことは簡単で、完全な意味合いを持っていました。 – chaotic3quilibrium

5

マップメソッド、単独リンクリストでデータ型を定義しましょう。

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") 
} 

リストが空であるか、またはそれがリスト(tail)の残りの部分と対になった単一の値(head)です。密接な親クラスMyListから継承した、クラス階層としての2つのケースを表します。

Cons#mapの実装、我々はheadで機能を実行するために、そして第二にtail.mapへの再帰呼び出しに沿って渡すために、まず、二度パラメータfを使用しました。

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

これまでのところ、とても良いです。リストがリストを構築する。

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

は今、私たちは、各要素を変換することにより、String Sの新しいリストにIntsのリストを変換したいです。 Javaでは、明示的に匿名でサブクラス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) 

我々はさらに行くと、コンパイラはそれらを推測するのに十分な情報を持っている明示的な型注釈を省略することができます。

まず、要素タイプlistに基づいて、パラメータaのタイプを推測します。

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

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

val stringList4 = list.map(_.toString) 
+0

'trait'ではなく' abstract class'を使う理由は何ですか?特にパフォーマンスに違いはありますか?トレイトで定義されたメソッドを呼び出すときに –

+2

小さな、理論的なパフォーマンスの低下があります - それは特色に転送引数に混入されていることをクラスでは。これは、HotSpotがインラインで行うことができるため、めったに問題にはなりません。パフォーマンスにかかわらず、抽象クラスはここで最も自然な選択だと主張したい。あなたの答えは – retronym

+0

@retroynm Tyvmです。ドットを結ぶより良い問題を作成していただき、本当にありがとうございます。私の混乱の大部分は、その特性が具体的に例に関連していなかったという事実から来たものだと私は思う。 – chaotic3quilibrium

6

高次関数(又はメソッド)は、その結果として、そのパラメータとして関数を取るまたは機能をもたらすいずれかの関数/メソッドでありますまたは両方。

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

ので(1 to 10) map { x => x * 2 }(1 to 10).map(x => x * 2)ための糖衣構文が1 to 10から番号にx => x * 2を適用ところであります。コールバック関数と考えることができます。また、このようにそれを記述することができ:

(1 to 10).map(new Function1[Int, Int] { 
    override def apply(x: Int) = x * 2 
}) 
+0

最後の例が本当に好きです。これは、単純化の後ろで起こっていることの詳細を理解するのに役立ちます(コードの定型文の削減)。 – chaotic3quilibrium

2

あなたの例では、自身がコレクションを変換する際に、最も具体的なタイプを生成するタイプのシステムのいくつかの洗練された用法を持ってScalaのコレクションフレームワーク、を参照してください。さて、the mechanismこれは把握が難しく、実際の例とは関係ありません。高次関数は、単にa function or methodで、引数として(または返される)other functionsをとります。この例では、型パラメータを追加し、Scala Collection Frameworkのimplicitsの使用方法については言及していません。

+0

Tyvm。それは私が接続を作るのに役立ちます。すなわち、その形質はScalaコレクションライブラリから得られる。そして、以下の行は、ライブラリのこれらの種類の関数定義の多くを活用しています。 – chaotic3quilibrium

2

ないあなたが得ることはありませんが、例を説明するために正確に何を確認してください。

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

traitは、Javaインターフェースのようなものです具体的な方法もあります。

Xは型名であり、[A]は型パラメータです - Java genericsと思う<A>。 (多くの場合ABなどがコレクション内の要素タイプに使用してT他の場所で、それは単に慣例だされている。)

特色は、メンバーが別の型パラメータ[B]とする方法である、mapと呼ばれ、機能を取る指定戻り値の型がX[B]A => B型の引数です。メソッド本体がないのでここでは抽象です。

あなたが欠けているかもしれないビットはA => BFunction1[A, B]の略です。 Function1は、1つの引数を取る関数オブジェクトの型です。 (A, B) => CFunction2[A, B, C]などの略です。Function型をJavaで作ることができます。これは楽しい試行です。関数オブジェクトは基本的にはapplyメソッドを持つメソッドであり、いくつかの引数から結果を生成します。

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

これらはa.method(b)a method bように書かれている点を含まない表記を含みます。従ってtoRichIntの方法でIntをとり、Rangeを生成する方法である。 mapは、Function1引数を取る方法です(関数はFunction1のオブジェクトであることに注意してください)。Rangeのメソッドです。

=>は、機能自体を記述するためにも使用されます(前述のタイプレベルに加えて)。したがって、次のタイプ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をマッピングする機能が考えられます。

val f: String => Int = _.length 

ヴァルスの種類が正常に推測されているので、あなたが

val f = (_: String).length 

でのみ必要な型注釈を提供することができますそれはおそらく少し混乱だから構文的な砂糖とタイプの推論は、同じことを書くいくつかの方法があることを意味しますが、一度それを得ると、あなたの人生を楽にし、騒音を減らすことができます。あなたがまだいない場合は、REPLでこれらと良い遊びをしてください。

+0

うわー!それは非常に詳細な説明でした。私は今、形質が間違って2つの例(ダニエル・ソブラルといくつかの他のポスター)に関連していたことを知っています。そして私は今、これは非自明なケースであることを起こるそれらの「簡素化」(定型)のすべてを理解し始めています。 – chaotic3quilibrium

3

ので、これは一つのパラメータ、fと方法で、定義されているあなたの特性X

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

OKを定義されているのは、ちょうどマップ方法に集中しましょう。 f(コロンの後のビット)のタイプはA => Bです。これは関数型です。形質Function1[A, B]の省略形ですが、私はこれらの形質をまったく考えずに、Aの与えられた値に対してBを生成することができる何かと思っています。

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

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

だから今X[A]が何であるかを考えます。 は、Listのようなコレクションタイプになります。List[String]と上記のString => Intファンクションgを所有している場合、は明らかにList[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) 

これらはすべて基本的に同じものです。最初は、関数がマップに渡されることを明確に宣言します。スカラには型推論があるので、このユースケースでは関数入力の型を省略できます。プレースホルダシンタックス_は識別子xの代わりに使用され、入力を一度参照する必要がある場合は、砂糖の素晴らしいビットです。また、多くの場合、複数のステートメント機能を除いて、中括弧の代わりに括弧を使用することができます。

+1

あなたの最後の例では、簡略化の進行を見せる(コード定型の除去)(「文字列はマップ」で始まる4コード行は)非常に役に立ちました。より多くのドットがつながります。 – chaotic3quilibrium

+1

これは面白いです。数年前、私はまったく同じ事**によって謎に包まれました**しかし、今は第二の性質です。スカラのあること。あなたが困惑しているところでは、正確な問題に固有の、いくつかの特別な言い回しがあるという結論に簡単にジャンプできます。それはほとんど決してありません!あなたの答えは –

1

       スカラ:(1 to 10) map { x => x * 2 }
       英語:注意すべきいくつかのものを1〜10、値を取り、そして2

で各1を乗算:

  • (1〜10)、Scalaはこれが整数のコレクションであり、特にRange [Int]であることを認識します。別のコレクション型に変換することができます。 (1 to 10).toList

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

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

  • アンダバー(_)は

           スカラx => x


と置換することができ:trait X[A] { def map[B](f: A => B): X[B] }
       英語:
       私たちは、それが値をとり、Xの新しいクラスの別の値にそれをマップする方法を持っている

            、我々はXのクラスに追加することができる形質を定義する型A. 

注:

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

  • f: A => Bは、このマップを引数として関数を取り、それがタイプAの一つのパラメータを有し、リターンがScalaのコレクション型の全てに定義されているB.

  • map入力手段。あなたは通常これを自分で定義しません。

+0

Tyvmです。ついに私はこの特性をかなりはっきりと理解しています。そして、私は今、この形質がスライドショーの形質の下で提供された例に対応していないことを理解しています。 – chaotic3quilibrium