Scala Programming Language
  1. Scala Programming Language
  2. SI-6806

Add @ambiguiousImplicits annotation similar to @implicitNotFound

    Details

    • Type: New Feature New Feature
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None

      Description

      Ambiguous implicit values can be employed intentionally in order to enforce a compile-time constraint (see for example http://stackoverflow.com/questions/4403906/is-it-possible-in-scala-to-force-the-caller-to-specify-a-type-parameter-for-a-po, in which ambiguity is used to prevent "Nothing" from being inferred as the return type of a method).

      However, the compile time error for "ambiguous implicit values" is opaque and not obviously related to the constraint being enforced. There should be a @ambiguousImplicits annotation similar to @implicitNotFound that allows you to provide a user-friendly error message in the case that an implicit search for the annotated type triggers an "ambiguous implicit values" compile error.

        Activity

        Hide
        Aaron Novstrup added a comment -

        An @ambiguousImplicits annotation would be useful in any situation in which ambiguity is intentionally used to enforce a constraint. However, I'm not sure it should go on the implicit method(s) since that could itself introduce ambiguity. For the use cases I know of, it would be reasonable to put the annotation on the target type. As in

        
        @ambiguousImplicits(msg = "An explicit type parameter is required.")
        sealed trait NotNothing[T] { type U }                                          
        object NotNothing {
           implicit val nothingIsNothing = new NotNothing[Nothing] { type U = Any }
           implicit def notNothing[T] = new NotNothing[T] { type U = T }           
        }
        

        I also don't think that this issue rises to the level of "blocker", but I have no power to edit that field.

        Show
        Aaron Novstrup added a comment - An @ambiguousImplicits annotation would be useful in any situation in which ambiguity is intentionally used to enforce a constraint. However, I'm not sure it should go on the implicit method(s) since that could itself introduce ambiguity. For the use cases I know of, it would be reasonable to put the annotation on the target type. As in @ambiguousImplicits(msg = "An explicit type parameter is required.") sealed trait NotNothing[T] { type U } object NotNothing { implicit val nothingIsNothing = new NotNothing[Nothing] { type U = Any } implicit def notNothing[T] = new NotNothing[T] { type U = T } } I also don't think that this issue rises to the level of "blocker", but I have no power to edit that field.
        Hide
        Eric Pederson added a comment - - edited

        Right, that's how @implicitNotFound works too. I've updated the description. Yeah, I agree, blocker is too strong, that was only choice when I logged the bug

        Show
        Eric Pederson added a comment - - edited Right, that's how @implicitNotFound works too. I've updated the description. Yeah, I agree, blocker is too strong, that was only choice when I logged the bug
        Hide
        Eric Pederson added a comment -

        Thanks for the edits

        Show
        Eric Pederson added a comment - Thanks for the edits
        Hide
        Eric Pederson added a comment - - edited

        Something related - it would be nice to add a types not equal operator to the other type comparison "operators". Types not equal can be implemented using ambiguous implicits similar to this:

        // Courtesy of Miles Sabin.  See http://stackoverflow.com/a/6944070/158658
        object TypesNotEqual {
          trait =!=[A, B]
         
          implicit def neq[A, B] : A =!= B = null
         
          // This pair excludes the A =:= B case
          implicit def neqAmbig1[A] : A =!= A = null
          implicit def neqAmbig2[A] : A =!= A = null
        }
        
        Show
        Eric Pederson added a comment - - edited Something related - it would be nice to add a types not equal operator to the other type comparison "operators". Types not equal can be implemented using ambiguous implicits similar to this: // Courtesy of Miles Sabin. See http://stackoverflow.com/a/6944070/158658 object TypesNotEqual { trait =!=[A, B] implicit def neq[A, B] : A =!= B = null // This pair excludes the A =:= B case implicit def neqAmbig1[A] : A =!= A = null implicit def neqAmbig2[A] : A =!= A = null }
        Hide
        Brian McKenna added a comment -

        I'm guessing this is going to be possible with Type Macros. Usages of NotNothing will just need to check that a type parameter was not supplied then just use Context#error(pos, "An explicit type parameter is required.").

        Show
        Brian McKenna added a comment - I'm guessing this is going to be possible with Type Macros. Usages of NotNothing will just need to check that a type parameter was not supplied then just use Context#error(pos, "An explicit type parameter is required.") .
        Hide
        Brian McKenna added a comment -

        Only part of the way but the following is already possible in Macros Paradise:

        import reflect.macros.Context
        
        type NotNothing[A] = macro notNothingImpl[A]
        def notNothingImpl[A: c.WeakTypeTag](c: Context) = {
          import c.universe._
          val isNothing = weakTypeOf[A] =:= typeOf[Nothing]
          if (isNothing)
            c.error(NoPosition, "An explicit type parameter is required.")
          TypeTree(weakTypeOf[A])
        }
        
        Show
        Brian McKenna added a comment - Only part of the way but the following is already possible in Macros Paradise: import reflect.macros.Context type NotNothing[A] = macro notNothingImpl[A] def notNothingImpl[A: c.WeakTypeTag](c: Context) = { import c.universe._ val isNothing = weakTypeOf[A] =:= typeOf[Nothing] if (isNothing) c.error(NoPosition, "An explicit type parameter is required.") TypeTree(weakTypeOf[A]) }
        Hide
        Jason Zaugg added a comment -

        Even a def macro in 2.10.1 should handle this: we're likely to backport the Eugene's improvements to def macros to allow polymorphic macros to abide by the usual inference rules.

        % scala-hash origin/paradise/macros
        [info] downloading http://scala-webapps.epfl.ch/artifacts/a49722990655633c2c97ddf5699adf25bc8bea76/pack.tgz ...done.
        [info] scala revision from 2013-01-14 11:39:03 -0800 downloaded to /Users/jason/usr/scala-v2.11.0-M1-80-ga497229
        [info] origin/par => /Users/jason/usr/scala-v2.11.0-M1-80-ga497229
        Welcome to Scala version 2.11.0-20130114-113903-a497229906 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_27).
        Type in expressions to have them evaluated.
        Type :help for more information.
        
        scala> class NotNothing[T]
        defined class NotNothing
        
        scala> object NotNothing {
             |   val NotNothingShared = new NotNothing[Any]
             | } 
        defined object NotNothing
        warning: previously defined class NotNothing is not a companion to object NotNothing.
        Companions must be defined together; you may wish to use :paste mode for this.
        
        scala> import language.experimental.macros
        import language.experimental.macros
        
        scala> def impl[A: c.WeakTypeTag](c: reflect.macros.Context): c.Expr[NotNothing[A]] = {
             |   import c.universe._
             |   val isNothing = weakTypeOf[A] =:= typeOf[Nothing]
             |   if (isNothing)
             |     c.error(NoPosition, "An explicit, non-Nothing type parameter is required.")
             |   reify { NotNothing.NotNothingShared.asInstanceOf[NotNothing[A]] }
             | }
        impl: [A](c: scala.reflect.macros.Context)(implicit evidence$1: c.WeakTypeTag[A])c.Expr[NotNothing[A]]
        
        scala> implicit def notNothing[A]: NotNothing[A] = macro impl[A]
        defined term macro notNothing: [A]=> NotNothing[A]
        
        scala> def foo[A: NotNothing] = ()
        foo: [A](implicit evidence$1: NotNothing[A])Unit
        
        scala> foo[Int]
        
        scala> foo[Nothing]
        <console>:20: error: could not find implicit value for evidence parameter of type NotNothing[Nothing]
                      foo[Nothing]
                         ^
        scala> foo
        <console>:20: error: could not find implicit value for evidence parameter of type NotNothing[A]
                      foo
                      ^
        
        Show
        Jason Zaugg added a comment - Even a def macro in 2.10.1 should handle this: we're likely to backport the Eugene's improvements to def macros to allow polymorphic macros to abide by the usual inference rules. % scala-hash origin/paradise/macros [info] downloading http://scala-webapps.epfl.ch/artifacts/a49722990655633c2c97ddf5699adf25bc8bea76/pack.tgz ...done. [info] scala revision from 2013-01-14 11:39:03 -0800 downloaded to /Users/jason/usr/scala-v2.11.0-M1-80-ga497229 [info] origin/par => /Users/jason/usr/scala-v2.11.0-M1-80-ga497229 Welcome to Scala version 2.11.0-20130114-113903-a497229906 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_27). Type in expressions to have them evaluated. Type :help for more information. scala> class NotNothing[T] defined class NotNothing scala> object NotNothing { | val NotNothingShared = new NotNothing[Any] | } defined object NotNothing warning: previously defined class NotNothing is not a companion to object NotNothing. Companions must be defined together; you may wish to use :paste mode for this. scala> import language.experimental.macros import language.experimental.macros scala> def impl[A: c.WeakTypeTag](c: reflect.macros.Context): c.Expr[NotNothing[A]] = { | import c.universe._ | val isNothing = weakTypeOf[A] =:= typeOf[Nothing] | if (isNothing) | c.error(NoPosition, "An explicit, non-Nothing type parameter is required.") | reify { NotNothing.NotNothingShared.asInstanceOf[NotNothing[A]] } | } impl: [A](c: scala.reflect.macros.Context)(implicit evidence$1: c.WeakTypeTag[A])c.Expr[NotNothing[A]] scala> implicit def notNothing[A]: NotNothing[A] = macro impl[A] defined term macro notNothing: [A]=> NotNothing[A] scala> def foo[A: NotNothing] = () foo: [A](implicit evidence$1: NotNothing[A])Unit scala> foo[Int] scala> foo[Nothing] <console>:20: error: could not find implicit value for evidence parameter of type NotNothing[Nothing] foo[Nothing] ^ scala> foo <console>:20: error: could not find implicit value for evidence parameter of type NotNothing[A] foo ^
        Hide
        Aaron Novstrup added a comment -

        These macro-based solutions are interesting alternatives to Miles Sabin's ambiguous-implicit-based one (see http://stackoverflow.com/a/4580176/65299) and would be useful contributions to the related Stack Overflow question. However, they don't address the more general issue: ambiguous implicits can be used to enforce type constraints (and may be preferred over macros since macros are advanced language features), but the error messages are uninformative without some mechanism for overriding the default message.

        Show
        Aaron Novstrup added a comment - These macro-based solutions are interesting alternatives to Miles Sabin's ambiguous-implicit-based one (see http://stackoverflow.com/a/4580176/65299 ) and would be useful contributions to the related Stack Overflow question. However, they don't address the more general issue: ambiguous implicits can be used to enforce type constraints (and may be preferred over macros since macros are advanced language features), but the error messages are uninformative without some mechanism for overriding the default message.

          People

          • Assignee:
            Unassigned
            Reporter:
            Eric Pederson
          • Votes:
            6 Vote for this issue
            Watchers:
            8 Start watching this issue

            Dates

            • Created:
              Updated:

              Development