Programing

Scala 이중 정의 (2 개의 메서드는 동일한 유형의 삭제를 가짐)

lottogame 2020. 11. 11. 07:50
반응형

Scala 이중 정의 (2 개의 메서드는 동일한 유형의 삭제를 가짐)


나는 이것을 스칼라로 썼고 컴파일되지 않을 것입니다.

class TestDoubleDef{
  def foo(p:List[String]) = {}
  def foo(p:List[Int]) = {}
}

컴파일러는 다음을 알립니다.

[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit

JVM에는 제네릭에 대한 기본 지원이 없으므로이 오류를 이해합니다.

나는에 대해 래퍼를 쓸 수 List[String]List[Int]하지만 난 게으른 해요 :)

의심 스럽지만 표현하는 다른 방식 List[String]이 같은 유형이 List[Int]아닌가?

감사.


암시 적 사용에 대한 Michael Krämer의 아이디어가 마음에 들지만 더 직접적으로 적용될 수 있다고 생각합니다.

case class IntList(list: List[Int])
case class StringList(list: List[String])

implicit def il(list: List[Int]) = IntList(list)
implicit def sl(list: List[String]) = StringList(list)

def foo(i: IntList) { println("Int: " + i.list)}
def foo(s: StringList) { println("String: " + s.list)}

나는 이것이 매우 읽기 쉽고 간단하다고 생각합니다.

[최신 정보]

작동하는 것처럼 보이는 또 다른 쉬운 방법이 있습니다.

def foo(p: List[String]) { println("Strings") }
def foo[X: ClassManifest](p: List[Int]) { println("Ints") }
def foo[X: ClassManifest, Y: ClassManifest](p: List[Double]) { println("Doubles") }

모든 버전에 대해 추가 유형 매개 변수가 필요하므로 확장되지는 않지만 3 ~ 4 개의 버전은 괜찮다고 생각합니다.

[업데이트 2]

정확히 두 가지 방법에 대해 또 다른 좋은 트릭을 찾았습니다.

def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}

더미 암시 적 값을 발명하는 대신에 정확히 만들어진 것처럼 보이는 DummyImplicit정의를 사용할 수 있습니다 Predef.

class TestMultipleDef {
  def foo(p:List[String]) = ()
  def foo(p:List[Int])(implicit d: DummyImplicit) = ()
  def foo(p:List[java.util.Date])(implicit d1: DummyImplicit, d2: DummyImplicit) = ()
}

유형 삭제의 경이로 인해 컴파일 중에 메서드 목록의 형식 매개 변수가 지워 지므로 두 메서드가 모두 동일한 서명으로 줄어 듭니다. 이는 컴파일러 오류입니다.


Michael Krämer의 솔루션 을 이해하려면 암시 적 매개 변수 유형이 중요하지 않다는 것을 인식해야합니다. 무엇 입니다 중요한 것은 자신의 유형이 서로 다른 점이다.

다음 코드는 동일한 방식으로 작동합니다.

class TestDoubleDef {
   object dummy1 { implicit val dummy: dummy1.type = this }
   object dummy2 { implicit val dummy: dummy2.type = this }

   def foo(p:List[String])(implicit d: dummy1.type) = {}
   def foo(p:List[Int])(implicit d: dummy2.type) = {}
}

object App extends Application {
   val a = new TestDoubleDef()
   a.foo(1::2::Nil)
   a.foo("a"::"b"::Nil)
}

바이트 코드 수준에서 fooJVM 바이트 코드는 암시 적 매개 변수 또는 여러 매개 변수 목록을 전혀 알지 못하므로 메서드는 두 인수 메서드가됩니다. foo호출 사이트 에서 Scala 컴파일러는 전달되는 목록의 유형 (나중까지 지워지지 않음)을 확인하여 호출 할 적절한 메서드 (따라서 전달할 적절한 더미 객체)를 선택합니다.

더 장황하지만이 접근 방식은 호출자가 암시 적 인수를 제공해야하는 부담을 덜어줍니다. 실제로 dummyN 객체가 TestDoubleDef클래스 전용 인 경우에도 작동합니다 .


Viktor Klang이 이미 말했듯이 일반 유형은 컴파일러에 의해 지워집니다. 다행히 해결 방법이 있습니다.

class TestDoubleDef{
  def foo(p:List[String])(implicit ignore: String) = {}
  def foo(p:List[Int])(implicit ignore: Int) = {}
}

object App extends Application {
  implicit val x = 0
  implicit val y = ""

  val a = new A()
  a.foo(1::2::Nil)
  a.foo("a"::"b"::Nil)
}

팁을 위한 Michid 에게 감사합니다 !


여기서 Daniel응답Sandor Murakozi 의 응답을 결합하면 다음 과 같은 결과를 얻습니다.

@annotation.implicitNotFound(msg = "Type ${T} not supported only Int and String accepted")   
sealed abstract class Acceptable[T]; object Acceptable {
        implicit object IntOk extends Acceptable[Int]
        implicit object StringOk extends Acceptable[String]
}

class TestDoubleDef {
   def foo[A : Acceptable : Manifest](p:List[A]) =  {
        val m = manifest[A]
        if (m equals manifest[String]) {
            println("String")
        } else if (m equals manifest[Int]) {
            println("Int")
        } 
   }
}

typesafe (ish) 변형을 얻습니다.

scala> val a = new TestDoubleDef
a: TestDoubleDef = TestDoubleDef@f3cc05f

scala> a.foo(List(1,2,3))
Int

scala> a.foo(List("test","testa"))
String

scala> a.foo(List(1L,2L,3L))
<console>:21: error: Type Long not supported only Int and String accepted
   a.foo(List(1L,2L,3L))
        ^             

scala> a.foo("test")
<console>:9: error: type mismatch;
 found   : java.lang.String("test")
 required: List[?]
       a.foo("test")
             ^

로직은 다음과 같이 유형 클래스에 포함될 수도 있습니다 ( jsuereth 덕분에 ) : @ annotation.implicitNotFound (msg = "Foo는 $ {T}를 지원하지 않습니다. Int 및 String 만 허용됨") 봉인 된 특성 Foo [T] {def apply (목록 : 목록 [T]) : 단위}

object Foo {
   implicit def stringImpl = new Foo[String] {
      def apply(list : List[String]) = println("String")
   }
   implicit def intImpl = new Foo[Int] {
      def apply(list : List[Int]) =  println("Int")
   }
} 

def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)

다음을 제공합니다.

scala> @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted") 
     | sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
     |         implicit def stringImpl = new Foo[String] {
     |           def apply(list : List[String]) = println("String")
     |         }
     |         implicit def intImpl = new Foo[Int] {
     |           def apply(list : List[Int]) =  println("Int")
     |         }
     |       } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit

scala> foo(1)
<console>:8: error: type mismatch;
 found   : Int(1)
 required: List[?]
       foo(1)
           ^    
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:32: error: Foo does not support Double only Int and String accepted
foo(List(1.0))
        ^

implicitly[Foo[A]].apply(x)컴파일러 implicitly[Foo[A]](x)는 그것이 우리 implicitly가 매개 변수로 호출 한다는 것을 의미 한다고 생각 하기 때문에 작성해야합니다 .


너무 좋지 않고 실제로 형식이 안전하지 않더라도 다른 방법이 있습니다.

import scala.reflect.Manifest

object Reified {

  def foo[T](p:List[T])(implicit m: Manifest[T]) = {

    def stringList(l: List[String]) {
      println("Strings")
    }
    def intList(l: List[Int]) {
      println("Ints")
    }

    val StringClass = classOf[String]
    val IntClass = classOf[Int]

    m.erasure match {
      case StringClass => stringList(p.asInstanceOf[List[String]])
      case IntClass => intList(p.asInstanceOf[List[Int]])
      case _ => error("???")
    }
  }


  def main(args: Array[String]) {
      foo(List("String"))
      foo(List(1, 2, 3))
    }
}

The implicit manifest paramenter can be used to "reify" the erased type and thus hack around erasure. You can learn a bit more about it in many blog posts,e.g. this one.

What happens is that the manifest param can give you back what T was before erasure. Then a simple dispatch based on T to the various real implementation does the rest.

Probably there is a nicer way to do the pattern matching, but I haven't seen it yet. What people usually do is matching on m.toString, but I think keeping classes is a bit cleaner (even if it's a bit more verbose). Unfortunately the documentation of Manifest is not too detailed, maybe it also has something that could simplify it.

A big disadvantage of it is that it's not really type safe: foo will be happy with any T, if you can't handle it you will have a problem. I guess it could be worked around with some constraints on T, but it would further complicate it.

And of course this whole stuff is also not too nice, I'm not sure if it worth doing it, especially if you are lazy ;-)


Instead of using manifests you could also use dispatchers objects implicitly imported in a similar manner. I blogged about this before manifests came up: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/

This has the advantage of type safety: the overloaded method will only be callable for types which have dispatchers imported into the current scope.


Nice trick I've found from http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html by Aaron Novstrup

Beating this dead horse some more...

It occurred to me that a cleaner hack is to use a unique dummy type for each method with erased types in its signature:

object Baz {
    private object dummy1 { implicit val dummy: dummy1.type = this }
    private object dummy2 { implicit val dummy: dummy2.type = this } 

    def foo(xs: String*)(implicit e: dummy1.type) = 1
    def foo(xs: Int*)(implicit e: dummy2.type) = 2
} 

[...]


I tried improving on Aaron Novstrup’s and Leo’s answers to make one set of standard evidence objects importable and more terse.

final object ErasureEvidence {
    class E1 private[ErasureEvidence]()
    class E2 private[ErasureEvidence]()
    implicit final val e1 = new E1
    implicit final val e2 = new E2
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1) = 1
    def foo(xs: Int*)(implicit e:E2) = 2
}

But that will cause the compiler to complain that there are ambiguous choices for the implicit value when foo calls another method which requires an implicit parameter of the same type.

Thus I offer only the following which is more terse in some cases. And this improvement works with value classes (those that extend AnyVal).

final object ErasureEvidence {
   class E1[T] private[ErasureEvidence]()
   class E2[T] private[ErasureEvidence]()
   implicit def e1[T] = new E1[T]
   implicit def e2[T] = new E2[T]
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1[Baz]) = 1
    def foo(xs: Int*)(implicit e:E2[Baz]) = 2
}

If the containing type name is rather long, declare an inner trait to make it more terse.

class Supercalifragilisticexpialidocious[A,B,C,D,E,F,G,H,I,J,K,L,M] {
    private trait E
    def foo(xs: String*)(implicit e:E1[E]) = 1
    def foo(xs: Int*)(implicit e:E2[E]) = 2
}

However, value classes do not allow inner traits, classes, nor objects. Thus also note Aaron Novstrup’s and Leo’s answers do not work with a value classes.


I didn't test this, but why wouldn't an upper bound work?

def foo[T <: String](s: List[T]) { println("Strings: " + s) }
def foo[T <: Int](i: List[T]) { println("Ints: " + i) }

Does the erasure translation to change from foo( List[Any] s ) twice, to foo( List[String] s ) and foo( List[Int] i ):

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108

I think I read that in version 2.8, the upper bounds are now encoded that way, instead of always an Any.

To overload on covariant types, use an invariant bound (is there such a syntax in Scala?...ah I think there isn't, but take the following as conceptual addendum to the main solution above):

def foo[T : String](s: List[T]) { println("Strings: " + s) }
def foo[T : String2](s: List[T]) { println("String2s: " + s) }

then I presume the implicit casting is eliminated in the erased version of the code.


UPDATE: The problem is that JVM erases more type information on method signatures than is "necessary". I provided a link. It erases type variables from type constructors, even the concrete bound of those type variables. There is a conceptual distinction, because there is no conceptual non-reified advantage to erasing the function's type bound, as it is known at compile-time and does not vary with any instance of the generic, and it is necessary for callers to not call the function with types that do not conform to the type bound, so how can the JVM enforce the type bound if it is erased? Well one link says the type bound is retained in metadata which compilers are supposed to access. And this explains why using type bounds doesn't enable overloading. It also means that JVM is a wide open security hole since type bounded methods can be called without type bounds (yikes!), so excuse me for assuming the JVM designers wouldn't do such an insecure thing.

At the time I wrote this, I didn't understand that stackoverflow was a system of rating people by quality of answers like some competition over reputation. I thought it was a place to share information. At the time I wrote this, I was comparing reified and non-reified from a conceptual level (comparing many different languages), and so in my mind it didn't make any sense to erase the type bound.

참고URL : https://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-have-the-same-type-erasure

반응형