@@ -140,7 +140,7 @@ and use the `RequestConfig` wrapper.
140
140
Apart from the boilerplate, some things to note:
141
141
142
142
1 . The ` RequestConfig ` object is really just an implementation detail of ` download ` meant
143
- to shared parameters and args between the different ` download ` methods. From a user
143
+ to share parameters and args between the different ` download ` methods. From a user
144
144
perspective, the name is meaningless and the contents are arbitrary: someone calling
145
145
` downloadAsync ` would have to pass some params inside a ` RequestConfig ` , some parameters
146
146
outside ` RequestConfig ` , with no reason why some parameters should go in one place or another
@@ -149,14 +149,15 @@ Apart from the boilerplate, some things to note:
149
149
objects that the user has to construct to call your method, possibly nested. The user would
150
150
have to import several ` Config ` classes and instantiate a tree-shaped data structure just to
151
151
call these methods. But this tree-structure does not model anything the user cares about, but
152
- instead models the code-sharing relationships between the various ` def download ` methods
152
+ instead models the internal code-sharing relationships between the various ` def download ` methods
153
153
154
154
``` scala
155
155
case class RequestConfig (url : String ,
156
156
timeoutConfig : TimeoutConfig )
157
157
case class TimeoutConfig (connectTimeout : Int ,
158
158
readTimeout : Int )
159
159
case class AsyncConfig (retry : Boolean , ec : ExecutionContext )
160
+
160
161
def downloadSimple (config : RequestConfig ) = doSomethingWith(config)
161
162
def downloadAsync (config : RequestConfig , asyncConfig : AsyncConfig ) = doSomethingWith(config)
162
163
def downloadStream (config : RequestConfig , asyncConfig : AsyncConfig ) = doSomethingWith(config)
@@ -176,19 +177,25 @@ val stream = downloadStream(
176
177
)
177
178
```
178
179
179
- There are other more sophisticated ways that a library author can try to resolve this problem -
180
+ Forcing the user to construct this tree-shaped ` case class ` data structure is an abstraction leak:
181
+ the user has to write code matching the internal implementation details and code sharing of
182
+ the ` def download ` methods, and construct the corresponding ` case class ` tree, even though they
183
+ may really only care about calling a single ` downloadAsync ` method.
184
+
185
+ There are other more sophisticated ways that a library author can try to mitigate this -
180
186
e.g. builder patterns - but the fundamental problem is unsolvable today. ` unpack ` /` * ` solves
181
187
this neatly, allowing the library author to use ` unpack ` in their definition-site parameter lists
182
188
to share parameters between definitions, and the library user can either pass parameters
183
189
individually or unpack a configuration object via ` * ` , resulting in both the definition site
184
- and the call site being boilerplate-free, even in the more involved example above :
190
+ and the call site being boilerplate-free even in the more involved example below :
185
191
186
192
``` scala
187
193
case class RequestConfig (url : String ,
188
194
unpack timeoutConfig : TimeoutConfig )
189
195
case class TimeoutConfig (connectTimeout : Int ,
190
196
readTimeout : Int )
191
197
case class AsyncConfig (retry : Boolean , ec : ExecutionContext )
198
+
192
199
def downloadSimple (unpack config : RequestConfig ) = doSomethingWith(config)
193
200
def downloadAsync (unpack config : RequestConfig , unpack asyncConfig : AsyncConfig ) = doSomethingWith(config)
194
201
def downloadStream (unpack config : RequestConfig , unpack asyncConfig : AsyncConfig ) = doSomethingWith(config)
@@ -279,6 +286,7 @@ class Requester{
279
286
)
280
287
...
281
288
}
289
+
282
290
def stream (
283
291
url : String ,
284
292
auth : RequestAuth = sess.auth,
@@ -399,6 +407,7 @@ class Requester{
399
407
)
400
408
...
401
409
}
410
+
402
411
def stream (
403
412
unpack request : Request ,
404
413
chunkedUpload : Boolean = false ,
@@ -519,7 +528,9 @@ os.walk.attrs(path, preOrder = false, followLinks = true)
519
528
os.walk.stream(path, preOrder = false , followLinks = true )
520
529
```
521
530
522
- These are defined as
531
+ These are defined as shown below: each version of ` os.walk ` has a different return type, and
532
+ so needs to be a different method, but they share many parameters and default values, and
533
+ require a lot of boilerplate forwarding these internally:
523
534
524
535
``` scala
525
536
object walk {
@@ -540,6 +551,7 @@ object walk{
540
551
includeTarget
541
552
).toArray[Path ].toIndexedSeq
542
553
}
554
+
543
555
def attrs (
544
556
path : Path ,
545
557
skip : (Path , os.StatInfo ) => Boolean = (_, _) => false ,
@@ -559,6 +571,7 @@ object walk{
559
571
)
560
572
.toArray[(Path , os.StatInfo )].toIndexedSeq
561
573
}
574
+
562
575
object stream {
563
576
def apply (
564
577
path : Path ,
@@ -577,6 +590,7 @@ object walk{
577
590
includeTarget
578
591
).map(_._1)
579
592
}
593
+
580
594
def attrs (
581
595
path : Path ,
582
596
skip : (Path , os.StatInfo ) => Boolean = (_, _) => false ,
@@ -593,37 +607,44 @@ With `unpack`, this could be consolidated into
593
607
594
608
``` scala
595
609
object walk {
596
- case class Config (path : Path ,
597
- skip : Path => Boolean = _ => false ,
598
- preOrder : Boolean = true ,
599
- followLinks : Boolean = false ,
600
- maxDepth : Int = Int .MaxValue ,
601
- includeTarget : Boolean = false )
602
-
603
- def apply (unpack config : Config ): IndexedSeq [Path ] = {
610
+ case class Config [ SkipType ] (path : Path ,
611
+ skip : SkipType = _ => false ,
612
+ preOrder : Boolean = true ,
613
+ followLinks : Boolean = false ,
614
+ maxDepth : Int = Int .MaxValue ,
615
+ includeTarget : Boolean = false )
616
+
617
+ def apply (unpack config : Config [os. Path => Boolean ] ): IndexedSeq [Path ] = {
604
618
stream(config* ).toArray[Path ].toIndexedSeq
605
619
}
606
- def attrs (unpack config : Config ): IndexedSeq [(Path , os.StatInfo )] = {
620
+ def attrs (unpack config : Config [(os. Path , os. StatInfo ) => Boolean ] ): IndexedSeq [(Path , os.StatInfo )] = {
607
621
stream.attrs(config* )
608
622
.toArray[(Path , os.StatInfo )].toIndexedSeq
609
623
}
610
624
object stream {
611
- def apply (unpack config : Config ): Generator [Path ] = {
625
+ def apply (unpack config : Config [os. Path => Boolean ] ): Generator [Path ] = {
612
626
attrs(path, (p, _) => skip(p), preOrder, followLinks, maxDepth, includeTarget).map(_._1)
613
627
}
614
- def attrs (unpack config : Config ): Generator [(Path , os.StatInfo )] = ???
628
+ def attrs (unpack config : Config [(os. Path , os. StatInfo ) => Boolean ] ): Generator [(Path , os.StatInfo )] = ???
615
629
}
616
630
}
617
631
```
618
632
619
633
Things to note:
620
634
621
- 1 . A lot of these methods are forwarders/wrappers for each other, purely for convenience, and
635
+ 1 . The different ` def ` s can all share the same ` unpack config: Config ` parameter to share
636
+ the common parameters
637
+
638
+ 2 . The ` .attrs ` method take a ` Config[(os.Path, os.StatInfo) => Boolean] ` , while the
639
+ ` .apply ` methods take a ` Config[os.Path => Boolean] ` , as the shared parameters have some
640
+ subtle differences accounted for by the type parameter
641
+
642
+ 3 . A lot of these methods are forwarders/wrappers for each other, purely for convenience, and
622
643
` * ` can be used to forward the ` config ` object from the wrapper to the inner method
623
644
624
- 2 . Sometimes the parameter lists are subtly different, e.g. ` walk.stream.apply ` and
625
- ` walk.stream.attrs ` have a different type for ` skip ` . In such cases ` unpack ` cannot work
626
- and so the forwarding has to be done manually.
645
+ 4 . Sometimes the parameter lists are subtly different, e.g. ` walk.stream.apply ` and
646
+ ` walk.stream.attrs ` have a different type for ` skip ` . In such cases ` * ` at the call-site
647
+ cannot work and so the forwarding has to be done manually.
627
648
628
649
## Detailed Behavior
629
650
0 commit comments