הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

היום למדתי: טקסט לדיבור באפליקציות ווב

21/04/2023

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

מצד שני לפעמים אנחנו צריכים פיתרון מהיר שפשוט יגיד כמה מילים, ומוכנים להתפשר על האיכות. בשביל המצבים האלה נוכל להשתמש ב Web Speech API. בואו נראה איך זה עובד:

  1. האוביקט SpeechSynthesisUtterance מייצג משהו שאנחנו רוצים להגיד. יוצרים אותו עם new והפרמטר היחיד הוא הטקסט (אפשר בכל שפה).

  2. בשדה voice של אותו Utterance אנחנו כותבים את הקול שאנחנו רוצים שיגיד את המשפט. אפשר לקבל את רשימת כל הקולות מ speechSynthesis.getVoices(). יש עוד כמה שדות באוביקט זה שמאפשרים לנו לבקש ממנו לדבר יותר מהר או להתאים פרמטרים אחרים.

  3. אחרי שיש לנו Utterance מוכן נשלח אותו לפונקציה speak של window.speechSynthesis כדי לשמוע אותו.

קוד? בטח. לחצו על הכפתור בקודפן כדי לשמוע את כרמית אומרת בעברית "היו שלום ותודה על הדגים":

וזה הקוד:

synth =  window.speechSynthesis;
voice = synth.getVoices().find(v => v.lang === "he-IL");

text = new SpeechSynthesisUtterance("היו שלום ותודה על הדגים");
text.voice = voice;

function say() {
  synth.speak(text);
}

btn.addEventListener('click', say);

האשבנג ב JavaScript? זה לא היה כבר?

19/04/2023

רשימת הפיצ'רים שייכנסו ל JavaScript ב 2023 כוללת את:

  1. האפשרות לחפש במערך מהסוף.

  2. תמיכה ב Hashbang.

  3. אפשרות להשתמש בסימבולים בתור מפתחות ב WeakMap.

  4. פונקציות לשינוי מערך (לדוגמה מיון) בהעתקה ולא בשינוי In Place.

בעוד שפיצ'רים 1, 3 ו-4 די צפויים, פיצ'ר מספר 2 הפתיע אותי. נזכיר ש HashBang הוא סימן ה #! שאומר ליוניקס איזה תוכנית צריך להפעיל כשלקובץ טקסט יש הרשאות הרצה ואנחנו "מפעילים" את הקובץ. מתכנתי Node.JS מאז ומעולם השתמשו בסימן הזה כדי לכתוב סקריפטים ב JavaScript והכל עבד, לדוגמה הסקריפט הזה:

#!/usr/bin/env node

console.log('hello world');

עובד גם ב Node וגם ב Deno ואפילו בדפדפנים, למרות שהסימן #! לא חוקי ב JavaScript. המנועים פשוט מתעלמים ממנו.

מסתבר שלמרות התמיכה הגורפת בפקודת ה Shebang, הפקודה עדיין לא היתה חלק מהשפה. לכל מקרה שהייתם מודאגים, תשמחו לשמוע שהחל מהגירסה הבאה של JavaScript היא צפויה להישאר איתנו בתקן לנצח.

פירוט ההצעה כאן: https://tc39.es/proposal-hashbang/out.html

ופירוט על כל הפיצ'רים החדשים ב 2023 זמין כאן: https://pawelgrzybek.com/whats-new-in-ecmascript-2023/

מה עושים כשמצליחים את כל התרגילים?

18/04/2023

מה עושים אם המשחק קל מדי? אם מצליחים את כל התרגילים? אם יום עבודה שלם עובר בלי רגעים של תסכול, בלי להיתקע, בלי לטעות?

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

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

וככל שהצלחתי לענות נכון על יותר שאלות ולספר סיפורים יותר מעניינים, למדתי שהסכנה האמיתית היא בכלל הפוכה.

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

מה עושים אם המשחק קל מדי? עולים לשלב הבא.

צריך הארכה

17/04/2023

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

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

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

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

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

ציונים

16/04/2023

מסצ'וסס היתה המדינה הראשונה בארה"ב שהעבירה חוק שמחייב הורים לשלוח את ילדיהם לבתי ספר, וזה קרה ב 1852. בשנים שיבואו ועד 1917 שאר המדינות בארה"ב יעבירו חוקים דומים ויקימו תשתית של בתי ספר לכל הילדים שם. אותם בתי ספר התחילו להשתמש בציונים ובמבחנים סטנדרטיים כדי לתקשר החוצה את ההצלחה של התלמידים.

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

היום, לפחות בתעשיית ההייטק, התשתית הזאת כמעט לא קיימת.

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

כשבתי ספר ממשיכים לדרג תלמידים לפי ציונים הם מזיקים פעמיים:

  1. הם גורמים לילדים עם נקודת פתיחה טובה להתאמץ פחות, כי אי אפשר לקבל יותר מ 100.

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

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

קפיצה גדולה מדי

15/04/2023

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

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

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

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

מבוא זריז לספריית spaCy

14/04/2023

ספייסי היא ספריית nlp בפייתון שיודעת לעשות הרבה דברים ש nltk עושה אבל הממשק שלה קצת יותר נוח ויש אומרים שחלק מהדברים היא עושה מהר או מדויק יותר. יש להם תמיכה במעל 72 שפות, ופייפליינים ל 24 שפות (מה שאומר שעם 24 שפות אפשר לעשות ממש הרבה דברים). אני מאוד אהבתי בספייסי שהכל עבד מהקופסה וכשדברים לא עבדו מספיק טוב אפשר היה לבחור פייפליין שונה כדי לשפר את הדיוק.

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

תחילה התקנה - pip עובד כאן רגיל לגמרי:

$ pip install spacy

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

$ python -m spacy download en_core_web_trf

ובשביל ספרדית אפשר להוריד את:

$ python -m spacy download es_dep_news_trf

הפרמטר האחרון בשורה הוא שם הפייפליין. זיכרו אותו כי תכף נשתמש בו בתוכנית פייתון. עכשיו לקוד, אני פותח REPL של פייתון וטוען את ספייסי ואת הפייפליין:

>>> import spacy
>>> en = spacy.load("en_core_web_trf")

את הפונקציה en שנוצרה אני יכול להפעיל על משפט או קטע טקסט באנגלית כדי לקבל אוביקט spacy.tokens.doc.Doc שמתאים לו. אחרי זה אפשר להשתמש באותו אוביקט כדי לקבל מידע על המילים:

  1. המסמך מתפקד כ Generator שמחזיר את רשימת הטוקנים בו (כל טוקן זה מילה או סימן פיסוק).

  2. מכל טוקן אפשר לקחת את המאפיין pos_ כדי לקבל מה תפקידו במשפט.

  3. המאפיין lemma של טוקן מחזיר את הצורה הבסיסית של הדבר (ללא הטיות).

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

>>> text = 'Inflectional morphology is the process by which a root form of a word is modified by adding prefixes or suffixes that specify its grammatical function but do not change its part-of-speech. We say that a lemma (root form) is inflected (modified/combined) with one or more morphological features to create a surface form.'

>>> doc = en(text)
>>> for token in doc[:10]:
>>>     print(token.text, token.pos_, token.lemma_)

Inflectional ADJ inflectional
morphology NOUN morphology
is AUX be
the DET the
process NOUN process
by ADP by
which PRON which
a DET a
root NOUN root
form NOUN form

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

import spacy
from spacy.matcher import Matcher

nlp = spacy.load("en_core_web_sm")
matcher = Matcher(nlp.vocab)
# Add match ID "HelloWorld" with no callback and one pattern
pattern = [{'POS': 'ADJ', 'OP': '?'},
           {'LEMMA': 'match', 'POS': 'NOUN'},
           {'LEMMA': 'be'}]
matcher.add("match", [pattern])

doc = nlp("""
A match is a tool for starting a fire. Typically, modern matches are made of small wooden sticks or stiff paper. One end is coated with a material that can be ignited by frictional heat generated by striking the match against a suitable surface. Wooden matches are packaged in matchboxes, and paper matches are partially cut into rows and stapled into matchbooks.
""")

matches = matcher(doc)
for match_id, start, end in matches:
    string_id = nlp.vocab.strings[match_id]  # Get string representation
    span = doc[start:end]  # The matched span
    print(match_id, string_id, start, end, span.text)

שימו לב למשתנה pattern: הקוד מחפש רצף של מילים שיכול להתחיל ב Adjective, אחריו המילה match בתור שם עצם (יחיד או רבים) ואחריה הפועל be. פלט התוכנית הוא:

16065818573247886523 match 2 4 match is
16065818573247886523 match 13 16 modern matches are
16065818573247886523 match 14 16 matches are
16065818573247886523 match 49 52 Wooden matches are
16065818573247886523 match 50 52 matches are
16065818573247886523 match 58 60 matches are

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

במחשבה שלישית

13/04/2023

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

במחשבה ראשונה ניסיתי למצוא דרך להפעיל git config אוטומטית כשהקונטיינר עולה כדי להגדיר את האימייל. אפשר למשל להעביר את המייל הנכון במשתנה סביבה ולהריץ את ה config מסקריפט ה entrypoint של הקונטיינר.

זה עבד אבל לא אהבתי את הכתיבה של המייל במשתנה סביבה או את הצורך להריץ דברים בעלייה. במחשבה שניה חשבתי למפות את הפלט של git config --list מהמחשב המארח פנימה לקונטיינר, אבל היה יותר קל למפות את קבצי ה .gitconfig פנימה לקונטיינר. גם זה עבד, אבל שוב לא היה מושלם - קבצי ה .gitconfig כוללים יותר הגדרות ממה שהייתי צריך בקונטיינר.

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

$ git -c user.name ynon -c user.email ynon@tocode.co.il commmit -m '...'

בפיתוח יש כיף של לסמן "וי" ולהמשיך למשימה הבאה, ויש עוד סוג של כיף, אולי יותר חשוב, של למצוא פיתרון יותר טוב אחרי שמשהו עובד.

היום למדתי: פרופילים ב docker compose

12/04/2023

מה קורה אם אתם צריכים להפעיל גם משימה חד פעמית בפרויקט שמשתמש ב docker compose? טוב דרך אחת היא להתחבר לקונטיינר שכבר מריץ את היישום ולהפעיל את המשימה מתוכו. לדוגמה נניח שיש לי את התוכן הבא ב docker-compose.yml:

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

ויש לי סקריפט בשם myapp migrate שאני יכול להפעיל רק מתוך האימג' של backend, אז אני יכול להעלות את הסרביסים וכשצריך להפעיל את הסקריפט:

$ docker compose up -d
$ docker compose run backend bash -c "myapp migrate"

אבל זה עקום כי אולי הפעלה של backend לוקחת הרבה זמן או שאני צריך שהסקריפט ירוץ כשהיישום שלי למטה או לפני שהיישום עולה.

הייתי מעדיף לכתוב סרביס עבור הסקריפט ב docker-compose.yml שיכלול גם את הפקודה ויאפשר להריץ את הסקריפט עם הפרמטרים שאני צריך והאימג' הרלוונטי:

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

  migrate:
    image: backend
    command: myapp migrate

אבל עכשיו יש לי בעיה חדשה - הסקריפט ירוץ כל פעם שאני כותב docker compose up, ובשביל למנוע ממנו לרוץ אני צריך להעביר במיוחד את רשימת כל הסרביסים האחרים ל docker compose up.

מסתבר שיש פיתרון פשוט ומובנה ב docker compose שנקרא פרופילים: אנחנו מציינים לכל סרביס לאיזה פרופיל הוא שייך. סרביסים שלא שייכים לאף פרופיל יעלו כשאני כותב docker compose up, וסרביסים אחרים יופעלו כשאני מפעיל את הפרופיל או כשאני מפעיל אותם בצורה יזומה. במקרה של הסקריפט שלי זה אומר לעבור ל docker-compose.yml הבא:

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

  migrate:
    image: backend
    command: myapp migrate
    profiles: ["tools"]

עכשיו אני יכול להפעיל docker compose up כדי להפעיל את המערכת הרגילה, או docker compose run migrate כדי להפעיל את סקריפט המיגרציה.