يساعدك على كتابة كود C++ موجز وقابل للقراءة.
يجب أن تكون التعليمات البرمجية الرائعة في الغالب ذاتية التوثيق، ولكن أثناء استخدام C++ في الواقع، يمكنك أن تجد نفسك تتعامل مع أشياء منخفضة المستوى مثل التكرارات أو الحلقات المكتوبة بخط اليد التي تصرف الانتباه عن الجوهر الفعلي للتعليمات البرمجية الخاصة بك.
FunctionalPlus هي مكتبة صغيرة للرؤوس فقط تدعمك في تقليل تشويش التعليمات البرمجية والتعامل مع مستوى واحد فقط من التجريد في المرة الواحدة. من خلال زيادة الإيجاز وقابلية الصيانة للتعليمات البرمجية الخاصة بك، يمكن أن يؤدي ذلك إلى تحسين الإنتاجية (والمتعة!) على المدى الطويل. وهو يسعى إلى تحقيق هذه الأهداف من خلال توفير وظائف نقية وسهلة الاستخدام تحررك من تنفيذ تدفقات التحكم شائعة الاستخدام مرارًا وتكرارًا.
لنفترض أن لديك قائمة بالأرقام وتهتم بالأرقام الفردية فقط.
bool is_odd_int ( int x) { return x % 2 != 0 ; }
int main ()
{
typedef vector< int > Ints;
Ints values = { 24 , 11 , 65 , 44 , 80 , 18 , 73 , 90 , 69 , 18 };
// todo: get odd numbers from values ...
}
هناك احتمالات مختلفة لتحقيق هدفك. بعض منهم:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
من STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
من FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
إذا كنت تعتقد أن الإصدار 3 هو الإصدار الأكثر متعة في العمل معه، فقد يعجبك FunctionalPlus. وإذا كنت لا تزال تعتقد أن حلقة for المكتوبة بخط اليد أسهل في الفهم، ففكر أيضًا فيما سيحدث إذا كان نص الحلقة (أي دالة lambda المقابلة في استدعاء fplus::keep_if
) أطول بكثير. عند قراءة keep_if
ستظل تعرف على الفور أن odds
لا يمكن أن تحتوي إلا على عناصر جاءت من values
وتم تحديدها بواسطة بعض المسندات، التي ربما تكون معقدة. في حالة الحلقة، ليس لديك أي فكرة عما يحدث حتى تقرأ نص الحلقة بالكامل. من المحتمل أن تحتاج نسخة الحلقة إلى تعليق في الأعلى يوضح ما سيقوله استخدام keep_if
للوهلة الأولى.
فيما يلي بعض الأمثلة القصيرة التي توضح الأشياء الرائعة التي يمكنك القيام بها باستخدام الوظائف والحاويات باستخدام FunctionalPlus.
يمكنك اختبار محتوى الحاوية لمختلف الخصائص، على سبيل المثال
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::list things = { " same old " , " same old " };
if ( fplus::all_the_same (things))
std::cout << " All things being equal. " << std::endl;
}
team
هناك أيضًا بعض الوظائف الملائمة لاسترداد خصائص الحاويات. على سبيل المثال، يمكنك حساب تكرارات حرف ما في سلسلة.
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::string team = " Our team is great. I love everybody I work with. " ;
std::cout << " There actually are this many 'I's in team: " <<
fplus::count ( " I " , fplus::split_words ( false , team)) << std::endl;
}
الإخراج:
There actually are this many 'I's in team: 2
يعد العثور على العنصر الأعلى تقييمًا في الحاوية أمرًا بسيطًا جدًا مقارنة بالنسخة المكتوبة بخط اليد (1، 2).
# include < fplus/fplus.hpp >
# include < iostream >
struct cat
{
double cuteness () const
{
return softness_ * temperature_ * roundness_ * fur_amount_ - size_;
}
std::string name_;
double softness_;
double temperature_;
double size_;
double roundness_;
double fur_amount_;
};
void main ()
{
std::vector cats = {
{ " Tigger " , 5 , 5 , 5 , 5 , 5 },
{ " Simba " , 2 , 9 , 9 , 2 , 7 },
{ " Muffin " , 9 , 4 , 2 , 8 , 6 },
{ " Garfield " , 6 , 5 , 7 , 9 , 5 }};
auto cutest_cat = fplus::maximum_on ( std::mem_fn (&cat::cuteness), cats);
std::cout << cutest_cat. name_ <<
" is happy and sleepy. *purr* *purr* *purr* " << std::endl;
}
الإخراج:
Muffin is happy and sleepy. *purr* *purr* *purr*
لنفترض أن لديك الوظيفة التالية المعطاة.
std::list< int > collatz_seq ( int x);
وتريد إنشاء std::map
يحتوي على تمثيلات سلسلة لتسلسلات Collatz لجميع الأرقام الأقل من 30. يمكنك تنفيذ ذلك بشكل جيد وبطريقة وظيفية أيضًا.
# include < fplus/fplus.hpp >
# include < iostream >
// std::list collatz_seq(std::uint64_t x) { ... }
int main ()
{
typedef std::list< int > Ints;
// [1, 2, 3 ... 29]
auto xs = fplus::numbers( 1 , 30 );
// A function that does [1, 2, 3, 4, 5] -> "[1 => 2 => 3 => 4 => 5]"
auto show_ints = fplus::bind_1st_of_2 (fplus::show_cont_with, " => " );
// A composed function that calculates a Collatz sequence and shows it.
auto show_collats_seq = fplus::compose (collatz_seq, show_ints);
// Associate the numbers with the string representation of their sequences.
auto collatz_dict = fplus::create_map_with (show_collats_seq, xs);
// Print some of the sequences.
std::cout << collatz_dict[ 13 ] << std::endl;
std::cout << collatz_dict[ 17 ] << std::endl;
}
الإخراج:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
لا تعمل الوظائف المعروضة فقط مع حاويات STL الافتراضية مثل std::vector
و std::list
و std::deque
و std::string
وما إلى ذلك، ولكن أيضًا مع الحاويات المخصصة التي توفر واجهة مماثلة.
يستنتج FunctionalPlus الأنواع لك حيثما أمكن ذلك. لنأخذ سطرًا واحدًا من التعليمات البرمجية من مثال Collatz:
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
هي دالة تأخذ uint64_t
وتعيد list
. يأخذ show_ints
list
ويعيد string
. من خلال الاستفادة من function_traits
، التي كتبها kennyim، من الممكن استنتاج التعبير تلقائيًا fplus::compose(collatz_seq, show_ints)
باعتباره دالة تأخذ uint64_t
وتعيد string
، لذلك لا يتعين عليك تقديم تلميحات الكتابة يدويًا إلى المترجم.
إذا تم تمرير وظيفتين لا تتطابق "أنواع الاتصال" الخاصة بهما، فسيتم إنشاء رسالة خطأ لا لبس فيها تصف المشكلة. يستخدم FunctionalPlus تأكيدات وقت الترجمة لتجنب رسائل الخطأ الطويلة المربكة التي ينشئها المترجمون عند مواجهة أخطاء الكتابة في قوالب الوظائف.
سيؤدي تغيير الطريقة التي تبرمج بها من "كتابة الحلقات الخاصة بك وifs المتداخلة" إلى "إنشاء واستخدام وظائف صغيرة" إلى حدوث المزيد من الأخطاء في وقت الترجمة، ولكنه سيدفع الثمن من خلال وجود أخطاء أقل في وقت التشغيل. كما أن أخطاء وقت الترجمة الأكثر دقة ستؤدي إلى تقليل الوقت المستغرق في تصحيح الأخطاء.
توفر المقالة "البرمجة الوظيفية في C++ مع مكتبة FunctionalPlus؛ اليوم: تحدي HackerRank Gemstones" مقدمة سلسة للمكتبة من خلال إظهار كيف يمكن للمرء تطوير حل أنيق لمشكلة باستخدام نهج FunctionalPlus.
يوجد أيضًا على Udemy دورة بعنوان "البرمجة الوظيفية باستخدام C++" والتي تستخدم بكثرة FunctionalPlus لشرح المفاهيم الوظيفية العامة.
يشرح البرنامج التعليمي "الأحجار الكريمة" أعلاه كيف يمكن للمرء تطبيق التفكير الوظيفي للوصول إلى الحل أدناه للمشكلة التالية:
ابحث عن عدد الأحرف الموجودة في كل سطر من نص الإدخال.
std::string gemstone_count ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
const auto lines = split_lines ( false , input); // false = no empty lines
const auto sets = transform (
convert_container,
lines);
// Build the intersection of all given character sets (one per line).
const auto gem_elements = fold_left_1 (
set_intersection, sets);
return show ( size_of_cont (gem_elements));
}
باستخدام الوظيفة من namespace fwd
، يمكنك الاستغناء عن المتغيرات المؤقتة، وتوضيح أن العملية برمتها هي ببساطة دفع الإدخال من خلال سلسلة من الوظائف، على غرار مفهوم توجيه الإخراج في سطر أوامر Unix.
std::string gemstone_count_fwd_apply ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
return fwd::apply (
input
, fwd::split_lines ( false )
, fwd::transform (convert_container)
, fwd::fold_left_1 (set_intersection)
, fwd::size_of_cont ()
, fwd::show ()
);
}
في fplus::fwd::
تجد العديد من وظائف fplus::
مرة أخرى، ولكن في نسخة متقنة جزئيًا، على سبيل المثال fplus::foo : (a, b, c) -> d
له نظيره مع fplus::foo : (a, b) -> (c -> d)
. وهذا يجعل النمط أعلاه ممكنًا.
بدلاً من إصدار التطبيق المتقدم، يمكنك أيضًا الكتابة بدون نقاط وتحديد وظيفتك من خلال التركيب:
using namespace fplus ;
typedef std::set characters;
const auto gemstone_count_fwd_compose = fwd::compose(
fwd::split_lines ( false ),
fwd::transform(convert_container),
fwd::fold_left_1(set_intersection),
fwd::size_of_cont(),
fwd::show()
);
بالمناسبة، في حال كنت بحاجة إلى معلمات دالة ثنائية بترتيب عكسي، namespace fplus::fwd::flip
موجودة أيضًا. fplus::bar : (a, b) -> c
ليس له تمثيله فقط في fplus::fwd::bar : a -> b -> c
ولكن أيضًا في fplus::fwd::flip::bar : b -> a -> c
.
إذا كنت تبحث عن وظيفة FunctionalPlus محددة ولا تعرف اسمها بعد، فيمكنك بالطبع استخدام ميزة الإكمال التلقائي في IDE الخاص بك لتصفح محتوى namespace fplus
. لكن الطريقة الموصى بها هي استخدام موقع البحث FunctionalPlus API . يمكنك البحث بسرعة عن طريق الكلمات الرئيسية أو توقيعات نوع الوظيفة باستخدامها. إذا كنت تفضل ذلك، يمكنك أيضًا تصفح الكود المصدري باستخدام Sourcegraph.
الوظائف الأساسية سريعة، وذلك بفضل مفهوم التجريد في لغة C++ دون أي تكاليف إضافية. فيما يلي بعض القياسات من المثال الأول، التي تم التقاطها على جهاز كمبيوتر مكتبي قياسي، وتم تجميعها مع علم دول مجلس التعاون الخليجي وعلامة O3
.
5000 random numbers, keep odd ones, 20000 consecutive runs accumulated
----------------------------------------------------------------------
| Hand-written for loop | std::copy_if | fplus::keep_if |
|-----------------------|--------------|----------------|
| 0.632 s | 0.641 s | 0.627 s |
لذلك يبدو أن المترجم يقوم بعمل جيد جدًا في تحسين كل شيء وتضمينه ليعادل أداء كود الآلة بشكل أساسي.
على الرغم من أنه في بعض الأحيان يمكن كتابة الوظائف الأكثر تعقيدًا بطريقة أكثر تحسينًا. إذا كنت تستخدم FunctionalPlus في سيناريو حرج للأداء ويظهر التوصيف أنك بحاجة إلى إصدار أسرع من إحدى الوظائف، فيرجى إبلاغي بذلك أو حتى المساعدة في تحسين FunctionalPlus.
غالبًا ما يمكن أن يعمل FunctionalPlus داخليًا في مكانه إذا كانت حاوية معينة ذات قيمة r (على سبيل المثال في الاستدعاءات المتسلسلة)، وبالتالي يتجنب العديد من عمليات التخصيص والنسخ غير الضرورية. ولكن هذا ليس هو الحال في جميع الحالات. ومع ذلك، بفضل العمل باستخدام لغة متعددة النماذج، يمكن للمرء بسهولة الجمع بين التعليمات البرمجية الحتمية المحسّنة يدويًا ووظائف fplus
. لحسن الحظ، تُظهر التجربة (المعروفة أيضًا باسم التوصيف) أنه في معظم الحالات، لا تكون الغالبية العظمى من التعليمات البرمجية في التطبيق ذات صلة بالأداء العام واستهلاك الذاكرة. لذا فإن التركيز في البداية على إنتاجية المطورين وسهولة قراءة التعليمات البرمجية يعد فكرة جيدة.
يوجد لدى FunctionalPlus و range-v3 (أساس ranges
في C++-20) أشياء مشتركة، كما يظهر مقتطف التعليمات البرمجية التالي.
const auto times_3 = []( int i){ return 3 * i;};
const auto is_odd_int = []( int i){ return i % 2 != 0 ;};
const auto as_string_length = []( int i){ return std::to_string (i). size ();};
// FunctionalPlus
using namespace fplus ;
const auto result_fplus = fwd::apply(
numbers ( 0 , 15000000 )
, fwd::transform(times_3)
, fwd::drop_if(is_odd_int)
, fwd::transform(as_string_length)
, fwd::sum());
// range-v3
const auto result_range_v3 =
accumulate (
views::ints ( 0 , ranges::unreachable)
| views::take( 15000000 )
| views::transform(times_3)
| views::remove_if(is_odd_int)
| views::transform(as_string_length), 0);
هناك بعض الاختلافات رغم ذلك. نطاقات Range-v3 كسولة، مما يعني عدم تخصيص ذاكرة وسيطة أثناء الخطوات الفردية لسلسلة المعالجة مثل ما سبق. عند استخدام FunctionalPlus من ناحية أخرى فإنك تعمل مع حاويات STL العادية. كما يعد تنفيذ وظيفة جديدة أسهل مقارنة بكتابة محول نطاق جديد. بالإضافة إلى ذلك، يوفر FunctionalPlus المزيد من الوظائف خارج الصندوق ولديه موقع بحث API. لذا فإن الاختيار بين المكتبتين يعتمد على تفضيلاتك واحتياجات المشروع.
هناك حاجة إلى مترجم متوافق مع C++ 14 . المترجمون من هذه الإصدارات فصاعدًا جيدون:
يمكن العثور على أدلة حول الطرق المختلفة لتثبيت FunctionalPlus في INSTALL.md.
تطورت الوظائف الموجودة في هذه المكتبة في البداية بسبب حاجتي الشخصية إليها أثناء استخدام لغة C++ بانتظام. أبذل قصارى جهدي لجعله خاليًا من الأخطاء ومريحًا للاستخدام قدر الإمكان. لا تزال واجهة برمجة التطبيقات (API) قد تتغير في المستقبل. إذا كانت لديك أي اقتراحات، أو وجدت أخطاء، أو فاتتك بعض الوظائف، أو كنت ترغب في تقديم تعليقات/انتقادات عامة، فيسعدني أن أسمع منك. وبطبيعة الحال، المساهمات هي أيضا موضع ترحيب كبير.
يتم توزيعه بموجب ترخيص برنامج Boost، الإصدار 1.0. (راجع ملف LICENSE
المصاحب أو انسخه على http://www.boost.org/LICENSE_1_0.txt)