Filter usage in shapeless, Scala
Asked Answered
L

1

8

It is easy to filter HList in shapeless by type:

val hlist = 1 :: 2 :: "3" :: true :: false :: HNil
hlist.filter[Int]

But how can I make my custom type filter? I want smth like that: for example I got list of some functions:

def function1(s: String) = s.toInt
def function2(s: String) = s.toDouble
def function3(i: Int) = i.toDouble

val hflist = function1 _ :: function3 _ :: function2 _ :: HNil

hflist customFilter[String] //> function1 _ :: function2 _ :: HNil

So after usage of this filter, list of functions from type String to some other type will be constructed.

I had an idea to use map for this, but it was not successfull.

EDITION

More information about my comment:

I tried to test this ideas in map:

So if i got some lists (lets operate with hlist & hflist):

object allFunction extends Poly1 {
  implicit def default[T, M] =
    at[T => M](t => { 
      object grabStringFunc extends skip {
        implicit def stringFunc[A] = at[T => A](_ :: HNil)
      }

      println(hflist flatMap grabStringFunc) //> here we should see result, list of functions 
 })

 hlist map allFunction
 //> result of this should be smth like (types)
 //> shapeless.::[Int => Double,shapeless.HNil]]
 //> shapeless.::[Int => Double,shapeless.HNil]]
 //> shapeless.::[String => Int,shapeless.::[String => Double,shapeless.HNil]] 
 //> shapeless.HNil
 //> shapeless.HNil

Very interesting, why it compiles and works incorrect? As I think it is not works, cause object cant take type prameters in such a way...

Laborer answered 10/11, 2013 at 18:49 Comment(0)
C
15

The easiest way is to use a fold. First we need a polymorphic function that will add each item to the accumulator if it has the desired type (String => A for some A), and ignore it otherwise:

trait ignore extends Poly2 {
  implicit def default[A, L <: HList] = at[A, L]((_, l) => l)
}

object keepStringFunc extends ignore {
  implicit def stringFunc[A, L <: HList] = at[String => A, L](_ :: _)
}

Now the following will give the result you want in both 1.2.4 and 2.0.0-M1:

val filtered = hflist.foldRight(HNil)(keepStringFunc)

You could also write your own type class on the model of Filter, FilterAux (or Filter.Aux), etc.—and doing so would be a good exercise if you're trying to get the hang of Shapeless—but foldRight is a lot simpler.


Update: actually, for what it's worth, there's a slightly more concise way to do this with flatMap:

trait skip extends Poly1 {
  implicit def default[A] = at[A](_ => HNil)
}

object grabStringFunc extends skip {
  implicit def stringFunc[A] = at[String => A](_ :: HNil)
}

val filtered = hflist flatMap grabStringFunc

I personally find the foldRight version a little more obvious, but this one's also pretty elegant.


In response to your comment: you can make the solution a little more generic like this:

trait skip extends Poly1 {
  implicit def default[A] = at[A](_ => HNil)
}

trait grabFuncFrom[T] extends skip {
  implicit def stringFunc[A] = at[T => A](_ :: HNil)
}

object grabStringFunc extends grabFuncFrom[String]

val filtered = hflist flatMap grabStringFunc

But you're still going to need that last step where you create the higher rank function as an object (see e.g. this answer and Miles's comment there for some discussion of this issue).

Cheyenne answered 10/11, 2013 at 19:24 Comment(3)
omg, such a pretty decision; thx a lot! I'll try also to write my own Filter too. thx!Laborer
Hm, a little edition to my question, can we pass the prameter in function, i mean smth like def stringFunc[A] = at[T => A](_ :: HNil) ? why it is compiling, and why, in fact, it is not working? p.s. I am on my way writing own Filter :) thx!Laborer
y, for understanding shapeless. I wish I could upvote your answers lots of times. I added some information about my comment, what i tried. thx for your time! :)Laborer

© 2022 - 2024 — McMap. All rights reserved.