Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
2591 connectés 

 

 

 Mot :   Pseudo :  
 
 Page :   1  2
Page Précédente
Auteur Sujet :

Blabla@Scala

n°2371253
DDT
Few understand
Posté le 11-12-2020 à 18:55:55  profilanswer
 

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
mood
Publicité
Posté le 11-12-2020 à 18:55:55  profilanswer
 

n°2371461
DDT
Few understand
Posté le 14-12-2020 à 00:56:29  profilanswer
 

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
n°2371469
LeRiton
Posté le 14-12-2020 à 08:51:19  profilanswer
 

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

n°2371551
LeRiton
Posté le 14-12-2020 à 20:11:38  profilanswer
 

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.

n°2371558
DDT
Few understand
Posté le 14-12-2020 à 21:44:35  profilanswer
 

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
n°2372101
LeRiton
Posté le 21-12-2020 à 14:49:10  profilanswer
 

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.

n°2372108
DDT
Few understand
Posté le 21-12-2020 à 16:07:49  profilanswer
 

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
n°2373371
LeRiton
Posté le 07-01-2021 à 16:25:43  profilanswer
 

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.
 

n°2373410
DDT
Few understand
Posté le 07-01-2021 à 23:06:05  profilanswer
 

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
n°2373690
LeRiton
Posté le 11-01-2021 à 17:47:20  profilanswer
 

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 !
 

mood
Publicité
Posté le 11-01-2021 à 17:47:20  profilanswer
 

n°2373805
DDT
Few understand
Posté le 13-01-2021 à 00:22:34  profilanswer
 


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
n°2373806
LeRiton
Posté le 13-01-2021 à 08:45:55  profilanswer
 

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
n°2375634
LeRiton
Posté le 11-02-2021 à 10:39:52  profilanswer
 

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]


n°2375635
DDT
Few understand
Posté le 11-02-2021 à 11:00:23  profilanswer
 

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
n°2375640
LeRiton
Posté le 11-02-2021 à 11:49:33  profilanswer
 

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
n°2375642
DDT
Few understand
Posté le 11-02-2021 à 12:21:20  profilanswer
 

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
n°2375647
LeRiton
Posté le 11-02-2021 à 13:42:06  profilanswer
 

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
n°2375678
DDT
Few understand
Posté le 12-02-2021 à 00:58:25  profilanswer
 

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
n°2380190
LeRiton
Posté le 26-03-2021 à 13:54:40  profilanswer
 

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 !

n°2380200
DDT
Few understand
Posté le 26-03-2021 à 15:11:47  profilanswer
 

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
n°2380206
LeRiton
Posté le 26-03-2021 à 15:32:48  profilanswer
 

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
n°2380267
DDT
Few understand
Posté le 27-03-2021 à 14:13:02  profilanswer
 

defaultWithAlign et 120 colonnes max.


---------------
click clack clunka thunk
n°2381719
LeRiton
Posté le 14-04-2021 à 12:12:49  profilanswer
 

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.

n°2381862
DDT
Few understand
Posté le 16-04-2021 à 10:53:24  profilanswer
 

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
n°2381989
DDT
Few understand
Posté le 17-04-2021 à 19:30:52  profilanswer
 

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


---------------
click clack clunka thunk
n°2382272
LeRiton
Posté le 21-04-2021 à 08:22:23  profilanswer
 

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
 
 
 

n°2382288
DDT
Few understand
Posté le 21-04-2021 à 10:20:48  profilanswer
 

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
n°2382369
LeRiton
Posté le 21-04-2021 à 21:10:45  profilanswer
 

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.

n°2382566
DDT
Few understand
Posté le 23-04-2021 à 11:05:43  profilanswer
 

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
n°2384558
el muchach​o
Comfortably Numb
Posté le 09-05-2021 à 19:27:37  profilanswer
 

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
n°2384564
DDT
Few understand
Posté le 09-05-2021 à 20:25:32  profilanswer
 

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
n°2385309
LeRiton
Posté le 17-05-2021 à 09:16:35  profilanswer
 

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
n°2385310
DDT
Few understand
Posté le 17-05-2021 à 09:59:43  profilanswer
 

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
n°2385340
LeRiton
Posté le 17-05-2021 à 13:24:45  profilanswer
 

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 ?

n°2385354
DDT
Few understand
Posté le 17-05-2021 à 13:47:23  profilanswer
 

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
n°2385365
LeRiton
Posté le 17-05-2021 à 14:03:24  profilanswer
 

Je pars là dessus, merci :jap:

n°2389044
LeRiton
Posté le 24-06-2021 à 17:43:11  profilanswer
 

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 !

n°2389046
DDT
Few understand
Posté le 24-06-2021 à 18:15:21  profilanswer
 

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
n°2389049
DDT
Few understand
Posté le 24-06-2021 à 18:31:54  profilanswer
 

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
n°2389059
LeRiton
Posté le 24-06-2021 à 22:32:54  profilanswer
 

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
mood
Publicité
Posté le   profilanswer
 

 Page :   1  2
Page Précédente

Aller à :
Ajouter une réponse
 

Sujets relatifs
Lunch@blabla@devoxxblabla@django
Blabla@Progue[Topic unique] .Net @ Prog
Page Jsp qui affiche le ${ blabla }case in blabla (KSH) ===> fichier CSV
BlaBla@SQLSuivant / Précédant dans appli js (non c'est pas history.blabla ...)
Blabla@Python \o/SBP: le Système d'unités de Blabla@Prog
Plus de sujets relatifs à : Blabla@Scala


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR