سؤال

انتهيت للتو البرمجة في scala., ، لقد كنت أبحث في التغييرات بين SCALA 2.7 و 2.8. الشخص الذي يبدو أنه الأكثر أهمية هو البرنامج المساعد المستمر، لكنني لا أفهم ما هو مفيد أو كيف يعمل. لقد رأيت أنه من الجيد عن I / O غير متزامن، لكنني لم أتمكن من معرفة السبب. بعض الموارد الأكثر شعبية حول هذا الموضوع هي:

وهذا السؤال على تجاوز المكدس:

لسوء الحظ، لا تحاول أي من هذه المراجع تحديد المساكنات التي تتمتع بها أو ما من المفترض أن تفعل وظائف التحول / إعادة الضبط، ولم أجد أي مراجع تقوم به. لم أتمكن من تخمين كيفية عمل أي من الأمثلة في المقالات المرتبطة (أو ما يفعلونه)، لذا يمكن أن يكون هناك طريقة واحدة لمساعدتي في الخروج من خط التنقل عبر إحدى هذه العينات. حتى هذا بسيط من المقالة الثالثة:

reset {
    ...
    shift { k: (Int=>Int) =>  // The continuation k will be the '_ + 1' below.
        k(7)
    } + 1
}
// Result: 8

لماذا النتيجة 8؟ ربما سيساعدني في البدء.

هل كانت مفيدة؟

المحلول

لي مقالات لا يفسر ما reset و shift القيام به، لذلك قد ترغب في قراءة ذلك مرة أخرى.

مصدر جيد آخر، الذي أشر أيضا في مدونتي، هو دخول ويكيبيديا استمرار النمط. وبعد هذا هو واحد، إلى حد بعيد، الأكثر وضوحا حول هذا الموضوع، على الرغم من أنه لا يستخدم بناء جملة SCALA، ويتم تمرير الاستمرار بشكل صريح.

ورقة على مستمرات محددة، التي أربط بها في مدونتي ولكنها تبدو مكسورة، تعطي العديد من الأمثلة على الاستخدام.

لكنني أعتقد أن أفضل مثال على مفهوم المستمرات المحددة هي سرب scala. في ذلك، المكتبة توقف تنفيذ التعليمات البرمجية الخاصة بك عند نقطة واحدة، والحساب المتبقي يصبح الاستمرار. تقوم المكتبة ثم بعمل شيء ما - في هذه الحالة، نقل حساب إلى مضيف آخر، وإرجاع النتيجة (قيمة المتغير التي تم الوصول إليها) بحساب تم إيقافه.

الآن، أنت لا تفهم حتى المثال البسيط على صفحة Scala، لذلك فعل قراءة مدونتي. في ذلك أنا فقط تشعر بالقلق مع شرح هذه الأساسيات، لماذا النتيجة هي 8.

نصائح أخرى

لقد وجدت التفسيرات القائمة لتكون أقل فعالية في شرح المفهوم أكثر مما آمل. آمل أن يكون هذا واحد واضحا (وصحيحا.) لم أستخدم استمرار استمرار.

عندما وظيفة الاستمرارية cf يسمى:

  1. التنفيذ يتخطى على بقية shift كتلة وتبدأ مرة أخرى في نهاية ذلك
    • المعلمة مرت cf هو ما shift كتلة "تقيم" كما يستمر التنفيذ. هذا يمكن أن يكون مختلفا لكل مكالمة cf
  2. يستمر التنفيذ حتى نهاية reset كتلة (أو حتى دعوة إلى reset إذا لم يكن هناك كتلة)
    • نتيجة reset كتلة (أو المعلمة ل reset() إذا لم يكن هناك كتلة) هو ما cf عائدات
  3. يستمر التنفيذ بعد ذلك cf حتى نهاية shift منع
  4. التنفيذ يتخطى حتى نهاية reset كتلة (أو مكالمة لإعادة التعيين؟)

لذلك في هذا المثال، اتبع الحروف من A إلى z

reset {
  // A
  shift { cf: (Int=>Int) =>
    // B
    val eleven = cf(10)
    // E
    println(eleven)
    val oneHundredOne = cf(100)
    // H
    println(oneHundredOne)
    oneHundredOne
  }
  // C execution continues here with the 10 as the context
  // F execution continues here with 100
  + 1
  // D 10.+(1) has been executed - 11 is returned from cf which gets assigned to eleven
  // G 100.+(1) has been executed and 101 is returned and assigned to oneHundredOne
}
// I

هذه المطبوعات:

11
101

بالنظر إلى المثال الكنسي من ورقة ابحاث بالنسبة إلى مستمرات Scala المحددة، تم تعديلها قليلا حتى تدخل الوظيفة shift يتم إعطاء الاسم f وبالتالي لم يعد مجهول.

def f(k: Int => Int): Int = k(k(k(7)))
reset(
  shift(f) + 1   // replace from here down with `f(k)` and move to `k`
) * 2

يحول البرنامج المساعد Scala هذا المثال بحيث الحساب (ضمن وسيطة الإدخال reset) بدءا من كل shift إلى استدعاء reset يكون استبدالها مع الوظيفة (على سبيل المثال f) المدخلات إلى shift.

الحساب المستبدل هو تحول (أي انتقل) إلى وظيفة k. وبعد الوظيفة f مدخلات الوظيفة k, ، أين k يحتوي على حساب استبدال، k المدخلات x: Int, والحساب في k يستبدل shift(f) مع x.

f(k) * 2
def k(x: Int): Int = x + 1

الذي له نفس التأثير كما هو:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1

لاحظ النوع Int من المعلمة الإدخال x (أي نوع توقيع k) أعطيت من نوع توقيع من النوع لمعلمة الإدخال f.

اخر اقترضت، استعارت مثال مع التجريد المكافئ المعادل، أي read هي مدخلات الوظيفة shift:

def read(callback: Byte => Unit): Unit = myCallback = callback
reset {
  val byte = "byte"

  val byte1 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "1 = " + byte1)
  val byte2 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "2 = " + byte2)
}

أعتقد أن هذا سيتم ترجمته إلى ما يعادله المنطقي من:

val byte = "byte"

read(callback)
def callback(x: Byte): Unit {
  val byte1 = x
  println(byte + "1 = " + byte1)
  read(callback2)
  def callback2(x: Byte): Unit {
    val byte2 = x
    println(byte + "2 = " + byte1)
  }
}

آمل أن يقوم هذا بتوضيح التجريد المشترك المتماسك الذي تم تنبه إلى حد ما وفقا لعرض مسبق لهذين هذه الأمثلة. على سبيل المثال، تم تقديم المثال القانوني الأول في ورقة ابحاث كدالة مجهولة، بدلا من اسمي f, ، وبالتالي لم يكن واضحا على الفور لبعض القراء أنه كان مشجعا مجردا read في ال اقترضت، استعارت المثال الثاني.

وبالتالي فإن مستمرات محددة تخلق وهم انقلابي للتحكم من "أنت تتصل بي من خارج reset"إلى" أنا أتصل بك في الداخل reset".

لاحظ نوع العودة من f ما هو إلا k ليس كذلك أن يكون هو نفسه نوع العودة reset, ، بمعنى آخر f لديه حرية إعلان أي نوع العودة ل k طالما f إرجاع نفس النوع كما reset. وبعد كما سبق read و capture (أنظر أيضا ENV أقل).


المستمرات المحددة لا تنقل ضمنا السيطرة على الدولة، على سبيل المثال read و callback ليست وظائف نقية. وبالتالي لا يمكن للمتصل إنشاء تعبيرات شفافة مرره، وبالتالي لا تملك التعريفي (AKA شفافة) السيطرة على الدلالات الحادية المقصودة.

يمكننا أن نحقق الوظائف النقية بشكل صريح مع استمرار محددات.

def aread(env: ENV): Tuple2[Byte,ENV] {
  def read(callback: Tuple2[Byte,ENV] => ENV): ENV = env.myCallback(callback)
  shift(read)
}
def pure(val env: ENV): ENV {
  reset {
    val (byte1, env) = aread(env)
    val env = env.println("byte1 = " + byte1)
    val (byte2, env) = aread(env)
    val env = env.println("byte2 = " + byte2)
  }
}

أعتقد أن هذا سيتم ترجمته إلى ما يعادله المنطقي من:

def read(callback: Tuple2[Byte,ENV] => ENV, env: ENV): ENV =
  env.myCallback(callback)
def pure(val env: ENV): ENV {
  read(callback,env)
  def callback(x: Tuple2[Byte,ENV]): ENV {
    val (byte1, env) = x
    val env = env.println("byte1 = " + byte1)
    read(callback2,env)
    def callback2(x: Tuple2[Byte,ENV]): ENV {
      val (byte2, env) = x
      val env = env.println("byte2 = " + byte2)
    }
  }
}

هذا هو الحصول على صاخبة، بسبب البيئة الصريحة.

ملاحظة مميزة، لا تملك SCALA الاستدلال من النوع العالمي في Haskell، وبالتالي، كما أعرف، لم أستطع دعم الرفع الضمني إلى ولاية موناد unit (كاستراتيجية واحدة محتملة لإخفاء البيئة الصريحة)، فإن الاستدلال من نوع Haskell العالمي (HASKELLNER) يعتمد على عدم دعم الماس المتعدد الظاهري.

استمرار الاستيلاء على حالة الحساب، ليتم الاحتجاج بها لاحقا.

فكر في الحساب بين ترك تعبير التحول وتترك تعبير إعادة تعيين كدالة. داخل تعبير التحول، تسمى هذه الوظيفة ك، إنه استمرار. يمكنك تمريرها، استدعاءها لاحقا، حتى أكثر من مرة.

أعتقد أن القيمة التي تم إرجاعها بواسطة تعبير إعادة الضبط هي قيمة التعبير داخل تعبير التحول بعد =>، ولكن حول هذا ليس متأكدا تماما.

لذلك مع استمرار التواصل، يمكنك اختتام جزء من التعليمات البرمجية التعسفية وغير المحلي في وظيفة. يمكن استخدام هذا لتنفيذ تدفق التحكم غير القياسي، مثل Coroutining أو التراجع.

لذلك يجب استخدام المستمرات على مستوى النظام. ستكون رشها من خلال رمز التطبيق الخاص بك وصفة مؤكدة للكوابيس، أسوأ بكثير من أسوأ رمز السباغيتي باستخدام GOTO يمكن أن يكون.

تنصل: ليس لدي فهم عمق استمرار في سكالا، لقد استنتجته من النظر إلى الأمثلة ومعرفة استمرار المخطط.

من وجهة نظري، تم إعطاء أفضل تفسير هنا: http://jim-mcbeath.blogspot.ru/201/0/delimited-continuations.html.

واحدة من الأمثلة:

لمعرفة تدفق التحكم بشكل أكثر وضوحا، يمكنك تنفيذ مقتطف الكود هذا:

reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}

إليك الناتج الذي ينتج عنه الرمز أعلاه:

A
B
D
E
G
F
C

آخر (أحدث مؤخرا - مايو 2016) مقال حول استمرار سكالا هو:
"السفر الوقت في Scala: CPS في Scala (استمرار Scala)" بواسطةشيفانش سريفاستافا (shiv4nsh).
كما يشير إلى جيم ماكبيثشرط مذكور في ديمتري بيبالوفإجابه.

ولكن قبل ذلك، يصف المستمرات مثل ذلك:

استمرار تمثيل مجردة لحالة التحكم في برنامج الكمبيوتر.
إذن ما يعنيه في الواقع هو أنه هيكل بيانات يمثل العملية الحسابية في نقطة معينة في تنفيذ العملية؛ يمكن الوصول إلى بنية البيانات التي تم إنشاؤها بواسطة لغة البرمجة، بدلا من إخفاءها في بيئة وقت التشغيل.

لشرح ذلك، يمكننا أن يكون لدينا واحدة من أكثر مثال كلاسيك،

قل أنك في المطبخ أمام الثلاجة، والتفكير في ساندويتش. كنت تأخذ استمرار هناك وعصيها في جيبك.
ثم تحصل على بعض تركيا والخبز من الثلاجة وجعل نفسك ساندويتش، وهو يجلس الآن على العداد.
يمكنك استدعاء الاستمرار في جيبك، وتجد نفسك تقف أمام الثلاجة مرة أخرى، والتفكير في ساندويتش. ولكن لحسن الحظ، هناك ساندويتش على العداد، وجميع المواد المستخدمة لجعلها ذهبت. لذلك تأكله. :-)

في هذا الوصف، sandwich جزء من بيانات البرنامج (على سبيل المثال، كائن على كومة)، وبدلا من الاتصال "make sandwich"روتين ثم العودة، الشخص الذي دعا"make sandwich with current continuation"روتين، الذي ينشئ الساندويتش ثم يستمر حيث توقف التنفيذ.

التي يقال، كما أعلن في أبريل 2014 ل Scala 2.11.0-RC1

نحن نبحث عن المشرفين لتولي الوحدات التالية: scala-swing., scala-contunations..
2.12 لن تدرجها إذا لم يتم العثور على موافقة جديدة.
من المرجح أن نستمر في الحفاظ على الوحدات الأخرى (SCALA-XML، Scala-Parser-Confinators)، لكن المساعدة لا تزال موضع تقدير كبير.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top