Blabla@Scala

Blabla@Scala - Langages fonctionnels - Programmation

Marsh Posté le 11-12-2020 à 18:55:55    

MP public entre LeRiton et moi pour l'instant, pour éviter d'entremêler les questions relatives à Scala dans blabla@prog. :D
 
Je tenterai de faire quelque chose du premier post s'il y a de la demande pour ça.
 
Sujets bienvenus:
- écosystèmes fonctionnels: Scalaz, Typelevel, ZIO
- systèmes distribués: Finagle, Akka,  
- frameworks web: Play, Akka HTTP, http4s, ...
- bibliothèques et outils en tout genre
- resources pour l'apprentissage
- Scala 3  [:frog sad]  
- Spark [:ponaygay:4]  
- drama dans la communauté [:sord:5]


---------------
click clack clunka thunk
Reply

Marsh Posté le 11-12-2020 à 18:55:55   

Reply

Marsh Posté le 14-12-2020 à 00:56:29    

Question du Riton, pourquoi utiliser Tapir, ici avec http4s.
 
Petit exemple, une API qui prend un code postal suisse en paramètre, qui ont la propriété d'être compris entre 1000 et 9999, ça va être plus simple qu'un code postal français pour la démo. :D
 
Un value class pour emballer l'entier qui représente le code postal, et son codec en partant du décodeur d'entier fourni par Tapir:

Code :
  1. case class PostalCode(id: Int) extends AnyVal
  2. object PostalCode {
  3.   implicit val postalCodeCodec: Codec[String, PostalCode, CodecFormat.TextPlain] =
  4.     Codec.int
  5.       .validate(Validator.min(1000).and(Validator.max(9999)))
  6.       .map(i => PostalCode(i))(_.id)
  7. }


 
Gestion custom des erreurs pour leur attacher un statut HTTP:
 

Code :
  1. sealed trait CustomError extends Product with Serializable
  2. case class PostalCodeNotFound(code: PostalCode) extends CustomError
  3. case class ServerError(reason: String) extends CustomError
  4. val errorHandling = oneOf[CustomError](
  5.     statusMappingFromMatchType(StatusCode.NotFound, jsonBody[PostalCodeNotFound]),
  6.     statusMappingFromMatchType(StatusCode.BadGateway, jsonBody[ServerError])
  7.   )


 
Les extracteurs pour les requêtes, un path param, un query param qui accepte une liste non-vide de codes postaux:

Code :
  1. val pathParam = path[PostalCode]("postalCode" )
  2. val queryParam = query[List[PostalCode]]("codes" ).validate(Validator.minSize(1))


 
Les routes:

Code :
  1. val base = endpoint.get.in("tapir" )
  2. val getOne = base.in("area" / pathParam).out(jsonBody[Response]).errorOut(errorHandling)
  3. val getMany = base.in("areas" / queryParam).out(jsonBody[List[Response]]).errorOut(errorHandling)


 
La doc OpenAPI autogénérée:

openapi: 3.0.3
info:
  title: Swiss postal codes
  version: '1.0'
servers:
- url: http://localhost:8080
paths:
  /tapir/area/{postalCode}:
    get:
      operationId: getTapirAreaPostalcode
      parameters:
      - name: postalCode
        in: path
        required: true
        schema:
          type: integer
          minimum: 1000
          maximum: 9999
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Response'
        '404':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostalCodeNotFound'
        '502':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerError'
  /tapir/areas:
    get:
      operationId: getTapirAreas
      parameters:
      - name: codes
        in: query
        required: false
        schema:
          type: array
          items:
            type: integer
            minimum: 1000
            maximum: 9999
          minItems: 1
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Response'
        '404':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostalCodeNotFound'
        '502':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerError'
components:
  schemas:
    PostalCodeNotFound:
      required:
      - code
      type: object
      properties:
        code:
          type: integer
    ServerError:
      required:
      - reason
      type: object
      properties:
        reason:
          type: string
    Response:
      required:
      # schéma de la réponse
      type: object
      properties:
        # types des champs


 
Et il n'y a plus qu'à connecter les endpoints à la logique qui appelle un service ou une BDD qui retourne un IO[Either[CustomError, Response]] par exemple.


---------------
click clack clunka thunk
Reply

Marsh Posté le 14-12-2020 à 08:51:19    

C'est top, merci !
Je vous tiendrais au jus des choix et difficultés :o

Reply

Marsh Posté le 14-12-2020 à 20:11:38    

DDT a écrit :


Un value class pour emballer l'entier qui représente le code postal, et son codec en partant du décodeur d'entier fourni par Tapir:


J'ai un peu de mal sur le rôle du Codec, notamment comment il complète la partie serialization. Le Codec se suppléé au Decoder Circe ? Ou il le remplace ? Je comprend bien que le rôle du Codec est plus large (validation, transfo), mais est-ce qu'il descend aussi bas ?
 
La serialization est un vrai sujet dans mon cas, on doit bosser avec des classes Java très complexes (bean avec un max d'héritage) et on aimerait autant que possible renforcer les I/O de l'API en fonction de ces beans.

Reply

Marsh Posté le 14-12-2020 à 21:44:35    

Les codecs de Tapir sont nécessaires à transformer les entrées/sorties de tes endpoints; la sérialisation en JSON des entités en entrée et/ou sortie de tes requêtes et réponses n'est qu'une des combinaisons possibles. Dans certains cas Tapir le fait directement, dans d'autres comme le JSON, tu as besoin d'une autre bibliothèque.
Ici par exemple, le binding entre les deux codecs est la méthode jsonBody, dans le module tapir-json-circe (si tu veux utiliser Circe).
 
Dans mon code plus haut je veux construire deux endpoints qui extraient un ou plusieurs codes postaux directement depuis l'URL de la requête.
Donc on a String -> Int qui est déjà fourni par Codec.int, puis Int -> PostalCode que je construis moi-même.
Dans l'autre sens, si je voulais utiliser mes endpoints avec le client STTP par exemple, le codec permet de faire PostalCode -> String pour construire la requête.
 
Avec le Codec attaché aux E/S de ton endpoint, tu as une instance optionnelle de Validator et aussi de Schema qui est dérivé de manière automatique.
Mais tu peux décrire tes schémas explicitement si tu n'es pas satisfait du YAML généré. J'ai pas de Java, mais j'ai des hiérarchies parfois un peu trop complexes pour la dérivation automatique.


---------------
click clack clunka thunk
Reply

Marsh Posté le 21-12-2020 à 14:49:10    

Tu utilises ce pattern pour l'implémentation Tapir <=> http4s ?
https://github.com/hejfelix/tapir-h [...] tion.scala
 
C'est franchement aride pour qui n'a pas 10 ans de cats derrière lui, ça tire énormément de concepts, et le fait que ça dépende d'explicits à foison n'arrange rien.

Reply

Marsh Posté le 21-12-2020 à 16:07:49    

Oui je fais des choses similaires.
 
Note qu'ici y a plein de choses dans la même classe, en vrai tu pourrais simplifier pas mal
 
- La route pour la doc et Swagger UI est déjà fournie par le module swagger-ui-http4s, pas besoin d'aller mélanger le DSL à ton API.
- T'es pas obligé d'utiliser un F générique et tagless final, même si c'est une bonne pratique. Utiliser IO concret partout va réduire le nombre d'implicites.
- ... voire considérer BIO si tu te retrouves à gérer beaucoup de IO[Either[Throwable, R]]
- Le reduceK pour combiner toutes les routes d'un coup c'est malin (je devrais faire ça tiens :D) mais tu peux très bien combiner les routes une à une comme dans les exemples moins compliqués.
- Tous les F.delay ici c'est pour illustrer les effets de bord, en pratique tu devrais déjà recevoir un IO quand tu connectes les routes à ta logique (par exemple si tu appelles doobie).
 


---------------
click clack clunka thunk
Reply

Marsh Posté le 07-01-2021 à 16:25:43    

DDT a écrit :


- T'es pas obligé d'utiliser un F générique et tagless final, même si c'est une bonne pratique. Utiliser IO concret partout va réduire le nombre d'implicites.
[...]
- Le reduceK pour combiner toutes les routes d'un coup c'est malin (je devrais faire ça tiens :D) mais tu peux très bien combiner les routes une à une comme dans les exemples moins compliqués.


C'est typiquement l'exemple où sans la compréhension de F[_] / cats / FP / whatever je butte et je ne trouve rien de pertinent.
C'est sans doute simple, mais comment tu combines plusieurs HttpRoutes[IO] (pas générique) ? J'ai ni reduceK ni <+> a dispo ou alors je vois pas comment.
 
Il y a une barrière à l'entrée assez haute pour qui ne maîtrise pas le FP a utiliser ces outils, on parle pourtant juste de dev une API.
 

Reply

Marsh Posté le 07-01-2021 à 23:06:05    

Oui t'as raison pour <+> (combineK) il faut importer les méthodes d'extension pour SemigroupK

Code :
  1. import cats.syntax.semigroupk._


 
Tu peux toujours partir avec un:

Code :
  1. import cats.syntax.all._


voire en dernier recours:

Code :
  1. import cats.implicits._


s'il te manque aussi l'instance de la typeclass, puis essayer de raffiner tes imports ensuite.
 
Pour celui-là, faut le savoir, ou aller voir un exemple dans la doc, c'est vrai. :jap:
 
Pour le reste, si je vois ce que j'utilise dans ma codebase, c'est surtout des map/flatMap/sequence/traverse, exactement comme je le ferais avec des Future, et 2-3 trucs qui viennent assez naturellement, comme *> (productR) dans Apply qui est très pratique. Et là si tu utilises directement IO, tu n'auras pas besoin d'imports.
 
Je te conseille https://www.scala-exercises.org/cats/
C'est vite fait et ça devrait suffire comme introduction. Il n'y a pas besoin de connaître Cats de bout en bout pour utiliser http4s confortablement. Par exemple HttpRoutes est simplement un type alias qui utilise un Kleisli, mais t'as pas vraiment besoin de le savoir.
 
Et le livre d'Underscore si tu veux approfondir ou juste avoir une référence sous le coude: https://www.scalawithcats.com/
 
Et pourquoi s'embêter avec tout ça finalement? De mon expérience c'est dur d'anticiper le moment ou tu vas faire face à une limitation du framework.
Et là si je dois faire un truc un peu plus bas niveau, c'est vraiment appréciable de pouvoir utiliser Cats Effect (Resource est vraiment génial par exemple) ou fs2 directement, de manière parfaitement intégrée au reste.


---------------
click clack clunka thunk
Reply

Marsh Posté le 11-01-2021 à 17:47:20    

DDT a écrit :

Oui t'as raison pour <+> (combineK) il faut importer les méthodes d'extension pour SemigroupK

Code :
  1. import cats.syntax.semigroupk._


 
Tu peux toujours partir avec un:

Code :
  1. import cats.syntax.all._


voire en dernier recours:

Code :
  1. import cats.implicits._




 
C'est bien ce que j'ai essayé, mais j'arrive à rien même avec un exemple minimaliste :

Code :
  1. import cats.effect.{ContextShift, IO, Sync, Timer}
  2. import cats.syntax.semigroupk._
  3. import cats.implicits._
  4. import cats.syntax.all._
  5. import org.http4s.HttpRoutes
  6. import org.http4s.dsl.Http4sDsl
  7. import sttp.tapir.server.http4s.{Http4sServerOptions, RichHttp4sHttpEndpoint}
  8. class FooService(endpoints: FooEndpoints)
  9.                          (implicit serverOptions: Http4sServerOptions[IO], fs: Sync[IO], timer: Timer[IO], fcs: ContextShift[IO]) {
  10.  object dsl extends Http4sDsl[IO]
  11.  import dsl._
  12.  val routes = testRoute <+> testRoute
  13.  
  14.  private def testRoute: HttpRoutes[IO] =
  15.    HttpRoutes.of[IO] {
  16.      case GET -> Root / "youpi" => Ok("youpla" )
  17.    }
  18. }


 
J'importe la terre entière, il n'y a strictement aucune code et j'obtiens ça :
 

value <+> is not a member of org.http4s.HttpRoutes[cats.effect.IO]


je n'ai évidemment pas possibilité d'appeler combineK sur l'objet non plus.
 
Je suis en Scala 2.12, j'ai bien -Ypartial-unification dans les options de compilation.
Franchement je sèche. Je passe à côté d'une évidence ?
 

DDT a écrit :


Je te conseille https://www.scala-exercises.org/cats/
C'est vite fait et ça devrait suffire comme introduction. Il n'y a pas besoin de connaître Cats de bout en bout pour utiliser http4s confortablement. Par exemple HttpRoutes est simplement un type alias qui utilise un Kleisli, mais t'as pas vraiment besoin de le savoir.
 
Et le livre d'Underscore si tu veux approfondir ou juste avoir une référence sous le coude: https://www.scalawithcats.com/
 
Et pourquoi s'embêter avec tout ça finalement? De mon expérience c'est dur d'anticiper le moment ou tu vas faire face à une limitation du framework.
Et là si je dois faire un truc un peu plus bas niveau, c'est vraiment appréciable de pouvoir utiliser Cats Effect (Resource est vraiment génial par exemple) ou fs2 directement, de manière parfaitement intégrée au reste.


:jap:
J'étais tombé sur le livre d'Underscore, il est dans ma todo. Je vais commencer par scala-exercices/cats
Merci encore pour ton aide !
 

Reply

Marsh Posté le 11-01-2021 à 17:47:20   

Reply

Marsh Posté le 13-01-2021 à 00:22:34    


import cats.effect.{IO, Sync}
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
 
class FooService()(implicit fs: Sync[IO]) extends Http4sDsl[IO] {
 
  val routes: HttpRoutes[IO] = testRoute <+> testRoute
 
  private def testRoute: HttpRoutes[IO] =
    HttpRoutes.of[IO] { case GET -> Root / "youpi" =>
      Ok("youpla" )
    }
}
 


 
Ça doit compiler.
 
- Les imports faut choisir: soit tu importes uniquement les méthodes d'extension pour SemigroupK, soit toutes (cats.syntax.all), soit tous les implicites (qui correspond à syntax.all et instances.all). Si tu importes plusieurs fois les mêmes implicites ça compile pas.
- Le trait Http4sDsl tu peux l'utiliser comme mix-in, c'est plus propre.
 
Mais sinon ça devrait marcher.
 
Pour les paramètres de scalac j'utilise https://github.com/DavidGregory084/sbt-tpolecat
Ça force à garder le code relativement propre.


---------------
click clack clunka thunk
Reply

Marsh Posté le 13-01-2021 à 08:45:55    

Haha, un énorme merci tu m'as aidé à trouver le problème :D

DDT a écrit :


- Les imports faut choisir: soit tu importes uniquement les méthodes d'extension pour SemigroupK, soit toutes (cats.syntax.all), soit tous les implicites (qui correspond à syntax.all et instances.all). Si tu importes plusieurs fois les mêmes implicites ça compile pas.


Bien sûr, j'ai mis la totale dans mon exemple pour montrer ce que j'avais essayé :jap:

 

C'était bien un problème de compilateur et pas d'imports au final. Important pour la suite : j'utilise Maven (because reasons) sur Intellij, pas sbt.
Le `-Ypartial-unification` était dans les settings scalac de maven, pas ceux de l'IDE.
Ton exemple minimaliste et l'assurance que ça devait compiler m'ont aidé à isoler le problème, merci beaucoup. C'est ce qui est compliqué en débutant sur un truc très riche, j'ai tendance à remettre en question mon code plutôt que le setup.

 

Edit : bon ça build dans l'IDE mais c'est toujours rouge dans le code. Au moins le problème est isolé :D non rien :o


Message édité par LeRiton le 13-01-2021 à 09:00:18
Reply

Marsh Posté le 11-02-2021 à 10:39:52    

Je sens que c'est pas très idiomatique, mais est-ce qu'il y a un moyen d'utiliser un type paramétré sur la définition d'un endpoint ?
 

Code :
  1. def getOneEndpoint: Endpoint[UUID, String, T, Any] = {
  2.    typeRoot
  3.      .get
  4.      .in(path[UUID]("uuid" ))
  5.      .out(jsonBody[T])
  6.      .errorOut(jsonBody[String])
  7.      .description(s"GET one $modelType" )
  8.  }


 
le code lève une erreur sur 'jsonBody' du fait de l'absence de decoder et encoder implicit pour T.
 

Code :
  1. implicit val encoder: Encoder[T] = deriveEncoder[T]


n'aide pas :

could not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder[T]


Reply

Marsh Posté le 11-02-2021 à 11:00:23    

Y a pas de raisons que tu puisses pas mais du coup il te faut

 
Code :
  1. def getOneEndpoint[T]
 

ou

 
Code :
  1. def getOneEndpoint[T: Encoder]
 

selon où tu vas appeler cette méthode.

 

Mais tu vas vraiment connecter l'endpoint à un service qui peut te retourner n'importe quel type en sortie?
Car là tu ne fais que déplacer la contrainte sur le polymorphisme de T: il te faut forcément une instance d'Encoder lorsque tu appelles cette méthode, donc ça veut dire que tu sais ce qu'est T à un endroit ou un autre, non?


Message édité par DDT le 11-02-2021 à 11:34:55

---------------
click clack clunka thunk
Reply

Marsh Posté le 11-02-2021 à 11:49:33    

Chez moi ça compile pas, les encoders/decodes/schemas ne sont pas accessibles.

 
Code :
  1. class TypedEndpoints[T <: MyTrait] {
  2.  
  3.  def getOneEndpoint: Endpoint[UUID, String, T, Any] = {
  4.    typeRoot
  5.      .get
  6.      .in(path[UUID]("uuid" ))
  7.      .out(jsonBody[T])
  8.      .errorOut(jsonBody[String])
  9.      .description("GET one" )
  10.  }
  11.  
  12.  def createOneEndpoint: Endpoint[T, String, UUID, Any] = {
  13.    typeRoot
  14.      .post
  15.      .in(jsonBody[T])
  16.      .out(jsonBody[UUID])
  17.      .errorOut(jsonBody[String])
  18.      .description("Creates new" )
  19.  }
  20. }
 

que je veux utiliser ensuite de cette manière

Code :
  1. case class Foo extends MyTrait
  2. val endpoints = new Endpoints[Foo]
 

Et oui je comprend ta remarque, je pense que je passe à côté de quelque chose. Dans mon code tous les sous types de MyTrait sont des case class, mais le compilo n'a pas moyen de le savoir.
Je voudrais donner un contrat simple disant que je veux des endpoints typés avec la garantie que le type est une case class et pour lequel je m'attend donc à ce que jsonBody[T] sache décoder/encoder sans surcoût.
Les case classes correspondantes ont toutes une logique métier commune qui est détaillée dans MyTrait et pour laquelle j'ai également un service paramétré.

 

Je pense qu'il y a moyen de faire autrement, mais j'aimerais avant d'exposer ça de manière détaillée expérimenter jusqu'au bout cette première implem, et je ne butte que sur le jsonBody.


Message édité par LeRiton le 11-02-2021 à 11:51:48
Reply

Marsh Posté le 11-02-2021 à 12:21:20    

Dans ce cas

Code :
  1. class TypedEndpoints[T <: MyTrait: Encoder: Decoder] { ... }


 
Si MyTrait est sealed tu peux utiliser la dérivation (semi)automatique de Circe.
 
Si c'est pas le cas, tu définis tes codecs à la main et tu gères toutes les case classes.
 
Par exemple dans un trait MyTraitCodecs comme mix-in

Code :
  1. trait MyTraitCodecs {
  2.   implicit val myTraitEncoder: Encoder[MyTrait] = ...
  3.   implicit val myTraitDecoder: Decoder[MyTrait] = ...
  4. }
  5. class TypedEndpoints[T <: MyTrait] extends MyTraitCodecs { ... }


 
En gros 1) avoir les codecs quelque part 2) les passer à jsonBody: soit implicitement, soit par un import, soit mixed in.


---------------
click clack clunka thunk
Reply

Marsh Posté le 11-02-2021 à 13:42:06    

DDT a écrit :

Si MyTrait est sealed tu peux utiliser la dérivation (semi)automatique de Circe.

 

Tu peux compléter ce point particulier ?

Code :
  1. sealed trait MyTrait {
  2.  implicit val decoder: Decoder[MyTrait] = deriveDecoder[MyTrait]
  3.  implicit val encoder: Encoder[MyTrait] = deriveEncoder[MyTrait]  
  4. }
 


could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[foo.MyTrait]
  implicit val decoder: Decoder[MyTrait] = deriveDecoder[MyTrait]


ou je comprend mal et il faut tout de même déclarer les implicits dans chacune des case class ?
Comme :

Code :
  1. sealed trait MyTrait[T] {
  2.  implicit val decoder: Decoder[T]
  3.  implicit val encoder: Encoder[T]
  4. }
  5. case class Foo extends MyTrait[Foo] {
  6.  override implicit val decoder: Decoder[Foo] = deriveDecoder[Foo]
  7.  override implicit val encoder: Encoder[Foo] = deriveEncoder[Foo]  
  8. }


Message édité par LeRiton le 11-02-2021 à 13:42:44
Reply

Marsh Posté le 12-02-2021 à 00:58:25    

Y a plusieurs stratégies pour dériver les codecs d'un ADT, mais oui tu peux appeler deriveEncoder/deriveDecoder (ou deriveCodec tant qu'à faire) dans chaque objet companion de tes case classes.
Ici avec la dérivation auto ça marche en tout cas.
 

Code :
  1. import io.circe.{Decoder, Encoder}
  2. import io.circe.generic.auto._
  3. import sttp.tapir._
  4. import sttp.tapir.json.circe.jsonBody
  5. import sttp.tapir.generic.auto._
  6. import java.util.UUID
  7. sealed trait Foo
  8. case class Bar(i: Int) extends Foo
  9. case class Baz(s: String) extends Foo
  10. case class Qux(b: Boolean) extends Foo
  11. class TypedEndpoints[T <: Foo: Encoder: Decoder: Schema: Validator] {
  12.   def getOneEndpoint: Endpoint[UUID, String, T, Any] = {
  13.     endpoint
  14.       .get
  15.       .in(path[UUID]("uuid" ))
  16.       .out(jsonBody[T])
  17.       .errorOut(jsonBody[String])
  18.       .description("GET one" )
  19.   }
  20.   def createOneEndpoint: Endpoint[T, String, UUID, Any] = {
  21.     endpoint
  22.       .post
  23.       .in(jsonBody[T])
  24.       .out(jsonBody[UUID])
  25.       .errorOut(jsonBody[String])
  26.       .description("Creates new" )
  27.   }
  28. }
  29. object TypedEndpoints {
  30.   val endpoints = new TypedEndpoints[Bar]
  31. }


---------------
click clack clunka thunk
Reply

Marsh Posté le 26-03-2021 à 13:54:40    

Vous utilisez quoi comme formateur / règles de formatage de code, des imports ?
scalafmt est ce qui revient le plus souvent mais j'aime pas le style (:o) et j'aimerais pour autant ne pas avoir à créer de règles customs. Il y a un standard admis par la communauté ? Si le tooling suit, c'est bonus.
 
Merci !

Reply

Marsh Posté le 26-03-2021 à 15:11:47    

J'utilise scalafmt, c'est un truc que j'ai introduit dans toutes mes équipes dès qu'Olaf l'a présenté publiquement. :D
Y a plusieurs presets pour le style, t'as essayé quoi?

 

IntelliJ détecte le fichier de configuration et l'utilise par défaut depuis un moment déjà.
J'ai ajouté un check dans notre CI/CD.

 

Pour les imports on se contente de l'optimisation des imports par IntelliJ. L'ordre n'est pas vérifié mais si tu as "-Wunused:imports" au moins tu ne rates pas les imports inutiles.
Pour paramétrer scalac de manière stricte cf. https://github.com/DavidGregory084/sbt-tpolecat


Message édité par DDT le 26-03-2021 à 15:34:13

---------------
click clack clunka thunk
Reply

Marsh Posté le 26-03-2021 à 15:32:48    

Je ne suis pas allé plus loin que le preset par défaut. Ce qui m'irrite dans scalafmt, c'est le parti-pris du line break pour les paramètres de classes et arguments de méthode.
J'aime que le line break n'intervienne que sur mon max column, je préfère avoir plus de code sous les yeux sur une même hauteur d'écran.

 

Alors oui j'imagine que ça se configure, mais je suis plutôt partisan d'adhérer en bloc (à la limite à un preset de haut niveau alternatif) plutôt que de maintenir ses propres règles.

 

Mais si tout le monde aime scalafmt et le default preset, je ferais avec :o

 

Edit : en se basant sur mon rant et si j'ai bien compris, le preset Intellij devrait faire le job.
Tu (vous ?) utilisez quel preset si scalafmt ?


Message édité par LeRiton le 26-03-2021 à 15:38:10
Reply

Marsh Posté le 27-03-2021 à 14:13:02    

defaultWithAlign et 120 colonnes max.


---------------
click clack clunka thunk
Reply

Marsh Posté le 14-04-2021 à 12:12:49    

Vous utilisez des best practices / coding conventions / coding styles particuliers, et idéalement documentés et publics ?
Le langage permet de décliner sur plusieurs paradigmes et j'aimerais m'appuyer sur des références pour homogénéiser notre base de code et les reviews.

Reply

Marsh Posté le 16-04-2021 à 10:53:24    

Je dirige les débutants vers https://nrinaudo.github.io/scala-best-practices/ (Nicolas a présenté le contenu à plusieurs conférences, y a même une présentation en français à Scala IO).
 
En plus de Scalafmt, j'aime pas Scalastyle mais on utilise Scapegoat et scalac configuré avec les flags activés par sbt-tpolecat.
 
T'utilises Maven et scala 2.12?

Code :
  1. "-encoding", "utf8",
  2. "-deprecation",
  3. "-explaintypes",
  4. "-feature",
  5. "-language:existentials",
  6. "-language:experimental.macros",
  7. "-language:higherKinds",
  8. "-language:implicitConversions",
  9. "-unchecked",
  10. "-Xcheckinit",
  11. "-Xfatal-warnings",
  12. "-Xlint:adapted-args",
  13. "-Xlint:by-name-right-associative",
  14. "-Xlint:constant",
  15. "-Xlint:delayedinit-select",
  16. "-Xlint:doc-detached",
  17. "-Xlint:inaccessible",
  18. "-Xlint:infer-any",
  19. "-Xlint:missing-interpolator",
  20. "-Xlint:nullary-override",
  21. "-Xlint:nullary-unit",
  22. "-Xlint:option-implicit",
  23. "-Xlint:package-object-classes",
  24. "-Xlint:poly-implicit-overload",
  25. "-Xlint:private-shadow",
  26. "-Xlint:stars-align",
  27. "-Xlint:type-parameter-shadow",
  28. "-Xlint:unsound-match",
  29. "-Yno-adapted-args",
  30. "-Ywarn-dead-code",
  31. "-Ywarn-extra-implicit",
  32. "-Ywarn-nullary-override",
  33. "-Ywarn-nullary-unit",
  34. "-Ywarn-numeric-widen",
  35. "-Ywarn-unused:implicits",
  36. "-Ywarn-unused:imports",
  37. "-Ywarn-unused:locals",
  38. "-Ywarn-unused:params",
  39. "-Ywarn-unused:patvars",
  40. "-Ywarn-unused:privates",
  41. "-Ywarn-value-discard",
  42. "-Ypartial-unification",
  43. "-Ywarn-macros:after"


 
Pour les faux positifs, y a l'annotation @nowarn qui a été backportée dans Scala 2.12.13.


---------------
click clack clunka thunk
Reply

Marsh Posté le 17-04-2021 à 19:30:52    

Wartremover est populaire également, peut-être plus que Scapegoat. Mais son fonctionnement semble plus primitif.


---------------
click clack clunka thunk
Reply

Marsh Posté le 21-04-2021 à 08:22:23    

DDT a écrit :

Je dirige les débutants vers https://nrinaudo.github.io/scala-best-practices/ (Nicolas a présenté le contenu à plusieurs conférences, y a même une présentation en français à Scala IO).


C'est pas exhaustif mais c'est une bonne première base :jap:
Certains point un peu à contre courant de mon intuition, c'est intéressant, à s'approprier.
 

DDT a écrit :


En plus de Scalafmt, j'aime pas Scalastyle mais on utilise Scapegoat et scalac configuré avec les flags activés par sbt-tpolecat.
 
T'utilises Maven et scala 2.12?


Top merci !
Pour le moment on est sur Sonar pour des raisons historiques, autant dire peu d'utilité pour Scala. Je vais regarder dans le détail.
Oui pour les flags de compil, cette liste était déjà dans ma todo :jap:
 
Encore merci pour tes conseils
 
 
 

Reply

Marsh Posté le 21-04-2021 à 10:20:48    

Par curiosité quels sont les points contre-intuitifs? :D
 
Après les bonnes pratiques et pièges de bases, si tu veux une présentation un peu plus haut niveau, récemment il y a eu ça: https://www.youtube.com/watch?v=UT2K9c66xCU
 
Ça vient avec l'expérience mais c'est pas mal de remettre les choses à plat avant de se laisser influencer par le design des frameworks qu'on utilise.


---------------
click clack clunka thunk
Reply

Marsh Posté le 21-04-2021 à 21:10:45    

DDT a écrit :

Par curiosité quels sont les points contre-intuitifs? :D

 

Après les bonnes pratiques et pièges de bases, si tu veux une présentation un peu plus haut niveau, récemment il y a eu ça: https://www.youtube.com/watch?v=UT2K9c66xCU

 

Ça vient avec l'expérience mais c'est pas mal de remettre les choses à plat avant de se laisser influencer par le design des frameworks qu'on utilise.


Je suis plus devant le PC mais de tête, j'ai tiqué sur privilégier abstract class VS trait.
On doit d'ailleurs pouvoir lire le contraire sur pas mal de blog Scala, bien que je n'ai rien pour sourcer à l'instant.
Je ne dis pas que l'argumentaire derrière ne tient pas, seulement qu'avant de lire je serais parti dans la direction inverse.

Reply

Marsh Posté le 23-04-2021 à 11:05:43    

Oui celle-ci est peut-être contre-intuitive, tu as raison. :jap: Mais il explique bien dans quel cas l'utilisation systématique de trait peut poser problème, et si ça te concerne pas...

 

D'ailleurs les traits de Scala 3 supportent des paramètres donc j'imagine qu'on va de moins en moins voir des classes abstraites.


Message édité par DDT le 23-04-2021 à 11:05:55

---------------
click clack clunka thunk
Reply

Marsh Posté le 09-05-2021 à 19:27:37    

Salut ! :o
 
Petite question: qu'est-ce que vous avez comme ressource, pour apprendre le scala le plus rapidement possible, pour un javateux ?


---------------
Les aéroports où il fait bon attendre, voila un topic qu'il est bien
Reply

Marsh Posté le 09-05-2021 à 20:25:32    

C'est pour quelle utilisation ?

 

Car Scala pour faire du Spark, Akka ou http4s ça va pas vraiment être la même chose.

 

Le blog de Daniel Westheide est un grand classique : https://danielwestheide.com/books/t [...] -to-scala/

 

Scala Exercises: https://www.scala-exercises.org/ au moins le chapitre sur la bibliothèque standard.


---------------
click clack clunka thunk
Reply

Marsh Posté le 17-05-2021 à 09:16:35    

C'est agité ici :o
Tu conseilles quel bouquin pour apprendre le FP ? Le Red Book ? On est toujours sur la première édition de 2014, ce n'est pas dérangeant ?

 

Edit : Functional Programming for Mortals est fait pour moi sur le papier, mais basé sur Scalaz là où j'ai tendance à partir sur du vanilla pour apprendre un nouveau concept. Accessoirement, on est plutôt sur Typelevel dans notre stack actuelle.


Message édité par LeRiton le 17-05-2021 à 09:27:52
Reply

Marsh Posté le 17-05-2021 à 09:59:43    

Le livre rouge est daté mais je pense que la théorie des catégories n'a pas changé tant que ça. :D
Si tu veux apprendre comment Scalaz et Cats ont été implémentées ça reste une référence.
 
Après j'y ai jamais touché et je préfère également les approches plus haut niveau.
 
Il y avait un version with Cats de FP for mortals mais elle semble avoir disparu. [:gratgrat]  
 
J'ai déjà mentionné https://www.scalawithcats.com/
Il y a aussi https://eed3si9n.com/herding-cats/ qui est très bon.


---------------
click clack clunka thunk
Reply

Marsh Posté le 17-05-2021 à 13:24:45    

Merci.
Selon toi et en venant de l'OOP sans background de math très poussé, l'approche Scala with Cats est suffisante ?
Est-ce que le Red Book conserve un intérêt ?

Reply

Marsh Posté le 17-05-2021 à 13:47:23    

Scala with Cats me semble largement suffisant.
 
Les concepts mathématiques sont pas très compliqués cela dit, peu importe la manière dont ils sont approchés.


---------------
click clack clunka thunk
Reply

Marsh Posté le 17-05-2021 à 14:03:24    

Je pars là dessus, merci :jap:

Reply

Marsh Posté le 24-06-2021 à 17:43:11    

J'ai deux questions Tapir. Comme d'habitude c'est sans doute trivial mais je sèche un peu et la doc ne m'aide pas.
 

Code :
  1. import sttp.tapir._
  2. import sttp.tapir.generic.auto._
  3. object Test {
  4.  case class Foo(foo: String)
  5.  val testEndpoint = endpoint
  6.    .in(formBody[Foo])
  7. }


ça compile mais Intellij me donne du rouge sur formBody avec un hint "No implicits found for evidence Codec[String, Foo, CodecFormatXWwwForUrlencoded]", c'est un peu pénible. Je suis en Scala 2.13 et l'IDE n'a aucune config spécifique.
 
Ensuite, j'aimerais trouver un moyen élégant pour définir une route qui sera utilisée par un client, en GET, devant porter un header 'authorization: Bearer {access_token}'. Le token est le résultat d'une auth OAuth2 pour info.
Bien évidemment le token est variable.
 

Code :
  1. endpoint.in(auth.bearer[String])


a l'air dédié a de la lecture ("reads data from the Authorization header" ) et je ne trouve pas de manière élégante de faire autrement. J'ai bien  

Code :
  1. .in(header[String]("authorization" ))


mais ça m'impose de passer `s"Bearer {accessToken}"` en paramètre plutôt que le token seul et c'est plutôt moche, je pense passer à côté d'un truc obvious.
 
Merci !

Reply

Marsh Posté le 24-06-2021 à 18:15:21    

Pour ton premier point j'ai l'impression que c'est IntelliJ qui ne réalise pas que ta classe hérite bien de Product with Serializable.
 
Si je fais

case class Foo(foo: String) extends Product with Serializable


 
Plus de soulignement en rouge.


---------------
click clack clunka thunk
Reply

Marsh Posté le 24-06-2021 à 18:31:54    

Pour ta 2e question j'ai pas compris, auth.bearer fonctionne dans un sens (pour le serveur) mais pas dans l'autre (client)?
En regardant le code j'ai bien l'impression que ça fait ce que tu veux...  [:gratgrat]

Message cité 1 fois
Message édité par DDT le 24-06-2021 à 18:32:23

---------------
click clack clunka thunk
Reply

Marsh Posté le 24-06-2021 à 22:32:54    

DDT a écrit :

Pour ton premier point j'ai l'impression que c'est IntelliJ qui ne réalise pas que ta classe hérite bien de Product with Serializable.

 

Si je fais

case class Foo(foo: String) extends Product with Serializable

 

Plus de soulignement en rouge.


Exact, même comportement de mon côté. C'est quand même pénible, je vais créer un ticket.

 
DDT a écrit :

Pour ta 2e question j'ai pas compris, auth.bearer fonctionne dans un sens (pour le serveur) mais pas dans l'autre (client)?
En regardant le code j'ai bien l'impression que ça fait ce que tu veux...  [:gratgrat]

 

Yep my bad, il lui faut les parenthèses.

 

Tu sais s'il existe un moyen simple de voir la requête client qui serait générée à partir d'un endpoint ?

 

Comme d'habitude, merci pour tes réponses :jap:


Message édité par LeRiton le 24-06-2021 à 22:33:15
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed