• בלוג
  • חמש תוכניות coconut כדי להכיר שפה חדשה

חמש תוכניות coconut כדי להכיר שפה חדשה

06/11/2021

קוקונאט היא שפה פונקצונאלית שמתקמפלת לפייתון ונותנת גישה מלאה לכל האקוסיסטם של פייתון אבל בכתיב פונקציונאלי עם כל הפינוקים. מתקינים אותה כמו כל ספריית פייתון עם:

$ pip install coconut

ואחרי ההתקנה אפשר ליצור תוכניות קוקונאט בתוך קבצים עם סיומת coco או להפעיל את ה interpreter עם הפקודה coconut ולהריץ ביטויי קוקאנט. ויש גם סביבת הרצה נוחה בענן בקישור: https://cs121-team-panda.github.io/coconut-interpreter/.

בשביל להכיר את השפה לקחתי מספר תרגילי פייתון וניסיתי לממש אותם בקוקנאט. התוצאות לפניכם:

1. היפוך כל מילה במחרוזת

נתונה המחרוזת:

text = 'My Name is Jessa'

כיתבו תוכנית שתדפיס את המחרוזת תוך היפוך סדר האותיות בכל מילה, כלומר הפלט הצפוי יהיה:

yM emaN si asseJ

פיתרון בפייתון מתוך אתר התרגול:

def reverse_words(Sentence):
    words = Sentence.split(" ")
    new_word_list = [word[::-1] for word in words]
    res_str = " ".join(new_word_list)
    return res_str

# Given String
str1 = "My Name is Jessa"
print(reverse_words(str1))

פיתרון שלי ב coconut:

def reverse_words(sentence) = (
    sentence
       |> .split(" ")
       |> map$(n -> n[::-1])
       |> list
       |> ' '.join)

reverse_words("My Name is Jessa") |> print

מאוד דומה לקוד פייתון המקורי אבל בזכות השימוש ב Pipeline Operator לא צריך להגדיר משתני ביניים ובעצם התוצאה של כל פונקציה ממשיכה לפונקציה הבאה ב Pipeline.

2. החלפת תוי סוף שורה ברווחים

כתבו תוכנית שקוראת את הקובץ sample.txt ומדפיסה את כל השורות בו מופרדות ברווחים (במקום בתו ירידת שורה).

פיתרון פייתון:

with open('sample.txt', 'r') as file:
    data = file.read().replace('\n', ' ')
    print(data)

פיתרון קוקאנאט שלי:

with open("sample.txt") as file:
    (file.read()
    |> .replace("\n", " ")
    |> print)

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

טריק מעניין נוסף כאן הוא הקריאה ל .replace. אנחנו רגילים מפייתון שלפני הפעלת מתודה צריך לבוא אוביקט, ופה בקוקנט קריאה זו היא Partial Application של הפונקציה, כלומר היא מייצרת פונקציה שכשיגיע אליה אוביקט היא פשוט תפעיל את פונקציית replace שלו.

3. מיפוי מילון הפוך

בהינתן מילון:

ascii_dict = {'A': 65, 'B': 66, 'C': 67, 'D': 68}

כתבו תוכנית שתהפוך את המפתחות והערכים ותדפיס:

{65: 'A', 66: 'B', 67: 'C', 68: 'D'}

פיתרון בקוד פייתון:

ascii_dict = {'A': 65, 'B': 66, 'C': 67, 'D': 68}
# Reverse mapping
new_dict = {value: key for key, value in ascii_dict.items()}
print(new_dict)

פיתרון בקוד קוקנאט:

({'A': 65, 'B': 66, 'C': 67, 'D': 68}
    |> fmap$((k, v) -> [v, k])
    |> print)

הפונקציה fmap של קוקנט היא פשוט פונקציית map גנרית שאפשר להפעיל על כל אוביקט והיא תמיד עושה את הדבר הנכון. על מילון היא מפעילה את הפונקציה על ה items של המילון וזה בעצם מחליף את ה Dictionary Comprehension שראינו בפייתון.

4. הדפסת כל הערכים ברשימה שמופיעים יותר מפעם אחת

בהינתן רשימה של מספרים:

sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]

הציגו את כל הפריטים מהרשימה שמופיעים יותר מפעם אחת. בדוגמה שלנו אלה 20, 60 ו 30.

פיתרון בקוד פייתון:

import collections

sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]

duplicates = []
for item, count in collections.Counter(sample_list).items():
    if count > 1:
        duplicates.append(item)
print(duplicates)

פיתרון בקוד קוקנאט:

import collections
sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]

(sample_list
    |> collections.Counter
    |> .items()
    |> filter$(x -> x[1] > 1)
    |> map$(x -> x[0])
    |> list
    |> print)

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

דבר אחד שכבר אנחנו רואים שמטריד לגבי השפה הוא שאין תמיכה ב Destructuring בארגומנטים של פונקציות, ולכן filter מוגדרת לקבל משתנה יחיד x וצריכה לגשת למקום השני בו (במקום לקבל זוג ולבצע השמה). כלומר הייתי רוצה לכתוב את הביטוי:

|> filter$((k, v) -> v > 1)

אבל זה לא היה קוקנאט תקני.

5. משולש מספרים

תרגיל אחרון לסיבוב הזה הוא הדפסת משולש מספרים, כלומר כתבו תוכנית שמדפיסה את תבנית המספרים הבאה:

1 1 1 1 1 
2 2 2 2 
3 3 3 
4 4 
5

פיתרון בשפת פייתון:

rows = 5
x = 0
# reverse for loop from 5 to 0
for i in range(rows, 0, -1):
    x += 1
    for j in range(1, i + 1):
        print(x, end=' ')
    print('\r')

פיתרון שלי בקוקנאט:

def triangle(size)= (
    range(size, 0, -1)
    |> map$(i -> [i, size-i + 1])
    |> map$(x -> f"{x[1]} " * x[0])
    |> '\n'.join)


print(triangle(5))

חישובי אינדקסים אף פעם לא היו הצד החזק שלי אבל הפעם נראה שקוד קוקנאט חוסך לי חלק מהחישובים ומאפשר להתמקד ב"מה צריך לעשות" במקום באיך. במקום להסתכל על הבעיה בתור לולאה כפולה אני מסתכל עליה כסידרה של שתי פונקציות map שמייצרות את הערך להדפסה.

יש עוד המון דברים ש coconut יודעת לעשות ולא הופיעו כאן בדוגמאות הקצרות, בפרט יש כמה מנגנונים נחמדים לפיתוח מקבילי ותמיכה מובנית ב Pattern Matching (שעכשיו קיים כבר גם בפייתון). סך הכל התחביר בעיניי יותר נוח מקוד פייתון מקביל.

למידע נוסף על coconut שווה לבקר בדף התיעוד הראשי שלהם בקישור: https://coconut.readthedocs.io/en/master/DOCS.html.