الأنماط مطابقة الأنواع الهيكلية في سكالا
-
22-09-2019 - |
سؤال
لماذا هذه الطباعة WTF؟ هل مطابقة الأنماط لا تعمل على الأنواع الهيكلية؟
"hello" match {
case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
case _ => println("okie dokie")
}
المحلول
تشغيل هذا المثال في مترجم سكالا مع تحذيرات غير محددة على (scala -unchecked
) ينتج التحذير التالي: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure
. لسوء الحظ ، لا يمكن التحقق من نوع عام مثل هذا في وقت التشغيل لأن JVM لا يحتوي على جيرلات عامة.
كل ما يراه JVM في هذا النمط هو:
"hello" match {
case s: Object => ...
case annon: Object => ...
}
تعديل: ردا على تعليقاتك ، كنت أفكر في حل ولكن لم يكن لدي الوقت لنشره بالأمس. لسوء الحظ ، حتى لو ينبغي العمل ، فشل المترجم في ضخ ما Manifest
.
المشكلة التي تريد حلها هي مقارنة ما إذا كان كائن من نوع هيكلي معين. إليك بعض التعليمات البرمجية التي كنت أفكر فيها (Scala 2.8-R20019 ، حيث تحطمت Scala 2.7.6.final لي عدة مرات أثناء اللعب بأفكار مماثلة)
type Foo = AnyRef { def doesNotExist(i: Int, x: List[_]): Double }
def getManifest[T](implicit m: Manifest[T]) = m
def isFoo[T](x: T)(implicit mt: Manifest[T]) =
mt == getManifest[Foo]
طريقة isFoo
يقارن بشكل أساسي مظاهر الفصل x
من Foo
. في عالم مثالي ، يجب أن يكون بيان النوع الهيكلي مساوياً للبيان من أي نوع يحتوي على الطرق المطلوبة. على الأقل هذا هو قطار فكرتي. لسوء الحظ ، فشل هذا في التجميع ، حيث يقوم المترجم بحقن أ Manifest[AnyRef]
بدل من Manifest[Foo]
عند الاتصال getManifest[Foo]
. ومن المثير للاهتمام ، إذا لم تستخدم نوعًا هيكليًا (على سبيل المثال ، type Foo = String
) ، هذا الرمز يجمع ويعمل كما هو متوقع. سأقوم بنشر سؤال في مرحلة ما لمعرفة سبب فشل هذا في الأنواع الهيكلية - هل هو قرار تصميم ، أو أنه مجرد مشكلة في واجهة برمجة تطبيقات الانعكاس التجريبي.
إذا فشل ذلك ، يمكنك دائمًا استخدام انعكاس Java لمعرفة ما إذا كان الكائن يحتوي على طريقة.
def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
try {
x.getClass.getMethod(name, params: _*)
true
}
catch {
case _ => false
}
}
الذي يعمل كما هو متوقع:
containsMethod("foo", "concat", classOf[String]) // true
containsMethod("foo", "bar", classOf[List[Int]]) // false
... لكنها ليست لطيفة للغاية.
لاحظ أيضًا أن هيكل النوع الهيكلي غير متوفر في وقت التشغيل. إذا كان لديك طريقة def foo(x: {def foo: Int}) = x.foo
, ، بعد محو ستحصل عليه def foo(x: Object) = [some reflection invoking foo on x]
, ، نوع المعلومات التي تضيع. لهذا السبب يتم استخدام الانعكاس في المقام الأول ، حيث يجب عليك استدعاء طريقة على Object
و JVM لا يعرف ما إذا كان Object
لديه هذه الطريقة.
نصائح أخرى
إذا كنت ستضطر إلى استخدام التفكير ، فيمكنك على الأقل جعلها تبدو أجمل مع مستخرج:
object WithFoo {
def foo(){
println("foo was called")
}
}
object HasFoo {
def containsMethod(x: AnyRef, name: String, params: Array[java.lang.Class[_]]) : Boolean = {
try {
x.getClass.getMethod(name, params: _*)
true
} catch {
case _ => false
}
}
def unapply(foo:AnyRef):Option[{def foo():Unit}] = {
if (containsMethod(foo, "foo", new Array[Class[_]](0))) {
Some(foo.asInstanceOf[{def foo():Unit}])
} else None
}
}
WithFoo.asInstanceOf[AnyRef] match {
case HasFoo(foo) => foo.foo()
case _ => println("no foo")
}