• בלוג
  • חמש דוגמאות קצרות לשימוש ב cat effect בסקאלה

חמש דוגמאות קצרות לשימוש ב cat effect בסקאלה

11/11/2023

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

מסיבה זאת נוצרו הרבה ספריות הרחבה לשפה שמאפשרות לייצר חישוב עתידי בצורה פונקציונאלית וללא Side Effects. אחת הפופולריות היא cats effect והנה חמש דוגמאות ראשונות שכתבתי עם הספריה.

1. שלום עולם

תוכנית סקאלה ראשונה עם cat effect היא כמובן פונקציה שמדפיסה שלום עולם:

  def demo1(): IO[ExitCode] =
    IO.println("Hello WOrld") >> IO.pure(ExitCode.Success)

הרעיון הראשון החדש כאן הוא שפונקציה שאפשר להריץ בצורה אסינכרונית זה בעצם פונקציה שמחזירה IO, וזה נקרא אפקט. לכן הפונקציה demo1 שמחזירה IO[ExitCode] מתאימה להרצה בצורה אסינכרונית. הסימן >> הוא דרך לחבר שני אפקטים ולהחזיר רק את התוצאה של השני, הפונקציה IO.println מייצרת אפקט שמדפיס למסך ו IO.pure מייצרת אפקט שמחזיר ערך קבוע (וכן אפשר לחשוב על אפקט בתור Promise אבל זה טיפה יותר מסובך כי אפקט אפשר גם לבטל).

2. שלום עולם עם המתנה

עכשיו שהבנו את הסיפור של אפקטים אפשר להמשיך ולראות איך מחברים אותם. דרך אחת לחבר אפקטים היא סימן ה >> שכבר פגשנו ואפשר לקחת אותו עוד צעד קדימה. הפונקציה IO.sleep מחזירה אפקט שמחכה, ולכן הקוד הבא:

  def demo11(): IO[ExitCode] =
    IO.sleep(1000.millis) >> IO.println("1") >>
    IO.sleep(1000.millis) >> IO.println("2") >>
    IO.sleep(1000.millis) >> IO.println("3") >>
    IO.pure(ExitCode.Success)

מייצר אפקט שמחכה שניה, מדפיס 1, מחכה עוד שניה, מדפיס 2, מחכה עוד שניה, מדפיס 3 ואז מחזיר הצלחה.

דרך אחרת וקצת יותר ידידותית בסקאלה לכתוב אותו היא:

  def demo2(): IO[ExitCode] =
    for {
      _ <- IO.sleep(1000.millis) >> IO.println("1")
      _ <- IO.sleep(1000.millis) >> IO.println("2")
      _ <- IO.sleep(1000.millis) >> IO.println("3")
    } yield ExitCode.Success

וזו גם מאפשרת לכל אפקט להשתמש בתוצאה של האפקט שלפניו, למרות שבדוגמה הפעם לא עשיתי את זה והתעלמתי מהתוצאה של האפקט הקודם, בכך שנתתי לה את השם _.

3. שלום עולם במקביל

אחרי שאנחנו מבינים איך להגדיר מספר אפקטים הצעד הבא הוא להריץ אותם במקביל, בדומה ל Promise.all. ב cats effect זה קורה עם פונקציה בשם parSequence:

  def demo3(): IO[ExitCode] =
    List(
      IO.sleep(1000.millis) >> IO.println("1"),
      IO.sleep(1000.millis) >> IO.println("2"),
      IO.sleep(1000.millis) >> IO.println("3")
    ).parSequence >> IO.pure(ExitCode.Success)

הפעם הקוד מגדיר רשימה של 3 אפקטים ומפעיל על הרשימה את parSequence. התוצאה היא הפעלה של שלושת האפקטים במקביל כך שאחרי שניה יודפסו שלוש ההודעות בסדר אקראי.

4. משיכת מידע מהרשת

ספריות הרחבה רבות של סקאלה שקשורות ל IO כוללת גירסה מותאמת ל cats effect, לדוגמה הספריה sttp שיודעת לשלוח בקשות HTTP. הקוד הבא בסקאלה מחזיר אפקט שפונה ל URL (בדוגמה שלי זה https://icanhazdadjoke.com/) ומחזיר את גוף התשובה בתור מחרוזת:

def randomDadJoke(): IO[String] =
  HttpClientCatsBackend.resource[IO]().use { backend =>
    basicRequest
      .header("Accept", "application/json")
      .get(uri"https://icanhazdadjoke.com/")
      .response(asString)
      .send(backend)
      .map {response => response.body.right.get}
  }

הדבר החשוב לשים לב הוא ערך ההחזרה של הפונקציה שזה IO[String] כלומר מדובר באפקט שיחזיר מחרוזת. אפשר להשתמש באפקט זה כדי להדפיס את התשובה:

  def demo4(): IO[ExitCode] =
    randomDadJoke().map(s => println(s)) >> IO.pure(ExitCode.Success)

5. משיכת מידע מהרשת במקביל

וכמובן אחרי שיש אפקט אפשר להריץ אותו במקביל כדי לקבל מספר בדיחות:

  def demo5(): IO[ExitCode] =
    List(
      randomDadJoke().map(s => println(s)),
      randomDadJoke().map(s => println(s)),
      randomDadJoke().map(s => println(s))
    ).parSequence >> IO.pure(ExitCode.Success)