"Unpacking" classes into method argument lists

But I think that’s not what it does, right? It doesn’t replace any typed objects with parameters. It replaces parameters with an object. From the perspective of the function author, this keyword does nothing. In the body of the function you still interact with the instance of the type. So the entire purpose of the keyword is to describe a capability offered to the caller. Which is that they may specify the fields of the type as parameters and it will be automatically packed into the expected argument type.

Or is this supposed to be a compiler directive and during parsing it will deconstruct the type and replace it with parameters at the def site? If that’s the case, what happens with callers who opt to pass an instance of the type rather than params? Will that also be deconstructed while parsing? And the references to the instance in the function body would also be replaced with references to the parameters? I’m fairly confident from the thread that this is not the case, but am open to be corrected on that understanding.

FWIW “pack” aligns more closely with “box” as both turn more primitive constructs into objects. “Unpack” and “unbox” go the other way. But using “unpack” to mean “turn a collection of parameters into an object” is backwards.

I totally see that point. Since we are referencing the object in the definition site, someone will always have to create that in practice, and this keyword is a shortcut for that creation. But for a mental model of whats going on here, I think its equivalent to think of this as a 1 method that takes an object, and another that takes the parameters that are the constituents of that object (and that second method would just create the object and call the first). My point was that one can think of the unpack as referring to the creation of this second method with all the parameters laid out, even though this is not whats happening in practice. I am also not trying to say that there is not better language to be considered here, but that unpack isnt completely opposite of whats going on here, when viewed from a certain lens.

1 Like

So… the synthetic method would… pack(?) the arguments into an object? :upside_down_face:

Seriously, though, I think I see where you are going with that. I still think it’s backwards but with that conceptual approach I suppose I could hold my nose and at least try to explain it to someone else with a straight face.

Experience shows that I likely could not, alas.

I would be more likely to go to pains to avoid using the feature than use one that is named backwards from how it appears at use-site. At use-site, if you need to know anything at all, you need to know that your arguments are being packed into an object.

Perhaps younger minds are better at unlearning, but I can do without the struggle.

I think where this might get weird is with mocking frameworks like mockito, Especially when it comes to verification. If I have an (un)packed class method what would happen with something like

 there were two(foo).bar(baz = ===(1), biz = any[String])

(i think that’s close to the syntax)

there is also the into soft modifier that potentially could be reused, but i do not love its positioning in the declaration site in this use case. that one doesnt make a verb statement about what is actually being done, but more what the result will be (intent, rather than implementation).

https://dotty.epfl.ch/docs/reference/experimental/into-modifier

If we’re going to reuse something, I’d rather reuse transparent:

case class Foo(i: Int, j: Long) {}

def quux(s: String, foo: transparent Foo) = ???

quux("salmon", i = 5, j = 6L)

That the type is transparent would mean that you can use arguments for its constructor(s) in place.

Furthermore, this suggests a different disambiguation strategy: use the name of the named arguments.

case class Box[A](a: A) {}

def bippy(box: transparent Box): Int = box.a.##

bippy("salmon")   // Could allow this
bippy(Box("salmon"))  // Error, probably?  Maybe Box[Box[String]]?
bippy(box = Box("salmon")) // Fine
bippy(a = Box("salmon"))   // Fine, gives a Box[Box[String]]

If one wanted to get really wild, one could generalize it so that

val box: transparent Box = "salmon"

val foo: transparent Foo = (1, 4L)

would work. But I am not really comfortable with that level of generalization.

Addendum: it would also be possible to allow dotted arguments for disambiguation:

case class One(i: Int) {}
case class Two(i: Int, j: Int, k: Int = 4) {}

// Perhaps legacy code made you want to do this...
def baz(one: transparent One, two: transparent Two) = ???

baz(one.i = 1, two.i = 2, j = 3)

Seems also like a bit of a potential minefield, but if the one.i form were always available, it would make weird un-nameable corner cases impossible, so it might be good to have.

1 Like

Names are confusing. Python uses unpack, I believe for good reason.

I don’t claim to be any kind of Python expert, but from what I can see, their terminology aligns quite nicely with what I’ve been saying.

From here (just an early result in a quick google):

# A sample function that takes 4 arguments
# and prints the,

def fun(a, b, c, d):

    print(a, b, c, d)
 
# Driver Code

my_list = [1, 2, 3, 4]
 
# Unpacking list into four arguments

fun(*my_list)

and


# A Python program to demonstrate use
# of packing
 
# This function uses packing to sum
# unknown number of arguments

def mySum(*args):

    return sum(args)
 
# Driver code

print(mySum(1, 2, 3, 4, 5))

print(mySum(10, 20))

So packing is collapsing multiple arguments at call site into a single item at def site. And unpacking is the reverse. I believe this thread is proposing packing for Scala, or have I very much misunderstood?

I would like propose that we stop talking about pack/unpack, checked/unchecked, etc. These discussions go nowhere

The reason these discussions go nowhere because both words are always true:

  1. Outside the function you are writing unpacked key-value pairs, which get packed into a case class inside the function. But when you define the function, you unpack the named parameters of the case class into the function parameter list, and get back a single parameter typed as a packed case class . They also allow you to pass in a packed case class but use * to unpack it into the argument list

  2. In one case safety is checked by the compiler and unchecked at runtime, in the other case safety is unchecked by the compiler but checked at runtime.

There is no sense thinking so hard about this, or discussing it so thoroughly. Both words are true. We’re not going to pick a longer enough keyword or notation to make it unambigious (def foo(v: unpackedOutsideFunctionButPackedInside MyCaseClass)?). We should just pick one and stick with the meaning. Programmers aren’t so rigid that they won’t be able to figure it out.

(This applies to the other thread as well. @unchecked is OK. .checked is OK. What’s not OK is swapping over from @unchecked to .checked with the same meaning; that will result in endless confusion)

1 Like

I hear you. Naming is tedious, pedantic, and famously hard. But despite those things, naming matters a great deal (c.f. “extension”). It is the difference between clarity leading to expressive power in a language vs confusion leading to incomprehension. Yes, I know that seems hyperbolic, but if this one doesn’t matter so much and the next one doesn’t either, where does it stop and actually start to matter?

To be clear here, “packed” and “unpacked” are adjectives. Where “pack” and “unpack” are verbs.

Are we using the keyword to describe the parameters at the call site? The parameter at the definition? And thus an adjective? So akin to “lazy val” or “implicit class”?

def foo(b: Unpacked[Bar])

Or is it a directive, as in “pack arguments to satisfy this parameter”? So a verb like “match” or “flatMap”?

def foo(pack b: Bar)

Or would we rather an adjective describing the parameter list, like “packable” or “unpackable”?

def foo(@unpackable b: Bar)

Ultimately I’m not trying to say one is better than another. I AM saying that naming matters and clarity is important.

In any case, I think I’ve said all there is to say on this, so I’ll not say more.

Best of luck on the SIP.

1 Like

Another piece of prior art has turned up: Kotlin is planning exactly this feature for Kotlin 2.2:

5 Likes