diff --git a/build.sbt b/build.sbt index 12c3250..a7c8df7 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,9 @@ val sharedSettings = Seq( "org.typelevel" %%% "cats-core" % "1.5.0", "org.typelevel" %%% "cats-effect" % "1.1.0", "org.scala-graph" %%% "graph-core" % "1.12.5", - ) + ), + resolvers += Resolver.sonatypeRepo("releases"), + addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.8" cross CrossVersion.binary) ) lazy val cataquack = diff --git a/cataquack/src/main/scala/tf/bug/cataquack/Raw.scala b/cataquack/src/main/scala/tf/bug/cataquack/Raw.scala new file mode 100644 index 0000000..7315c6f --- /dev/null +++ b/cataquack/src/main/scala/tf/bug/cataquack/Raw.scala @@ -0,0 +1,29 @@ +package tf.bug.cataquack + +import cats.Functor +import cats.effect.IO + +/** + * A case class meant to be used under IORaw (see package.scala) like so: + * {{{ + * type StringIORaw[T] = IORaw[T, String] + * }}} + * @param s A concrete value that has been retrieved + * @tparam T The target type + * @tparam S The raw type + */ +case class Raw[S, T](s: S) { + + def read(implicit readEv: Read[S, T]): T = readEv.apply(s) + + def map[U](f: T => U): Raw[S, U] = Raw[S, U](s) + +} + +trait RawImplicits { + + implicit def rawFunctor[S]: Functor[Raw[S, ?]] = new Functor[Raw[S, ?]] { + override def map[A, B](fa: Raw[S, A])(f: A => B): Raw[S, B] = fa.map(f) + } + +} diff --git a/cataquack/src/main/scala/tf/bug/cataquack/Storage.scala b/cataquack/src/main/scala/tf/bug/cataquack/Storage.scala index 02f65fd..99ed171 100644 --- a/cataquack/src/main/scala/tf/bug/cataquack/Storage.scala +++ b/cataquack/src/main/scala/tf/bug/cataquack/Storage.scala @@ -1,9 +1,20 @@ package tf.bug.cataquack -import cats.Functor +import cats._ +import cats.data._ +import cats.implicits._ -trait Storage[F[_], T] { +trait Storage[F[_], C[_]] { - def get(key: String): F[T] + def query[T](key: String): F[T] + + def map[A, B](fa: F[A])(f: A => B)(implicit functor: Functor[F]): F[B] = fa.map(f) + def flatMap[A, B](fa: F[A])(f: A => F[B])(implicit flatMap: FlatMap[F]): F[B] = fa.flatMap(f) + + def filter[T](ft: F[T])(f: T => Boolean)(implicit filterFunctor: FunctorFilter[F]): F[T] = ft.filter(f) + + /** Type like List, Id, etc */ + type Output[T] + def execute[T: C](f: F[T]): this.Output[T] } diff --git a/cataquack/src/main/scala/tf/bug/cataquack/implicits.scala b/cataquack/src/main/scala/tf/bug/cataquack/implicits.scala index c6b8353..05ecaad 100644 --- a/cataquack/src/main/scala/tf/bug/cataquack/implicits.scala +++ b/cataquack/src/main/scala/tf/bug/cataquack/implicits.scala @@ -1,3 +1,3 @@ package tf.bug.cataquack -object implicits extends ReadImplicits +object implicits extends ReadImplicits with RawImplicits diff --git a/cataquack/src/main/scala/tf/bug/cataquack/package.scala b/cataquack/src/main/scala/tf/bug/cataquack/package.scala new file mode 100644 index 0000000..fa935e8 --- /dev/null +++ b/cataquack/src/main/scala/tf/bug/cataquack/package.scala @@ -0,0 +1,9 @@ +package tf.bug + +import cats.effect.IO + +package object cataquack { + + type IORaw[S, T] = IO[Raw[S, T]] + +} diff --git a/cataquack/src/main/scala/tf/bug/cats/Cat.scala b/cataquack/src/main/scala/tf/bug/cats/Cat.scala deleted file mode 100644 index c2fda76..0000000 --- a/cataquack/src/main/scala/tf/bug/cats/Cat.scala +++ /dev/null @@ -1,39 +0,0 @@ -package tf.bug.cats - -import cats._ -import cats.data._ -import cats.implicits._ -import scalax.collection.Graph -import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ -import tf.bug.cataquack.{Read, Storage} - -case class IDCat(id: Int, name: String, friends: Set[Int]) { - def asNode: CatNode = CatNode(id, name) -} -case class CatNode(id: Int, name: String) - -object Cat { - - def getCats[F[_]: Functor, T](s: Storage[F, T])(implicit read: Read[T, Vector[IDCat]]): F[Vector[IDCat]] = { - s.get("cats").map(read.apply) - } - - def getCatGraphFromVec[F[_]: Functor, T](s: Storage[F, T])(implicit read: Read[T, Vector[IDCat]]): F[Graph[CatNode, UnDiEdge]] = { - getCats(s).map(cats => { - val emptyGraph = Graph.empty[CatNode, UnDiEdge] - cats.foldLeft(emptyGraph)((g, nc) => g ++ nc.friends.map(friendID => { - val fc = cats.find(_.id == friendID) - fc.map(rdc => { - val ncd = nc.asNode - val nrd = rdc.asNode - ncd ~ nrd - }) - }).filter(_.isDefined).map(_.get)) - }) - } - - def getCatGraph[F[_]: Functor, T](s: Storage[F, T])(implicit read: Read[T, Graph[CatNode, UnDiEdge]]): F[Graph[CatNode, UnDiEdge]] = { - s.get("cats").map(read.apply) - } - -} diff --git a/cataquack/src/main/scala/tf/bug/example/Example.scala b/cataquack/src/main/scala/tf/bug/example/Example.scala index 4afd8a7..9486680 100644 --- a/cataquack/src/main/scala/tf/bug/example/Example.scala +++ b/cataquack/src/main/scala/tf/bug/example/Example.scala @@ -6,41 +6,56 @@ import cats._ import cats.data._ import cats.implicits._ import cats.effect.IO -import tf.bug.cataquack.{Read, Storage} +import tf.bug.cataquack.{IORaw, Raw, Read, Storage} import tf.bug.cataquack.implicits._ import scala.io.Source object Example { + type StringIORaw[T] = IORaw[String, T] + type ReadString[T] = Read[String, T] + def main(args: Array[String]): Unit = { val f: File = new File("counter.txt") - val s: Storage[IO, String] = (key: String) => IO { - Source.fromFile(f).getLines().find(_.startsWith(key ++ " ")) match { - case Some(line) => line.drop(key.length + 1) - case None => throw new IllegalArgumentException(s"No such key: $key") + val s: Storage[StringIORaw, ReadString] = new Storage[StringIORaw, ReadString] { + override def query[T](key: String): StringIORaw[T] = IO { + val s = Source.fromFile(f).getLines() + s.find(_.startsWith(key ++ " ")) match { + case Some(l) => Raw[String, T](l.drop(key.length + 1)) + case None => throw new IllegalArgumentException(s"No such key: $key") + } } + + /** Type like IO[List], Id, etc */ + override type Output[T] = IO[T] + + override def execute[T: ReadString](f: StringIORaw[T]): Output[T] = f.map(_.read) } val count = readCount(s) val desc = readDescription(s) val pr = for { c <- count d <- desc - } yield println(s"$d: $c") + } yield println(s"${d.read}: ${c.read}") pr.unsafeRunSync() } case class Counter(n: Long) case class Description(n: String) - def readCount[F[_]: Functor, T](s: Storage[F, T])(implicit read: Read[T, Long]): F[Long] = { - s.get("count").map(read.apply) + def readCount[F[_]: Functor, C[_]](s: Storage[F, C]): F[Long] = { + s.query[Long]("count") } - def readDescription[F[_]: Functor, T](s: Storage[F, T])(implicit read: Read[T, String]): F[String] = { - s.get("desc").map(read.apply) + def readDescription[F[_]: Functor, C[_]](s: Storage[F, C]): F[String] = { + s.query[String]("desc") } implicit def readStringLong: Read[String, Long] = (i: String) => i.toLong + implicit def stringIORawFunctor: Functor[StringIORaw] = new Functor[StringIORaw] { + override def map[A, B](fa: StringIORaw[A])(f: A => B): StringIORaw[B] = fa.map(a => rawFunctor[String].map(a)(f)) + } + }