Διαδρομές
/
Factor
Factor
/
Ασκήσεις
/
Στένσιλ Σημάτων
Στένσιλ Σημάτων

Στένσιλ Σημάτων

Άσκηση εκμάθησης

Εισαγωγή

Ξέρεις ήδη πώς να φτιάχνεις μια quotation από κομμάτια και να την εκτελείς. Μια quotation δεν είναι παρά μια ακολουθία κώδικα, οπότε οι λέξεις για ακολουθίες που ξέρεις ισχύουν και γι' αυτήν:

USING: kernel math prettyprint quotations sequences ;

{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call .   ! => 9
[ 1 + ] dup append .                                 ! => [ 1 + 1 + ]

Το append ενώνει δύο quotations, το concat επιπεδοποιεί έναν πίνακα από αυτές, το map μετατρέπει δεδομένα σε quotations και το >quotation κάνει το αποτέλεσμα να μπορεί να κληθεί. Το MACRO: παίρνει ακριβώς αυτή την ικανότητα και τη μεταφέρει στον χρόνο μεταγλώττισης.

Τι είναι ένα μάκρο

Ένας ορισμός MACRO: μοιάζει με μια συνηθισμένη λέξη, αλλά το σώμα του εκτελείται όσο μεταγλωττίζεται το πρόγραμμά σου, όχι όταν αυτό εκτελείται. Το σώμα πρέπει να αφήσει μία quotation στη στοίβα. Ο μεταγλωττιστής τότε παρεμβάλλει αυτή την quotation στο σημείο κλήσης. Αντικαθιστά την κλήση του μάκρο με τον κώδικα που παρήγαγε το σώμα. Κατά την εκτέλεση δεν μένει κανένα μάκρο, μόνο ο κώδικας που δημιουργήθηκε.

USING: kernel macros sequences ;

MACRO: twice ( quot -- spliced )
    dup append ;

Όπου κι αν γράψεις [ 1 + ] twice, ο μεταγλωττιστής:

  1. εκτελεί το σώμα με το [ 1 + ] στη στοίβα,
  2. το dup append φτιάχνει την quotation [ 1 + 1 + ],
  3. παρεμβάλλει τον κώδικά της, 1 + 1 +, σε εκείνο το σημείο.

Έτσι, μια λέξη που καλεί το twice μεταγλωττίζεται σαν να είχες γράψει 1 + 1 + με το χέρι:

: inc-twice ( x -- y ) [ 1 + ] twice ;

5 inc-twice .   ! => 7

Η βασική αντίθεση: μια συνηθισμένη λέξη που παίρνει μια quotation της κάνει call κατά την εκτέλεση. Ένα μάκρο δεν καλεί ποτέ τίποτα. Δίνει στον μεταγλωττιστή νέο κώδικα για να μεταγλωττίσει στη θέση του.

Οι είσοδοι πρέπει να είναι κυριολεκτικές τιμές

Επειδή το σώμα εκτελείται στον χρόνο μεταγλώττισης, οι είσοδοι ενός μάκρο πρέπει να είναι γνωστές τότε. Πρέπει να είναι κυριολεκτικά στο σημείο κλήσης: μια quotation [ … ], ένας αριθμός, ένας πίνακας, μια συμβολοσειρά. Μια τιμή που υπάρχει μόνο κατά την εκτέλεση δεν μπορεί να δοθεί σε ένα μάκρο.

! DOCTEST: SKIP — illustrative call sites, not standalone code
[ 1 + ] twice          ! ok: the quotation is a literal
[ 1 + ] 3 repeat-quot  ! ok: the quotation and the number are literals

Τα εργαλεία

Το σώμα ενός μάκρο είναι συνηθισμένος κώδικας που επιστρέφει μια quotation. Λέξεις που έχεις ήδη συναντήσει κάνουν τη μεγαλύτερη δουλειά:

για να φτιάξεις… χρησιμοποίησε
το [ A B ] από τα [ A ] και [ B ] append
μία quotation από έναν πίνακα από αυτές concat >quotation
μία quotation για κάθε στοιχείο μιας ακολουθίας map, μετά concat >quotation

Δύο ακόμη λέξεις, από τα sequences και kernel, συμπληρώνουν το σετ.

Το <repetition> (στο sequences) φτιάχνει μια ακολουθία από n αντίγραφα ενός στοιχείου. Η επίδρασή του στη στοίβα είναι ( length elt -- repetition ), με το πλήθος πρώτο. Το αποτέλεσμα είναι μια εικονική ακολουθία, οπότε την επιπεδοποιείς με τον συνηθισμένο τρόπο:

3 [ 1 + ] <repetition> concat >quotation .   ! => [ 1 + 1 + 1 + ]

Το curry (στο kernel) προσθέτει μια τιμή στην αρχή μιας quotation και παράγει μια νέα quotation που σπρώχνει πρώτα αυτή την τιμή:

3 [ + ] curry .          ! => [ 3 + ]
{ 1 2 3 } [ . ] curry .  ! => [ { 1 2 3 } . ]

Με το curry ενσωματώνεις ένα συγκεκριμένο στοιχείο μέσα σε μια quotation, ακριβώς αυτό που μετατρέπει τα { 10 20 30 } και [ + ] σε [ 10 + ] [ 20 + ] [ 30 + ].

Γιατί αυτό είναι πιο απλό απ' όσο ακούγεται

Σε κάποιες γλώσσες, τα μάκρο χρειάζονται ειδικό συντακτικό quote/unquote και επιπλέον μηχανισμούς για να αποφεύγονται οι συγκρούσεις ονομάτων. Η Factor δεν χρειάζεται τίποτα από αυτά: μια quotation είναι ήδη μια ακολουθία από επιλυμένες λέξεις. Ο αναλυτής έψαξε και βρήκε κάθε dup, + και append όταν διάβασε το [ … ]. Άρα το σώμα ενός μάκρο είναι απλώς ο χειρισμός ακολουθιών που κάνεις ήδη και που τυχαίνει να επιστρέφει μια quotation.

Ένας κοντινός συγγενής: $[ … ]

Ένα μάκρο επιστρέφει μια quotation για να παρεμβληθεί σε μια κλήση. Όταν αντίθετα θέλεις μια απλή τιμή υπολογισμένη τη στιγμή της ανάλυσης, το $[ code ] εκτελεί τον code όσο το αρχείο διαβάζεται και ενσωματώνει το αποτέλεσμα στον πηγαίο κώδικα ως κυριολεκτικό:

! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;

CONSTANT: build-time $[ now ]

Το $[ … ] προέρχεται από το λεξιλόγιο literals, που συγκεντρώνει τις λέξεις για την παρεμβολή αποτελεσμάτων του χρόνου ανάλυσης σε κυριολεκτικά. Αναφέρεται για πληρότητα. Τα τέσσερα στένσιλ που θα γράψεις, τα twice, repeat-quot, compose-many και each-literal, είναι όλα δηλώσεις MACRO:.

Οδηγίες

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

Γράφεις μόνο τις τέσσερις μακροεντολές. Το αρχείο δοκιμών ορίζει μικρές λέξεις-περιτυλίγματα που καλούν τις μακροεντολές σου (μια μακροεντολή μπορεί να παρατηρηθεί μόνο μέσω του κώδικα που παράγει) και μετά ελέγχει τα αποτελέσματα εκτέλεσης των περιτυλιγμάτων. Άφησε τα περιτυλίγματα όπως είναι.

Για κάθε εργασία, το σώμα λαμβάνει στη στοίβα τα literal ορίσματα του σημείου κλήσης και πρέπει να αφήσει μία παράθεση. Οι γραμμές "compiles to" δείχνουν τον γραμμικό κώδικα που πρέπει να παράγει η παράθεσή σου.

1. Δύο φορές

Το twice παίρνει μια literal παράθεση και επιστρέφει μια παράθεση που εκτελεί την αρχική δύο φορές στη σειρά.

! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ]  ->  must build [ 1 + 1 + ]
5 inc-twice .   ! => 7

2. N επαναλήψεις

Το repeat-quot παίρνει μια literal παράθεση και ένα literal πλήθος n, και επιστρέφει μια παράθεση που εκτελεί την αρχική n φορές στη σειρά. Με n = 0 επιστρέφει την κενή παράθεση [ ].

! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3  ->  must build [ 1 + 1 + 1 + ]
5 inc-thrice .   ! => 8

3. Σύνθεση πολλών

Το compose-many παίρνει έναν literal πίνακα από παραθέσεις και επιστρέφει μία μόνο παράθεση που εκτελεί την καθεμιά διαδοχικά. Ένας κενός πίνακας επιστρέφει την κενή παράθεση.

! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] }  ->  must build [ 1 + 2 * 1 - ]
5 pipeline .   ! => 11

4. Ξεδιπλωμένη επανάληψη

Το each-literal παίρνει μια literal ακολουθία και μια παράθεση. Επιστρέφει μια παράθεση που, για κάθε στοιχείο της ακολουθίας, βάζει αυτό το στοιχείο στη στοίβα και μετά εκτελεί την παράθεση: επανάληψη που συμβαίνει σε χρόνο μεταγλώττισης, οπότε ο κώδικας που παράγεται είναι γραμμικός.

! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ]  ->  must build [ 10 + 20 + 30 + ]
sum-three .   ! => 60

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

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Factor Exercism

Έτοιμος να ξεκινήσεις την άσκηση Στένσιλ Σημάτων;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Factor με 47 έννοιες163 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.