developer tip

유형 차이 적용

optionbox 2020. 12. 30. 08:04
반응형

유형 차이 적용


Scala에서는 컴파일 타임에 형식 평등을 적용 할 수 있습니다. 예를 들면 :

case class Foo[A,B]( a: A, b: B )( implicit ev: A =:= B )

scala> Foo( 1, 2 )
res3: Foo[Int,Int] = Foo(1,2)

scala> Foo( 1, "2" )
<console>:10: error: Cannot prove that Int =:= java.lang.String.

유형 A와 유형 B가 달라야한다는 것을 강제하는 방법이 있습니까?


Jean-Philippe의 아이디어에서 벗어나면 다음과 같이 작동합니다.

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  implicit def nequal[A,B](implicit same: A =:= B = null): =!=[A,B] = 
    if (same != null) sys.error("should not be called explicitly with same type")
    else new =!=[A,B]
}     

case class Foo[A,B](a: A, b: B)(implicit e: A =!= B)

그때:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

"속임수"에 대한 검사는 항상 피할 수 있기 때문에 다음과 같이 단순화 할 수 있습니다 (예 : Foo(1, 1)(null)또는 =!=.nequal(null)).

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def nequal[A,B]: =!=[A,B] = new =!=[A,B]
}

모호성을 활용하는 더 간단한 솔루션이 있습니다.

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

원래 사용 사례,

case class Foo[A,B](a : A, b : B)(implicit ev: A =!= B)
new Foo(1, "1")
new Foo("foo", Some("foo"))

// These don't compile
// new Foo(1, 1)
// new Foo("foo", "foo")
// new Foo(Some("foo"), Some("foo"))

최신 정보

우리는 이것을 나의 "마법의 유형 시스템 트릭" (@jpp ;-)에게 다음과 같이 연관시킬 수 있습니다 .

type ¬[T] = T => Nothing
implicit def neg[T, U](t : T)(implicit ev : T =!= U) : ¬[U] = null

def notString[T <% ¬[String]](t : T) = t

샘플 REPL 세션,

scala> val ns1 = notString(1)
ns1: Int = 1

scala> val ns2 = notString(1.0)
ns2: Double = 1.0

scala> val ns3 = notString(Some("foo"))
ns3: Some[java.lang.String] = Some(foo)

scala> val ns4 = notString("foo")
<console>:14: error: No implicit view available from 
  java.lang.String => (String) => Nothing.
       val ns4 = notString2("foo")
                            ^

Miles Sabin의 첫 번째 솔루션의 단순성과 효율성이 마음에 들었지만 우리가 얻은 오류가 그다지 도움이되지 않는다는 사실에 약간 불만족 스러웠습니다.

예를 들어 다음과 같은 정의를 사용합니다.

def f[T]( implicit e: T =!= String ) {}

수행하려는 시도는 다음 f[String]과 함께 컴파일되지 않습니다.

<console>:10: error: ambiguous implicit values:
 both method neqAmbig1 in object =!= of type [A]=> =!=[A,A]
 and method neqAmbig2 in object =!= of type [A]=> =!=[A,A]
 match expected type =!=[String,String]
              f[String]
               ^

차라리 컴파일러가 "T is not different from String"줄을 따라 말 해주길 원합니다. 모호성 오류를 암시 적 오류로 바꾸는 방식으로 또 다른 수준의 암시 적 내용을 추가하면 매우 쉽습니다. 오류를 찾았습니다 . 그런 다음 implicitNotFound주석을 사용하여 사용자 지정 오류 메시지를 내보낼 수 있습니다 .

@annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A,B]
object =!= {
  class Impl[A, B]
  object Impl {
    implicit def neq[A, B] : A Impl B = null
    implicit def neqAmbig1[A] : A Impl A = null
    implicit def neqAmbig2[A] : A Impl A = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A =!= B = null
}

이제 다음을 호출 해 보겠습니다 f[String].

scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
              f[String]
           ^

그게 낫다. 감사합니다 컴파일러.

컨텍스트 바운드 구문 설탕을 좋아하는 사람들을위한 마지막 트릭으로이 별칭을 정의 할 수 있습니다 (람다 유형 기반) :

type IsNot[A] = { type λ[B] = A =!= B }

그런 다음 다음 f과 같이 정의 할 수 있습니다 .

def f[T:IsNot[String]#λ] {}

읽기 쉬운 지 여부는 매우 주관적입니다. 어쨌든 완전한 암시 적 매개 변수 목록을 작성하는 것보다 확실히 짧습니다.

업데이트 : 완전성을 위해 다음과 A같은 하위 유형이 아닌 표현을위한 동등한 코드 가 있습니다 B.

@annotation.implicitNotFound(msg = "Cannot prove that ${A} <:!< ${B}.")
trait <:!<[A,B]
object <:!< {
  class Impl[A, B]
  object Impl {
    implicit def nsub[A, B] : A Impl B = null
    implicit def nsubAmbig1[A, B>:A] : A Impl B = null
    implicit def nsubAmbig2[A, B>:A] : A Impl B = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A <:!< B = null
}

type IsNotSub[B] = { type λ[A] = A <:!< B }

그리고 그것을 표현 A하기 위해 B다음과 같이 변환 할 수 없습니다 .

@annotation.implicitNotFound(msg = "Cannot prove that ${A} <%!< ${B}.")
trait <%!<[A,B]
object <%!< {
  class Impl[A, B]
  object Impl {
    implicit def nconv[A, B] : A Impl B = null
    implicit def nconvAmbig1[A<%B, B] : A Impl B = null
    implicit def nconvAmbig2[A<%B, B] : A Impl B = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A <%!< B = null
}

type IsNotView[B] = { type λ[A] = A <%!< B }

를 기반으로 Landei 의 생각, 다음이 작동하는 것 같다 :

case class Foo[A, B <: A, C <: A]( a: B, b: C)(implicit f: AnyVal <:< A)

scala> Foo(1f, 1.0)
res75: Foo[AnyVal,Float,Double] = Foo(1.0,1.0)

scala> Foo("", 1.0)
res76: Foo[Any,java.lang.String,Double] = Foo(,1.0)

scala> Foo(1f, 1f)
<console>:10: error: Cannot prove that AnyVal <:< Float.
       Foo(1f, 1f)
          ^

scala> Foo("", "")
<console>:10: error: Cannot prove that AnyVal <:< java.lang.String.
       Foo("", "")
          ^

scala> Foo("", 1)
res79: Foo[Any,java.lang.String,Int] = Foo(,1)

또 다른 시도가 있습니다.

class =!=[A, B] private () extends NotNull

object =!= {
  implicit def notMeantToBeCalled1[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def notMeantToBeCalled2[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def unambigouslyDifferent[A, B](implicit same: A =:= B = null): =!=[A, B] =
    if (same != null) error("should not be called explicitly with the same type")
    else new =!=
}

case class Foo[A, B](a: A, b: B)(implicit ev: A =!= B)

그런 다음 다시 :

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

내 다른 제안에서, 여기에 목표처럼 컴파일시 모호성을 소개 할 때하는 것입니다 AB동일합니다. 여기서는와 A동일한 경우에 대해 두 가지 B암시 적을 제공하고 그렇지 않은 경우에는 모호하지 않은 암시 적을 제공합니다.

Note that the problem is that you could still explicitly provide the implicit parameter by manually calling =!=.notMeantToBeCalled1 or =!=.unambigouslyDifferent. I couldn't think of a way to prevent this at compile time. However, we can throw an exception at runtime, with the trick that unambigouslyDifferent requires an evidence parameter itself indicating whether A is the same as B. But wait... Aren't we trying to prove the exact opposite? Yes, and that's why that same implicit parameter has a default value of null. And we expect it to be null for all legal uses — the only time where it would not be null is when a nasty user calls e.g. Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float]), and there we can prevent this cheating by throwing an exception.


How about something like this, then?

class Foo[A, B] private (a: A, b: B)

object Foo {
  def apply[A, B <: A, C >: A <: B](a: A, b: B)(implicit nothing: Nothing) = nothing
  def apply[A, B >: A, C >: B <: A](a: A, b: B)(implicit nothing: Nothing, dummy: DummyImplicit) = nothing
  def apply[A, B](a: A, b: B): Foo[A, B] = new Foo(a, b)
}

Then:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

The idea is to make resolution ambiguous when Ais the same as B, and unambiguous when they are not the same. To further emphasize that the ambiguous methods should not be called, I added an implicit of type Nothing, which should never be around (and should certainly look wrong to the caller if they try to insert one explicitly). (The role of the DummyImplicit is just to give a different signature to the first two methods.)


This is not an answer, just the beginnings of what I could think is an answer. The code below will return either an Yes or an No depending on whether the types are equal or not, if you ask for implicitly[AreEqual[A,B]]. How to go from there to actually making a check I haven't been able to figure out. Maybe the whole approach is doomed, maybe someone can make something out of it. Mind you, implicitly[No[A, B]] will always return something, one can't use that. :-(

class AreEqual[A, B]
trait LowerPriorityImplicits {
  implicit def toNo[A : Manifest, B : Manifest]: No[A, B] = No[A, B]
}
object AreEqual extends LowerPriorityImplicits {
  implicit def toYes[A, B](implicit ev: A =:= B, m1: Manifest[A], m2: Manifest[B]): Yes[A, B] = Yes[A, B]
}

case class Yes[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "Yes(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}
case class No[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "No(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}

Test:

scala> implicitly[AreEqual[String, Option[String]]]
res0: AreEqual[String,Option[String]] = No(java.lang.String, scala.Option[java.lang.String])

scala> implicitly[AreEqual[String, String]]
res1: AreEqual[String,String] = Yes(java.lang.String, java.lang.String)

ReferenceURL : https://stackoverflow.com/questions/6909053/enforce-type-difference

반응형