From ef697c04c69b1f80d636250d6cfef14126e746d2 Mon Sep 17 00:00:00 2001 From: Quentin Leblanc <quentin.leblanc@etu.hesge.ch> Date: Thu, 19 Mar 2020 15:42:02 +0100 Subject: [PATCH] TP7 --- TP3/src/main/scala/7.result.scala | 83 ++++++++++++++++ TP3/src/test/scala/7.resultTest.scala | 136 ++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 TP3/src/main/scala/7.result.scala create mode 100644 TP3/src/test/scala/7.resultTest.scala diff --git a/TP3/src/main/scala/7.result.scala b/TP3/src/main/scala/7.result.scala new file mode 100644 index 0000000..709a94c --- /dev/null +++ b/TP3/src/main/scala/7.result.scala @@ -0,0 +1,83 @@ +package ch.hepia.tpscala.result + + +sealed trait Result[+E,+A] { + + def map[B]( f: A=>B ): Result[E,B] = this match { + case OK(value) => OK(f(value)) + case Err(value) => Err(value) + } + + //F >: E, signifie "F est un supertype de E" + def flatMap[B, F >:E]( f: A=>Result[F,B] ): Result[F,B] = this match { + case OK(value) => f(value) + case Err(value) => Err(value) + } + + //Retourne le résultat si OK + //transforme l'erreur en une valeur valide sinon + //B >: A signifie "B est un supertype de A + //Une seul implémentation possible ! + def get[B >: A]( f: E => B ): B = this match { + case OK(value) => value + case Err(value) => f(value) + } + + //Une seul implémentation possible ! + def fold[B]( f: E => B, g: A => B ): B = this match { + case OK(value) => g(value) + case Err(value) => f(value) + } + + def toOption: Option[A] = + fold[Option[A]] (_ => None, Some.apply) + +} +case class OK[A]( value: A ) extends Result[Nothing,A] +case class Err[E]( value: E ) extends Result[E,Nothing] +object Result { + //Si il y a au moins une erreur dans la liste, le résultat est une erreur + //Si il n'y a que des succès, liste les succès + def sequence[E,A]( res: List[Result[E,A]] ): Result[E,List[A]] = { + def reqSequence[E, A](req: List[Result[E, A]], acc: Result[E, List[A]]): Result[E, List[A]] = req match { + case Nil => acc.map(_.reverse) + case h :: tail => reqSequence(tail, acc.flatMap( r => h.map( a => a :: r))) + } + reqSequence(res, OK(Nil)) + } +} + + +case class Config( hostname: String, port: Int ) +object Config { + private def line2keyValue( line: String ): Result[String,(String,String)] = { + val elements = line.split("=") + if( elements.size != 2 ) Err("Syntax error: " + line ) + else OK( (elements(0), elements(1) ) ) + } + + private def lines2map( lines: List[String] ): Result[String,Map[String,String]] = + Result.sequence( lines.map(line2keyValue) ).map( _.toMap ) + + private def mget[K,V](mkv: Map[K,V], key: K): Result[String, V] = mkv.get(key) match { + case Some(v) => OK(v) + case None => Err("Missing Key: "+key) + } + + private def safeInt(str: String): Result[String, Int] = + try { + OK(str.toInt) + }catch { + case _ => Err("Cannot convert String :" + str + " : to Int") + } + + //Utilisez lines2map pour parvenir au résultat + //Attention au clés manquantes + def parse( lines: List[String] ): Result[String,Config] = + for { + keyVal <- lines2map(lines) + h <- mget(keyVal, "hostname") + pStr <- mget(keyVal, "port") + p <- safeInt(pStr) + } yield Config(h, p) +} diff --git a/TP3/src/test/scala/7.resultTest.scala b/TP3/src/test/scala/7.resultTest.scala new file mode 100644 index 0000000..196fe74 --- /dev/null +++ b/TP3/src/test/scala/7.resultTest.scala @@ -0,0 +1,136 @@ +package ch.hepia.tpscala + +import org.scalatest.funsuite.AnyFunSuite + +import result._ + +/**** + * + * Dans SBT vous pouvez n'exécuter que ces tests: + * + * > testOnly ch.hepia.tpscala.Result7Suite + * + */ + +class Result7Suite extends AnyFunSuite { + + + test( "map" ) { + val hello: Result[String,String] = OK("hello") + val notFound: Result[Int,String] = Err(404) + + assert( hello.map(_.size) == OK(5) ) + assert( notFound.map(_.toString) == notFound ) + + assert( hello.map(identity) == hello ) + assert( notFound.map(identity) == notFound ) + + assert( hello.map(_.size).map( _ > 2) == + hello.map( _.size > 2 ) ) + + assert( notFound.map(_ * 2 ).map( _.toString) == + notFound.map( i => (i*2).toString ) ) + } + + test( "flatMap" ) { + type Res[A] = Result[String,A] + val goodHost: Res[String] = OK("hello.com") + val badHost: Res[String] = Err("NA") + val goodPort: Res[Int] = OK(80) + val badPort: Res[Int] = Err("undefined") + + assert( + goodHost.flatMap{ host => + goodPort.map{ port => + Config(host,port) + } + } == OK( Config("hello.com",80) ) + ) + + assert( + goodHost.flatMap{ host => + badPort.map{ port => + Config(host,port) + } + } == badPort + ) + + assert( + badHost.flatMap{ host => + goodPort.map{ port => + Config(host,port) + } + } == badHost + ) + + assert( + badHost.flatMap{ host => + badPort.map{ port => + Config(host,port) + } + } == badHost + ) + } + + test("get") { + val found: Result[String, Long] = OK(665) + val notFound: Result[String,Long] = Err("not found") + + assert( found.get( _ => -1 ) == 665 ) + assert( notFound.get( _ => -1 ) == -1 ) + + def str2status( str: String ) = str match { + case "not found" => 404 + case "conflict" => 409 + case "unauthorized" => 403 + case _ => 400 + } + + assert( found.map( _ => 200).get(str2status) == 200 ) + assert( notFound.map( _ => 200).get(str2status) == 404 ) + } + + + test( "parse config" ) { + val lines1 = List( + "port=8888", + "hostname=www.example.com", + ) + + val lines2 = List( + "port: 8888", + "hostname: www.example.com", + ) + + val lines3 = List( + "port=8888", + ) + + val lines4 = List( + "port=HTTP", + "hostname=www.example.com", + ) + + + val default = Config("localhost", 8080 ) + + assert( Config.parse( lines1 ).get( _ => default ) + == Config("www.example.com",8888) ) + + assert( Config.parse( lines2 ).get( _ => default ) + == default ) + + assert( Config.parse( lines3 ).get( _ => default ) + == default ) + + assert( Config.parse( lines4 ).get( _ => default ) + == default ) + + + } + + + + + +} -- GitLab