Skip to content

Instantly share code, notes, and snippets.

@quelgar quelgar/LensURI.scala
Last active Dec 18, 2015

Embed
What would you like to do?
Using Lenses with java.net.URI.
import java.net.URI
/**
* State co-monad.
*
* `Store[F, _]` is the co-monad.
*/
final case class Store[F, R](get: F, set: F => R) {
def map[S](f: R => S): Store[F, S] = Store(get, f compose set)
/**
* Aka "coFlatMap" aka "extend".
*
* @param f
* @tparam S
* @return
*/
def =>>[S](f: Store[F, R] => S): Store[F, S] = Store(get, x => f(Store(x, set)))
/**
* Aka "coFlatten".
*
* @return
*/
def duplicate: Store[F, Store[F, R]] = this =>> identity
}
/**
* An asymmetric lens.
*
* @tparam R The record type.
* @tparam F The field type.
*/
final case class @>[R, F](store: R => Store[F, R]) {
/**
* Compose two lenses.
*/
def <=<[R2](g: R2 @> R): R2 @> F = @> {
q =>
val rr2Store: Store[R, R2] = g.store(q)
val frStore: Store[F, R] = this.store(rr2Store.get)
Store(get = frStore.get, set = rr2Store.set compose frStore.set)
}
/**
* Compose with arguments reversed, aka "andThen".
*/
def >=>[F2](g: F @> F2): R @> F2 = g <=< this
def modify(record: R)(mod: F => F): R = {
val s = store(record)
s.set(mod(s.get))
}
def get(record: R): F = store(record).get
def set(record: R, value: F): R = store(record).set(value)
/**
* Converts this lens to a partial lens that is always defined.
*/
def partial: R @>? F = @>?(r => Some(store(r)))
}
/**
* A partial lens.
*/
final case class @>?[T, C](store: T => Option[Store[C, T]]) {
/**
* Compose with arguments reversed, aka "andThen".
*/
def >=>[C2](other: C @>? C2): T @>? C2 = @>?(t => for {
c <- store(t)
d <- other store c.get
} yield Store(get = d.get, set = x => c set (d set x)))
def set(t: T, c: C): T = store(t) map (_.set(c)) getOrElse t
def get(t: T): Option[C] = store(t) map (_.get)
def modify(t: T)(mod: C => C): T = store(t) map (s => s.set(mod(s.get))) getOrElse t
def isDefined(t: T): Boolean = store(t).isDefined
}
object Lens {
def apply[R, F](getter: R => F, setter: F => R => R): R @> F = @>[R, F](r => Store(getter(r), setter(_)(r)))
}
object LensURI {
implicit final class RichURI(val uri: URI) extends AnyVal {
def scheme = Option(uri.getScheme)
def userInfo = Option(uri.getUserInfo)
def host = Option(uri.getHost)
def port = if (uri.getPort < 0) None else Some(uri.getPort)
def path = Option(uri.getPath)
def query = Option(uri.getQuery)
def fragment = Option(uri.getFragment)
def copy(scheme: Option[String] = scheme,
userInfo: Option[String] = userInfo,
host: Option[String] = host,
port: Option[Int] = port,
path: Option[String] = path,
query: Option[String] = query,
fragment: Option[String] = fragment): URI = new URI(
scheme.orNull,
userInfo.orNull,
host.orNull,
port getOrElse -1,
path.orNull,
query.orNull,
fragment.orNull
)
}
object lens {
val scheme: URI @> Option[String] = Lens(_.scheme, v => _.copy(scheme = v))
val userInfo: URI @> Option[String] = Lens(_.userInfo, v => _.copy(userInfo = v))
val host: URI @> Option[String] = Lens(_.host, v => _.copy(host = v))
val port: URI @> Option[Int] = Lens(_.port, v => _.copy(port = v))
val pathString: URI @> Option[String] = Lens(_.path, v => _.copy(path = v))
val queryString: URI @> Option[String] = Lens(_.query, v => _.copy(query = v))
val fragment: URI @> Option[String] = Lens(_.fragment, v => _.copy(fragment = v))
val emptyFragment: URI @> String = Lens(r => r.fragment getOrElse "", { v =>
val x = if (v.isEmpty) None else Some(v)
_.copy(fragment = x)
})
val query: URI @> Map[String, String] = Lens(
r => parse(r.query),
v => _.copy(query = toQueryString(v))
)
val pathList: URI @> List[String] = Lens(pathToList _,
v => r => r.copy(path = v match {
case Nil => None
case xs => Some(xs.reverse mkString "/")
})
)
}
def pathToList(uri: URI) = uri.path match {
case Some("") if uri.isAbsolute => List("")
case None | Some("") => Nil
case Some(s) if s forall (_ == '/') => List("")
case Some(s) => s.split('/').toList.reverse
}
def appendFooToPathJava(u: URI): URI = {
new URI(u.getScheme, u.getAuthority, (if (u.getPath eq null) "" else u.getPath) + "/foo", u.getQuery, u.getFragment)
}
def appendFooToPathCopy(u: URI): URI = u.copy(path = Some((u.path getOrElse "") + "/foo"))
def appendFooToPathLens(u: URI): URI = lens.pathList.modify(u)("foo" :: _)
private val regex = """([^=&]+)=([^&]+)&?""".r // demo purposes only!!
def parse(queryString: Option[String]): Map[String, String] = queryString map { q =>
(regex.findAllMatchIn(q) map (m => m.group(1) -> m.group(2))).toMap
} getOrElse Map.empty
def toQueryString(pairs: Iterable[(String, String)]) = if (pairs.isEmpty) None else Some(pairs map {
case (k, v) => s"$k=$v"
} mkString "&")
def mapLens[A, B](key: A): Map[A, B] @> Option[B] = Lens(_ get key, _ map (v => (_: Map[A, B]) + (key -> v)) getOrElse (_ - key))
def mapLensDefault[A, B](key: A, default: B): Map[A, B] @> B = Lens(
_.getOrElse(key, default),
v => if (v == default) (_ - key) else _ + (key -> v)
)
/**
* Java version of appending a query argument to an existing URI.
*
* I can't believe I used to program like this.
*/
def addQueryArgJava(u: URI): URI = {
val origQuery = if (u.getQuery eq null) "" else u.getQuery + '&'
new URI(u.getScheme, u.getAuthority, u.getPath, origQuery + "foo=bar", u.getFragment)
}
def addQueryArgCopy(u: URI): URI = {
u.copy(query = Some((u.query map (_ + '&') getOrElse "") + "foo=bar" ))
// or - u.copy(query = toQueryString(parse(u.query) + ("foo" -> "bar")))
}
def addQueryArgLensString(u: URI): URI = {
lens.queryString.modify(u)(x => Some((x map (_ + '&') getOrElse "") + "foo=bar"))
}
def addQueryArgLens(u: URI): URI = {
lens.query >=> mapLens("foo") set (u, Some("bar"))
}
/**
* Java version of removing query argument "foo", if it is present.
*/
def removeFooArgJava(u: URI): URI = {
val re = "foo=([^&]+)&?".r.pattern
val matcher = re.matcher(if (u.getQuery eq null) "" else u.getQuery)
val newQuery = matcher.replaceFirst("")
new URI(u.getScheme, u.getAuthority, u.getPath, if (newQuery.isEmpty) null else newQuery, u.getFragment)
}
def removeFooArgCopy(u: URI): URI = {
val re = "foo=([^&]+)&?".r
val newQuery = re.replaceFirstIn(u.query getOrElse "", "")
u.copy(query = if (newQuery.isEmpty) None else Some(newQuery))
}
def removeFooArgLens(u: URI): URI = {
lens.query >=> mapLens("foo") set (u, None)
}
def main(args: Array[String]) {
val uri1 = URI.create("http://www.site.fake/path/a/b?a=1&b=2&c=3")
assert(addQueryArgJava(uri1) == URI.create("http://www.site.fake/path/a/b?a=1&b=2&c=3&foo=bar"))
assert(addQueryArgCopy(uri1) == URI.create("http://www.site.fake/path/a/b?a=1&b=2&c=3&foo=bar"))
assert(addQueryArgLensString(uri1) == URI.create("http://www.site.fake/path/a/b?a=1&b=2&c=3&foo=bar"))
assert(addQueryArgLens(uri1) == URI.create("http://www.site.fake/path/a/b?a=1&b=2&c=3&foo=bar"))
val uri2 = URI.create("/path?a=1&foo=bar&b=2")
assert(removeFooArgJava(uri2) == URI.create("/path?a=1&b=2"))
assert(removeFooArgCopy(uri2) == URI.create("/path?a=1&b=2"))
assert(removeFooArgLens(uri2) == URI.create("/path?a=1&b=2"))
val uri3 = URI.create("http://www.google.com")
assert(removeFooArgJava(uri3) == uri3)
assert(removeFooArgCopy(uri3) == uri3)
assert(removeFooArgLens(uri3) == uri3)
println("Done!")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.