From 88f1f141ce85d63fef6267486bdeb4d75bb9395a Mon Sep 17 00:00:00 2001 From: Oto Brglez Date: Fri, 7 Aug 2020 16:51:04 +0200 Subject: [PATCH 1/2] Preparing new DSL and parser. --- build.sbt | 4 +- worksheets/new_parser.sc | 102 +++++++++++++++++++++++++++++++++++++++ worksheets/sanbox_two.sc | 24 +++++++++ worksheets/sandbox.sc | 44 +++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 worksheets/new_parser.sc create mode 100644 worksheets/sanbox_two.sc create mode 100644 worksheets/sandbox.sc diff --git a/build.sbt b/build.sbt index a9daa45..24ad7c9 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ lazy val mydis = (project in file(".")) .settings(sharedSettings: _*) .settings( name := "mydis", - version := "0.1", + version := "0.2", libraryDependencies ++= Seq( D.fp, D.atto, @@ -35,7 +35,7 @@ lazy val mydis = (project in file(".")) dockerCommands := dockerCommands.value.flatMap { case add @ Cmd("RUN", args @ _*) if args.contains("id") => List( - Cmd("RUN", "apk add --no-cache bash jq curl"), + Cmd("RUN", "apk add --no-cache bash"), Cmd("ENV", "SBT_VERSION", sbtVersion.value), Cmd("ENV", "SCALA_VERSION", scalaVersion.value), Cmd("ENV", "MYDIS_VERSION", version.value), diff --git a/worksheets/new_parser.sc b/worksheets/new_parser.sc new file mode 100644 index 0000000..ac53f9d --- /dev/null +++ b/worksheets/new_parser.sc @@ -0,0 +1,102 @@ +// https://medium.com/rahasak/scala-case-class-to-map-32c8ec6de28a + +import scala.language.implicitConversions + +sealed trait Command extends Product with Serializable + +case class Set(key: String, value: String) extends Command + +case class Get(key: String) extends Command + +case object Time extends Command + +case object Ping extends Command + +case object Pong extends Command + +object DSL { + type ProtocolSymbol = String + type Prefix = ProtocolSymbol + type Postfix = ProtocolSymbol + val `$`: Prefix = "$" + val `*`: Prefix = "*" + val `:`: Prefix = ":" + val `+`: Prefix = "+" + val `-`: Prefix = "-" + val `S`: Prefix = "" + val EOL: Postfix = "\r\n" + + type ProtocolContent = String + type MessageDsl = List[(ProtocolSymbol, ProtocolContent)] +} + +trait Protocol { + + import DSL._ + + def messageDsl: MessageDsl + + def message: String = messageDsl.foldLeft(S)((agg, c) => + agg + c.productIterator.mkString(S) + EOL) +} + +object Protocol { + + object Implicits { + + import DSL._ + + implicit private[this] val intToString: Int => ProtocolContent = _.toString + + /* + implicit def setMessage(set: Set): Protocol = new Protocol { + def messageDsl: MessageDsl = List( + (*, 1), + (S, "set") + ) + } + */ + implicit def getMessage(get: Get): Protocol = new Protocol { + def messageDsl: MessageDsl = List( + (*, 1), + (S, "get") + ) + } + + implicit def genericMessage[A <: Command](command: A): Protocol = new Protocol { + val commandName: String = command.getClass.getSimpleName + + val parametersValues: List[(String, Any)] = + command.getClass.getDeclaredFields.map(_.getName) + .zip(command.productIterator.toList) + .toList + + val parameters: MessageDsl = + parametersValues.flatMap { + case (_, value: String) => List(($, value.length), (S, value)) + case _ => List() //TODO: NUMBERS! + } + + def messageDsl: MessageDsl = + List[(ProtocolSymbol, ProtocolContent)]( + (*, parametersValues.size + 1), + ($, commandName.length), + (S, commandName) + ) ++ parameters + } + + implicit def dslToString(dsl: MessageDsl): String = "DSL TO STRING" + } + +} + +import Protocol.Implicits._ + +println("---- set:") +println(Set("name", "Oto").message) +println(Get("name").message) +println("---- time:") +println(Time.message) +// println(Ping.message) +//println(Pong.message) +// Set("#", "x").productIterator \ No newline at end of file diff --git a/worksheets/sanbox_two.sc b/worksheets/sanbox_two.sc new file mode 100644 index 0000000..8519f24 --- /dev/null +++ b/worksheets/sanbox_two.sc @@ -0,0 +1,24 @@ +import cats._ +import cats.implicits._ + +sealed trait Command + +type Key = String +type Value = String + +case class Set(key: Key, value: Value) + +case class Get(key: Key) + +def parseGet(input: String): Option[Get] = Get(s"go, ${input}").some + +def parseSet(input: String): Option[Set] = if (input.contains("oto")) None else Set("x", input).some + +val p: String => Option[String] = (parseGet _, parseSet _).mapN { + case (Some(get), None) => + s"yey ${get}".some + case _ => None +} + +p("oto") +p("martina") \ No newline at end of file diff --git a/worksheets/sandbox.sc b/worksheets/sandbox.sc new file mode 100644 index 0000000..36d198e --- /dev/null +++ b/worksheets/sandbox.sc @@ -0,0 +1,44 @@ +// https://medium.com/rahasak/scala-case-class-to-map-32c8ec6de28a +import scala.reflect.runtime.universe._ + +sealed trait Command + +case class Get(key: String) extends Command + +object Get + +case class Set(key: String, value: String) extends Command + +object Set + +sealed trait Reply + +object NoReply extends Reply + +type String2Command = String => Command + +object ParserOne { + val commandsMap: Map[Type, String2Command] = Map( + typeOf[Get] -> { s: String => + Get(s) + }, + typeOf[Get] -> { s: String => + Set(s"key ${s}", s"value ${s}") + } + ) + + def parse[T : TypeTag](input: String) = { + print(commandsMap) + println(typeOf[T]) + commandsMap.get(typeOf[T]).map(_.apply(input)) + } +} + +ParserOne.parse[Get]("oto") +ParserOne.parse[Set]("oto") + +ParserOne.commandsMap.keys + +// commandsMap.get(Get.getClass).map(_.apply("Oto")) +// commandsMap.get(Set.getClass).map(_.apply("Oto")) + From ec1f53019d4815abf2b252e6317b357d28542094 Mon Sep 17 00:00:00 2001 From: Oto Brglez Date: Tue, 6 Jul 2021 10:02:47 +0200 Subject: [PATCH 2/2] Some goofing... --- README.md | 5 +- build.sbt | 3 +- project/Dependencies.scala | 6 + project/Settings.scala | 2 + .../experimental/ex1/ExampleApp.scala | 74 ++++++++++ .../scala/com/pinkstack/mydis2/Protocol.scala | 136 ++++++++++++++++++ .../scala/com/pinkstack/mydis3/Protocol.scala | 107 ++++++++++++++ .../com/pinkstack/mydis4/SandboxApp.scala | 113 +++++++++++++++ .../pinkstack/mydis4/SecondShapelessApp.scala | 17 +++ .../com/pinkstack/mydis4/ShapelessApp.scala | 114 +++++++++++++++ .../mydis4/ShapelessExerciseApp.scala | 40 ++++++ .../com/pinkstack/mydis2/ProtocolSpec.scala | 41 ++++++ .../com/pinkstack/mydis3/ProtocolSpec.scala | 50 +++++++ worksheets/better_f.sc | 34 +++++ worksheets/drop_some_errors.sc | 33 +++++ worksheets/new_parser.sc | 47 ++++-- worksheets/proto_read.sc | 29 ++++ worksheets/shapeless.sc | 88 ++++++++++++ 18 files changed, 929 insertions(+), 10 deletions(-) create mode 100644 src/main/scala/com/pinkstack/experimental/ex1/ExampleApp.scala create mode 100644 src/main/scala/com/pinkstack/mydis2/Protocol.scala create mode 100644 src/main/scala/com/pinkstack/mydis3/Protocol.scala create mode 100644 src/main/scala/com/pinkstack/mydis4/SandboxApp.scala create mode 100644 src/main/scala/com/pinkstack/mydis4/SecondShapelessApp.scala create mode 100644 src/main/scala/com/pinkstack/mydis4/ShapelessApp.scala create mode 100644 src/main/scala/com/pinkstack/mydis4/ShapelessExerciseApp.scala create mode 100644 src/test/scala/com/pinkstack/mydis2/ProtocolSpec.scala create mode 100644 src/test/scala/com/pinkstack/mydis3/ProtocolSpec.scala create mode 100644 worksheets/better_f.sc create mode 100644 worksheets/drop_some_errors.sc create mode 100644 worksheets/proto_read.sc create mode 100644 worksheets/shapeless.sc diff --git a/README.md b/README.md index eaee281..90b50cc 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ echo -e '*1\r\n$4\r\nPING\r\n' | nc localhost 6667 - [(source) sixsigmapymes/akkaLab](https://github.com/sixsigmapymes/akkaLab/blob/master/akka-docs/src/test/scala/docs/stream/operators/flow/FromSinkAndSource.scala) - [A simple way to write parsers: using the State monad. By StΓ©phane Derosiaux](https://www.sderosiaux.com/articles/2018/06/15/a-simple-way-to-write-parsers-using-the-state-monad/) +- [Demystifying Type Class derivation with Shapeless](https://www.slideshare.net/ssuserf35af4/demystifying-type-class-derivation-with-shapeless) +- [Type classes and generic derivation](https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/) + ## Benchmarking Please understand that mydis is highly experimental project and is not meant to be @@ -117,4 +120,4 @@ redis-benchmark -p 8080 -t set,get,del,ping -n 10000 -c 5 --csv [Scala]: https://www.scala-lang.org/ [netcat]: https://en.wikipedia.org/wiki/Netcat [resp]: https://redis.io/topics/protocol -[akka-streams]: https://doc.akka.io/docs/akka/current/stream/index.html \ No newline at end of file +[akka-streams]: https://doc.akka.io/docs/akka/current/stream/index.html diff --git a/build.sbt b/build.sbt index 24ad7c9..9e2757f 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,7 @@ lazy val mydis = (project in file(".")) version := "0.2", libraryDependencies ++= Seq( D.fp, + D.shapeless, D.atto, D.akka, D.configuration, @@ -33,7 +34,7 @@ lazy val mydis = (project in file(".")) dockerAliases ++= Seq(dockerAlias.value.withTag(Option("latest"))), dockerExposedPorts ++= Seq(6667), dockerCommands := dockerCommands.value.flatMap { - case add @ Cmd("RUN", args @ _*) if args.contains("id") => + case add@Cmd("RUN", args@_*) if args.contains("id") => List( Cmd("RUN", "apk add --no-cache bash"), Cmd("ENV", "SBT_VERSION", sbtVersion.value), diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6ec378c..22240dc 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,6 +14,7 @@ object Dependencies { val AkkaSlf4j: Version = "2.6.8" val Atto: Version = "0.7.0" val ScalaTest: Version = "3.2.0" + val Shapeless: Version = "2.3.3" } object D { @@ -33,6 +34,10 @@ object Dependencies { "org.typelevel" %% "cats-effect" % V.CatsEffect ) + lazy val shapeless: Seq[ModuleID] = Seq( + "com.chuusai" %% "shapeless" % V.Shapeless, + ) + lazy val cli: Seq[ModuleID] = Seq( "com.monovore" %% "decline" % V.Decline ) @@ -53,4 +58,5 @@ object Dependencies { "org.scalatest" %% "scalatest-shouldmatchers" % V.ScalaTest % "test" ) } + } \ No newline at end of file diff --git a/project/Settings.scala b/project/Settings.scala index 2eaf77e..0ce8347 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -21,6 +21,8 @@ object Settings { ), resolvers ++= Seq( Resolver.bintrayRepo("ovotech", "maven"), + Resolver.sonatypeRepo("releases"), + Resolver.sonatypeRepo("snapshots"), "Confluent Maven Repository" at "https://packages.confluent.io/maven/", "jitpack" at "https://jitpack.io" ) diff --git a/src/main/scala/com/pinkstack/experimental/ex1/ExampleApp.scala b/src/main/scala/com/pinkstack/experimental/ex1/ExampleApp.scala new file mode 100644 index 0000000..f636947 --- /dev/null +++ b/src/main/scala/com/pinkstack/experimental/ex1/ExampleApp.scala @@ -0,0 +1,74 @@ +package com.pinkstack.experimental.ex1 + +import com.pinkstack.experimental.ex1.ExampleApp.computeSize +import shapeless._ + + +object ExampleApp extends App { + val log: Any => Unit = Console.println + val title: String => Unit = s => log("\n" + "πŸ”₯ " * 10 + s + " πŸ”₯" * 10) + + log("\n" + "☒️ " * 10 + getClass.getSimpleName + " ☒️" * 10 + "\n") + + type Email = String + + case class Person(firstName: String, lastName: String, email: Email) + + val personGen = Generic[Person] + + val me = Person("Oto", "Brglez", "otobrglez@gmail.com") + val she = Person("Martina", "Brglez", "martinahe@gmail.com") + + val meRepr: String :: String :: Email :: HNil = personGen.to(me) + log(meRepr) + + val rudi: Person = personGen.from("Rudi" :: "Brglez" :: "none" :: HNil) + + val tupleGen = Generic[(String, String)] + log(tupleGen.to(("status", "ok"))) + log(tupleGen.from("status" :: "fail" :: HNil)) + + title("Generic coproducts") + + case class Red() + + case class Green() + + case class Blue() + + type Colors = Red :+: Green :+: Blue :+: CNil + + title("Switching encodings using Generics") + + sealed trait Shape + + final case class Rectangle(width: Double, height: Double) extends Shape + + final case class Circle(radius: Double) extends Shape + + val genShape = Generic[Shape] + log(genShape.to(Circle(10.0))) + log(genShape.to(Rectangle(10.0, 3.0))) + + type Shapes = Rectangle :+: Circle :+: CNil + + val rectangle = Coproduct[Shapes](Rectangle(10, 10)) + val circle = Coproduct[Shapes](Circle(33.0)) + + object computeSize extends Poly1 { + type Out = Double + + implicit def caseRectangle: Case[Rectangle] { + type Result = Out + } = at[Rectangle] { r => r.width * r.height } + + implicit def caseCircle: Case.Aux[Circle, Out] = at[Circle](c => 2 * Math.PI * c.radius) + } + + log { + rectangle.map(computeSize) + } + + log(circle.map(computeSize)) + +} diff --git a/src/main/scala/com/pinkstack/mydis2/Protocol.scala b/src/main/scala/com/pinkstack/mydis2/Protocol.scala new file mode 100644 index 0000000..ffa61cf --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis2/Protocol.scala @@ -0,0 +1,136 @@ +package com.pinkstack.mydis2 + +/* + - https://medium.com/rahasak/scala-case-class-to-map-32c8ec6de28a + */ + +import scala.language.implicitConversions + +object DSL { + type Key = String + type Value = String + + type ProtocolSymbol = String + type Prefix = ProtocolSymbol + type Postfix = ProtocolSymbol + val `$`: Prefix = "$" + val `*`: Prefix = "*" + val `:`: Prefix = ":" + val `+`: Prefix = "+" + val `-`: Prefix = "-" + val `S`: Prefix = "" + val EOL: Postfix = "\r\n" + + type ProtocolContent = String + type MessageDsl = List[(ProtocolSymbol, ProtocolContent)] +} + +import DSL._ + + +sealed trait Command extends Product with Serializable + +case class Set(key: Key, value: Value) extends Command + +case class Get(key: Key) extends Command + +sealed trait MultiCommand[T] { + def fields: List[T] +} + +case class Mget(fields: List[Key]) extends Command with MultiCommand[Key] + +object Mget { + def apply(fields: Key*): Mget = new Mget(fields.toList) +} + +case class Mset(fields: List[(Key, Value)]) extends Command with MultiCommand[(Key, Value)] + +object Mset { + def apply(fields: String*): Mset = fromList(fields.toList) + + private[this] def fromList(fields: List[String]): Mset = { + assert(fields.size % 2 == 0, "Mset needs odd number of fields.") + new Mset(fields.sliding(2).map(x => (x.head, x.tail.head)).toList) + } + + // def apply(fields: (Key, Value)*): Mset = new Mset(fields.toList) + // def apply(fields: List[String]): Mset = fromList(fields) +} + +case object Time extends Command + +case object Ping extends Command + +case object Pong extends Command + + +trait Protocol { + + import DSL._ + + def messageDsl: MessageDsl + + def message: String = + messageDsl.foldLeft(S)((agg, c) => agg + c.productIterator.mkString(S) + EOL) +} + +object Protocol { + + object Implicits { + + import DSL._ + + + implicit private[this] val intToString: Int => ProtocolContent = _.toString + + // implicit private[this] def dslToString(dsl: MessageDsl): String = "DSL TO STRING" + + + /* + implicit def setMessage(set: Set): Protocol = new Protocol { + def messageDsl: MessageDsl = List( + (*, 1), + (S, "set") + ) + } + + implicit def getMessage(get: Get): Protocol = new Protocol { + def messageDsl: MessageDsl = List( + (*, 1), + (S, "get") + ) + } + */ + + implicit def genericMessage[A <: Command](command: A): Protocol = new Protocol { + val commandName: String = + Option(command.getClass.getSimpleName).map { name => + Option.when(name.endsWith("$"))(name.substring(0, name.length - 1)) + .getOrElse(name) + }.get + + val parametersValues: List[(String, Any)] = + command.getClass + .getDeclaredFields + .map(_.getName) + .zip(command.productIterator.toList) + .toList + + val parameters: MessageDsl = + parametersValues.flatMap { + case (_, value: String) => List(($, value.length), (S, value)) + case (_, value: Int) => List(($, value.toString.length), (S, value)) + case _ => List() //TODO: NUMBERS! + } + + def messageDsl: MessageDsl = + List[(ProtocolSymbol, ProtocolContent)]( + (*, parametersValues.size + 1), + ($, commandName.length), + (S, commandName) + ) ++ parameters + } + } + +} \ No newline at end of file diff --git a/src/main/scala/com/pinkstack/mydis3/Protocol.scala b/src/main/scala/com/pinkstack/mydis3/Protocol.scala new file mode 100644 index 0000000..bec86de --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis3/Protocol.scala @@ -0,0 +1,107 @@ +package com.pinkstack.mydis3 + +import cats._ +import cats.implicits._ +import cats.syntax.EitherSyntax._ + +import scala.util.{Failure, Try} + +object Model { + + sealed trait Command + + case class Set(key: String, value: String) extends Command + + case class Get(key: String) extends Command + + case class Exists(key: String) extends Command + + case object Ping extends Command + + sealed trait Reply + + case class ArrayReply[T](array: Array[T]) extends Reply + + case class SimpleString(string: String) extends Reply + + case class Error(message: String) extends Reply + + case object OK extends Reply + + case object Pong extends Reply + +} + +object Protocol { + + import Model._ + + private[Protocol] val `*` = "*" + private[Protocol] val `+` = "+" + private[Protocol] val `-` = "-" + private[Protocol] val `:` = ":" + private[Protocol] val `$` = "$" + private[Protocol] val EOL = "\r\n" + + trait EncodingError { + def errorMessage: String + } + + final object GenericEncodingError extends EncodingError { + def errorMessage: String = "Something went wrong with encoding." + } + + trait Encoder[T <: Command] { + def encode(command: T): List[String] + } + + def encode(command: Command): Either[EncodingError, String] = { + command.getClass.getSimpleName.asRight + } + + def encode(reply: Reply): Either[EncodingError, String] = { + reply.getClass.getSimpleName.asRight + } + + /* + implicit val commandSet = new Encoder[Set] { + def encode(command: Set): List[String] = List("hello set") + } + + implicit val replyEncoder = new Encoder[Reply] { + override def encode(command: Reply): List[String] = List("command") + } + + def encode[A <: Command](c: A)(implicit encoder: Encoder[A]): Either[EncodingError, String] = { + encoder.encode(c).mkString(EOL).asRight + } + + def encode[A <: Reply](c: A)(implicit encoder: Encoder[A]): Either[EncodingError, String] = { + encoder.encode(c).mkString(EOL).asRight + } + + */ + +} + +object Parser { + import shapeless._ + + class ConverterException(s: String) extends RuntimeException(s) + + trait Converter[T] { + def from(s: String): Try[T] + def to(t: T): String + } + + object Converter { + def apply[T](implicit st: Lazy[Converter[T]]): Converter[T] = st.value + + def fail(s: String) = Failure(new ConverterException(s)) + } + + + def parse(input: String) = { + + } +} \ No newline at end of file diff --git a/src/main/scala/com/pinkstack/mydis4/SandboxApp.scala b/src/main/scala/com/pinkstack/mydis4/SandboxApp.scala new file mode 100644 index 0000000..2a14130 --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis4/SandboxApp.scala @@ -0,0 +1,113 @@ +package com.pinkstack.mydis4 + +import cats._ +import cats.implicits._ + +object SandboxApp extends App { + println("\n " * 1 + "🎈 " * 10 + getClass.getSimpleName + " 🎈" * 10 + "\n") + + val meh: Map[String, String] = Semigroup.combine(Map("name" -> "Oto"), Map("email" -> "otobrglez@gmail.com")) + println(meh) + + println { + Monoid[Map[String, Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3))) + } + + println { + List(1, 2, 3, 4, 5).foldMap(identity) + } + + println { + List(1, 2, 3, 4, 5).foldMap(i => (i, i.toString)) + } + + println { + Functor[List].map(List("a", "b", "c", "d", "e"))(_.toUpperCase) + } + + val upIt: Option[String] => Option[Int] = Functor[Option].lift(_.length) + + println { + upIt("Oto".some) + } + + // fproduct + val s = List("Oto", "was", "here") + val p = Functor[List].fproduct(s)(_.length).toMap + println(p.get("Oto")) + + val listOpts = Functor[List] compose Functor[Option] + println { + listOpts.map(List(Some(1), None, Some(3)))(_ + 1) + } + + val stringToInt: String => Int = Integer.parseInt + + // Cats - Apply + println { + Apply[Option].map(Some("99"))(stringToInt) + } + + // Apply and compose + val listOps = Apply[List] compose Apply[Option] + val addOne: Int => Int = _ + 1 + + println { + listOps.ap(List(Some(addOne)))(List(Some(1), None, Some(3))) + } + + // Apply / AP + println { + Apply[Option].ap(Some(addOne))(Some(12)) + } + + val addArity2 = (a: Int, b: Int) => a + b + println(Apply[Option].ap2(Some(addArity2))(Some(1), None)) + + println { + Apply[Option].tuple2(Some(1), Some(2)) + } + + println { + Apply[Option].map2(Some(1), Some(2))(addArity2) + } + + println { + (Option(1), Option(2)) + } + + println { + (Option(1), Option(2)).mapN(addArity2) + } + + val op2: (Option[Int], Option[Int]) = (1.some, 2.some) + val op3 = (op2._1, op2._2, Option.empty[Int]) + println { + op3.mapN((a, b, c) => a + b + c) + } + + println { + op3.apWith(Some((a: Int, b: Int, c: Int) => a + b + c)) + } + + println(op2.tupled) + println(op3.tupled) + + println("\n# Applicative\n") + + println(Applicative[Option].pure(1)) + println(Applicative[List].pure(1)) + + println { + (Applicative[List] compose Applicative[Option]).pure(1) + } + + /** + * Applicative is a generalization of Monad, allowing expression of effectful computations in a pure functional way. + * + * Applicative is generally preferred to Monad when the structure of a computation is fixed a priori. + * That makes it possible to perform certain kinds of static analysis on applicative value + */ + println(Monad[Option].pure(1)) + println(Applicative[Option].pure(1)) +} diff --git a/src/main/scala/com/pinkstack/mydis4/SecondShapelessApp.scala b/src/main/scala/com/pinkstack/mydis4/SecondShapelessApp.scala new file mode 100644 index 0000000..3124c75 --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis4/SecondShapelessApp.scala @@ -0,0 +1,17 @@ +package com.pinkstack.mydis4 + +import shapeless._, record._ + +object SecondShapelessApp extends App { + println("\n " * 1 + "🎈 " * 10 + getClass.getSimpleName + " 🎈" * 10 + "\n") + + type Client = Long :: HNil + type Admin = Long :: Boolean :: HNil + type Role = Admin :+: Client :+: CNil + type User = Long :: String :: Boolean :: Role :: HNil + + val admin: Role = Inl(2L :: true :: HNil) + val me: User = 1 :: "Oto" :: true :: admin :: HNil + + println(me) +} diff --git a/src/main/scala/com/pinkstack/mydis4/ShapelessApp.scala b/src/main/scala/com/pinkstack/mydis4/ShapelessApp.scala new file mode 100644 index 0000000..4c222ea --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis4/ShapelessApp.scala @@ -0,0 +1,114 @@ +package com.pinkstack.mydis4 + +import java.net.InetAddress +import java.time.Instant + +import cats._ +import cats.implicits._ +import shapeless._ + +import scala.util.{Failure, Success, Try} + +// Domain +sealed trait Command extends Product with Serializable + +case class Set(name: String, value: String) extends Command + +case class Get(name: String) extends Command + +case class Strlen(name: String) extends Command + +case object Ping extends Command + +// Parsing +trait Parser[T] { + def apply(s: String): Either[String, T] +} + +trait TryParser[T] extends Parser[T] { + def parse(s: String): T + + def apply(s: String): Either[String, T] = + Try(parse(s)).toEither.leftMap(_.getMessage) +} + +object LogParsers { + implicit val instantParser: TryParser[Instant] = (s: String) => Instant.parse(s) + + implicit val inetAddressParser: TryParser[InetAddress] = (s: String) => InetAddress.getByName(s) +} + +trait LineParser[Out] { + def apply(l: List[String]): Either[List[String], Out] +} + +object LineParser { + implicit val hnilParser: LineParser[HNil] = { + case Nil => Right(HNil) + case h +: _ => Left(List(s"Expected EOL got $h")) + } + + implicit def hconsParser[H: Parser, T <: HList : LineParser]: LineParser[H :: T] = { + case Nil => Left(List("Expected list element.")) + case h +: t => + val head = implicitly[Parser[H]].apply(h) + val tail = implicitly[LineParser[T]].apply(t) + (head, tail) match { + case (Left(error), Left(errors)) => Left(error :: errors) + case (Left(error), Right(_)) => Left(error :: Nil) + case (Right(_), Left(errors)) => Left(errors) + case (Right(h), Right(t)) => Right(h :: t) + } + } + + implicit def caseClassParser[Out, R <: HList](implicit gen: Generic[Out] {type Repr = R}, + reprParser: LineParser[R]): LineParser[Out] = + (s: List[String]) => reprParser.apply(s).right.map(gen.from) + + def apply[A](s: List[String])(implicit parser: LineParser[A]): Either[List[String], A] = + parser(s) +} + +object ShapelessApp extends App { + val examples: Map[String, String] = Map( + "set" -> + """*3 + |$3 + |set + |$4 + |name + |$3 + |Oto""".stripMargin, + "get" -> + """*2 + |$3 + |get + |$4 + |name""".stripMargin, + "strlen" -> + """*2 + |$6 + |strlen + |$4 + |name""".stripMargin, + "ping" -> + """*1 + |$4 + |ping""".stripMargin + ) + + examples.foreach { case (exampleName, input) => + println(s"\n--- Running example $exampleName ---\n") + println(input) + + println { + Semigroup[Int => Int].combine(_ + 1, _ * 10).apply(6) + } + println { + Semigroup.combine(Map("foo" -> Map("bar" -> 5)), Map("foo" -> Map("bar" -> 6))).get("foo") + } + + + + } +} diff --git a/src/main/scala/com/pinkstack/mydis4/ShapelessExerciseApp.scala b/src/main/scala/com/pinkstack/mydis4/ShapelessExerciseApp.scala new file mode 100644 index 0000000..5022bca --- /dev/null +++ b/src/main/scala/com/pinkstack/mydis4/ShapelessExerciseApp.scala @@ -0,0 +1,40 @@ +package com.pinkstack.mydis4 + +import java.time.LocalDate +import java.util.{Calendar, Date, Locale} + +import shapeless._, record._ +import shapeless.PolyDefns.~> + +case class Person(name: String, email: String, bornOn: Option[LocalDate] = None) + +object ShapelessExerciseApp extends App { + println("\n " * 1 + "🎈 " * 10 + getClass.getSimpleName + " 🎈" * 10 + "\n") + + val personGen = LabelledGeneric[Person] + println(personGen) + + val me = Person("Oto", "otobrglez@gmail.com") + val wife = Person("Martina", "martinahe@gmail.com", Some(LocalDate.now())) + + val rec = personGen.to(me) + println(rec) + println(rec.keys) + println(rec.values) + println(rec.get(Symbol("email"))) + + println("---") + + println { + personGen.from(rec.updateWith(Symbol("email"))(v => v + "x")) + } + + println("---") + + type ISB = Int :+: String :+: Boolean :+: CNil + val isb = Coproduct[ISB]("xxx") + + println { + isb + } +} diff --git a/src/test/scala/com/pinkstack/mydis2/ProtocolSpec.scala b/src/test/scala/com/pinkstack/mydis2/ProtocolSpec.scala new file mode 100644 index 0000000..b535c2f --- /dev/null +++ b/src/test/scala/com/pinkstack/mydis2/ProtocolSpec.scala @@ -0,0 +1,41 @@ +package com.pinkstack.mydis2 + +import org.scalatest.flatspec._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.{EitherValues, PartialFunctionValues} + +import Protocol._ +import Protocol.Implicits._ + +class ProtocolSpec extends AnyFlatSpec + with EitherValues + with PartialFunctionValues + with Matchers { + + "set" should "work" in { + val s = Set("name", "Oto").message + println(s) + } + + it should "mset" in { + val m2 = Mset("key1", "Hello", "key2", "World") + println("---") + println(m2) + println("---") + } + + it should "get" in { + val m = Get("name").message + println(m) + } + + it should "ping" in { + val m = Ping.message + println(m) + } + + it should "pong" in { + val m = Pong.message + println(m) + } +} diff --git a/src/test/scala/com/pinkstack/mydis3/ProtocolSpec.scala b/src/test/scala/com/pinkstack/mydis3/ProtocolSpec.scala new file mode 100644 index 0000000..7b196de --- /dev/null +++ b/src/test/scala/com/pinkstack/mydis3/ProtocolSpec.scala @@ -0,0 +1,50 @@ +package com.pinkstack.mydis3 + +import com.pinkstack.mydis3._ +import com.pinkstack.mydis3.Model._ +import org.scalatest.flatspec._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.{EitherValues, PartialFunctionValues} + +class ProtocolSpec extends AnyFlatSpec + with EitherValues + with PartialFunctionValues + with Matchers { + + "set" should "work" in { + + println { + Protocol.encode(Set("name", "Oto")) + } + + println { + Protocol.encode(Get("name")) + } + + println { + Protocol.encode(Exists("name")) + } + + println { + Protocol.encode(ArrayReply(Array(1, 2, 3))) + } + + println { + Protocol.encode(Error("Some error here")) + } + + println { + Protocol.encode(OK) + } + + println { + Protocol.encode(Ping) + } + + println { + Protocol.encode(Pong) + } + + assert(1 == 1, "it is") + } +} diff --git a/worksheets/better_f.sc b/worksheets/better_f.sc new file mode 100644 index 0000000..1d14cc0 --- /dev/null +++ b/worksheets/better_f.sc @@ -0,0 +1,34 @@ +val stripLast: String => String = { s => + if (s.endsWith("$")) s.substring(0, s.length - 1) else s +} + +val stripLast2: String => String = { + s => Option.when(s.endsWith("$"))(s.substring(0, s.length - 1)).getOrElse(s) +} + +stripLast("Oto$") +stripLast("Oto") + +stripLast2("Oto$") +stripLast2("Oto") + +val stripLast3: Object => String = { + o => + Option(o.getClass.getSimpleName).map { name => + Option.when(name.endsWith("$"))(name.substring(0, name.length - 1)) + .getOrElse(name) + }.get +} + +class MyClass { + +} + +object MyObject + +stripLast3("Oto$") +stripLast3("Oto") + + +stripLast3(MyObject) +stripLast3(new MyClass) \ No newline at end of file diff --git a/worksheets/drop_some_errors.sc b/worksheets/drop_some_errors.sc new file mode 100644 index 0000000..e01b31a --- /dev/null +++ b/worksheets/drop_some_errors.sc @@ -0,0 +1,33 @@ +import cats._ +import cats.implicits._ +import cats.data._ + +sealed trait MyError { + def message: String +} + +case class IFailedYou(name: String) extends MyError { + def message = s"I failed ${name}" +} + +object HorribleFail extends MyError { + def message = "Horrible fail here" +} + +object Validator { + def validate(input: String): Validated[MyError, String] = { + val validateName: String => Validated[MyError, String] = n => + Either.cond(n.contains("X"), n, IFailedYou(n)).toValidated + + val anotherValidityCheck: String => Validated[MyError, String] = n => + Either.cond(n.contains("god"), n, HorribleFail).toValidated + + for { + validName <- validateName(input) + validateAgain <- anotherValidityCheck(input) + } yield validName + } +} + +Validator.validate("Oto") +Validator.validate("Mr X") diff --git a/worksheets/new_parser.sc b/worksheets/new_parser.sc index ac53f9d..0ba8a45 100644 --- a/worksheets/new_parser.sc +++ b/worksheets/new_parser.sc @@ -8,6 +8,27 @@ case class Set(key: String, value: String) extends Command case class Get(key: String) extends Command +case class Mget(keys: List[String]) extends Command + +object Mget { + def apply(keys: String*): Mget = new Mget(keys.toList) +} + +case class Mset(fields: List[(String, String)]) extends Command + +object Mset { + def apply(fields: (String, String)*): Mset = new Mset(fields.toList) + + def fromList(fields: List[String]) = { + assert(fields.size % 2 == 0, "Mset needs odd number of fields.") + new Mset(fields.sliding(2).map(x => (x.head, x.tail.head)).toList) + } + + def apply(fields: List[String]): Mset = fromList(fields) + + def apply(fields: String*): Mset = fromList(fields.toList) +} + case object Time extends Command case object Ping extends Command @@ -36,8 +57,8 @@ trait Protocol { def messageDsl: MessageDsl - def message: String = messageDsl.foldLeft(S)((agg, c) => - agg + c.productIterator.mkString(S) + EOL) + def message: String = + messageDsl.foldLeft(S)((agg, c) => agg + c.productIterator.mkString(S) + EOL) } object Protocol { @@ -46,8 +67,12 @@ object Protocol { import DSL._ + implicit private[this] val intToString: Int => ProtocolContent = _.toString + implicit private[this] def dslToString(dsl: MessageDsl): String = "DSL TO STRING" + + /* implicit def setMessage(set: Set): Protocol = new Protocol { def messageDsl: MessageDsl = List( @@ -55,13 +80,14 @@ object Protocol { (S, "set") ) } - */ + implicit def getMessage(get: Get): Protocol = new Protocol { def messageDsl: MessageDsl = List( (*, 1), (S, "get") ) } + */ implicit def genericMessage[A <: Command](command: A): Protocol = new Protocol { val commandName: String = command.getClass.getSimpleName @@ -84,19 +110,24 @@ object Protocol { (S, commandName) ) ++ parameters } - - implicit def dslToString(dsl: MessageDsl): String = "DSL TO STRING" } - } import Protocol.Implicits._ println("---- set:") println(Set("name", "Oto").message) + +println("---- get:") println(Get("name").message) -println("---- time:") -println(Time.message) + +println("---- mget:") +println(Mget("key1", "key2")) + +println("---- mset:") +println(Mset(("key1", "Hello"), ("key2", "World"))) +println(Mset("key1", "Hello", "key2", "World")) + // println(Ping.message) //println(Pong.message) // Set("#", "x").productIterator \ No newline at end of file diff --git a/worksheets/proto_read.sc b/worksheets/proto_read.sc new file mode 100644 index 0000000..a84ce6e --- /dev/null +++ b/worksheets/proto_read.sc @@ -0,0 +1,29 @@ +import cats._ +import cats.implicits._ +import shapeless._ + +import scala.util.{Try, Success} + +sealed trait Command + +type Key = String +type Value = String + +case class Set(key: Key, value: Value) extends Command + +case class Get(key: Key) extends Command + +trait CommandParser[T] { + def apply(s: String): Either[String, T] +} + +trait TryParser[T] extends CommandParser[T] { + def parse(s: String): T + + def apply(s: String): Either[String, T] = { + Try(parse(s)).transform( + s => Success(Right(s)), + f => Success(Left(f.getMessage)) + ).get + } +} \ No newline at end of file diff --git a/worksheets/shapeless.sc b/worksheets/shapeless.sc new file mode 100644 index 0000000..461c992 --- /dev/null +++ b/worksheets/shapeless.sc @@ -0,0 +1,88 @@ +import shapeless._ + +sealed trait Command extends Product with Serializable + +type Key = String +type Value = String + +case class Set(key: Key, value: Value) extends Command + +case class Get(key: Key) extends Command + +case class Del(key: Key) extends Command + +case class MGet(key: Key*) extends Command + +case class MSet(values: (Key, Value)*) extends Command + +// case object Ping extends Command + +// case object Pong extends Command + +// Generic[Set].to(Set("name", "Oto")) +// Generic[Get].from("name" :: HNil) + +// type Commands = Set :+: Get :+: Del :+: MGet :+: MSet :+: Ping.type :+: CNil +// type Replies = Pong.type :+: CNil + +// val set = Inl(Set) +// val del = Inl(Inl(Inl(Del))) + +val gen = Generic[Command] +gen.to(Get("name")) + +// With type classes +trait CommandEncoder[A] { + def encode(value: A): List[String] +} + +implicit val setEncoder: CommandEncoder[Set] = new CommandEncoder[Set] { + override def encode(value: Set): List[String] = { + List("oook") + } +} + +implicit val getEncoder: CommandEncoder[Get] = (value: Get) => { + List("set me") +} + +def encodeCommands[A](commands: List[A])(implicit encoder: CommandEncoder[A]): String = { + commands.map(command => encoder.encode(command)).mkString("\n") +} + +println(encodeCommands(List[Set]( + Set("name", "Oto"), + Set("acc", "True"), +))) + +def encodeCommand[A <: Command](command: A)(implicit e: CommandEncoder[A]): String = { + val commandName = Option(command.getClass.getSimpleName).map { name => + Option.when(name.endsWith("$"))(name.substring(0, name.length - 1)) + .getOrElse(name) + }.get + + val parametersValues: List[(String, Any)] = + command.getClass + .getDeclaredFields + .map(_.getName) + .zip(command.productIterator.toList) + .toList + + val parameters: List[(String, Any)] = + parametersValues.flatMap { + case (_, value: String) => List(("$", value.length), ("", value)) + case (_, value: Int) => List(("$", value.toString.length), ("", value)) + case _ => throw new Exception("Unknown type") + } + + (List( + ("$", command.productArity + 1), + ("$", commandName.length), + ("", commandName) + ) ++ parameters).map(_.productIterator.mkString("")).mkString("\r\n") +} + +Set("name", "Oto").productElementNames.toList + +encodeCommand(Set("name", "Oto")) +encodeCommand(Set("cnt", "100")) \ No newline at end of file