All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hael.jjm-core_2.13.0.2.3.source-code.DotMap.scala Maven / Gradle / Ivy

The newest version!
package jjm

import cats.Applicative
import cats.Eq
import cats.~>
import cats.Monoid
import cats.MonoidK
import cats.Semigroup
import cats.implicits._

import io.circe.Encoder
import io.circe.Decoder
import io.circe.KeyEncoder
import io.circe.KeyDecoder
import io.circe.HCursor
import io.circe.Json

import monocle.Traversal
import monocle.function.At
import monocle.function.Index

// TODO maybe use a different name for the F[_] version and DotMap for F = Id?
class DotMap[F[_], A <: Dot] private (private val map: Map[A, F[_]]) {

  def get(key: A): Option[F[key.Out]] =
    map.get(key).asInstanceOf[Option[F[key.Out]]]

  def put(key: A)(value: F[key.Out]): DotMap[F, A] =
    new DotMap[F, A](map + (key -> value))

  def +(pair: DotPair[F, A]): DotMap[F, A] =
    new DotMap[F, A](map + (pair.fst -> pair.snd))

  def remove(key: A): DotMap[F, A] =
    new DotMap[F, A](map - key)

  def keySet: Set[A] =
    map.keySet

  def iterator: Iterator[DotPair[F, A]] = map.iterator.map { case (k, v) =>
    DotPair[F](k)(v.asInstanceOf[F[k.Out]])
  }

  def size: Int =
    map.size

  override def toString =
    map.toString

  override def equals(that: Any): Boolean = that match {
    case that: DotMap[_, _] => this.map == that.map
    case _ => false
  }

  override def hashCode: Int = map.hashCode

  def toDotKleisli: DotKleisli[λ[B => Option[F[B]]], A] =
    new DotKleisli[λ[B => Option[F[B]]], A] {
      def apply(a: A) = get(a)
    }

  def toFunctionK: (DotF[A]#Aux ~> λ[B => Option[F[B]]]) =
    new (DotF[A]#Aux ~> λ[B => Option[F[B]]]) {
      override def apply[B](fa: DotF[A]#Aux[B]): Option[F[B]] =
        get(fa)
    }

  def withDefault(f: DotKleisli[F, A]): TotalDotMap[F, A] =
    new TotalDotMap(map.withDefault(f.apply(_)))
}

object DotMap extends DotMapInstances {
  def empty[F[_], A <: Dot] = new DotMap[F, A](Map.empty[A, F[A#Out]])

  def apply[F[_], A <: Dot](pairs: DotPair[F, A]*): DotMap[F, A] = {
    pairs.foldLeft(empty[F, A])(_ + _)
  }
}

abstract private[jjm] class DotMapInstances extends DotMapInstances0 {

  // TODO probably should only be done with underlying Map based on proper Hash and Eq classes
  // implicit def dotMapEq[A <: Dot](
  //   implicit eq: Eq[A],
  //   dotEq: DotKleisli[Eq, A]
  // ) = new Eq[DotMap[A]] {
  //   override def eqv(x: DotMap[F, G], y: DotMap[F, G]): Boolean =
  //     x.map == y.map
  // }

  // TODO: semigroupK, commutative monoid/semigroup, and anything else from Cats

  implicit def dotMapMonoid[F[_], A <: Dot](
    implicit dotMonoid: DotKleisli[λ[B => Monoid[F[B]]], A]
  ): Monoid[DotMap[F, A]] =
    new Monoid[DotMap[F, A]] {
      override def empty =
        DotMap.empty[F, A]
      override def combine(x: DotMap[F, A], y: DotMap[F, A]) = {
        (x.keySet ++ y.keySet).foldLeft(empty) {
          case (dMap, key) => dMap.put(key) {
            implicit val m = dotMonoid(key)
            (x.get(key).combineAll |+| y.get(key).combineAll)
          }
        }
      }
    }

  implicit def dotMapEncoder[F[_], A <: Dot](
    implicit keyEncoder: KeyEncoder[A],
    dotEncoder: DotKleisli[λ[B => Encoder[F[B]]], A]
  ): Encoder[DotMap[F, A]] = new Encoder[DotMap[F, A]] {
    final def apply(m: DotMap[F, A]) = Json.obj(
      // TODO would be best if I can iterate over DotPairs but I can't seem to get those to work
      m.keySet.toList.map(key =>
        keyEncoder(key) -> dotEncoder(key)(m.get(key).get)
      ): _*
    )
  }

  implicit def dotMapDecoder[F[_], A <: Dot](
    implicit keyDecoder: KeyDecoder[A],
    dotDecoder: DotKleisli[λ[B => Decoder[F[B]]], A]
  ): Decoder[DotMap[F, A]] = new Decoder[DotMap[F, A]] {
    final def apply(c: HCursor): Decoder.Result[DotMap[F, A]] = {
      // TODO aah replace the get
      c.keys.get.toList.foldM[Decoder.Result, DotMap[F, A]](DotMap.empty[F, A]) { (m, keyStr) =>
        val key = keyDecoder(keyStr).get // TODO aah replace the get
        val value = dotDecoder(key).tryDecode(c.downField(keyStr))
        // val foo = Foo(key)
        // type Out = foo.Out
        value.map(v => m.put(key)(v))
      }
    }
  }

  // TODO Traversal over DotPair elements, if we can get DotPair to work...

  implicit def dotMapAt[F[_], A <: Dot, Out0]: At[DotMap[F, A], A { type Out = Out0 }, Option[F[Out0]]] =
    At[DotMap[F, A], A { type Out = Out0 }, Option[F[Out0]]](
      (i: A { type Out = Out0 }) => (map: DotMap[F, A]) => map.get(i))(
      (i: A { type Out = Out0 }) => (optV: Option[F[Out0]]) => (map: DotMap[F, A]) =>
      optV.fold(map.remove(i))(v => map.put(i)(v))
    )

  implicit def dotMapIndex[F[_], A <: Dot, Out0]: Index[DotMap[F, A], A { type Out = Out0 }, F[Out0]] = Index.fromAt

  // NOTE: it would be nice to have something like UnorderedFoldable, but we can't since DotMap is not a proper functor
  // intuitively we could instead use a Traversal,
  // but we can't do a Traversal over values because their types depend on their particular keys.
  // We also shouldn't do a Traversal over pairs because modifications could end up collapsing the structure non-deterministically.
  // but I'm not sure if we actually want to be able to do modifications
  // over pairs since that may collapse the structure.
  // -- Here we have that not-great definition of Traversal. I don't think we want it. Just convert to a List instead.
  // implicit def dotMapTraversal[F[_], A <: Dot]: Traversal[DotMap[F, A], DotPair[F, A]] = new Traversal[DotMap[F, A], DotPair[F, A]] {
  //   def modifyF[G[_]: Applicative](f: DotPair[F, A] => G[DotPair[F, A]])(s: DotMap[F, A]): G[DotMap[F, A]] = {
  //     s.iterator.toList.traverse(f).map(
  //       _.foldLeft(DotMap.empty[F, A])(_ + _)
  //     )
  //   }
  // }
}

sealed trait DotMapInstances0 extends DotMapInstances1 {
  implicit def dotMapMonoidFromMonoidK[F[_]: MonoidK, A <: Dot]: Monoid[DotMap[F, A]] =
    new Monoid[DotMap[F, A]] {
      override def empty = DotMap.empty[F, A]
      override def combine(x: DotMap[F, A], y: DotMap[F, A]) = {
        (x.keySet ++ y.keySet).foldLeft(DotMap.empty[F, A]) {
          case (dMap, key) => dMap.put(key) {
            (x.get(key).foldK <+> y.get(key).foldK)
          }
        }
      }
    }
}

sealed trait DotMapInstances1 {
  implicit def dotMapSemigroup[F[_], A <: Dot](
    implicit dotSemigroup: DotKleisli[λ[B => Semigroup[F[B]]], A]
  ): Semigroup[DotMap[F, A]] =
    new Semigroup[DotMap[F, A]] {
      override def combine(x: DotMap[F, A], y: DotMap[F, A]) = {
        (x.keySet ++ y.keySet).foldLeft(DotMap.empty[F, A]) {
          case (dMap, key) => dMap.put(key) {
            implicit val m = dotSemigroup(key)
            (x.get(key), y.get(key)) match {
              case (Some(xv), Some(yv)) => xv |+| yv
              case (Some(xv), None) => xv
              case (None, Some(yv)) => yv
              case (None, None) => ??? // should never happen
            }
          }
        }
      }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy