0

I'm trying to write a generic repository module that can handle a varying number of keys

  trait Repo[K, V] {
    def read(key: K): V
  }
trait DynamoDBRepo extends Repo[K,V]{

def aRepo[K:StringIdentifiable,V]() = new Repo[K,V]{

  val dynamoDBClient = ???

  override def read(key: K, tableName: String): V =  { 

  val tableKey: String = implicitly[StringIdentifiable].identify(key)

  dynamoDBClient.Table(tableName).get(tableKey)  //(*)
 }  
}

}

@typeclass trait StringIdentifiable[M] {
  def identify(id: M): String
}

(*) However, dynamoDBClient.Table(tableName).get(key) can also take a tuple as a key (partition key and sort key).

Therefore, I want to somehow extract String from K or (String, String) from (K,K) from key:K override def read(key: K)

I first tried this to extract types from a tuple that implements a StringIdentifiable typeclass. I got stuck.

Then tried to rewrite a StringIdentifiable type class, that would return String when the argument was one key but (String, String) when the argument to the read was a tuple. But I was not able to use this method either.

How can I solve his problem without losing my abstraction

Rabzu
  • 52
  • 5
  • 26
  • Well, in such case I would simply create a wrapper that would use ADT: `sealed trait RepoKey; case class SimpleKey(key: String) extends RepoKey; case class PartitionedKey(partition: String, key: String) extends RepoKey`. If there are only 2 cases any further generalization is just overengeenering. – Mateusz Kubuszok Apr 11 '20 at 19:22

1 Answers1

2

If requirements from the question are all requirements that you solution must fulfill, then shapeless, path-dependent types and type classes are not necessary at all.

sealed trait RepoKey[K] // TBH I am not sure if genericness here is even needed in your case...
case class SimpleKey(key: K) extends RepoKey[K]
case class PartitionedKey(partition: K, key: K) extends RepoKey[K]

trait Repo[K, V] {
  def read(key: RepoKey[K]): V
}

class DynamoDBRepo(dynamoDBClient: ...) extends Repo[String, V] {

  def read(key: RepoKey[String]): V = key match { 
    case SimpleKey(key)                 => ...
    case PartitionedKey(partition, key) => ...
  }
}

Even if you store your key as something else than String, the only typeclass you would need, would be something like:

trait KeyExtractor[K] {
  def extract(key: RepoKey[K]): String
}

which almost certainly wouldn't need derivation, but one explicit definition.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64