Skip to content

Instantly share code, notes, and snippets.

@quelgar
Last active December 18, 2015 17:39
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save quelgar/5819633 to your computer and use it in GitHub Desktop.
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