简体   繁体   中英

how to create an HList of Lenses from an HList

I am writing a generic table viewer which should be able to display any type of Seq[Row] where Row <: HList . The Table class looks like this:

class Table[TH<:HList, TR<:HList](val hdrs: TH, val rows: Seq[TR])

When someone clicks on a column of the table viewer I would like to redraw the whole table sorted on the Ordering of that column. For that I need a to be able assign a function to sort the table on a particular colum. Using lenses for that seems one reasonable option.

def sort[Elem<:Nat](lens: Lens[R, Elem]) = {
   ...
   table.rows.sortBy(lens.get(_)) //sort the rows of the table using the lens
}

I then need to tie that function to a click event on the table header. On a first naive attempt I would build the html header like this using scalajs-react

def header = {
    tr(for (ci <- 0 to tab.hdrs.runtimeLength) yield 
          th(onclick --> B.sort(hlistNthLens[R,nat(ci)]))(tab.hdrs(ci).toString))
}

This is setting up a onClick even on the table header to call the sort method above. But this won't work, because one has lost the type structure information by using the Int named ci . One really needs to keep all the type information around for the compiler to know that a particular field of the HList is the n th element of the list, so that the construction of the lens can work at the type level.

So that is of course the difficult part of programming with shapeless.

The function that is needed is one that would take me from any subclass of HList to an HList of Lenses each of which would select that element for any instance of that particular subclass of HList .

 def lenses[H <: HList] = /* return an HList of Lenses, where 
        the first Lens will always select the first element of any 
        instance of `H`, the second will always select the second
        element of any instance of `H`, ... */

( Ideally one could then generalise this to allow one to combine these lenses, so that a user could select a primary then a secondary sort order. )

Ok I think I found the answer. First tests seem to confirm this.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import shapeless._
import shapeless.ops.hlist.At
import shapeless.syntax.std.tuple._

final class myHListOps[L <: HList](l: L) {

  import hlistaux._

  def extractors(implicit extractor : Extractor[_0, L,L]) : extractor.Out = extractor()
}

object hlistaux {
  trait Extractor[HF<:Nat, In <: HList, Remaining<: HList] extends DepFn0 { type Out <: HList }

  object Extractor {
    def apply[HL <: HList]
    (implicit extractor: Extractor[_0, HL,HL]):
       Aux[_0, HL, HL, extractor.Out] = extractor

    type Aux[HF<:Nat, In <: HList, Remaining<: HList, Out0 <: HList] = Extractor[HF, In, Remaining] { type Out = Out0 }

    //To deal with case where HNil is passed. not sure if this is right.
    implicit def hnilExtractor: Aux[_0, HNil, HNil, HNil] =
      new Extractor[_0, HNil, HNil] {
        type Out = HNil
        def apply(): Out = HNil
      }

    implicit def hSingleExtractor1[N<:Nat, In<:HList, H ]
    (implicit att : At[In, N]): Aux[N, In, H::HNil, At[In,N]::HNil] =
      new Extractor[N, In, H::HNil] {
        type Out = At[In,N]::HNil
        def apply(): Out = att::HNil
      }


    implicit def hlistExtractor1[N <: Nat, In<:HList, H, Tail<: HList]
    (implicit mt : Extractor[Succ[N], In, Tail],
              att : At[In, N])
    :Aux[N, In, H::Tail, At[In,N]::mt.Out] = {
      new Extractor[N, In, H::Tail] {
        type Out = At[In,N]::mt.Out

        def apply(): Out = {
          att :: mt()
        }
      }
    }
  }
}

// Exiting paste mode, now interpreting.

import shapeless._
import shapeless.ops.hlist.At
import shapeless.syntax.std.tuple._
defined class myHListOps
defined object hlistaux

scala> val l = "Hello"::HNil
l: shapeless.::[String,shapeless.HNil] = Hello :: HNil

scala> val lo = new myHListOps(l).extractors
lo: shapeless.::[shapeless.ops.hlist.At[shapeless.::[String,shapeless.HNil],shapeless._0],shapeless.HNil] = shapeless.ops.hlist$At$$anon$54@12d33d1c :: HNil

scala> lo.head(l)
res0: lo.head.Out = Hello

scala> val m = 42::l
m: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 42 :: Hello :: HNil

scala> val mo = new myHListOps(m).extractors
mo: shapeless.::[shapeless.ops.hlist.At[shapeless.::[Int,shapeless.::[String,shapeless.HNil]],shapeless._0],shapeless.::[shapeless.ops.hlist.At[shapeless.::[Int,shapeless.::[String,shapeless.HNil]],shapeless.Succ[shapeless._0]],shapeless.HNil]] = shapeless.ops.hlist$At$$anon$54@5e181eeb :: shapeless.ops.hlist$At$$anon$55@1960690 :: HNil

scala> mo.head(m)
res3: mo.head.Out = 42

scala> mo.tail.head(m)
res4: mo.tail.head.Out = Hello

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM