Publié le 23 Novembre 2016

En scala l'utilisation des expressions régulières combinée avec le pattern matching permet de simplifier l'extraction de groupes à l'intérieur des regex.

Par exemple imaginons que nous devons extraire les composants d'une date au format aaaa-mm-dd (soit année-mois-jour).

Il nous faut tout d'abord une regex pour extraire les 3 composants: (\d{4})-(\d{2})-(\d{2}). On utilise 3 groupes (les parenthèses) composés de 4 puis 2 digits et séparé par des -.

Ensuite il n'y a plus qu'a combiner cette regex avec un pattern matching. Ce qui donne:

// la date à parser
val str = "2016-11-23"
// la regex pour extraire les composants
val date = """(\d{4})-(\d{2})-(\d{2})""".r

str match {
   case date(annee, mois, jour) => println(s"Date $jour/$mois/$annee")
   case _ => println("date invalide")
}

 

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala

Publié le 10 Octobre 2016

Play ne supporte pas nativement la serialisation d'enum vers json. (Je pense qu'il s'agit d'une sombre histoire de compatibilité avec la version Java de Play).

Qu'à cela ne tienne on peut facilement écrire un json format qui fonctionne pour n'importe quelle Enumeration en quelques lignes:

import play.api.libs._

object EnumFormat {
   def formatEnum[E <: Enumeration](enum: E): Format[E#Value] = {
      // méthode pour gérer les erreurs en cas de valeur d'enum inconnue
      def readError(json: JsValue) =
         ValidationError("Can't parse {}, expected one of {}",
             json,
             enum.values.map(_.toString).mkString(", ")
        )

      new Format[E#Value] = {
         override def reads(json: JsValue): JsResult[E#Value] =
            json.validate[String]
              .map(s => Try(enum.withName(s)).toOption)
              .collect(readError(json)) { case Some(e) => e }

         override def writes(e: E#Value): JsValue =
           JsString(e.toString)
   }
}

 

Ensuite il n'y a plus qu'à utiliser EnumFormat.format pour définir un format json pour n'importe quelle enum:

object MyEnum extends Enumeration {
  // Enum avec Oui et Non comme valeur
   val Oui, Non = Value
  implicit val format = EnumFormat.format(MyEnum)
}

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala

Publié le 10 Octobre 2016

Play ne fournit pas par défaut de format pour sérialiser une map en Json. Bien sûr si vous utiliser une Map[String, V] et que vous avez un format implicitement disponible pour le type V alors vous pouvez sérialiser votre map sans effort supplémentaire.

 

En revanche lorsque les clés sont d'un type non-standard. Par exemple pour sérialiser une map dont les clés sont aussi une case class. Dans ce cas autant écrire un format qui supporte n'importe quel type the map: Map[K, V].

 

Pour cela on utilise une case class qui va comporter 2 champs: 1 pour la clé et un pour la valeur. Ce qui donnera le json suivant:

 

[
  {
    "key": {....},
    "value": {...}
  }, {
    "key": {...},
    "value": {....}
  },
  ...
]

 

Il faut donc définir la case class suivante avec les champs key et value:

case class Entry[K, V](key: K, value: V)

 

Ensuite il faut écrire le format qui permettra de sérialiser notre map vers/depuis json:

import play.api.libs.json._
import play.api.libs.functional.syntax._

object MapFormat {
   def format[K, V](implicit formatKey: Format[K], formatValue: Format[V]) =
     new Format[Map[K, V]] {
        // on définit un format qui permet de sérialiser notre case class Entry
        implicit def formatEntry = (
          (__ \ "key").format[K] ~
          (__ \ "value").format[V]
        )(Entry.apply, unlift(Entry.unapply[K, V]))
       
// Apparemment Json.format[Entry[K, V]] ne fonctionne pas
       // (Assertion error at runtime)


      // gère la transformation Map[K, V] vers json

     override def writes(entries: Map[K, V]): JsValue =
        // on transforme notre map en sequence d'Entry et on sérialise vers json
        Json.toJson(entries.map { case (key, value) => Entry(key, value)})

      // gère la transformtion json -> Map[K, V]
      override
def reads(json: JsValue): JsResult[Map[K, V]] =
         // parse le json en List[Entry[K, V]]
         // puis extrait les paires clé/valeur pour en faire une map

         Json.fromJson[List[Entry[K, V]]](json).map(
           _.map(entry => entry.key -> entry.value).toMap
        )
   }
}

 

L'utilisation est la suivante:

// définit les type pour les clés et les valeurs
case class MyKey(underlying: String)

case class MyValue(underlying: String)
// ainsi que les format qui vont avec
implicit val formatKey = Json.format[MyKey]
implicit val formatValue = Json.format[MyValue]
implicit val mapFormat = MapFormat.format[MyKey, MyValue]
// définit notre map
val myMap = Map(
   MyKey("1") -> MyValue("A"),
   MyKey("2") -> MyValue("B")
)
val json = Json.toJson(myMap)

Ce qui produit le json suivant:

[
  {
    "key": {
     
"underlying": "1"
   },
    "value": {
     
"underlying": "A"
   }
  }, {
    "key": {
     
"underlying""2"
   },
    "value": {
     
"underlying": "B"
   }
  }
]

Ici les types MyKey and MyValue sont intentionnellement trop simple. Mais ce qui est bien c'est que le format fonctionne avec n'importe quel type du moment qu'il y a un format disponible.

Et enfin les formats pour les types MyKey et MyValue sont normalement définis dans les companions object pour éviter des problèmes d'import.

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala

Publié le 26 Septembre 2016

Il peut être intéressant de limiter les nombres de cores CPU disponible pour un conteneur.

Par exemple on peut vouloir n'utiliser qu'un seul core pour les conteneurs docker de façon à laisser assez de CPU disponible pour l'hôte.

docker permet de préciser les cores CPU utilisable par un conteneur grâce à l'option --cpus-set.

Cette option accept soit une liste de nombres et/ou d'intervalles.

Par exemple

--cpus-set 0 # CPU 0
--cpus-set 0-3 # CPU 0, 1, 2 et 3
--cpus-set 0,2-4 # CPU 0, 2, 3 et 4

et dans une commande docker run ça donne quelque chose comme:

docker run --cpus-set 0 <image> <commande>

Voir les commentaires

Rédigé par Bliz

Publié dans #docker

Publié le 23 Septembre 2016

Si vous utilisez git avec les feature branch vous avez probablement beaucoup de branch qui ont été mergées et qui ne servent plus à rien mais qui reste là car c'est un peu ennuyeux de les supprimer manuellement.

En revanche sur le repo central les branches mergées sont souvent supprimées automatiquement lorsqu'on merge la pull request associée.

Et bien on peut simplement profiter de ce fait lorsqu'on met à jour notre repo local. Il suffit d'utiliser l'option --prune lorsqu'on pull/fetch le repo central.

git fetch --prune

ou

git pull --prune

Voir les commentaires

Rédigé par Bliz

Publié dans #git

Publié le 19 Septembre 2016

Si vous utilisez docker vous avez surement remarqué que l'espace disque utilisé augmente rapidement. Voici quelques règles pour économiser un peu de place sur votre disque.

Supprimer les volumes associés à un conteneur

Lorsqu'on supprime un conteneur penser à utiliser l'option -v qui permet de supprimer les volumes associés à un conteneur.

Pour supprimer tous les conteneurs qui ne tournent pas on peut utiliser la commande suivante:

docker rm -v $(docker ps -aqf status=exited)

Recréé un conteneur est assez rapide du moment que son image est disponible. Ce qui nous amène vers le nettoyage des images inutiles.

Supprimer les images inutiles

J'appelle image "inutile" une image "intermédiaire" qui sert dans la construction d'une image "finale" et qui n'est donc jamais utilisé pour créer un conteneur.

On peut supprimer ces images avec la commande suivante:

docker rmi $(docker images -qf dangling=true)

Souvent indispensable après un docker pull.

Supprimer les volumes orphelins

Un volume orphelin est un volume pour lequel son conteneur associé a été supprimé sans l'option -v. Pour supprimer ces volumes on a la commande suivante:

docker volume rm $(docker volume ls -qf dangling=true)

Voir les commentaires

Rédigé par Bliz

Publié dans #docker

Publié le 16 Septembre 2016

J'ai récemment eu un build sbt qui échouait à cause d'un OutOfMemory exception.

La solution: donner plus de mémoire à sbt. Et la bonne nouvelle c'est que c'est maintenant beaucoup plus simple. Il suffit d'ajouter l'option -mem. Par exemple:

sbt -mem 2048 run

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala

Publié le 13 Septembre 2016

Dans un fichier build.sbt les dépendances se déclarent de la façon suivante:

libraryDependencies += "org.nd4j" % "nd4j-native-platform" % "0.5.0"

mais parfois on trouve aussi un double pourcent "%%"

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.4.8"

Quelle est la différence ?

Dans le premier cas (%) on déclare une dépendance vers une librairie java (qui ne dépend pas de la version de scala donc). Dans ce cas le % est suffisant.

Dans le deuxième cas on déclare une dépendance vers une librairie scala qui dépend de la version de scala. Le %% ajoute automatiquement la version de scala au nom de la librairie.

C'est donc équivalent à ceci:

libraryDependencies += "com.typesafe.akka" % "akka-actor_2.11" % "2.4.8"

Notez le suffixe '_2.11' à la fin du nom de la librairie. Le %% permet de l'ajouter automatiquement.

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala

Publié le 12 Août 2016

En ce moment je développe une application play scala et j'utilise donc sbt pour compiler et lancer mon application.

Le moyen le plus simple que j'ai trouvé pour la debugger et de lancer l'appli avec l'option -jvm-debug:

sbt -jvm-debug 9999 run

Ensuite j'utilise mon IDE (IntelliJ dans mon cas) que je configure pour debugger une application distante sur le port que j'ai spécifier avec l'option -jvm-debug.

Voir les commentaires

Rédigé par Bliz

Publié dans #Scala, #Play

Publié le 14 Juillet 2016

Pour accéder un répertoire local depuis un conteneur docker il faut le monter en temps que volume docker. C'est l'équivalent des "shared folders" de Virtual box.

La définition d'un volume s'effectue avec l'option -v de manière similaire au mapping des ports.

Par exemple pour accéder à son répertoire "home" depuis docker on utilisera la commande suivante:

docker run -it -v $HOME:/shared ubuntu /bin/bash

L'option -v précise que le répertoire /shared à l'intérieur du conteneur docker correspond au $HOME de l'utilisateur hôte.

Voir les commentaires

Rédigé par Bliz

Publié dans #docker