रूबी में ब्लॉक और पैदावार


275

मैं ब्लॉक को समझने की कोशिश कर रहा हूं yieldऔर वे रूबी में कैसे काम करते हैं।

कैसे yieldकिया जाता है? कई रेल एप्लिकेशन मैंने yieldअजीब तरीके से उपयोग किए हैं।

क्या कोई मुझे समझा सकता है या मुझे दिखा सकता है कि उन्हें समझने के लिए मुझे कहाँ जाना है?


2
कंप्यूटर विज्ञान के संबंध में रूबी की उपज सुविधा के जवाब में आपकी रुचि हो सकती है । हालाँकि यह आपसे कुछ अलग सवाल है, फिर भी यह इस मामले पर कुछ प्रकाश डाल सकता है।
केन ब्लूम

जवाबों:


393

हां, यह पहली बार में थोड़ा हैरान करने वाला है।

रूबी में, कोड के मनमाने खंडों को करने के लिए तरीकों को एक कोड ब्लॉक प्राप्त हो सकता है।

जब कोई विधि किसी ब्लॉक की अपेक्षा करती है, तो यह yieldफ़ंक्शन को कॉल करके इसे आमंत्रित करता है।

उदाहरण के लिए, किसी सूची पर पुनरावृति करना या कोई कस्टम एल्गोरिथ्म प्रदान करना बहुत आसान है।

निम्नलिखित उदाहरण लें:

मैं Personएक नाम के साथ आरम्भ की गई कक्षा को परिभाषित करने जा रहा हूँ , और एक do_with_nameविधि प्रदान करता हूँ , जब आह्वान किया जाता है, बस nameप्राप्त विशेषता को, ब्लॉक को प्राप्त होगा।

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

इससे हम उस पद्धति को कॉल कर सकते हैं और एक मनमाना कोड ब्लॉक कर सकते हैं।

उदाहरण के लिए, नाम छापने के लिए हम क्या करेंगे:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

प्रिंट होगा:

Hey, his name is Oscar

ध्यान दें, ब्लॉक एक पैरामीटर के रूप में प्राप्त होता है, जिसे एक चर कहा जाता है name(एनबी आप इस चर को अपनी पसंद के अनुसार कुछ भी कह सकते हैं, लेकिन यह इसे कॉल करने के लिए समझ में आता है name)। जब कोड इनवॉइस करता है yieldतो इस मान को भरता है @name

yield( @name )

हम एक अलग कार्रवाई करने के लिए एक और ब्लॉक प्रदान कर सकते हैं। उदाहरण के लिए, नाम को उल्टा करें:

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

हमने बिल्कुल उसी विधि का उपयोग किया ( do_with_name) - यह सिर्फ एक अलग ब्लॉक है।

यह उदाहरण तुच्छ है। अधिक दिलचस्प उपयोग एक सरणी में सभी तत्वों को फ़िल्टर करने के लिए हैं:

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

या, हम एक कस्टम प्रकार एल्गोरिथ्म भी प्रदान कर सकते हैं, उदाहरण के लिए स्ट्रिंग आकार के आधार पर:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

मुझे उम्मीद है कि यह आपको इसे बेहतर ढंग से समझने में मदद करेगा।

BTW, यदि ब्लॉक वैकल्पिक है तो आपको इसे कॉल करना चाहिए:

yield(value) if block_given?

यदि वैकल्पिक नहीं है, तो इसे लागू करें।

संपादित करें

@hmak ने इन उदाहरणों के लिए एक repl.it बनाया: https://repl.it/@makstaks/blocksandyieldsrubbxample


यह कैसे प्रिंट racsOहोता है the_name = ""
परितोष पुष्तवर

2
क्षमा करें, नाम एक उदाहरण चर है जिसका आरंभिक "Oscar" उत्तर (उत्तर में बहुत स्पष्ट नहीं है)
OscarRyz

इस तरह कोड के बारे में क्या? person.do_with_name {|string| yield string, something_else }
f.ardelian

7
तो जावास्क्रिप्ट शब्दों में, यह किसी दिए गए तरीके से कॉलबैक पास करने और इसे कॉल करने का एक मानकीकृत तरीका है। स्पष्टीकरण के लिए धन्यवाद!
19 दिसंबर को yitznewton

अधिक सामान्य तरीके से - एक पैटर्न रणनीति पैटर्न के लिए एक रूबी "एन्हांस्ड" सिंटैक्स चीनी है। क्योंकि विशिष्ट उपयोग अन्य ऑपरेशन के संदर्भ में कुछ करने के लिए एक कोड प्रदान करना है। लेकिन रूबी संवर्द्धन ऐसी शांत चीजों के लिए एक रास्ता खोलते हैं जैसे कि डीएसएल को लिखने के लिए ब्लॉक का उपयोग करते हुए संदर्भ के आसपास
रोमन बुल्गाकोव

25

रूबी में, तरीके यह देखने के लिए जांच कर सकते हैं कि क्या उन्हें इस तरह से बुलाया गया था कि सामान्य तर्कों के अलावा एक ब्लॉक प्रदान किया गया था। आमतौर पर यह block_given?विधि का उपयोग करके किया जाता है, लेकिन आप &अंतिम तर्क नाम से पहले एक एम्परसेंड ( ) उपसर्ग करके एक स्पष्ट प्रोक के रूप में ब्लॉक को भी संदर्भित कर सकते हैं ।

यदि किसी विधि को किसी ब्लॉक से जोड़ दिया जाता है तो विधि कर सकते हैं yield है, तो जरूरत पड़ने कुछ तर्क के साथ ब्लॉक (ब्लॉक को कॉल नियंत्रित । इस उदाहरण विधि पर विचार करें जो प्रदर्शित करता है:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

या, विशेष ब्लॉक तर्क सिंटैक्स का उपयोग कर:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

एक ब्लॉक को ट्रिगर करने के विभिन्न तरीकों को जानने के लिए अच्छा है।
जं।

22

यह बहुत संभव है कि कोई व्यक्ति यहां वास्तव में विस्तृत उत्तर देगा, लेकिन मैंने हमेशा रॉबर्ट सोसिन्स्की की इस पोस्ट को ब्लॉक, प्रॉक्स और लैम्ब्डा के बीच सूक्ष्मताओं का एक बड़ा विवरण पाया है ।

मुझे यह जोड़ना चाहिए कि मुझे विश्वास है कि मैं जिस पोस्ट से जुड़ रहा हूं वह रूबी 1.8 के लिए विशिष्ट है। रूबी 1.9 में कुछ चीजें बदल गई हैं, जैसे ब्लॉक वैरिएबल ब्लॉक से स्थानीय हो रहे हैं। 1.8 में, आपको निम्न जैसा कुछ मिलेगा:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

जबकि 1.9 आपको देगा:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

मेरे पास इस मशीन पर 1.9 नहीं है, इसलिए उपरोक्त में कोई त्रुटि हो सकती है।


उस लेख में शानदार विवरण, मुझे यह पता लगाने में महीनों का समय लगा कि मेरे सभी =)
मैरिकिक्स

मैं सहमत हूँ। मुझे नहीं लगता कि मुझे पता था कि जब तक मैं इसे नहीं पढ़ता, तब तक आधा सामान समझा जाता था।
TheIV

अद्यतन लिंक अब 404 भी है। यहाँ पर Wayback Machine लिंक दिया गया है
klenwell

@klenwell सिर के लिए धन्यवाद, मैंने फिर से लिंक अपडेट किया है।
TheIV

13

मैं जोड़ना चाहता था कि आप उन चीजों को क्यों करेंगे जो पहले से ही महान उत्तरों के लिए हैं।

पता नहीं आप किस भाषा से आ रहे हैं, लेकिन यह एक स्थिर भाषा है, इस तरह की बात परिचित होगी। यह है कि आप जावा में एक फ़ाइल कैसे पढ़ते हैं

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

इस बात की पूरी धारा को अनदेखा करना, विचार यह है

  1. प्रारंभिक संसाधन जो साफ करने की आवश्यकता है
  2. संसाधन का उपयोग करें
  3. इसे साफ करना सुनिश्चित करें

यह आप माणिक में कैसे करते हैं

File.open("readfile.rb", "r") do |infile|
    while (line = infile.gets)
        puts "#{counter}: #{line}"
        counter = counter + 1
    end
end

बेतहाशा अलग। इस एक को तोड़कर

  1. फ़ाइल वर्ग को बताएं कि संसाधन को कैसे आरंभ किया जाए
  2. फाइल क्लास को बताएं कि इसके साथ क्या करना है
  3. हंसी जो लोग अभी भी टाइप कर रहे हैं पर ;-)

यहाँ, चरण एक और दो को संभालने के बजाय, आप मूल रूप से उस को दूसरी कक्षा में रखते हैं। जैसा कि आप देख सकते हैं, कि नाटकीय रूप से आपके पास लिखने के लिए कोड की मात्रा कम हो जाती है, जिससे चीजें पढ़ना आसान हो जाता है, और मेमोरी लीक, या फ़ाइल लॉक जैसी चीजों की संभावना कम हो जाती है।

अब, इसकी तरह आप जावा में ऐसा कुछ नहीं कर सकते हैं, वास्तव में, लोग इसे दशकों से कर रहे हैं। इसे स्ट्रेटेजी पैटर्न कहा जाता है । अंतर यह है कि ब्लॉक के बिना, फ़ाइल उदाहरण की तरह कुछ सरल के लिए, उन कक्षाओं और विधियों की मात्रा के कारण रणनीति ओवरकिल हो जाती है जिन्हें आपको लिखने की आवश्यकता है। ब्लॉक के साथ, यह ऐसा करने का एक सरल और सुरुचिपूर्ण तरीका है, कि यह आपके कोड को उस तरह से संरचना करने के लिए कोई मतलब नहीं है।

यह एकमात्र तरीका नहीं है, जैसे ब्लॉक का उपयोग किया जाता है, लेकिन अन्य (बिल्डर पैटर्न की तरह, जिसे आप rails में form_for api में देख सकते हैं) समान पर्याप्त हैं कि यह स्पष्ट होना चाहिए कि एक बार जब आप इस पर अपना सिर लपेटते हैं। जब आप ब्लॉक देखते हैं, तो आमतौर पर यह मान लेना सुरक्षित होता है कि विधि कॉल वही है जो आप करना चाहते हैं, और ब्लॉक यह वर्णन कर रहा है कि आप इसे कैसे करना चाहते हैं।


5
आइए इसे थोड़ा सरल करें: File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" endऔर जावा दोस्तों पर भी हँसें।
माइकल हैम्पटन

1
@ मिचेल हैम्पटन, जब आप एक फ़ाइल को गीगाबाइट के एक जोड़े को लंबे समय तक पढ़ने के बाद हँसते हैं।
akostadinov

@akostadinov नहीं ... जो मुझे रोना चाहता है!
माइकल हैम्पटन

3
@ मिचेल हैम्पटन या, अभी तक बेहतर: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(प्लस नो मेमोरी इश्यूज)
फंड मोनिका का मुकदमा

12

मुझे यह लेख बहुत उपयोगी लगा। विशेष रूप से, निम्नलिखित उदाहरण:

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

जो निम्न आउटपुट देना चाहिए:

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

इसलिए अनिवार्य रूप से हर बार जब yieldमाणिक को कॉल किया जाता है तो वह doब्लॉक या अंदर कोड चलाएगा {}। यदि एक पैरामीटर प्रदान किया जाता है, yieldतो यह doब्लॉक को एक पैरामीटर के रूप में प्रदान किया जाएगा ।

मेरे लिए, यह पहली बार था जब मुझे समझ में आया कि doब्लॉक क्या कर रहे थे। यह मूल रूप से फ़ंक्शन के लिए आंतरिक डेटा संरचनाओं तक पहुंच देने का एक तरीका है, जो कि पुनरावृत्ति के लिए या फ़ंक्शन के कॉन्फ़िगरेशन के लिए है।

तो जब रेल में आप निम्नलिखित लिखते हैं:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

यह respond_toफ़ंक्शन do(आंतरिक) formatपैरामीटर के साथ ब्लॉक को पैदावार देगा । फिर आप .htmlइस आंतरिक चर पर फ़ंक्शन को कॉल करते हैं जो बदले में renderकमांड को चलाने के लिए कोड ब्लॉक का उत्पादन करता है । ध्यान दें कि .htmlकेवल तभी उपज होगी जब यह अनुरोधित फ़ाइल प्रारूप है। (तकनीकीता: ये फ़ंक्शन वास्तव में उपयोग block.callनहीं करते हैं yieldजैसा कि आप स्रोत से देख सकते हैं लेकिन कार्यक्षमता अनिवार्य रूप से समान है, इस प्रश्न को चर्चा के लिए देखें ।) यह फ़ंक्शन को कुछ आरंभ करने के लिए एक तरीका प्रदान करता है फिर कॉलिंग कोड से इनपुट लें। यदि आवश्यक हो तो प्रसंस्करण पर ले जाएँ।

या एक और तरीका रखो, यह एक फ़ंक्शन के रूप में एक अनाम फ़ंक्शन को एक तर्क के रूप में ले रहा है और फिर इसे जावास्क्रिप्ट में कॉल कर रहा है।


8

रूबी में, एक ब्लॉक मूल रूप से कोड का एक हिस्सा है जिसे किसी भी विधि द्वारा पारित और निष्पादित किया जा सकता है। ब्लॉक हमेशा विधियों के साथ उपयोग किए जाते हैं, जो आम तौर पर उन्हें (तर्क के रूप में) डेटा खिलाते हैं।

ब्लॉक व्यापक रूप से रूबी जवाहरात (रेल सहित) और अच्छी तरह से लिखित रूबी कोड में उपयोग किए जाते हैं। वे वस्तुएं नहीं हैं, इसलिए उन्हें चर नहीं सौंपा जा सकता है।

बेसिक सिंटेक्स

एक खंड {} या डू..इन द्वारा संलग्न कोड का एक टुकड़ा है। अधिवेशन द्वारा, घुंघराले ब्रेस सिंटैक्स का उपयोग सिंगल-लाइन ब्लॉक के लिए किया जाना चाहिए और do..end सिंटैक्स का उपयोग मल्टी-लाइन ब्लॉक के लिए किया जाना चाहिए।

{ # This is a single line block }

do
  # This is a multi-line block
end 

किसी भी विधि को एक अंतर्निहित तर्क के रूप में एक ब्लॉक प्राप्त हो सकता है। एक ब्लॉक को एक विधि के भीतर उपज स्टेटमेंट द्वारा निष्पादित किया जाता है। मूल वाक्यविन्यास है:

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

जब पैदावार कथन पूरा हो जाता है, तो ध्यान विधि से ब्लॉक पर नियंत्रण प्राप्त होता है, ब्लॉक के भीतर कोड निष्पादित होता है और नियंत्रण विधि में वापस आ जाता है, जो उपज स्टेटमेंट के तुरंत बाद निष्पादन को फिर से शुरू करता है।

जब किसी विधि में उपज कथन होता है, तो यह कॉलिंग समय पर ब्लॉक प्राप्त करने की अपेक्षा करता है। यदि एक ब्लॉक प्रदान नहीं किया जाता है, तो एक अपवाद उपज के बयान तक पहुंचने के बाद फेंक दिया जाएगा। हम ब्लॉक को वैकल्पिक बना सकते हैं और उठाए जाने से एक अपवाद से बच सकते हैं:

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

एक विधि से कई ब्लॉक पास करना संभव नहीं है। प्रत्येक विधि केवल एक ब्लॉक प्राप्त कर सकती है।

अधिक देखें: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html


यह (केवल) उत्तर है जो वास्तव में मुझे समझता है कि ब्लॉक और उपज क्या है, और उनका उपयोग कैसे करें।
एरिक वांग

5

मैं कभी-कभी इस तरह "उपज" का उपयोग करता हूं:

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

ठीक है, लेकिन क्यों ? इसके कई कारण हैं, जैसे Loggerअगर उपयोगकर्ता को ज़रूरत नहीं है तो किसी को कुछ कार्य नहीं करना है। हालांकि आपको अपनी व्याख्या करनी चाहिए ...
उल्यानसे बीएन

4

पैदावार, इसे सीधे शब्दों में कहें, ब्लॉक बनाने और कॉल करने के लिए आपके द्वारा बनाई गई विधि की अनुमति दें। उपज कीवर्ड विशेष रूप से वह स्थान है जहां ब्लॉक में 'सामान' का प्रदर्शन किया जाएगा।


1

मैं यहां उपज के बारे में दो बिंदु बनाना चाहता हूं। पहला, जबकि यहां बहुत सारे उत्तर ब्लॉक को एक विधि से पारित करने के विभिन्न तरीकों के बारे में बात करते हैं जो उपज का उपयोग करते हैं, चलो नियंत्रण प्रवाह के बारे में भी बात करते हैं। यह विशेष रूप से प्रासंगिक है क्योंकि आप एक ब्लॉक में कई बार मल्टीप्ले कर सकते हैं। आइए एक उदाहरण देखें:

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block

जब प्रत्येक विधि को लागू किया जाता है, तो यह लाइन द्वारा लाइन निष्पादित करता है। अब जब हम 3. बार ब्लॉक में जाते हैं, तो यह ब्लॉक 3 बार लागू किया जाएगा। हर बार यह पैदावार को आमंत्रित करता है। उस उपज को उस विधि से जुड़े ब्लॉक से जोड़ा जाता है जिसे प्रत्येक विधि कहा जाता है। यह ध्यान रखना महत्वपूर्ण है कि हर बार उपज का आह्वान किया जाता है, यह क्लाइंट कोड में प्रत्येक विधि के ब्लॉक पर वापस नियंत्रण देता है। एक बार जब ब्लॉक निष्पादित हो जाता है, तो यह 3.times ब्लॉक पर वापस आ जाता है। और ऐसा 3 बार होता है। ताकि क्लाइंट कोड में ब्लॉक को 3 अलग-अलग अवसरों पर लागू किया जाए क्योंकि उपज को स्पष्ट रूप से 3 अलग-अलग समय कहा जाता है।

मेरा दूसरा बिंदु enum_for और उपज के बारे में है। enum_for Enumerator वर्ग को तुरंत बताता है और यह Enumerator ऑब्जेक्ट भी उपज का जवाब देता है।

class Fruit
  def initialize
    @kinds = %w(orange apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "apple" 

इसलिए हर बार जब हम बाहरी पुनरावृत्त के साथ प्रकार आमंत्रित करते हैं, तो यह केवल एक बार उपज प्राप्त करेगा। अगली बार जब हम इसे बुलाएंगे, तो यह अगली उपज और इसी तरह का आह्वान करेगा।

Enum_for के संबंध में एक दिलचस्प tidbit है। प्रलेखन ऑनलाइन निम्नलिखित बताता है:

enum_for(method = :each, *args)  enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

यदि आप प्रतीक को enum_for के तर्क के रूप में निर्दिष्ट नहीं करते हैं, तो माणिक रिसीवर के प्रत्येक विधि में गणनाकर्ता को हुक करेगा। कुछ कक्षाओं में प्रत्येक विधि नहीं होती है, जैसे स्ट्रिंग वर्ग।

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

इस प्रकार, enum_for के साथ आमंत्रित कुछ वस्तुओं के मामले में, आपको स्पष्ट होना चाहिए कि आपकी गणना विधि क्या होगी।


0

विधि में मान लौटाने के लिए यील्ड का उपयोग बिना ब्लॉक के किया जा सकता है। निम्नलिखित कोड पर विचार करें:

Def Up(anarg)
  yield(anarg)
end

आप एक विधि "अप" बना सकते हैं जिसे एक तर्क दिया गया है। अब आप इस तर्क को उपज के लिए असाइन कर सकते हैं जो संबंधित ब्लॉक को कॉल और निष्पादित करेगा। आप पैरामीटर सूची के बाद ब्लॉक असाइन कर सकते हैं।

Up("Here is a string"){|x| x.reverse!; puts(x)}

जब अप विधि एक तर्क के साथ उपज को बुलाती है, तो इसे अनुरोध को संसाधित करने के लिए ब्लॉक चर में पास किया जाता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.