Skip to content

Commit 0272901

Browse files
committed
Reproduce Scala bug scala/bug#10448
1 parent 4350d8f commit 0272901

File tree

6 files changed

+201
-53
lines changed

6 files changed

+201
-53
lines changed

.sbtopts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
-J-Xms256M
22
-J-Xmx1024M
33
-J-Xss2M
4-
-J-XX:MaxMetaspaceSize=512M
4+
-J-XX:MaxMetaspaceSize=1024M

build.sbt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.fu
9696
val monix = "io.monix" %% "monix" % "3.0.0-M3"
9797
val googleMaps = "com.google.maps" % "google-maps-services" % "0.2.6"
9898
val squants = "org.typelevel" %% "squants" % "1.3.0"
99+
val cats = "org.typelevel" %% "cats-core" % "1.0.1"
100+
val enumeratum = "com.beachape" %% "enumeratum" % "1.5.12"
99101

100102
val scalacache = ((version: String) =>
101103
Seq(
@@ -113,6 +115,8 @@ val testKit = Seq(
113115
libraryDependencies ++= Seq(
114116
monix % Provided,
115117
squants % Provided,
118+
cats,
119+
enumeratum,
116120
googleMaps
117121
) ++ scalacache ++ testKit.map(_ % Test)
118122

src/main/scala/com/guizmaii/distances/DistanceApi.scala

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,34 @@ import com.guizmaii.distances.utils.WithCache
55
import monix.eval.Task
66
import monix.execution.CancelableFuture
77

8-
import scala.collection.immutable.Seq
8+
trait DistanceApi extends WithCache[(TravelMode, SerializableDistance)] {
99

10-
trait DistanceApi extends WithCache[SerializableDistance] {
10+
def distanceT(
11+
origin: LatLong,
12+
destination: LatLong,
13+
travelMode: List[TravelMode] = List(TravelMode.Driving)
14+
): Task[Map[TravelMode, Distance]]
1115

12-
def distanceT(origin: LatLong, destination: LatLong): Task[Distance]
16+
def distance(
17+
origin: LatLong,
18+
destination: LatLong,
19+
travelMode: List[TravelMode] = List(TravelMode.Driving)
20+
): CancelableFuture[Map[TravelMode, Distance]]
1321

14-
def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance]
15-
16-
def distanceFromPostalCodesT(geocoder: Geocoder)(origin: PostalCode, destination: PostalCode): Task[Distance]
22+
def distanceFromPostalCodesT(geocoder: Geocoder)(
23+
origin: PostalCode,
24+
destination: PostalCode,
25+
travelMode: List[TravelMode] = List(TravelMode.Driving)
26+
): Task[Map[TravelMode, Distance]]
1727

1828
def distanceFromPostalCodes(geocoder: Geocoder)(
1929
origin: PostalCode,
20-
destination: PostalCode
21-
): CancelableFuture[Distance]
30+
destination: PostalCode,
31+
travelMode: List[TravelMode] = List(TravelMode.Driving)
32+
): CancelableFuture[Map[TravelMode, Distance]]
2233

23-
def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]]
34+
def distancesT(paths: List[DirectedPath]): Task[Map[(TravelMode, LatLong, LatLong), Distance]]
2435

25-
def distances(paths: Seq[DirectedPath]): CancelableFuture[Seq[DirectedPathWithDistance]]
36+
def distances(paths: List[DirectedPath]): CancelableFuture[Map[(TravelMode, LatLong, LatLong), Distance]]
2637

2738
}

src/main/scala/com/guizmaii/distances/Types.scala

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.guizmaii.distances
22

3+
import com.google.maps.model.{LatLng => GoogleLatLng, TravelMode => GoogleTravelMode}
4+
import enumeratum.{Enum, EnumEntry}
35
import squants.space.Length
46
import squants.space.LengthConversions._
57

8+
import scala.collection.immutable
9+
import scala.collection.immutable.Seq
610
import scala.concurrent.duration._
711

812
object Types {
@@ -12,7 +16,9 @@ object Types {
1216

1317
final case class PostalCode(value: String) extends AnyVal
1418

15-
final case class LatLong(latitude: Double, longitude: Double)
19+
final case class LatLong(latitude: Double, longitude: Double) {
20+
private[distances] def toGoogleLatLng: GoogleLatLng = new GoogleLatLng(latitude, longitude)
21+
}
1622

1723
final case class Distance(length: Length, duration: Duration)
1824

@@ -24,7 +30,44 @@ object Types {
2430
final lazy val Inf: Distance = Distance(Double.PositiveInfinity meters, Duration.Inf)
2531
}
2632

27-
type DirectedPath = (LatLong, LatLong)
28-
type DirectedPathWithDistance = (LatLong, LatLong, Distance)
33+
final case class DirectedPath(origin: LatLong, destination: LatLong, travelModes: List[TravelMode] = List(TravelMode.Driving))
34+
35+
sealed trait TravelMode extends EnumEntry
36+
object TravelMode extends Enum[TravelMode] {
37+
38+
val values: immutable.IndexedSeq[TravelMode] = findValues
39+
40+
case object Driving extends TravelMode
41+
case object Bicycling extends TravelMode
42+
case object Unknown extends TravelMode
43+
44+
implicit final class RichTravelMode(val travelMode: TravelMode) extends AnyVal {
45+
def toGoogleTravelMode: GoogleTravelMode =
46+
travelMode match {
47+
case Driving => GoogleTravelMode.DRIVING
48+
case Bicycling => GoogleTravelMode.BICYCLING
49+
case Unknown => GoogleTravelMode.UNKNOWN
50+
}
51+
}
52+
53+
implicit final class RichGoogleTravelMode(val travelMode: GoogleTravelMode) extends AnyVal {
54+
55+
/**
56+
* For now, I don't want to handle WALKING and TRANSIT.
57+
*
58+
* @return
59+
*/
60+
def fromGoogleTravelMode: TravelMode = {
61+
import GoogleTravelMode._
62+
63+
travelMode match {
64+
case DRIVING => Driving
65+
case BICYCLING => Bicycling
66+
case UNKNOWN | WALKING | TRANSIT => Unknown
67+
}
68+
}
69+
}
70+
71+
}
2972

3073
}
Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,139 @@
11
package com.guizmaii.distances.implementations.google.distanceapi
22

3-
import com.google.maps.DistanceMatrixApi
3+
import cats._
4+
import cats.data._
5+
import cats.implicits._
6+
import com.google.maps.DirectionsApi.RouteRestriction
7+
import com.google.maps.{DistanceMatrixApi, DistanceMatrixApiRequest}
8+
import com.google.maps.model.{TrafficModel, TransitMode, Unit => GoogleDistanceUnit}
49
import com.guizmaii.distances.Types._
510
import com.guizmaii.distances.implementations.cache.GeoCache
611
import com.guizmaii.distances.implementations.google.GoogleGeoApiContext
712
import com.guizmaii.distances.{DistanceApi, Geocoder}
813
import monix.eval.Task
914
import monix.execution.CancelableFuture
1015

11-
import scala.collection.immutable.Seq
12-
1316
final class GoogleDistanceApi(
1417
geoApiContext: GoogleGeoApiContext,
15-
override protected val alternativeCache: Option[GeoCache[SerializableDistance]] = None
18+
override protected val alternativeCache: Option[GeoCache[(TravelMode, SerializableDistance)]] = None
1619
) extends DistanceApi {
1720

1821
import com.guizmaii.distances.utils.MonixSchedulers.AlwaysAsyncForkJoinScheduler._
1922
import com.guizmaii.distances.utils.RichImplicits._
2023

21-
private def toGoogleRepresentation(latLong: LatLong): String = s"${latLong.latitude},${latLong.longitude}"
24+
//
25+
//override def distanceFromPostalCodesT(geocoder: Geocoder)(
26+
// origin: PostalCode,
27+
// destination: PostalCode
28+
//): Task[Distance] =
29+
// if (origin == destination) Task.now(Distance.zero)
30+
// else Task.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination)).flatMap((distanceT _).tupled)
31+
//
32+
//override def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance] =
33+
// distanceT(origin, destination).runAsync
34+
//
35+
//override def distanceFromPostalCodes(geocoder: Geocoder)(
36+
// origin: PostalCode,
37+
// destination: PostalCode
38+
//): CancelableFuture[Distance] = distanceFromPostalCodesT(geocoder)(origin, destination).runAsync
39+
//
40+
//override def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]] = Task.sequence {
41+
// paths.map {
42+
// case (origin, destination) =>
43+
// if (origin == destination) Task.now((origin, destination, Distance.zero))
44+
// else distanceT(origin, destination).map(distance => (origin, destination, distance))
45+
// }
46+
//}
47+
//
48+
//override def distances(paths: Seq[DirectedPath]): CancelableFuture[Seq[DirectedPathWithDistance]] =
49+
// distancesT(paths).runAsync
50+
51+
import TravelMode._
2252

23-
override def distanceT(origin: LatLong, destination: LatLong): Task[Distance] = {
24-
def fetch: Task[SerializableDistance] =
53+
override def distanceT(
54+
origin: LatLong,
55+
destination: LatLong,
56+
travelModes: List[TravelMode] = List(TravelMode.Driving)
57+
): Task[Map[TravelMode, Distance]] = {
58+
def fetch(mode: TravelMode): Task[(TravelMode, SerializableDistance)] =
2559
DistanceMatrixApi
26-
.getDistanceMatrix(
27-
geoApiContext.geoApiContext,
28-
Array(toGoogleRepresentation(origin)),
29-
Array(toGoogleRepresentation(destination))
30-
)
60+
.newRequest(geoApiContext.geoApiContext)
61+
.mode(mode.toGoogleTravelMode)
62+
.origins(origin.toGoogleLatLng)
63+
.destinations(destination.toGoogleLatLng)
64+
.units(GoogleDistanceUnit.METRIC)
3165
.toTask
32-
.map(_.rows.head.elements.head.asSerializableDistance)
66+
.map(res => mode -> res.rows.head.elements.head.asSerializableDistance)
67+
68+
def fetchAndCache(mode: TravelMode): Task[(TravelMode, Distance)] = {
69+
val key = (mode, origin, destination)
70+
cache.getOrTask(key)(fetch(mode)).map { case (m, serializableDistance) => m -> Distance.apply(serializableDistance) }
71+
}
3372

34-
val key = origin -> destination
35-
cache
36-
.getOrTask(key)(fetch)
37-
.map(Distance.apply)
73+
if (origin == destination) Task.now(travelModes.map(_ -> Distance.zero).toMap)
74+
else travelModes.map(fetchAndCache).sequence.map(_.toMap)
3875
}
3976

77+
override def distance(
78+
origin: LatLong,
79+
destination: LatLong,
80+
travelModes: List[TravelMode] = List(TravelMode.Driving)
81+
): CancelableFuture[Map[TravelMode, Distance]] = distanceT(origin, destination, travelModes).runAsync
82+
4083
override def distanceFromPostalCodesT(geocoder: Geocoder)(
4184
origin: PostalCode,
42-
destination: PostalCode
43-
): Task[Distance] =
44-
if (origin == destination) Task.now(Distance.zero)
45-
else Task.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination)).flatMap((distanceT _).tupled)
46-
47-
override def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance] =
48-
distanceT(origin, destination).runAsync
85+
destination: PostalCode,
86+
travelModes: List[TravelMode] = List(TravelMode.Driving)
87+
): Task[Map[TravelMode, Distance]] = {
88+
if (origin == destination) Task.now(travelModes.map(_ -> Distance.zero).toMap)
89+
else
90+
Task
91+
.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination))
92+
.flatMap { case (o, d) => distanceT(o, d, travelModes) }
93+
}
4994

5095
override def distanceFromPostalCodes(geocoder: Geocoder)(
5196
origin: PostalCode,
52-
destination: PostalCode
53-
): CancelableFuture[Distance] = distanceFromPostalCodesT(geocoder)(origin, destination).runAsync
54-
55-
override def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]] = Task.sequence {
56-
paths.map {
57-
case (origin, destination) =>
58-
if (origin == destination) Task.now((origin, destination, Distance.zero))
59-
else distanceT(origin, destination).map(distance => (origin, destination, distance))
97+
destination: PostalCode,
98+
travelModes: List[TravelMode] = List(TravelMode.Driving)
99+
): CancelableFuture[Map[TravelMode, Distance]] =
100+
distanceFromPostalCodesT(geocoder)(origin, destination, travelModes).runAsync
101+
102+
override def distancesT(paths: List[DirectedPath]): Task[Map[(TravelMode, LatLong, LatLong), Distance]] = {
103+
//def fetch(mode: TravelMode): Task[(TravelMode, SerializableDistance)] =
104+
// DistanceMatrixApi
105+
// .newRequest(geoApiContext.geoApiContext)
106+
// .mode(mode.toGoogleTravelMode)
107+
// .origins(origin.toGoogleLatLng)
108+
// .destinations(destination.toGoogleLatLng)
109+
// .units(GoogleDistanceUnit.METRIC)
110+
// .toTask
111+
// .map(res => mode -> res.rows.head.elements.head.asSerializableDistance)
112+
113+
def fetchAndCache(mode: TravelMode): Task[(TravelMode, Distance)] = {
114+
val key = (mode, origin, destination)
115+
cache.getOrTask(key)(fetch(mode)).map { case (m, serializableDistance) => m -> Distance.apply(serializableDistance) }
60116
}
117+
118+
paths
119+
.distinctBy { case (origin, destination, t) => DirectedPath(origin, destination, t) }
120+
.map {
121+
case DirectedPath(origin, destination, travelModes) =>
122+
if (origin == destination) Task.now(travelModes.map(mode => (mode, origin, destination) -> Distance.zero).toMap)
123+
else {}
124+
}
125+
126+
???
127+
61128
}
62129

63-
override def distances(paths: Seq[DirectedPath]): CancelableFuture[Seq[DirectedPathWithDistance]] =
64-
distancesT(paths).runAsync
130+
override def distances(paths: List[DirectedPath]): CancelableFuture[Map[(TravelMode, LatLong, LatLong), Distance]] = ???
65131

66132
}
67133

68134
object GoogleDistanceApi {
69135
def apply(geoApiContext: GoogleGeoApiContext): GoogleDistanceApi = new GoogleDistanceApi(geoApiContext)
70136

71-
def apply(geoApiContext: GoogleGeoApiContext, geoCache: GeoCache[SerializableDistance]): GoogleDistanceApi =
137+
def apply(geoApiContext: GoogleGeoApiContext, geoCache: GeoCache[(TravelMode, SerializableDistance)]): GoogleDistanceApi =
72138
new GoogleDistanceApi(geoApiContext, Some(geoCache))
73139
}

src/main/scala/com/guizmaii/distances/utils/RichImplicits.scala

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ import com.google.maps.model.{DistanceMatrixElement, LatLng}
55
import com.guizmaii.distances.Types.{LatLong, SerializableDistance}
66
import monix.eval.Task
77

8+
import scala.collection.{IterableLike, TraversableLike}
9+
import scala.collection.generic.CanBuildFrom
810
import scala.concurrent.Promise
911

1012
private[distances] object RichImplicits {
1113
implicit final class RichGoogleLatLng(val latLng: LatLng) extends AnyVal {
1214
def toInnerLatLong: LatLong = LatLong(latitude = latLng.lat, longitude = latLng.lng)
1315
}
1416

15-
implicit final class RichInnerLatLng(val latLong: LatLong) extends AnyVal {
16-
def toGoogleLatLong: LatLng = new LatLng(latLong.latitude, latLong.longitude)
17-
}
18-
1917
private[this] final class CallBack[T](promise: Promise[T]) extends PendingResult.Callback[T] {
2018
override def onResult(t: T): Unit = {
2119
val _ = promise.success(t)
@@ -39,4 +37,30 @@ private[distances] object RichImplicits {
3937
SerializableDistance(value = element.distance.inMeters.toDouble, duration = element.duration.inSeconds.toDouble)
4038
}
4139

40+
implicit final class RichCollection[A, Repr](val xs: IterableLike[A, Repr]) extends AnyVal {
41+
42+
/**
43+
* Come from here: https://stackoverflow.com/a/34456552/2431728
44+
*
45+
* @param f
46+
* @param cbf
47+
* @tparam B
48+
* @return
49+
*/
50+
def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[Repr, A, That]): That = {
51+
val builder = cbf(xs.repr)
52+
val i = xs.toIterator
53+
var set = Set[B]()
54+
while (i.hasNext) {
55+
val o = i.next
56+
val b = f(o)
57+
if (!set(b)) {
58+
set += b
59+
builder += o
60+
}
61+
}
62+
builder.result
63+
}
64+
}
65+
4266
}

0 commit comments

Comments
 (0)