Created
November 14, 2012 22:23
-
-
Save channingwalton/4075285 to your computer and use it in GitHub Desktop.
Shapeless FunctionThisUp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package stereotypes | |
import shapeless._ | |
import Searchable._ | |
object FunctionThisUp { | |
// pretend we have some case classes - they needn't be ours of course | |
case class Name(name: String) | |
case class Description(description: String) | |
case class Alias(alias: String) | |
case class Stereotype(name: Name, description: Description, aliases: List[Alias]) | |
// just convenience for the mess I've created above | |
def mkStereotype(name: String, description: String, aliases: String*) = Stereotype(Name(name), Description(description), aliases.map(Alias(_)).toList) | |
// mix in the magic (compile time, not runtime) | |
implicit val stereoTypeIso = HListIso(Stereotype.apply _, Stereotype.unapply _) | |
implicit val nameIso = HListIso(Name.apply _, Name.unapply _) | |
implicit val descIso = HListIso(Description.apply _, Description.unapply _) | |
implicit val aliasIso = HListIso(Alias.apply _, Alias.unapply _) | |
// build the usual set | |
val australia = mkStereotype("australia", "a person from australia", "aussie", "legend") | |
val newzealand = mkStereotype("newzealand", "a person from new zealand", "kiwi") | |
val preston = mkStereotype("preston", "a person from preston", "scally") | |
val liverpool = mkStereotype("liverpool", "a person from liverpool", "scouser", "thief") | |
val manchester = mkStereotype("manchester", "a person from manchester", "manc", "mancunian", "fighter") | |
val all = australia :: newzealand :: preston :: liverpool :: manchester :: Nil | |
// use shapeless to filter the list by recursively searching for strings that match our search function: (_: String) equalsIgnoreCase name | |
// note that the type of the function determines what types get searched for. | |
def find(name: String) = filter(all.toList, (_: String) equalsIgnoreCase name).headOption map(Right(_)) getOrElse Left(exception(name)) | |
private def exception(name: String) = "Invalid Stereotype [" + name + "]. Must be one of [" + all + "]" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package stereotypes | |
/** | |
* Digging through arbitrarily nested case classes, tuples, and lists | |
* by Travis Brown | |
* | |
* In response to this question by Channing Walton on the Shapeless dev list: | |
* | |
* https://groups.google.com/d/msg/shapeless-dev/hn7_U21tupI/Zm9h3uNb51gJ | |
* | |
* Tested with Scala 2.9.2 and Shapeless 1.2.3. Should work on 1.2.2 with minor edits. | |
*/ | |
import shapeless._ | |
// Our type class: | |
trait Searchable[A, Q] { | |
def find(p: Q ⇒ Boolean)(a: A): Option[Q] | |
} | |
object Searchable { | |
// Our instances: | |
implicit def elemSearchable[A] = new Searchable[A, A] { | |
def find(p: A ⇒ Boolean)(a: A) = if (p(a)) Some(a) else None | |
} | |
implicit def listSearchable[A, Q](implicit s: Searchable[A, Q]) = | |
new Searchable[List[A], Q] { | |
def find(p: Q ⇒ Boolean)(a: List[A]) = a.flatMap(s.find(p)).headOption | |
} | |
implicit def hnilSearchable[Q] = new Searchable[HNil, Q] { | |
def find(p: Q ⇒ Boolean)(a: HNil) = None | |
} | |
implicit def hlistSearchable[H, T <: HList, Q]( | |
implicit hs: Searchable[H, Q] = null, ts: Searchable[T, Q]) = new Searchable[H :: T, Q] { | |
def find(p: Q ⇒ Boolean)(a: H :: T) = | |
Option(hs).flatMap(_.find(p)(a.head)) orElse ts.find(p)(a.tail) | |
} | |
implicit def hlistishSearchable[A, L <: HList, Q]( | |
implicit iso: HListIso[A, L], s: Searchable[L, Q]) = new Searchable[A, Q] { | |
def find(p: Q ⇒ Boolean)(a: A) = s.find(p)(iso.toHList(a)) | |
} | |
// A wrapper class and converter for convenience: | |
case class SearchableWrapper[A](a: A) { | |
def deepFind[Q](p: Q ⇒ Boolean)(implicit s: Searchable[A, Q]) = | |
s.find(p)(a) | |
} | |
implicit def wrapSearchable[A](a: A) = SearchableWrapper(a) | |
def filter[A, Q](a: List[A], f: Q => Boolean)(implicit s: Searchable[A, Q]) = a.filter(_.deepFind(f).isDefined) | |
} | |
object SearchableExample { | |
import Searchable._ | |
// An example predicate: | |
val p = (_: String) endsWith "o" | |
// On strings: | |
"hello".deepFind(p) == Some("hello") | |
"hell".deepFind(p) == None | |
// On lists: | |
List("yes", "maybe", "no").deepFind(p) == Some("no") | |
// On arbitrarily sized and nested tuples: | |
("yes", "maybe", ("no", "why")).deepFind(p) == Some("no") | |
("a", ("b", "c"), "d").deepFind(p) == None | |
// On tuples with non-string elements: | |
(1, "two", ('three, '4')).deepFind(p) == Some("two") | |
// Search the same tuple for a specific character instead: | |
(1, "two", ('three, '4')).deepFind((_: Char) == 52) == Some('4') | |
// Our case class: | |
case class Foo(a: String, b: String, c: List[String]) | |
// Plus one line of boilerplate: | |
implicit def fooIso = HListIso(Foo.apply _, Foo.unapply _) | |
// And it works: | |
Foo("four", "three", List("two", "one")).deepFind(p) == Some("two") | |
Foo("a", "b", "c" :: Nil).deepFind(p) == None | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment