Variable-length tuple?

1,161 views
Skip to first unread message

Joe Barnes

unread,
May 19, 2013, 4:58:47 PM5/19/13
to scala...@googlegroups.com
I've started an open-source project (SNMP4S if you're interested), and I've run into a snag trying to accomplish my type-safety goals.  I think what I need is some kind of variable-length tuple.  A list won't work because the type of the contents are parameterized with different.  A tuple accomplishes what I need, but I have to hand-write a function for every number of arguments I hope to support.  It is much like Scala's limit on functions having at most 22 parameters.

To avoid gory details of my project, suppose I wanted to have this Tuplizer class below.  I really like what the Consumer class looks like because each variable returned infers the correct type.  However, I'd like to avoid hand-writing the 3 different functions and be able to support an arbitrary number of Options passed to f.

class Tuplizer {
  def f[A](a:Option[A]):A = ???
  def f[A, B](a:Option[A], b:Option[B]):(A,B) = ???
  def f[A, B, C](a:Option[A], b:Option[B], c:Option[C]):(A,B,C) = ???
  // ...
  
}

class Consumer {
  def t:Tuplizer = new Tuplizer
  
  def use = {
    val a = t.f(Some(1))
    val (b, c) = t.f(Some("String"), Some(5))
    val (d, e, f) = t.f(Some(7), Some("Other"), Some(true))
  }
}

The best hint I've found towards accomplishing this is to use shapeless per this post.  But I don't think that helps me get a varying number of args.  Any good ideas?

Jed Wesley-Smith

unread,
May 19, 2013, 6:50:05 PM5/19/13
to Joe Barnes, scala-user
We have something similar (using shapeless) to do command-line option parsing. Basically we pass in a function of variable arity and it will work out the length from that, chomping off that many arguments from a list.



All of the work is done in the opt method:

  def opt[H <: HList, N <: Nat, F, R](t: T, f: F)(
    implicit hlister: FnHListerAux[F, H => R],
    length: LengthAux[H, N],
    toHList: FromTraversable[H],
    size: ToInt[N]): Option[R] =
    for {
      ts <- if (size() > 0) tailfind(t) else rawdata.find { _ == t }.map { _ => Nil }
      hl <- toHList(ts.take(size()))
      op <- nonFatalCatch.opt { hlister(f)(hl) }
    } yield op

where the T is the input type (in practice always String) and R is the output type. The various implicits witness that we can get the arguments of our function as an HList, that we can work out the length of that HList, that we can turn our rawdata (a field in this case) into an HList, and that we can turn our size into an Int value so we can take that many elements from ou rawdata.

It takes a little while to puzzle though, but it works really well and is nice and compact.

cheers,
jed. 


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Vlad Patryshev

unread,
May 19, 2013, 7:02:45 PM5/19/13
to Joe Barnes, scala-user
I believe Shapeless HList was designed exactly with the purpose of solving this kind of problems.

Thanks,
-Vlad


--

Miles Sabin

unread,
May 20, 2013, 9:53:38 AM5/20/13
to Joe Barnes, scala-user
On Sun, May 19, 2013 at 9:58 PM, Joe Barnes <barn...@gmail.com> wrote:
> To avoid gory details of my project, suppose I wanted to have this Tuplizer
> class below. I really like what the Consumer class looks like because each
> variable returned infers the correct type. However, I'd like to avoid
> hand-writing the 3 different functions and be able to support an arbitrary
> number of Options passed to f.
>
> class Tuplizer {
> def f[A](a:Option[A]):A = ???
> def f[A, B](a:Option[A], b:Option[B]):(A,B) = ???
> def f[A, B, C](a:Option[A], b:Option[B], c:Option[C]):(A,B,C) = ???
> // ...
>
> }
>
> class Consumer {
> def t:Tuplizer = new Tuplizer
>
> def use = {
> val a = t.f(Some(1))
> val (b, c) = t.f(Some("String"), Some(5))
> val (d, e, f) = t.f(Some(7), Some("Other"), Some(true))
> }
> }
>
> The best hint I've found towards accomplishing this is to use shapeless per
> this post. But I don't think that helps me get a varying number of args.
> Any good ideas?

You should be able to do this with shapeless ... but one thing I'm
missing though ... what is the intended semantics if one of the
argument to f is None? Is there supposed to be a corresponding
default?

Cheers,


Miles

--
Miles Sabin
tel: +44 7813 944 528
skype: milessabin
gtalk: mi...@milessabin.com
g+: http://www.milessabin.com
http://twitter.com/milessabin

Joe Barnes

unread,
May 20, 2013, 10:30:23 AM5/20/13
to scala...@googlegroups.com, j...@wesleysmith.io
Thanks for the reply.  I'll try to pick through this and get it working for my code.

Joe

Joe Barnes

unread,
May 20, 2013, 10:32:05 AM5/20/13
to scala...@googlegroups.com, Joe Barnes
Hi Miles, don't worry about missing that detail.  The example I gave is a bit contrived to avoid explaining the details of my project.  The parameterized types that I will be passing to my functions aren't of type Option[A] but some other Type[A] that doesn't have a None.

Joe

Joe Barnes

unread,
May 20, 2013, 11:06:37 AM5/20/13
to scala...@googlegroups.com
I'll have to keep working on the example that Jed offered.  I didn't immediately understand it.  

I have a better, slightly less contrived illustrative code for what I am trying to accomplish.  The HeadHunter object is where I want to support an arbitrary number of arguments to the get function.  The HeadHunterSuite class should help show how it could be called.

object HeadHunter {
  def get[A](a:Seq[A]):Option[A] = 
    a.headOption
  def get[A,B](a:Seq[A],b:Seq[B]):(Option[A],Option[B]) = 
    (a.headOption, b.headOption)
  def get[A,B,C](a:Seq[A],b:Seq[B],c:Seq[C]):(Option[A],Option[B],Option[C]) = 
    (a.headOption, b.headOption, c.headOption)
  
}

class HeadHunterSuite extends WordSpec with ShouldMatchers {
  "HeadHunter" should {
    "be type safe and therefore awesome" in {
      import HeadHunter._
      get(Seq(1,2,3)) should equal (
          Some(1))
      get(Seq("strs","str2"), Seq(4,5,6)) should equal (
          (Some("strs"),Some(4)))
      get(Seq(true,false,true), Seq("str1","str2"), Seq(3)) should equal (
          (Some(true), Some("str1"), Some(3)))
    }
  }
}

Thanks!

Joe

Joe Barnes

unread,
Jun 1, 2013, 2:50:52 PM6/1/13
to scala...@googlegroups.com
Apparently this requires more Scala skills than I have at this point.  I've been batting around some of the example code in shapeless like a blindfolded kid on a pinata.  Can any of you give me some hints on how to make this "HeadHunter" example work?  

Thanks in advance!
Joe

Mark Lister

unread,
Jun 1, 2013, 6:28:17 PM6/1/13
to scala...@googlegroups.com
It's not particularly pretty, but it's fast and effective: https://github.com/sbt/sbt-boilerplate

Joe Barnes

unread,
Jun 1, 2013, 6:35:05 PM6/1/13
to scala...@googlegroups.com
Not a bad idea. I'm optimistic that the likes of Jed and Miles can set me straight with shapeless, tho.

Joe Barnes

unread,
Jun 1, 2013, 10:47:42 PM6/1/13
to scala...@googlegroups.com
I made a bit of good progress.  Using shapeless, I now can pass an arbitrary-length tuple to my HeadHunter object.  However, I've not figured out how to make that function return the tupled result.  The implicit Tupler isn't right.  It says it needs a Tupler[mapper.Out], but of course I can't specify that due to illegal dependency among the arguments.  My goal is to remove the call to .tupled in the test code.

Here is the code:

import shapeless._
  
object header extends (Seq ~> Option) {
  def apply[T](s : Seq[T]) = s.headOption
}

object HeadHunter {
  def get[A](a:Seq[A]):Option[A] = 
    a.headOption
    
  def get[T <: Product, L <: HList](t : T)
    (implicit hl : HListerAux[T, L], 
     mapper : Mapper[header.type, L],
     tupler : Tupler[L]) = {
    val h = hl(t)
    val o = h map header
//    o.tupled  // <-- How can I make this work?
    o
  }
}

// **********************************************

import org.scalatest.WordSpec
import org.scalatest.matchers.ShouldMatchers
import shapeless._

class HeadHunterSuite extends WordSpec with ShouldMatchers {
  "HeadHunter" should {
    "be type safe and therefore awesome" in {
      import HeadHunter._
      val t1:Option[Int] = get(Seq(1,2,3))
      t1 should equal (Some(1))
      
      val t2:(Option[String], Option[Int]) = get((Seq("strs","str2"), Seq(4,5,6))).tupled 
      t2 should equal ((Some("strs"),Some(4)))
      
      val t3:(Option[Boolean], Option[String], Option[Int]) = get(Seq(true,false,true), Seq("str1","str2"), Seq(3)).tupled 
      t3 should equal ((Some(true), Some("str1"), Some(3)))
    }
  }
}

I also have the source up on github.

Joe

Joe Barnes

unread,
Jun 2, 2013, 1:43:14 PM6/2/13
to scala...@googlegroups.com
And I figured it out!  Well, I don't understand it... but it works. :)

  def get[InT <: Product, OutT <: Product, InL <: HList, OutL <: HList](t : InT)
    (implicit hl : HListerAux[InT, InL], 
     mapper : MapperAux[header.type, InL, OutL],
     tupler : TuplerAux[OutL, OutT]) = {
    val h = hl(t)
    val o = h map header
    val r = o.tupled  
    r

Kevin Wright

unread,
Jun 2, 2013, 2:49:35 PM6/2/13
to Joe Barnes, scala-user
  def get[InT <: Product, OutT <: Product, InL <: HList, OutL <: HList](t : InT)

define a method with a single parameter of type InT, this type will (obviously) be known when it's called.
The types OutT, InL and OutL will be inferred using a series of implicit scope lookups based on InT

    (implicit hl : HListerAux[InT, InL], 

find an instance of HListerAux parameterised with the already-known InT, there will only be one such instance.
From this implicit, InL can be inferred, we now know the types InT and InL

     mapper : MapperAux[header.type, InL, OutL],

As with h1, finds the single MapperAux instance using the known types header.type (from the surrounding class) and InL, so that OutL can be inferred
InT, InL and OutL are now known (in addition to header.type)

     tupler : TuplerAux[OutL, OutT]) = {

As with h1 and mapper, use implicit search for a TuplerAux so that OutT can be inferred
InT, InL, OutL and OutT are now known - we have all the specified type params.

    val h = hl(t)
    val o = h map header
    val r = o.tupled  
    r

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Kevin Wright
mail: kevin....@scalatechnology.com
gtalk / msn : kev.lee...@gmail.com
vibe / skype: kev.lee.wright
steam: kev_lee_wright

"My point today is that, if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent": the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger" ~ Dijkstra

Léonard Schneider

unread,
Jun 2, 2013, 7:17:05 PM6/2/13
to scala...@googlegroups.com
Hi,

Glad you found a way to solve your problem. I have also written some macros working on tuples that may help in such use case as yours. This requires macro paradise though (it seems it might be back ported to upcoming 2.10.2). See https://github.com/leonardschneider/macrogen for details.

You could simply write (1, "two", 3.0).head in your case.

BR,


Leo

Joe Barnes

unread,
Jun 2, 2013, 7:35:42 PM6/2/13
to scala...@googlegroups.com
I just realized that a tuple approach isn't going to better than Mark's boilerplate plugin. In particular I hoped to circumvent the 22-arg limitation, but tuples are likewise limited. While I have figured out the example I put forth in this post, the use case in my project is a little more involved. I'll let y'all know how it goes.

Joe Barnes

unread,
Jun 6, 2013, 4:29:21 PM6/6/13
to scala...@googlegroups.com
Well, even that's got some problems.  If you're interested, I've decided to outline it all in a blog post.

Johannes Rudolph

unread,
Jun 7, 2013, 5:12:06 AM6/7/13
to Joe Barnes, scala-user
On Mon, Jun 3, 2013 at 1:35 AM, Joe Barnes <barn...@gmail.com> wrote:
I just realized that a tuple approach isn't going to better than Mark's boilerplate plugin. In particular I hoped to circumvent the 22-arg limitation, but tuples are likewise limited. While I have figured out the example I put forth in this post, the use case in my project is a little more involved. I'll let y'all know how it goes.

I should add a comment to sbt-boilerplate's README that it isn't thought as a general solution to generate lots of source code. Indeed, you should try to understand and use shapeless since it allows you to write general code on a very high level. The main purpose of sbt-boilerplate was to generate some glue code which seemed to be missing in shapeless at the time.

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

Joe Barnes

unread,
Jun 7, 2013, 6:05:26 AM6/7/13
to scala...@googlegroups.com
Got ya. You're welcome to reference me in that comment with a note stating "don't do what this jackass did" LOL

This has been great, tho. I'm finding out there is a whole world in the Scala type system that I am ignorant of.

Mark Lister

unread,
Jun 7, 2013, 5:47:03 PM6/7/13
to scala...@googlegroups.com
I must point out that sbt-boilerplate is Johannes' work :)
Reply all
Reply to author
Forward
0 new messages