dotty / scala3 unmap a mapped tuple type to its constituent types [duplicate] - scala

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

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).
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)))
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]).
Travis Stevens, the OP, has managed to get the solution above this one 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 using dependent types, just for fun (Scastie):
type Extract[T <: Tuple] <: Tuple = T match {
case EmptyTuple => EmptyTuple
case Get[a] *: t => a *: Extract[t]
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def genericF[T <: Tuple : AllGs, B](
t: T,
f: Extract[t.type] => B
): Get[B] = {
def extract[T <: Tuple](t: T): Extract[T] = t match {
case _: EmptyTuple => EmptyTuple
case (head *: tail): (Get[_] *: _) => head.get *: extract(tail)
}
Put(f(extract(t)))
}
I was hoping Extract wouldn't compile for tuples like (Put("foo"), 3), but unfortunately, AllGs is still necessary.

Related

SYB `mkT` function in Scala

Continuing on from a previous question of mine, I am attempting to implement Scrap Your Boilerplate in scala 3 and am running into an issue now with the mkT function described in the paper. Given the following definition of cast:
trait Cast[A, B]:
def apply(a: A): Option[B]
object Cast:
given cSome[A, B](using t: A =:= B): Cast[A, B] with
def apply(a: A) = Some(t(a))
given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] with
def apply(a: A) = None
def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c(a)
I have tried to make mkT as follows:
class MakeTransform[A] (val f: A => A) {
def apply[B](b: B)(using c: Cast[A => A, B => B]): B = c(f) match {
case Some(fb) => fb(b)
case _ => b
}
}
def mkT[A](f: A => A): MakeTransform[A] = MakeTransform(f)
And this seems to work fine with the boolean example:
def not(a: Boolean): Boolean = !a
mkT(not)(true) // false, function is clearly called on input value
mkT(not)('a') // 'a'
However, when I try it with the company model objects, I can only get it to function as expected when I provide an explicit type call and the parameter matches that type. So given the following Salary definition:
sealed trait Salary
case class S(amt: Float) extends Salary
def incS(amt: Float): Salary => Salary = {
case S(a) => S(a * (1 + amt))
}
val ralf: Employee = E(P("Ralf", "Amsterdam"), S(8000))
I attempt to raise a Salary:
inc(.1)(S(8000)) // S(8000) <= no change
Unless, however, I am explicit with the type:
inc(.1)[Salary](S(8000)) // S(8800.0)
But when I do that, I can only pass objects of the specified type as input:
inc(.1)[Salary](ralf) // does not compile
which obviously defeats the purpose.
My thought was, that because MakeTransform's apply method takes a type parameter, that the input type would be inferred by the value passed to it, but that doesn't seem to always be the case. Even more baffling to me is the inconsistent behavior between the Boolean and Salary examples. Any ideas why? Also, while debugging things like this, is there a way to see what types are being inferred? The debugger shows the runtime type of the variables, but it would be helpful if there was a way to see what type parameters are at runtime.
UPDATE: new thought, does this have to do with the fact that S <: Salary and not S =:= Salary?
You seem to again miss an implicit parameter (constraint in Haskell terms)
inc :: Typeable a => Float -> a -> a
-- ^^^^^^^^^^
inc k = mkT (incS k)
Confer
def inc[A](amt: Float): A => A = mkT(incS(amt))(_)
inc(.1)(S(8000)) // S(8000.0) -- not changed
with
def inc[A](amt: Float)(using c: Cast[Salary => Salary, A => A]): A => A = mkT(incS(amt))(_)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
inc(.1)(S(8000)) // S(8800.0) -- changed
The whole code
https://scastie.scala-lang.org/DmytroMitin/v82LGbOtRieGmJX7gCb99A/1
Regarding debugging you can switch on
scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")
in build.sbt.
Your mkT looks quite different from what's in the paper. Here is my take at it:
import util.NotGiven
case class Cast[A, B](ev: Option[A =:= B])
object Cast:
given cSome[A, B](using t: A =:= B): Cast[A, B] = Cast(Some(t))
given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] = Cast(None)
def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c.ev.map(e => e(a))
def mkT[A, B](f: B => B)(a: A)(using c: Cast[A, B]): A =
c.ev match
case Some(aToB) => aToB.flip(f(aToB(a)))
case None => a
def not(a: Boolean): Boolean = !a
println(mkT(not)(true)) // false
println(mkT(not)('a')) // 'a'
sealed trait Salary
case class S(amt: Float) extends Salary
def incS(amt: Float): S => S = {
case S(a) => S(a * (1 + amt))
}
def inc[A](k: Float)(a: A)(using c: Cast[A, S]): A = mkT(incS(k))(a)
println(inc(.1)(S(8000))) // increased to `S(8800.0)`
println(inc(.1)('a')) // left as-is
It works just fine when you change the type of incS from Salary => Salary to S => S, because in your case, S is a subtype of Salary that's not equal to Salary.

Why Type Check Error are not detected when Pattern Matching on Generic Type in Scala

Defining the following structure:
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[A](head: A, tail: List[A]) extends List[A]
and the following function (please note that the second line which is the type check error spot is also an algorithmic mistake for short-circuiting the result, but nonetheless it came up and i wonder why type checking is not spotting it)
def foldRight[A, B] (list: List[A], z: B)(f: (A,B) => B ): B = list match {
case Nil => z
case Cons(0, _) => z // problematic line
case Cons(s, xs) => f(s, foldRight(xs, z) (f))
}
I know scala also suffer from Type Erasure, but i am surprised that in this case, this can't be detected at compilation time ? Cons is Cons[A] in this case, and z is clearly of type B ?
Any reason why such code actually compile ?
EDIT1
Sounds like there is a lengthy explanation here https://gist.github.com/jkpl/5279ee05cca8cc1ec452fc26ace5b68b, but Long read. If someone could make it simpler :)
Why these or those decisions about language design were made is opinion based.
In Scala spec it's written:
https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-parameter-inference-for-constructor-patterns
8.3.2 Type parameter inference for constructor patterns
Assume a constructor pattern 𝐶(𝑝1,…,𝑝𝑛) where class 𝐶 has type
parameters 𝑎1,…,𝑎𝑛. These type parameters are inferred in the same
way as for the typed pattern (_: 𝐶[𝑎1,…,𝑎𝑛]).
So for pattern Cons(h, t) type parameter A in Cons[A](h, t) is inferred as if the pattern were _: Cons[A] (which becomes _: Cons[_] at runtime because of erasure).
There is example there in the spec how types are inferred for
class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
case y: Number => y.n
}
It's explained there why for the pattern y: Number type parameter B (new type parameter B) is inferred Int.
Similarly, in our case for the pattern Cons(0, _) (as if it were _: Cons[A]) A is inferred Int.
After typer phase (scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")) the code becomes
def foldRight[A, B](list: App.List[A], z: B)(f: (A, B) => B): B = list{App.List[A]} match {
case App.this{App.type}.Nil{App.Nil.type} => z{B}
case (head: A, tail: App.List[A]): App.Cons[?A1](0{Int(0)}, _{App.List[A]}){App.Cons[?A1]} => z{B}
case (head: A, tail: App.List[A]): App.Cons[?A2]((s # _{A}){A}, (xs # _{App.List[A]}){App.List[A]}){App.Cons[?A2]} => f.apply{(v1: A, v2: B): B}(s{A}, App.this{App.type}.foldRight{[A, B](list: App.List[A], z: B)(f: (A, B) => B): B}[A, B]{(list: App.List[A], z: B)(f: (A, B) => B): B}(xs{App.List[A]}, z{B}){(f: (A, B) => B): B}(f{(A, B) => B}){B}){B}
}{B}
and after erasure (scalacOptions += "-Xprint:erasure") it becomes
def foldRight(list: App$List, z: Object, f: Function2): Object = {
<synthetic> var rc9: Boolean = false;
<synthetic> <stable> var x2: App$Cons = (null: App$Cons);
{
case <synthetic> val x1: App$List = list;
case11(){
if (App$Nil.==(x1))
matchEnd10(z)
else
case12()
};
case12(){
if (x1.$isInstanceOf[App$Cons]())
{
rc9 = true;
x2 = (x1.$asInstanceOf[App$Cons](): App$Cons);
{
<synthetic> val p3: Object = x2.head();
if (scala.Int.box(0).==(p3))
matchEnd10(z)
else
case13()
}
}
else
case13()
};
case13(){
if (rc9)
{
val s: Object = x2.head();
val xs: App$List = x2.tail();
matchEnd10(f.apply(s, App.this.foldRight(xs, z, f)))
}
else
case14()
};
case14(){
matchEnd10(throw new MatchError(x1))
};
matchEnd10(x: Object){
x
}
}
};
Actually, code similar to yours compiles even in Haskell if we add the information that a type parameter a belongs to type classes Eq and Num to the context of function signature
https://ideone.com/qqOsI2
data List a = Nil | Cons a (List a)
foldRight :: (Eq a, Num a) => List a -> b -> (a -> b -> b) -> b
foldRight list z f = case list of
Nil -> z
Cons 0 _ -> z
Cons s xs -> f s (foldRight xs z f)
Type classes are not first-class citizens in Scala. So Scala can't request similar thing about something like Num (in Scala everything can be compared with ==, so Eq is "automatical").
Cons is Cons[A] in this case
This is not correct, or at very least it is confusing. There are two types A here: the type parameter in Cons[A] and the type parameter in foldRight[A,B]. There is nothing to say that they are the same type. You could equally well define
case class Cons[Z](head: Z, tail: List[Z]) extends List[Z]
So on the problematic line
case Cons(0, _) => z // problematic line
the compiler treats this as Cons[Int](0, _) because the type of head is Int. The compiler is promiscuous in this case and allows this match in all cases even though it can only succeed if foldLeft is called with a List[Int].

Pattern matching on classes containing generalized type constraints

I have a trait that is extended by multiple subclasses
trait Sup
case class Sub[A, B](a: A, f: B => B)(implicit val ev: A =:= B) extends Sup
case class Sub2[A, B](a: A, f: B => Unit)(implicit val ev: A =:= B) extends Sup
And two functions:
def foo[A, B](a: A, f: B => B)(implicit ev: A =:= B) = f(a)
def bar[A, B](a: A, f: B => Unit)(implicit ev: A =:= B) = f(a)
Now I can do some form of dynamic dispatching and call foo if the object is a Sub and bar if the object is a Sub2.
def dispatch(obj: Sup) = {
obj match {
case Sub(a, f) => foo(a, f)
case Sub2(a, f) => bar(a, f) // type mismatch: found: Nothing => Unit. required: B => Unit
}
}
I've also tried to pass the evidence explicitly but it results in the same error:
case o # Sub2(a, f) => bar(a, f)(o.ev) // type mismatch
It is very weird that f: B => B works (I can call foo), but f: B => Unit doesn't work (I can't call bar).
Not an answer but something to think about:
case class Sub1[A, B](a: A, f: B => B)
case class Sub2[A, B](a: A, f: B => Unit)
def foo[A, B](a: A, f: B => B)(implicit ev: A =:= B) = f(a)
def bar[A, B](a: A, f: B => Unit)(implicit ev: A =:= B) = f(a)
def dispatch(obj: Any) = obj match {
case Sub1(a, f) => foo(a, f)
case Sub2(a, f) => bar(a, f) // type mismatch: found: Nothing => Unit. required: B => Unit
}
This code have the same problem as yours but Sub1 and Sub2 case classes don't even have implicit blocks.
implicit section in case class doesn't effect pattern resolution. This section is just syntax sugar for calling apply(a: A, f: B => B)(implicit val ev: A =:= B) method on Sub1/2's companion objects. Pattern matching use unapply method to match the pattern at runtime and this unapply don't even know about evidences.
But I'm still wondering why first case is compiled without having this evidence.
Edit: Adding helpful comment from #AlexeyRomanov
More type inference than type erasure. But yes, the compiler infers type Any for a and Any => Any for f and then produces and uses evidence that Any =:= Any. In the second case it infers Nothing => Unit for f, because B => Unit is contravariant in B, and fails to find Any =:= Nothing.
You can actually make it work using type variable patterns:
def dispatch(obj: Sup) = {
obj match {
case obj: Sub[a, b] => foo(obj.a, obj.f)(obj.ev)
case obj: Sub2[a, b] => bar(obj.a, obj.f)(obj.ev)
}
}
This part is an answer to the comments, because it doesn't really fit in there:
Btw, there is still one subtlety I do not get: why is B => Unit contravariant in B
what is compiler's logic for this Nothing => Unit inference staff
You need to start with function variance. X => Y is a subtype of X1 => Y1 if and only if X is a supertype of X1 and Y is a subtype of Y1. We say it's contravariant in X and covariant in Y.
So if you fix Y = Unit, what remains is just contravariant in X. Any => Unit is a subtype of String => Unit, which is a subtype of Nothing => Unit. In fact, Nothing => Unit is the most general of all B => Unit, and that's why it gets inferred in the Sub2 case.
and B => B not (since it infers Any => Any) ?
The situation with B => B is different: String => String is neither a subtype nor a supertype of Any => Any, or of Nothing => Nothing. That is, B => B is invariant. So there is no principled reason to infer any specific B, and in this case the compiler uses the upper bound for B (Any), and B => B becomes Any => Any.

Is it possible to use a fixed constant instance of Failure as Try[B] for any B without casting?

While thinking about my previous question, I realized I ought to be able to write something like the following:
val empty: Try[B, forall types B] = Failure(new RuntimeException("empty"))
def firstSuccess[A, B](xs: Iterable[A], f: A => Try[B]): Try[B] = {
xs.foldLeft(empty)((e, a) => e.recoverWith { case _ => f(a) })
}
because a Failure is a valid Try[B] for any type B. Is there a way to achieve my "B, forall types B" in Scala?
You can use the Nothing type since everything in scala is Nothing:
val empty = Failure[Nothing](new RuntimeException("empty"))
def firstSuccess[A, B](xs: Iterable[A], f: A => Try[B]): Try[B] = {
xs.foldLeft[Try[B]](empty)((e, a) => e.recoverWith { case _ => f(a) })
}
You do have to sprinkle in a few types here and there though (added type parameter to foldLeft).

Correct encoding of this existential type in Scala?

I'm interested in encoding this Stream type from the Stream Fusion paper from Coutts et al. I'm exploring stream fusion in Scala, attempting to use macros in place of GHC's rewrite rules.
data Stream a = ∃s. Stream (s → Step a s) s
data Step a s = Done
| Yield a s
| Skip s
I've tried a few different approaches but I'm not sure how to encode the type of Stream in Scala such that both occurrences of S refer to the same type. I've written the Step type easily as.
sealed abstract class Step[+A, +S]
case object Done extends Step[Nothing, Nothing]
case class Yield[A, S](a: A, s: S) extends Step[A, S]
case class Skip[S](s: S) extends Step[Nothing, S]
So far this type seems correct. I've used covariance so that a function of type A => A will work even if we receive a Yield and return a Done or Step. Just like in Haskell.
My sticking point has been the signature of Stream. I've been attempting to define it as just a case class. The only signature that has worked so far is using an Exists type operator and Tuple to perserve the equality of type S in both components as below.
type Exists[P[_]] = P[T] forSome { type T }
case class Stream[A](t: Exists[({ type L[S] = (S => Step[A, S], S)})#L])
Is there a way to encode it such that the tuple is not needed? Something closer to Haskell's (assuming existential operator) this:
case class Stream(∃ S. f: S => Step[A, S], s: S)
where each member can be separate field.
It also occurs to me that I could encode this in an SML Module/Functor style like so:
trait Stream[A] {
type S <: AnyRef
val f: S => Step[A, S]
val s: S
}
object Stream {
def apply[A, S1 <: AnyRef](next: S1 => Step[A, S1], st: S1): Stream[A] = new Stream[A] {
type S = S1
val f = next
val s = st
}
def unapply[A](s: Stream[A]): Option[(s.f.type, s.s.type)] = Some(s.f, s.s)
}
but this is a little more complicated. I was hoping there exists a clearer way, that I am ignorant of. Also as I attempted to explore this path, I had to do a few things to satisfy the compiler such as add the AnyRef bound, and the unapply method doesn't work. With this error message from scalac:
scala> res2 match { case Stream(next, s) => (next, s) }
<console>:12: error: error during expansion of this match (this is a scalac bug).
The underlying error was: type mismatch;
found : Option[(<unapply-selector>.f.type, <unapply-selector>.s.type)]
required: Option[(s.f.type, s.s.type)]
res2 match { case Stream(next, s) => (next, s) }
^
First off, Step looks perfect to me. As for Stream, I think you're on the right track with the abstract type. Here's what I came up with (including implementations of the remaining methods in section 2.1 of the Coutts paper):
abstract class Stream[A] {
protected type S
def next: S => Step[A, S]
def state: S
def map[B](f: A => B): Stream[B] = {
val next: S => Step[B, S] = this.next(_) match {
case Done => Done
case Skip(s) => Skip(s)
case Yield(a, s) => Yield(f(a), s)
}
Stream(next, state)
}
def unstream: List[A] = {
def unfold(s: S): List[A] = next(s) match {
case Done => List.empty
case Skip(s) => unfold(s)
case Yield(a, s) => a :: unfold(s)
}
unfold(state)
}
}
object Stream {
def apply[A, S0](n: S0 => Step[A, S0], s: S0) = new Stream[A] {
type S = S0
val next = n
val state = s
}
def apply[A](as: List[A]): Stream[A] = {
val next: List[A] => Step[A, List[A]] = {
case a :: as => Yield(a, as)
case Nil => Done
}
Stream(next, as)
}
def unapply[A](s: Stream[A]): Option[(s.S => Step[A, s.S], s.S)] =
Some((s.next, s.state))
}
A couple things to note:
My unapply has a dependent method type: it depends on the s.S. I think that might have been your stumbling block.
The unfold method in unstream is not tail-recursive.
The thing I'm still not really clear on myself is why it's important for S to be existential / hidden / whatever. If it's not, you could just write:
case class Stream[A, S](next: S => Step[A, S], state: S)
... but I assume there's a reason for it. That being said, I'm also not sure this approach actually hides S the way you want. But this is my story and I'm sticking to it.