יציאה מלולאה בסקאלה

18/11/2023

התוכנית הבאה בפייתון קוראת שורות מהמשתמש, מדפיסה כל שורה חזרה ועוצרת כשמישהו כתב את המילה stop:

while True:
    line = input()
    print(line)
    if line == "stop":
        break

בואו ננסה לתרגם אותה לסקאלה.

1. גירסה 1 - מילה במילה

ניסיון לתרגום מילולי לסקאלה לא נשמע מסובך, רק צריך למצוא את המקבילה בסקאלה לכל פקודה בפייתון. לולאת while נשארת לולאת while, פקודת input הופכת ל readLine, תנאי נשאר תנאי - אבל מה לגבי ה break?

def v1(): Unit =
  while (true) {
    val line = readLine()
    println(line)
    if (line == "stop") {
      // ???
    }
  }

דווקא הפקודה הכי חשובה בתוכנית לא קיימת בצורה מובנית בסקאלה.

2. גירסה 2 - ה break של סקאלה

הגישה של סקאלה ל break היא קצת מוזרה. במקום להוסיף מילה לשפה הם משתמשים במנגנון המובנה של Exception - מי שרוצה לעצור באמצע לולאה זורק Exception, וקוד שעוטף את הלולאה תופס את ה Exception וממשיך בתוכנית. יש קיצורי דרך כמובן אז הקלאס boundary אחראי על עטיפת הלולאה בקוד שתופס Exceptions והפונקציה break היא בסך הכל throw. הקוד נראה ככה:

def v2(): Unit =
  boundary {
    while (true) {
      val line = readLine()
      println(line)
      if (line == "stop") {
        break()
      }
    }
  }

המימוש של break (מובנה בשפה, לא צריך לכתוב לבד) הוא:

  def break[T](value: T)(using label: Label[T]): Nothing =
    throw Break(label, value)

והמימוש של boundary (שוב לא צריך לכתוב לבד, מגיע עם השפה) הוא:

  inline def apply[T](inline body: Label[T] ?=> T): T =
    val local = Label[T]()
    try body(using local)
    catch case ex: Break[T] @unchecked =>
      if ex.label eq local then ex.value
      else throw ex

3. גירסה 3 - הגישה הפונקציונאלית

גירסה קצת פחות מוזרה של אותו קוד תשתמש ב takeWhile בתור תחליף ל while של פייתון. היתרון ב takeWhile הוא שלא צריך break, פשוט מסיימים את האיטרציה עם ערך "שקר" כדי לעצור. זה הקוד:

def v3(): Unit =
  LazyList.from(0).takeWhile(_ => {
    val line = readLine()
    println(line)
    line != "stop"
  }).toList