Die funktionale Programmierung in Scala kann aufgrund einiger syntaktischer und semantischer Merkmale der Sprache schwierig zu beherrschen sein. Insbesondere einige der Sprachwerkzeuge und -methoden zur Implementierung der von Ihnen geplanten Aufgaben mithilfe der Hauptbibliotheken scheinen offensichtlich zu sein, wenn Sie mit ihnen vertraut sind. Zu Beginn des Studiums, insbesondere allein, ist es jedoch nicht so einfach, sie zu erkennen.
Aus diesem Grund habe ich beschlossen, dass es nützlich ist, einige funktionale Programmiertipps in Scala zu teilen. Beispiele und Namen entsprechen Katzen, aber die Syntax in Scalaz sollte aufgrund der allgemeinen theoretischen Grundlage ähnlich sein.

9) Konstruktoren der Erweiterungsmethode
Beginnen wir mit den vielleicht grundlegendsten Tool-Erweiterungsmethoden aller Art, die eine Instanz in Option, Entweder usw. verwandeln, insbesondere:
- .someund die entsprechende- noneKonstruktor-Methode für- Option;
- .asRight,- .asLeftfür- .asLeft;
- .valid,- .invalid,- .validNel,- .invalidNelfür- Validated
Zwei Hauptvorteile ihrer Verwendung:
- Es ist kompakter und verständlicher (da die Reihenfolge der Methodenaufrufe gespeichert ist).
- Im Gegensatz zu Konstruktoroptionen werden die Rückgabetypen dieser Methoden auf einen Supertyp erweitert, d.h.
 import cats.implicits._ Some("a")  
Obwohl sich die Typinferenz im Laufe der Jahre verbessert hat und die Anzahl möglicher Situationen, in denen dieses Verhalten dem Programmierer hilft, ruhig zu bleiben, abgenommen hat, sind Kompilierungsfehler aufgrund übermäßig spezialisierter Typisierung in Scala heute noch möglich. Sehr oft entsteht bei der Arbeit mit 
Either der Wunsch, den Kopf gegen einen Tisch zu 
Either (siehe 
Scala with Cats, Kapitel 4.4.2).
Noch etwas zum Thema: 
.asRight und 
.asLeft noch einen Typparameter. Beispiel: 
"1".asRight[Int] ist 
Either[Int, String] . Wenn dieser Parameter nicht angegeben wird, versucht der Compiler, ihn auszugeben und 
Nothing . Trotzdem ist es bequemer, als jedes Mal beide Parameter anzugeben oder auch nicht, wie im Fall von Konstruktoren.
8) Fünfzig Farben *>
Der in einer 
Apply Methode definierte *> -Operator ( 
Monad in 
Applicative , 
Monad usw.) bedeutet einfach "die anfängliche Berechnung verarbeiten und das Ergebnis durch das ersetzen, was im zweiten Argument angegeben ist". In der Code-Sprache (im Fall von 
Monad ):
 fa.flatMap(_ => fb) 
Warum sollte ein obskurer symbolischer Operator für eine Operation verwendet werden, die keinen spürbaren Effekt hat? Wenn Sie ApplicativeError und / oder MonadError verwenden, werden Sie feststellen, dass der Vorgang den Fehlereffekt für den gesamten Workflow beibehält. Nehmen Sie 
Either als Beispiel:
 import cats.implicits._ val success1 = "a".asRight[Int] val success2 = "b".asRight[Int] val failure = 400.asLeft[String] success1 *> success2  
Wie Sie sehen, bleibt die Berechnung auch im Fehlerfall kurzgeschlossen. *> hilft Ihnen bei der Arbeit mit verzögerten Berechnungen in 
Monix , 
IO und dergleichen.
Es gibt eine symmetrische Operation <*. Im Fall des vorherigen Beispiels:
 success1 <* success2  
Wenn Ihnen die Verwendung von Symbolen fremd ist, müssen Sie nicht darauf zurückgreifen. *> Ist nur ein Alias für 
productR und * <ist ein Alias für 
productL .
Hinweis
In einem persönlichen Gespräch bemerkte Adam Warski (danke, Adam!) Zu Recht, dass es neben *> ( 
productR ) auch >> von 
FlatMapSyntax . >> wird wie 
fa.flatMap(_ => fb) , jedoch mit zwei Nuancen:
- Es wird unabhängig von productRdefiniert. Wenn sich daher aus irgendeinem Grund der Vertrag dieser Methode ändert (theoretisch kann es geändert werden, ohne die monadischen Gesetze zu verletzen, aber ich bin mir beiMonadErrornicht sicher), werden Sie nicht leiden.
- was noch wichtiger ist, >> hat einen zweiten Operanden, der durch Call-by-Name aufgerufen wird, d.h. fb: => F[B]. Der Unterschied in der Semantik wird grundlegend, wenn Sie Berechnungen durchführen, die zu einer Stapelexplosion führen können.
Aus diesem Grund habe ich *> häufiger verwendet. Vergessen Sie auf die eine oder andere Weise nicht die oben aufgeführten Faktoren.
7) Segel setzen!
Viele nehmen sich Zeit, um das 
lift in den Kopf zu bekommen. Aber wenn Sie Erfolg haben, werden Sie feststellen, dass er überall ist.
Wie viele Begriffe, die in der Luft der funktionalen Programmierung aufsteigen, stammt der 
lift aus der 
Kategorietheorie . Ich werde versuchen zu erklären: Nehmen Sie eine Operation vor und ändern Sie die Signatur ihres Typs so, dass sie in direktem Zusammenhang mit dem abstrakten Typ F steht.
In Cats ist das einfachste Beispiel 
Functor :
 def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f) 
Dies bedeutet: Ändern Sie diese Funktion so, dass sie auf den angegebenen Funktortyp F wirkt.
Die Lift-Funktion ist häufig gleichbedeutend mit verschachtelten Konstruktoren für einen bestimmten Typ. Also ist 
EitherT.liftF im Wesentlichen 
EitherT.right. Beispiel aus Scaladoc :
 import cats.data.EitherT import cats.implicits._ EitherT.liftF("a".some)  
Kirsche auf dem Kuchen: 
lift überall in der Scala-Standardbibliothek vorhanden. Das beliebteste (und vielleicht nützlichste Beispiel für die tägliche Arbeit) ist 
PartialFunction :
 val intMatcher: PartialFunction[Int, String] = { case 1 => "jak się masz!" } val liftedIntMatcher: Int => Option[String] = intMatcher.lift liftedIntMatcher(1)  
Jetzt können wir zu dringlicheren Themen übergehen.
6) mapN
mapN ist eine nützliche 
mapN für die Arbeit mit Tupeln. Auch dies ist keine Neuheit, sondern ein Ersatz für den guten alten Operator 
|@| Er ist ein Schrei.
So sieht mapN bei einem Tupel aus zwei Elementen aus:
 
Im Wesentlichen können wir Werte innerhalb eines Tupels von jedem F abbilden, das eine Halbgruppe (Produkt) und ein Funktor (Zuordnung) ist. Also:
 import cats.implicits._ ("a".some, "b".some).mapN(_ ++ _)  
Vergessen Sie übrigens nicht, dass Sie bei Katzen eine Karte und eine 
leftmap für Tupel erhalten:
 ("a".some, List("b","c").mapN(_ ++ _))  
Eine weitere nützliche 
.mapN Funktion ist das Instanziieren von 
.mapN :
 case class Mead(name: String, honeyRatio: Double, agingYears: Double) ("półtorak".some, 0.5.some, 3d.some).mapN(Mead) //Some(Mead(półtorak,0.5,3.0)) 
Natürlich verwenden Sie hierfür lieber den for-Schleifenoperator, aber mapN vermeidet in einfachen Fällen monadische Transformatoren.
 import cats.effect.IO import cats.implicits._  
Methoden haben ähnliche Ergebnisse, aber letztere verzichten auf monadische Transformatoren.
5) Verschachtelt
Nested ist im Wesentlichen ein verallgemeinertes Doppel von Monadentransformatoren. Wie der Name schon sagt, können Sie unter bestimmten Bedingungen Anhangsvorgänge ausführen. Hier ist ein Beispiel für 
.map(_.map( : import cats.implicits._ import cats.data.Nested val someValue: Option[Either[Int, String]] = "a".asRight.some Nested(someValue).map(_ * 3).value  
Zusätzlich zu 
Functor verallgemeinert 
Nested Applicative , 
ApplicativeError und 
Traverse . Weitere Informationen und Beispiele finden Sie 
hier .
4) .recover / .recoverWith / .handleError / .handleErrorWith / .valueOr
Die funktionale Programmierung in Scala hat viel mit der Behandlung des Fehlereffekts zu tun. 
ApplicativeError und 
MonadError verfügen über einige nützliche Methoden, und es kann hilfreich sein, die subtilen Unterschiede zwischen den vier Hauptmethoden herauszufinden. Also, mit 
ApplicativeError F[A]:- handleErrorkonvertiert alle Fehler am- handleErrorgemäß der angegebenen Funktion in A.
- recoverfunktioniert auf ähnliche Weise, akzeptiert jedoch Teilfunktionen und kann daher von Ihnen ausgewählte Fehler in A konvertieren.
- handleErrorWithähnelt- handleError, das Ergebnis sollte jedoch wie- F[A]aussehen. Dies bedeutet, dass Sie Fehler konvertieren können.
- recoverWithwie "Wiederherstellen", erfordert jedoch als Ergebnis auch- F[A].
Wie Sie sehen, können 
handleErrorWith auf die 
handleErrorWith von 
handleErrorWith und 
recoverWith , die alle möglichen Funktionen abdecken. Jede Methode hat jedoch ihre Vorteile und ist auf ihre Weise bequem.
Im Allgemeinen empfehle ich Ihnen, sich mit der 
ApplicativeError- API vertraut zu machen, die eine der reichsten an Katzen ist und von MonadError geerbt wird. Dies bedeutet, dass sie in 
cats.effect.IO , 
monix.Task usw. unterstützt wird.
Es gibt eine andere Methode für 
Either/EitherT , 
Validated und 
Ior - 
.valueOr . Im Wesentlichen funktioniert es wie 
.getOrElse für 
Option , ist jedoch generisch für Klassen, die etwas „links“ enthalten.
 import cats.implicits._ val failure = 400.asLeft[String] failure.valueOr(code => s"Got error code $code")  
3) Gassenkatzen
Gassenkatzen sind eine bequeme Lösung für zwei Fälle:
- Fälle von Kachelklassen, die ihren Gesetzen nicht zu 100% entsprechen;
- Ungewöhnliche Hilfstypklassik, die richtig verwendet werden kann.
Historisch gesehen ist die Monadeninstanz für 
Try beliebteste in diesem Projekt, da 
Try , wie Sie wissen, nicht alle monadischen Gesetze in Bezug auf schwerwiegende Fehler erfüllt. Jetzt ist er wirklich mit Katzen bekannt.
Trotzdem empfehle ich Ihnen, sich mit 
diesem Modul vertraut zu machen. Es scheint Ihnen nützlich zu sein.
2) Importe verantwortungsbewusst behandeln
Sie müssen aus der Dokumentation, dem Buch oder von einem anderen Ort wissen, dass Katzen eine bestimmte Importhierarchie verwenden:
cats.x für grundlegende (Kernel-) Typen;
cats.data für Datentypen wie Validiert, Monadentransformatoren usw.;
cat.syntax.x._ zur Unterstützung von Erweiterungsmethoden, damit Sie sth.asRight, sth.pure usw. aufrufen können;
cats.instances.x. _ um die Implementierung verschiedener Typklassen direkt in den impliziten Bereich für einzelne spezifische Typen zu importieren, sodass beim Aufrufen von beispielsweise sth.pure der Fehler "implizit nicht gefunden" nicht auftritt.
Natürlich haben Sie den Import von 
cats.implicits._ bemerkt, der die gesamte Syntax und alle Instanzen der Typklasse im impliziten Bereich importiert.
Grundsätzlich sollten Sie bei der Entwicklung mit Cats mit einer bestimmten Reihenfolge von Importen aus den FAQ beginnen, nämlich:
 import cats._ import cats.data._ import cats.implicits._ 
Wenn Sie die Bibliothek besser kennenlernen, können Sie sie nach Ihrem Geschmack kombinieren. Befolgen Sie eine einfache Regel:
- cats.syntax.xbietet eine Erweiterungssyntax für x.
- cats.instances.xbietet- cats.instances.x.
Wenn Sie beispielsweise 
.asRight benötigen, eine Erweiterungsmethode für 
Either , gehen Sie wie folgt vor:
 import cats.syntax.either._ "a".asRight[Int]  
Um 
Option.pure zu erhalten, 
Option.pure Sie jedoch 
cats.syntax.monad AND cats.instances.option :
 import cats.syntax.applicative._ import cats.instances.option._ "a".pure[Option]  
Durch manuelles Optimieren Ihres Imports begrenzen Sie implizite Bereiche in Ihren Scala-Dateien und reduzieren dadurch die Kompilierungszeit.
Bitte tun Sie dies jedoch nicht, wenn die folgenden Bedingungen nicht erfüllt sind:
- Sie haben Cats bereits gut gemeistert
- Ihr Team besitzt die Bibliothek auf derselben Ebene
Warum? Weil:
 
Dies liegt daran, dass sowohl 
cats.implicits als auch 
cats.instances.option Erweiterungen von 
cats.instances.OptionInstances . Tatsächlich importieren wir den impliziten Bereich zweimal, als wir den Compiler verwirren.
Darüber hinaus gibt es keine Magie in der Hierarchie der Impliziten - dies ist eine klare Folge von Typerweiterungen. Sie müssen sich nur auf die Definition von 
cats.implicits beziehen und die 
cats.implicits untersuchen.
Für 10-20 Minuten können Sie es genug studieren, um Probleme wie diese zu vermeiden - glauben Sie mir, diese Investition wird sich definitiv auszahlen.
1) Vergessen Sie nicht die Katzen-Updates!
Sie denken vielleicht, Ihre FP-Bibliothek ist zeitlos, aber tatsächlich werden 
cats und 
scalaz aktiv aktualisiert. Nehmen Sie als Beispiel Katzen. Hier sind nur die neuesten Änderungen:
Vergessen Sie daher bei der Arbeit mit Projekten nicht, die Bibliotheksversion zu überprüfen, die Hinweise für neue Versionen zu lesen und rechtzeitig zu aktualisieren.