آموزش کلوژر ۴ - توابع بی‌نام

clojure
learning_clojure
clojurescript
tutorial

#1

قبل از اینکه شروع کنیم بد نیست بدونید که، اگر نیاز به REPL انلاین دارید و می خواین کد های این قسمت رو تست کنید می تونین از این REPL استفاده کنین. فقط نکته ای که باید بهش توجه کنید این هست که این REPL در واقع clojurescript هست و ممکن هست بعضی از نتایج با REPL کلوژر متفاوت باشه.

یادآوری

قبل از هرچیزی، یه یادآوری میکنم از بخش قبلی آموزشهای کلوژر.
گفتیم (اساتید فرمودند) که این تابع:

(defn cube [number]
  (* number number number))

برابر است با این تابع:

(def cube (fn [number] (* number number number)))

(مثال رو دقیقا عین بخش قبلی آموزش نیاوردم چون حس میکنم این مثال برای توضیحات من مناسبتره. توی این مثالها، یک عدد وارد تابع میشه و در خودش ضرب میشه)
یعنی defn ترکیبی از def و fn هست. اینکه چرا اینطوریه، مربوط به مبحث macroها میشه که بعدا توضیح داده خواهد شد. یه کم مفصله. فعلا بدونید که defn یه ماکرو هست که کدهامون ساده‌تر و خواناتر میکنه.
و اینکه با استفاده از def میتونیم یه تابع رو بایند کنیم به یه symbol که بعدا بتونیم صداش بزنیم.


Anonymous functions

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

;; عدد به توان ۲
(fn [number] (* number number))

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

((fn [number] (* number number)) 5)

دقت کنید که یه پرانتز اضافه شده. یعنی کل مثال قبل رو داخل یه پرانتز دیگه گذاشتیم. یک پرانتز که فاکتور اولش، تابع ما هست. مثل زمانی که فاکتور اول یه تابع رو + یا * قرار میدادیم!
الآن عدد ۵ فرستاده میشه به تابع ما، و خروجی ۵×۵ نمایش داده میشه.

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


سینتکس کوتاه (یه ماکرو که پیشفرض داخل کلوژر وجود داره)

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

#(* % %)

توضیح: علامت درصد، یعنی جایی که ورودی تابع قرار داره. مثلا اگه عدد ۵ رو بفرستیم به تابع، میشه چیزی شبیه این:

(* 5 5)

نکته فقط اینه که «پارامتر‌هایی که به تابع فرستاده میشه، جایگزین % میشه».
علتش هم اینه که دیگه لازم نباشه اسم برای متغیر تعریف کنیم. هرجا گفتیم % یعنی متغیر میفته اونجا.


ارسال چند پارامتر به تابع بی‌نام

اگه بخوایم چند متغیر توی این توابع پردازش بشن، باید به صورت %1 و %2 و… اونها رو مشخص کنیم.

(def some-fn #(* %1 %2))
(some-fn 5 12)

اینجا ۵×۱۲ میشه.


مثال

توی این مثالها، خطهایی که با user=> شروع میشه، کدی هستن که من نوشتم و بقیه‌ی خطها، پاسخی هستن که از REPL دریافت کردم. یه سری چیزهای جدید توی این مثالها وجود داره که توضیح نمیدم. خودتون توی REPL با این مثالها و چیزهای جدیدی که میبینید بازی کنید تا یاد بگیرید. یادگیری این شکلی خیلی مفیدتر از خوندن و حفظ کردنه. ( با کلیک کردن روی این لینک می تونید مثال های زیر رو در REPL جدا باز کنید. )

(map #(println "Hello" %) ["sameer" "amirrezaask" "pouya"])
;; => Hello sameer
;; => Hello amirrezaask
;; => Hello pouya
;; => (nil nil nil)

(def print-hello #(println "Hello" %&))
(print-hello "sameer" "amirrezaask" "pouya")
;; => Hello (sameer amirrezaask pouya)
;; => nil

(def fn1 #(/ (* %1 %2) 2))
(fn1 5 7)
;; => 35/2

(#(/ (* %1 %2) 2.0) 5 7)
;; => 17.5

(def filter-even #(filter even? %))
(filter-even [1 2 3 4 5 6])
;; => (2 4 6)

(map #(* % %) (range 1 20))
;; => (1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361)

(map #(* %1 %2) (range 1 20) (range 21 40))

مقایسه

اون مثالی که زدم برای ضرب کردن دوتا عدد توی همدیگه، اگه میخواستم به صورت تابع معمولی بنویسم میشد این:

(defn vector-multiply [num1 num2] (* num1 num2))
(vector-multiply 5 6)

پس رسما توی وقت صرفه‌جویی کردم. هم وقت نوشتن و هم وقت دیباگ کردن. دکمه‌های کمتری هم فشار دادم!
همونطور که میگن «قبل از کد زدن، دوبار فکر کن. وگرنه قبل از فکر کردن مجبور میشی دوبار کد بزنی.»


پاورقی

حتی میشه یکی از ورودیها رو فاکتور گرفت و مثلا فقط دومی رو پردازش کرد:

(#(println %2) 5 12)

اینجا اولین ورودی رو نادیده میگیره و دومی رو پرینت میکنه.
شاید به این ویژگی به نظر مفید نیاد ولی یه وقتایی توی پردازش دیتاها ممکنه یه if بذاریم که اگر فلان شرط اتفاق افتاد دیگه فاکتور nام رو نادیده بگیر. تا حالا خودم از این استفاده نکردم ولی توی یه سایت این مثال رو.


اونقدرها هم که اعتماد به نفس دارم حرفه‌ای نیستم. خودم تازه دارم این زبان رو یاد میگیرم و مثالهایی که اینجا نوشتم هم با یه مقدار تغییرات از جاهای دیگه آوردم. امیدوارم زیاد پرت نگفته باشم.
با سرچ “clojure anonymous function” میتونید مطالب زیادی توی اینترنت پیدا کنید.


تجربیات شخصی

شاید عجیب باشه. تابعی که اسم نداره و نمیشه اونو صدا زد. حداقل برای من عجیب بود چون قبلا باهاش کار نکرده بودم و یه جاهایی استفاده کرده بودم ولی نمیدونستم چیه. فقط از روی مثالها کپی کرده بودم :sweat_smile:
این توابع توی زبانهای دیگه هم استفاده میشن. نمونش رو توی پایتون دیدم. توی جاوا اسکریپت خیلی استفاده کردم (برای callback) و شنیدم توی جاوا هم برای کارهای gui زیاد استفاده میشه. (نزدیکترین تماسم با جاوا، ران کردن کلوژر بوده، هست و خواهد بود)

شاید بهتر باشه برای یادآوری اینو بگم که تابع یعنی یه دستگاه، یه بلوک از کد که یه کار خاص رو انجام میده.
علت استفادش معمولا اینه که یه کار رو چند بار انجام ندیم (Don’t Repeat Yourself) و هروقت میخوایم، بتونیم صدا بزنیمش.
ولی علت استفاده از «توابع بی‌نام» یه کم فرق میکنه! علت اصلی اینه که حجم کد کمتر بشه. به زبان دیگه، تابع بی‌نام برای زمانی استفاده میشه که میخوایم حجم کد کمتر بشه و خوانایی بالا بره ولی اون قسمت از کد رو لازم نیست چند بار صدا بزنیم.


ممنون از @lxsameer که وقت گذاشت و بهم توی اصلاح این متن کمک کرد.


رهنمای شروع برنامه نویسی با کلوژر ( Clojure )
#2

به زودی همین مثال ها رو به صورت interactive به همین پست اضافه می کنم.


#3

لطفا سمیر جان و دوست عزیزی که پست رو درست کردن فکر کنم اگر برچسب توتریال بزنید بعدا بهتر بشه همشون رو پیدا کرد
تشکر از دو بزرگوار


#4

انجام شد.
ممنون بابت تذکر.


#5

پست رو آپدیت کردم. یه مقدار استایل رو مارک داون رو درست کردم. اما REPL محاوره ای خیلی سنگین بود واسه همین لینکش رو اضافه کردم فقط