ช่วยให้คุณเขียนโค้ด 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 loop ที่เขียนด้วยลายมือนั้นเข้าใจง่ายกว่า ให้ลองพิจารณาด้วยว่าจะเกิดอะไรขึ้นหากเนื้อหาของลูป (เช่น ฟังก์ชันแลมบ์ดาที่สอดคล้องกันในการเรียก fplus::keep_if
) จะยาวกว่ามาก เมื่ออ่าน keep_if
คุณจะยังคงรู้ได้ทันทีว่า odds
สามารถมีได้เฉพาะองค์ประกอบที่มาจาก values
และได้รับเลือกโดยเพรดิเคตบางส่วนที่อาจซับซ้อน ในกรณี for loop คุณไม่รู้ว่าเกิดอะไรขึ้นจนกว่าคุณจะอ่านเนื้อหาของลูปทั้งหมด เวอร์ชันลูปอาจต้องมีความคิดเห็นที่ด้านบนโดยระบุว่าการใช้ 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 Challenge 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::
มากมายอีกครั้ง แต่ในเวอร์ชัน curried บางส่วน เช่น 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++ โดยไม่มีค่าใช้จ่ายใดๆ ต่อไปนี้คือการวัดบางส่วนจากตัวอย่างแรก ซึ่งวัดจากเดสก์ท็อปพีซีมาตรฐาน ซึ่งคอมไพล์ด้วย GCC และแฟล็ก 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 Software License เวอร์ชัน 1.0 (ดูไฟล์ LICENSE
อนุญาตประกอบหรือคัดลอกได้ที่ http://www.boost.org/LICENSE_1_0.txt)