الثلاثاء، 20 سبتمبر 2011

سكالا …الدرس الثالث

سكالا …الدرس الثالث


Built-in Control Structures

المقصود بهذا العنوان هو الجمل التي تتحكم من خلالها فى مسار البرنامج مثل if و while و for و لقد تكلمنا عنهم سابقا بشكل سريع و الان مع التفاصيل.
قلنا بالسابق ان تقريبا فى سكالا كل شئ هو تعبير Expression و بالتالى فتلك الجمل هى فى الحقيقة Expressions اي انها ترجع قيمة و ذلك هو الفرق الجوهرى بين تلك الجمل و نظيرتها فى اللغات الاخري مثل الجافا.
هنلاحظ اننا قلنا تقريبا لان هناك استثناء من تلك القاعده و هو while loop فهى لا تعامل كتعبير.

If expressions

فى الغالب ﻻ يوجد If ﻻ تعود بناتج
مثال على if



  1. scala> val x = if (3>4) 11 else 12
  2. x: Int = 12

الحلقة التكرارية For expressions

مثال يقوم باستخرج اسماء الملفات فى المجلد الحالى (.) Current directory و هنلاحظ اننا بنستخدم كلاس File من الجافا عادي جدا و هنلاحظ كمان ان الناتج هو صف Array عناصرها من النوع File


  1. scala> val filesHere = (new java.io.File(".")).listFiles
  2. filesHere: Array1 = Array(./scalSession#1,./scalSession#2,./scalSession#3,./scala-2.9.0.1,./scala-2.8.1.final,./hello.scala,./ChecksumAccumulator.scala,./Summer.scala,./rational.scala)
  3. scala> for (file <- filesHere)
  4.      | println(file)
  5. ./scalSession#1
  6. ./scalSession#2
  7. ./scalSession#3
  8. ./scala-2.9.0.1
  9. ./scala-2.8.1.final
  10. ./hello.scala
  11. ./ChecksumAccumulator.scala
  12. ./Summer.scala
  13. ./rational.scala




حتى الان نعرف كل هذا و قمنا به من قبل المثير هو الجزء القادم و هو
  • التصفية (الترشيح) Filtering
فلنتامل هذا الكود

  1. scala> for (file <- filesHere if file.getName.endsWith(".scala"))
  2.      | println(file)
  3. ./hello.scala
  4. ./ChecksumAccumulator.scala
  5. ./Summer.scala
  6. ./rational.scala



قمنا هنا بعمل for loop علي صف array الملفات و لكننا وضعنا شرط و هو ان ينتهى اسم الملف بـ .scala
هل تتذكر فى اللغات اخرى ماذا كان يحدث على سبيل المثال جافا
كنا نقوم بعمل for loop بداخلها عدة جمل من ال if لكى نقوم بعمل Filtering لنحصل ما نريده بالتحديد
فى سكالا الوضع مختلف هنا يمكن وضع كل الشروط التى تريد توافرها فى اى عنصر بداخل الجمله ال for loop نفسها
فى المثال السابق
يمكننا عمل هذا الشرط عن طريق اضافة if داخل اقواس for
يمكن فى الحقيقة اضافة العديد من الشروط عن طريق اضافة المزيد من if



  1. scala> for(
  2.      | file <- filesHere
  3.      | if file.isFile;
  4.      | if file.getName.endsWith(".scala")
  5.      | )println(file)
  6. ./hello.scala
  7. ./ChecksumAccumulator.scala
  8. ./Summer.scala
  9. ./rational.scala



 مثال اخر
هذه المرة سنقوم بعمل مثال يقوم باستثناء المجلدات و اختيار الملفات فقط التى ينتهى اسمها ب .scala و تحتوى على pattern معين
و طبعا يمكننا ايضا ان نقوم ب nested iteration اي loop داخلها loop اخري

  1. scala> def fileLines(file: java.io.File) =
  2.      | scala.io.Source.fromFile(file).getLines.toList
  3. fileLines: (file: java.io.File)List[String]
  4.  
  5. scala> def grep(pattern: String) =
  6.      | for (
  7.      | file <- filesHere
  8.      | if file.getName.endsWith(".scala");
  9.      | line <- fileLines(file)
  10.      | if line.trim.matches(pattern)
  11.      | ) println(file+": "+line.trim)
  12. grep: (pattern: String)Unit
  13.  
  14. scala> grep(".*gcd.*")
  15. ./rational.scala: private val g = gcd(n.abs, d.abs)
  16. ./rational.scala: private def gcd(a: Int, b: Int): Int =
  17. ./rational.scala: if (b == 0) a else gcd(b, a % b)




قلنا من قبل ان الفلترة ليست جديدة و اننا اعتدنا عليها قبل ذلك و لكن ربما بشكل مختلف قليلا و يمكن تطبيق الشكل القديم فى سكالا ايضا


  1. scala> for (file <- filesHere)
  2.      | if (file.getName.endsWith(".scala"))
  3.      | println(file)
  4. ./hello.scala
  5. ./ChecksumAccumulator.scala
  6. ./Summer.scala
  7. ./rational.scala


هذا هو نفس المثال الاول و يعطي نفس النتيجة و ربما تكون تلك الطريقة مألوفة لنا و استخدمناها سابقا مع لغات اخري كما ذكرنا
فلماذا اذا طريقة سكالا فى وضع الشروط داخل الاقواس و ليس فى جسم الحلقة التكرارية ؟؟
السبب هو جعلها كما قلنا سابقا expression ترجع قيمة
فى طريقه سكالا تجد ان اذا لم يتوافر الشروط فى بدايه FOR LOOP لن يدخل الى جسم الحلقة التكرارية و هذا افضل و اكثر منطقية
  • Yield



  1. scala> val scalaFiles = for (
  2.      | file <- filesHere
  3.      | if file.getName.endsWith(".scala")
  4.      | ) yield file
  5. scalaFiles: Array1 = Array(./hello.scala, ./ChecksumAccumulator.scala, ./Summer.scala, ./rational.scala)


قمنا هنا باستقبال قيمة التعبير فى متغير scalaFiles و بعد تعيينه نلاحظ انه صف array عناصره من نوع java.io.File وقد تم هذا عن طريق استخدام كلمة yield وهي يجب ان تكون اول كلمة بعد الاقواس.

Exception handling with try expressions التقاط الاستثناءات

فى هذه الجزئيه ستجد اختلاف كبير عن اللغات الاخرى
لنرى هذا المثال




  1. import java.io.FileReader
  2. import java.io.FileNotFoundException
  3. import java.io.IOException
  4.  
  5.   try {
  6.      val f = new FileReader("input.txt")
  7.     // Use and close file
  8.   } catch {
  9.      case ex: FileNotFoundException => // Handle missing file
  10.      case ex: IOException => // Handle other I/O error
  11.   }



  1. هنلاحظ فى syntax اننا نستخدم بلوك catch مرة واحدة ونضيف به استثناءات متعددة على عكس الجافا حيث نستخدم بلوك catch لكل اسثناء.
  2. فى سكالا ليس اجباري التقاط checked exceptions على عكس الجافا.
  3. في سكالا try catch block هو تعبير اي يرجع قيمة
مثال


  1. scala> import java.net.URL
  2. import java.net.URL
  3.  
  4. scala> import java.net.MalformedURLException
  5. import java.net.MalformedURLException
  6.  
  7. scala> def urlFor(path: String) =
  8.      | try {
  9.      |  new URL(path)
  10.      | } catch{
  11.      | case e: MalformedURLException =>
  12.      | new URL("http://www.google.com")
  13.      | }
  14. urlFor: (path: String)java.net.UR



هنلاحظ فى هذه الحالة فان الدالة ترجع قيمة try اذا لم يلقى استثناء Exception is thrown و ترجع قيمة Catch اذا تم القاء استثناء و تم التقاطه.

Match expressions

يمكن اعتبار match بديل لـ switch فى اللغات الاخري مثل جافا و لكن مع بعض الاختلافات و المزايا الاخري.
مثال



  1. object ScalaApp{
  2.     def main(args: Array[String]) {
  3.         val firstArg = if (args.length > 0) args(0) else ""
  4.         val friend =
  5.             firstArg match {
  6.             case "salt" => "pepper"
  7.             case "chips" => "salsa"
  8.             case _ => "huh?"
  9.             }
  10.             println(friend)
  11.     }
  12. }



يمثل هذا الكود برنامج يصلح لل compilation يأخذ اول argument من سطر الاوامر و يطبع كلمة تختلف باختلاف argument
ملحوظه:- علامة _ تمثل فى سكالا معنى ائ شئ مجهول



  1. ~$ scalac ScalaApp.scala
  2. ~$ scala ScalaApp salt
  3. pepper


نلاحظ الاختلاف بين match و switch
  1. تستخدم مع اي نوع من المتغيرات ليس فقط integers و الاعداد الرقمية كـ switch
  2. لا يوجد بها كلمة break.
  3. اهم اختلاف كالعادة انها ترجع قيمة كما فى المثال يمكن استقبال القيمة المرجعة فى متغير friend .

Living without break and continue

هذا ما كان يحدث فى جافا على سبيل المثال




  1. int i = 0;                // This is Java
  2. boolean foundIt = false;
  3. while (i < args.length) {
  4.   if (args[i].startsWith("-")) {
  5.     i = i + 1;
  6.     continue;
  7.   }
  8.   if (args[i].endsWith(".scala")) {
  9.     foundIt = true;
  10.     break;
  11.   }
  12.   i = i + 1;
  13. }



اذا حاولنا تحويل هذا المثال الى سكالا فلن نحتاج ل break , continue
الغرض هنا من المثال ان نثبت لك ان يمكن كتابه اى كود بدون break ,continue
يمكن ان يتم الاستغناء عن continue,break ب if و قيمه  
boolean

 

 

 

  1. var i = 0
  2. var foundIt = false
  3.  
  4. while (i < args.length && !foundIt) {
  5.   if (!args(i).startsWith("-")) {
  6.     if (args(i).endsWith(".scala"))
  7.       foundIt = true
  8.   }
  9.   i = i + 1
  10. }
 
 
 
 
 
 

مدي المتغيرات Variables Scope

المقصود بهذا المصطلح هو المدي الذي يظهر فيه المتغير و يكون قابل للاستخدام خلاله Accessible و هو مشابه تماما للجافا عدا فى اختلاف واحد و هو انه يمكنك ان تسمي متغير بنفس الاسم فى نطاقات متداخلة و بالتالي سمكنك فى المدي الداخلى استخدام المتغير الداخلى
مثال


  1. scala> val a=1
  2. a: Int = 1
  3.  
  4. scala> {
  5.      | val a=2
  6.      | println (a)
  7.      | }
  8. 2
  9.  
  10. scala> println(a)
  11. 1
 
 
 
 فى هذه الحالة سيطبع 2 و المرة الثانية 1 و يقال فى هذه الحالة على المتغير الداخلى انه أظل على المتغير الخارجي shadow فلم يعد يمكننا رؤية المتغير الخارجي داخل النطاق الداخلى لانه يوجد متغير بنفس الاسم فى الداخل. هذا لم يكن مسموح به فى الجافا.

Local function

الدوال Functions
تكلمنا سابقا عن الدوال و الان نكمل بشئ من التفصيل بعض خواص سكالا الفريدة فى التعامل مع الدوال
  • الدوال المحلية Local functions
يمكن فى سكالا التصريح بدالة داخل دالة و بالتالى يكون مداها scope داخل تلك الدالة فقط و لا يمكن استخدامها خارجها و تلك الاستراتيجية يمكن ان تكون مفيدة جدا فى الدوال المساعدة helper functions و هى الدوال التى تصنع لتقوم بمهمات صغيرة داخل دالة كبيرة فبدلا من التصريح بها بالخارج و هى لن تستخدم الا فى الدالة الكبيرة فقط فلماذا لا يتم التصريح بها داخل الدالة ؟
مثال يوضح الفرق بين الطريقتين
الطريقة الاولى عن طريق التصريح بالدالة المساعده خارجا
الدالة الرئيسية هي processFile و التى تاخذ اسم ملف و عرض معين كـ argument ثم تمر عبر سطور الملف و تمرر كل سطر كـ argument للدالة المساعدة processLine لتقوم باختبار طول السطر بالمقارنة بالعرض المعطى فى البداية و فى النهاية تقوم بطباعة السطور التى يتخطى عرضها هذا الرقم و طباعة ايضا اسماء ملفاتهم
 
 
 
 
  1. import scala.io.Source
  2. object LongLines {
  3.     def processFile(filename: String, width: Int) {
  4.         val source = Source.fromFile(filename)
  5.         for (line <- source.getLines)
  6.             processLine(filename, width, line)
  7.     }
  8.     private def processLine(filename: String,width: Int, line: String) {
  9.     if (line.length > width)
  10.         println(filename +": "+ line.trim)
  11.     }
  12. }
 
 
 
 
 
 الان مع الطريقة الثانية باستخدام الدالة المحلية
 
  1. def processFile(filename: String, width: Int) {
  2.     def processLine(filename: String,width: Int, line: String) {
  3.         if (line.length > width)
  4.         print(filename +": "+ line)
  5.     }
  6.     val source = Source.fromFile(filename)
  7.     for (line <- source.getLines) {
  8.         processLine(filename, width, line)
  9.     }
  10. }
 
قمنا بتعريف الدالة processLine داخل الدالة processFile و سنلاحظ الان ميزة اضافية و هى ان الدالة المحلية يمكن لها ان تستخدم معطيات الدالة الاب مباشره فبدلا من ان نمرر للدالة المحلية اسم الملف و العرض و هما فى الاساس تم تمريرهم مسبقا للدالة الاب يمكن الاكتفاء بتمرير السطر فقط لتصبح كالاتي

  1. import scala.io.Source
  2. object LongLines {
  3.     def processFile(filename: String, width: Int) {
  4.         def processLine(line: String) {
  5.         if (line.length > width)
  6.             print(filename +": "+ line)
  7.         }
  8.     val source = Source.fromFile(filename)
  9.     for (line <- source.getLines)
  10.         processLine(line)
  11.     }
  12. }
 

Function literals الدوال الحرفية (البسيطة)

تعرفنا سابقا على الدوال الحرفية التى يمكن تمييزها فقط من شكلها و يمكن ان تستخدم كـ argument لدوال اخري First class functions
مثال


  1. scala> (x:Int) => (x*x)
  2. res16: (Int) => Int = <function>
  3.  
 
 
يمكن تعيينها لمتغير حتي يمكن استخدامها

  1. scala> val sqr =(x:Int) => (x*x)
  2. sqr: (Int) => Int = <function>
  3. scala> sqr(3)
  4. res18: Int = 9
 و يمكن ادخالها كـ argumment لدالة اخري
 
 
 
  1. scala> val myList = List(1,2,3,4,5,6)
  2. myList: List[Int] = List(1, 2, 3, 4, 5, 6)
  3.  
  4. scala> myList.filter( (x:Int)=> x>3 )
  5. res20: List[Int] = List(4, 5, 6)
 
 
 
 

Short forms of function literals

الان سنقوم بتبسيط المثال الاخير قليلا
  1. يمكننا ان نحذف نوع parameter لان الـ compiler يعلم ان عناصر myList هم Integers
  2. بعدها يمكننا حذف الاقواس حول x لتصبح هكذا 


  1. scala> myList.filter( x => x>3 )
  2. res21: List[Int] = List(4, 5, 6)

فى النهاية يمكننا الوصول لهذا الشكل
 
 
 
 
 
 
  1. scala> myList.filter( _>3 )
  2. res22: List[Int] = List(4, 5, 6)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
تلك الـ _ تمثل حاجز مكان placeholder للـ parameter ينتكلم عنه الان

Placeholder syntax

لجعل الموضوع اكثر اختصار بمكنك استخدام _ مكان parameter واحد او اكتر


  1. scala> myList.filter( _>3 )
  2. res22: List[Int] = List(4, 5, 6)

 و لكن بشرط ان يتخدم هذا الـ parameter ليس اكثر من مرة واحده فقط فى جسم الدالة
 
 
 
 
 
  1. scala> val f = _ + _
  2.   <console>:4: error: missing parameter type for expanded
  3.   function ((x$1, x$2) => x$1.$plus(x$2))
  4.          val f = _ + _
  5.                  ^

 

ﻻحظ ان + هى فى الاصل دالة تاخذ قيمتين
و لكن يكتب مثل هذا المثال هكذا

 

  1. scala> val f = (_: Int) + (_: Int)
  2.  f: (Int, Int) => Int = <function>
  3.  
  4.  scala> f(5, 10)
  5.  res11: Int = 15

 

الدوال غير مكتملة التطبيق Partially applied functions

تلك الدوال هي التعبير الذي ينتج من عدم تمرير كل الـ arguments التي تطلبها الدالة و يمكن عمل ذلك باستخدام placeholder


  1. scala> def sum(a: Int, b: Int, c:Int) = a + b + c
  2. sum: (a: Int, b: Int, c: Int)Int
  3. scala> sum(1,2,3)
  4. res0: Int = 6


 تمثل الـ underscore هنا placeholder لكل الـ argument



  1. scala> val a = sum _
  2. a: (Int, Int, Int) => Int = <function>
  3.  
  4. scala> a(1,2,3)
  5. res1: Int = 6


 تمثل الـ underscore هنا placeholder لـلـ argument الثاني فقط فتخرج لنا دالة ينقصها argument واحد

  1. scala> val b = sum(1, _: Int, 3)
  2. b: (Int) => Int = <function>
  3.  
  4. scala> b(2)
  5. res2: Int = 6
  6.  
  7. scala> b(5)
  8. res3: Int = 9





الاغلاق Closures

تطلق تلك التسمية على الدالة التي الدوال التى تستجدم متغيرات حرة من خارجها حيث انها عند التصريح بها تقوم الاحتفاظ بـمرجع reference لذلك المتغير (تقوم بالغلاق عليه داخلها)
مثال

  1. scala> var more = 1
  2. more: Int = 1
  3.  
  4. scala> val addMore = (x: Int) => x + more
  5. addMore: (Int) => Int = <function>
  6.  
  7. scala> addMore(10)
  8. res4: Int = 11


عند طلب الدالة قامت باضافة 10 لـقيمة المتغير more الذي تحتفظ بمرجع له و الدليل على انها تحتفظ بمرجع و ليست تحتفظ بقيمته الفعلية هو المثال القادم حيث نغير قيمة more و نلاحظ تغير نتيجة الدالة



  1. scala> more = 2
  2. more: Int = 2
  3.  
  4. scala> addMore(10)
  5. res6: Int = 12


ملحوظة هامة :-
فى سكالا يمكنك اثناء التصريح بالدالة اعطاءها امكانية استقبال عدد غير معروف من arguments من نفس النوع عن طريق وضع الرمز * بجانب النوع
مثال


  1. scala> def echo(args: String*) = for (arg <- args) println(arg)
  2. def echo(args: String*) = for (arg <- args) println(arg)
  3.  
  4. scala> echo("one","two")
  5. one
  6. two


الاستدعاء الذاتي Recursion

الاستدعاء الذاتي هو عندما تنادي الدالة على نفسها و هو يعتبر فى Functional style بديل للحلقات التكرارية loops حيث يمكننا من تجنب المتغيرات vars و لكن كانت هناك دوما مشكلة فى الاداء performance فعلى عكس الحلقات التكرارية التى تعتمد فى عملها فى الذاكرة على قفزات jumps من نهاية الحلقة لبدايتها فى حالة recursion تقوم الدالة بمناداة نفسها اكثر من مرة و ذلك يعتبر عبء على stack فى الذاكرة.
من هنا جاء مصطلح اخر و هو
  • Tail Recursion
و هو نوع من الـ recursion تقوم بكتابته recursion و لكن يقوم compiler فى سكالا بتحويله لقفزات jumps من نهاية الدالة لبدايتها تماما كالحلقات التكرارية و بذلك نكون قد استفدنا من مميزات الطريقتين.
كيف نكتب tail recursion ؟؟
الشرط الرئيسي هو أن يكون نداء الاستدعاء الذاتي هو اخر شئ فى جسم الدالة
مثال : نقوم بتعريف دالة تنادي نفسها عده مرات ثم تلقي استثناء exception.
فى تلك الحالة ليست tail recursion لانها تقوم باضافة 1 بعد الاستدعاء و بالتالي نلاحظ فى output انها استخدمت اكثر من اطار فى stack




  1. scala> def boom(x: Int): Int =
  2.      | if (x==0) throw new Exception("boom!") else
  3.      | boom(x-1) + 1
  4. boom: (x: Int)Int
  5.  
  6. scala> boom(3)
  7. java.lang.Exception: boom!
  8.     at .boom(<console>:8)
  9.     at .boom(<console>:9)
  10.     at .boom(<console>:9)
  11.     at .boom(<console>:9)
  12.     at .<init>(<console>:9)
  13.     at .<clinit>(<console>)
  14.     at .<init>(<console>:11)
  15.     at .<clinit>(<console>)
  16.     at $export(<console>)
  17.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  18.     at sun.reflect.NativeMethodAccessorImpl...


  1.  اما فى الحالة التالية فاصبح الاستدعاء هو اخر ما تنفذه الدالة و بالتاى يمكن تطبيق tail recursion و سنلاحظ فى output انه استخدم اطار stack واحد.
  2.  
  3. scala> def boom(x: Int): Int =
  4.      | if (x==0) throw new Exception("boom!") else
  5.      | boom(x-1)
  6. boom: (x: Int)Int
  7.  
  8. scala> boom(3)
  9. java.lang.Exception: boom!
  10.     at .boom(<console>:8)
  11.     at .<init>(<console>:9)
  12.     at .<clinit>(<console>)
  13.     at .<init>(<console>:11)
  14.     at .<clinit>(<console>)
  15.     at $export(<console>)
  16.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  17.     at sun.reflect.NativeMethodAccessorImpl....


 فى النهاية ارجو ان اكون قد قدمت معلومة جديدة و بشكل جديد
















































 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ليست هناك تعليقات:

إرسال تعليق