تعريفات الوظائف الكسول في سكالا
-
01-10-2019 - |
سؤال
لقد كنت أتعلم سكالا وأقول إنها لغة رائعة حقًا. أحب بشكل خاص إمكانات مطابقة الأنماط والأفراد الحرفيين ، لكنني أتيت من جافا سكريبت ، خلفية روبي وواحدة من أنماطاتي المفضلة في هذه اللغات هي وظيفة تعريف الأسلوب الكسول ونمط تعريف الطريقة. مثال في JavaScript
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
};
يعمل نفس الرمز مع Minor Tweaks في Ruby حيث يمكنك فقط استخدام كائن Singleton لإعادة تعريف الطريقة بعد إجراء الحساب. يأتي هذا النوع من الأشياء في متناول الجميع عندما يشارك حساب مكلف ولا تعرف في وقت مبكر إذا كنت ستحتاج إلى النتيجة. أعلم أنه في Scala يمكنني استخدام ذاكرة التخزين المؤقت لمحاكاة نفس النتيجة ، لكنني أحاول تجنب عمليات الفحص المشروطة وحتى الآن أعادت تجاربي نتائج سلبية. هل يعرف أي شخص ما إذا كان هناك وظيفة تعريف كسول أو نمط تعريف الطريقة في سكالا؟
ملاحظة: رمز JavaScript هو من Peter Michaux's موقع.
المحلول
يبدو أن كل هذا الرمز المعقد في JavaScript يحاول فقط تخزين قيمة التاريخ. في Scala ، يمكنك تحقيق نفس الشيء بشكل تافه:
lazy val foo = new Date
وإذا كنت لا ترغب حتى في صنع VAL ، ولكن تريد الاتصال بوظيفة من شأنها أن تنفذ الرمز باهظ الثمن فقط إذا احتجت إليه ، يمكنك ذلك
def maybeExpensive(doIt: Boolean, expensive: => String) {
if (doIt) println(expensive)
}
maybeExpensive(false, (0 to 1000000).toString) // (0 to 1000000).toString is never called!
maybeExpensive(true, (0 to 10).toString) // It is called and used this time
حيث النمط expensive: => String
يطلق عليه معلمة اسمها ، والتي يمكنك التفكير فيها ، "أعطني شيئًا من شأنه أن يولد سلسلة عند الطلب." لاحظ أنه إذا كنت تستخدمه مرتين ، فسيقوم بتجديده في كل مرة ، وهو المكان الذي يأتي فيه نمط Randall Schultz في:
def maybeExpensiveTwice(doIt: Boolean, expensive: => String) {
lazy val e = expensive
if (doIt) {
println(e)
println("Wow, that was " + e.length + " characters long!")
}
}
أنت الآن تنشئ فقط إذا كنت في حاجة إليها (عبر المعلمة ذات الاسم) و قم بتخزينه وإعادة استخدامه إذا كنت بحاجة إليه مرة أخرى (عبر Val Lazy).
لذا افعل ذلك بهذه الطريقة ، وليس طريقة JavaScript ، على الرغم من أنك استطاع اجعل Scala تبدو مثل JavaScript.
نصائح أخرى
سكالا lazy val
S ، التي لا يتم تقييم المهيئات التي لا يتم تقييمها إلا حتى يتم استخدام VAL. يمكن استخدام Vals البطيئة كطريقة المتغيرات المحلية.
تحتوي Scala أيضًا على معلمات طريقة الاسم ، والتي يتم لفها في تعبيرات المعلمة الفعلية في thunk ويتم تقييم thunk في كل مرة يتم الإشارة إلى المعلمة الرسمية في الجسم.
يمكن استخدامها معًا لتحقيق دلالات التقييم البطيئة مثل الافتراضي في Haskell (على الأقل في فهمي المحدود للغاية لـ Haskell).
def meth(i: => Int): Something = {
// ^^^^^^ by-name parameter syntax
lazy val ii = i
// Rest of method uses ii, not i
}
في هذه الطريقة ، سيتم تقييم التعبير المستخدم كمعلمة فعلية إما مرات صفر (إذا لم يستخدم مسار التنفيذ الديناميكي للطريقة أبدًا ii
) أو مرة واحدة (إذا كان يستخدم ii
مرة أو أكثر).
يمكنك تحديد فال كسول وهو وظيفة:
lazy val foo = {
val d = new Date
() => { d }
}
println(foo())
foo()
سيعود الآن كائن التاريخ نفسه في كل مرة ، كائن سيتم تهيئته في المرة الأولى التي يتم فيها استدعاء FOO.
لشرح الكود قليلاً ، يتم استدعاء المرة الأولى FOO () { val d = new Date; () => { d } }
يتم تنفيذها ، يتم تعيين D لقيمة تاريخ جديدة ثم يقوم بتقييم التعبير الأخير () => { d }
وتعيينها إلى قيمة FOO. ثم FOO هي وظيفة بدون معلمات تعود د.
أعتقد أن بعض المستجيبين كانوا في حيرة من أمرهم بالطريقة التي صاغتها السؤال. بناء Scala الذي تريده هنا هو تعريف كسول بسيط:
lazy val foo = new java.util.Date
سيحدث بناء كائن التاريخ مرة واحدة على الأكثر ويتم تأجيله حتى المرجع الأول إلى FOO.
لم أكن أعرف شيئًا عن روبي ، لكن Scala لديها نمط كائن Singleton أيضًا:
Welcome to Scala version 2.8.0.r22634-b20100728020027 (Java HotSpot(TM) Client VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.
scala> object LazyInit {
| val msec = { println("Hi,I'm here!"); System.currentTimeMillis }
| }
defined module LazyInit
scala> System.currentTimeMillis
res0: Long = 1282728315918
scala> println(System.currentTimeMillis +" : " + LazyInit.msec)
Hi,I'm here!
1282728319929 : 1282728319930
scala> println(System.currentTimeMillis +" : " + LazyInit.msec)
1282728322936 : 1282728319930
scala> println(System.currentTimeMillis +" : " + LazyInit.msec)
1282728324490 : 1282728319930
scala>
إذا كنت ترغب في الحصول على وظيفة ، يمكنك جعلها نوعًا فرعيًا من نوع الوظيفة:
scala> object LazyFun extends (() => Long) {
| val msec = System.currentTimeMillis
| def apply() = msec
| }
defined module LazyFun
scala> System.currentTimeMillis
res2: Long = 1282729169918
scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729190384 : 1282729190384
scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729192972 : 1282729190384
scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729195346 : 1282729190384
أعتقد أن ما تعنيه "الوظيفة الكسول" هي وظيفة وظيفة حرفية أو مجهولة.
في Scala ، يمكنك أن تفعل أشياء مثل هذا ، تشبه إلى حد كبير رمز JavaScript الذي نشرته.
val foo = () => {
val t = new Date()
val foo = () => {t}
foo()
}
println ("Hello World:" + foo())
الفرق الرئيسي هو:
- لا يمكنك إعادة تقييم FOO الخارجي
- لا توجد كلمة رئيسية "دالة" ، بدلاً من ذلك يمكنك استخدام شيء مثل (s: string) => {code}
- البيان الأخير هو قيمة الإرجاع للكتلة ، لذلك لا تحتاج إلى إضافة "عودة".