Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala implicit ambiguity doesn't get resolved without annoying dummy argument to mark the type.

After working through some examples of Scala higher-kinded types in this tutorial, I started playing around with (a) implicits and (b) techniques for writing methods that generically handle subclasses of a trait that is defined as a higher-kinded type. I already got a great answer on (b) here which is moving me closer to solving the problem below:

I want to create a factory for creating singleton containers (Sets, Lists, Arrays) that wrap the item that I pass in to the factory function. [ Even if there is already a Scala library that does this, I'm doing this as a learning exercise. But, hey... if there is one, please let me know!]

The end result should let me do this:

scala> var j: Set[String] =  wrapItemInContainer("foo")
j: Set[String] = Set(foo)

scala> var k: List[Int] =  wrapItemInContainer(9)
k: List[Int] = List(9)

The solution I came up with is shown below. I have to supply an annoying dummy final argument so that I can help the compiler figure out what kind of container I want. This works, but I am puzzled by the fact that the type signature of the variable that I want to assign the result to (j, k..etc.) is not giving the compiler enough information to figure out which implicitly defined ContainerFactory needs to be used.

This clunky solution works:

trait ContainerFactory[M[_]]  { def put[A](x: A): M[A]  }


implicit val factory = new ContainerFactory[List] { def put[A](x: A) = List(x)  }   // factory for List containers
implicit val factory2 = new ContainerFactory[Set] { def put[A](x: A) = Set(x)}    // factory for Set containers

def wrapItemInContainer[ M[A]: ContainerFactory, A](item: A,  helper: M[A]) : M[A] = { 
    val c = implicitly[ContainerFactory[M]]                             
    c.put(item)
}

var j: List[Int] =  wrapItemInContainer(9, List(0))

But I really want something with out the noisy second argument:

def wrapItemInContainer[ M[A]: ContainerFactory, A](item: A) : M[A] = { 
    val c = implicitly[ContainerFactory[M[A]]]                             
    c.put(item)
}

var j: List[Int] =  wrapItemInContainer(9)    // this does not work.

I get this error:

<console>:17: error: ambiguous implicit values:
 both value factory of type => ContainerFactory[List]
 and value factory2 of type => ContainerFactory[Set]
 match expected type ContainerFactory[M]
       var j: List[Int] =  wrapItemInContainer(9)

Any ideas or tips are much appreciated !

-chris

like image 690
Chris Bedford Avatar asked Apr 24 '15 02:04

Chris Bedford


1 Answers

You need to make the factory covariant in M. See https://groups.google.com/forum/#!topic/scala-language/dQEomVCH3CI and https://issues.scala-lang.org/browse/SI-7332.

This compiles:

import language.higherKinds

trait Factory[+M[_], A] { def put(x: A): M[A] }

implicit def factory1[A] =
  new Factory[List, A] { def put(x: A) = List(x) }
implicit def factory2[A] =
  new Factory[Set, A] { def put(x: A) = Set(x) }

def wrap[M[_], A](a: A)(
    implicit factory: Factory[M, A]): M[A] =
  factory.put(a)

val j: Set[String] = wrap("foo")
val k: List[Int] = wrap(9)

I've taken the liberty of shortening your names. I also followed the advice you received at how to generically handle Scala Higher-kinded types when coding factories for generating containers of items of type 'X' to make A a type parameter on the factory itself.

like image 68
Seth Tisue Avatar answered Sep 30 '22 04:09

Seth Tisue