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

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

בואו נתקן את cycle בפייתון

29/02/2024

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

print(list(itertools.islice(itertools.cycle("abc"), 10)))

ולקבל את התוצאה:

['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a']

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

print(list(itertools.islice(itertools.cycle([1, 2, 3]), 10)))

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

def easy_cycle(*args):
    match args:
        case [col] if hasattr(col, '__iter__'):
            return itertools.cycle(col)

        case _:
            return itertools.cycle(args)

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

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

print(list(itertools.islice(easy_cycle("abc"), 10)))
print(list(itertools.islice(easy_cycle("a", "b", "c"), 10)))
print(list(itertools.islice(easy_cycle(["a", "b", "c"]), 10)))

נ.ב. היה נחמד אם מנגנון ה Type Hints של פייתון היה מאפשר לי להגדיר שערך ההחזר של הפונקציה שכתבתי זהה לערך ההחזר של הפונקציה itertools.cycle. בינתיים אפשר להעתיק חתימות או לוותר על ה Type Hint במקרה כזה.

פיתרון Advent Of Code 2023 יום 13 בסקאלה

28/02/2024

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

המשך קריאה

פוטנציאל (טיפ לחיפוש עבודה)

27/02/2024

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

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

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

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

  3. להבליט שינויים מקצועיים קודמים שעשיתם, רצוי במכתב מקדים המצורף לקורות החיים.

  4. להיות מוכנים לתקופת הכשרה או ירידה בשכר. לא לבוא בדרישות.

  5. להתחיל בקטן - תוך כדי החיפוש שווה למצוא מקומות בתפקיד הנוכחי או אפילו בהתנדבות בהם אתם יכולים לעשות את הדבר שאתם רוצים לעשות. אותו מתכנת יוכל לבנות עיצובי UI/UX לאפליקציות דמיוניות או בהתנדבות. לא בטוח שהייתי מצרף אותם לקורות חיים אבל כן שיהיה משהו לדבר עליו בראיון.

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

בואו נשבור קצת ריאקט

26/02/2024

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

המשך קריאה

משחקים עם סקאלה - סריקת פיסקאות בקובץ

25/02/2024

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

Source.fromFile("demo.txt").getLines().foreach(println)

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

Scala (/ˈskɑːlə/ SKAH-lah)[8] is a strong statically typed
high-level general-purpose programming language that supports
both object-oriented programming and functional programming.

Designed to be concise,[9] many of Scala's design decisions are
intended to address criticisms of Java.[7]

Scala source code can be compiled to Java bytecode and run on
a Java virtual machine (JVM).
Scala can also be compiled to JavaScript to run in a browser,
or directly to a native executable.

On the JVM Scala provides language interoperability with Java so that libraries written in either language may be referenced directly in Scala or Java code.[10] Like Java, Scala is object-oriented, and uses a syntax termed curly-brace which is similar to the language C. Since Scala 3, there is also an option to use the off-side rule (indenting) to structure blocks, and its use is advised. Martin Odersky has said that this turned out to be the most productive change introduced in Scala 3.[11]

אני יודע שאני יכול להפעיל את getLines כדי לקבל רשימה של כל השורות, אבל אני רוצה לבנות פונקציה שתהפוך את זה לרשימה של רשימות של שורות, כאשר בכל תת רשימה יהיו השורות הצמודות. סך הכל יהיו לי 4 רשימות שיתאימו לארבעת הפיסקאות בקובץ.

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

    Source
      .fromResource("demo.txt")
      .getLines()
      .toParagraphs
      .maxBy(_.size)
      .foreach(println)

או כזה כדי למצוא את הפיסקה הארוכה ביותר (עם הכי הרבה תווים):

    Source
      .fromResource("demo.txt")
      .getLines()
      .toParagraphs
      .maxBy(_.mkString.length)
      .foreach(println)

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

class ChunkedIterator[T](iterator: Iterator[T])(p: (T => Boolean)) extends Iterator[List[T]] {
  override def hasNext: Boolean = iterator.hasNext

  override def next(): List[T] = {
    if (!hasNext) throw new NoSuchElementException("next on empty iterator")
    iterator.takeWhile(p).toList
  }
}

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

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

extension (i: Iterator[String]) {
  def toParagraphs: ChunkedIterator[String] = {
    ChunkedIterator[String](i) { f => f.nonEmpty }
  }
}

אז נכון הייתי שמח אם המנגנון הזה היה מובנה בסקאלה בדומה ל partition-by של קלוז'ר או chunk_by של אליקסיר, אבל לפעמים צריך לקבל גם את הקשיים של החיים ולעבוד עם מה שיש.

איך לזהות תירוצים לאי התקדמות מקצועית

24/02/2024

יש המון סיבות טובות להתרחק מטכנולוגיה חדשה כמו למשל-

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

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

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

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

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

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

משחקים עם מקביליות בסקאלה (חלק 2)

23/02/2024

בחלק הקודם של הפוסט כתבתי על pmap ואיך אפשר להשתמש בו כדי לחלק פעולה חישובית למספר תהליכונים כדי לשפר ביצועים. היום אני רוצה לדבר על עבודת IO, על המגבלה של Thread Pool במיקבול משימות הקשורות ל IO ועל הפיתרון עם Virtual Threads.

המשך קריאה

ביי ביי map בשביל לשנות ערכים במיקום מסוים ב JavaScript

22/02/2024

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

const newItems = oldItems.map((item, index) => 
    index === 1 ? 99 : item);

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

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

const newItems = oldItems.with(1, 99);

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

[].with(99, 0)

בנוסף with שובר מערכים מרווחים, אבל אני לא בטוח כמה נזק זה עלול לגרום. כלומר הקוד הזה:

Array(10).forEach(() => console.log('1'))

והקוד הזה:

Array(10).with(0, 0).forEach(() => console.log('1'))

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

[1, 2, 3, 4, 5].with(-1, 10)

היום למדתי - איפוס הגדרות postcss בפרויקט vite

21/02/2024

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

הסיפור שהיום לקח לי יותר מדי שעות מהחיים קשור ל postcss ולהתנהגות המוזרה שלו בתוך פרויקט vite - בהעדר הגדרה אחרת, postcss יחפש קובץ הגדרות במעלה עץ התיקיות עד לתיקיית הבית. בשביל הניסוי שמתי בתיקיית הבית קובץ בשם postcss.config.js עם התוכן הבא:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

ואז יצרתי פרויקט vite חדש לגמרי בתיקייה:

/Users/ynonp/a/b/c/d/helloworld

הפעלתי npm run build בתיקיה וקיבלתי את הודעת השגיאה הבאה:

> helloworld@0.0.0 build
> tsc && vite build

vite v5.1.3 building for production...
transforming (1) index.htmlnode:internal/process/promises:289
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/ynonp/a/b/c/d/helloworld): [Error] Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js

(@/Users/ynonp/postcss.config.js)
Error: Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js

(@/Users/ynonp/postcss.config.js)
    at load (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28883:11)
    at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28908:16
    at Array.map (<anonymous>)
    at plugins (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28907:8)
    at processResult (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28977:14)
    at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:29107:14]

Node.js v21.5.0

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

פיתרון? די פשוט מסתבר אחרי שמבינים את הבעיה. יוצרים קובץ vite.config.js בתיקיית הפרויקט עם התוכן הבא והכל מסתדר:

import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  css: {
    postcss: {},
  },
})

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

עוד מחשבה על תבנית ה index.ts שמייצא הכל

20/02/2024

בואו נדמיין פרויקט ריאקט שיש בו תיקייה בשם src/components/HomePage ובתוכה ערימה של תיקיות וקובץ אחד בשם index.ts:

.
├── EmptyState
│   ├── EmptyState.tsx
│   └── index.ts
├── ErrorState
│   ├── ErrorState.tsx
│   ├── ErrorState.types.ts
│   └── index.ts
├── RecentArticles
│   ├── RecentArticles.tsx
│   ├── RecentArticles.types.ts
│   └── index.ts
├── RecentArticlesCard
│   ├── RecentArticleCard.tsx
│   ├── RecentArticleCard.types.ts
│   └── index.ts
├── RecentArticlesContent
│   ├── RecentArticlesContent.tsx
│   ├── RecentArticlesContent.types.ts
│   └── index.ts
└── index.ts

תוכן הקובץ index.ts יהיה:

export * from './EmptyState';
export * from './ErrorState';
export * from './RecentArticles';
export * from './RecentArticlesCard';
export * from './RecentArticlesContent';

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

המשך קריאה