• בלוג
  • טיפ סקאלה: החזרת תוצאה עתידית

טיפ סקאלה: החזרת תוצאה עתידית

07/12/2023

המנגנון המובנה של Future בסקאלה הוא אומנם פחות מתוחכם מספריות כמו cats effect אבל בהחלט יכול להיות שימושי בבעיות פשוטות, ולא דורש ארגון מחדש של קוד התוכנית. נראה איך זה עובד דרך דוגמה פשוטה.

הפונקציה הבאה מייצרת ומחזירה רשימה של מספרים:

  def createNumbers(): List[Int] =
    (1 to 10).map(_ =>
      Thread.sleep(Random.nextInt(500))
      Random.nextInt(100)).toList

וכמובן יש טוויסט - בשביל לייצר מספר אנחנו מחכים עד חצי שניה עם Thread.sleep. אם ניקח את הרשימה מבחוץ וננסה להדפיס אותה עם קוד כזה:

createNumbers().foreach(println(_))

הקוד יחכה עד שהרשימה כולה מוכנה ורק אז ידפיס במכה אחת את כל ה-10 מספרים. זה עלול להיות בעייתי כי אם מאיזושהי סיבה התוכנית תסתיים עם שגיאה לפני שהצליחה לייצר את כל הרשימה אף מספר לא יודפס. בעזרת מנגנון Future אני יכול לייצר רשימה שתהיה זמינה לקוד החיצוני גם לפני שכל הפריטים חושבו במלואם.

הרעיון בגדול הוא שבמקום להחזיר רשימה של מספרים אני מחזיר רשימה של תוצאות עתידיות (כן Future מייצג תוצאה עתידית של חישוב). זה הקוד:

  def createFutureNumbers(): List[Future[Int]] =
    (1 to 10).map(_ => Future[Int] {
      Thread.sleep(Random.nextInt(500))
      Random.nextInt(100)
    }).toList

בקוד החיצוני אני לוקח כל Future ו"מדביק" לו קוד שיבוצע אחרי שאותו חישוב עתידי יהיה מוכן. בדוגמת ההדפסה זה יהיה:

val futures = createFutureNumbers()
futures.foreach(f => f.onComplete(i => println(i.get)))

לאחר מכן אני רוצה לחכות עד שכל החישובים העתידיים יסתיימו, לכן אני הופך את רשימת החישובים העתידיים ל Future אחד שיהיה מוכן כשכל הפריטים ברשימה חושבו בעזרת הפונקציה sequence. פונקציה נוספת בשם Await.ready תחכה עד ש Future מסתיים וכך תאפשר לתוכנית לרוץ עד לסיום כל ההדפסות. סך הכל הקוד החיצוני יהיה:

  @main
  def f(): Unit =
      val futures = createFutureNumbers()
      futures.foreach(f => f.onComplete(i => println(i.get)))
      Await.ready(Future.sequence(futures), Duration.Inf)

יותר מזה, ה Future בסקאלה כולל פונקציה בשם flatMap שמאפשרת להוסיף לו המשך חישוב. בחזרה לדוגמה נניח שאחרי שהמספר מוכן התוכנית תחכה עוד מספר מילי שניות לפי המספר שחושב ואז תעלה אותו בריבוע, ובסוף תדפיס את ריבועי כל המספרים. הקוד המעודכן לזה יהיה בסך הכל:

  def f(): Unit =
      val futures = createFutureNumbers().map {f =>
        f.flatMap(i => Future({
          Thread.sleep(i)
          i * i
        }))
      }

      futures.foreach(f => f.onComplete(i => println(i.get)))
      Await.ready(Future.sequence(futures), Duration.Inf)