Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Addressing questions one to three: one of the main applications for <code>HLists</code> is abstracting over arity. Arity is typically statically known at any given use site of an abstraction, but varies from site to site. Take this, from shapeless's <a href="https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/flatten.scala#L70" rel="noreferrer">examples</a>,</p> <pre><code>def flatten[T &lt;: Product, L &lt;: HList](t : T) (implicit hl : HListerAux[T, L], flatten : Flatten[L]) : flatten.Out = flatten(hl(t)) val t1 = (1, ((2, 3), 4)) val f1 = flatten(t1) // Inferred type is Int :: Int :: Int :: Int :: HNil val l1 = f1.toList // Inferred type is List[Int] val t2 = (23, ((true, 2.0, "foo"), "bar"), (13, false)) val f2 = flatten(t2) val t2b = f2.tupled // Inferred type of t2b is (Int, Boolean, Double, String, String, Int, Boolean) </code></pre> <p>Without using <code>HLists</code> (or something equivalent) to abstract over the arity of the tuple arguments to <code>flatten</code> it would be impossible to have a single implementation which could accept arguments of these two very different shapes and transform them in a type safe way.</p> <p>The ability to abstract over arity is likely to be of interest anywhere that fixed arities are involved: as well as tuples, as above, that includes method/function parameter lists, and case classes. See <a href="https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/monoids.scala" rel="noreferrer">here</a> for examples of how we might abstract over the arity of arbitrary case classes to obtain type class instances almost automatically,</p> <pre><code>// A pair of arbitrary case classes case class Foo(i : Int, s : String) case class Bar(b : Boolean, s : String, d : Double) // Publish their `HListIso`'s implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _) implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _) // And now they're monoids ... implicitly[Monoid[Foo]] val f = Foo(13, "foo") |+| Foo(23, "bar") assert(f == Foo(36, "foobar")) implicitly[Monoid[Bar]] val b = Bar(true, "foo", 1.0) |+| Bar(false, "bar", 3.0) assert(b == Bar(true, "foobar", 4.0)) </code></pre> <p>There's no runtime <em>iteration</em> here, but there is <em>duplication</em>, which the use of <code>HLists</code> (or equivalent structures) can eliminate. Of course, if your tolerance for repetitive boilerplate is high, you can get the same result by writing multiple implementations for each and every shape that you care about.</p> <p>In question three you ask "... if the function f you map over an hlist is so generic that it accepts all elements ... why not use it via productIterator.map?". If the function you map over an HList really is of the form <code>Any =&gt; T</code> then mapping over <code>productIterator</code> will serve you perfectly well. But functions of the form <code>Any =&gt; T</code> aren't typically that interesting (at least, they aren't unless they type cast internally). shapeless provides a form of polymorphic function value which allows the compiler to select type-specific cases in exactly the way you're doubtful about. For instance,</p> <pre><code>// size is a function from values of arbitrary type to a 'size' which is // defined via type specific cases object size extends Poly1 { implicit def default[T] = at[T](t =&gt; 1) implicit def caseString = at[String](_.length) implicit def caseList[T] = at[List[T]](_.length) } scala&gt; val l = 23 :: "foo" :: List('a', 'b') :: true :: HNil l: Int :: String :: List[Char] :: Boolean :: HNil = 23 :: foo :: List(a, b) :: true :: HNil scala&gt; (l map size).toList res1: List[Int] = List(1, 3, 2, 1) </code></pre> <p>With respect to your question four, about user input, there are two cases to consider. The first is situations where we can dynamically establish a context which guarantees that a known static condition obtains. In these kinds of scenarios it's perfectly possible to apply shapeless techniques, but clearly with the proviso that if the static condition <em>doesn't</em> obtain at runtime then we have to follow an alternative path. Unsurprisingly, this means that methods which are sensitive to dynamic conditions have to yield optional results. Here's an example using <code>HList</code>s,</p> <pre><code>trait Fruit case class Apple() extends Fruit case class Pear() extends Fruit type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil type APAP = Apple :: Pear :: Apple :: Pear :: HNil val a : Apple = Apple() val p : Pear = Pear() val l = List(a, p, a, p) // Inferred type is List[Fruit] </code></pre> <p>The type of <code>l</code> doesn't capture the length of the list, or the precise types of its elements. However, if we expect it to have a specific form (ie. if it ought to conform to some known, fixed schema), then we can attempt to establish that fact and act accordingly,</p> <pre><code>scala&gt; import Traversables._ import Traversables._ scala&gt; val apap = l.toHList[Apple :: Pear :: Apple :: Pear :: HNil] res0: Option[Apple :: Pear :: Apple :: Pear :: HNil] = Some(Apple() :: Pear() :: Apple() :: Pear() :: HNil) scala&gt; apap.map(_.tail.head) res1: Option[Pear] = Some(Pear()) </code></pre> <p>There are other situations where we might not care about the actual length of a given list, other than that it is the same length as some other list. Again, this is something that shapeless supports, both fully statically, and also in a mixed static/dynamic context as above. See <a href="https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/sized.scala" rel="noreferrer">here</a> for an extended example.</p> <p>It is true, as you observe, that all of these mechanism require static type information to be available, at least conditionally, and that would seem to exclude these techniques from being usable in a completely dynamic environment, fully driven by externally provided untyped data. But with the advent of the support for runtime compilation as a component of Scala reflection in 2.10, even this is no longer an insuperable obstacle ... we can use runtime compilation to provide a form of <a href="https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/staging.scala" rel="noreferrer">lightweight staging</a> and have our static typing performed at runtime in response to dynamic data: excerpt from the preceding below ... follow the link for the full example,</p> <pre><code>val t1 : (Any, Any) = (23, "foo") // Specific element types erased val t2 : (Any, Any) = (true, 2.0) // Specific element types erased // Type class instances selected on static type at runtime! val c1 = stagedConsumeTuple(t1) // Uses intString instance assert(c1 == "23foo") val c2 = stagedConsumeTuple(t2) // Uses booleanDouble instance assert(c2 == "+2.0") </code></pre> <p>I'm sure <a href="https://twitter.com/PLT_Borat" rel="noreferrer">@PLT_Borat</a> will have something to say about that, given his <a href="https://twitter.com/PLT_Borat/status/163966991877685249" rel="noreferrer">sage comments about dependently typed programming languages</a> ;-)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload