Scala: specify a default generic type instead of Nothing

前端 未结 3 1206
小蘑菇
小蘑菇 2020-12-31 08:04

I have a pair of classes that look something like this. There\'s a Generator that generates a value based on some class-level values, and a GeneratorFact

相关标签:
3条回答
  • 2020-12-31 08:35

    One option is to move the summoning of the CanBuildFrom to a place where it (or, rather, its instances) can help to determine S,

    case class Generator[T,S](a: T, b: T, c: T)(implicit bf: CanBuildFrom[S, T, S]) {
      def generate : S =
        bf() += (a, b, c) result
    }
    

    Sample REPL session,

    scala> val g2 = Generator('a', 'b', 'c')
    g2: Generator[Char,String] = Generator(a,b,c)
    
    scala> g2.generate
    res0: String = abc
    

    Update

    The GeneratorFactory will also have to be modified so that its build method propagates an appropriate CanBuildFrom instance to the Generator constructor,

    case class GeneratorFactory[T]() {
      def build[S](seq: S)(implicit conv: S => Seq[T], bf: CanBuildFrom[S, T, S]) =
        Generator[T, S](seq(0), seq(1), seq(2))
    }
    

    Not that with Scala < 2.10.0 you can't mix view bounds and implicit parameter lists in the same method definition, so we have to translate the bound S <% Seq[T] to its equivalent implicit parameter S => Seq[T].

    0 讨论(0)
  • 2020-12-31 08:37

    This does not directly answer your main question, as I think others are handling that. Rather, it is a response to your request for default values for type arguments.

    I have put some thought into this, even going so far as starting to write a proposal for instituting a language change to allow it. However, I stopped when I realized where the Nothing actually comes from. It is not some sort of "default value" like I expected. I will attempt to explain where it comes from.

    In order to assign a type to a type argument, Scala uses the most specific possible/legal type. So, for example, suppose you have "class A[T](x: T)" and you say "new A[Int]". You directly specified the value of "Int" for T. Now suppose that you say "new A(4)". Scala knows that 4 and T have to have the same type. 4 can have a type anywhere between "Int" and "Any". In that type range, "Int" is the most specific type, so Scala creates an "A[Int]". Now suppose that you say "new A[AnyVal]". Now, you are looking for the most specific type T such that Int <: T <: Any and AnyVal <: T <: AnyVal. Luckily, Int <: AnyVal <: Any, so T can be AnyVal.

    Continuing, now suppose that you have "class B[S >: String <: AnyRef]". If you say "new B", you won't get an B[Nothing]. Rather you will find that you get a B[String]. This is because S is being constrained as String <: S <: AnyRef and String is at the bottom of that range.

    So, you see, for "class C[R]", "new C" doesn't give you a C[Nothing] because Nothing is some sort of default value for type arguments. Rather, you get a C[Nothing] because Nothing is the lowest thing that R can be (if you don't specify otherwise, Nothing <: R <: Any).

    This is why I gave up on my default type argument idea: I couldn't find a way to make it intuitive. In this system of restricting ranges, how do you implement a low-priority default? Or, does the default out-priority the "choose the lowest type" logic if it is within the valid range? I couldn't think of a solution that wouldn't be confusing for at least some cases. If you can, please let me know, as I'm very interested.

    edit: Note that the logic is reversed for contravariant parameters. So if you have "class D[-Q]" and you say "new D", you get a D[Any].

    0 讨论(0)
  • 2020-12-31 08:40

    Is there a reason you don't want to use a base trait and then narrow S as needed in its subclasses? The following for example fits your requirements:

    import scala.collection.generic.CanBuildFrom
    
    trait Generator[T] {
      type S
      def a: T; def b: T; def c: T
      def generate(implicit bf: CanBuildFrom[S, T, S]): S = bf() += (a, b, c) result
    }
    
    object Generator {
      def apply[T](x: T, y: T, z: T) = new Generator[T] {
        type S = Seq[T]
        val (a, b, c) = (x, y, z)
      }
    }
    
    case class GeneratorFactory[T]() {
      def build[U <% Seq[T]](seq: U) = new Generator[T] {
        type S = U
        val Seq(a, b, c, _*) = seq: Seq[T]
      }
    }
    

    I've made S an abstract type to keep it a little more out of the way of the user, but you could just as well make it a type parameter.

    0 讨论(0)
提交回复
热议问题