ch
Feedback
ToCode

ToCode

前往频道在 Telegram

טיפים קצרים למתכנתים מאת ינון פרק

显示更多
1 419
订阅者
+124 小时
-17
-530
帖子存档
ToCode
1 419
טיפ SQL: החברים של בוב אחד הדברים שלא נכנסו לקורס SQL החדש שכתבתי היה שאילתות רקורסיביות. וכן מאוד רציתי להקליט עליהן שיעור אבל הרגשתי שזה יותר מדי בשביל קורס SQL בסיסי, אז בואו נראה אותן בפוסט במקום. יצירת הנתונים אני מתחיל עם טבלה של אנשים, בשביל הדוגמה מספיק רק שמות ומזהים:
create table people(id integer primary key, name string);

insert into people(id, name) values
(1, 'Brian'),
(2, 'Bob'),
(3, 'John'),
(4, 'Mike'),
(5, 'Frank'),
(6, 'Sarah'),
(7, 'Layla'));
ואז אני יוצר טבלת חיבור רק שבמקום לחבר בין טבלת people לטבלה אחרת, טבלת החיבור שלי מחברת בין people לבין עצמה - כלומר זו טבלת חברים שכל שורה בה אומרת ששני אנשים הם חברים אחד של השני:
create table friends(id integer primary key, person_1 integer, person_2 integer);

insert into friends(person_1, person_2) values
(3, 5),
(3, 7),
(6, 7),
(2, 4);
קל לראות שיש לנו בנתונים שתי קבוצות חברים - בקבוצה אחת יש את ג'ון, פרנק, שרה וליילה (מזהים 3, 5, 6 ו-7) ובקבוצה השנייה את בוב ומייק (מזהים 2 ו-4). עכשיו בואו נראה איך בסיס הנתונים יכול לזהות את שתי הקבוצות האלה. חיפוש חברים בשביל לחפש חברים אני צריך להתחיל ממזהה של בן אדם ולחפש איפה הוא מופיע בטבלת החברים - אם המזהה הזה מופיע בשדה person_1 אז אני לוקח את person_2 בתור חבר, אם המזהה שלי מופיע בעמודה person_2 אז עמודת person_1 היא מזהה החבר שלו. בדוגמה שלנו השורה הראשונה בטבלת החברים אומרת ש 3 הוא חבר של 5, ולכן אם אני מתחיל לחפש חברים של 3 אני קודם כל מוסיף לחבורה את 5. אחרי זה בשורה השנייה אני מוסיף את 7 ובשורה השלישית אני מזהה ש 7 ו-6 חברים, 7 כבר נמצא אצלי באוסף התוצאות ולכן אני מוסיף גם את 6. התרגום של זה ל SQL נראה ככה:
WITH RECURSIVE nodes(x) AS (
   SELECT 3
   UNION
   SELECT friends.person_1 FROM friends JOIN nodes ON friends.person_2 = nodes.x
   UNION
   SELECT friends.person_2 FROM friends JOIN nodes ON friends.person_1 = nodes.x
)
SELECT nodes.x, people.name FROM nodes
inner join people
on people.id = nodes.x;
הפקודה WITH RECURSIVE מגדירה "לולאה" ב SQL, ובסוגריים אני כותב מזהה לעמודה. במקרה שלנו הלולאה אוספת תוצאות רק בעמודה אחת והעמודה נקראת x. אני מתחיל עם הערך 3 ומשתמש ב UNION כדי להמשיך את הלולאה. ה SELECT הראשון מחפש שורות שעמודת ה person_1 שלהן נמצאת ברשימת התוצאות שלי, וה SELECT השני מחפש את השורות שעמודת ה person_2 נמצאת ברשימת התוצאות. למעשה כל פעם שאחד ה SELECT-ים מהשלושה מחזיר משהו חדש בסיס הנתונים ישתמש ברשימת התוצאות החדשה שנוצרה כדי לחפש עוד תוצאות, וימשיך בזה עד שכבר לא יהיו תוצאות חדשות. בצורה כזאת השאילתה מחזירה את החבורה הראשונה - זו שכוללת את פרנק, ג'ון, ליילה ושרה. ואיך מוצאים את החברים של בוב? את זה אתן לכם לגלות לבד.

ToCode
1 419
חדש באתר: קורס SQL בסיסי הי חברים. מה לא הייתי עושה בשביל לא ללמוד SQL ... למדתי ריילס ואת ה ORM שלהם ושכנעתי את עצמי ש ORM זה הדבר וממילא אף אחד כבר לא כותב SQL; למדתי לעבוד עם מונגו ואז עם סייפר וגרמלין ואפילו דטהלוג ושכנעתי את עצמי ש NoSQL זה הדבר ותכף אף אחד לא יצטרך יותר SQL; ואפילו שמחתי כש ChatGPT התחיל לכתוב שאילתות וראיתי איך ממש תוך רגע נוכל לקבל שאילתות מהירות וטובות יותר ממה שאפשר לכתוב לבד. וכמו איזה גירוד מציק שמסרב להיעלם ה SQL תמיד חוזר ומזכיר לך שיש דרך קלה להוציא מידע מבסיסי נתונים. שבעצם זו רק עוד שפה עם כללי הדיקדוק שלה והעקרונות שלה וששליטה בה יכולה לשפר משמעותית את איכות הקוד שאנחנו כותבים - בין אם אנחנו משתמשים ב ORM, בין אם אנחנו נעזרים ב ChatGPT ואפילו כשאנחנו עובדים עם בסיסי נתונים שלא משתמשים ב SQL. ועם ההבנה הזאת ישבתי לכתוב ולהקליט קורס SQL מיוחד למתחילים ומתחילות שמתאים גם לאנשים בלי שום רקע קודם בתכנות. בקורס תלמדו מהצעד הראשון למה חשוב לשמור את המידע בצורה מאורגנת, מהי טבלה ואיך היא עוזרת לשמור מידע והכי חשוב תלמדו את התחביר של שפת SQL ואיתו תדברו עם הנתונים שלכם. וכן על הדרך אנחנו מדברים על ההבדלים בין בסיסי הנתונים השונים, ואיך SQL מאפשרת את השוני. הקורס הוא המשך והרחבה של סידרת פוסטים שכתבתי בנושא, ואם אהבתם את הסידרה אני בטוח שתאהבו גם את הקורס. הפרק הראשון חופשי להתרשמות, המשך הקורס במסגרת תוכנית המנויים (רק 117 ש"ח לחודש לגישה חופשית לכל תכני האתר). פרטים והרשמה בקישור: https://www.tocode.co.il/bundles/sql

ToCode
1 419
ידע מינימלי הכרחי תמיד היו אנשים שהיו יותר טובים בחיפוש בגוגל מאחרים. תמיד אם ידעת מה לחפש קיבלת תוצאות טובות יותר ומהר יותר. תמיד היו שטויות באינטרנט ואנשים שלא מספיק מבינים האמינו להן. ובכל זאת העבודה עם AI רק מחדדת את החשיבות של אותו "ידע מינימלי". בדיוק בגלל שהשטות נראית בדיוק כמו הדבר הנכון, ובדיוק בגלל שלפחות בתחום שלנו - השטות הרבה פעמים עובדת. זה לא סתם ש AI מצליח לכתוב דוגמאות קוד קטנות אבל לא מצליח לעשות עבודה אמיתית על פרויקט גדול (וכן Cursor AI מרשים ביותר ובדיוק ממחיש את הפער בין הרצוי למצוי). אנחנו לא שם. ואם AI לא מבין (לא יכול להבין) את ההשלכות של הדברים שהוא כותב, ולא כולל תפיסת עומק של הבעיה, הקונטקסט וכל האילוצים סביבה, אז מישהו אחר (אנחנו) יהיה חייב להשלים את החסר. ידע מינימלי הכרחי הוא הידע המינימלי שאנחנו צריכים על אודות נושא מסוים כדי לתפעל מנוע AI ולהגיע לתוצאות טובות. דוגמה פשוטה בריאקט היא הקוד הזה ש Chat GPT הציע לי כדי להציג את ימות השבוע ותיבת חיפוש על הנתונים:
import React, { useState } from 'react';

const WeekDaysList = () => {
  const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  const [searchTerm, setSearchTerm] = useState('');

  const handleSearchChange = (event) => {
    setSearchTerm(event.target.value);
  };

  const filteredDays = daysOfWeek.filter(day =>
    day.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <h2>Days of the Week</h2>
      <input
        type="text"
        placeholder="Search days..."
        value={searchTerm}
        onChange={handleSearchChange}
      />
      <ul>
        {filteredDays.map((day, index) => (
          <li key={index}>{day}</li>
        ))}
      </ul>
    </div>
  );
};

export default WeekDaysList;
אם אני לא אשאל אותו במפורש על ה keys ועל הבעיות בשימוש באינדקס בתור מפתח שם הוא לא יחשוב להציף את זה - פשוט בגלל שהוא לא חושב. זה בסדר בשביל מנוע AI, אבל לא בסדר בשביל קוד אמיתי. בהיבט של ה HTML אם אני לא יודע לבקש טיפים על נגישות ה AI לא יחשוב להשתמש ב label בשביל להציג את הכותרת. במיוחד עכשיו שאפשר לקבל כל כך הרבה עבור פרומפט הרבה יותר חשוב לדעת מה לבקש.

ToCode
1 419
החוכמה היא לדעת מה לשאול כשאנשים רואים בפעם הראשונה איך להשתמש ב fetch כלומר את הקוד הזה:
const res = await fetch(url);
const data = await res.json();
חלק קטן מהם ישאל "למה צריך await פעמיים". חלק מאלה ששאלו יסתפקו בתשובה "כי json מחזיר Promise", ואולי אפילו יגלו שאפשר לוותר על await אחד אם יכתבו:
const res = await fetch(url).then(r => r.json());
אבל רק מעטים ישאלו את השאלה החשובה - למה json צריך להחזיר Promise, אם JSON.parse הוא סינכרוני? מרגע ששאלתם את השאלה מאוד קל לגלות את התשובה. היא גם בתיעוד וגם Chat GPT יודע לתת הסבר די טוב. אבל לא התיעוד ולא ChatGPT יעזרו אם לא נשאל אותם. כן אפילו שהכלים נהיים יותר חכמים כל יום, ואולי במיוחד לאור עובדה זו, אנחנו צריכים להמשיך ולהיות יותר סקרנים ויותר מקצועיים ממה שאי פעם היינו. נ.ב. קרדיט לטום באינטרנט על ההשראה.

ToCode
1 419
ג'אסט דו איט - הרצת סקריפטים עם just במקום npm ג'אסט הוא כלי להרצת משימות לפרויקט. הוא עובד עם כל שפה ויש לו תחביר שקצת דומה ל make, רק שבעוד ש make נועד ספציפית לבנות מודולים לפרויקט, ג'אסט הוא יותר כללי במטרה שלו ומכוון להרצת משימות וניהול התלויות ביניהן. בואו נראה דוגמה קצרה איך just יכול לעזור לנו בפרויקט node כש npm מתחיל להסתבך. נניח שיש לי קובץ package.json כזה:
{
  "name": "demo-project",
  "version": "1.0.0",
  "description": "A simple demo project",
  "main": "index.js",
  "scripts": {
    "start": "npm run build && NODE_ENV=production node index.js",
    "build": "echo building the app",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^2.0.22",
    "babel-cli": "^6.26.0"
  }
}
שימו לב לבלוק הסקריפטים ובמיוחד לסקריפט start:
"start": "npm run build && NODE_ENV=production node index.js",
הסקריפט מריץ שני דברים, קודם את build ואחרי זה מפעיל את התוכנית. הוא יפעיל את התוכנית רק אם build הצליח ובהפעלת התוכנית הוא יגדיר משתנה סביבה בשם NODE_ENV. זה סקריפט שנועד להפעלה רק על מערכות יוניקס ואין בו באמת ניהול תלויות למרות שזה קצת נראה כמו. נדמיין שמישהו יוסיף עוד סקריפט לקובץ:
publish: "npm run build && npm run start"
אז במצב כזה ה build ירוץ פעמיים, כי publish יקרא לו ואז start גם יפעיל אותו. כשה npm מתחיל להסתבך, just יכול להציל את המצב. ג'אסט הוא כלי שרץ משורת הפקודה, אבל אנחנו יכולים להוסיף אותו לקובץ ה package.json בתור תלות נוספת לפיתוח כדי שאנשים לא יצטרכו להתקין אותו בנפרד:
  "devDependencies": {
    "just-install": "2.0.2",
  }
אחרי שהוספנו והתקנו אותו אנחנו יוצרים קובץ בשם justfile בתיקייה הראשית של הפרויקט עם תוכן שנראה כמו Makefile:
default:
  just --list

start: build
  NODE_ENV=production
  node index.js

build:
  echo building app
ועכשיו אפשר למחוק את כל בלוק הסקריפטים מ package.json ולעבור לעבוד עם just. אני מריץ:
$ npx just
just --list
Available recipes:
    build
    default
    start
כדי לראות את רשימת האפשרויות, ואז בשביל המשחק אני מריץ:
$ npx just start
echo building app
building app
NODE_ENV=production
node index.js
starting app
ואני רואה ש start הריץ גם את build וגם את פקודת ה node. בשביל להוסיף סקריפט publish ל justfile אני כותב:
publish: build start
  echo publish
ובהפעלה ברור ש build רץ רק פעם אחת:
$ npx just publish
echo building app
building app
NODE_ENV=production
node index.js
starting app
echo publish
publish
התיעוד של just מצוין ויש לו הרבה פחות פינות חדות בהשוואה ל make וגם ChatGPT מכיר אותו די טוב אז לא תהיה לכם בעיה להיעזר ב AI כשצריך. אפשר לקרוא יותר על הכלי בגיטהאב שלהם בקישור: https://github.com/casey/just/

ToCode
1 419
ניסוי דינו - מה אפשר לעשות היום עם Deno ו next.js בדף התיעוד של דינו יש רשימה של Web Frameworks שדינו תומך בהם ואני מודה שהפריט הראשון ברשימה בלבל אותי. כלומר שמעתי את אנשי דינו מדברים על תמיכה ב next.js אבל לא הבנתי שהכל כבר מוכן, ואם זה מוכן לא ברור למה האנשים ב vercel לא נותנים יותר פירסום לסיפור. אני מפרסם פה כמה ניסויים שעשיתי עם השילוב, לצערי בלי מסקנות מלהיבות. איך לפתוח פרויקט next.js חדש עם דינו הדבר הראשון שעובד עם הגירסה החדשה של דינו מותקנת הוא יצירת פרויקט next חדש. פשוט מפעילים:
deno run -A npm:create-next-app@latest my-next-app
באופן לא טיפוסי ל deno, הפקודה מייצרת תיקיית node_modules ושמה בה את כל הסקריפטים של next ואת כל התלויות שלהם. אפשר להפעיל את הפרויקט עם דינו במצב פיתוח וגם זה עובד:
deno task dev
והוא הצליח להגיש את הפרויקט כשנכנסתי עם הדפדפן ל localhost:3000. איך לוודא שאנחנו ב Deno למרות שהפעלתי את הפרויקט מדינו עדיין לא הייתי רגוע - למה צריך שם תיקיית node.js ואיך הסקריפטים של next עובדים עם קוד דינו. אז עדכנתי את הנתיב הראשי והוספתי לו קוד שקורא קובץ תוך שימוש ב API ספציפי של דינו:
export default async function Home() {
  const serverRenderedValue = await Deno.readFile('./value.txt');
  const decoded = new TextDecoder().decode(serverRenderedValue);

  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <p>Text in file is: {decoded}</p>
    </div>
  );
}
למרבה ההפתעה זה גם עבד והצלחתי לראות את התוכן של הקובץ בקומפוננטה על המסך. הכל עובד יפה עם ה Server Components של next. איפה הבעיה האתגר הבא היה לחשוב על Deployment ופה הגעתי לסוף המסלול בניסוי היום. הפעלה של deno task build כדי לבנות את הפרויקט החזירה:
Task build next build
  ▲ Next.js 14.2.13

   Creating an optimized production build ...
 ✓ Compiled successfully
   Linting and checking validity of types  ..Cannot read properties of undefined (reading 'bold')
זה היה עם הגירסה החדשה ביותר של דינו (ה RC לגירסה 2). בעיקרון אפשר היה להפעיל npm run build כדי לבנות בעזרת node אבל זה לא יעבוד כי השתמשתי ב API ספציפי לדינו כדי לקרוא קובץ:
> my-next-app@0.1.0 build
> next build

  ▲ Next.js 14.2.13

   Creating an optimized production build ...
 ✓ Compiled successfully
   Linting and checking validity of types  ..Failed to compile.

./src/app/page.tsx:6:37
Type error: Cannot find name 'Deno'.

  4 |
  5 | export default async function Home() {
> 6 |   const serverRenderedValue = await Deno.readFile('./value.txt');
    |                                     ^
  7 |   const decoded = new TextDecoder().decode(serverRenderedValue);
  8 |
  9 |   return (
פקודת build נחוצה כדי שאפשר יהיה להפעיל start ו deploy ולעבור לסביבת פרודקשן. אין ספק שדינו מתקדם בכיוון הנכון מבחינת תאימות אבל אני חייב להודות שאני עדיין לא רגוע. לפחות בהיבט של next.js, קשה לראות איך הם יצליחו לבנות תאימות בלי עזרה של החברים ב vercel, ושם נראה שמעדיפים להתמקד ב node. אז כן אפשר לבנות פרויקט next ב deno ולרוץ מקומית במצב פיתוח, ואם הקפדתם להשתמש רק ב APIs שקיימים ב node תוכלו גם בעתיד לבנות אותו לפרודקשן ב node, אבל במצב כזה לא ברור למה שנרצה לבצע את כל הפיתוח ב deno.

ToCode
1 419
היום למדתי להיזהר מהפונקציה contains בקלוז'ר האתגר הכי גדול עבורי עם קלוז'ר הוא חוסר העקביות של השפה, או שאולי יש לה איזשהו הגיון פנימי שאני לא מבין. הקיטור היום הוא על הפונקציות contains? ו keys. הפונקציה keys בקלוז'ר מקבלת מפה ומחזירה את המפתחות שלה. זה קל:
user=> (keys {:a 10 :b 20 })
(:a :b)
ברור שאי אפשר להשתמש ב keys על וקטור, כי אין לו מפתחות. קלוז'ר היא הרי לא JavaScript והיא לא חושבת שמפתחות של מערך זה האינדקסים שלו. זה מה שקורה אם מנסים:
user=> (keys ["a" "b" "c"])
Error printing return value (ClassCastException) at clojure.lang.APersistentMap$KeySeq/first (APersistentMap.java:168).
class java.lang.String cannot be cast to class java.util.Map$Entry (java.lang.String and java.util.Map$Entry are in module java.base of loader 'bootstrap')
בהתבסס על ההגיון הזה הלכתי לשחק עם הפונקציה contains? בעבודה על מפה, הפונקציה contains? בודקת אם ערך מסוים הוא מפתח במפה. עד פה הכל גם הגיוני:
user=> (contains? {:a 10 :b 20} :a)
true
בגלל ש keys לא עובדת על מערכים, חשבתי שגם contains? לא אמורה לעבוד על מערכים, וקצת הופתעתי לגלות שהיא דווקא עובדת ואפילו לפעמים צודקת:
user=> (contains? [1 2 3] 2)
true
אבל זה היה רק מוקש. למעשה contains? על מערכים בקלוז'ר בודקת אם הפרמטר שהיא קיבלה הוא אינדקס במערך - ולא ערך. הדוגמה הקודמת עבדה רק במקרה. הנה כמה דוגמאות יותר טובות:
user=> (contains? [1 2 3] 0)
true
user=> (contains? [1 2 3] 3)
false
user=> (contains? ['a' 'b' 'c'] 0)
true
user=> (contains? ['a' 'b' 'c'] 'a')
false
user=> (contains? ['a' 'b' 'c'] 9)
false
ונשארתי עם השאלה - אם האינדקסים של מערך הם המפתחות שלו, למה keys לא מחזירה את רשימת האינדקסים? ואם הם לא מפתחות, למה contains? בודקת אם ערך שהיא קיבלה הוא אינדקס חוקי במערך?

ToCode
1 419
בסוף זה תמיד הטיפול בשגיאות מצאתי השבוע באג מעניין פה באתר בפונקציה שתמיד נראתה לי כמו קסם. הסיפור הוא פשוט, לפעמים אנשים קונים מנוי לאתר עבור עובדים שלהם ואז העובד מקבל קישור מיוחד להרשמה. כשהעובד נרשם דרך הקישור הוא מחובר לאותו חשבון חברה שכבר נקנה עבורו במרוכז ויכול לגשת לקורסים. הקוד שטיפל ביצירת בחיבור העובד לקוד המיוחד הוא בסך הכל השורות:
create_user(!!params[:quickjoin]) do |user|
  user.after_confirmation
end
נו, למעשה המימוש נמצא בתוך הבלוק after_confirmation אבל זה לא חשוב לשיחה שלנו כרגע. ועכשיו שאני קורא את זה אני גם לא בטוח מה תפקיד הפרמטר quickjoin, אבל גם זה לא חשוב לשיחה שלנו כרגע. מה שכן חשוב וקצת קשה לראות מהמבנה זה ש create_user יכולה להיכשל. למשל אם מישהו בחר אימייל שכבר קיים במערכת, לא בחר סיסמה או לא בחר שם משתמש. במצב כזה הבלוק הפנימי לא נקרא וזה ברור כי לא נוצר משתמש. הקוד שמופעל אחרי כישלון ביצירת משתמש מתחבא בתוך הפונקציה create_user והוא השורות:
clean_up_passwords resource
set_minimum_password_length
return redirect_to quickjoin_path, alert: resource.errors.full_messages.join(', ') if params[:quickjoin] == '1'

respond_with resource
בפרט מה שמעניין אותנו זו השורה האחרונה - respond_with. היא מעניינת כי היא עובדת ממש בסדר לכל המשתמשים שנרשמים רגיל לאתר. כשיש כישלון ברישום היא מחזירה את הנרשם לדף הרישום הראשי שם הוא יכול לתקן את השגיאה ברישום ולנסות שוב. אבל אם מראש הקישור לרישום היה שונה כי הבן אדם הגיע מתוך קישור מיוחד כדי להשתמש בחשבון מתנה, הפונקציה respond_with לא רואה את זה ומחזירה את הנרשם לדף הרישום הראשי, שם הוא מנסה שוב להירשם אבל החשבון כבר לא מחובר לחשבון שקנו עבורו. ב Rails הפונקציה respond_with היא קיצור לזה:
def create
  @user = User.new(params[:user])
  respond_to do |format|
    if @user.save
      flash[:notice] = 'User was successfully created.'
      format.html { redirect_to(@user) }
      format.xml { render xml: @user }
    else
      format.html { render action: "new" }
      format.xml { render xml: @user }
    end
  end
end
בכתיב המלא הכל ברור - כשיצירת המשתמש נכשלה מציגים את הטופס מ new, ולא מהקישור המיוחד דרכו הוא ניסה להירשם. ברישום הבא המשתמש כבר ייכנס לאתר כמשתמש רגיל ולא מחובר לחשבון שהוכן עבורו. תיקונים? קל. קודם כל ב create_user עדיף להחליף את respond_with בגירסה הארוכה שלה, ואז בכישלון גם להפעיל redirect לקישור ממנו הוא ניסה להירשם. בנוסף להוסיף בדיקות צד-לקוח בטופס הרישום. בדיקות אלה אומנם לא יתפסו את כל הבעיות אבל ישפרו את החוויה למי ששכח למלא תוכן באחד השדות.

ToCode
1 419
setState(() {
      _counter--;
    });
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Counter Example'),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.remove),
              onPressed: _decrementCounter,
            ),
            SizedBox(
              width: 100,
              child: TextField(
                controller: TextEditingController(text: _counter.toString()),
                textAlign: TextAlign.right,
                readOnly: true,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                ),
              ),
            ),
            IconButton(
              icon: const Icon(Icons.add),
              onPressed: _incrementCounter,
            ),
          ],
        ),
      ),
    );
  }
}
אני לא יודע אם זה קלוד או שככה dart, אבל גירסת הפייתון נראית הרבה יותר טוב.

ToCode
1 419
חמש דקות עם flet עם כל האהבה לריאקט אני חייב להודות שלא הצלחתי ליהנות מכתיבת קוד React Native. תמיד היה יותר מדי Boilerplate ודברים לא עבדו מספיק כמו שרציתי או שלא נראו מספיק טוב על מכשירים שונים. אני יודע שיש המון אפליקציות טובות שכתובות בריאקט נייטיב ובטוח שהבעיה זה קודם כל אני אבל זה מה שיש ואני חושב שאני לא היחיד בסיפור הזה. מצד שני הרבה זמן גם התרחקתי מפלאטר בעיקר כי לא היה לי כח ל dart. לכן שמחתי למצוא לאחרונה את flet, סביבה לפיתוח יישומי flutter ב Python. הקוד יצא הרבה יותר נקי וממוקד בהשוואה למה שזכרתי את dart וכמעט לא היו התקנות. ככה זה עובד- התקנה ותוכנית ראשונה אני מפעיל PyCharm נכנס לתפריט החבילות ומתקין את החבילה flet. עכשיו יוצר קובץ חדש ומדביק את התוכן הבא (רובו מהתיעוד באתר של פלט עם תיבת טקסט שאני הוספתי):
import flet as ft


def main(page: ft.Page):
    page.title = "Flet counter example"
    page.vertical_alignment = ft.MainAxisAlignment.CENTER

    txt_number = ft.TextField(value="0",
                              text_align=ft.TextAlign.RIGHT,
                              width=100)

    def minus_click(e):
        txt_number.value = str(int(txt_number.value) - 1)
        page.update()

    def plus_click(e):
        txt_number.value = str(int(txt_number.value) + 1)
        page.update()

    page.add(
        ft.Row(
            [ft.Text("Counter Example", size=18)],
            alignment=ft.MainAxisAlignment.CENTER,
        ),
        ft.Row(
            [
                ft.IconButton(ft.icons.REMOVE, on_click=minus_click),
                txt_number,
                ft.IconButton(ft.icons.ADD, on_click=plus_click),
            ],
            alignment=ft.MainAxisAlignment.CENTER,
        )
    )

ft.app(main)
קראתי לקובץ demo1.py ונכנסתי למסוף בתוך ה pycharm. בתוך המסוף כתבתי:
flet run -r demo1.py
זה הספיק ל flet בשביל להציג ממשק גרפי על המסך שכלל תיבה עם מספר ושני כפתורים סביבה, אחד לפלוס ושני למינוס. לחיצות על הכפתורים כמובן שינו את המספר. כששיניתי את הקוד באופן אוטומטי הממשק על המסך התרענן כדי לשקף את הקוד החדש. אמריקה. אחרי זה יצאתי מהתוכנית וכתבתי במסוף:
flet run --web -r demo1.py
וזה הספיק בשביל ש flet יפתח שרת פיתוח, יריץ דפדפן שיתחבר אליו ויציג לי בדיוק את אותו ממשק בתוך הדפדפן. כמו שאמרתי - אמריקה. מאחורי הקלעים צריך להגיד, החבר'ה ב flet קצת מרמים. הם לא מתרגמים את הפייתון שלנו לפלאטר אלא משתמשים בפלאטר כדי להציג ממשק משתמש שבגדול נוצר דרך הפייתון שלנו. במלים אחרות: 1. הארכיטקטורה היא ממשק משתמש מרונדר בשרת. 2. יש שרת שזה יישום שכתוב ב go והוא מקבל JSON-ים שמתארים את ממשק המשתמש. 3. יש לקוח שזה אפליקציית פלאטר שמחוברת לשרת וקוראת ממנו איזה ממשק להציג. 4. שרת ה go מריץ את קוד הפייתון, הפייתון מעביר את ה JSON-ים ל go, ה go מייצר הגדרות פלאטר ושולח אותן לאפליקציית ה client. בצורה כזאת בפייתון אנחנו עובדים עם רכיבי UI של flet ולא רכיבים רגילים של פלאטר. כן פלאטר שם אבל הוא מוחבא טוב. מצאתי לא מעט התלהבות ברשת מ flet וגם מהמעט ששיחקתי איתו הכל עבד חלק ומהר, אבל אי אפשר להתבסס על "פלאטר" ולחשוב שאם אפשר לבנות משהו בפלאטר אז בטוח אפשר יהיה לבנות את זה ב flet. המטרה של flet היא בנייה מהירה של ממשקים לאפליקציה שכבר יש לכם, תוך שימוש בכלים ובסביבת העבודה הרגילה שלכם. השוואה ל dart ופלאטר אמיתי אני מודה שזכרתי ש Dart היתה שפה מאוד מסורבלת אז בשביל המשחק הלכתי ל Claude וביקשתי שיתרגם לי את הקוד ל dart. זה מה שקיבלתי:
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Counter Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _decrementCounter() {