A programação funcional no Scala pode ser difícil de dominar devido a alguns recursos sintáticos e semânticos da linguagem. Em particular, algumas das ferramentas de linguagem e maneiras de implementar o que você planejou com a ajuda das principais bibliotecas parecem óbvias quando você está familiarizado com elas - mas no início do estudo, especialmente por conta própria, não é tão fácil reconhecê-las.
Por esse motivo, decidi que seria útil compartilhar algumas dicas de programação funcional no Scala. Exemplos e nomes correspondem a gatos, mas a sintaxe no escalaz deve ser semelhante devido à base teórica geral.

9) Construtores de métodos de extensão
Vamos começar com, talvez, a ferramenta mais básica - métodos de extensão de qualquer tipo que transformam uma instância em Option, Ou, etc., em particular:
- .somee o método- noneconstrutor correspondente para- Option;
- .asRight,- .asLeftfor- Either;
- .valid,- .invalid,- .validNel,- .invalidNelpara- Validated
Duas vantagens principais de seu uso:
- É mais compacto e compreensível (já que a sequência de chamadas de método é salva).
- Diferentemente das opções do construtor, os tipos de retorno desses métodos são estendidos para um supertipo, ou seja:
 import cats.implicits._ Some("a")  
Embora a inferência de tipo tenha melhorado ao longo dos anos e o número de possíveis situações em que esse comportamento ajude o programador a ficar calmo tenha diminuído, os erros de compilação devido à digitação excessivamente especializada ainda são possíveis no Scala hoje. Muitas vezes, surge o desejo de bater a cabeça em uma mesa ao trabalhar com o 
Either (consulte 
Scala com gatos, capítulo 4.4.2).
Mais uma coisa sobre o tópico: 
.asRight e 
.asLeft ainda têm mais um parâmetro de tipo. Por exemplo, 
"1".asRight[Int] é E 
Either[Int, String] . Se esse parâmetro não for fornecido, o compilador tentará produzi-lo e obterá 
Nothing . No entanto, é mais conveniente do que fornecer ambos os parâmetros de cada vez ou não, como no caso de construtores.
8) Cinquenta tons *>
O operador *> definido em qualquer método 
Apply (ou seja, em 
Applicative , 
Monad etc.) significa simplesmente "processar o cálculo inicial e substituir o resultado pelo que é especificado no segundo argumento". No idioma do código (no caso da 
Monad ):
 fa.flatMap(_ => fb) 
Por que usar um operador simbólico obscuro para uma operação que não tem um efeito perceptível? Começando a usar o ApplicativeError e / ou MonadError, você verá que a operação retém o efeito de erro para todo o fluxo de trabalho. Tome 
Either como exemplo:
 import cats.implicits._ val success1 = "a".asRight[Int] val success2 = "b".asRight[Int] val failure = 400.asLeft[String] success1 *> success2  
Como você pode ver, mesmo no caso de um erro, o cálculo permanece em curto-circuito. *> irá ajudá-lo a trabalhar com cálculos adiados em 
Monix , 
IO e similares.
Existe uma operação simétrica, <*. Então, no caso do exemplo anterior:
 success1 <* success2  
Finalmente, se o uso de símbolos é estranho para você, não é necessário recorrer a ele. *> É apenas um alias para 
productR e * <é um alias para 
productL .
Nota
Em uma conversa pessoal, Adam Warski (obrigado, Adam!) Observou com razão que, além de *> ( 
productR ), também existe >> do 
FlatMapSyntax . >> é definido da mesma maneira que 
fa.flatMap(_ => fb) , mas com duas nuances:
- é definido independentemente do productRe, portanto, se por algum motivo o contrato desse método for alterado (teoricamente, ele poderá ser alterado sem violar as leis monádicas, mas não tenho certeza sobre oMonadError), você não sofrerá;
- mais importante, >> tem um segundo operando chamado por chamada por nome, ou seja, fb: => F[B]. A diferença na semântica se torna fundamental se você executar cálculos que podem levar a uma explosão na pilha.
Com base nisso, comecei a usar *> com mais frequência. De uma forma ou de outra, não se esqueça dos fatores listados acima.
7) Levante as velas!
Muitos levam tempo para colocar o conceito de 
lift em suas cabeças. Mas quando você tiver sucesso, descobrirá que ele está em todo lugar.
Como muitos termos que pairavam no ar da programação funcional, o 
lift veio da 
teoria das 
categorias . Vou tentar explicar: faça uma operação, altere a assinatura do tipo para que fique diretamente relacionada ao tipo abstrato F.
Em Gatos, o exemplo mais simples é o 
Functor :
 def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f) 
Isto significa: altere esta função para que ela atue no tipo de função F.
A função de elevação geralmente é sinônimo de construtores aninhados para um determinado tipo. Portanto, 
EitherT.liftF é essencialmente 
EitherT.right. Exemplo do Scaladoc :
 import cats.data.EitherT import cats.implicits._ EitherT.liftF("a".some)  
Cereja no bolo: o 
lift presente em toda a biblioteca padrão da Scala. O exemplo mais popular (e talvez o mais útil no trabalho diário) é 
PartialFunction :
 val intMatcher: PartialFunction[Int, String] = { case 1 => "jak się masz!" } val liftedIntMatcher: Int => Option[String] = intMatcher.lift liftedIntMatcher(1)  
Agora podemos avançar para questões mais prementes.
6) mapN
mapN é uma função auxiliar útil para trabalhar com tuplas. Novamente, isso não é uma novidade, mas um substituto para o bom e velho operador 
|@| Ele é um grito.
Aqui está a aparência de mapN no caso de uma tupla de dois elementos:
 
Em essência, ele permite mapear valores dentro de uma tupla a partir de qualquer F que seja um semigrupo (produto) e um functor (mapa). Então:
 import cats.implicits._ ("a".some, "b".some).mapN(_ ++ _)  
A propósito, não esqueça que, com gatos, você obtém mapa e mapa 
leftmap para tuplas:
 ("a".some, List("b","c").mapN(_ ++ _))  
Outra função útil 
.mapN é instanciar classes de caso:
 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)) 
Obviamente, você prefere usar o operador de loop for para isso, mas o mapN evita transformadores monádicos em casos simples.
 import cats.effect.IO import cats.implicits._  
Os métodos têm resultados semelhantes, mas o último dispensa transformadores monádicos.
5) Aninhado
Nested é essencialmente um duplo generalizado de transformadores de mônada. Como o nome sugere, ele permite executar operações de anexo sob certas condições. Aqui está um exemplo para 
.map(_.map( : import cats.implicits._ import cats.data.Nested val someValue: Option[Either[Int, String]] = "a".asRight.some Nested(someValue).map(_ * 3).value  
Além do 
Functor , o 
Nested generaliza 
Applicative , 
ApplicativeError e 
Traverse . Informações e exemplos adicionais estão 
aqui .
4) .recover / .recoverWith / .handleError / .handleErrorWith / .valueOr
A programação funcional no Scala tem muito a ver com o tratamento do efeito de erro. 
ApplicativeError e 
MonadError têm alguns métodos úteis, e pode ser útil descobrir as diferenças sutis entre os quatro principais. Então, com 
ApplicativeError F[A]:- handleErrorconverte todos os erros no ponto de chamada em A de acordo com a função especificada.
- recoveratua de maneira semelhante, mas aceita funções parciais e, portanto, pode converter erros selecionados em A.
- handleErrorWithé semelhante ao- handleError, mas seu resultado deve se parecer com- F[A], o que significa que ajuda a converter erros.
- recoverWithage como recuperar, mas também requer- F[A]como resultado.
Como você pode ver, você pode limitar- 
handleErrorWith a 
handleErrorWith e 
recoverWith , que cobrem todas as funções possíveis. No entanto, cada método tem suas vantagens e é conveniente à sua maneira.
Em geral, aconselho que você se familiarize com a API 
ApplicativeError , que é uma das mais ricas em gatos e herdada do MonadError - o que significa que é suportada em 
cats.effect.IO , 
monix.Task etc.
Há outro método para 
Either/EitherT , 
Validated e 
.valueOr - 
.valueOr . Essencialmente, ele funciona como 
.getOrElse for 
Option , mas é genérico para classes que contêm algo "à esquerda".
 import cats.implicits._ val failure = 400.asLeft[String] failure.valueOr(code => s"Got error code $code")  
3) gatos de rua
gatos de rua é uma solução conveniente para dois casos:
- instâncias de classes de blocos que não seguem suas leis 100%;
- Typklassy auxiliar incomum, que pode ser usado corretamente.
Historicamente, a instância de mônada para o 
Try mais popular neste projeto, porque o 
Try , como você sabe, não satisfaz todas as leis monádicas em termos de erros fatais. Agora ele é verdadeiramente apresentado aos gatos.
Apesar disso, recomendo que você se familiarize com 
este módulo , que pode lhe parecer útil.
2) Tratar responsavelmente as importações
Você deve saber - da documentação, do livro ou de outro lugar - que os gatos usam uma hierarquia de importação específica:
cats.x para tipos básicos (kernel);
cats.data para tipos de dados como Validado, transformadores de mônada, etc;
cats.syntax.x._ para oferecer suporte a métodos de extensão para que você possa chamar sth.asRight, sth.pure, etc;
cats.instances.x. _ para importar diretamente a implementação de várias classes para o escopo implícito de tipos concretos individuais, para que, ao chamar, por exemplo, sth.pure, o erro "implícito não encontrado" não ocorra.
Obviamente, você notou a importação de 
cats.implicits._ , que importa toda a sintaxe e todas as instâncias da classe type no escopo implícito.
Em princípio, ao desenvolver com o Cats, você deve começar com uma certa sequência de importações do FAQ, a saber:
 import cats._ import cats.data._ import cats.implicits._ 
Se você conhece melhor a biblioteca, pode combiná-la com o seu gosto. Siga uma regra simples:
- cats.syntax.xfornece sintaxe de extensão relacionada a x;
- cats.instances.xfornece classes de instância.
Por exemplo, se você precisar de 
.asRight , que é um método de extensão para 
Either , faça o seguinte:
 import cats.syntax.either._ "a".asRight[Int]  
Por outro lado, para obter o 
Option.pure você precisa importar 
cats.syntax.monad AND cats.instances.option :
 import cats.syntax.applicative._ import cats.instances.option._ "a".pure[Option]  
Ao otimizar manualmente sua importação, você limitará os escopos implícitos nos seus arquivos Scala e, assim, reduzirá o tempo de compilação.
No entanto, por favor: não faça isso se as seguintes condições não forem atendidas:
- você já domina bem os gatos
- sua equipe é proprietária da biblioteca no mesmo nível
Porque Porque:
 
Isso ocorre porque 
cats.implicits e 
cats.instances.option são extensões de 
cats.instances.OptionInstances . De fato, importamos seu escopo implícito duas vezes, depois confundimos o compilador.
Além disso, não há mágica na hierarquia de implícitos - esta é uma sequência clara de extensões de tipo. Você só precisa se referir à definição de 
cats.implicits e examinar a hierarquia de tipos.
Por cerca de 10 a 20 minutos, você pode estudá-lo o suficiente para evitar problemas como esses - acredite, esse investimento definitivamente compensará.
1) Não se esqueça das atualizações dos gatos!
Você pode pensar que sua biblioteca FP é atemporal, mas na verdade 
cats e 
scalaz atualizando ativamente. Tome gatos como um exemplo. Aqui estão apenas as alterações mais recentes:
Portanto, ao trabalhar com projetos, não se esqueça de verificar a versão da biblioteca, ler as notas para novas versões e atualizar a tempo.