Value classes introduce unwanted public methods

前端 未结 5 2262
孤街浪徒
孤街浪徒 2021-02-19 03:44

Looking at some scala-docs of my libraries, it appeared to me that there is some unwanted noise from value classes. For example:

implicit class RichInt(val i: In         


        
5条回答
  •  夕颜
    夕颜 (楼主)
    2021-02-19 04:11

    It does introduce noise (note: in 2.10, in 2.11 and beyond you just declare the val private). You don't always want to. But that's the way it is for now.

    You can't get around the problem by following the private-value-class pattern because the compiler can't actually see that it's a value class at the end of it, so it goes through the generic route. Here's the bytecode:

       12: invokevirtual #24;
              //Method Definition$.IntOps:(I)LDefinition$IntOps;
       15: invokeinterface #30,  1;
              //InterfaceMethod Definition$IntOps.squared:()I
    

    See how the first one returns a copy of the class Definition$IntOps? It's boxed.

    But these two patterns work, sort of:

    (1) Common name pattern.

    implicit class RichInt(val repr: Int) extends AnyVal { ... }
    implicit class RichInt(val underlying: Int) extends AnyVal { ... }
    

    Use one of these. Adding i as a method is annoying. Adding underlying when there is nothing underlying is not nearly so bad--you'll only hit it if you're trying to get the underlying value anyway. And if you keep using the same name over and over:

    implicit class RicherInt(val repr: Int) extends AnyVal { def sq = repr * repr }
    implicit class RichestInt(val repr: Int) extends AnyVal { def cu = repr * repr * repr }
    
    scala> scala> 3.cu
    res5: Int = 27
    
    scala> 3.repr
    :10: error: type mismatch;
     found   : Int(3)
     required: ?{def repr: ?}
    Note that implicit conversions are not applicable because they are ambiguous:
     both method RicherInt of type (repr: Int)RicherInt
     and method RichestInt of type (repr: Int)RichestInt
    

    the name collision sorta takes care of your problem anyway. If you really want to, you can create an empty value class that exists only to collide with repr.

    (2) Explicit implicit pattern

    Sometimes you internally want your value to be named something shorter or more mnemonic than repr or underlying without making it available on the original type. One option is to create a forwarding implicit like so:

    class IntWithPowers(val i: Int) extends AnyVal {
      def sq = i*i
      def cu = i*i*i 
    }
    implicit class EnableIntPowers(val repr: Int) extends AnyVal { 
      def pow = new IntWithPowers(repr)
    }
    

    Now you have to call 3.pow.sq instead of 3.sq--which may be a good way to carve up your namespace!--and you don't have to worry about the namespace pollution beyond the original repr.

提交回复
热议问题