total-map

total-map is a small library for working with types which have finite set of possible values.

Why?

To have a Map[K, V] that guarantees totality of apply.

What?

The core of the library is just two things:

A typeclass which express all possible values of given type

trait AllValues[T] {
    def toSet: Set[T]
}

And a data structure which guarantees that there exist an entry for every possible key

trait TotalMap[K, +V] {
    def toMap: Map[K, V]
    def apply(k: K): V
}

How?

Install

libraryDependencies += "com.github.krever" % "total-map" % "0.0.3+1-ee160804"

Import

import totalmap._

An then you need to have an instance of AllValues[T]. It can be acquired in few ways:

Create named set

val myStrings = NamedSet("a", "b", "c")
// myStrings: NamedSet[String] = totalmap.NamedSet$$anon$2@7f1f8381
import myStrings.allValues

implicitly[AllValues[myStrings.Elem]]
// res1: AllValues[myStrings.Elem] = AllValues(a,b,c)

Use preexisting instances

implicitly[AllValues[Unit]]
// res2: AllValues[Unit] = AllValues(())
implicitly[AllValues[Boolean]]
// res3: AllValues[Boolean] = AllValues(true,false)

Instances that require materializing big collections are not available, but might come if someone comes with reasonable use case

implicitly[AllValues[Int]]
// error: could not find implicit value for parameter e: totalmap.AllValues[Int]
// implicitly[AllValues[Int]]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^

Enumeratum

You can get enumeratum support by installing

libraryDependencies += "com.github.krever" % "total-map-enumeratum" % "0.0.3+1-ee160804"

So when you have an Enum

import enumeratum._
sealed trait Animal extends EnumEntry  
object Animal extends Enum[Animal] {
  case object Cat extends Animal
  case object Dog extends Animal
  override def values: scala.collection.immutable.IndexedSeq[Animal] = findValues
}

You can the instances for free

import totalmap.modules.enumeratum._

implicitly[AllValues[Animal]]
// res5: AllValues[Animal] = AllValues(Cat,Dog)

Pureconfig support

You can load TotalMaps directly from config. This works currently only for Strings as keys.

import totalmap._
import com.typesafe.config.ConfigFactory
import pureconfig._
import totalmap.modules.pureconfig._

val mySet = NamedSet("a", "b", "c")
// mySet: NamedSet[String] = totalmap.NamedSet$$anon$2@7881b440
import mySet.allValues

implicitly[AllValues[mySet.Elem]]
// res7: AllValues[mySet.Elem] = AllValues(a,b,c)

val cfg = ConfigFactory.parseString(
    """
    |a = foo
    |b = bar
    |c = baz
    """.stripMargin
)
// cfg: com.typesafe.config.Config = Config(SimpleConfigObject({"a":"foo","b":"bar","c":"baz"}))

val result = pureconfig.loadConfig[TotalMap[mySet.Elem, String]](cfg)
// result: ConfigReader.Result[TotalMap[mySet.Elem, String]] = Right(
//   TotalMap(b -> bar,a -> foo,c -> baz)
// )