how to create an HList of Lenses from an HList

落花浮王杯 提交于 2019-12-23 16:28:24

问题


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 nth 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. )


回答1:


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


来源:https://stackoverflow.com/questions/25222471/how-to-create-an-hlist-of-lenses-from-an-hlist

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!