Skip to content

Reproduce Scala bug https://github.com/scala/bug/issues/10448 #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sbtopts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-J-Xms256M
-J-Xmx1024M
-J-Xss2M
-J-XX:MaxMetaspaceSize=512M
-J-XX:MaxMetaspaceSize=1024M
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.fu
val monix = "io.monix" %% "monix" % "3.0.0-M3"
val googleMaps = "com.google.maps" % "google-maps-services" % "0.2.6"
val squants = "org.typelevel" %% "squants" % "1.3.0"
val cats = "org.typelevel" %% "cats-core" % "1.0.1"
val enumeratum = "com.beachape" %% "enumeratum" % "1.5.12"

val scalacache = ((version: String) =>
Seq(
Expand All @@ -113,6 +115,8 @@ val testKit = Seq(
libraryDependencies ++= Seq(
monix % Provided,
squants % Provided,
cats,
enumeratum,
googleMaps
) ++ scalacache ++ testKit.map(_ % Test)

Expand Down
31 changes: 21 additions & 10 deletions src/main/scala/com/guizmaii/distances/DistanceApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,34 @@ import com.guizmaii.distances.utils.WithCache
import monix.eval.Task
import monix.execution.CancelableFuture

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

trait DistanceApi extends WithCache[SerializableDistance] {
def distanceT(
origin: LatLong,
destination: LatLong,
travelMode: List[TravelMode] = List(TravelMode.Driving)
): Task[Map[TravelMode, Distance]]

def distanceT(origin: LatLong, destination: LatLong): Task[Distance]
def distance(
origin: LatLong,
destination: LatLong,
travelMode: List[TravelMode] = List(TravelMode.Driving)
): CancelableFuture[Map[TravelMode, Distance]]

def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance]

def distanceFromPostalCodesT(geocoder: Geocoder)(origin: PostalCode, destination: PostalCode): Task[Distance]
def distanceFromPostalCodesT(geocoder: Geocoder)(
origin: PostalCode,
destination: PostalCode,
travelMode: List[TravelMode] = List(TravelMode.Driving)
): Task[Map[TravelMode, Distance]]

def distanceFromPostalCodes(geocoder: Geocoder)(
origin: PostalCode,
destination: PostalCode
): CancelableFuture[Distance]
destination: PostalCode,
travelMode: List[TravelMode] = List(TravelMode.Driving)
): CancelableFuture[Map[TravelMode, Distance]]

def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]]
def distancesT(paths: List[DirectedPath]): Task[Map[(TravelMode, LatLong, LatLong), Distance]]

def distances(paths: Seq[DirectedPath]): CancelableFuture[Seq[DirectedPathWithDistance]]
def distances(paths: List[DirectedPath]): CancelableFuture[Map[(TravelMode, LatLong, LatLong), Distance]]

}
49 changes: 46 additions & 3 deletions src/main/scala/com/guizmaii/distances/Types.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.guizmaii.distances

import com.google.maps.model.{LatLng => GoogleLatLng, TravelMode => GoogleTravelMode}
import enumeratum.{Enum, EnumEntry}
import squants.space.Length
import squants.space.LengthConversions._

import scala.collection.immutable
import scala.collection.immutable.Seq
import scala.concurrent.duration._

object Types {
Expand All @@ -12,7 +16,9 @@ object Types {

final case class PostalCode(value: String) extends AnyVal

final case class LatLong(latitude: Double, longitude: Double)
final case class LatLong(latitude: Double, longitude: Double) {
private[distances] def toGoogleLatLng: GoogleLatLng = new GoogleLatLng(latitude, longitude)
}

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

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

type DirectedPath = (LatLong, LatLong)
type DirectedPathWithDistance = (LatLong, LatLong, Distance)
final case class DirectedPath(origin: LatLong, destination: LatLong, travelModes: List[TravelMode] = List(TravelMode.Driving))

sealed trait TravelMode extends EnumEntry
object TravelMode extends Enum[TravelMode] {

val values: immutable.IndexedSeq[TravelMode] = findValues

case object Driving extends TravelMode
case object Bicycling extends TravelMode
case object Unknown extends TravelMode

implicit final class RichTravelMode(val travelMode: TravelMode) extends AnyVal {
def toGoogleTravelMode: GoogleTravelMode =
travelMode match {
case Driving => GoogleTravelMode.DRIVING
case Bicycling => GoogleTravelMode.BICYCLING
case Unknown => GoogleTravelMode.UNKNOWN
}
}

implicit final class RichGoogleTravelMode(val travelMode: GoogleTravelMode) extends AnyVal {

/**
* For now, I don't want to handle WALKING and TRANSIT.
*
* @return
*/
def fromGoogleTravelMode: TravelMode = {
import GoogleTravelMode._

travelMode match {
case DRIVING => Driving
case BICYCLING => Bicycling
case UNKNOWN | WALKING | TRANSIT => Unknown
}
}
}

}

}
Original file line number Diff line number Diff line change
@@ -1,73 +1,139 @@
package com.guizmaii.distances.implementations.google.distanceapi

import com.google.maps.DistanceMatrixApi
import cats._
import cats.data._
import cats.implicits._
import com.google.maps.DirectionsApi.RouteRestriction
import com.google.maps.{DistanceMatrixApi, DistanceMatrixApiRequest}
import com.google.maps.model.{TrafficModel, TransitMode, Unit => GoogleDistanceUnit}
import com.guizmaii.distances.Types._
import com.guizmaii.distances.implementations.cache.GeoCache
import com.guizmaii.distances.implementations.google.GoogleGeoApiContext
import com.guizmaii.distances.{DistanceApi, Geocoder}
import monix.eval.Task
import monix.execution.CancelableFuture

import scala.collection.immutable.Seq

final class GoogleDistanceApi(
geoApiContext: GoogleGeoApiContext,
override protected val alternativeCache: Option[GeoCache[SerializableDistance]] = None
override protected val alternativeCache: Option[GeoCache[(TravelMode, SerializableDistance)]] = None
) extends DistanceApi {

import com.guizmaii.distances.utils.MonixSchedulers.AlwaysAsyncForkJoinScheduler._
import com.guizmaii.distances.utils.RichImplicits._

private def toGoogleRepresentation(latLong: LatLong): String = s"${latLong.latitude},${latLong.longitude}"
//
//override def distanceFromPostalCodesT(geocoder: Geocoder)(
// origin: PostalCode,
// destination: PostalCode
//): Task[Distance] =
// if (origin == destination) Task.now(Distance.zero)
// else Task.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination)).flatMap((distanceT _).tupled)
//
//override def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance] =
// distanceT(origin, destination).runAsync
//
//override def distanceFromPostalCodes(geocoder: Geocoder)(
// origin: PostalCode,
// destination: PostalCode
//): CancelableFuture[Distance] = distanceFromPostalCodesT(geocoder)(origin, destination).runAsync
//
//override def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]] = Task.sequence {
// paths.map {
// case (origin, destination) =>
// if (origin == destination) Task.now((origin, destination, Distance.zero))
// else distanceT(origin, destination).map(distance => (origin, destination, distance))
// }
//}
//
//override def distances(paths: Seq[DirectedPath]): CancelableFuture[Seq[DirectedPathWithDistance]] =
// distancesT(paths).runAsync

import TravelMode._

override def distanceT(origin: LatLong, destination: LatLong): Task[Distance] = {
def fetch: Task[SerializableDistance] =
override def distanceT(
origin: LatLong,
destination: LatLong,
travelModes: List[TravelMode] = List(TravelMode.Driving)
): Task[Map[TravelMode, Distance]] = {
def fetch(mode: TravelMode): Task[(TravelMode, SerializableDistance)] =
DistanceMatrixApi
.getDistanceMatrix(
geoApiContext.geoApiContext,
Array(toGoogleRepresentation(origin)),
Array(toGoogleRepresentation(destination))
)
.newRequest(geoApiContext.geoApiContext)
.mode(mode.toGoogleTravelMode)
.origins(origin.toGoogleLatLng)
.destinations(destination.toGoogleLatLng)
.units(GoogleDistanceUnit.METRIC)
.toTask
.map(_.rows.head.elements.head.asSerializableDistance)
.map(res => mode -> res.rows.head.elements.head.asSerializableDistance)

def fetchAndCache(mode: TravelMode): Task[(TravelMode, Distance)] = {
val key = (mode, origin, destination)
cache.getOrTask(key)(fetch(mode)).map { case (m, serializableDistance) => m -> Distance.apply(serializableDistance) }
}

val key = origin -> destination
cache
.getOrTask(key)(fetch)
.map(Distance.apply)
if (origin == destination) Task.now(travelModes.map(_ -> Distance.zero).toMap)
else travelModes.map(fetchAndCache).sequence.map(_.toMap)
}

override def distance(
origin: LatLong,
destination: LatLong,
travelModes: List[TravelMode] = List(TravelMode.Driving)
): CancelableFuture[Map[TravelMode, Distance]] = distanceT(origin, destination, travelModes).runAsync

override def distanceFromPostalCodesT(geocoder: Geocoder)(
origin: PostalCode,
destination: PostalCode
): Task[Distance] =
if (origin == destination) Task.now(Distance.zero)
else Task.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination)).flatMap((distanceT _).tupled)

override def distance(origin: LatLong, destination: LatLong): CancelableFuture[Distance] =
distanceT(origin, destination).runAsync
destination: PostalCode,
travelModes: List[TravelMode] = List(TravelMode.Driving)
): Task[Map[TravelMode, Distance]] = {
if (origin == destination) Task.now(travelModes.map(_ -> Distance.zero).toMap)
else
Task
.zip2(geocoder.geocodeT(origin), geocoder.geocodeT(destination))
.flatMap { case (o, d) => distanceT(o, d, travelModes) }
}

override def distanceFromPostalCodes(geocoder: Geocoder)(
origin: PostalCode,
destination: PostalCode
): CancelableFuture[Distance] = distanceFromPostalCodesT(geocoder)(origin, destination).runAsync

override def distancesT(paths: Seq[DirectedPath]): Task[Seq[DirectedPathWithDistance]] = Task.sequence {
paths.map {
case (origin, destination) =>
if (origin == destination) Task.now((origin, destination, Distance.zero))
else distanceT(origin, destination).map(distance => (origin, destination, distance))
destination: PostalCode,
travelModes: List[TravelMode] = List(TravelMode.Driving)
): CancelableFuture[Map[TravelMode, Distance]] =
distanceFromPostalCodesT(geocoder)(origin, destination, travelModes).runAsync

override def distancesT(paths: List[DirectedPath]): Task[Map[(TravelMode, LatLong, LatLong), Distance]] = {
//def fetch(mode: TravelMode): Task[(TravelMode, SerializableDistance)] =
// DistanceMatrixApi
// .newRequest(geoApiContext.geoApiContext)
// .mode(mode.toGoogleTravelMode)
// .origins(origin.toGoogleLatLng)
// .destinations(destination.toGoogleLatLng)
// .units(GoogleDistanceUnit.METRIC)
// .toTask
// .map(res => mode -> res.rows.head.elements.head.asSerializableDistance)

def fetchAndCache(mode: TravelMode): Task[(TravelMode, Distance)] = {
val key = (mode, origin, destination)
cache.getOrTask(key)(fetch(mode)).map { case (m, serializableDistance) => m -> Distance.apply(serializableDistance) }
}

paths
.distinctBy { case (origin, destination, t) => DirectedPath(origin, destination, t) }
.map {
case DirectedPath(origin, destination, travelModes) =>
if (origin == destination) Task.now(travelModes.map(mode => (mode, origin, destination) -> Distance.zero).toMap)
else {}
}

???

}

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

}

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

def apply(geoApiContext: GoogleGeoApiContext, geoCache: GeoCache[SerializableDistance]): GoogleDistanceApi =
def apply(geoApiContext: GoogleGeoApiContext, geoCache: GeoCache[(TravelMode, SerializableDistance)]): GoogleDistanceApi =
new GoogleDistanceApi(geoApiContext, Some(geoCache))
}
32 changes: 28 additions & 4 deletions src/main/scala/com/guizmaii/distances/utils/RichImplicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import com.google.maps.model.{DistanceMatrixElement, LatLng}
import com.guizmaii.distances.Types.{LatLong, SerializableDistance}
import monix.eval.Task

import scala.collection.{IterableLike, TraversableLike}
import scala.collection.generic.CanBuildFrom
import scala.concurrent.Promise

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

implicit final class RichInnerLatLng(val latLong: LatLong) extends AnyVal {
def toGoogleLatLong: LatLng = new LatLng(latLong.latitude, latLong.longitude)
}

private[this] final class CallBack[T](promise: Promise[T]) extends PendingResult.Callback[T] {
override def onResult(t: T): Unit = {
val _ = promise.success(t)
Expand All @@ -39,4 +37,30 @@ private[distances] object RichImplicits {
SerializableDistance(value = element.distance.inMeters.toDouble, duration = element.duration.inSeconds.toDouble)
}

implicit final class RichCollection[A, Repr](val xs: IterableLike[A, Repr]) extends AnyVal {

/**
* Come from here: https://stackoverflow.com/a/34456552/2431728
*
* @param f
* @param cbf
* @tparam B
* @return
*/
def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[Repr, A, That]): That = {
val builder = cbf(xs.repr)
val i = xs.toIterator
var set = Set[B]()
while (i.hasNext) {
val o = i.next
val b = f(o)
if (!set(b)) {
set += b
builder += o
}
}
builder.result
}
}

}