Skip to content

Instantly share code, notes, and snippets.

@channingwalton
Created November 14, 2012 22:23
Show Gist options
  • Save channingwalton/4075285 to your computer and use it in GitHub Desktop.
Save channingwalton/4075285 to your computer and use it in GitHub Desktop.
Shapeless FunctionThisUp
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 + "]"
}
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