Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
[akka-streams]: https://doc.akka.io/docs/akka/current/stream/index.html
7 changes: 4 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ lazy val mydis = (project in file("."))
.settings(sharedSettings: _*)
.settings(
name := "mydis",
version := "0.1",
version := "0.2",
libraryDependencies ++= Seq(
D.fp,
D.shapeless,
D.atto,
D.akka,
D.configuration,
Expand All @@ -33,9 +34,9 @@ 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 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),
Expand Down
6 changes: 6 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
)
Expand All @@ -53,4 +58,5 @@ object Dependencies {
"org.scalatest" %% "scalatest-shouldmatchers" % V.ScalaTest % "test"
)
}

}
2 changes: 2 additions & 0 deletions project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
74 changes: 74 additions & 0 deletions src/main/scala/com/pinkstack/experimental/ex1/ExampleApp.scala
Original file line number Diff line number Diff line change
@@ -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))

}
136 changes: 136 additions & 0 deletions src/main/scala/com/pinkstack/mydis2/Protocol.scala
Original file line number Diff line number Diff line change
@@ -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
}
}

}
107 changes: 107 additions & 0 deletions src/main/scala/com/pinkstack/mydis3/Protocol.scala
Original file line number Diff line number Diff line change
@@ -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) = {

}
}
Loading