:clang: Μοντέρνος προγραμματισμός, C++ και λίγο πίπες

Μιας και κάθε ιστορία πρέπει να έχει μια αρχή ας ξεκινήσουμε με ένα απλό πρόβλημα.

Διάβασε ένα αρχείο κειμένου, βρες τις n λέξεις με την μεγαλύτερη συχνότητα, και τύπωσε την λίστα αυτή, ταξινομημένη, μαζί με τις αντίστοιχες συχνότητες.

Εύκολο; Γιατί δεν δοκιμάζεις να το κάνεις; Πόσο εκτιμάς να σου πάρει; Θα περιμένω λίγο.

Το πρόβλημα δόθηκε στον διάσημο Donald Knuth (και αν δεν ξέρεις ποιος είναι αυτός ο κύριος σταμάτα, μάθε πρώτα και μετά συνέχισε την ανάγνωση) το 1986 για την στήλη “Programming Pearls” του μηνιαίου περιοδικού Communications of the ACM.

Η λύση του Knuth γραμμένη σε Pascal ήταν 10 σελίδες. Τα περισσότερα ήταν βέβαια σχόλια (και επειδή διάβασες και έμαθες για το τι ήταν ο Knuth θα έπρεπε να το περιμένεις αυτό). Η υλοποίηση του Knuth είχε μέσα μια όμορφη δομή δεδομένων για να κρατάει τις συχνότητες.

Σε απάντηση ο Doug McIlroy (που κάτι ήξερε από πίπες μιας και αυτός τις ανακάλυψε) έγραψε το παρακάτω

tr -cs A-Za-z '\n' |
    tr A-Z a-z |
    sort |
    uniq -c |
    sort -rn |
    sed ${1}q

Αφήνοντας κατά μέρος το ζήτημα της απόδοσης, τι θα προτιμούσες να καταλάβεις; Τις 10 σελίδες του Knuth ή τα ιερογλυφικά του Doug McIlroy και τις πίπες του;

Πίπα (pipe) που την κάνουμε στο τερματικό με το σύμβολο | είναι ένας τρόπος να σπάμε ένα πρόβλημα σε μικρά προγράμματα που κάνουν μια μικρή αλλά συγκεκριμένη δουλεία και να τα συνδέουμε μεταξύ τους. Ελπίζω να μην σκεφτήκατε ότι θα μίλαγα για κάτι άλλο. Δεν είμαι clickbait τύπος.

Σίγουρα όλοι θα προτιμούσαμε την λύση του Doug McIlroy. Απλά βήματα που μπορούμε να καταλάβουμε τι κάνει το κάθε ένα (εντάξει δεν μπορούμε αν δεν είμαστε μάγοι στο bash, αλλά αν είμασταν;) . Δεν μας απασχολεί πως θα βρει ο υπολογιστής την απάντηση και τι βήματα θα κάνει. Εμείς θα καθήσουμε να του πούμε τι θέλουμε και να κάτσει να τα βρει μόνος του ο παλιοντεμπέλης. Ορίστε μας/

Το στυλ αυτό του προγραμματισμού είναι αυτό που οι επιστήμονες της πληροφορικής το λένε συναρτησιακός προγραμματισμός. Εντάξει αυτός περιέχει και άλλα πράγματα (βασικά μια γλώσσα είναι συναρτησιακή, μόνο αν έχει όλα τα χαρακτηριστικά της δικιάς σου αγαπημένης γλώσσας) αλλά ας μείνουμε σε αυτό για την ώρα.

Εντάξει τώρα, καλό το clickbait αλλά που κολλάει η C++; Πριν επιτρέψτε μου να μοιραστώ μια προσωπική στιγμή

std::vector<int>
count_lines_in_files(const std::vector<std::string>& files)
{
    return files | transform(count_lines);
}

Δεν έχω ξανακλάψει ποτέ αντικρίζοντας μια τέτοια ομορφιά σε κώδικά. Αλήθεια λέω και είμαι μεγάλο παιδί.

Η κλασσική υλοποίηση σε C++ ή Java θα ήταν 20 γραμμές τουλάχιστον, που θα έπρεπε να τις διαβάσεις για να καταλάβεις τι κάνει, θα είχε ένα βρόχο και θα έπρεπε να τις δεις προσεχτικά γιατί κάθε γραμμή, μπορεί να περιέχει ένα λάθος. Και η απόδοση που θα σέρνετε θα πει κάποιος και θα κάνει λάθος, γιατί θα έχουν την ίδια.

Ο κώδικάς αυτός θα τρέξει σε κάθε μοντέρνο μεταγλωττιστή της C++. Σήμερα θέλει μια εξωτερική βιβλιοθήκη (ranges την λένε) αλλά αυτή θα ενσωματωθεί στην επόμενη C++20.

Οι επιστήμονες της πληροφορικής περίμεναν δεκαετίες για να γίνει δημοφηλής μια συναρτησιακή γλώσσα στην πιάτσα. Και τσάμπα περίμεναν, γιατί όλες οι καινούργιες γλώσσες έχουν κλέψει πράγματα από τις συναρτησιακές.

Ναι αλλά η C++ είναι μια παλιά γλώσσα θα πεις. Και θα έχεις άδικο, γιατί η C++11 είναι ουσιαστικά μια καινούργια γλώσσα. Και σταμάτα σε παρακαλώ να την λες αντικειμενοστραφή γιατί δεν είναι, η τουλάχιστον δεν είναι μόνο. Η C++ έχει πολλά πρόσωπα και η κεντρική της βιβλιοθήκη η STL δεν ακολουθεί καν το αντικειμενοστραφές παράδειγμα. Πλέον έχει μια ακόμα όψη.

Πίσω στα σχολεία λοιπόν. Και αυτό είναι το ωραίο στον προγραμματισμό. Πλέον με τους επεξεργαστές με τους πολους πυρήνες, τις ιεραρχίες τις cache και την gpgpu θέλουμε νέες γλώσσες, καθώς και να αλλάξουμε τις παλιές. Ξαναδές την αγαπημένη σου γλώσσα λοιπόν, δες τι καινούργια έχει να σου δώσει και πρόσθεσε και νέα εργαλεία στο οπλοστάσιο σου. Το ποια γλώσσα αυτή θα είναι είναι, αδιάφορο στην τελική.

Και οποίος θέλει ας δώσει την λύση του με όποιο τρόπο ή γλώσσα θέλει στο παλιό αυτό πρόβλημα από το μακρινό 1986.

Ο κώδικάς και η ιστορία είναι από το βιβλίο του Ivan Čukić “Functional Programming in C++” (https://www.manning.com/books/functional-programming-in-c-plus-plus)

7 Likes

Από την άλλη έχεις τυπικές κλάσεις και μπορείς να κάνεις τα αγαπημένα σου pattern (dependency injection) σε C++ .

Όμως θα ομολογήσω πως το debate της “Ποια γλώσσα είναι η καλύτερη” είναι παρωχημένο. Στην τελική μια Γλώσσα είναι καλή όσο το οικοσύστημά της έτσι πχ. έαν θες να κάνεις νευρωνικό δίκτυο σε σε python θα είναι λιιιγο ευκολότερο λόγο ότι έχει άτομα που κάνουν εργαλεία και βιβλιοθήκες για νευρωνικά δίκτυα.

Εαν θες high performance web τότε πας σε λύσεις που είναι περισσότερο event driven κοκ. Πλέον μια γλώσσα είναι ικανή όσο υπάρχουν άτομα που κάνουν api’s για ένα συγκεκριμένο σκοπό. Στην τελική μην ξεχνάμε ότι πρέπει να πληρωθείς το 1000ρικο.

3 Likes

Δεν θα έπρεπε να είναι transform (count_lines, files)?

Καλώς ήρθες @Kostis_Anagnostopoul στο linux-users,gr :smiley:

Στην τρέχουσα έκδοση της C++ χρησιμοποιείς την transform κάπως έτσι :hot_face:

std::transform (files.begin(), files.end(), 
                          count_vector.begin(), count_lines);

Ο κώδικας του παραδείγματος χρησιμοποιεί την βιβλιοθήκη ranges του Eric Niebler. Θα γίνει μέρος της C++20 και πιστεύω θα μεταμορφώσει την χρήση της γλώσσας στον ίδιο βαθμό με την C++11.

Τα ranges προσθέτουν στην C++ κάτι παραπάνω από ένα ζεύγος δυο iterators. Προσφέρουν composability από μικρά πράγματα. Το μυστικό στον κώδικά είναι ότι κάπου κάνει ένα auto operator|(Pipe0 pipe0, Pipe1 pipe1) και επαναορίζει τον τελεστή | που κανονικά κάνει bitwise inclusive OR. :dizzy_face:

Με αυτό το “απλό” κόλπο η γλώσσα αποκτά μεγάλη εκφραστικότητα. Κάπως σαν τον επανορισμό του τελεστή left shift ‘<<’ στα streams. Και που δείχνει φυσικό σε κάποιον που μαθαίνει C++

#include <iostream>
using namespace std;

int main() 
{
    cout << "Hello, World!";
    return EXIT_SUCESS;
}

ενώ ο μηχανισμός είναι αρκετά πολύπλοκος. Έτσι και η διοχέτευση και τα ρεύματα κάποια μέρα θα είναι κάτι το εντελώς φυσικό σε κάποιον που θα μαθαίνει C++. :mask:

Αν κάτι δείχνει την δύναμη της C++ είναι αυτό το παράδειγμα. Γιατί και η Lisp είναι ζυμάρι με τα macros και την Ruby :ruby: την κάνεις αγνώριστη με τα DSL. Η C++ δεν έχει τόση ευλυγισία μεν αλλά έχει αρκετή.

Και - ας το παραδεχτούμε - για να τα κάνεις αυτά πρέπει να φας τα νιάτα σου σε σκοτεινές γωνίες της γλώσσας :poop:. Ευτυχώς δεν είναι ανάγκη ακόμα και ένας χρόνια επαγγελματίας της γλώσσας να ξέρει αυτές τις σκοτεινές γωνίες.

Η διαφορά της με τις άλλες γλώσσες είναι πως αυτά τα κόλπα παράγουν κώδικά εξίσου καλό με κώδικά που δεν τα χρησιμοποιεί και δεν θα βρεις πολλές γλώσσες να την κοντράρουν σε αυτό.

1 Like

Ευχαριστώ για το spoiler :slight_smile:
Όπως ισχύει στο coconut[1], jinja ή sspipes[2], στον κόσμο του python :python:.

1: http://coconut-lang.org
2: https://github.com/sspipe/sspipe

1 Like