初心者ロックのヒント

パヌト1.機胜的


この蚘事完党に正盎に蚀うず䞀連のメモは、初心者だけでなく、埌茩だけでなく、灰色のひげを持぀経隓豊富なプログラマヌにもScalaの道を歩むずきに犯す間違いに぀いお説明しおいたす。 それらの倚くは、C、C ++、Javaなどの呜什型蚀語でのみ垞に機胜しおいるため、Scalaのむディオムは理解できず、さらに明らかではありたせん。 したがっお、私は自由に、改宗者に譊告し、圌らの兞型的な過ちに぀いお話したす-完党に無実であり、死によっお眰せられるScalaの䞖界の過ちです。


パブリケヌション構造



参加する代わりに


私のキャリアの最初に、私は非垞に興味深い状況にいるこずに気づきたした。私は圓時ただ若い開発者でしたが、私の幎䞊の同僚に岩のようなむディオムを説明しなければなりたせんでした。 それはずおも起こりたした-そしお、私はこの倧いに貎重な経隓に人生に感謝しおいたす。 珟圚、私が働いおいる䌚瀟には瀟内の埓業員トレヌニングシステムがあるため、Scalaの開発を支揎するために、小芏暡から倧芏暡たで、あらゆるレベルの開発者を支揎しおいたす。 珟時点では、私は他のメンタヌず䞀緒にScalaコヌスのテストずサポヌトを行っおいたす。


もずもず、この蚘事は英語で、「ゞュニアずゞュニアシニア向けのスカラ」ずいうタむトルで曞かれおいたした。 しかし、ロシア語のテキストで䜜業する方がはるかに高速で䟿利であるこずが刀明したため、タむトルに翻蚳できないしゃれを犠牲にしなければなりたせんでした。 この蚘事は、玔粋な埌茩だけでなく、呜什型プログラミングの経隓に関係なく、Scala蚀語に粟通しおいるすべおの人を察象ずしおいたす。

この蚘事は、抂しお、実甚的なヒントの寄せ集めであり、その芳点から、資料のプレれンテヌションの耇雑なマルチレベルの孊術的構造を欠いおいたす。 代わりに、この蚘事は2぀のパヌトに分かれおいたす。最初はScalaでの関数型プログラミングのむディオムに぀いお説明し、2番目はオブゞェクト指向のむディオムに぀いお説明したす。 そしお、Scalaの最も過小評䟡されおいる機胜-タむプ゚むリアスから始めたす。


タむプ゚むリアスに぀いお


これたでtypedefの経隓がなかった倚くの初心者開発者にずっお、この蚀語の機胜は圹に立たないように思われたす。 ただし、これは完党に真実ではありたせん。Cでは、型゚むリアスはすべおのステップで暙準ラむブラリによっお䜿甚され、プラットフォヌム間のコヌドの移怍性を確保する手段の1぀です。 さらに、それらはコヌドの読みやすさを倧幅に改善したす。コヌドには、間接性の倧きなポむンタヌが含たれたす。 よく知られた䟋- signal関数の暙準的な宣蚀は完党に読めたせん


  void (*signal(int sig, void (*func)(int)))(int); 

ただし、シグナルハンドラヌ関数ぞのポむンタヌの゚むリアスを远加するず、この問題は解決したす。


 typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 

C ++では、型゚むリアスなしでは静的メタプログラミングは考えられたせん。さらに、テンプレヌト型の完党な名前に倢䞭にならないようにしたす。


OcamlやHaskellなどの蚀語にはtypeキヌワヌドがあり、そのセマンティクスはScalaの堎合よりもはるかに耇雑です。 たずえば、 typeキヌワヌドを䜿甚するず、 型の同矩語だけでなく、 代数的なデヌタ型も䜜成できたす。


 (*   Ocaml*) type suit = Club | Diamond | Heart | Spade;; 

これは、OcamlずSMLで䜜成するこずもできたす。
ナニオン型ナニオン型


 (* OCaml *) type intorstring = Int of int | String of string;; (* SML *) datatype intorreal = INT of int | REAL of real 

珟時点Scala 2.12では、方法がわかりたせん。typeキヌワヌドを䜿甚しお宣蚀された機胜は、 type同矩語化に限定され、パス䟝存型を宣蚀したす。 ただし、将来のバヌゞョンでは、この機胜が远加される予定です 説明に぀いおは seniaに感謝したす 。 既に既知のタむプに他の名前を付ける必芁があるのはなぜですか たず、これは远加のセマンティックロヌドを远加したす。たずえば、 DateString型の意味は単なるStringよりも理解しやすくなり、 Map[Username, Key]はMap[String, String]よりも良く芋えたす。 第二に、同矩語化により、倧きく耇雑なタむプの眲名を枛らすこずができたす。 Map[Username, Key]は芋栄えが良いですが、 Keystoreずっず短く、より理解しやすいです。


もちろん、シノニム型には欠点もありたすPerson型が衚瀺され、オブゞェクト、クラス、たたぱむリアスであるかどうかを理解できたせん。


このツヌルを悪甚するこずは間違いなく䟡倀がありたせんが、実際に圹立぀状況がいく぀かありたす。



ここでより倚くの䟋を芋぀けるこずができたす。䟋のセクションたで少し䞋にスクロヌルしおください。


割り圓おに぀いおもう䞀床


Scalaの割り圓おは、あなたが慣れおいるものではありたせん。 この操䜜を芋おみたしょう。䞀芋したほど簡単ではないこずがわかりたす。


 //    ,     scala> val address = ("localhost", 80) address: (String, Int) = (localhost,80) scala> val (host, port) = address host: String = localhost port: Int = 80 

タプルを2぀の倉数に゜ヌトしたばかりですが、問題はタプルに限定されたせん。


 scala> val first::rest = List(1,2,3,4,5) first: Int = 1 rest: List[Int] = List(2, 3, 4, 5) 

case class同様の操䜜を実行できたす。


 case class Person(name: String, age: Int) val max = Person("Max", 36) // max: Person = Person(Max,36) val Person(n, a) = max // n: String = Max // a: Int = 36 

さらに


 scala> val p @ Person(n, a) = max // p: Person = Person(Max,36) // n: String = Max // a: Int = 36 

埌者の堎合、名前pでcase classレコヌド自䜓を取埗し、名前nで名前を取埗したすa-幎霢。


掗緎された読者は、割り圓おがパタヌンマッチングずたったく同じように動䜜するこずにすでに気付いおいたす。 同様の機胜は、PythonやErlangなどの他の蚀語でも実装されおいたす。 この機胜は、䞻にデヌタ構造を解凍するために䜿甚したす。 ただし、乱甚しないでください。耇雑なデヌタ構造を解凍するず、読みやすさが倧幅に䜎䞋したす。


オプション


倚くの人はすでにJava 8のOptional型に粟通しおいたす。Scalaでは、 Option型は同じ機胜を実行したす。 たた、倚くのJava支持者にずっお、このタむプはGuavaのGoogleラむブラリから知られおいるかもしれたせん。


はい、 Optional䜿甚しおnullを回避し、埌でNullPointerExceptionを回避しnull 。 はい、 isEmptyおよびnonEmptyたす。 GuavaバリアントにはisPresentメ゜ッドがありたす。 たた、Javaやその他の蚀語でOptionalを䜿甚したか䜿甚しおいない倚くの人がScalaでOptionalを誀甚しおいたす。


ただし、 Optional 、Skalov mapず同様に動䜜する同じGuavaで定矩されたtransformメ゜ッドOptionalあるこずに気づいおいるわけではありたせん。

Option誀甚は䞀般的な問題です。 Option 、 たず、存圚しない可胜性のある゚ンティティを抂念的に瀺すために必芁であり、NPEから逃げないために必芁です。 はい、問題があり、問題は深刻です。 誰かがこれのために圌自身の蚀語を発明しさえしたす。 しかし、ScalaのOptionの誀甚に戻りたしょう。


 if (option.isEmpty) default else //   c NoSuchElementException ( ) option.get 

チェックを行っおいたすが、ここで爆発するものはないようです。 私を信じおください、あなたは産業コヌドで間違いを犯すこずができたす、そしお、条件は予想された衚珟でないかもしれたせん。 たた、テストのスペルも間違っおいる可胜性がありたす。 あなたによっおではなく、あなたの前任者によっお。


䞀般に、䞊蚘の䟋には別の問題がありたす。 あなたのフロヌは䜕らかのブヌル倀に䟝存しおおり、その敎合性が䟵害されおいたす。

䞀郚の開発者は、すでに「機胜する」コヌド甚にテストをカスタマむズする機胜を備えおいたす。 䞊蚘のコヌドよりも正確で短いコヌドは、次のように蚘述できたす。


 option getOrElse default 

コヌドをコンパクトにすればするほど、コヌド内の゚ラヌを芋぀けやすくなり、ミスを犯しにくくなりたす。 さたざたなOptionを連鎖できる䟿利なorElseメ゜ッドがありたす。


Option内に倀が存圚する堎合、その倀を倉換する必芁がありたす。 これにはmapメ゜ッドがありたす。倀を取埗しお倉換し、コンテナに戻したす。


 val messageOpt = Some("Hello") val updMessageOpt = messageOpt.map(msg => s"$mgs cruel world!") updMessageOpt: Option[String] 

そしお時々それは次のように起こりたす


 val messageOptOpt = Some(Some("Hello")) 

Optionは非垞にネストできたす。 この問題は、 flatMapメ゜ッドたたはflattenメ゜ッドによっお解決されたす。 最初はmapず同様に機胜しmap -内郚倀を倉換したすが、同時に構造を単玔化し、2番目は単玔に構造を単玔化したす。


Optionを返す特定の関数があるず想像しおください


 def concatOpt (s0: String, s1: String) = Option(s0 + s1) 

その埌、同様の結果が埗られたす。


 messageOpt.map(msg => concatOpt(msg, " cruel world")) res0: Option[Option[String]] = Some(Some(Hello cruel world)) //    `flatMap`: messageOpt.flatMap(msg => concatOpt(msg, " cruel world")) res6: Option[String] = Some(Hello cruel world) //   flatten messageOptOpt.flatten == Some("Hello") res1: Option[String] = Some(Hello) 

Scalaには、 Optionた䜜業を倧幅に促進できる別のメカニズムがあり、「理解のため」ずいう名前で知られおいる堎合がありたす。


 val ox = Some(1) val oy = Some(2) val oz = Some(3) for { x <- ox; y <- oy; z <- oz } yield x + y + z // res0: Option[Int] = 6 

OptionタむプのいずれかがNoneに等しい堎合、 yield埌yieldナヌザヌは空のコンテナヌ、たたは空のコンテナヌの倀を受け取りたす。 Optionの堎合OptionこれはNoneです。 リストの堎合、 Nil 。


そしお最も重芁なこずは、 getメ゜ッドを呌び出さないようにするためにあらゆるこずを詊みるこずです。 これは朜圚的な問題に぀ながりたす。


私はあなたがよくやったずすべおをチェックしたこずを知っおいたす。 あなたのお母さんもそう思うず思いたすが、これはあなたget再び匕っ匵る理由を䞎えたせん。

リスト


Optionはgetあり、リストにはhead 、 initおよびtailもありinit 。 空のリストで䞊蚘のメ゜ッドを呌び出すこずで取埗できるものを次に瀺したす。


 //   : init: java.lang.UnsupportedOperationException head: java.lang.NoSuchElementException last: java.lang.NoSuchElementException tail: java.lang.UnsupportedOperationException 

もちろん、空のシヌトをチェックする堎合、これはあなたに起こるこずはありたせん。


初心者ロッカヌは、悪名高いif - elseコンストラクトを䜿甚しおこれを行いたす。

list.headず仲間を呌び出すこずは、自分自身を殺すための最良の方法の1぀です。
リストの操䜜䞭にスリヌプしたす。


ガラガラヘビをlist.head 、 list.headずその友達を䜿わないようにできる限りのこずをしおlist.head 。

head代わりに、 headOptionメ゜ッドを䜿甚するこずをおheadOptionたす。 lastOptionメ゜ッドも同様に動䜜したす。 䜕らかの方法でむンデックスにアタッチされおいる堎合、 isDefinedAtメ゜ッドを䜿甚できたす。 isDefinedAtメ゜ッドは、パラメヌタヌずしお敎数の匕数むンデックスを取りたす。 䞊蚘のすべおには、忘れるこずができるチェックが含たれおいたす。 あなたが意識的にそれらを省略するもう䞀぀の理由がありたす。 正しい慣甚的な代替手段は、パタヌンマッチングを䜿甚するこずです。 リストが代数型であるずいう事実により、 Nilを忘れるこずはありたせんNilずtail呌び出しを安党に回避でき、数行のコヌドを保存できたす。


 def printRec(list: List[String]): Unit = list match { //      ,     // n,  k ,  . That's the power! case Nil => () case x::xs => println(x) printRec(xs) } 

パフォヌマンスに぀いお少し


単䞀リンクリストSkalov List 別名scala.collection.immutable.List のパフォヌマンスの芳点から芋るず、最も安䟡な操䜜は、リストの最埌ではなく先頭に曞き蟌むこずです。 リストの最埌に曞き蟌むには、リスト党䜓を最埌たで調べる必芁がありたす。 リストの先頭ぞの曞き蟌みの耇雑さはO1からOnの終わりたでです。 それを忘れないでください。

オプション[リスト[]]


Option[List[A]] 、うらやたしい頻床でScalaに䌚ったばかりの人のコヌドにありたす。 関数の匕数ず戻り倀の型の䞡方。 倚くの堎合、このような傑䜜を䜜成した人は次の匕数を䜿甚したす。「リストがあるかもしれたせんが、代わりにnullを䜿甚するのは䜕ですか」


さお、別の状況を想像しおみたしょう。 Optionは抂念的に考えられる倱敗した結果を衚し、リストは返されたデヌタのセットです。 Option[List[Message]]を返すサヌバヌがあるず想像しおOption[List[Message]] 。 すべおが順調であれば、 Some内のメッセヌゞのリストを取埗したす。 メッセヌゞがない堎合、 Some内に空のリストを取埗したす。 サヌバヌで゚ラヌが発生した堎合、 Noneを取埗したす。 合理的で実行可胜ですか


そしおいや システムで゚ラヌが発生する可胜性がある堎合、どの゚ラヌを知る必芁がありたす。 このために、 Throwableたたは䜕らかのコヌドを返す必芁がありたす。 Optionはそれを可胜にしたすか 実際にはそうではありたせんが、 Try and Eitherはこれを支揎したす。


Optionようにリストは空にするこずができるため、䜕か問題が発生した堎合に空のリストを安党に枡すこずができたす。 Option[List]コンストラクトが実行可胜な反䟋はただ芋おいたせん。 そのような䟋があればずおも嬉しいですし、私ず共有するでしょう。


オプション[A] =>オプション[B]


最近、 Option別の興味深いアプリケヌションに出䌚いたした。 次の関数のシグネチャを芋おみたしょう。


 def process (item: Option[Item]): Option[UpdatedItem] = ??? 

远加のコンテナを䜿甚しお倉換を耇雑にする必芁はありたせん。これにより、関数の汎甚性が䜎くなり、関数のシグネチャが芖芚的に乱雑になりたす。 代わりに、タむプA => B関数を䜿甚しおくださいA => B たた、元のコンテナのタむプを保存する堎合は、元の結果をこのコンテナにラップし、 mapたたはflatmap map flatmapを䜿甚しおさらにデヌタを倉換したす。


タプル


タプルタプルの存圚は、倚くの関数型だけでなく蚀語の興味深い機胜です。 関数型蚀語では、タプルはレコヌドず同じ方法で䜿甚できたす。 タプルを必芁なデヌタで蚘述し、新しいタむプにラップしたす。たずえば、 Haskellでnewtypeを䜿甚しお、結果ずしお、ナヌザヌが䜕も知らない実装の新しいタむプを取埗したす。 タプルのない玔粋に機胜的な蚀語では、どこにもありたせん。蟞曞を完党に提瀺できたす。 それらのない革呜はそれほど明癜ではありたせん。


Erlangなどの䞀郚の蚀語では、レコヌドはタプルよりもはるかに遅れお衚瀺されたした。 さらに、Erlangのレコヌドもタプルです。

Scalaはオブゞェクト指向蚀語です。 はい、関数型プログラミング芁玠をサポヌトしおいたす。 倚くの人が私に反察するず確信しおいたすが、Scalaではすべおがオブゞェクトであるこずを忘れないでください。 caseクラスの存圚はcaseタプルの必芁性を倧幅に枛らしたす。䞍倉のレコヌドを取埗し、パタヌンず比范するこずもできたすこれに぀いおは埌で説明したす。 各caseクラスには独自のタむプがありたす。


タプルは、倚くの堎合、オブゞェクト指向蚀語の出身者が䜿甚する必芁がありたす。これらの蚀語ツヌルに興味がありたす。 そもそも、名前は付けられおいたせん。


タプルが匿名のガベヌゞダンプずしお䜿甚されない堎合は、名前を付ける必芁がありたす。

タプルを䜿甚しおデヌタを保存するcase class 、このためにcase classを䜿甚したす。

機胜的なスタむルの堎合、前述の型の゚むリアスを䜿甚するこずをお勧めしたす型゚むリアス


 type Point = (Double, Double) 

将来的には、非垞に名前の付いた型を参照するようになりたすが、そのようなひどいものはなくなりたす。


 //  def drawLine(x: (Double, Double), y: (Double, Double)): Line = ??? //   def drawLine(x: Point, y: Point): Line = ??? 

Scalaでは、むンデックスによっおtuple芁玠にアクセスするこずもできたす。 䟋


 // ! val y = point._2 //   

コレクションを操䜜しおいるずきは特に悲しいようです


 // ! points foreach { point: Point => println(s"x: ${point._1}, y: ${point._2}") } 

そしお、あなたはそれをする必芁はありたせん。 もちろん、そのような手段が読みやすさを高める䟋倖的なケヌスがありたす


 //  rows.groupBy(_._2) 

ただし、ほずんどの堎合、䞋線付きの構文は避けるのが最善です。 䞀般に、圌に぀いおは忘れお、芚えおいない方が良いです。 Scalaには、この構文を䜿甚しない自然な方法がありたす。


Scalaでは、 pair._2なしでい぀でも実行できpair._2 。 そしお、それを行う必芁がありたす。

すべおがそうである理由を理解しお理解するために、関数型蚀語に目を向けたしょう。


質問 芪愛なる線集者、なぜScalaのリストむンデックスはれロから始たり、タプルは1から始たるのですか ワシリヌ、ポハビンスク。


回答 こんにちは、ノァシリヌ。 答えは簡単です。なぜなら、それは歎史的に起こったからです。 SMLには、タプル芁玠にアクセスするための関数 #1および#2が存圚したす。 Haskellは、タプル芁玠にアクセスするための2぀の関数fstずsndたす。


 -- - .  Haskell        --   .  . fst tuple 

しかし、タプルの3番目たたは5番目の芁玠を取埗するだけではうたくいきたせん。 信じられない しかし、無駄に 。 そしお、サンプルずの比范が最も自然であるずあなたが蚀うならば、それを信じないでください。 そしおHaskellだけでなく。


Occaml


 let third (_, _, elem) = elem 

アヌラン


 1> Tuple = {1,3,4}. {1,3,4} 2> Third = fun ({_Fst, _Snd, Thrd}) -> Thrd end. #Fun<erl_eval.6.50752066> 3> Third(Tuple). 4 

Python
そしお、これは関数型蚀語ではない䟋です


 >> (ip, hostname) = ("127.0.0.1", "localhost") >>> ip '127.0.0.1' >>> hostname 'localhost' >>> 

この知識をScalaに適甚したしょう


 // ,     trait Rectangle { def topLeft: Point ... } //      val (x0, y0) = rectangle.topLeft //     : points foreach { case (x, y) => println(s"x: ${x}, y: ${y}") } 

matchキヌワヌドを䜿甚した暙準のマッチングメカニズムもキャンセルされおいたせん。


タプルは匿名のガベヌゞダンプずしおも䜿甚でき、これは正圓化される堎合がありたす。 実際、倚くの関数型蚀語では、関数シグネチャのレベルでサンプルず比范されおいたす。


 --    haskell --   : map :: (a -> b) -> [a] -> [b] --         --   --     --  : map _ [] = [] --      x:xs , ,   --  Haskell head:tail  . : -   cons --    :: map fun (head:tail) = fun head : map fun tail 

同様のメカニズムがSMLずErlangで䜿甚されおいたす。 残念ながら、Scalaはそのような機䌚を奪われおいたす。 したがっお、タプルは、グルヌプ化およびサンプルずの埌続のマッチングに䜿甚できたす。


 //   Haskell,    :( def map [A, B] (f: A => B, l: List[A]): List[B] = (f, l) match { case (f, Nil) => List.empty case (f, head::tail) => f(head) :: map(f, tail) } 

倚くの堎合、タプルの䞀郚の芁玠の倀を曎新する必芁がありたす。 copy方法はこれに適しおいたす。


 val dog = ("Rex", 13) val olderDog = tuple.copy(_2 = 14) 

HaskellおよびSMLでの タプルの䜿甚に぀いおは、リンクをクリックしお読むこずができたす。


実際には、タプルを䜿甚するこずは少なくずもScalaで座暙を衚す最良の方法ではありたせん。 Scalaでは、このためにcase classクラスを䜿甚するこずをお勧めしたす。 タプルの必芁性は、䞻に、耇合レコヌドを䞀般化された圢匏で折りたたむ必芁がある堎合のナニバヌサルラむブラリの存圚によっお決たりたす。 たずえば、 zipたたはgroupBy 。 したがっお、タプルを䜿甚する堎合は、䞀般化されたアルゎリズムを蚘述するずきにのみ䜿甚しおください。 他のすべおのケヌスでは、叀き良きcase classするこずをお勧めしたす。


䞋線に぀いお


Scalaで_䜿甚されおいるすべおのケヌスをリストできたすか 調査によるず、これを行うこずができるScala開発者はわずか7です。 アンダヌスコアは、蚀語で䜕床もさたざたなコンテキストで䜿甚されたす。 これはここでよく説明されおいたす。 ほずんどの堎合、アンダヌスコアなしで実行するこずはできたせん。構文では、耇数のむンポヌトたたは䟋倖のあるむンポヌトに察しおアンダヌスコアが必芁です。 他にも合理的な甚途がありたすそれらがなくおも䜿甚できたす。 ただし、ラムダ匏内で読みやすさは远加されたせん。 アンダヌスコアが3぀以䞊あるず、ラムダの読み取りが困難になりたす。


サンプルず比范するず、圌らはあなたの人生を台無しにするこずもできたす。 ForkずLeaf理解したす。


 def weight(tree: CodeTree): Int = tree match { case Fork(_, _, _, weight) => weight case Leaf(_, weight) => weight } 

それで


 def weight(tree: CodeTree): Int = tree match { case Fork(left, right, chars, weight) => weight case Leaf(char, weight) => weight } 

お気づきかもしれたせんが、これらの倀は䜿甚されたせん。 しかし、アンダヌスコアで䞊曞きする必芁がある堎合はそうではありたせん。 私を信じお、プログラミングの速床はタむピングの速床に䟝存したせん。読みやすくするために、いく぀かの䜙分な文字を曞くこずができたす。


結論ずしお


この蚘事では、Scalaの䞻芁な機胜むディオムに぀いお説明し、他の機胜蚀語ず倚くの類䌌点を匕き出そうず詊みたしたそしお、刀断するのではなく、刀明したかどうか。 次の郚分では、OOPずコレクションに関連するむディオムに぀いお説明し、倚くの初心者開発者を悩たせおいるむンフラストラクチャの問題に関する私の考えを述べたす。 あなたがこの蚘事を楜しんだこずを本圓に願っおいたす。 蟛抱匷く最埌たで読んでくれおありがずう。 継続する。



Source: https://habr.com/ru/post/J323706/


All Articles