2012-03-23 10 views
11

में लूप वैरिएबल पर बंद होने पर एरिक लिपर्ट के ब्लॉग पोस्ट Closing over the loop variable considered harmful में चर्चा की गई, सी # में लूप वैरिएबल पर बंद होने से अप्रत्याशित परिणाम हो सकते हैं। मैं समझने की कोशिश कर रहा था कि क्या "गेटचा" Scala पर लागू होता है।स्कैला

सभी की

सबसे पहले, के बाद से यह एक स्काला सवाल यह है कि, मैं एरिक Lippert के सी # उदाहरण अपने कोड

// Create a list of integers 
var values = new List<int>() { 100, 110, 120 }; 

// Create a mutable, empty list of functions that take no input and return an int 
var funcs = new List<Func<int>>(); 

// For each integer in the list of integers we're trying 
// to add a function to the list of functions 
// that takes no input and returns that integer 
// (actually that's not what we're doing and there's the gotcha). 
foreach(var v in values) 
    funcs.Add(()=>v); 

// Apply the functions in the list and print the returned integers. 
foreach(var f in funcs) 
    Console.WriteLine(f()); 

अधिकांश लोगों को इस कार्यक्रम में 100, 110, 120 मुद्रित करने के लिए उम्मीद की कुछ टिप्पणियां जोड़ने समझा कोशिश करता हूँ । यह वास्तव में 120 प्रिंट, 120, 120 के मुद्दा () => v समारोह हम funcs सूची में जोड़ने के वी चर, नहीं वी के मूल्य पर बंद है। जैसा कि वी बदलता है, पहले लूप में, हम तीनों बंदरगाहों को funcs सूची में जोड़ते हैं, वही वैरिएबल बनाम "देखें", (जब तक हम उन्हें दूसरे लूप में लागू करते हैं) उनमें से सभी के लिए मूल्य 120 है।

मैं स्काला के उदाहरण कोड का अनुवाद करने की कोशिश की है:

import collection.mutable.Buffer 
val values = List(100, 110, 120) 
val funcs = Buffer[() => Int]() 

for(v <- values) funcs += (() => v) 
funcs foreach (f => println(f())) 
// prints 100 110 120 
// so Scala can close on the loop variable with no issue, or can it? 

स्काला वास्तव में एक ही मुद्दा से ग्रस्त नहीं है या मैं सिर्फ एरिक Lippert के कोड बुरी तरह से अनुवाद किया है और यह पुन: पेश करने में विफल रहा है?

इस व्यवहार ने कई बहादुर सी # डेवलपर को तोड़ दिया है, इसलिए मैं यह सुनिश्चित करना चाहता था कि स्कैला के साथ कोई अजीब समान गठिया न हो। लेकिन, एक बार जब आप समझते हैं कि सी # जिस तरह से करता है, एरिक लिपर्ट के उदाहरण कोड के प्रकार का अर्थ समझ में आता है (यह मूल रूप से काम बंद करने का तरीका है): तो स्कैला अलग-अलग क्या कर रही है?

+2

'v' स्कैला कोड में एक परिवर्तनीय चर नहीं है। ध्यान रखें कि 'लूप्स के लिए 'समझ' _not_' हैं। स्कैला कोड वास्तव में मानक 'फॉर' लूप की तुलना में प्रकृति में कहीं अधिक कार्यात्मक रूप से अनुवाद करता है, इसलिए, जहां आपके पास सी # कोड में कई मानों के साथ एक 'v' था, आपके पास एकाधिक' v 'है कि प्रत्येक को अपना एकल मूल्य मिलता है स्कैला कोड में। – Destin

+0

@ डेस्टिन: धन्यवाद, आपको इसे एक उत्तर के रूप में पोस्ट करना चाहिए था। मैं कम से कम इसे ऊपर उठाया होगा। (आप अभी भी इसे कर सकते हैं, वास्तव में) –

उत्तर

9

स्कैला में कोई समस्या नहीं है क्योंकि v एक var नहीं है, यह एक वैल है। इसलिए, जब आप

() => v 

संकलक समझता है कि यह उस फ़ंक्शन का उत्पादन करने वाला है जो उस स्थिर मूल्य को लौटाता है।

यदि आप var का उपयोग करते हैं, तो आपको एक ही समस्या हो सकती है।लेकिन यह जब से तुम स्पष्ट रूप से एक वर बनाते हैं और फिर समारोह इसे वापस है एक बहुत स्पष्ट है कि इस से पूछा-के लिए व्यवहार है,:

val values = Array(100, 110, 120) 
val funcs = collection.mutable.Buffer[() => Int]() 
var value = 0 
var i = 0 
while (i < values.length) { 
    value = values(i) 
    funcs += (() => value) 
    i += 1 
} 
funcs foreach (f => println(f())) 

(ध्यान दें कि यदि आप funcs += (() => values(i)) कोशिश आप सीमा से बाहर हो जाएगा अपवाद क्योंकि आपने चरपर बंद कर दिया है, जिसे आप कॉल करते हैं, अब है!)

+0

स्कैला में एक ही व्यवहार को पुन: उत्पन्न करने के लिए धन्यवाद। अब जब मैंने देखा है कि परिणामस्वरूप स्कैला कितना बेवकूफ होगा, मुझे विश्वास है कि (जैसा कि आप कहते हैं) यह दुर्घटना से होने की संभावना नहीं है। –

5

सी # उदाहरण के करीब समकक्ष while लूप और var के साथ होगा। यह सी # में व्यवहार करेगा।

दूसरी ओर, for(v <- values) funcs += (() => v)values.foreach(v => funcs +=() => v)

सिर्फ नाम देने के लिए करने के लिए अनुवाद किया है, कि

def iteration(v: Int) = {funcs +=() => v) 
values.foreach(iteration) 

बंद () => v यात्रा के शरीर में प्रकट होता है, और क्या यह दर्शाता है कुछ नहीं है हो सकता है var सभी पुनरावृत्तियों द्वारा साझा किया गया है, लेकिन पुनरावृत्ति के लिए कॉल का तर्क, जिसे साझा नहीं किया गया है, और इसके अलावा एक चर के बजाय निरंतर मूल्य है। यह अनजान व्यवहार को रोकता है।

foreach के कार्यान्वयन में एक चर हो सकता है, लेकिन यह बंद नहीं होता है।

यदि सी # में, आप लूप के शरीर को एक अलग विधि में ले जाते हैं, तो आपको वही प्रभाव मिलता है।

+0

eeek .... दो महान उत्तरों एक ही समय में पोस्ट किए गए हैं और मैं केवल जवाब के रूप में चिह्नित कर सकता हूं! क्षमा करें, मैं केवल आपको +1 दे सकता हूं, लेकिन मैं वास्तव में आपकी अंतर्दृष्टि की सराहना करता हूं –

1

यदि आप सी # उदाहरण को अलग करते हैं, तो आप देखेंगे कि बंद चर को पकड़ने के लिए एक वर्ग संकलक द्वारा उत्पन्न होता है। Reflector renders उस वर्ग की तरह: परावर्तक

[CompilerGenerated] 
private sealed class <>c__DisplayClass2 
{ 
    // Fields 
    public int v; 

    // Methods 
    public int <Main>b__1() 
    { 
     return this.v; 
    } 
} 

renders इस तरह के सुंदर सी #, तुम सच में नहीं देख सकते हैं कि उस वर्ग के लिए इस्तेमाल किया जा रहा है। यह देखने के लिए कि आपको कच्चे आईएल को देखने की जरूरत है।

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    .maxstack 4 
    .locals init (
     [0] class [mscorlib]System.Collections.Generic.List`1<int32> values, 
     [1] class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Func`1<int32>> funcs, 
     [2] class ConsoleApplication1.Program/<>c__DisplayClass2 CS$<>8__locals3, 
     [3] class [mscorlib]System.Func`1<int32> f, 
     [4] class [mscorlib]System.Collections.Generic.List`1<int32> <>g__initLocal0, 
     [5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<int32> CS$5$0000, 
     [6] bool CS$4$0001, 
     [7] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Func`1<int32>> CS$5$0002) 
    L_0000: nop 
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() 
    L_0006: stloc.s <>g__initLocal0 
    L_0008: ldloc.s <>g__initLocal0 
    L_000a: ldc.i4.s 100 
    L_000c: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) 
    L_0011: nop 
    L_0012: ldloc.s <>g__initLocal0 
    L_0014: ldc.i4.s 110 
    L_0016: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) 
    L_001b: nop 
    L_001c: ldloc.s <>g__initLocal0 
    L_001e: ldc.i4.s 120 
    L_0020: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) 
    L_0025: nop 
    L_0026: ldloc.s <>g__initLocal0 
    L_0028: stloc.0 
    L_0029: newobj instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Func`1<int32>>::.ctor() 
    L_002e: stloc.1 
    L_002f: nop 
    L_0030: ldloc.0 
    L_0031: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<!0> [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() 
    L_0036: stloc.s CS$5$0000 
    L_0038: newobj instance void ConsoleApplication1.Program/<>c__DisplayClass2::.ctor() 
    L_003d: stloc.2 
    L_003e: br.s L_0060 
    L_0040: ldloc.2 
    L_0041: ldloca.s CS$5$0000 
    L_0043: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator`0<int32>::get_Current() 
    L_0048: stfld int32 ConsoleApplication1.Program/<>c__DisplayClass2::v 
    L_004d: ldloc.1 
    L_004e: ldloc.2 
    L_004f: ldftn instance int32 ConsoleApplication1.Program/<>c__DisplayClass2::<Main>b__1() 
    L_0055: newobj instance void [mscorlib]System.Func`1<int32>::.ctor(object, native int) 
    L_005a: callvirt instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Func`1<int32>>::Add(!0) 
    L_005f: nop 
    L_0060: ldloca.s CS$5$0000 
    L_0062: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator`0<int32>::MoveNext() 
    L_0067: stloc.s CS$4$0001 
    L_0069: ldloc.s CS$4$0001 
    L_006b: brtrue.s L_0040 
    L_006d: leave.s L_007e 
    L_006f: ldloca.s CS$5$0000 
    L_0071: constrained. [mscorlib]System.Collections.Generic.List`1/Enumerator`0<int32> 
    L_0077: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    L_007c: nop 
    L_007d: endfinally 
    L_007e: nop 
    L_007f: nop 
    L_0080: ldloc.1 
    L_0081: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<!0> [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Func`1<int32>>::GetEnumerator() 
    L_0086: stloc.s CS$5$0002 
    L_0088: br.s L_009e 
    L_008a: ldloca.s CS$5$0002 
    L_008c: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Func`1<int32>>::get_Current() 
    L_0091: stloc.3 
    L_0092: ldloc.3 
    L_0093: callvirt instance !0 [mscorlib]System.Func`1<int32>::Invoke() 
    L_0098: call void [mscorlib]System.Console::WriteLine(int32) 
    L_009d: nop 
    L_009e: ldloca.s CS$5$0002 
    L_00a0: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Func`1<int32>>::MoveNext() 
    L_00a5: stloc.s CS$4$0001 
    L_00a7: ldloc.s CS$4$0001 
    L_00a9: brtrue.s L_008a 
    L_00ab: leave.s L_00bc 
    L_00ad: ldloca.s CS$5$0002 
    L_00af: constrained. [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Func`1<int32>> 
    L_00b5: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    L_00ba: nop 
    L_00bb: endfinally 
    L_00bc: nop 
    L_00bd: ret 
    .try L_0038 to L_006f finally handler L_006f to L_007e 
    .try L_0088 to L_00ad finally handler L_00ad to L_00bc 
} 

पहले foreach के अंदर, आप देख सकते हैं कि उस वर्ग का केवल एक उदाहरण बनाया गया है। इटरेटर के मान उस उदाहरण के सार्वजनिक v फ़ील्ड में असाइन किए जाते हैं। funcs सूची उस ऑब्जेक्ट की b__1 विधि के प्रतिनिधियों के साथ आबादी है।

तो अनिवार्य रूप से क्या सी # में हो

  1. में funcs
  2. अद्यतन मूल्यों पर
  3. पुनरावृत्ति को बंद करने की गुंजाइश वस्तु बनाएं ...
    1. पुश बंद के एक्सेसर कार्य करने के लिए एक संदर्भ है वर्तमान मूल्य के साथ क्लोजर स्कोप ऑब्जेक्ट का v
  4. funcs पर इटरेटर। प्रत्येक कॉल v का वर्तमान मान वापस कर देगी।
2

ध्यान दें कि स्कैला की समझ बहुत अलग तरीके से काम करती है। यह:

for(v <- values) funcs += (() => v) 

इस में संकलन समय पर अनुवाद किया है:

values.foreach(v => funcs += (() => v)) 

तो v एक नई प्रत्येक मान के लिए चर रहा है।