유형 차이 적용
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("", "")
내 다른 제안에서, 여기에 목표처럼 컴파일시 모호성을 소개 할 때하는 것입니다 A
와 B
동일합니다. 여기서는와 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 A
is 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
'developer tip' 카테고리의 다른 글
XML 문자열을 XML 문서로 (0) | 2020.12.30 |
---|---|
Django 쿼리 관련 필드 수 (0) | 2020.12.30 |
StringBuilder.append 체인이 문자열 연결보다 효율적입니까? (0) | 2020.12.30 |
std :: function <>과 표준 함수 포인터의 차이점은 무엇입니까? (0) | 2020.12.30 |
Mac에서 Bash를 사용하여 기본 브라우저로 .html 파일 열기 (0) | 2020.12.30 |