I am working in a DSL where having to repeatedly type List in List(a,b,c).foreach is a pain point, I want to extend a (a,b,c).foreach method so that I can write the elements in tuple literal notation which will translate into a List() or Seq() .foreach, like so
val s = (1,2,3).foreach(n => n + 1)
// List(1,2,3).foreach(n => n + 1) when evaluated
It must work for tuples of arbitrary length.
I attempt it here
extension [T <: Tuple](tuple: T)
inline def foreach =
tuple.productIterator.toList.foreach
val s = (1,2,3).foreach(n => n + 1)
Yes you are right I am looking for a map. Your solution looks good, what is wrong about it’s semantics? You just mean it’s misleading in what is typically understood as what mapping does? (i.e mapping on a container should preserve the container type)
Yes, exactly. And off my head wouldn’t know how to do this. Tuple has #from[I]Array(), but we don’t have a proper element type for this, I guess…? One might match on list size (for some finite range of sizes), but this match won’t work at compile time, and from there it’s not that far to full case-by-case extensions, anyway.
extension [A](t1: Tuple1[A])
def map[B](f: A => B): (B) =
f(t1(0))
extension [A](t2: (A, A))
def map[B](f: A => B): (B, B) =
(f(t2(0)), f(t2(1)))
// ...
If you’re fine with lists as results from the “map”, this needn’t concern you, of course. (I’d probably call it something other than #map(), though.)
There’s already builtin map functionality in the std library. It’s not super easy to use because it’s built on polymorphic functions and match types, but it works:
scala> type F[A] = A match
| case Int => String
| case String => Boolean
|
scala> val f: [A] => (a: A) => F[A] = [A] => (a: A) => a match
| case i: Int => s"int: $i"
| case s: String => s.nonEmpty
|
val f: [A] => (a: A) => F[A] = Lambda/0x00000008006e1800@64b1d0ab
scala> (32, "", "foo", 5).map(f)
val res1: (String, Boolean, Boolean, String) = (int: 32,false,true,int: 5)
Ah, right, nice. In the meantime I found that a past version of me knew that there is some kind of #map() for tuples, but since I never could put it to good use, it slipped from my radar.
Just to note, depending on compiler settings (and Scala version?), it should be a.asMatchable match ....
Not sure if there’s any sane way to put plain functions for uniform tuples on top of this mechanism to make this a match for the DSL use case. But the OP seems to be happy with lists, anyway…
Yeah the receiving function in my DSL expects a List, not a Tuple, It’s just that I find having to type List(a,b,c) over and over again everywhere quite terrible in a markup DSL that’s aiming to be terse and readable, so I just want to write it in tuple literal form and have that convert to a List. Other implicit conversion solutions I would typically go for can’t work here as I must do a .map(fn) at the call-site inlined. i.e I can’t simply do f(a,b,c) with * or f((a,b,c)) with an implicit Tuple -> List conversion imported, then inside the function call .map; instead I must do the map in the callsite f((a,b,c).map(g)) (that’s what the DSL looks like), and I want to avoid f(List(a,b,c).map(g)
I see. Personally, I’d probably be willing to spend one additional char per instantiation in order to keep things simple and just introduce a simple factory function for lists, e.g.