Ability to force the caller to specify a type parameter for a polymorphic method


#1

We need black box macros for final class definition. For example

create[Card]

which will generate something like

    new Card {
      def this2Tag[MAVI <: AnyRef](avi: MAVI)(implicit tag: TypeTag[MAVI]): TypeTag[MAVI] =tag
      override def meta = this2Tag(this)
    }

Or without macros, we can use implicit:

  class Form[T](implicit tag: TypeTag[T]) extends DefaultForm {
    override def meta: TypeTag = tag
  }  
  
   new Form[Card] with Card

Unfortunately there is no simple way to guaranty that the end user specify type parameter.
So the code will not work correctly in runtime after correct compilation.

See also:


#2

Hey Alexey,

Could you elaborate on what you’d like Scala to do more concretely? The link you provide in SO is helpful in understanding what you want to see, but all we can do is guessing, and I imagine most people in the forum won’t take the time to go and read the long answer in the SO question :wink:

Usually, the best way to propose a change is to provide more or less all the information needed for someone to implement the change you want to see, with clear examples of what you’re trying to do, how your code does it, and how you’d like it to work.

Have a good day!


#3

Hi.

I want there is some ability to let compiler to know that:

  sumFunc[SomeTypte]

Compilation completed successfully

  sumFunc

Compilation completed with error: you must specify type paramter

I don’t know how it may be done.
May be with required, for example:

   def somFunc[required T]:SomeType = ???

May be with @DisableImplict for example:

class Holder[T]

@DisableImplict("You must specify parameter")
implicit def holderNothing():Hoder[Nothing] = ???

def somFunc[required T](implicit h:Hoder[T]):SomeType = ???

Сurrently, I write somethink like:

class Holder[T]

@annotation.implicitAmbiguous("You must specify parameter")
implicit def holderNothing():Hoder[Nothing] = ???
implicit def holderNothing():Hoder[Nothing] = ???

def somFunc[required T](implicit h:Hoder[T]):SomeType = ???

But it seems bad decision.


#4

I think you’re talking about two different things. Your last example is not related to the example that you link in the SO question. Let’s tackle the discrepancy first.

sumFunc with no type parameters is inferred to be Nothing and then the implicit search is triggered for whatever type you’re looking for, in this case Holder. If the implicit Holder[Nothing] did not exist in your example, then sumFunc with no type parameter would fail, as shown by the following REPL session:

➜  Code scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> class Holder[T]
defined class Holder

scala> def sumFunc[T: Holder]: T = ???
sumFunc: [T](implicit evidence$1: Holder[T])T

scala> sumFunc
<console>:13: error: could not find implicit value for evidence parameter of type Holder[T]
       sumFunc
       ^

When you add implicit def holderNothing: Holder[Nothing] = ??? and try again, sumFunc succeeds because it’s inferred to be sumFunc[Nothing] and there exists an implicit Holder[Nothing].

So, coming back to your initial example with Form and TypeTag (which I haven’t fully understood, but I’ll use the example in the SO question as a reference), what you seem to want is to avoid a definition type to be inferred to Nothing. For example, in val person = find("Joe"), person is typed as Nothing because the type in find is not set at the call-site and the return type is the same polymorphic type T, with no further type restrictions. This is the expected behavior of the code.

The Scala way of avoiding Nothing to be inferred is to create your own type class and then restrict the return type the function will return by using implicits at the definition site of find (or sumFunc in your previous case). So long as you don’t define the implicit for Nothing, the code find("Joe") will fail to compile if you implement something like the answer in the SO question.


#5

Sorry, I must be more precise

Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_121).
Type in expressions for evaluation. Or try :help.

scala> import scala.reflect.runtime.universe._

class Form[T](implicit tag: TypeTag[T]){
  def meta: TypeTag[_] = tag
}

trait SomeView{
  def someMethod():Unit = ???
}

//I need that `Form` has meta data about it's view. 

new Form[SomeView] with SomeView


new Form with SomeView   //It compiles, but it has no sence, it's error 
import scala.reflect.runtime.universe._

scala>      |      | defined class Form

scala>      |      | defined trait SomeView

scala> 
scala> res0: Form[SomeView] with SomeView = $anon$1@10e4ce98

scala> 
scala> res1: Form[Nothing] with SomeView = $anon$1@20505460

scala> 

So if I want to save user from errors, I need some hack

Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_121).
Type in expressions for evaluation. Or try :help.

scala> import scala.annotation.implicitAmbiguous
import scala.reflect.runtime.universe._
class TagHolder[T ](val tag:TypeTag[T] )

@implicitAmbiguous("Set parameter")
implicit def tagHolder1: TagHolder[Nothing] = null
implicit def tagHolder2: TagHolder[Nothing] = null
implicit def tagHolder[T ](implicit tag: TypeTag[T]):TagHolder[T] = new TagHolder(tag)


class Form[T](implicit th: TagHolder[T]){
  def meta: TypeTag[_] = th.tag
}

trait SomeView{
  def someMethod():Unit = ???
}  
import scala.annotation.implicitAmbiguous

scala> import scala.reflect.runtime.universe._

scala> defined class TagHolder

scala>      | tagHolder1: TagHolder[Nothing]

scala> tagHolder2: TagHolder[Nothing]

scala> tagHolder: [T](implicit tag: reflect.runtime.universe.TypeTag[T])TagHolder[T]

scala>      |      | defined class Form

scala>      |      | defined trait SomeView

scala> new Form[SomeView] with SomeView  
res0: Form[SomeView] with SomeView = $anon$1@331fe6d4

scala> new Form with SomeView  
<console>:21: error: Set parameter
       new Form with SomeView
           ^

scala> 

#6

@AMatveev But it’s not a hack, it’s the solution you should use given that an implicit instance for TypeTag[Nothing] exists!

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> implicitly[TypeTag[Nothing]]
res3: reflect.runtime.universe.TypeTag[Nothing] = TypeTag[Nothing]

#7
 import scala.reflect.runtime.universe._

class Form[T](implicit tag: TypeTag[T]){
  def meta: TypeTag[_] = tag
}

trait SomeView{
  def someMethod():Unit = ???
}


new Form with SomeView   //It compiles, but it has no sence, it's error 

...
scala> res1: Form[Nothing] with SomeView = $anon$1@20505460

scala> 

‘’’
@implicitAmbiguous(“Set parameter”)
implicit def tagHolder1: TagHolder[Nothing] = null
implicit def tagHolder2: TagHolder[Nothing] = null
‘’’
I don’t think it is a good solution for disable Nothing.

Moreover I think there are many other cases, for example with orm It’s quite common to write something like

val id = 10L
val  person = session.load[Person](id)

And type define table. If type is not defined it is error.
it would be very good if IDE can assist that case.(insert “[]” automaticly :wink: [Just a litle dream :)] )


#8

We could debate whether if Scala has no information how to infer a type argument, it should arbitrarily pick Nothing. I’ve had runtime errors when I assumed Scala could infer the right type from the context when in fact it inferred Nothing. I think there’s a reasonable case to be made, that if Scala can infer nothing about a type argument, it should not infer Nothing. :wink: Instead it would be an error to omit it.


#9

I think there’a a generic way to tell that some generic bound shouldn’t be set to Nothing. I’ve created typeclass NotNothing here:

import scala.annotation.implicitAmbiguous

object NoNothing {
  class NotNothing[T]

  object NotNothing {
    implicit def good[T]: NotNothing[T] = null

    @implicitAmbiguous("Specify generic type other than Nothing")
    implicit def wrong1: NotNothing[Nothing] = null
    implicit def wrong2: NotNothing[Nothing] = null
  }

  def method[T: NotNothing](message: String): Unit = println(message)

  def main(args: Array[String]): Unit = {
    method[Int]("Hello!")
    import NotNothing.wrong1 // comment out to see compilation error
    method[Nothing]("Forced Nothing!")
  }
}

Instead of method[required T] we have method[T: NotNothing]


#10

That is exactly what the SO answer proposes too, and what I claimed to be the solution :wink: Thanks for writing down the code snippet in the thread, I’m sure it will be useful for others in the future.


#11

Note that this trick for implicit negation won’t work in Dotty, instead one must use the Not class from the standard library, see https://github.com/lampepfl/dotty/blob/master/library/src/scala/implicits/Not.scala (we can still get code to cross-compile if we get this class in the Scala 2 standard library).


#12

OK, I’ve missed that SO answer. Anyway, I think my code snippet is more straightforward :slight_smile:

As for Dotty, I haven’t yet started playing with it.


#13

In Dotty, ambiguity is a global error

What does that mean?


#14

See point 4 of http://dotty.epfl.ch/docs/reference/changed/implicit-resolution.html


#15

The following ticket keeps track of the addition of Not to the Scala 2 standard library for 2.13.0-M5:


#16

I have tested this code snippet in scastie . It works correctly.

I don’t understand how Not class can help in this task.

I think It would be better if there was anotation like:

@implicitError("Specify generic type other than Nothing")
implicit def wrong1: NotNothing[Nothing] = null

#17

Sometimes it makes sense to omit a type parameter, because it is not needed, like

**Welcome to Scala 2.12.4 (OpenJDK 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.
^

def m[T](opt: Option[T]): String = opt.map(x => "I got some " + x.toString).getOrElse(“I got nothing.”)
m: [T](opt: Option[T])String

m(None)
res1: String = I got nothing.**

In this case, we don’t want an error and Nothing makes perfect sense. Should we instead have a type Unknown? Would Unknown ever behave differently from Nothing?


#18

In that case Scala is inferring Nothing because it has information that suggests Nothing, namely that None extends Option[Nothing]. That’s fine by me. What I’m arguing is that Scala should kick the habit of making inferences when it has zero information at all about what to infer. At that point Nothing is not even a reasonable guess, it’s simply picking Nothing because it doesn’t want to give up.


#19

Oh, I’m sorry, that was a bad example. Here is a better one:

**Welcome to Scala 2.12.4 (OpenJDK 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.

def isEmpty[T](iterable: Iterable[T]): Boolean = iterable.isEmpty
isEmpty: [T](iterable: Iterable[T])Boolean

isEmpty(List.empty)
res0: Boolean = true**

List.empty[A] has a type parameter, but it is not needed here.


#20

I see your point, but I think if you put such cases on one side of the scale and cases where Nothing is a bad choice, I’m not sure who wins.