الأحد، 11 سبتمبر 2011

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

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


المجموعات Scala Collections

فى الدرس السابق توقفنا عند المجموعات Collections و تم عرض بعض Functional Methods مثل Map , Filler و اليوم كان المقدمه مع Reduce
وهى دالة تأخذ دالة أخري ك argument و تنفذها على قائمة لترجع فى النهاية قيمة واحده فقط value

1 التصريح بالقائمة




1 Scala> val x=List(1,2,3,4,5)
2 x: List[Int] = List(1, 2, 3, 4, 5)

 2 التصريح بالدالة المراد تطبيقها على القائمة



 1 scala> def add(a:Int, b:Int):Int=a+b
 2 add: (Int,Int)Int                                                                                              

3 استخدام reduceLeft و هذا يعني ان هناك reduceRight وهي تقوم بنفس الوظيفة و لكن من الاتجاه المعاكس
تقوم reduce بتنفيذ دالة add التي تأخذ 2 parameters على اول عنصرين من القائمة لتنتج رقما واحدا تستخدمه كأول parameter والعنصر الثالث كثاني parameter فى دالة add مرة اخري و هكذا حتى نهاية القائمة لتخرج قيمة و احدة .

 

1 scala> x.reduceLeft(add)
2 res1: Int = 15

 

قراءة الملفات

سنقوم بعمل كود يأخذ ملف نصي بعد اسم البرنامج فى سطر الاوامر كـ argument ويقوم بطباعة عدد احرف كل سطر و بعدها يقوم بطباعة السطر نفسه

1
3
4
5
6
 import scala.io.Source
if (args.length > 0) {
for (line print(line.length +""+line)
}
else
Console.err.println("Please enter filename")

 

شرح الكود
  1. نقوم باستيراد ال Source class من الحزمة scala.io
  2. نقوم باختبار لنتأكد ان المستخدم قد أدخل فعلا اسم ملف والا نقوم بطباعة please enter filename
  3. يقوم التعبير ((Source.fromFile(args(0 بفتح الملف و تعيينه لكائن object ننفذ عليه الدالة getLines لترجع لنا Iterator يرجع لنا سطر فى كل مرة
  4. تقوم for بالمرور على السطور و طباعة عدد احرف السطور ثم مسافة ثم السطر نفسه .

Class and Objects

نعرف جميعا ان الكلاس هى الخريطة التى تصف الكائن و هى تصفه عن طريق بعض الحقول (قيم و متغيرات ) و الدوال.
    • فلنتأمل الكود التالي

1 class ChecksumAccumulator ‫}‬
2 var sum = 0
3 }
4 val acc = new ChecksumAccumulator
5 val csa = new ChecksumAccumulator

قمنا بتعريف كلاس و اضفنا بها حقل sum ثم أنشأنا منها كائن عن طريق الامر new
نلاحظ هنا ان sum هو متغير var و بالتالي يمكن تغيير قيمته كالتالى
1 acc.sum=3

 و هذا يجعلنا نلاحظ انه فى سكالا لايوجد كلمة public مثل جافا لأنها هى الوضع الافتراضي اذا لم نضع كلمة private.
قمنا باضافة دالتين هما add و checksum

ملحوظة هامة : تعامل الـ parameters الخاصة بالدوال كـ val وبالتالى لا يمكن تغيير قيمتها داخل الدالة اي لا يمكن ان نقوم بالاتي مثلا

 

1 def add(b: Byte): Unit‫= ‬{
2 b=2
3 }

 


يمكن اختصار الكود للاتي تبعا لقواعد سكالا التى تم شرحها فى اليوم الأول

1 class ChecksumAccumulator ‫}‬
2 private var sum = 0
3 def add(b: Byte) { sum += b }
4 def checksum(): Int = ~(sum & 0xFF) + 1
5 }

Semicolon Interface

بالتاكيد لاحظنا ان فى سكالا لا يهم ان تكتب ; فى نهاية كل سطر الا بالتاكيد اذا كنت تنوي كتابة اكثرمن جملة فى سطر واحد و ذلك يرجع الى قواعد Semicolon inference و هى :-
    • يجب الا ينتهى السطر بـ Inflix operator مثل +
مثال

1 +X
2 Y

فى هذه الحالة سيفهم انه لا بد ان السطرين مكملين لبعضهم البعض حيث ان + هو inflix operator اي يجب ان يكون فى الوسط بين كائنين و بالتالى سيرجع ناتج الجمع
    • يجب الا ينتهى السطر داخل احد الاقواس
مثال

1 X)
2 (y+


فى هذه الحالة ايضا سيفهم انهم جملة واحدة و يرجع ناتج الجمع
  • أما فى هذه الحالة فلن يبتج ناتج الجمع و سيعامل كل سطر كجملة مستقلة حيث لم تنطبق اي من القواعد و كل سطر يمكن ان يكون له ناتج مستقل
1 X
2 y+


Singleton Objects

كائن الـ Singleton هو احد انماط التصميم design patterns التي يمكن وصفها بأنها كلاس يستنسخ منها كائن واحد فقط و لا تسمح بأكثر من ذلك .
فى سكالا يتم تعريفه باستخدام كلمة object بدلا من class
مثال


  1. import scala.collection.mutable.Map ‫}‬
  2. object ChecksumAccumulator
  3. private val cache = Map[String, Int]()
  4. def calculate(s: String): Int =
  5. if (cache.contains(s‫((‬
  6. cache(s‫(‬
  7. else‫}‬
  8. val acc = new ChecksumAccumulator
  9. for (c  cs‫(‬
  10. cs
  11. }
  12. }


ملاحظات
    • نلاحظ انه يأخد نفس اسم الكلاس التى قمنا بتعريفها من قبل و فى هذه الحالة يسمى كائن مرافق companion object وتسمى هي كلاس مرافقة companion class ولكن يجب ان يكونا معا فى نفس الملف المصدري و سيكون لدى كل منهما القدرة على استخدام محتويات بعضهم الخاصة private.
    • يحوي هذا الكائن على دالة calculate التى تأخذ string كـ parameter المراد استخراج checksum له ويحوي map المسمى cache يحفظ بداخله كل من الـ strings و checksum الخاص به الذي تم استخراجه قبل ذلك
      تبدأ الدالة بالبحث داخل cache لتري ان كان قد تم معالجة هذا string من قبل فاذا وجدته اعادت قيمته و ان لم تجد تقوم بحسابه عن طريق for loop التي تمر على الحوف المكونة للـ string وتقوم بعمل كائن من الكلاس المرافقة واستخدام الدوال الخاصة بها التى تقوم بالحساب (دالة add و دالة checksum )ثم تقوم بحفظ الناتج cs فى cache ليكون جاهز اذا تم طلبه مرة اخري و ذلك لتوفير موارد الجهاز حيث ان تلك العملية مكلفة ثم تقوم فى النهاية بارجاع القيمة للمستخدم.
    • يمكن استخدام هذا الكائن مباشرة حيث لايجوز عمل منه اي كائنات اخري باستخدام new وهذا هو الفرق الجوهري بينه و بين ال class
      يتم استخدامه كالتالي

  1. ChecksumAccumulator.calculate("hello‫"‬)

 

Scala Compliled Application

    • كي تقوم بمعاملة الكود ك application وتقوم بعمل له compile يجب ان يحتوي على دالة main التي تاخذ array من strings كـ parameter و لا ترجع شئ Unit

  1. def main(args: Array[String]) {
  2. }


و طبعا تلك ال array تمثل الـ arguments التى يمكن ان يأخذها البرنامج فى سطر الاوامر بعد كتابة اسم البرنامج.
مثال :
  1. object hello{
  2. def main(args: Array[String])
  3. println("hello world")
  4. }
  5. }

 نقوم بعمل compilation عن طريق الامر scalac يليه اسم البرنامج

  1.  amr@Amr:~$ scalac hello.scala

 

نقوم بتشغيل البرنامج باستخدام الامر scala يليه اسم البرنامج بدون امتداد وفى الحقيقة انت مش بتكتب اسم البرنامج انت بتكتب اسم الكائن object الذي يحتوي داله main ولكن قمنا بتسمسة الملف بنفس اسمه وهذا ليس اجباريا فى سكالا عكس الجافا حيث يكون اجباريا ان يكون اسم الملف باسم ال  class

 

  1. amr@Amr:~$ scala hello
  2. hello world

 

Types and Operation

الانواع الاساسية Basic types
Type size
Byte 8-bit
Short 16-bit
Int 32-bit
Long 64-bit
Char 64-bit
String a sequence of Chars
Float 32-bit
Double 64-bit
Boolean true or false
    • و طبعا نقدر نعرف Int بنظام Hexadecimal

  1. scala> val hex = 0x5
  2. hex: Int = 5
  3. scala> val hex2 = 0x00FF
  4. hex2: Int = 255

او نظام octal

  1. scala> val oct = 035
  2. oct: Int = 29
  3. scala> val nov = 0777
  4. nov: Int = 511

 او نظام decimal العادي

  1. scala> val dec1 = 31
  2. dec1: Int = 31
  3. scala> val dec2 = 255
  4. dec2: Int = 255

 علامة التنصيص المفردة تعني حرف character عكس المزدوجة التى تعني string

  1. scala> val a = 'A'
  2. a: Char = A

 

 

    • Special characters escaping sequences
تلك عبارة عن علامات ترمز لعلامات اخري لا يمكن وضعها داخل علامات التنصيص فى string
مثل /n التي ترمز لسطر جديد (زر Enter) و /t التي ترمز لزر Tab و /” و /’ و //
    • طريقة اخري لتفادى الرموز السابقة التى من السهل ان تصبح مزعجة اذا استخدمت بكثرة هى “”" “”" فبوضع النص داخل ثلاثة علامات تنصيص مزدوجة من كل ناحية يمكنك وضع كل العلامات الخاصة و المسافات و ستحفظ كما هي
مثال


  1. scala> println("""hello every body
  2.      | i just hit enter""")
  3. hello every body
  4.        i just hit enter

قلنا قبل كده ان ال operators ما هى الا دوال يعنى عندما نقوم بالتى

 

  1. scala> val sum = 1 + 2
  2. sum: Int = 3

 

فكل ما نفعله هو اننا نقوم بطلب الدالة + الموجودة فى كلاس Int

 

 

  1. scala> val sum = (1).+(2)
  2. sum: Int = 3

 مثال اخر ولكن تلك المرة على prefix operator

 

  1. scala> -2
  2. res2: Int = -2
  3.  
  4. scala> (2).unary_-
  5. res3: Int = -2= -2

 

 

    • نلاحظ ان تلك الخاصية تعطينا الكثير من المرونة فى كتابة الكود فيمكننا ان نصنع operators خاصة بنا
               سكالا تسمح ان نستخدم كـ prefix oprerator العلانات التالية فقط + – ! ~
               اما كـ inflix operator فتقريبا كل شئ مسموح
    • المساواة Equality
             نسستخدم == للتعبير عن يساوي و =! للتعبير عن لا يساوي و سنلاحظ ان فى سكالا انه يقوم بالمقارنة على اساس التساوي فى القيمة و ليس فى المرجع Value equality not reference equality
أمثلة


  1. scala> 1 == 2
  2. res31: Boolean = false
  3. scala> 1 != 2
  4. res32: Boolean = true
  5.  
  6. scala> 2 == 2
  7. res33: Boolean = true
  8.  
  9. scala> List(1, 2, 3) == List(1, 2, 3)
  10. res34: Boolean = true
  11.  
  12. scala> List(1, 2, 3) == List(4, 5, 6)
  13. res35: Boolean = false
  14.  
  15. scala> ("he"+"llo") == "hello"
  16. res40: Boolean = true

 

 

    • الاسبقية Operator precedence
الاسبقية تحدد اي جزء من التعبير ستقيم أولا مثال 2+3*5 تساوي 17 و ليس 30
و لكننا عرفنا قبل ذلك ان الـ operators ماهى دوال فكيف تقوم سكالا بتحديد الاسبقية ؟؟
تقوم سكالا بتحديد الاسبقية بناء على اول حرف فى الداله و ترتيبه فى جدول الاسبقيات
* / %
+ -
:
= !
<>
&
ˆ
|
(all letters)
(all assignment operators)
فاذا قمنا مثلا بتصنيع operator اسمه ++ و اخر اسمه *< سيكون لـ ++ الاسبقية لانه يبدأ بـ + بينما الاخر يبدأ بـ <
نلاحظ ان اخر القائمة هو assignment operator لانه لابد ان ينفذ بعد ان تتم كل العمليات الحسابية.

Functional Objects الكائنات الدالية

الفكرة الرئيسية هنا هي انها كائنات Immutable اي لا يمكن تغيير القيم بداخلها .
مثال سنتعرف فيه على تلك الفكرة و نتعرف فيه اكثر عن البرمجة الكائنية Object-Oriented Programming فى سكالا
البرنامج يقوم بمعالجة الاعداد المنطقية او الكسرية



  1. class Rational(n: Int, d: Int) {
  2. require(d != 0)
  3. private val g = gcd(n.abs, d.abs)
  4. val numer = n / g
  5. val denom = d / g
  6. def this(n: Int) = this(n, 1)
  7. def + (that: Rational): Rational =
  8. new Rational(
  9. numer * that.denom + that.numer * denom,
  10. denom * that.denom ) def + (i: Int): Rational = new Rational(numer + i * denom, denom) def - (that: Rational): Rational = new Rational( numer * that.denom - that.numer * denom, denom * that.denom ) def - (i: Int): Rational = new Rational(numer - i * denom, denom) def * (that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom) def * (i: Int): Rational = new Rational(numer * i, denom) def / (that: Rational): Rational = new Rational(numer * that.denom, denom * that.numer) def / (i: Int): Rational = new Rational(numer, denom * i) override def toString = numer +"/"+ denom private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }

 

 

نقوم الان بشرح الكود جزء جزء
  • }(class Rational(n: Int, d: Int
فى هذا الجزء نقوم بتعريف الكلاس و وضع لها parameters و هى تعتبر بمثابة الباني الاساسي primary constructor حيث تمثل n قيمة البسط و d قيمة المقام.
  • (require(d != 0
تقوم تلك الدالة باختبار الا يكون المقام بصفر لان فى هذه الحالة لن يكون عدد صحيح فهى توقف البرنامج اذا كانت قيمتها false و تكمل اذا كانت true
  • private val g = gcd(n.abs, d.abs)

  1. val numer = n / g
  2. val denom = d / g

 فى هذا الجزء نقوم بحساب القاسم المشترك الاكبر greatest common divisor عن طريق الدالة gcd و هى معرفة فى اخر الكلاس و تاخذ parameters القيمة المطلقة absolute value عن طريق دالة abs وهى دالة جاهزة فى سكالا معرفة فى الكلاس الخاصة بالـ Integers

 

  1. private def gcd(a: Int, b: Int): Int =
  1.      (if (b == 0) a else gcd(b, a % b

 

 

بعد ذلك نقوم بقسمة كل من البسط و المقام على هذا القاسم لنحصل على اصغر قيمة لهم
مثال 2/4 تصبح ½ وهكذا


  1. scala> val x =new Rational(2,4)
  2. x: Rational = 1/2

 

 

  • (def this(n: Int) = this(n, 1
ده constructor تاني بياخد قيمة واحده (البسط) و هو تلقائيا يقوم باعطائها المقام واحد و بالتالي تستطيع الان ان تنشئ rational number 5 بكتابة 5 فقط بدلا من 5,1
و نلاحظ ان فى سكالا الـ constructors عبارة عن سلسلة تنتهي دائما بـ primary constructor


  1. scala> val x=new Rational(5)
  2. x: Rational = 5/1

 

  • =def + (that: Rational): Rational

  1. new Rational(
  2. numer * that.denom + that.numer * denom,
  3. denom * that.denom
  4. )

 

تلك دالة تسمى + تاخذ parameter من نوع Rational و ترجع كائن جديد به مجموع العددين و هنا نلاحظ موضوع immutable حيث لم تضيف الدالى العدد الجديد على العدد الحالى و تغير قيمته و لكنها بدلا من ذلك قامت بخلق كائن جديد تحفظ فيه القيمة الجديدة

 

  1. scala> val x=new Rational(1,2)
  2. x: Rational = 1/2
  3.  
  4. scala> val y=new Rational(1,4)
  5. y: Rational = ¼
  6.  
  7. scala> x.+(y)
  8. res3: Rational = 3/4l = 3/4

 

طبعا عارفين الحركة الجاية دي سببها ايه ؟

  1. scala> x + y
  2. res4: Rational = ¾


المشكلة هنا ان دالة زي + بتاخد parameter من نوع Rational يعني لو اخدت Integer لن تعمل

 

  1. scala> val x=new Rational(1,2)
  2. x: Rational = 1/2
  3.  
  4. scala> x+2
  5. :7: error: type mismatch;
  6.  found   : Int(2)
  7.  required: Rational
  8.    x+2
  9.    ^

 فكان الحل هو overloading وهو ان نقوم بالتصريح بدالة اخري بنفس الاسم و لكن بـ parameters مختلفة
و عند طلب الدالة يختار comiler الدالة التى يستخدمها حسب parameter المعطي .

 

  1. def + (i: Int): Rational=
  2. new Rational(numer + i * denom, denom))

 و الان عند التجربة

 

  1. Scala> val x = new Rational (1,2)
  2. x: Rational = 1/2
  3.  
  4. scala> x + 2
  5. res2: Rational = 5/2 5/2

دعنا الان نجرب

  1. scala> 2 + x
  2. :7: error: overloaded method value + with alternatives (Double)Double  (Float)Float  (Long)Long  (Int)Int  (Char)Int  (Short)Int  (Byte)Int  (java.lang.String)java.lang.String cannot be applied to (Rational) 2+x
  3.  ^

لماذا حدث ذلك ؟؟
لاننا الان نستخدم الدالة + الموجودة فى كلاس Integer وليس فى كلاس Rational و ليس من اختصاصها ان تاخذ Rational كـ parameter فما الحل ؟؟
الحل فى سكالا هو التحويل الضمني Implicit Conversions
التحويل الضمني هو اعطاء الصلاحيات لل compiler ان يقوم ببعض التحويلات ضمنيا بدون تدخل منك
مثال : سنقوم باضافة هذا السطر

 

  1. scala> implicit def intToRational(x: Int) = new Rational(x)






    والان نجرب مرة اخري
    scala> 2 + x
    res6: Rational = 5/2
    لان سكالا الان تقوم بتحويل كل Int ضمنيا الى Rational.
  2. override def toString = numer +”/”+ denom
toString هى دالة تقوم بتحويل الكائن الي string و فى الحقيقة فكل ما تفعله هي انها تقوم بطباعة اسم الكلاس و بعدها علامة @ و بعدها رقم معين. و هى اصلا دالة ترثها inherit اي كلاس من كلاس java.lang.Object
مثال للبرنامج من غير سطر override
  1.  
     
     
     
     
     
     
     
     
     
     
    1. scala> val x = new Rational(1,2)
    2. x: Rational = Rational@11126f6
     
     
     
     
     
     
    تقوم كلمة override باستبدال تلك الدالة بدالة من صنعنا لتقوم بوظيفة افضل (طباعة العدد ).
    مثال للبرنامج بعد اسستخدام override
     
     
     
     
     
     
     
     
    1.  
    2. scala> val x=new Rational(1,2)
    3. x: Rational = 1/2
     
     
     
     
     
     
     
     
     
     
     
     
     
    اعداد الزميلة :

    أمنية جمال

     
     
     
     
     
     
     

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

إرسال تعليق