初心者ロッカー向けのヒント(パート2)

パート2.すべてについて、そして何についても







今日は記事の最初の部分に収まらなかった多くの岩のようなイディオムについて説明ます。 言語とJavaとの相互作用、そして最も重要なこととして、Scalaのオブジェクト指向機能の誤用について見ていきます。







サイクル構造









式の長さ



Scalaでは、ほとんどすべてが式であり、何かがUnit



返す場合でも、常に()



出力を取得できます。 ステートメントが普及している言語での長期プログラミングの後、私たち(そして私も例外ではない)の多くは、すべての計算を1つの式に押し込み、長期的なトレーニングにしたいと望んでいます。 次の例は、Effective Scalaから大胆にドラッグしました。 タプルのシーケンスがあるとしましょう:







 val votes = Seq(("scala", 1), ("java", 4), ("scala", 10), ("scala", 1), ("python", 10))
      
      





1つの式で有名に処理できます(グループに分割、グループ内で要約、降順で並べ替え):







 val orderedVotes = votes .groupBy(_._1) .map { case (which, counts) => (which, counts.foldLeft(0)(_ + _._2)) }.toSeq .sortBy(_._2) .reverse
      
      





このコードはシンプルで、わかりやすく、表現力豊かですか? おそらく、犬を食べた岩だらけの犬のために。 ただし、式を名前付きコンポーネントに分割すると、誰にとっても簡単になります。







 val votesByLang = votes groupBy { case (lang, _) => lang } val sumByLang = votesByLang map { case (lang, counts) => val countsOnly = counts map { case (_, count) => count } (lang, countsOnly.sum) } val orderedVotes = sumByLang.toSeq .sortBy { case (_, count) => count } .reverse
      
      





おそらく、この例は十分に明確ではありません-なぜ、私はそれを自分で思い付くにはあまりにも面倒でした。 しかし、私を信じてください。著者がいくつかのラインに移すことすら気にかけない非常に長いデザインに出くわしました。

多くの場合、Sparkを使用してScalaにアクセスする人が多く、Sparkを使用する場合でも、変換の「トレーラー」を長く表現力豊かな「列車」に引っ掛けたいと思っています。 そのような表現を読むことは困難であり、ナレーションのスレッドはすぐに失われます。







余分な長い式と演算子表記







Scalaの2 + 2



が式2.+(2)



構文糖衣であることを皆さんが知っていることを願っています。 このタイプのレコードは、演算子表記法と呼ばれます。 そのおかげで、言語自体には演算子はありませんが、文字以外の名前ではありますがメソッドのみがあり、表現力豊かなDSLを作成できる強力なツールです(実際、このために記号表記が言語に追加されました)。 ドットと括弧なしで、必要な限りメソッド呼び出しを記録できます: object fun arg fun1 arg1



読み取り可能なDSLを作成する場合、これは非常にクールです。







 myList should have length 10
      
      





しかし、ほとんどの場合、長い式と組み合わせた演算子表記は連続的な不便さをもたらします。はい、括弧なしのコレクションの操作は見栄えが良く、名前付きコンポーネントに分割されている場合にのみ理解できます。







列車と後置記法







後置演算子は、特定の条件下で、不幸なパーサーの頭を回す可能性があるため、最近のバージョンのScalaでは、これらの式を明示的にインポートする必要があります。







 import language.postfixOps
      
      





この言語機能を使用しないようにして、ユーザーが使用する必要がないようにDSLを設計してください。 これは非常に簡単です。







初期化されていない値



Scalaは初期化されていない値をサポートします。 たとえば、これはBeanを作成するときに役立ちます。 次のJavaクラスを見てみましょう。







 class MyClass { // -,   Object   null. //     -. String uninitialized; }
      
      





Scalaから同じ動作を実現できます。







 class MyClass { //      Scala,  //     . var uninitialized: String = _ }
      
      





これを軽率に行わないでください。 可能な限り値を初期化します。 この言語構​​造は、使用しているフレームワークまたはライブラリがそれを強く主張している場合にのみ使用してください。 不注意に使用すると、大量のNullPointerException



NullPointerException



する可能性があります。 ただし、この機能について知っておく必要があります。そのような知識があれば時間を節約できます。 初期化を遅らせたい場合は、 lazy



キーワードを使用します。







nullを使用しない





ヌル値がモデルの一部である場合があります。 おそらく、この状況は、チームに参加するずっと前から、さらにScalaの導入よりもずっと前に発生したのでしょう。 sayingにもあるように、飲酒を防ぐことができない場合は、飲酒をすべきです。 Null Objectと呼ばれるパターンがこれに役立ちます。 多くの場合、これはADTの別のケースクラスです。







 sealed trait User case class Admin extends User case class SuperUser extends User case class NullUser extends User
      
      





何が得られますか? ヌル、ユーザーおよびタイプの安全性。







混雑について



方法







Scalaには、クラスコンストラクターをオーバーロードする機能があります。 そして、これは問題を解決する最良の方法ではありません。 もっと言いますが、これは問題を解決する慣用的な方法ではありません 。 実践といえば、この関数は、Javaのリフレクションを使用しており、ScalaコードがJavaから呼び出される場合、またはこの動作が必要な場合に役立ちます(この場合Builderを使用しない理由)。 その他の場合、最良の戦略は、コンパニオンオブジェクトを作成し、その中にいくつかのapply



メソッドを定義することです。







最も注目すべきケースはデフォルトのパラメーターに関する無知によるコンストラクターのオーバーロードです。







最近、私は次の不名誉を目撃しました:







 //  ! case class Monster (pos: Position, health: Int, weapon: Weapon) { def this(pos: Position) = this(pos, 100, new Claws) def this(pos: Position, weapon: Weapon) = this(pos, 100, weapon) }
      
      





が簡単に開きます:







 case class Monster( pos: Position, health: Short = 100, weapon: Weapon = new Claws )
      
      





モンスターにバズーカで報酬を与えたいですか? はい、問題ありません:







 val aMonster = Monster(Position(300, 300, 20), weapon = new Bazooka)
      
      





私たちは世界をより良くし、モンスターをより平和にしたと同時に、動くものすべてに過負荷をかけることを止めました。 もっと平和ですか? 間違いなく。 結局のところ、バズーカは楽器でもあります(ウィキペディアはこれについて黙っていません)。







これは設計者だけに当てはまるわけではありません。人々はしばしば従来の方法をオーバーロードします(これは回避できたはずです)。







オペレーターの過負荷







非常に物議を醸す機能Scalaと見なされます。 私が言語に没頭したとき、演算子のオーバーロードはどこでも、誰でも、どこでも、可能な限り使用されました。 現在、この機能はあまり人気がありません。 最初に、Parboiledやakka-httpのルーティングのように、DSLを作成できるようにするために、まずオペレーターのオーバーロードが行われました。







演算子を不必要にオーバーロードしないでください。必要があると思われる場合は、とにかくオーバーロードしないでください。







また、オーバーロードする場合(DSLが必要な場合、またはライブラリで数学的な(または言葉で表現するのが難しい)場合)、通常の名前の関数で演算子を複製してください。 そして、その結果について考えてください。 ありがとう、scalazオペレーター|@|



Applicative Builder )はMaculay Culkinと名付けられました。 そして、これが「犯人」の写真です。







ショックを受けたMaculay Culkin







もちろん、デザイナーを何度もオーバーロードした後は、画像を完成させるためにゲッターとセッターを貼り付ける必要があります。







ゲッターとセッターについて



ScalaはJavaとの優れた相互作用を提供します。 彼女はまた、いわゆる豆を設計するときにあなたの人生を楽にすることができます。 JavaまたはBeansの概念に慣れていない場合は、Javaに慣れてください







Project Lombokについて聞いたことがありますか? Scala標準ライブラリにも同様のメカニズムがあります。 BeanProperty



と呼ばれBeanProperty



。 必要なのは、Beanを作成し、ゲッターまたはセッターを作成する各フィールドにBeanProperty



アノテーションを追加することだけです。







ブール型変数のビュー名isProperty



を取得するには、 scala.beans.BooleanBeanProperty



をスコープに追加します。

@BeanProperty



は、クラスフィールドにも使用できます。







 import scala.beans.{BooleanBeanProperty, BeanProperty} class MotherInLaw { //  ,    : @BeanProperty var name = "Megaera" //      . @BeanProperty var numberOfCatsSheHas = 0 //    . @BooleanBeanProperty val jealous = true }
      
      





ケースクラスの場合も機能します。







 import scala.beans.BeanProperty case class Dino(@BeanProperty name: String, @BeanProperty var age: Int)
      
      





恐竜で遊ぼう:







 //   ,         val barney = Dino("Barney", 29) barney.setAge(30) barney.getAge // res4: Int = 30 barney.getName // res14: String = Barney
      
      





名前を変数にしなかったため、セッターを使用しようとすると、次のようになります。







 barney.setName <console>:15: error: value setName is not a member of Dino barney.setName
      
      





ケースクラスといえば



ケースクラスの出現は、JVMプラットフォームのブレークスルーです。 彼らの主な利点は何ですか? その不変性、および既製のequals



toString



、およびhashCode



の可用性においてそうです。 ただし、多くの場合、次のものを見つけることができます。







 //   var. case class Person(var name: String, var age: Int)
      
      





場合によっては、ケースクラスを変更可能にする必要があります。たとえば、上記の例のように、Beanを模倣する場合。

しかし、これはしばしば、深い後輩が免疫とは何かを理解していないときに起こります。 開発者にとって、レベルが高いことは、彼らが何をしているのかをよく知っているので、それほど興味深いものではありません。







 case class Person (name: String, age: Int) { def updatedAge(newAge: Int) = Person(name, newAge) def updatedName(newName: String) = Person(newName, age) }
      
      





ただし、誰もがcopy



方法を知っているわけではありません。 これが標準です。 私はそのようなことを何度も見ましたが、それはすでにそこにあり、かつて私はとてもフーリガンでした。 copy



は、タプル用に定義されている名前と同じように機能します。







 //  ,   . person.copy(age = 32)
      
      





ケースクラスサイズについて



場合によっては、ケースクラスが15〜20のフィールドに肥大化する傾向があります。 Scala 2.11より前では、このプロセスは22要素に制限されていました。 しかし今、あなたの手は解かれています:







 case class AlphabetStat ( a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int, x: Int, y: Int, z: Int )
      
      





まあ、私はあなたに嘘をつきました:もちろん、手はより自由になりましたが、だれもJVMの制限をキャンセルしませんでした。

大規模なケースクラスは不適切です。 これは非常に悪いです。 このための言い訳がある場合があります。あなたが働いているサブジェクトエリアでは集約が許可されておらず、構造が平らに見えます。 強力な精神安定剤に座っている深い馬鹿によって設計されたAPIを使用します。







そして、ご存知のように、ほとんどの場合、2番目のオプションに対処する必要があります。 ケースクラスがAPIに簡単かつ自然にフィットするようにします。 もしそうなら、私はあなたを理解しています。







しかし、私はあなたのケースクラスの極悪さの良い言い訳だけをリストしました。 最も明白なものがあります。ネストされたクラスの奥深くに隠されたフィールドを更新するには、多くの苦労が必要です。 各ケースクラスを慎重にソートし、値に置き換えて組み立てる必要があります。 そして、この病気には治療法があります。レンズを使用できます。







レンズがレンズと呼ばれるのはなぜですか? 彼らは主なことに集中できるからです。 構造の特定の部分にレンズの焦点を合わせ、それを取得するとともに、それを更新する機能(構造)を取得します。 まず、ケースクラスを宣言します。







 case class Address(street: String, city: String, postcode: String) case class Person(name: String, age: Int, address: Address)
      
      





次に、データを入力します。







 val person = Person("Joe Grey", 37, Address("Southover Street", "Brighton", "BN2 9UA"))
      
      





通りのレンズを作成します(キャラクターが動きたいと仮定します):







 import shapeless._ val streetLens = lens[Person].address.street
      
      





フィールドを読み取ります(文字列タイプが自動的に表示されることに注意してください):







 val street = streetLens.get(person) // "Southover Street"
      
      





フィールドの値を更新します。







 val person1 = streetLens.set(person)("Montpelier Road") // person1.address.street == "Montpelier Road"
      
      





例は、「 ここから」大胆に盗まれました

アドレスに対して同様の操作を実行できます。 ご覧のとおり、これは非常に簡単です。 残念ながら、おそらく幸いなことに、Scalaにはレンズが組み込まれていません。 したがって、サードパーティのライブラリを使用する必要があります。 shapeless



を使用することをお勧めします。 実際、上記の例はこのライブラリの助けを借りて書かれたもので、初心者にとって非常にアクセスしやすいです。







他にも多くのレンズの実装があります。必要に応じてscalazmonocleを使用できます。 後者は、光学を使用するためのより高度なメカニズムを提供します。さらに使用することをお勧めします。







残念ながら、レンズの作用メカニズムを説明するには、別の記事が必要な場合があります。したがって、光学システムの独自の研究を始めるには上記の情報で十分だと思います。







列挙型の再発明



経験豊富なJava開発者を引き取り、Scalaでの作成を強制します。 彼は必死に列挙型を探し始めたので、数日は過ぎません。 彼はそれらを見つけず、動揺しています。Scalaにはキーワードenum



または少なくともenumeration



はありません。 その後、イベントには2つのバージョンがあります。イディオムソリューションをグーグルで検索するか、独自の列挙を発明し始めます。 多くの場合、怠が勝ち、結果として次のようになります。







 object Weekdays { val MONDAY = 0 //    ... }
      
      





それから何? そして、ここに何があります:







 if (weekday == Weekdays.Friday) { stop(wearing, Tie) }
      
      





何が悪いの? Scalaには、ロシアの代数データ型でADT(代数データ型)と呼ばれる列挙を作成する慣用的な方法があります。 たとえば、Haskellで使用されます。 これは次のようなものです。







 sealed trait TrafficLight case object Green extends TrafficLight case object Yellow extends TrafficLight case object Red extends TrafficLight case object Broken extends TrafficLight
      
      





もちろん、詳細な、自作のリストはより短いものでした。 なぜそんなに書くの? 次の関数を宣言しましょう:







 def tellWhatTheLightIs(tl: TrafficLight): Unit = tl match { case Red => println("No cars go!") case Green => println("Don't stop me now!") case Yellow => println("Ooohhh you better stop!") }
      
      





そして、我々は得る:







 warning: match may not be exhaustive. It would fail on the following input: Broken def tellWhatTheLightIs(tl: TrafficLight): Unit = tl match { ^ tellWhatTheLightIs: (tl: TrafficLight)Unit
      
      





定数へのバインドのない列挙と、サンプルとの比較の完全性のチェックを取得します。 そして、はい、私の有名な同僚の1人が「貧しい人々のためのエヌム」と呼んだ場合、パターンマッチングを使用します。 これが最も慣用的な方法です。 注目に値する、これはScalaのプログラミングの本の冒頭で言及されています。 すべての鳥がドニエプルの真ん中に到達するわけではなく、すべてのロックマンがマグナムオーパスを読むわけでもありません。







奇妙なことに、代数的データ型はウィキペディアで説明されています。 Scalaに関しては、興味深いと思うかもしれないかなりアクセスしやすい投稿プレゼンテーションがあります。







関数シグネチャのブール引数を避けます



確かに、ブール値を引数として取るメソッドを作成しましたか? Javaの場合、状況は一般に悲惨です。







 PrettyPrinter.print(text, 1, true)
      
      





1はどういう意味ですか? 私たちは直感を信じて、これがコピーの数であると仮定します。 そして、 true



責任は何ですか? それは何でもかまいません。 さて、あきらめて、ソースに行き、それが何であるかを見てください。







Scalaでは、ADTを使用できます。







 def print(text: String, copies: Int, wrapWords: WordWrap)
      
      





論理引数を必要とするコードを継承する場合でも、デフォルトのオプションを使用できます。







 //    , //      ,  ? PrettyPrinter.print(text, copies = 1, WordWrap.Enabled)
      
      





反復について



再帰が優れている



末尾再帰は、ほとんどのループよりも高速です。 もちろん、それが尾の場合。 確認するには、 @tailrec



アノテーションを使用します。 状況は異なりますが、常に再帰的な解決策が簡単にアクセスできて理解できるとは限らないので、 while



を使用while



ます。 それには何の問題もありません。 さらに、コレクションライブラリ全体は、前提条件を持つ単純なループで記述されています。







反復ではなく内包表記の場合(インデックスによる)



リストジェネレーター、または "内包表記"とも呼ばれるリストジェネレーターについて知っておくべき主なことは、その主な目的がループを実装しないことです。







さらに、この構成を使用してインデックスを反復処理することは、非常に費用のかかる手順になります。 while



または末尾再帰の使用は、はるかに安価です。 より明確に。







理解のために、 map



flatMap



およびwithFilter



構文糖衣がありmap



yield



キーワードは、結果の構造の値の後続の集約に使用されます。 「理解のために」を使用すると、実際には、同じコンビネータをベールに包まれた形で使用します。 それらを直接使用します。







 //  1 to 10 foreach println //  for (i <- 1 to 10) println(i)
      
      





同じコードを呼び出したという事実に加えて、特定の変数i



も追加しましたが、これは絶対にここには属しません。 速度が必要な場合は、 while



使用しwhile









変数名について







質問回答形式の変数名の興味深い歴史:







質問i



j



k



はサイクルパラメータとしてどこから来たのですか?

回答 :数学から。 そして、Fortranのおかげでプログラミングに参加しました.Fortranでは、変数の型は名前で決定されます:名前の最初の文字がI、J、K、L、M、またはNで始まる場合、これは変数が整数型に属することを自動的に意味します。 それ以外の場合、変数は実数と見なされます( IMPLICIT



ディレクティブを使用してデフォルトの型を変更できます)。







そして、この悪夢は私たちとほぼ60年間生きてきました。 行列を乗算しない場合、Javaでもi



j



、およびk



を使用する言い訳はありません。 index



row



column



使用しindex



。 ただし、Scalaで記述する場合は、 for



内の変数の反復を避けてください。 lukvogoそれから。







このセクションに追加されるのは、リストジェネレーターについて知りたいことすべてを詳しく説明したビデオです。







式について



リターンを使用しないでください



Scalaでは、ほとんどすべてが表現です。 例外はreturn



です。どのような状況でも使用しないでください。 多くの人が考えるように、これはオプションの単語ではありません。 これは、プログラムのセマンティクスを変更する設計です。 詳細については、 こちらをご覧ください。







タグを使用しないでください



Scalaにタグがあることを想像してください。 そして、なぜそこに追加されたのかわかりません。 Scala 2.8より前は、 continue



ラベルはこのアドレスにあり、後で削除されました。







幸いなことに、ラベルは言語の一部ではありませんが、例外をスローしてキャッチすることで実装されます(この記事の後半で例外の処理方法について説明します)。

私の実践では、このような行動を少なくとも正当化できる単一のケースにはまだ出会っていません。 私がネットで見つけた例のほとんどは、手に負えず、私の指から吸い出されています。 いいえ、よく見ます:







この例はここから取られます







 breakable { for (i <- 1 to 10) { println(i) if (i > 4) break //   . } }
      
      





例外的な状況について



この例外的なトピックは、大きく別の記事に値します。 おそらく一連の記事ですら。 これについては長い間話すことができます。 まず、Scalaは例外処理に対するいくつかの根本的に異なるアプローチをサポートしているためです。 状況に応じて、最適なものを選択できます。







Scalaにはチェック済みの例外はありません。 - — . , . , . , . — . , C++ Java, Scala . - . goto



. . , flow. , , , — Scala .







, , Validation



scalaz, scala.util.Try



, Either



. Option



, , . - , .









. , , , . -, , , — . .







? extends App



:







 object Main extends App { Console.println("Hello World: " + (args mkString ", ")) }
      
      





« ». , , . . DelayedInit



. . App



, , DelayedInit



. App



.







It should be noted that this trait is implemented using the DelayedInit functionality, which means that fields of the object will not have been initialized before the main method has been executed.

-:







, DelayedInit, , main.

:







Future versions of this trait will no longer extend DelayedInit.

App



? . «Hello world»? . main



.







コレクション



Scala. :







 tvs.filter(tv => tv.displaySize == 50.inches).headOption
      
      





, :







 tvs.find(tv => tv.displaySize == 50.inches)
      
      





«» :







 list.size = 0 //  list.isEmpty // ok !list.empty //  list.nonEmpty // ok tvs.filter(tv => !tv.supportsSkype) //  tvs.filterNot(tv => tv.supportsSkype) // ok
      
      





, IntelliJ IDEA, . Scala IDE, , .

Scala . Scala collections Tips and Tricks , . , . .









, .









, Scala-. , , .









Scala







記事





映像





謝辞











EDU- DataArt , , Scala, . , , , ( ) .








All Articles