Higher kinded types madness

150 views
Skip to first unread message

Tom

unread,
Feb 12, 2015, 4:58:45 PM2/12/15
to scala...@googlegroups.com
Hi, 

so, to the point, I had the following trait (as an interface for abstracting many possible implementations of a repository):

trait FooRepository {
...
  def exists(fooId: Id): Future[Boolean]
  def asFilterFor(ids: Seq[Id]): Future[Seq[Id]] // Filter a sequence of ids against the existing ids
...
}

As you can see all the operations return eventual results, for this would be connected against some remote data store. In particular, asFilterFor implementation looked like this:

def asFilterFor(ids: Seq[Id]): Future[Seq[Id]] = Future.sequence(ids.map(someIfExists)).map(_.flatten)

// .option is Scalaz boolean-to-option utility: this is, Some(id) if true, None otherwise
private def someIfExists(id: Id) = exists(id).map(_.option(id)) 

Then I realized that asFilterFor would be used against more types of collections, so it seemed natural to implement it for any traversable. I thought it was going to be easy but ended up struggling a lot with the language, till I got something that worked:

def asFilterFor[C[Id] <: Traversable[Id]]
               (ids: Seq[Id])(implicit 
                bf1: CanBuildFrom[C[Id], Future[Option[Id]], C[Future[Option[Id]]]],
                bf2: CanBuildFrom[C[Future[Option[Id]]], Option[Id], C[Option[Id]]]
               ): Future[C[Id]] = 
  Future.sequence(ids.map(someIfExists)).map(_.flatten).asInstanceOf[Future[C[Id]]]

Woooow, needless is to say that this is very neat implementation of an outrageously frightful method signature. So my questions would be:

1) doesn't this signature expose implementation details by means of the implicit parameters (the two CanBuildFrom)?
2) is there anything I can do about it or any cleaner alternative for generalizing such a method?

Thanks in advance,
Tom;

PD: Names of methods aren't the real ones, of course.

Tom

unread,
Feb 12, 2015, 5:03:02 PM2/12/15
to scala...@googlegroups.com
Opps, made a mistake. The generic method signature is even worse than what I stated in the previous post. Here it is, corrected:

def asFilterFor[C[Id] <: Traversable[Id]]
               (ids: C[Id] with TraversableLike[String, C[Id]])(implicit 

Tom

unread,
Feb 12, 2015, 5:05:24 PM2/12/15
to scala...@googlegroups.com
Another correction (sorry again):

def asFilterFor[C[Id] <: Traversable[Id]]
               (ids: C[Id] with TraversableLike[Id, C[Id]])(implicit 

Tom

unread,
Feb 13, 2015, 10:08:58 AM2/13/15
to scala...@googlegroups.com
I discovered that I had some redundant cbf's. So this would be a slightly shorter signature that works:

def asFilterFor[C[Id] <: Traversable[Id]]
               (ids: C[Id] with TraversableLike[Id, C[Id]])
               (implicit bf: CanBuildFrom[C[Future[Option[Id]]], Option[Id], C[Option[Id]]]): Future[C[Id]] = Future.sequence(ids.map(someIfExists)).map(_.flatten).asInstanceOf[Future[C[Id]]]

Stephen Compall

unread,
Mar 1, 2015, 5:12:40 PM3/1/15
to Tom, scala...@googlegroups.com
On Thu, 2015-02-12 at 13:58 -0800, Tom wrote:
def asFilterFor[C[Id] <: Traversable[Id]]
               (ids: Seq[Id])(implicit 
                bf1: CanBuildFrom[C[Id], Future[Option[Id]], C[Future[Option[Id]]]],
                bf2: CanBuildFrom[C[Future[Option[Id]]], Option[Id], C[Option[Id]]]
               ): Future[C[Id]] = 
  Future.sequence(ids.map(someIfExists)).map(_.flatten).asInstanceOf[Future[C[Id]]]

Woooow, needless is to say that this is very neat implementation of an outrageously frightful method signature. So my questions would be:

1) doesn't this signature expose implementation details by means of the implicit parameters (the two CanBuildFrom)?

Yes; this is one of the drawbacks of an approach that doesn't depend on universal quantification, as a higher-kinded CBF would: you end up exposing "implementation details" like this. In a way, this is no different from the way that taking arguments reveals the "implementation details" of a function by providing insight into what data the function needs to operate on to successfully produce a result.


2) is there anything I can do about it or any cleaner alternative for generalizing such a method?

It should not be necessary to use a cast here; that's a good sign your operations don't quite line up, and you aren't really guaranteed to return a Future[C[Id]].  It's an accident that you've only encountered cases where that is true so far.

First step is to fuse the map followed by sequence into a traverse, as all such map-then-sequences should be transformed.  Finally, I added another constraint, GenericTraversableTemplate, so that flatten returns the correct type.

  // Filter a sequence of ids against the existing ids
  def asFilterFor[C[X] <: Traversable[X] with GenericTraversableTemplate[X, C]]
                 (ids: C[Id])
                 (implicit bf: CanBuildFrom[C[Id], Option[Id], C[Option[Id]]])
      : Future[C[Id]] = 
    Future.traverse(ids)(someIfExists).map(_.flatten)

See the full compiling example.


-- 
Stephen Compall
"^aCollection allSatisfy: [:each|aCondition]": less is better
signature.asc
Reply all
Reply to author
Forward
0 new messages