בפוסט הקרוב נערוך היכרות עם שפת groovy שהיא הבסיס של grails ונראה חלק מהיכולות (והחסרונות) שלה ושל תכנות דינמי בכלל.
מלחמת השפות
הרשת מלאה בדיונים בסגנון "איזה שפה טובה יותר" שבדרך כלל מתלהטים והופכים לעימותים בין חסידים של שפות שונות. לדעתי הגישה צריכה להיות הפוכה. היכרות עם שפות וסגנונות שונים עוזרת להרחבת ארגז הכלים שלנו, ומאפשרת בחירה בטכנולוגיה שמתאימה לסוג הבעיה. יותר חשוב מכך היא העובדה שהמחשבה מושפעת מהמרחב שניתן לה על ידי השפה, וגם בשפות תכנות היכרות עם שפות וסגנונות שונים מרחיבה לנו את יכולות החשיבה גם אם לעולם לא נעשה שימוש באותן שפות.
למען האובייקטיביות אני אזכיר גם את החסרונות של תכנות דינמי: העובדה שדברים רבים נקבעים בזמן ריצה (כפי שנראה בהמשך) מבטלת את האפשרות של הקומפיילר לתפוס טעויות שיקרו בזמן ריצה, וכמו כן מונעת אפשרות לכלים אוטומטיים כמו השלמת קוד. אחד הדברים היפים בGroovy ובכלל בשפות לפלטפורמת הJVM הוא שניתן לשלב אותן בקלות יחד עם קוד JAVA כך שניתן ליהנות מכל העולמות, ולא חייבים להכריע בדילמה בין שפה דינמית וסטטית .
תחביר בסיסי
התחביר של Groovy מושתת על התחביר של JAVA. דבר זה מקל מאוד על לימוד השפה. למעשה קוד JAVA הוא תקני גם בשפת Groovy, למשל
System.out.println(new Date().getTime())
//output a long number
הביטוי המקביל בשפת Groovy יראה כך
println new Date().time
סוכר סינטקטי
כמו בדוגמא הקודמת, השפה מאפשרת "קיצורי דרך" רבים לביצוע פעולות. נראה דוגמאות ספורות בקצרה:
//avoid null pointer exceptions
def ref
println ref?.toString()
//GString templates
def name="joe"
println "Hello ${name}"
//define list with range, iterate with 'each' and run a clojure
[1..3].each{println it}
//map
def map = [name:'Joe',age:100,height:99.9]
println map.name
//define a list
def nums=[1,2,3,4,5]
//print the penultimate object
nums[-2]
//filter the list
nums.findAll{num-> (num %2 )==0}
//simple SQL
sql = Sql.newInstance(connStr, driverClass)
sql.eachRow("select * from persons", { println it.name } );
מהאנקדוטות הנ"ל רואים את הסינטקס המקוצר של השפה.
תכנות דינמי
קיצורי דרך הם חשובים ומועילים, אבל לא מצדיקים שפה חדשה. היכולת שקיימת בשפה ואינה קיימת בJAVA היא תכנות דינמי.
שימו לב שבדוגמא השניה הגדרנו מחרוזת, אך לא הצהרנו על המצביע כמצביע למחרוזת אלא כמצביע כללי. זה תקף גם לארגומנטים של פונקציה, וגם לתוצאה שחוזרת ממנה. לדוגמא
def add(x,y){
x+y
}
add(3,2) //return 5
add("Hello ", "World") //return "Hello world"
הטיפוס של הארגומנטים של הפונקציה, כמו גם הטיפוס של הערך המוחזר, אינם מוגדרים. ניתן לקרוא לה עם מספרים (ולקבל סכום) אבל גם עם מחרוזות (ולקבל מחרוזת משורשרת).
זה העיקרון הראשון – אין חובה להגדיר טיפוס עבור מצביע. כל פניה בסגנון x.y היא חוקית (בזמן קומפילציה) ורק בזמן ריצה נבדקת חוקיות הפניה בפועל.
את אותה הפונקציה אפשר להגדיר גם באופן הבא:
def add = {x,y -> x+y}
כאן יש משהו מעניין – הפונקציה הוגדרה בתור ערך של משתנה. זה מאוד שונה מתכנות סטטי בו יש הבדל בין המשתנים לבין המבנים של התוכנית שהם קבועים.
זה העיקרון השני – קוד הוא ערך ככל ערך אחר. ניתן להגדיר אותו במשתנה, להעביר אותו כארגומנט לפונקציה או להחזיר אותו מפונקציה. למעשה זה מה שעשינו בדוגמא
[1..3].each{println it}
מזה נובע גם העיקרון השלישי – שימוש רב בשיקוף (reflection) ושינוי המבנה של מחלקות ומשתנים בזמן ריצה.
בואו נראה כמה דוגמאות:
def talkingString = "ABC"
talkingString.metaClass."sayHello" = {println "hi"}
talkingString.sayHello()
בדוגמא רואים הוספה דינמית של פונקציה למחלקה String (אפילו שזו לא מחלקה שאנו יצרנו !).
דוגמא נוספת:
class Stam {
}
def stam = new Stam()
stam.saySomething()
כאן כמובן נזרקת שגיאה – הפונקציה אינה מוגדרת (השגיאה כאמור נזרקת רק בזמן ריצה!).
נוסיף למחלקה את הפונקציה הבאה
def methodMissing(String name, args) {
"method ${name} is not defined"
}
ונריץ שוב. הפעם לא תהיה שגיאה, ויודפס
method sayHello is not defined
הפוקנציה methosMissing מיירטת קריאות לפונקציות שאינן מוגדרות.
ניתן לממש אותה בצורה יותר מעניינת:
def methodMissing(String name, args) {
if (name.startsWith("say")){
def word = name.substring(3)
word
}
else
"methos ${name} is not defined"
}
כאן אנו מגיבים לכל קריאה בסגנון
sayXYZ()
על ידי החזרת המחרוזת "XYZ".
למימוש יעיל נשלב את שתי השיטות, ונוסיף את הפונקציות שנוצרו באופן דינמי למחלקה.
בצורה כזו ניתן לייצר DSL בקלות. למשל חשבו על מחלקה שמחזיקה מבנה נתונים של אנשים, ופונים אליה בצורה הבאה
getMoshesAge(), getSarasHeight() …
למעשה יש דוגמא טובה לזה בGrails – אם יצרתי domain בשם Blog אשר יש לו את המשתנה Name אני יכול לבצע שאילתא מול בסיס הנתונים על ידי הפקודה הבאה
Def blog = Blog.findByName(someName)
כלומר אני קורא לפונקציה שנוצרת באופן דינמי על סמך המשתנים שקיימים במחלקה Blog.
אני מקווה שהרעיון של תכנות דינמי יותר מובן עכשיו. בפוסטים הבאים נראה יכולות מתקדמות של הפלטפורמה.