useuse मैक्रो की मदद से हम किसी दूसरे मॉड्यूल की दी हुई सुविधाएँ अपने मॉड्यूल में जल्दी से जोड़ सकते हैं। जब हम किसी मॉड्यूल को use करते हैं, तो वह मॉड्यूल हमारे मॉड्यूल में कोड डाल सकता है। उदाहरण के लिए, वह फंक्शन परिभाषित कर सकता है, दूसरे मॉड्यूलों को import या alias कर सकता है, या मॉड्यूल एट्रिब्यूट सेट कर सकता है।
अगर आपने कभी यहाँ Exercism पर Elixir के कुछ अभ्यासों की टेस्ट फाइलें देखी हैं, तो आपने ज़रूर देखा होगा कि वे सब use ExUnit.Case से शुरू होती हैं। कोड की यही एक लाइन टेस्ट मॉड्यूल में test और assert मैक्रो उपलब्ध कराती है।
defmodule LasagnaTest do
use ExUnit.Case
test "expected minutes in oven" do
assert Lasagna.expected_minutes_in_oven() === 40
end
end
__using__/1 मैक्रोजब आप किसी मॉड्यूल को use करते हैं, तो ठीक-ठीक क्या होता है, यह उस मॉड्यूल का __using__/1 मैक्रो तय करता है। यह एक आर्गुमेंट लेता है, जो विकल्पों वाली एक कीवर्ड लिस्ट होती है, और एक कोटेड एक्सप्रेशन लौटाता है। use कॉल करने पर इस कोटेड एक्सप्रेशन का कोड हमारे मॉड्यूल में जोड़ दिया जाता है।
defmodule ExUnit.Case do
defmacro __using__(opts) do
# some real-life ExUnit code omitted here
quote do
import ExUnit.Assertions
import ExUnit.Case, only: [describe: 2, test: 1, test: 2, test: 3]
end
end
end
use कॉल करते समय विकल्प दूसरे आर्गुमेंट के रूप में भी दिए जा सकते हैं, जैसे use ExUnit.Case, async: true। जब ये स्पष्ट रूप से न दिए जाएँ, तो इन्हें खाली लिस्ट मान लिया जाता है।
बिहेवियर की मदद से हम किसी बिहेवियर मॉड्यूल में इंटरफ़ेस (फंक्शन और मैक्रो के समूह) परिभाषित कर सकते हैं, जिन्हें बाद में अलग-अलग कॉलबैक मॉड्यूल कार्यान्वित करते हैं। साझा इंटरफ़ेस की वजह से उन कॉलबैक मॉड्यूलों को एक-दूसरे की जगह इस्तेमाल किया जा सकता है।
ध्यान दीजिए कि "behaviours" की वर्तनी ब्रिटिश है।
बिहेवियर परिभाषित करने के लिए हमें एक नया मॉड्यूल बनाना होता है और उन सारे फंक्शनों की एक लिस्ट देनी होती है जो हमारे चाहे गए इंटरफ़ेस का हिस्सा हैं। हर फंक्शन को @callback मॉड्यूल एट्रिब्यूट की मदद से परिभाषित करना होता है। इसका सिंटैक्स फंक्शन टाइपस्पेक (@spec) जैसा ही होता है। हमें फंक्शन का नाम, आर्गुमेंट टाइप की एक लिस्ट, और सभी संभावित रिटर्न टाइप बताने होते हैं।
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
अपने मॉड्यूल में कोई मौजूदा बिहेवियर जोड़ने के लिए (यानी कॉलबैक मॉड्यूल बनाने के लिए) हम @behaviour मॉड्यूल एट्रिब्यूट इस्तेमाल करते हैं। इसकी वैल्यू उस बिहेवियर मॉड्यूल का नाम होनी चाहिए जिसे हम जोड़ रहे हैं।
इसके बाद हमें उन सारे फंक्शन (कॉलबैक) को परिभाषित करना होता है जो उस बिहेवियर मॉड्यूल के लिए ज़रूरी हैं। अगर हम किसी और का बिहेवियर कार्यान्वित कर रहे हैं, जैसे Elixir के बने-बनाए Access या GenServer बिहेवियर, तो उस बिहेवियर के सारे कॉलबैक की लिस्ट हमें hexdocs.pm की डॉक्युमेंटेशन में मिल जाएगी।
किसी कॉलबैक मॉड्यूल को सिर्फ़ उन्हीं फंक्शनों तक सीमित रहना ज़रूरी नहीं है जो उसके बिहेवियर का हिस्सा हैं। एक ही मॉड्यूल कई बिहेवियर भी कार्यान्वित कर सकता है।
यह बताने के लिए कि कौन-सा फंक्शन किस बिहेवियर से आता है, हमें हर फंक्शन से पहले @impl मॉड्यूल एट्रिब्यूट लगाना चाहिए। इसकी वैल्यू उस बिहेवियर मॉड्यूल का नाम होनी चाहिए जो यह कॉलबैक परिभाषित करता है।
defmodule BookCollection do
@behaviour Countable
defstruct [:list, :owner]
@impl Countable
def count(collection) do
Enum.count(collection.list)
end
def mark_as_read(collection, book) do
# other function unrelated to the Countable behaviour
end
end
बिहेवियर परिभाषित करते समय किसी कॉलबैक का डिफ़ॉल्ट कार्यान्वयन देना भी संभव है। यह कार्यान्वयन __using__/1 मैक्रो के कोटेड एक्सप्रेशन में परिभाषित करना चाहिए। ताकि बिहेवियर मॉड्यूल का इस्तेमाल करने वाले इस डिफ़ॉल्ट कार्यान्वयन को ओवरराइड कर सकें, फंक्शन के कार्यान्वयन के बाद defoverridable/1 मैक्रो कॉल कीजिए। यह एक कीवर्ड लिस्ट लेता है जिसमें की के रूप में फंक्शन के नाम और वैल्यू के रूप में फंक्शन की आर्टी होती हैं।
defmodule Countable do
@callback count(collection :: any) :: pos_integer
defmacro __using__(_) do
quote do
@behaviour Countable
def count(collection), do: Enum.count(collection)
defoverridable count: 1
end
end
end
ध्यान दीजिए कि डिफ़ॉल्ट कॉलबैक कार्यान्वयन बनाने के अलावा किसी और उद्देश्य के लिए __using__/1 के अंदर फंक्शन परिभाषित करना ठीक नहीं माना जाता, लेकिन आप हमेशा किसी दूसरे मॉड्यूल में फंक्शन परिभाषित करके उन्हें __using__/1 मैक्रो में import कर सकते हैं।
आपके एक मित्र ने, जो एक उभरते कलाकार हैं, एक प्रोजेक्ट आइडिया लेकर आपसे संपर्क किया। चलिए उनकी कलात्मक सोच को आपके तकनीकी कौशल के साथ मिलाते हैं। अब जनरेटिव आर्ट में हाथ आज़माने का समय है!
सीमाएँ रचनात्मकता को बढ़ाती हैं और प्रोजेक्ट की समय-सीमा छोटी करती हैं, इसलिए आप दोनों ने तय किया है कि अपनी कलाकृति को सिर्फ़ एक आकार तक सीमित रखेंगे, वृत्त (circle)। लेकिन वृत्त बहुत सारे होंगे। और वे घूम-फिर भी सकते हैं! आप इसका नाम रखेंगे... डांसिंग डॉट्स।
आपके मित्र निश्चित रूप से डॉट्स के लिए नई और जटिल हरकतें सोचना चाहेंगे, इसलिए आप कोडिंग की शुरुआत एक ऐसा आर्किटेक्चर बनाकर करेंगे जिससे बाद में नए एनिमेशन आसानी से बना सकें।
हर एनिमेशन मॉड्यूल को दो कॉलबैक लागू करने होते हैं: init/1 और handle_frame/3। इन्हें Animation मॉड्यूल में बनाइए।
init/1 कॉलबैक बनाइए। इसे opts टाइप का एक आर्गुमेंट लेना चाहिए और या तो {:ok, opts} टपल लौटाना चाहिए या {:error, error} टपल। इस कॉलबैक के कार्यान्वयन जाँचेंगे कि दिए गए विकल्प इस खास तरह के एनिमेशन के लिए मान्य हैं या नहीं।
handle_frame/3 कॉलबैक बनाइए। इसे तीन आर्गुमेंट लेने चाहिए: डॉट, फ्रेम संख्या और विकल्प। इसे हमेशा एक डॉट लौटाना चाहिए। इस कॉलबैक के कार्यान्वयन मौजूदा फ्रेम संख्या और एनिमेशन के विकल्पों के आधार पर डॉट के एट्रिब्यूट बदलेंगे।
init/1 कॉलबैक का डिफ़ॉल्ट कार्यान्वयन दीजिएuse DancingDots.Animation कॉल करके Animation बिहेवियर को दूसरे मॉड्यूल में शामिल करना आसान होना चाहिए।
ऐसा करने के लिए, Animation मॉड्यूल में __using__ मैक्रो लागू कीजिए, जो Animation मॉड्यूल को दूसरे मॉड्यूल का बिहेवियर बना दे। इसे init/1 कॉलबैक का डिफ़ॉल्ट कार्यान्वयन भी देना चाहिए। init/1 का डिफ़ॉल्ट कार्यान्वयन दिए गए विकल्पों को बिना बदले लौटाना चाहिए।
defmodule MyCustomAnimation do
use DancingDots.Animation
end
MyCustomAnimation.init(some_option: true)
# => {:ok, [some_option: true]}
Flicker एनिमेशन लागू कीजिएटिमटिमाने वाला एनिमेशन लागू करने के लिए Animation बिहेवियर का उपयोग कीजिए।
इसे डिफ़ॉल्ट init/1 कॉलबैक का उपयोग करना चाहिए क्योंकि यह कोई विकल्प नहीं लेता।
handle_frame/3 कॉलबैक लागू कीजिए, जो एक अकेले फ्रेम को संभालता है। अगर फ्रेम संख्या चार की गुणज है, तो फंक्शन को डॉट उसकी मूल ओपैसिटी की आधी ओपैसिटी के साथ लौटाना चाहिए। बाकी फ्रेम में इसे डॉट बिना बदले लौटाना चाहिए।
फ्रेम की गिनती 1 से होती है। handle_frame/3 को दिया जाने वाला डॉट हमेशा अपनी मूल स्थिति में होता है, पिछले फ्रेम की स्थिति में नहीं।
dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}
DancingDots.Flicker.handle_frame(dot, 1, [])
# => %DancingDots.Dot{opacity: 1, radius: 24, x: 100, y: 100}
DancingDots.Flicker.handle_frame(dot, 4, [])
# => %DancingDots.Dot{opacity: 0.5, radius: 24, x: 100, y: 100}
Zoom एनिमेशन लागू कीजिएज़ूम करने वाला एनिमेशन लागू करने के लिए Animation बिहेवियर का उपयोग कीजिए।
यह एनिमेशन एक विकल्प लेता है, वेलोसिटी। वेलोसिटी कोई भी संख्या हो सकती है। अगर यह ऋणात्मक है, तो डॉट ज़ूम इन होने की बजाय ज़ूम आउट हो जाता है।
init/1 कॉलबैक लागू कीजिए। इसे जाँचना चाहिए कि दिए गए विकल्प एक कीवर्ड लिस्ट हैं और उसमें :velocity की मौजूद है। वेलोसिटी की वैल्यू एक संख्या होनी चाहिए। अगर यह संख्या न हो, तो यह एरर लौटाइए: "The :velocity option is required, and its value must be a number. Got: #{inspect(velocity)}".
handle_frame/3 कॉलबैक लागू कीजिए। इसे डॉट लौटाना चाहिए, जिसकी त्रिज्या बढ़ी हुई हो। वृद्धि इतनी होगी: (मौजूदा फ्रेम संख्या में से एक घटाकर) गुणा वेलोसिटी।
फ्रेम की गिनती 1 से होती है। handle_frame/3 को दिया जाने वाला डॉट हमेशा अपनी मूल स्थिति में होता है, पिछले फ्रेम की स्थिति में नहीं।
DancingDots.Zoom.init(velocity: nil)
# => {:error, "The :velocity option is required, and its value must be a number. Got: nil"}
dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}
DancingDots.Zoom.handle_frame(dot, 1, velocity: 10)
# => %DancingDots.Dot{radius: 24, opacity: 1, x: 100, y: 100}
DancingDots.Zoom.handle_frame(dot, 2, velocity: 10)
# => %DancingDots.Dot{radius: 34, opacity: 1, x: 100, y: 100}
Exercism पर साइन अप कीजिए और Elixir को 58 कॉन्सेप्ट168 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।