আমার কাছে স্ট্রিমস এপিআইয়ের প্রাথমিক নকশা থেকে কিছু পুনরুদ্ধার রয়েছে যা ডিজাইনের যৌক্তিকতার উপর কিছুটা আলোকপাত করতে পারে।
২০১২ সালে, আমরা ভাষায় ল্যাম্বডাস যুক্ত করছিলাম, এবং আমরা ল্যাম্বডাস ব্যবহার করে প্রোগ্রাম করা একটি সংকলন-ভিত্তিক বা "বাল্ক ডেটা" অপারেশনগুলির সেট চাইছিলাম, যা সমান্তরালতা সহজতর করবে। অলসভাবে শৃঙ্খলাবদ্ধ অপারেশনগুলির ধারণাটি এই পয়েন্টটি দ্বারা ভালভাবে প্রতিষ্ঠিত হয়েছিল। আমরা মধ্যবর্তী ক্রিয়াকলাপগুলি ফলাফল সঞ্চয় করতে চাইনি।
আমাদের যে মুখ্য বিষয়গুলি সিদ্ধান্ত নেওয়ার দরকার হয়েছিল তা হ'ল এপিআই-তে চেইনের অবজেক্টগুলি কেমন দেখায় এবং কীভাবে তারা ডেটা উত্সগুলিতে ঝাঁকিয়ে পড়ে। উত্সগুলি প্রায়শই সংগ্রহ ছিল, তবে আমরা কোনও ফাইল বা নেটওয়ার্ক থেকে আসা ডেটা বা ফ্লাই-অন-ফ্লাইতে উত্পন্ন ডেটা, যেমন একটি এলোমেলো সংখ্যা জেনারেটর থেকেও সমর্থন করতে চেয়েছিলাম support
নকশায় বিদ্যমান কাজের অনেক প্রভাব ছিল। আরও প্রভাবশালীগুলির মধ্যে ছিল গুগলের পেয়ারা গ্রন্থাগার এবং স্কালা সংগ্রহ গ্রন্থাগার। (যদি কেউ পেয়ারা থেকে প্রভাব সম্পর্কে অবাক হন, তবে খেয়াল করুন যে পেয়ারা সীসা বিকাশকারী কেভিন বোউরিলিয়ন জেএসআর -৩৩5 ল্যাম্বদা বিশেষজ্ঞ গ্রুপে ছিলেন।) স্কালা সংগ্রহগুলিতে আমরা মার্টিন ওডারস্কির এই আলোচনাটিকে বিশেষ আগ্রহী বলে মনে করেছি: ভবিষ্যত- প্রুফিং স্কেল সংগ্রহ: মিউটেবল থেকে পার্সেন্টাল থেকে সমান্তরালে । (স্ট্যানফোর্ড EE380, 2011 জুন 1)
আমাদের প্রোটোটাইপ ডিজাইনটি প্রায় ভিত্তিক ছিল Iterable
। পরিচিত ক্রিয়াকলাপ filter
, map
এবং আরও অনেক কিছু ছিল এক্সটেনশন (ডিফল্ট) পদ্ধতিগুলি Iterable
। একজনকে ডেকে চেইনে অপারেশন যুক্ত করে অন্যটিকে ফিরিয়ে দিল Iterable
। একটি টার্মিনাল অপারেশন যেমন উত্সটিতে চেইনটি count
কল iterator()
করে, এবং প্রতিটি পর্যায়ের ইটারের মধ্যে অপারেশনগুলি কার্যকর করা হয়েছিল implemented
যেহেতু এগুলি ইটারেবলস, আপনি এই iterator()
পদ্ধতিটিকে একাধিকবার কল করতে পারেন । তাহলে কি হবে?
উত্সটি যদি সংগ্রহ হয় তবে এটি বেশিরভাগ ক্ষেত্রে সূক্ষ্মভাবে কাজ করে। সংগ্রহগুলি পর্যালোচনাযোগ্য এবং প্রতিটি কল iterator()
একটি স্বতন্ত্র আইট্রেটার উদাহরণ তৈরি করে যা অন্য কোনও সক্রিয় দৃষ্টান্তের চেয়ে স্বতন্ত্র এবং প্রতিটি সংগ্রহ স্বাধীনভাবে পৃথক করে। গ্রেট।
এখন যদি উত্সটি একটি শটযুক্ত হয় তবে কোনও ফাইলের লাইন পড়ার মতো? হতে পারে প্রথম আইট্রেটারের সমস্ত মান পাওয়া উচিত তবে দ্বিতীয় এবং পরবর্তী মানগুলি খালি থাকা উচিত। সম্ভবত মানগুলি ইটারেটরগুলির মধ্যে আন্তঃলিখন করা উচিত। অথবা হতে পারে প্রতিটি আইট্রেটারের একই মানগুলি পাওয়া উচিত। তারপরে, আপনার যদি দুটি পুনরুক্তি থাকে এবং একজন অন্যটির থেকে আরও দূরে হন? যে কেউ পড়া না হওয়া পর্যন্ত দ্বিতীয় আইট্রেটারে মানগুলি বাফার করতে হবে। সবচেয়ে খারাপ, আপনি যদি একজন আইট্রেটার পেয়ে থাকেন এবং সমস্ত মান পড়েন, এবং কেবল তখনই দ্বিতীয় আইট্রেটার পান। এখন থেকে মূল্যবোধগুলি কোথা থেকে আসে? কেউ যদি দ্বিতীয় আইট্রেটার চান কেবল তখনই কি তাদের সকলের জন্য বাফার করা দরকার ?
স্পষ্টতই, এক শট উত্সের মাধ্যমে একাধিক আইট্রেটারকে অনুমতি দেওয়া অনেক প্রশ্ন উত্থাপন করে। আমাদের কাছে তাদের পক্ষে ভাল উত্তর ছিল না। আপনি iterator()
দু'বার ফোন করলে কী হয় তার জন্য আমরা ধারাবাহিক, অনুমানযোগ্য আচরণ চাইছিলাম । এটি আমাদের পাইপলাইনগুলিকে এক-শট বানিয়ে একাধিক ট্র্যাভারসালকে অস্বীকার করার দিকে ঠেলে দিয়েছে।
আমরা অন্যদেরও এই বিষয়গুলিতে ঝাঁপিয়ে পড়ে পর্যবেক্ষণ করেছি। জেডিকে, বেশিরভাগ আইটেমেবলগুলি হ'ল সংগ্রহ বা সংগ্রহের মতো বস্তু, যা একাধিক ট্র্যাভারসালকে অনুমতি দেয়। এটি কোথাও সুনির্দিষ্ট করা হয়নি, তবে এমন একটি অলিখিত প্রত্যাশা বলে মনে হয়েছিল যা Iterables একাধিক ট্র্যাভারসালকে অনুমতি দেয়। একটি উল্লেখযোগ্য ব্যতিক্রম হ'ল এনআইও ডিরেক্টরি স্ট্রিম ইন্টারফেস। এর স্পেসিফিকেশনটিতে এই আকর্ষণীয় সতর্কতা অন্তর্ভুক্ত রয়েছে:
ডাইরেক্টরি স্ট্রিমটি স্বল্প পরিসরে প্রসারিত হওয়ার পরে এটি কোনও সাধারণ-উদ্দেশ্যমূলক আইটেবল নয় কারণ এটি কেবলমাত্র একক আইট্রেটরকে সমর্থন করে; দ্বিতীয় বা পরবর্তী পুনরুক্তি পেতে পুনরাবৃত্তি পদ্ধতিটি অনুরোধ করে ইলিজেলস্টেটএক্সেপশন ছুড়ে দেয়।
[মূল সাহসী]
এটিকে যথেষ্ট অস্বাভাবিক এবং অপ্রীতিকর বলে মনে হয়েছিল যে আমরা পুরো নতুন গুটি তৈরি করতে চাইনি যা কেবলমাত্র একবারের জন্য হতে পারে। এটি আমাদের আইটেবল ব্যবহার থেকে দূরে ঠেলে দিয়েছে।
এই সময়ে, ব্রুস এক্কেলের একটি নিবন্ধ প্রকাশিত হয়েছিল যাতে স্কালাকে নিয়ে তিনি যে সমস্যার মুখোমুখি হতেন তা বর্ণনা করেছিল। তিনি এই কোডটি লিখেছিলেন:
// Scala
val lines = fromString(data).getLines
val registrants = lines.map(Registrant)
registrants.foreach(println)
registrants.foreach(println)
এটা বেশ সোজা। এটি টেক্সটের লাইনগুলিকে Registrant
বস্তুগুলিতে পার্স করে এবং দু'বার মুদ্রণ করে। এগুলি ব্যতীত এটি কেবল একবারই তাদের প্রিন্ট করে। দেখা যাচ্ছে যে তিনি ভাবেন যে registrants
এটি একটি সংগ্রহ, যখন বাস্তবে এটি পুনরুক্তিকারী। foreach
খালি পুনরাবৃত্তির মুখোমুখি হওয়া দ্বিতীয় কল , যা থেকে সমস্ত মান শেষ হয়ে গেছে, তাই এটি কিছুই মুদ্রণ করে না।
এই জাতীয় অভিজ্ঞতা আমাদের বোঝায় যে একাধিক ট্র্যাভারসাল চেষ্টা করা থাকলে পরিষ্কারভাবে অনুমানযোগ্য ফলাফল পাওয়া খুব জরুরি ছিল। এটি তথ্য সংরক্ষণ করে এমন প্রকৃত সংগ্রহ থেকে অলস পাইপলাইনের মতো কাঠামোর মধ্যে পার্থক্যের গুরুত্বকেও তুলে ধরেছে। ফলস্বরূপ এটি অলস পাইপলাইন ক্রিয়াকলাপগুলিকে নতুন স্ট্রিম ইন্টারফেসে বিভক্ত করে এবং কেবল উত্সাহী, মিউটেশনীয় অপারেশনগুলি সরাসরি সংগ্রহে রাখে। ব্রায়ান গয়েট তার পক্ষে যুক্তি ব্যাখ্যা করেছেন ।
সংগ্রহ-ভিত্তিক পাইপলাইনগুলির জন্য একাধিক ট্র্যাভারসালকে অনুমতি দেওয়া কিন্তু সংগ্রহ-ভিত্তিক পাইপলাইনগুলির জন্য এটি অস্বীকার করার বিষয়ে কী? এটি অসঙ্গতিপূর্ণ, তবে এটি বুদ্ধিমান। আপনি যদি নেটওয়ার্ক থেকে মানগুলি পড়েন তবে অবশ্যই আপনি সেগুলি আর পেরে উঠতে পারবেন না। আপনি যদি এগুলি একাধিকবার অতিক্রম করতে চান তবে আপনাকে সেগুলি স্পষ্টভাবে কোনও সংগ্রহের মধ্যে টানতে হবে।
তবে আসুন সংগ্রহ-ভিত্তিক পাইপলাইনগুলি থেকে একাধিক ট্র্যাভারসালকে মঞ্জুর করে ঘুরে দেখুন। বলুন আপনি এটি করেছেন:
Iterable<?> it = source.filter(...).map(...).filter(...).map(...);
it.into(dest1);
it.into(dest2);
( into
অপারেশনটি এখন বানানযুক্ত collect(toList())
))
যদি উত্সটি কোনও সংগ্রহ হয়, তবে প্রথম into()
কলটি উত্সটিতে ফিরে আইট্রেটারের একটি শৃঙ্খল তৈরি করবে, পাইপলাইন ক্রিয়াকলাপ চালাবে এবং ফলাফলগুলি গন্তব্যে প্রেরণ করবে। দ্বিতীয় কলটি into()
আইট্রেটারদের আরেকটি চেইন তৈরি করবে এবং আবার পাইপলাইন ক্রিয়াকলাপ চালাবে । এটি স্পষ্টতই ভুল নয় তবে এতে প্রতিটি উপাদানটির জন্য দ্বিতীয়বারের মতো সমস্ত ফিল্টার এবং মানচিত্র পরিচালনা করার প্রভাব রয়েছে। আমি মনে করি অনেক প্রোগ্রামাররা এই আচরণ দেখে অবাক হবেন।
আমি উপরে উল্লিখিত হিসাবে, আমরা পেয়ারা বিকাশকারীদের সাথে কথা বলছিলাম। তাদের কাছে শীতল জিনিসগুলির মধ্যে একটি হ'ল একটি আইডিয়া কবরস্থান যেখানে তারা এমন বৈশিষ্ট্যগুলি বর্ণনা করে যা তারা কারণগুলি সহ কার্যকর না করার সিদ্ধান্ত নিয়েছিল । অলস সংগ্রহগুলির ধারণাটি খুব দুর্দান্ত লাগছে, তবে এ সম্পর্কে তাদের কী বলতে হবে তা এখানে। List.filter()
এমন একটি অপারেশন বিবেচনা করুন যা প্রত্যাবর্তন করে List
:
এখানে সবচেয়ে বড় উদ্বেগ হ'ল অনেকগুলি ক্রিয়াকলাপ ব্যয়বহুল, লিনিয়ার-সময় প্রস্তাবগুলি হয়ে ওঠে। আপনি যদি কোনও তালিকা ফিল্টার করতে চান এবং একটি তালিকা ফিরে পেতে চান, এবং কেবল কোনও সংগ্রহ বা অপরিবর্তনীয় নয়, আপনি এটি ব্যবহার করতে পারেন ImmutableList.copyOf(Iterables.filter(list, predicate))
, এটি কী করছে এবং কত ব্যয়বহুল তা "সামনের দিকে তুলে ধরে"।
একটি নির্দিষ্ট উদাহরণ নিতে, একটি তালিকার দাম get(0)
বা size()
কী? সাধারণত ব্যবহৃত ক্লাসগুলির জন্য ArrayList
, তারা ও (1)। তবে আপনি যদি অলস-ফিল্টারযুক্ত তালিকায় এগুলির একটির কল করেন তবে এটি ফিল্টারটি ব্যাকিং তালিকার উপর দিয়ে চালাতে হবে এবং হঠাৎ এই সমস্ত অপারেশনগুলি হ'ল (এন)। সবচেয়ে খারাপ, এটি প্রতিটি অপারেশনে ব্যাকিং লিস্টকে অতিক্রম করতে হয়।
এটি আমাদের কাছে খুব অলস বলে মনে হয়েছিল । কিছু অপারেশন সেট আপ করা এবং আপনার "Go" না হওয়া পর্যন্ত প্রকৃত বাস্তবায়ন স্থগিত করা এক জিনিস। জিনিসগুলি এমনভাবে সেট আপ করা অন্যটি যা সম্ভাব্য পরিমাণে পুনঃনির্মাণের গোপন করে।
অ-রৈখিক বা "পুনঃব্যবহার" স্ট্রিমগুলি অস্বীকার করার প্রস্তাব দেওয়ার ক্ষেত্রে, পল স্যান্ডোজ তাদের "অপ্রত্যাশিত বা বিভ্রান্তিমূলক ফলাফল" হিসাবে জন্ম দেওয়ার অনুমতি দেওয়ার সম্ভাব্য পরিণতিগুলি বর্ণনা করেছিলেন । তিনি আরও উল্লেখ করেছিলেন যে সমান্তরাল সম্পাদন জিনিসকে আরও জটিল করে তুলবে। পরিশেষে, আমি যুক্ত করব যে পার্শ্ব প্রতিক্রিয়াযুক্ত একটি পাইপলাইন অপারেশনটি অপ্রত্যাশিতভাবে একাধিকবার কার্যকর করা হলে বা প্রোগ্রামারের প্রত্যাশার চেয়ে কমপক্ষে একটি ভিন্ন সংখ্যক বার কার্যকর করা হলে সমস্যাগুলি এবং অস্পষ্ট বাগগুলির দিকে পরিচালিত করবে। (তবে জাভা প্রোগ্রামাররা ল্যাম্বডা এক্সপ্রেশনটি পার্শ্ব প্রতিক্রিয়া সহ লেখেন না, তারা কি? ??
সুতরাং এটি জাভা 8 স্ট্রিমস এপিআই ডিজাইনের জন্য মৌলিক যুক্তি যা ওয়ান-শট ট্র্যাভারসালকে অনুমতি দেয় এবং এর জন্য কঠোরভাবে রৈখিক (কোনও শাখা ছাড়াই) পাইপলাইন প্রয়োজন। এটি একাধিক বিভিন্ন স্ট্রিম উত্স জুড়ে সামঞ্জস্যপূর্ণ আচরণ সরবরাহ করে, এটি অলসভাবে আগ্রহী অপারেশনগুলি থেকে পৃথক করে এবং এটি একটি সরল বাস্তবায়ন মডেল সরবরাহ করে।
সম্পর্কিত IEnumerable
, আমি সি # এবং .NET এর বিশেষজ্ঞের থেকে অনেক দূরে, তাই আমি যদি কোনও ভুল উপসংহার আঁকে তবে আমি সংশোধন করা (মৃদুভাবে) প্রশংসা করব। এটি প্রদর্শিত হচ্ছে, IEnumerable
যা বিভিন্ন উত্সের সাথে একাধিক ট্র্যাভারসালকে আলাদা আচরণের অনুমতি দেয়; এবং এটি নেস্টেড IEnumerable
ক্রিয়াকলাপগুলির একটি শাখা কাঠামোকে অনুমতি দেয় , যার ফলে কিছু উল্লেখযোগ্য পুনর্বিবেচনা হতে পারে। যদিও আমি প্রশংসা করি যে বিভিন্ন সিস্টেমগুলি বিভিন্ন ট্রেডঅফ তৈরি করে, এটি দুটি বৈশিষ্ট্য যা আমরা জাভা 8 স্ট্রিমস এপিআইয়ের নকশায় এড়াতে চেয়েছিলাম।
ওপি কর্তৃক প্রদত্ত কুইকোর্টের উদাহরণটি আকর্ষণীয়, বিস্ময়কর এবং আমি কিছুটা ভয়াবহ বলে দুঃখিত। কলিংটি QuickSort
একটি গ্রহণ করে IEnumerable
এবং একটি ফেরত দেয় IEnumerable
, সুতরাং ফাইনালটি IEnumerable
ট্র্যাশিং না হওয়া পর্যন্ত কোনও বাছাই করা আসলেই করা হয় না । কলটি যা করছে বলে মনে হচ্ছে তা হল একটি গাছের কাঠামো তৈরি করা IEnumerables
যা বিভাজন প্রতিফলিত করে যা কুইকোর্টটি বাস্তবে না করেই করবে। (এটি সর্বোপরি অলস গণনা)) উত্সটিতে যদি এন উপাদান থাকে তবে গাছটি তার প্রশস্ত প্রস্থে N উপাদানগুলি বিশিষ্ট হবে এবং এটি এলজি (এন) স্তর গভীর হবে।
এটি আমার কাছে মনে হয় - এবং আবারও আমি কোনও সি # বা। নেট বিশেষজ্ঞ নই - কারণ এটি কিছু নিষ্প্রভ ints.First()
বর্ণন কলগুলি যেমন পিভট নির্বাচনের মাধ্যমে তাদের চেহারাগুলির চেয়ে ব্যয়বহুল হয়ে উঠবে। প্রথম স্তরে অবশ্যই এটি ও (1)। তবে ডান হাতের প্রান্তে গাছের গভীরে একটি পার্টিশন বিবেচনা করুন। এই পার্টিশনের প্রথম উপাদানটি গণনা করতে, পুরো উত্সকে অতিক্রম করতে হবে, একটি হে (এন) অপারেশন। তবে উপরের পার্টিশনগুলি অলস হওয়ার কারণে সেগুলি অবশ্যই পুনরায় সংশোধন করতে হবে, যার জন্য ও (এলজি এন) তুলনা প্রয়োজন। পিভটটি নির্বাচন করা একটি ও (এন এলজি এন) অপারেশন হবে যা পুরো সাজানোর মতো ব্যয়বহুল।
আমরা যতক্ষণ না ফিরে আসি ততক্ষণ আমরা আসলে বাছাই করি না IEnumerable
। স্ট্যান্ডার্ড কুইকোর্টোর্ট অ্যালগরিদমে, পার্টিশনের প্রতিটি স্তর পার্টিশনের সংখ্যা দ্বিগুণ করে। প্রতিটি পার্টিশন আকারের অর্ধেক, তাই প্রতিটি স্তর O (N) জটিলতায় থাকে। পার্টিশনের গাছটি ও (এলজি এন) উচ্চ, সুতরাং মোট কাজটি ও (এন এলজি এন) হয়।
অলস আইনিউমারেবলস গাছের সাথে গাছের নীচে এন পার্টিশন রয়েছে। প্রতিটি পার্টিশন গণনা করার জন্য এন উপাদানগুলির একটি ট্র্যাভারসাল প্রয়োজন, যার প্রত্যেকটিতে গাছের তুলনায় lg (N) প্রয়োজন। গাছের নীচে সমস্ত পার্টিশন গণনা করতে, তারপরে ও (এন ^ 2 এলজি এন) তুলনা প্রয়োজন।
(এটা কি ঠিক? আমি এই কথাটি বিশ্বাস করতে পারি না। কেউ দয়া করে আমার জন্য এটি পরীক্ষা করে দেখুন।)
যাই হোক না কেন, এটি সত্যিই দুর্দান্ত যে IEnumerable
গণনার জটিল কাঠামো তৈরি করতে এইভাবে ব্যবহার করা যেতে পারে। তবে এটি যদি কমপিটেশনাল জটিলতা বৃদ্ধি করে যতটা আমার মনে হয় যতটুকু করে, এটি মনে হয় যে এইভাবে প্রোগ্রামিং এমন একটি বিষয় যা কেউ অত্যন্ত সতর্ক না হলে এড়ানো উচিত।