Skip to content

Commit 7be3b19

Browse files
committed
wip
1 parent 40f078b commit 7be3b19

File tree

1 file changed

+117
-2
lines changed

1 file changed

+117
-2
lines changed

content/unpack.md

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ val futureData = downloadAsync(config*, asyncConfig*)
218218
val stream = downloadStream(config*, asyncConfig*)
219219
```
220220

221-
## Applications in the Wild
221+
## Applications
222222

223223
### Requests-Scala
224224

@@ -802,6 +802,100 @@ values by field name seems like it would be a lot more intuitive than relying on
802802
order and hoping it lines up between your `case class` and the parameter list you are unpacking
803803
it into.
804804

805+
### Binary Compatibility
806+
807+
parameter lists using `unpack` should be externally indistinguishable from individually-defined
808+
parameters. So a library should be able to take a method defining individual parameters
809+
810+
```scala
811+
def downloadSimple(url: String,
812+
connectTimeout: Int,
813+
readTimeout: Int)
814+
```
815+
816+
And later, perhaps in the interest of code sharing, replace it with a method `unpack`ing a `
817+
case class:
818+
819+
```scala
820+
case class RequestConfig(url: String,
821+
connectTimeout: Int,
822+
readTimeout: Int)
823+
824+
def downloadSimple(unpack config: RequestConfig)
825+
```
826+
827+
And this should require no changes at any callsites, and should not break binary or tasty
828+
compatibility.
829+
830+
831+
### Default parameter values
832+
833+
As can be seen from some of the other examples in this proposal, `unpack` should include
834+
the default parameter values:
835+
836+
```scala
837+
case class RequestConfig(url: String,
838+
connectTimeout: Int = 10000,
839+
readTimeout: Int = 10000)
840+
841+
// These two methods definitions should be equivalent
842+
def downloadSimple(unpack config: RequestConfig)
843+
def downloadSimple(url: String,
844+
connectTimeout: Int = 10000,
845+
readTimeout: Int = 10000)
846+
```
847+
848+
Large flat parameter lists often contain default parameters, and usually the user would
849+
want the same default parameter across all use sites. So default parameters should be maintained
850+
when `unpack`ing a `case class` type into the enclosing parameter list.
851+
852+
### `@unroll` interaction
853+
854+
`@unroll` annotations on the parameters of a `case class` should be preserved when unpacking
855+
those parameters into a method `def`
856+
857+
```scala
858+
case class RequestConfig(url: String,
859+
@unroll connectTimeout: Int = 10000,
860+
@unroll readTimeout: Int = 10000)
861+
862+
// These two methods definitions should be equivalent
863+
def downloadSimple(unpack config: RequestConfig)
864+
def downloadSimple(url: String,
865+
@unroll connectTimeout: Int = 10000,
866+
@unroll readTimeout: Int = 10000)
867+
```
868+
869+
We expect that both `unpack` and `unroll` would be used together frequently: `unpack` to
870+
preserve consistency between different methods in the same version, `unroll` to preserve
871+
binary and tasty compatibility of the same method across different versions. The two goals
872+
are orthogonal and a library author can be expected to want both at the same time, and so
873+
`unpack` needs to preserve the semantics of `@unroll` on each individual unpacked parameter.
874+
875+
### Modifier handling
876+
877+
`case class` fields can have modifiers like `val`, `var`, `private`, etc. that are not allowed
878+
in method `def`s. `unpack` should preserve these modifiers if the enclosing parameter list
879+
belongs to a `class` or `case class`, and strip these modifiers if the enclosing parameter list
880+
belongs to a method `def`
881+
882+
```scala
883+
case class RequestConfig(var url: String,
884+
var connectTimeout: Int = 10000,
885+
readTimeout: Int = 10000)
886+
887+
// These two methods definitions should be equivalent
888+
def downloadSimple(unpack config: RequestConfig)
889+
def downloadSimple(url: String,
890+
connectTimeout: Int = 10000,
891+
readTimeout: Int = 10000)
892+
// These two class definitions should be equivalent
893+
class Foo(unpack config: RequestConfig)
894+
class Foo(var url: String,
895+
var connectTimeout: Int = 10000,
896+
readTimeout: Int = 10000)
897+
```
898+
805899

806900
## Prior Art
807901

@@ -919,4 +1013,25 @@ fun f() {
9191013
foo(1)
9201014
foo()
9211015
}
922-
```
1016+
```
1017+
1018+
## Future Work
1019+
1020+
### Support for tuples and named tuples
1021+
1022+
For this initial proposal, we limit `unpack` an `*` to only work on `case class`es. This is
1023+
enough for the most painful scenarios [discussed above](#applications), and matches the most
1024+
closely: a `case class` parameter list _exactly_ matches the structure of the enclosing parameter
1025+
list we `unpack`ing the `case class` into, and unpacking values via `*` is also straightforward.
1026+
However, we could potentially expand this to allow use of `unpack` an `*` on positional and
1027+
named tuples.
1028+
1029+
While `unpack`/`*` on `case class`es is most useful for library authors, `*` on tuples
1030+
and named tuples could be of great convenience in application code: method bodies often have
1031+
local data structures containing tuples or named tuples that get passed as to method calls
1032+
as parameters, and `*` could make this a lot more convenient. than having to write
1033+
`foo(tuple._1, tuple._2, tuple._3)` or similar today.
1034+
1035+
`unpack` could also be used to unpack a named tuple into a parameter list, which would
1036+
work identically to unpacking a `case class` type except a named tuple would not have
1037+
any default param values.

0 commit comments

Comments
 (0)