Code Corona
آموزش Scheme - بخش دوم - نسخه‌ی قابل چاپ

+- Code Corona (http://forum.codecorona.com)
+-- انجمن: Private Forum (http://forum.codecorona.com/forumdisplay.php?fid=24)
+--- انجمن: Portal Forum (http://forum.codecorona.com/forumdisplay.php?fid=25)
+--- موضوع: آموزش Scheme - بخش دوم (/showthread.php?tid=134)



آموزش Scheme - بخش دوم - melomane - 08-04-2007

بخش دوم - انواع داده ها

یک نوع داده مجموعه ی مقادیری است که با هم ارتباطی دارند به عبارتی در خاصیتی با هم مشترک هستند. اسکیم مجموعه ای غنی از انواع داده ها را دارد.بعضی از این انواع ساده و بعضی دیگر ترکیبی از انواع دیگر هستند.

انواع داده های ساده:

در اسکیم انواع داده ی ساده شامل انواع بولی٬ اعداد٬ کاراکتر ها و سمبل ها است.

بولین(boolean):

داده های بولی در اسکیم به صورت t# برای true و f# برای false هستند. اسکیم به طور پیش فرض روالی به نام ?boolean دارد که بررسی می کند آرگومان ورودیش یک داده از نوع بول است یا خیر:
کد:
(boolean? #t)              =>  #t

(boolean? "Hello, World!") =>  #f
روال not آرگومان ورودی خود را منفی می کند. توجه کنید که آرگومان برای این روال یک نوع داده بولی در نظر گرفته می شود. از این رو اسکیم در برابر هر عبارتی که f# نباشد به عنوان t# رفتار می کند. مثال آخر این مسئله را به وضوح نشان می دهد:
کد:
(not #f)              =>  #t

(not #t)              =>  #f

(not "Hello, World!") =>  #f
ادامه مطلب ...


RE: آموزش Scheme - بخش دوم - melomane - 08-04-2007

اعداد(number):

اعداد در اسکیم می توانند صحیح (32-integer)٬ کسری (22/7-rational)٬ حقیقی (real-3.1416) یا مختلط (complex-2+3i) باشند. یک عدد صحیح عددی کسری٬ یک عدد کسری عددی حقیقی٬ یک عدد حقیقی عددی مختلط و یک عدد مختلط یک عدد است. مانند ?boolean روال های زیر نیز برای اعداد وجوددارند:
کد:
(number? 42)       =>  #t

(number? #t)       =>  #f

(complex? 2+3i)    =>  #t

(real? 2+3i)       =>  #f

(real? 3.1416)     =>  #t

(real? 22/7)       =>  #t

(real? 42)         =>  #t

(rational? 2+3i)   =>  #f

(rational? 3.1416) =>  #t

(rational? 22/7)   =>  #t

(integer? 22/7)    =>  #f

(integer? 42)      =>  #t
اعداد صحیح در اسکیم نیازی نیست که حتما در مبنای ده دهی تعریف شوند. با قرار دادن پیشوند b# برای دودویی٬ o# برای اکتال٬ x# برای هگزادسیمال می توان اعداد صحیح را در مبناهای دیگر تعریف کرد:
کد:
#b1100        عدد ۱۲
برای تست برابری دو عدد می توان از روال ?eqv که برای همه ی انواع داده ای کاربرد دارد استفاده کرد:
کد:
(eqv? 42 42)   =>  #t

(eqv? 42 #f)   =>  #f
(eqv? 42 42.0) =>  #f
در صورت اطمینان از عدد بودن پارامترها می توانید از = استفاده کنید:


کد:
(= 42 42)   =>  #t

(= 42 #f)   -->ERROR!!!

(= 42 42.0) =>  #t
سایر عملگرهای مقایسه نیز مجازند:

<, <=, >, >=
کد:
(< 3 2)    =>  #f

(>= 4.5 3) =>  #t
عملگرهای محاسباتی نیز عمل مورد انتظار را انجام می دهند:
کد:
(+ 1 2 3)    =>  6

(- 5.3 2)    =>  3.3

(- 5 2 1)    =>  2

(* 1 2 3)    =>  6

(/ 6 3)      =>  2

(/ 22 7)     =>  22/7

(expt 2 3)   =>  8

(expt 4 1/2) =>  2.0
اگر عملگرهای – و / یک آرگومان داشته باشند عدد را به ترتیب منفی و معکوس می کنند:
کد:
(- 4) =>  -4

(/ 4) =>  1/4
روال های max و min به ترتیب ماکزیمم و مینیمم آرگومان ها را برمی گردانند. این دو روال می توانند هر تعداد آرگومان ورودی داشته باشند:
کد:
(max 1 3 4 2 3) =>  4

(min 1 3 4 2 3) =>  1
روال abs نیز مقداز قدرمطلق یک عدد را بر می گرداند:
کد:
(abs  3) =>  3

(abs -4) =>  4

روال های ذکر شده تنها نمونه هایی از روال های از پیش تعریف شده در اسکیم هستند. اسکیم روالهای بسیار بیشتری دارد. به عنوان نمونه atan exp sqrt که به ترتیب ریشه ی دوم٬ آنتی لگاریتم طبیعی و آرک تانژانت عدد را بر می گردانند.

کاراکترها:

در اسکیم کاراکترها با قرار دادن \# قبل از کاراکتر معرفی می شوند. برای مثال c\# یک کاراکتر (کاراکتر c) است. کاراکترهای خاصی که هیچ نماد گرافیکی ندارند نیز newline\# و tab\# و \# یا space\# هستند.

روال ?char برای چک کردن کاراکتر بودن کاربرد دارد:
کد:
(char? #\c) =>  #t

(char? 1)   =>  #f

(char? #\;) =>  #t
نکته قابل توجه این است که کاراکتر ; به عنوان نماد توضیح در نظر گرفته نمی شود.

از روال های زیر برای مقایسه ی کاراکترها استفاده کنید:
کد:
char=?, char<?, char<=?, char>?, char>=?

کد:
(char=? #\a #\a)  =>  #t

(char<? #\a #\b)  =>  #t

(char>=? #\a #\b) =>  #f
اگر می خواهید مقایسه ها به بزرگ و کوچک بودن حروف حساس نباشد روال های زیر در اختیار هستند:
کد:
(char-ci=? #\a #\A) =>  #t

(char-ci<? #\a #\B) =>  #t
روال های زیر برای تبدیل حروف بزرگ و کوچک است:
کد:
(char-downcase #\A) =>  #\a

(char-upcase #\a)   =>  #\A
سمبل ها (symbols):

انواع داده هایی که تا اینجا گفته شدند خود-ارزیاب بودند. یعنی اگر آن ها را در listener تایپ کنید نتیجه همان چیزی است که شما تایپ کردید.
کد:
#t  =>  #t

42  =>  42

#\c =>  #\c

اما سمبل ها این گونه رفتار نمی کنند. علت این است که سمبل ها به عنوان شناسه ها برای متغیرها در اسکیم استفاده می شوند. از این رو حاصل ارزیابی آن ها مقداری است که متغیر در خود دارد. البته سمبل ها نوع داده ی ساده هستند نه مرکب.

برای تعریف یک سمبل در اسکیم به گونه ای که آن را یک متغیر در نظر نگیرد به این صورت عمل می کنیم:
کد:
(quote xyz) =>  xyz

اگر چه استفاده از quote در اسکیم رایج است اما می توان از این تعریف خلاصه و بهینه استفاده کرد:
کد:
'E
اسکیم با این دستور مانند (quote E) برخورد می کند.

سمبل ها در اسکیم توسط سلسله کاراکترها نام گذاری می شود. تنها محدودیت در نام گذاری سمبل ها این است که باید دقت شود با سایر انواع داده ها از قبیل اعداد٬ کاراکترها و داده های مرکب اشتباه نشوند. بنابراین this-is-a-symbol و i18n و <=> و *!$# سمبل هستند و 16 و i (یک عدد مختلط) و f# و “this-is-a-string” سمبل نیستند.

روال زیر برای تست سمبل بودن در اسکیم وجود دارد:
کد:
(symbol? 'xyz) =>  #t

(symbol? 42)   =>  #f
توجه داشته باشد که سمبل ها در اسکیم به طور پیش فرض نسبت به بزرگ و کوچک بودن حروف غیر حساس هستند. به عنوان مثال سمبل های calorie و Calorie یکسان تشخیص داده می‌شوند:
کد:
(eqv? 'Calorie 'calorie) =>  #t
به کمک فرم define می توان سمبل xyz را به عنوان یک متغیر global تعریف کرد:
کد:
(define xyz 9)
این تعریف مشخص می کند که متغیر xyz مقدار ۹ را در خود دارد. اگر xyz را به listener بدهیم نتیجه مقداری خواهد بود که xyz در خود دارد.
کد:
xyz =>  9
از فرم !set می توان برای تغییر مقداری که یک متغیر در خود نگهداری می کند استفاده کرد:
کد:
(set! xyz #\c)
و اکنون:
کد:
xyz =>  #\c

انواع داده های مرکب:
انواع داده های مرکب از طریق ترکیب مقادیری از انواع داده ها بر طبق قواعد مشخصی ساخته می‌شوند.



رشته ها (string):

رشته ها دنباله‌ای از کاراکتر ها هستند. (رشته ها نباید با سمبل ها اشتباه گرفته شوند. سمبل ها از انواع داده های ساده هستند و دنباله‌ای از کاراکترها را به عنوان نام خود دارند.)

رشته ها را می توان با قرار دادن دابل کوتیشن در دو طرف کاراکترها تعریف کرد. حاصل ارزیابی یک رشته خود رشته است:
کد:
"Hello, World!" =>  "Hello, World!"
روال string دسته ای از کاراکترها را گرفته و رشته‌ی حاصل از ترکیب آن ها را بر می‌گرداند:
کد:
(string #\h #\e #\l #\l #\o) =>  "hello"
اکنون بیایید یک متغیر global به نام greeting تعریف کنیم:
کد:
(define greeting "Hello; Hello!")
نکته این است که ; در داخل یک داده‌ی رشته‌ای به عنوان نماد توضیح در نظر گرفته نمی‌شود.

کاراکترهای یک رشته به صورت مجزا نیز قابل دسترسی و ویرایش هستند. روال string-ref یک رشته و یک اندیس (با شروع از ۰) را می گیرد و کاراکتر موجود در آن اندیس رشته را برمی‌گرداند:
کد:
(string-ref greeting 0) =>  #\H
با الحاق چند رشته می‌توان رشته‌ی جدید تولید کرد:
کد:
(string-append "E "

               "Pluribus "

               "Unum")

=>  "E Pluribus Unum"
همچنین در اسکیم می توانید یک رشته با طول مشخص بسازید و بعدا آن را با کاراکترهای دلخواه پر کنید:
کد:
(define a-3-char-long-string (make-string 3))
روال تشخیص رشته ?string است.

رشته های تولید شده توسط روال های string و string-append و make-string تغییرپذیرند. روال !string-set کاراکتر موجود در اندیس گرفته شده از یک رشته را جایگزین کاراکتر دیگر می‌کند. به این مثال توجه کنید:
کد:
(define hello (string #\H #\e #\l #\l #\o))

hello

=>  "Hello"



(string-set! hello 1 #\a)

hello

=>  "Hallo"


بردارها (vector):

vector ها نیز دنباله‌ای مانند رشته‌ها هستند با این تفاوت که عناصر آن‌ها هر چیزی می‌توان باشد٬ نه فقط کاراکترها. حتی عنصر آن می‌تواند vector دیگری باشد که راه مناسبی برای تولید vectorهای چند بعدی است.

تعریف یک vector حاوی پنج عدد صحیح اول در اسکیم این گونه است:
کد:
(vector 0 1 2 3 4) =>  #(0 1 2 3 4)
نمایش مقدار یک vector در اسکیم یک کاراکتر # به همراه محتویات vector در داخل پرانتز است. در قیاس با make-string روال make-vector برای ساختن یک vector با طول مشخص کاربرد دارد.
کد:
(define v (make-vector 5))
روال‌های vector-ref و !vector-set نیز برای دسترسی و ویرایش عناصر vector هستند. مانند سایر انواع داده‌ها ?vector برای تشخیص vector بودن یک داده است.



Dotted pairها و listها:

Dotted pair یک داده مرکب از دو مقدار دلخواه در یک زوج مرتب است. مقدار اول car و مقدار دوم cdrخوانده می‌شود. روال لازم برای ترکیب این دو مقدار و ساختن یک dotted pair ٬ cons است.
کد:
(cons 1 #t) =>  (1 . #t)
dotted pairها خود-ارزیاب نیستند و برای تعریف مستقیم آن ها به عنوان داده (بدون فراخونی روال cons) باید آن ها را مانند سمبل‌ها برای listener نقل (quote) کرد.
کد:
'(1 . #t) =>  (1 . #t)

(1 . #t)  -->ERROR!!!
روال‌های دستیابی به عناصر یک dotted pair ٬ cdr و car هستند:
کد:
(define x (cons 1 #t))

(car x)

=>  1

(cdr x)

=>  #t
عناصر dotted pair را می توان با روال های !car-set و !cdr-set تغییر داد:
کد:
(set-car! x 2)

(set-cdr! x #f)

x

=>  (2 . #f)
هر dotted pair خود می‌تواند شامل dotted pair دیگری باشد.


کد:
(define y (cons (cons 1 2) 3))

y

=>  ((1 . 2) . 3)
car در car این لیست 1 است. cdr در car این لیست 2 است. بنابراین:


کد:
(car (car y))

=>  1



(cdr (car y))

=>  2


اسکیم روال‌های اختصاری برای ترکیب‌های آبشاری روال‌های car و cdr دارد. به این صورت که caar یعنی “car یک car” ٬ cdar یعنی “cdr یک car” و این روند به همین صورت ادامه می‌یابد.


کد:
(car (car y))

=>  1



(cdr (car y))

=>  2


وقتی dotted pair ها به صورت تو در تو در عنصر دوم ظاهر شوند٬ اسکیم از یک نماد خاص برای نمایش نتیجه استفاده می کند.
کد:
(cons 1 (cons 2 (cons 3 (cons 4 5))))

=>  (1 2 3 4 . 5)

بنابراین (5 . 4 3 2 1) نمایش اختصاری ((((5 4) 3 ) 2) 1) است. آخرین cdr آن 5 است.

اگر آخرین cdr ٬ یک شیئ (object) خاص به نام لیست تهی (empty list) باشد (که به صورت () بیان می شود) اسکیم برای آن یک نماد اختصاری ویژه دارد. لیست خالی خود-ارزیاب در نظر گرفته نمی‌شود بنابراین برای استفاده از این مقدار در یک برنامه باید آن را نقل (quote) کرد:
کد:
'() =>  ()

نمایش اختصاری فرم
کد:
(1 . (2 . (3 . (4 . ()))))
به صورت زیر است:
کد:
(1 2 3 4)
این نوع خاص از dotted pairهای تو در تو یک لیست (list) نامیده می‌شوند. لیست بالا چهار عنصر دارد. این لیست را می‌توانستیم با نوشتن دستور زیر بسازیم:
کد:
(cons 1 (cons 2 (cons 3 (cons 4 '()))))


اما اسکیم روالی به نام list دارد که ساختن یک لیست را بهینه‌تر می‌کند. روال list هر تعداد آرگومان ورودی را می‌پذیرد و لیست حاصل از این اعداد را بر‌می‌گرداند.


کد:
(list 1 2 3 4)

=>  (1 2 3 4)

در واقع اگر تمام عناصر یک لیست را بدانیم می‌توانیم با نقل (quote) کردن٬ آن را به عنوان یک لیست معرفی کنیم:


کد:
'(1 2 3 4)

=>  (1 2 3 4)
عناصر یک لیست بر اساس اندیسشان قبل دسترسی هستند:
کد:
(

define y (list 1 2 3 4))

(list-ref y 0) =>  1

(list-ref y 3) =>  4



(list-tail y 1) =>  (2 3 4)

(list-tail y 3) =>  (4)

روال list-tail بقیه‌ی عناصر یک لیست را با شروع از اندیس گرفته شده برمی‌گرداند.

روال‌های ?pair ٬ ?list و ?null به ترتیب برای تشخیص dotted pair ٬ لیست‌ و لیست خالی به کار می‌روند.

کد:
(pair? '(1 . 2)) =>  #t

(pair? '(1 2))   =>  #t

(pair? '())      =>  #f

(list? '())      =>  #t

(null? '())      =>  #t

(list? '(1 2))   =>  #t

(list? '(1 . 2)) =>  #f

(null? '(1 2))   =>  #f

(null? '(1 . 2)) =>  #f

تبدیل انواع دادها به یک دیگر:

اسکیم روال‌های زیادی برای تبدیل انواع داده‌ها به یکدیگر در اختیار می‌گذارد. قبلا آموختیم که با روال‌های char-downcase و char-upcase می‌توان کاراکترهای بزرگ و کوچک را به یکدیگر تبدیل نمود.

با روال char->integer می‌توان کاراکترها را به اعداد صحیح و با روال integer->char اعداد صحیح را به کاراکترها تبدیل کرد. عدد صحیح معادل یک کاراکتر معمولا کد اسکی آن کاراکتر است.


کد:
(char->integer #\d) =>  100

(integer->char 50)  =>  #\2


رشته‌ها می‌توانند به لیست کاراکترهای سازنده تبدیل شوند:


کد:
(string->list "hello") =>  (#\h #\e #\l #\l #\o)

طبق این حالت سایر روال‌های تبدیل list->string ٬ vector->list و list->vector هستند.

اعداد قابل تبدیل به رشته هستند:
کد:
(number->string 16) =>  "16"
رشته‌ها نیز قابل تبدیل به عدد هستند. اما اگر رشته قابل معادل‌سازی با یک عدد نباشد مقدار f# برگردانده می‌شود:
کد:
(string->number "16")

=>  16

(string->number "Am I a hot number?")

=>  #f
روال string->number مبنای عدد را نیز در یک آرگومان ورودی دلخواه می‌پذیرد:



(string->number "16" 8) => 14

۱۶ در مبنای ۸ عدد ۱۴ است.

سمبل ها نیز می‌توانند به رشته تبدیل شوند و برعکس:

کد:
(symbol->string 'symbol)

=>  "symbol"



(string->symbol "string")

=>  string

سایر انواع داده‌ها:



اسکیم چند نوع داده‌ی دیگر نیز دارد. یکی از آن‌ها روال (procedure) است. تا اینجا با روال‌های زیادی مانند display ٬ + و . . . آشنا شدید. در واقع این‌ها هم متغیرهایی هستند که مقدار روال را در خود دارند اما آن گونه که در اعداد و کاراکترها می‌بینیم قابل رؤیت نیستند.
کد:
cons

=>  <procedure>
روال‌هایی که تا اینجا دیدیم روال‌های اولیه‌ای بودند که متغیرهای استاندارد global آن‌ها را در خود نگه می‌دارند. کاربرها هم می‌توانند روال‌های دیگری بسازند.

نوع داده‌ی دیگر پورت (port) است. یک پورت در واقع مجرایی است که ورودی و خروجی از طریق آن اعمال می‌شود. پورت‌ها اغلب وابسته به فایل‌ها و کنسول‌ها هستند.

در برنامه‌ی ”Hello World” که نوشتیم از روال display برای نمایش یک رشته در کنسول استفاده کردیم. روال display می‌تواند دو آرگومان ورودی بگیرد٬ یکی مقداری که باید نمایش داده شود و دیگری پورت خروجی که باید روی آن نمایش داده شود.
در برنامه‌ی ما آرگومان دوم روال display بیان نشده بود. در این صورت پورت خروجی پیش فرض به عنوان پورت خروجی استاندارد در نظر گرفته می‌شود.


می‌توانستیم با فراخوانی روال (current-output-port) از پورت خروجی کنونی استفاده کنیم. یعنی display را به صورت واضح فراخوانی می‌کردیم:
کد:
(display "Hello, World!" (current-output-port))

S-expression:

تمام انواع داده‌هایی که تا اینجا معرفی شدند را می‌توان به صورت مجموعه‌ای از یک نوع داده‌ی خاص به نام s-expression در نظر گرفت (s از symbolic) که تمامی انواع داده‌ها را دربردارد. بنابراین
42, #\c, (1 . 2), #(a b c), "Hello", (quote xyz), (string->number "16"), and (begin (display "Hello, World!") (newline))
همگی s-expression هستند.