آشنایی با مفاهیم اولیه یک پارادایم برنامه‌نویسی مهم
برنامه‌نویسی تابعی چیست و چه کاربردی دارد؟
برنامه‌نویسان تازه‌و‌ارد به دنیای برنامه‌نویسی در اولین گام با پارادایم برنامه‌نویسی شی‌گرایی آشنا می‌شوند. پارادیمی که وارثت و ارث‌بری اساس آن‌را شکل می‌دهد. با این حال، در دنیای برنامه‌نویسی پارادایم‌های دیگری نیز وجود دارد که برنامه‌نویسی تابعی (تابع‌گرا) یکی از مهم‌ترین آن‌ها است. پارادایمی که عملکرد کدها را بهبود می‌بخشد و روند توسعه برنامه‌ها را ساده‌تر می‌کند. در این مقاله قصد داریم به زبانی ساده پارادایم برنامه‌نویسی تابع‌گرا را بررسی کنیم و به‌طور اجمالی به معرفی مفاهیم مرتبط با این پارادایم بپردازیم.

برنامه‌نویسی تابعی چیست؟

برنامه‌نویسی تابعی، یک پارادایم برنامه‌نویسی برای ایجاد ساختار و مولفه‌های برنامه‌های کامپیوتری است که سعی دارد داده‌های تغییرناپذیر ارائه کند و مانع تغییر حالت اشیا شود. در یک برنامه تابع‌گرا همواره خروجی یکسان با مقادیری که به عنوان ورودی به توابع داده‌اید دریافت می‌کنید. به عبارت دقیق‌تر، خروجی‌های یک تابع در تعامل با ورودی‌های تابع هستند و قرار نیست اتفاق غیرمنتظره‌ای در پس‌زمینه برنامه رخ دهد. رویکردی که به‌نام حذف تاثیرات جانبی از آن نام برده می‌شود. در دنیای برنامه‌نویسی تابع‌گرا اصطلاحاتی همچون تغییرناپذیری (immutability) و تابع محض/خالص (pure function) وجود دارند که برای ساخت توابع بدون تاثیرات جانبی کاربرد دارند و اجازه می‌دهند ویژگی حفظ و نگه‌داری از سامانه‌ها بهبود پیدا کنند. 

توابع خالص/محض

اولین مفهوم زیربنای برنامه‌نویسی تابع‌گرا، توابع محض (Pure functions) هستند. تابع محض چیست، چه عاملی باعث محض شدن یک تابع می‌شود و چگونه تشخیص دهیم یک تابع محض است؟ تابع محض دو ویژگی مهم دارد، اول آن‌که زمانی که آرگومان‌های ثابتی دریافت می‌کند، همیشه نتیجه ثابتی ارائه می‌کند. این ویژگی قطعیت (deterministic) نام دارد. دومین ویژگی اشاره به این موضوع دارد که تابع محض هیچ‌گونه تاثیرات جانبی قابل مشاهده ندارد. 

 توابع محض برای ورودی‌های ثابت، خروجی‌های ثابت ارائه می‌کنند

فرض کنید قرار است تابعی بنویسید که مساحت دایره را محاسبه کند. یک تابع عادی شعاع دایره را به عنوان ورودی دریافت می‌کند و مطابق با فرمول radius * radius * PI شعاع را محاسبه می‌کند. در زبان برنامه‌نویسی Clojure عملگرها ابتدا ظاهر می‌شوند و بر همین اساس محاسبه شعاع (radius * radius *PI) به صورت (radius radius pi*) نوشته می‌شود. 

(def PI 3.14)

(defn calculate-area
  [radius]
  (* radius radius PI))
(calculate-area 10) ;; returns 314.0
فهرست یک

دلیل غیر محض بودن تابع فوق چیست؟ به دلیل این‌که تابع فوق از یک متغیر سراسری استفاده می‌کند و متغیر به عنوان پارامتر برای تابع ارسال نمی‌شود. فرض کنید برخی از ریاضی‌دانان این استدلال را مطرح کنند که مقدار عدد پی برابر با 42 است. در این حالت مقدار متغیر سراسری فوق تغییر می‌کند. در این حالت خروجی تابع غیر محض ما برابر با  10 * 10 * 42 = 4200 می‌شود. به عبارت دقیق‌تر برای پارامتر ثابت شعاع (radius =10) نتایج مختلفی را دریافت می‌کنیم. برای حل مشکل فوق مقدار PI را به عنوان پارامتری برای تابع ارسال می‌کنیم. در این حالت به جای آن‌که به یک شی خارجی دسترسی داشته باشیم به پارامترهای ارسالی برای تابع دسترسی داریم. در این حالت برای پارامتر radius = 10 و PI= 3.14 همواره نتیجه 314.0 را داریم و برای پارامترهای radius =10 و PI=42 همواره نتیجه 4200 را داریم.

(def PI 3.14)

(defn calculate-area
  [radius, PI]
  (* radius radius PI))

(calculate-area 10 PI) ;; returns 314.0
فهرست دو

خواندن فایل‌ها

اگر تابع قرار است فایل‌ها را بخواند دیگر یک تابع محض نیست، زیرا محتوای فایل‌ها متغیر است. 

(defn characters-counter
  [text]
  (str “Character count: “ (count text)))

(defn analyze-file
  [filename]
  (characters-counter (slurp filename)))

(analyze-file “test.txt”)
فهرست سه

تولید اعداد تصادفی

هر تابعی که عملکرد آن بر مبنای تولید اعداد تصادفی است یک تابع محض نیست. 

(defn year-end-evaluation
  []
  (if (> (rand) 0.5)
    “You get a raise!”
    “Better luck next year!”))
فهرست 4

توابع محض هیچ‌گونه تاثیرات جانبی قابل رویت ندارند

از جمله تاثیرات جانبی ناخواسته می‌توان به اصلاح یک شی سراسری یا یک پارامتر ارسالی یا ارجاعی اشاره کرد. اکنون قصد داریم تابعی پیاده‌سازی کنیم که مقدار صحیحی دریافت کند، یک مقدار به آن اضافه کند و نتیجه را باز گرداند. ما مقدار counter را داریم. تابع غیر محض مقدار را دریافت می‌کند، یک واحد به آن اضافه می‌کند و مقدار را دومرتبه درون متغیر قرار می‌دهد. 

(def counter 1)

(defn increase-counter
  [value]
  (def counter (inc value))) ;; please don’t do this

(increase-counter counter) ;; 2
counter ;; 2
فهرست 5

دقت کنید تغییر‌پذیری رویکردی نیست که مورد قبول برنامه‌نویسی تابع‌گرا باشد. اکنون می‌خواهیم یک شی سراسری را ویرایش کنیم. چگونه می‌توان این شی را به یک تابع محض تبدیل کرد؟ کافی است به سادگی این مقدار را به میزان یک واحد افزایش دهیم.  

(def counter 1)

(defn increase-counter
  [value]
  (inc value))

(increase-counter counter) ;; 2
counter ;; 1
فهرست 6

تابع محض increase-counter مقدار دو را بر می‌گرداند، اما counter همان مقدار اصلی را دارد. تابع فوق مقدار افزایش یافته را بدون ویرایش مقدار متغیر بر می‌گرداند. اگر از دو قاعده ساده فوق پیروی کنیم، درک برنامه‌ها ساده‌تر می‌شود. اکنون هر تابعی عملکرد مجزا دارد و نمی‌تواند روی سایر بخش‌های سیستم اثرگذار باشد. توابع محض پایدار، سازگار و قابل پیش‌بینی هستند. توابع محض زمانی که پارامترهای ثابتی دریافت کنند، همیشه نتایج یکسانی ارائه می‌کنند و ضرورتی ندارد به حالت‌هایی فکر کنیم که پارامترهای یکسان ممکن است نتایج مختلفی برگردانند، زیرا چنین حالت‌هایی هیچ‌گاه اتفاق نمی‌افتند. 

توابع محض چه مزیت‌هایی دارند؟

کدهایی که این‌گونه نوشته می‌شوند به شکل ساده‌ای آزمایش می‌شوند و نیازی نیست حالت‌های مختلف شبیه‌سازی شوند. همچنین، این امکان وجود دارد تا توابع محض را با چهارچوب‌های مختلف آزمایش واحد (unit test) کنیم. نمونه ساده‌ای از تابع محض، تابعی است که مجموعه‌ای از اعداد را دریافت و هر یک از اعداد را یک واحد افزایش می‌دهد. 

(defn increment-numbers
  [numbers]
  (map inc numbers))
فهرست 7

‌‌در مثال فهرست 7، مجموعه numbers را دریافت می‌کنیم و از map با تابع inc برای افزایش هر مقدار استفاده می‌کنیم و فهرست جدیدی از اعداد افزایش یافته بر می‌گردانیم.

(= [2 3 4 5 6] (increment-numbers [1 2 3 4 5])) ;; true

دقت کنید برای input [ 1 2 3 4 5] انتظار خروجی [2 3 4 5 6] را داریم و چنین اتفاقی نیز رخ می‌دهد. 

تغییرناپذیری

تغییرناپذیری به معنای عدم توانایی در تغییر و ثابت بودن مقدار در گذر زمان است. زمانی‌که داده‌های تغییرناپذیر داشته باشیم، وضعیت آن‌ها پس از ایجاد نمی‌تواند تغییر کند. در این حالت اگر بخواهید مقدار یک شی تغییرناپذیر را ویرایش کنید، این امکان وجود ندارد و به جای آن باید شی جدیدی با یک مقدار جدید ایجاد کنید. یکی از دستورات پر کاربرد جاوااسکریپت، حلقه for است. در فهرست ۸ حلقه for از متغیرهای تغییرپذیر استفاده می‌کند. 

var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;

for (var i = 0; i < values.length; i++) {
  sumOfValues += values[i];
}

sumOfValues // 15
فهرست 8

در هر بار تکرار حلقه مقدار متغیر i و وضعیت متغیر sumofValue تغییر می‌کند، اما تغییر‌پذیری در حلقه‌های تکرار نیز قابل کنترل است. اجازه دهید به زبان Clojure برگردیم. به قطعه کد فهرست نه دقت کنید. 

(defn sum
  [values]
  (loop [vals values
         total 0]
    (if (empty? vals)
      total
      (recur (rest vals) (+ (first vals) total)))))

(sum [1 2 3 4 5]) ;; 15
فهرست 9

‌تابعی به‌نام sum داریم که برداری از مقادیر عددی دریافت می‌کند. در این مثال recur به دفعات به loop بر می‌گردد تا وقتی که یک بردار خالی پیدا شود. در هر مرتبه تکرار حلقه این مقدار به مجموع متغیر total افزوده می‌شود. اگر به فهرست نه دقت کنید، مشاهده می‌کنید که از یک رویه بازگشتی متغیر برای حفظ تغییرناپذیری استفاده کرده‌ایم.
در فهرست 9 recur وظیفه پیاده‌سازی تابع را بر عهده دارد. ممکن است در این قطعه کد عملکرد recur خیلی شفاف و روشن نباشد، زیرا در حال استفاده از توابع درجه بالاتر (Higher Order Functions) هستیم. نگران نباشید. در شماره آینده اطلاعات بیشتری در خصوص این توابع به دست می‌آوریم. ساخت یک حالت نهایی برای اشیا در برنامه‌نویسی تابع‌گرا متداول است. 
فرض کنید، رشته‌ای داریم و می‌خواهیم رشته را به قالب url slug) SLug بخشی از یک آدرس اینترنتی است که به لینک صفحه‌ای که مدنظر است اشاره می‌کند) تبدیل کنیم. به فهرست ده دقت کنید. 

class UrlSlugify
  attr_reader :text

  def initialize(text)
    @text = text
  end

  def slugify!
    text.downcase!
    text.strip!
    text.gsub!(‘ ‘, ‘-’)
  end
end

UrlSlugify.new(‘ I will be a url slug   ‘).slugify! # “i-will-be-a-url-slug”
فهرست 10

اگر بخواهیم در زبان روبی همین قطعه کد را با رویه شی‌گرایی بنویسیم باید کلاسی به‌نام UrlSlugify ایجاد کنیم و متدی به‌نام slugify برای آن تعریف کنیم تا فرآیند تبدیل رشته ورودی به یک url slug انجام دهد. در قطعه کد فوق این رویه به درستی پیاده‌سازی شده است. قطعه کدی که در مرحله اول همه حروف را به حالت کوچک تبدیل می‌کند، فاصله‌های بدون استفاده را حذف می‌کند و فاصله‌های باقی مانده را با خط تیره‌ای پر می‌کند. 
فرض کنید در نظر داریم روند تغییرپذیری را با اجرای ترکیب تابع یا تغییر تابع مدیریت کنیم. به عبارت دقیق‌تر، خروجی (نتیجه) یک تابع به عنوان ورودی برای تابع دیگر ارسال شود تا تغییر رشته ورودی اصلی ضرورتی نداشته باشد. فهرست یازده این فرآیند را شرح می‌دهد. 

defn slugify
  [string]
  (clojure.string/replace
    (clojure.string/lower-case
      (clojure.string/trim string)) #” “ “-”))

(slugify “ I will be a url slug   “)
فهرست 11

در فهرست یازده توابع زیر را داریم:
Trim: فاصله‌های خالی انتهای رشته را حذف می‌کند. 
Lower-case: حروف رشته را به حالت کوچک تبدیل می‌کند. 
Replace: موارد مطابقت داده شده در یک رشته را با نمونه تعیین شده جایگزین می‌کند. 
ترکیب این سه تابع اجازه می‌دهد یک رشته slugify بسازیم. زمانی‌که درباره ترکیب توابع صحبت می‌کنیم از تابع comp برای ترکیب سه تابع به شرحی که فهرست دوازده نشان می‌دهد استفاده می‌کنیم.

(defn slugify
  [string]
  ((comp #(clojure.string/replace % #” “ “-”)
         clojure.string/lower-case
         clojure.string/trim)
    string))

(slugify “ I will be a url slug   “) ;; “i-will-be-a-url-slug”
فهرست 12

‌شفافیت ارجاعی  (Referentially Transparent)
اجازه دهید یک تابع square به شرح زیر پیاده‌سازی کنیم. 

(defn square
  [n]
  (* n n))

تابع محض زمانی که ورودی‌های یکسانی دریافت کند، همواره خروجی ثابتی را به شرح زیر ارائه می‌کند. 

(square 2) ;; 4
(square 2) ;; 4
(square 2) ;; 4
;; ...

زمانی که مقدار 2 به عنوان پارامتر برای تابع square ارسال می‌شود، تابع همیشه مقدار 4 را بر می‌گرداند و در نتیجه می‌توانیم آن‌را با مقدار4 جایگزین کنیم. این حالت تابع به‌نام شفافیت ارجاعی معروف است. به عبارت دقیق‌تر شفافیت ارجاعی برابر است با داده‌های تغییرناپذیر + تابع محض. با استفاده از مفهوم فوق، قادر به انجام کارهای جالبی همچون به‌خاطرسپاری تابع (memorize the function) هستیم. تصور کنید تابع زیر را داریم:
(+ 3 (+ 5 8))
می‌دانیم که حاصل جمع (+58) مقدار 13 است و در نتیجه تابع فوق همواره نتیجه 13 را بر می‌گرداند، در نتیجه می‌توانیم تابع بالا را به صورت زیر بنویسیم:
(+ 3 13)
عبارت فوق همواره مقدار 16 را بر می‌گرداند. به عبارت دیگر، می‌توانیم یک عبارت را با یک مقدار ثابت عددی جایگزین کنیم و به این شکل از تکنیک به‌خاطرسپاری تابع استفاده کنیم.

ماهنامه شبکه را از کجا تهیه کنیم؟
ماهنامه شبکه را می‌توانید از کتابخانه‌های عمومی سراسر کشور و نیز از دکه‌های روزنامه‌فروشی تهیه نمائید.

ثبت اشتراک نسخه کاغذی ماهنامه شبکه     
ثبت اشتراک نسخه آنلاین

 

کتاب الکترونیک +Network راهنمای شبکه‌ها

  • برای دانلود تنها کتاب کامل ترجمه فارسی +Network  اینجا  کلیک کنید.

کتاب الکترونیک دوره مقدماتی آموزش پایتون

  • اگر قصد یادگیری برنامه‌نویسی را دارید ولی هیچ پیش‌زمینه‌ای ندارید اینجا کلیک کنید.

ایسوس

نظر شما چیست؟