0

Learning Scala 3.

how can i make this code type-match and compile?

trait Key {
    type Value
}

object Name extends Key {
  type Value = String
}

object Age extends Key {
  type Value = Int
}


type DB = (k: Key) => Option[k.Value]


val dbImpl: DB = (k: Key) => {
  k match {
    case Name => Some("abc") // this does not compile, how can i make it sniff Value type is String automatically?
    case Age => None
  }
}

thanks

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Max
  • 1,741
  • 3
  • 23
  • 40

1 Answers1

2

As a workaround you can try match types

trait Key
object Name extends Key
object Age extends Key

type OptValue[K <: Key] = K match
  case Name.type => Option[String]
  case Age.type  => Option[Int]

def dbImpl[K <: Key](k: K): OptValue[K] = k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None

or

val dbImpl: [K <: Key] => K => OptValue[K] =
  [K <: Key] => (k: K) => k match
    case _: Name.type => Some("abc")
    case _: Age.type  => None

I'm reminding that

type DB = [K <: Key] => K => OptValue[K]

val dbImpl: DB = [K <: Key] => (k: K) => k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None

or

def dbImpl[K <: Key](k: K): OptValue[K] = k match
  case Name => Some("abc")
  case Age  => None

or

type Value[K <: Key] = K match
  case Name.type => String
  case Age.type  => Int

def dbImpl[K <: Key](k: K): Option[Value[K]] = k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None

will not work.

scala 3 map tuple to futures of tuple types and back

Scala 3: typed tuple zipping

Express function of arbitrary arity in vanilla Scala 3

Shapeless3 and annotations

How to get match type to work correctly in Scala 3

Another option is type classes

trait Key
object Name extends Key
object Age extends Key

// type class
trait KeyValue[K <: Key]:
  type Value
  type Out = Option[Value]
  def apply(k: K): Out

object KeyValue:
  type Aux[K <: Key, V] = KeyValue[K] { type Value = V }
  def instance[K <: Key, V](f: K => Option[V]): Aux[K, V] = new KeyValue[K]:
    override type Value = V
    override def apply(k: K): Out = f(k)

  given Aux[Name.type, String] = instance(_ => Some("abc"))
  given Aux[Age.type, Int]     = instance(_ => None)

def dbImpl[K <: Key](k: K)(using kv: KeyValue[K]): kv.Out = kv(k)

One more option is inlining and using scala.compiletime.summonFrom

trait Key:
  type Value

object Name extends Key:
  override type Value = String

object Age extends Key:
  override type Value = Int

inline def dbImpl(k: Key): Option[k.Value] = inline k match
  case Name => summonFrom {
    case _: (String =:= k.Value) => Some("abc")
  }
  case Age => summonFrom {
    case _: (Option[Int] =:= Option[k.Value]) => None: Option[Int]
  }

The easiest is to make Value a type parameter rather than type member

trait Key[Value]
object Name extends Key[String]
object Age extends Key[Int]

def dbImpl[V](k: Key[V]): Option[V] = k match
  case Name => Some("abc")
  case Age  => None

This implementation compiles while

trait Key:
  type Value
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int

def dbImpl[V](k: Key {type Value = V}): Option[V] = k match
  case Name => Some("abc")
  case Age  => None

or

trait Key:
  type Value
object Key:
  type Aux[V] = Key { type Value = V }
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int

def dbImpl[V](k: Key.Aux[V]): Option[V] = k match
  case Name => Some("abc")
  case Age  => None

or

trait Key:
  type Value
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int

def dbImpl(k: Key): Option[k.Value] = k match
  case Name => Some("abc")
  case Age  => None

doesn't. (Scala 3.2.2)

Aleksander Boruch-Gruszecki. GADTs in Dotty https://www.youtube.com/watch?v=VV9lPg3fNl8

Dale Wijnand. Space Engine for Pattern Matching https://www.youtube.com/watch?v=yaxJPIsy4Js

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Dmytro, thanks a lot for giving another clarif. I'm a bit surprised that they made it so complicated and not auto-inferred. Seems that `Name` unambiguously pointing to `String` type of `k.Value`... version with `summonFrom` is very interesting – Max Feb 18 '23 at 15:40
  • I have to agree with @Max, it seems like a lot of the benefit of these type-level features would be for the compiler to be able to infer and reason about types on its own a bit more, especially in what seems like a pretty straightforward case like this, but perhaps that will be improved in future versions. – anqit Feb 18 '23 at 16:37
  • 1
    @Max See update with type parameter `Value` – Dmytro Mitin Feb 18 '23 at 17:06
  • i see how example with `summonFrom` is the closest to dealing with plain dependent fn type `type DB = (k: Key) => Option[k.Value]`. `object Name extends Key[String]` seems like older style using generics instead of nested type – Max Feb 18 '23 at 17:19
  • 1
    @Max *seems like older style using generics instead of nested type* Actually they are different https://stackoverflow.com/questions/59148665/whats-different-between-def-applytct-and-type-tdef-applyct – Dmytro Mitin Feb 18 '23 at 17:33
  • By the way, `def foo[V](k: V): V` is unsound (on contrary to `def foo[V](k: Key[V]): V`) https://stackoverflow.com/questions/64209514/t-a-return-t-method https://stackoverflow.com/questions/61668592/why-cant-i-return-a-concrete-subtype-of-a-if-a-generic-subtype-of-a-is-declared/ https://stackoverflow.com/questions/52479695/type-mismatch-on-abstract-type-used-in-pattern-matching – Dmytro Mitin Apr 07 '23 at 15:47
  • https://github.com/lampepfl/dotty/issues/17235 – Dmytro Mitin Apr 12 '23 at 12:17