Passing a formless expandable record to a function (endless story?

I continue to research extensible records, as in Passing a seamless extensible record to a function (continued) : the provided solution works with functions that all take a parameter that includes at least foo1, foo2 and foo3; this can be written:

fun1(("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil) fun1(("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: ("foo4" ->> true) :: HNil) 

etc.

And if we can add a second fun2 function:

 def fun2[L <: HList : HasMyFields](xs: L) = { val selectors = implicitly[HasMyFields[L]] import selectors._ xs("foo1").length + xs("foo2") + xs("foo3") } 

So far so good.

Now suppose that we have a set of fields foo1, foo2, ... And a set of functions fun1, fun2 that take as an argument a record composed with any subset {foo1, foo2, ...}. For example, fun1 can take as a parameter a record containing foo1 and foo3, but not necessarily foo2, fun2 expects a record with at least foo4, etc.

Is there a way to avoid declaring as many classes as HasMyFields, since they are possible combinations (if we have n fields, there are 2 ** n combinations!)?

+6
source share
2 answers

This is much simpler without an additional type class with the new-ish Witness syntax:

 import shapeless._, ops.record.Selector, record._, syntax.singleton._ // Uses "foo1" and "foo2" fields; note that we get the appropriate static types. def fun1[L <: HList](l: L)(implicit foo1: Selector.Aux[L, Witness.`"foo1"`.T, String], foo2: Selector.Aux[L, Witness.`"foo2"`.T, Int] ): (String, Double) = (foo1(l), foo2(l)) 

And then if we have this entry:

 val rec = ("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil 

We get the following:

 scala> fun1(rec) res0: (String, Double) = (hello,1.0) 

The new syntax allows us to avoid creating witness values ​​separately, so it's pretty easy to just require the Selector instances you need.

+6
source

Starting with Travis's answer, I was able to improve it:

In the first file I have:

 package p2; import shapeless._, ops.record.Selector, record._, syntax.singleton._ object MyFields { type wfoo1[L<: HList]=Selector.Aux[L,Witness.`"foo1"`.T, String] type wfoo2[L<: HList]=Selector.Aux[L,Witness.`"foo2"`.T, Int] } 

and then, in another place:

 package p1; import shapeless._, ops.record.Selector, record._, syntax.singleton._ import p2.MyFields._ object testshapeless extends App { def fun1[L <: HList](l: L)(implicit foo1: wfoo1[L], foo2: wfoo2[L] ): (String, Double) = (foo1(l), foo2(l)) val rec = ("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil println(fun1(rec)); } 

The first file can be thought of as a scheme where I declare fields that I can potentially use in my application, and I just want to import them.

That's cool!

Edited June 30: I wonder if we can do better, maybe with a macro: Can I write something like:

  def fun1[L <:HList] WithSelectors(MyFields)=... 

The WithSelectors macro will generate:

  (implicit foo1: wfoo1[L], foo2: wfoo2[L] ) 

Any tips?

+2
source

Source: https://habr.com/ru/post/988647/


All Articles