|
انواع
داده ها
یک نوع داده مجموعه ی مقادیری
است که با هم ارتباطی دارند به عبارتی در خاصیتی با هم مشترک هستند. اسکیم مجموعه
ای غنی از انواع داده ها را دارد.بعضی از این انواع ساده و بعضی دیگر ترکیبی از
انواع دیگر هستند.
انواع
داده های ساده:
در اسکیم انواع داده ی ساده
شامل انواع بولی٬ اعداد٬
کاراکتر ها و سمبل ها است.
بولین(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
اعداد(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 هستند.
مهدی ییلاقی اشرفی
|