Τρίτη, 14 Νοεμβρίου 2017

Αναθεώρηση 7 Έκδοση 9.0

Σε αυτήν την αναθεώρηση έκανα μια επέκταση στις λάμδα συναρτήσεις για να μπορούν να διαχειριστούν τα "κλεισίματα" (closures), καλύτερα:

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

Στο παράδειγμα καλούμε την k και περνάμε με αναφορά στην k τον εαυτό της! Κάθε φορά καλούμε το εαυτό της όχι με την Lambda() (μια δεσμευμένη λέξη που σημαίνει να καλεί την εαυτό της οποιαδήποτε συνάρτηση όχι μόνο η Lambda(), δηλαδή λειτουργεί ανεξάρτητα από το όνομα). Σε κάθε κλήση της ίδιας συνάρτησης, είτε με τη Lambda() είτε όπως εδώ μέσω του Lamb, μια αναφορά στη k, υπάρχει εσωτερικά μια απαρίθμηση (refcount) και αυτή κάνει τον διερμηνευτή να μην παράγει νέες μεταβλητές με όνομα και δείκτη σε νέα αποθήκευση, αλλά με όνομα μόνο και δείκτη στην προηγούμενη αποθήκευση. Δηλαδή ο δείκτης για κάθε όνομα μεταβλητής υπάρχει, όπως και να έχει το πράγμα, αλλά αναφορά σημαίνει να δείχνει εκεί που δείχνει ήδη ένας άλλος δείκτης. Επειδή ο διερμηνευτής καταστρέφει τις μεταβλητές που φτιάχνει στο πέρας της κλήσης μιας συνάρτησης, οι μεταβλητές που έχουν δηλωθεί ως αναφορές, απλά διαγράφονται σαν όνομα, αλλά η αποθήκευση μένει, και θα φύγει και αυτή όταν δεν θα υπάρχει καμία αναφορά σε αυτήν. Οι αναφορές δηλαδή έχουν να κάνουν με τον πίνακα ονομάτων μεταβλητών και το δείκτη στο πίνακα που αποθηκεύονται. Οι μεταβλητές που θεωρούνται δείκτες σε αποθήκευση, ουσιαστικά είναι διπλές αναφορές, μια για το όνομα στο πίνακα αποθήκευσης και μια από το πίνακα αποθήκευσης στο πραγματικό αντικείμενο, εκτός πίνακα. Στο παράδειγμα το Ζ=(1,2,3) είναι τέτοιος δείκτης. Κάθε φορά που δημιουργεί μια αναφορά του Ζ, αυτή είναι αναφορά της πρώτης αναφοράς. Αν σε κάποια κλήση δοθεί νέο αντικείμενο, πχ Ζ=(,), ο κενός πίνακας, τότε θα αλλαχθεί η "τελική" αναφορά, που θα δείχνει έναν νέο κενό πίνακα, και ο παλιός πίνακας, ο (1,2,3) που τώρα πια δεν τον αναφέρει το Ζ και καμία αναφορά στο Ζ, θα "πεθάνει" βάσει του δικού του refcount, το οποίο έχουν όλα τα αντικείμενα με δείκτη. Αν δηλαδή πριν καλέσουμε την K είχαμε κάνει αυτό M=Ζ, τότε το Μ θα έδειχνε και αυτό το (1,2,3) και έτσι αν αλλάξει ο δείκτης στο Ζ θα μείνει στον Μ να κρατάει "ζωντανό" το (1,2,3)
 
\\This example call a lambda function and pass a reference to lambda object
\\ Inside lambda we call this object again, passing this object again

X=100
\\ X, M, Z are closures
\\ X is a copy of local X
\\ This lambda return 0 (0 is proper value for call, if was no zero then error happen, with the returned value)

k=lambda X, M=30, Z=(1,2,3) (&lamb, n) ->{
      Print n, X*M, Z
      X--
      if n>0 then call lamb(&lamb, n-1)
}
\\ We call k
Call k(&k, 3)
Call k(&k, 3)
Print X

k=lambda X, M=30, Z=(1,2,3) (n) ->{
      Print n, X*M, Z
      X--
      if n>0 then callme=Lambda(n-1)
}
\\ We call k
Call k(3)
Call k(3)
Print X

Ο μηχανισμός κλήσης της λάμδα, και πώς φτιάχνει τα κλεισίματα.
Ένα κλείσιμο παίρνει τιμή στην σύνταξη της λάμδα, ή υπήρχε με το ίδιο όνομα και απλά η λάμδα θα φτιάξει αντίγραφο. Σε άλλες γλώσσες τα κλεισίματα είναι αναφορές στις τοπικές που περνάμε. Η Μ2000 με την αναθεώρηση 7, της έκδοσης 9.0 κάνει αυτό το πράγμα για τις κλήσεις τις λάμδα στον εαυτό της. Επειδή δεν έχει όνομα μια λάμδα (μπορεί πρόσκαιρα να έχει επειδή είναι σε μια μεταβλητή, αλλά θα μπορούσε να ήταν σε στοιχείο πίνακα, ή σε κατάσταση ζευγών κλειδιού-τιμής). Υπάρχουν δυο λάμδα τύποι ανάλογα την επιστροφή, ο δεύτερος είναι για τα αλφαριθμητικά και ξεχωρίζει με την χρήση του $.

Ουσιαστικά κάθε αντικείμενο λάμδα έχει δυο πράγματα, μια λίστα κλεισιμάτων και το κώδικα της συνάρτησης. Όταν καλούμε την λάμδα ο διερμηνευτής βάζει πρόσκαιρα τη συνάρτηση στο σύστημα επιλέγοντας ένα όνομα αυτόματα. Πριν την καλέσει βάζει και τα κλεισίματα με ονόματα, και κρατάει τις θέσεις τους για να τα διαβάσει ξανά (μέσα στη λίστα κλεισιμάτων της λάμδα). Τώρα λοιπόν αυξάνει και ένα νούμερο refcount (μέσα στο αντικείμενο λάμδα) ώστε αν κληθεί το ίδιο και άλλη φορά πριν τερματίσει η πρώτη κλήση να μην δημιουργεί νέες μεταβλητές για τα κλεισίματα, αλλά μόνο αναφορές σε αυτά.  Σε κάθε κλήση η λάμδα μπορεί να έχει τοπικές μεταβλητές, αλλά τα κλεισίματα είναι τοπικές "αναφορές" στις κλεισμένες. Έτσι οι τοπικές μπορούν να έχουν διαφορετικές τιμές αφού κάθε μια, αν και με το ίδιο όνομα σε κάθε κλήση (σε αναδρομική κλήση δηλαδή), αφού έχουν διαφορετική αποθήκευση. Τα ονόματα μεταβλητών αν και τα βλέπουμε στο κώδικα και θεωρούμε ότι θα είναι τα ίδια σε κάθε κλήση, πχ το Ζ θα είναι Ζ, στην πραγματικότητα ο διερμηνευτής βάζει προθέματα, και με αυτά ξεχωρίζει τι  είναι σε ποια συνάρτηση που εκτελείται ή όπως λέμε στη Μ2000, σε ποιο αντικείμενο εκτέλεσης βρίσκεται. Οι συναρτήσεις και τα τμήματα εκτελούνται σε αντικείμενο εκτέλεσης, θα λέγαμε έναν "επεξεργαστή". Όταν καλούμε σε βάθος αναδρομής πχ 100 κλήσεων, θα υπάρχουν 100 αντικείμενα εκτέλεσης. Το μέγιστο βάθος είναι 3260 κλήσεις. Οι ρουτίνες που δημιουργούμε με Sub/End Sub ή Ρουτίνα/Τέλος Ρουτίνας έχουν μεγάλο αριθμό αναδρομής, μπορούμε να το ορίσουμε κιόλας πχ Όριο.Αναδρομής 100000 Recursion.Limit 100000), επειδή τρέχουν στο ίδιο αντικείμενο εκτέλεσης, από εκεί που τις καλούμε, και αυτό το αντικείμενο έχει δική του στοίβα επιστροφής για τις ρουτίνες (αν και το μέτρημα του ορίου πάει στο σύνολο για όλα τα αντικείμενα εκτέλεσης). Από το αντικείμενο εκτέλεσης μπορούμε να πάρουμε το όνομα με την Τμήμα$ ή Module$ (ακόμα και αν έχει κώδικα συνάρτησης). Επίσης με την Τμήμα() ως συνάρτηση μπορούμε να ρωτήσουμε αν υπάρχει κάποιο τμήμα για να μπορεί να κληθεί πριν κληθεί. Η Τμήμα(ΞεκινάειΜε()) δίνει -1, αληθές, αν το όνομα είναι θεατό στο τρέχον αντικείμενο εκτέλεσης.

Στο παράδειγμα έχουμε μια συνάρτηση που γυρίζει μια λάμδα, με μια λάμδα ως κλείσιμο σε αυτήν. Θα την χρησιμοποιήσουμε ως έχει, αλλά και σε στοιχείο πίνακα και σε στοιχεία κατάστασης.
 
Συνάρτηση ΠάρεΛάμδα {
      ΞεκινάειΜε=Λάμδα (Ν$, Μεαυτό$) ->{
                        Διάβασε ? Χωρίς$ ' ζητάει προαιρετικά τιμή αλλιώς δίνει κενό.
                        Αν Χωρίς$<>"" και Μήκος(Ν$)>Μήκος(Μεαυτό$) Τότε {
                              \\ η εντολή διέκοψε διακόπτει όλα τα μπλοκ μέχρι και τη συνάρτηση.
                              Αν Θέση(Χωρίς$, Μεσ$(Ν$,Μήκος(Μεαυτό$)+1,1))>0 Τότε Διέκοψε
                        }
                        =Αρισ$(Ν$, Μήκος(Μεαυτό$))=Μεαυτό$
      }
      \\ Βάζουμε δυο κλεισίματα, το ένα το παίρνει από την ΞεκινάειΜε, και το άλλο το δημιουργεί και βάζει το 0
  
      = Λάμδα ΞεκινάειΜε, Σημαία (Ν$) ->{
            Αν Ν$="" Τότε Έξοδος
            {
                  \\ θέλουμε να φεύγουμε από το μπλοκ με την έξοδος.
                  \\ αν φθάσουμε στο τέλος του η σημαία αλλάζει!
                  \\ η έξοδος διακόπτει ένα μπλοκ μόνο
                  Σημαία=Αληθής
                  Αν ΞεκινάειΜε(Ν$,"MMMM") Τότε Σημαία=Ψευδής : Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"CM","MD") Τότε = 900 + Λάμδα(Μεσ$(Ν$, 3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"D","DM") Τότε = 500 + Λάμδα(Μεσ$(Ν$, 2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"M") Τότε = 1000 + Λάμδα(Μεσ$(Ν$, 2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"CD","MD") Τότε = 400 + Λάμδα(Μεσ$(Ν$,3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"XC","MCDL") Τότε = 90 + Λάμδα(Μεσ$(Ν$, 3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"CCCC") Τότε Σημαία = Ψευδής : Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"C") Τότε = 100 + Λάμδα(Μεσ$(Ν$, 2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"L", "L") Τότε = 50 + Λάμδα(Μεσ$(Ν$,2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"XXXX") Τότε Σημαία = Ψευδής : Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"XL","MCXDL") Τότε = 40 + Λάμδα(Μεσ$(Ν$, 3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"X","MDL") Τότε = 10 + Λάμδα(Μεσ$(Ν$, 2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"IX","MCDLXVI") Τότε = 9 + Λάμδα(Μεσ$(Ν$,3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"IV","MDCLXVI") Τότε = 4 + Λάμδα(Μεσ$(Ν$, 3)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"V","MDCLXV") Τότε = 5 + Λάμδα(Μεσ$(Ν$,2)): Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"IIII") Τότε Σημαία = Ψευδής : Έξοδος
                  Αν ΞεκινάειΜε(Ν$,"I", "MCDLXV") Τότε = 1 + Λάμδα(Μεσ$(Ν$, 2)) : Έξοδος
                  Σημαία = Ψευδής
            }     
            Αν Όχι Σημαία Τότε =0
      }
}
ΣεΑραβικούς=ΠάρεΛάμδα()
Τύπωσε ΣεΑραβικούς("MMXVII")
Τύπωσε ΣεΑραβικούς("CXCIX")
Τύπωσε ΣεΑραβικούς("CIC") ' 0 δεν υπάρχει
Τύπωσε ΣεΑραβικούς("IIII") ' 0 δεν υπάρχει
Τύπωσε ΣεΑραβικούς("LL") ' 0 δεν υπάρχει
\\ Δοκιμή σε αποθήκες...όπως πίνακας και κατάσταση
Πίνακας α(10)
α(3)=ΣεΑραβικούς
Αναφορά "Με χρήση Λάμδα σε πίνακα (χωρίς όνομα, με θέση βάσει δείκτη)"
Τύπωσε α(3)("IXV") ' 0 δεν υπάρχει
Τύπωσε α(3)("CCCC") ' 0 δεν υπάρχει  θέλει CD
Τύπωσε α(3)("XL"),"XL"   ' 40
Τύπωσε α(3)("XC"),"XC"   ' 90
Αναφορά "Με χρήση Λάμδα σε κατάσταση (χωρίς όνομα, με θέση βάσει κλειδιού)"
Κατάσταση Τσάντα= "κλειδί1":=ΣεΑραβικούς, 300:=α(3)
Τύπωσε Τσάντα("κλειδί1")("CD") ' 400
Τύπωσε Τσάντα("κλειδί1")("C") ' 100
Τύπωσε Τσάντα(300)("MMMCMXCIX"), " Μέγιστος αριθμός για λατινική γραφή"
Τύπωσε Τσάντα("300")("XLIXL") ' 0  δεν υπάρχει