שפות תכנות - Lua
לואה (ירח בפורטוגזית) היא שפת תכנות מברזיל שהופיעה בפעם הראשונה בשנת 1993 בתקופה בה היו מעט מאוד שפות שדומות לה. לואה מצאה לעצמה מקום בעולם בעיקר בזכות זה שהיא קלה יחסית ללמידה, יש לה סינטקס וסמנטיקה שדומה יחסית למה שיכירו מתכנתי C, וקל מאוד להטמיע אותה בתוך אפליקציות הכתובות בשפת C ובאמצעותה לאפשר לכתוב לוגיקה וקונפיגורציה בשפה דומה אבל יותר קלה מ-C.
הגדרות
לואה היא שפת סקריפט רבת-פרדיגמות קלילה וקלה להטמעה עם טיפוסיות דינמית.
בואו נסקור מה המילים האלו אומרות.
טיפוסיות דינמית
בלואה בודקים את נכונות הטיפוסים תוך כדי ריצת התוכנית, ולא לפני שהתוכנית מתחילה. דיברנו בהרחבה על טיפוסים וטיפוסיות בבלוג פוסט על טיפוסים, לכן לא ארחיב בנושא כאן.
שפת סקריפט
הרבה אנשים מגדירים שפות סקריפט כשפות בעלות טיפוסיות דינמית. אני מגדיר שפות סקריפט כשפות שניתן לכתוב בהן פקודות זו אחר זו ללא הצורך לאגד אותן לפונקציות/פרוצדורות או מחלקות.
בלואה, לדוגמא, אני יכול לכתוב קובץ קוד שיכיל את השורה הבאה בלבד:
print('Hello, code!')
ואז להריץ את הקובץ הזה ולקבל את התוצאה הרצויה. אבל אני לא יכול לעשות את אותו הדבר בשפת C, או Java, או Haskell,
שדורשות לאגד קטעי קוד כאלה בתוך פונקציות או מחלקות ואז בריצת התוכנית פונקציה ספציפית (בדרך כלל בשם main
)
נקראת ושם התוכנית מתחילה.
למרות שהרעיון של שפות סקריפט מאוד נפוץ בשפות עם טיפוסיות דינמית ומאוד לא נפוץ בשפות עם טיפוסיות סטטית, קיימות שפות תכנות עם טיפוסיות סטטית שמאפשרות לכתוב אותן כמו סקריפט, לדוגמא שפת OCaml. קשה לי לחשוב על שפת תכנות עם טיפוסיות דינמית שאני מכיר שאינה גם שפת סקריפט, למרות שלא מסובך יהיה לבנות אחת כזו. אם אתם מכירים, כתבו בתגובות!
רבת פרדיגמות
כשמדברים על שפת תכנות רבת פרדיגמות, הכוונה היא לשפת תכנות המאפשרת למתכנתים לכתוב בכמה גישות. בלואה, אפשר לכתוב סקריפטים שפשוט מריצים פקודות בזו אחר זו, או שאפשר לאגד רעיונות שונים לפרוצדורות כמו שאולי נעשה ב-C, או שאפשר לאגד קוד למחלקות עם תכנות מונחה עצמים, ואפילו יש ללואה כמה פיצ'רים, כמו פונקציות "first-class", שמאפשרות תכנות בצורה פונקציונלית. הבחירה בסגנון התכנותי בדרך כלל תתאים לסוג התוכנה שאתם כותבים.
קלילה וקלה להטמעה
אפשר לקרוא ללואה שפה קלילה בגלל שיש בה יחסית מעט פיצ'רים. זה מפשט גם את תהליך הלמידה של המתכנתים, גם את המימוש, וגם מאפשר ליצור מימוש יחסית מהיר של השפה. לואה, במיוחד עם המימוש LuaJIT, נחשבת לאחת השפות הדינמיות היותר מהירות.
בנוסף, לואה גם קלה להטמעה באפליקציות אחרות ולמעשה זהו אחד השימושים העיקריים שלה. ישנם שלל אפליקציות שהטמיעו את לואה בתוכן ומאפשרות למשתמש ליצור מודים, קונפיגורציות, וסקריפטים בתוך האפליקציה. כמה דוגמאות לכך הן עורך הטקסט Neovim, חוקר פקטות הרשת Wireshark, נגן המדיה mpv, המשחק Minecraft, ועוד המון.
איך נראית תוכנית בשפת Lua?
הפעם נשכתב את אותה תוכנית שכתבנו בבלוג פוסט על שפת C (שהייתה חלון ובו כדור מטייל) בעזרת ספרייה לפיתוח משחקים ששמה LÖVE. זו גם תהיה הזדמנות טובה להשוות בין שתי השפות.
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
BALL_SPEED = 200
BALL_RADIUS = 50
אנחנו מתחילים בהגדרה של כל מני קבועים בהם נשתמש, כגון גודל המסך, מהירות הכדור בפיקסלים לשניה, והרדיוס של הכדור.
local background = {
["color"] = {
["r"] = 0x08 / 255,
["g"] = 0x18 / 255,
["b"] = 0x29 / 255,
}
}
local ball = {
position = {
["x"] = SCREEN_WIDTH/3,
["y"] = SCREEN_HEIGHT/3,
},
direction = {
["x"] = 1,
["y"] = 1,
},
color = {
["r"] = 0xf9 / 255,
["g"] = 0x92 / 255,
["b"] = 0x26 / 255,
},
["radius"] = BALL_RADIUS,
}
כאן אנחנו מגדירים את האובייקטים הרלוונטיים לתוכנית - הרקע, והכדור. שימו לב שאנחנו לא מגדירים כאן טיפוסים, אלא משתנים המכילים דאטה מסוג "טבלה" - מיפוי של מפתח לערך. בג'אווה סקריפט אלו יקראו אובייקטים, בפייתון מילונים, ובקלוז'ר מפות. השמות אולי שונים בין שפה לשפה, אבל הכוונה דומה.
function love.load()
love.window.setMode(SCREEN_WIDTH, SCREEN_HEIGHT, {vsync=true,msaa=4})
end
function love.update()
updateBall(ball)
end
function love.draw()
love.graphics.clear(background.color.r, background.color.g, background.color.b, 255, true, true)
love.graphics.setColor(ball.color.r, ball.color.g, ball.color.b)
love.graphics.circle("fill", ball.position.x, ball.position.y, ball.radius)
end
אחרי שהגדרנו את הקבועים והאובייקטים, אנחנו מגדירים שלוש פונקציות עבור הפריימוורק שלנו LÖVE שמגדירות:
- מה לעשות מיד בתחילת התוכנית - במקרה שלנו, להגדיר את החלון
- מה לעשות בכל איטרציה בה אנחנו רוצים לעדכן את מצב התוכנית - במקרה שלנו, לקרוא לפונקציה שמעדכנת את מצב הכדור
- מה לעשות כדי להציג את המצב הנוכחי על המסך - במקרה שלנו, לצבוע את המסך בצבע שהגדרנו לרקע, להגדיר את צבע הכדור, ואז לצבוע עיגול בצבע הזה במיקום המתאים
function updateBall(ball)
applyKeys(ball)
swingBall(ball)
end
function applyKeys(ball)
if love.keyboard.isDown("d") then ball.direction.x = 1 end
if love.keyboard.isDown("a") then ball.direction.x = -1 end
if love.keyboard.isDown("s") then ball.direction.y = 1 end
if love.keyboard.isDown("w") then ball.direction.y = -1 end
end
function swingBall(ball)
local p, d = swing(ball.position.x, ball.direction.x, 0 + ball.radius, SCREEN_WIDTH - ball.radius)
ball.position.x = p
ball.direction.x = d
local p, d = swing(ball.position.y, ball.direction.y, 0 + ball.radius, SCREEN_HEIGHT - ball.radius)
ball.position.y = p
ball.direction.y = d
end
function swing(position, direction, min, max)
if (position <= min) then
position = min
direction = 1
elseif (position >= max) then
position = max
direction = -1
end
local dt = love.timer.getDelta()
position = position + (direction * BALL_SPEED * dt)
return position, direction
end
בשלב האחרון אנחנו מגדירים איך לעדכן את מצב הכדור. הלוגיקה כאן מורכבת משני חלקים: הראשון הוא לשנות את כיוון הכדור לפי הקלט שמגיע מהשחקנים, והשני משנה את מיקום הכדור למיקום הבא שבו הוא אמור להיות.
יש שני דברים ששווה לשים לב אליהם ששונים בין הגרסא שכתובה ב-C לבין הגרסא שכתובה ב-Lua:
-
הראשון הוא העובדה שבלואה אין מצביעים ואנחנו לא מנהלים את הזיכרון בצורה ידנית. כשאנחנו מעבירים לפונקציה ערכים פשוטים, כמו מספרים ובוליאנים,
ערכם מועתק ומועבר לפונקציה. אם נשנה אותם, זה לא ישפיע על המשתנה מחוץ לפונקציה.
כאשר אנחנו מעבירים לפונקציה ערכים מורכבים, כמו טבלאות, הערך לא מועתק, אלא עובר רפרנס לערך.
כלומר, אם נשנה אותו, גם הערך המקורי ישתנה.
לכן אנחנו יכולים לשנות את
ball
אבל לא אתposition
ו-direction
. -
בגלל שלא יכלנו לשנות את
position
ו-direction
, אנחנו מחזירים את הערכים האלו מהפונקציהswing
ואז מציבים אותם ב-ball
. הדבר המעניין הוא שבלואה אנחנו יכולים להחזיר יותר מערך אחד מפונקציה.
אני חושב שזה מעניין להשוות את התוכנית שכתבו כאן לתוכנית C המקבילה שכתבנו לפני. בעוד ששתי התוכנית דיי דומות אחת לשניה, אפשר לראות שהתוכנית הכתובה ב-Lua צריכה להתעסק ולדאוג לפחות דברים מאשר התוכנית שכתובה בשפת C, וזה בזכות הניהול הזכרון האוטומטי של לואה ומבני הנתונים שמגיעים בילט-אין בשפה.
אתם יכולים לראות את הקוד בשלמותו כאן וגם להשוות מול קוד ה-C המממש את אותה התוכנית.
הקוד בפעולה
מאחר ומדובר בבדיוק אותה התוכנית כמו בבלוג פוסט הקודם, לא טרחתי ליצור בילד גם בשביל הקוד לואה, לכן אפנה אתכם לבלוג פוסט ההוא כדי לראות איך התוכנית נראית :)
איך ללמוד Lua?
לצערי לא מצאתי מדריך שמאוד אהבתי ללואה. אם מצאתם כזה, אתם מוזמנים לקשר אותו בתגובות. בגלל שלואה יחסית קלה ללמידה, בכל זאת יש לי כמה הצעות:
-
אם אתם מתכנתים יחסית מנוסים, יכול מאוד להיות שיספיק לכם לעבור על משהו כמו learnxinyminutes.com/lua, ואז פשוט להתחיל לנסות לכתוב פרוייקטים בדרגות קושי עולות, לדוגמא סקריפטים לתוכנות או משחקים עם Love2D, או לקרוא קוד קיים. בנוסף, יכול להיות שגם פשוט לקרוא את הרפרנס לשפה יעשה את העבודה.
-
אם אתם מתכנתים קצת פחות מנוסים, אולי כדאי להתחיל דווקא עם שפה אחרת שדיי דומה ללואה שיש לה מדריכים טובים למתחילים כמו ג'אווה סקריפט. אני חושב שבעזרת Eloquent JavaScript אפשר ללמוד הרבה על איך לתכנת בשפה שמאוד מאוד דומה ללואה, ואז המעבר ללואה יהיה יחסית פשוט.
האתר הרשמי של לואה מכיל עמוד דוקס שבו תוכלו למצוא מידע נוסף.
פרוייקטים מעניינים הכתובים ב-Lua
יש שני שימושים מרכזיים שאני מכיר ללואה:
-
משחקים - בין אם משחקים שכתובים לגמרי בלואה, כמו Balatro ו-Angry Birds, או משחקים בהם המנוע כתוב בשפה אחת והלוגיקה בלואה, ויש הרבה כאלה.
-
בתור שפת סקריפט לאפליקציה אחרת - וגם לזה יש רשימה ארוכה של תוכנות פופולריות המשתמשות בלואה.
סיכום
לואה היא שפת תכנות קלילה יחסית ששווה להכיר, בעיקר מכיוון שהיא יכולה לפתוח דלתות למאחורה של הרבה תוכנות. ואם אתם מתכנתים המתעניינים בעולם פיתוח המשחקים, השפה בשילוב עם הספרייה LÖVE יכולה להיות אחת הדרכים היותר טובות להיכנס לעולם הזה.
אם יש שפת תכנות (שאינה מיינסטרימית) שהייתם רוצים שאסקר, ספרו לי בתגובות!
רוצים להגיב? בדקו מהי שאלת הסינון בעמוד הראשי.