Scala 3 - Extract Tuple of wrappers and InverseMap on First Order Type

帅比萌擦擦* 提交于 2021-02-05 06:10:23

问题


I am trying to create a function, which takes a tuple of higher-kinded types and applies a function to the types within the higher-kinded types.

In the example below, there is a trait Get[A] which is our higher-kinded type. There is also a tuple of Get's: (Get[String],Get[Int]) as well as function from (String,Int) => Person.

Scala-3 has a Match-Type called InverseMap which converts the type (Get[String], Get[Int]) into what is essentially the type (String,Int).

So the ultimate goal is to write a function which can take a tuple with any number of Get[_] types and a function whose input matches the InserveMap types and finally return a Get[_], where the wrapped type is the result of the function.

I have attempted to create a function called genericF below to show the desired behavior, though it may not be correct -- but I think it does at least show the proper intent.

  case class Person(name: String, age: Int)
  trait Get[A] {
    def get: A
  }
  case class Put[A](get: A) extends Get[A]
    
  val t: (Get[String], Get[Int]) = (Put("Bob"), Put(42))
  
  val fPerson: (String,Int) => Person = Person.apply _
  
  def genericF[T<:Tuple,I<:Tuple.InverseMap[T,Get],B](f: I => B, t: T): Get[B] = ???
  val person: Get[Person] = genericF(fPerson, t)

I have set up a Scastie here: https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23


回答1:


Your code is almost compiling already - the only thing is that fPerson is of type (String, Int) => Person instead of ((String, Int)) => Person (taking a tuple instead of 2 separate parameters).

Edit: The solution below this one is not nice, although it is perhaps more efficient for TupleXXL's. Here's a nicer version with typeclasses (Scastie):

val fPerson: ((String, Int)) => Person = Person.apply _

opaque type Extract[GT <: Tuple, RT <: Tuple] = GT => RT
given Extract[EmptyTuple, EmptyTuple] = Predef.identity
given [A, PG <: Tuple, PR <: Tuple](using p: Extract[PG, PR])
   as Extract[Get[A] *: PG, A *: PR] = {
  case h *: t => h.get *: p(t)
}

def genericF[GT <: Tuple, RT <: Tuple, B](
    f: RT => B,
    t: GT
)(using extract: Extract[GT, RT]): Get[B] = Put(f(extract(t)))

Edit: Travis Stevens, the OP, has managed to get the solution at the bottom to work without creating AllGs, by using IsMappedBy. This is what they got (Scastie):

val fPerson: ((String, Int)) => Person = Person.apply _

type ExtractG = [G] =>> G match {
  case Get[a] => a
}

def extract[T <: Tuple, I <: Tuple.InverseMap[T, Get]](
    t: T
  )(using Tuple.IsMappedBy[Get][T]): I =
  t.map {
    [G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
  }.asInstanceOf[I]

def genericF[T <: Tuple, I <: Tuple.InverseMap[T, Get], B](
    t: T,
    f: I => B
)(using Tuple.IsMappedBy[Get][T]): Get[B] = Put(f(extract(t)))

And here's one way you could implement genericF using Tuple.InverseMap (note that I switched the two parameters to genericF:

val fPerson: ((String, Int)) => Person = Person.apply _

type ExtractG = [G] =>> G match {
  case Get[a] => a
}

type AllGs[T <: Tuple] = T match {
  case EmptyTuple => DummyImplicit
  case Get[_] *: t => AllGs[t]
  case _ => Nothing
}

def extract[T <: Tuple](t: T)(using AllGs[T]): Tuple.InverseMap[T, Get] =
  t.map {
    [G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
  }.asInstanceOf[Tuple.InverseMap[T, Get]]

def genericF[B](
    t: Tuple,
    f: Tuple.InverseMap[t.type, Get] => B
)(using AllGs[t.type]): Get[B] = Put(f(extract(t)))

val person: Get[Person] = genericF(t, fPerson)

ExtractG is to make the PolyFunction compile, because it requires you apply a type constructor to its type parameter.

AllGs is to verify that the tuple consists only of Gets, because as pointed out by Dmytro Mitin, it isn't typesafe otherwise. If it's all Gets, the type becomes DummyImplicit, which Scala provides for us. Otherwise, it's Nothing. I guess it could conflict with other implicit/given Nothings in scope, but if you do have one already, you're screwed anyways :).

Note that this will work only when you have Get and will need some modification if you also want it to work for tuples like (Put[String], GetSubclass[Int]).



来源:https://stackoverflow.com/questions/64339583/scala-3-extract-tuple-of-wrappers-and-inversemap-on-first-order-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!