Σε αυτό το σημείο της ύλης, έχεις ήδη δει πολλούς ορισμούς συναρτήσεων. Συνήθως, αυτοί παρουσιάζονταν χωρίς πολλά σχόλια για το αποτέλεσμα στο REPL:
julia> f(x) = x^2
f (generic function with 1 method)
Γιατί "generic function with 1 method";
Ας υποθέσουμε ότι θέλουμε να είμαστε πιο συγκεκριμένοι ως προς τους τύπους των ορισμάτων.
julia> f(x::Integer) = x^2
f (generic function with 2 methods)
julia> f(x::Real) = x^2
f (generic function with 3 methods)
Ή θα μπορούσαμε να αλλάξουμε τον αριθμό των ορισμάτων:
julia> f(x, y) = x * y
f (generic function with 4 methods)
Φαίνεται ότι οι νέοι ορισμοί της ίδιας συνάρτησης ονομάζονται "μέθοδοι". Οι μέθοδοι προστίθενται σε μια λίστα, αντί να αντικαθιστούν τον παλιό ορισμό.
Μπορούμε εύκολα να καταγράψουμε τις μεθόδους:
julia> methods(f) # output edited for brevity
# 4 methods for generic function "f" from Main:
[1] f(x, y)
[2] f(x::Integer)
[3] f(x::Real)
[4] f(x)
Αν καλέσουμε τη συνάρτηση f, η Julia με κάποιον τρόπο ξέρει ποια μέθοδο να χρησιμοποιήσει:
julia> f(2.5) # Float64
6.25
julia> f(2) # Int64
4
julia> f(2, 3) # Int64, Int64
6
Σύμφωνα με το εγχειρίδιο, μια συνάρτηση είναι ένα αντικείμενο που αντιστοιχίζει μια πλειάδα ορισμάτων σε μια τιμή επιστροφής.
Συνήθως σκεφτόμαστε τα ορίσματα της συνάρτησης στο f(2, 3) ως δύο ξεχωριστά στοιχεία, αλλά ο μεταγλωττιστής τα βλέπει ως μία πλειάδα.
Αυτό ισχύει ακόμη και για ένα μόνο όρισμα (αν και πρέπει να προσθέσουμε ένα κόμμα για να γίνει αυτό πιο σαφές):
julia> typeof( (2, 3) )
Tuple{Int64, Int64}
julia> typeof( (2,) )
Tuple{Int64}
Βλέποντας τη συνάρτηση ως μαύρο κουτί, μπορούμε να πούμε ότι μπαίνει μια πλειάδα και βγαίνει μια τιμή επιστροφής. Πιο τυπικά, η πλειάδα αντιστοιχίζεται σε μια τιμή επιστροφής.
Αυτό αναφέρεται συνήθως ως "πολυμορφισμός"
Αυτό αποτελεί σημαντικό θέμα στην επιστήμη των υπολογιστών εδώ και αρκετές δεκαετίες, και έχουν υιοθετηθεί διαφορετικές προσεγγίσεις.
Η προσέγγιση της Julia είναι ασυνήθιστη, αν και όχι μοναδική.
+ είναι μια συνάρτηση με 161 μεθόδους αυτή τη στιγμή).... μπορεί εν μέρει να το παρακάμψει).dispatched με τα δεδομένα ορίσματα και επιστρέφει ένα αποτέλεσμα.MethodError.Σημείωσε ότι η Julia έχει δυναμική τυποποίηση, οπότε η αποστολή μεθόδων γίνεται σε χρόνο εκτέλεσης.
Η προηγούμενη ενότητα είχε πολλά λόγια, οπότε ας μεταφράσουμε αυτές τις ιδέες σε κώδικα. Για λόγους απλότητας, οι παρακάτω συναρτήσεις έχουν μόνο ένα όρισμα, αλλά δεν είναι σπάνιο να βρεις συναρτήσεις με δεκάδες ορίσματα (όπως σε πακέτα σχεδίασης γραφημάτων ή στη μηχανική μάθηση).
Ξεκίνα ορίζοντας μερικούς προσαρμοσμένους τύπους, για να αναπαραστήσεις γεωμετρικά σχήματα.
julia> abstract type Shape end
julia> struct Circle <: Shape
radius::Float64
end
julia> struct Square <: Shape
side::Float64
end
julia> struct Rectangle <: Shape
length::Float64
width::Float64
end
Στη συνέχεια μπορούμε να ορίσουμε μεθόδους για να υπολογίζουμε το εμβαδόν κάθε σχήματος. Κάθε μία παίρνει ένα όρισμα, ενός διαφορετικού συγκεκριμένου, οριζόμενου από τον χρήστη τύπου σε κάθε περίπτωση.
julia> area(c::Circle) = π * c.radius^2
area (generic function with 1 method)
julia> area(s::Square) = s.side^2
area (generic function with 2 methods)
julia> area(r::Rectangle) = r.length * r.width
area (generic function with 3 methods)
Τώρα μπορούμε να δημιουργήσουμε μερικά σχήματα και να υπολογίσουμε τα εμβαδά, βασιζόμενοι στον μηχανισμό αποστολής της Julia για να χρησιμοποιήσει τον σωστό μαθηματικό τύπο για το καθένα.
julia> circle = Circle(2.3)
Circle(2.3)
julia> area(circle) # call signature Tuple{Circle}
16.619025137490002
julia> square = Square(1.6)
Square(1.6)
julia> area(square) # call signature Tuple{Square}
2.5600000000000005
julia> rectangle = Rectangle(1.4, 2.1)
Rectangle(1.4, 2.1)
julia> area(rectangle) # call signature Tuple{Rectangle}
2.94
Οι υπογραφές των μεθόδων μπορούν να δέχονται εικονικές τιμές τύπων, περίπου με τον ίδιο τρόπο όπως οι παραμετρικοί τύποι.
Η σύνταξη είναι χαρακτηριστική, καθώς απαιτεί μια ρήτρα where:
julia> same_type(v::Vector{T}, x::T) where {T} = true
same_type (generic function with 1 method)
julia> same_type([1, 2], 3)
true
julia> same_type([1, 2], "three")
ERROR: MethodError: no method matching same_type(::Vector{Int64}, ::String)
Το παραπάνω παράδειγμα απαιτεί και τα δύο ορίσματα να χρησιμοποιούν τον ίδιο τύπο T, όποιος κι αν είναι αυτός.
Μπορούμε να γίνουμε πιο συγκεκριμένοι βάζοντας έναν περιορισμό στο T:
julia> same_type(v::Vector{T}, x::T) where {T<:Number} = true
same_type (generic function with 2 methods)
Με αυτή τη μέθοδο, μόνο πανομοιότυποι αριθμητικοί τύποι επιστρέφουν true.
Είναι δυνατές πολλαπλές εικονικές μεταβλητές, χωρισμένες με κόμμα στη ρήτρα where.
julia> myfunc(a::S, b::T) where {S, T} = true
myfunc (generic function with 1 method)
Η πολλαπλή αποστολή είναι πολύ ευέλικτη και εξαιρετικά αποδοτική στη Julia, οπότε χρησιμοποιείται ευρύτατα.
Όπως υποδηλώνει το παράδειγμα των 161 μεθόδων για το +, οι προγραμματιστές της Julia στοχεύουν να ορίζουν μεθόδους με στενά καθορισμένους τύπους.
Η πρόσθεση δύο ακεραίων είναι πολύ διαφορετική από την πρόσθεση δύο αριθμών κινητής υποδιαστολής στο επίπεδο του bytecode, και η πρόσθεση ενός ακεραίου σε έναν αριθμό κινητής υποδιαστολής προσθέτει μια επιπλέον επιπλοκή, την προαγωγή τύπων.
Όσο πιο συγκεκριμένος είναι ο τύπος, τόσο περισσότερες βελτιστοποιήσεις μπορεί να κάνει ο μεταγλωττιστής: κάτι που έχει οριακή σημασία σε μια απλή άσκηση στο Exercism, αλλά είναι ζωτικής σημασίας για την απόδοση στις μεγάλες αριθμητικές προσομοιώσεις για τις οποίες έχει σχεδιαστεί η Julia.
Αν ξέρεις τι εισόδους θα λάβει μια μέθοδος, πες το στον μεταγλωττιστή!
Σε αυτή την άσκηση θα υλοποιήσεις μια προσομοίωση συναντήσεων. Έτσι θα εξοικειωθείς με τα βασικά της πολλαπλής αποστολής, του βασικού παραδείγματος της Julia.
For this exercise, all function definitions should be a single-line declaration with no if/else logic.
Γενικά, οι συναντήσεις περιλαμβάνουν μια οντότητα a που συναντά μια άλλη οντότητα b και αντιδρά σε αυτήν.
encounter(a, b) = "$(name(a)) meets $(name(b)) and $(meets(a, b))."
Στην αρχή, θα προσομοιώσουμε τι συμβαίνει όταν συναντιούνται γάτες και σκύλοι.
Πρώτα πρόσθεσε έναν αφηρημένο τύπο Pet.
Στη συνέχεια όρισε τους τύπους Cat και Dog ως υποτύπους του Pet.
Ο καθένας έχει ένα μόνο πεδίο name.
Υλοποίησε μια συνάρτηση name() που επιστρέφει το όνομα ενός κατοικίδιου.
julia> fido = Dog("Fido")
Dog("Fido")
julia> name(fido)
"Fido"
Υλοποίησε μεθόδους meets() για τις παρακάτω συναντήσεις:
julia> ginger = Cat("Ginger")
Cat("Ginger")
julia> meets(ginger, fido)
"hisses"
Υλοποίησε τη συνάρτηση encounter() με μία μέθοδο, ώστε να αναφέρει τι συμβαίνει όταν συναντιούνται δύο οντότητες με όνομα.
julia> encounter(ginger, fido)
"Ginger meets Fido and hisses."
Τι συμβαίνει αν στον περίπατό τους συναντήσουν ένα διαφορετικό κατοικίδιο, όπως ένα άλογο;
julia> bella = Horse("Bella")
Horse("Bella")
julia> encounter(fido, bella)
"Fido meets Bella and is cautious."
Υπάρχουν πολλά άλλα πράγματα που μπορεί να συναντήσουν τα κατοικίδια και που δεν είναι κατοικίδια: αυτοκίνητα, άνθρωποι, φυτά, φυσικές καταστροφές, αστεροειδείς… Τι συμβαίνει τότε;
julia> colnago = Bicycle("Colnago")
Bicycle("Colnago")
julia> encounter(ginger, colnago)
"Ginger meets Colnago and runs away."
Υπάρχουν πολλές άλλες συναντήσεις που θα μπορούσαν να συμβούν στην προσομοίωσή μας. Ένα αυτοκίνητο συναντά έναν σκύλο, δύο ηλεκτρόνια συναντιούνται μεταξύ τους και αλληλεπιδρούν…
Είναι αδύνατο να καλύψουμε όλες τις συναντήσεις εκ των προτέρων, οπότε θα υλοποιήσουμε μια γενική εφεδρεία.
julia> γ1 = Photon("γ1")
Photon("γ1")
julia> γ2 = Photon("γ2")
Photon("γ2")
julia> encounter(γ1, γ2)
"γ1 meets γ2 and nothing happens."
# This is true, photons just pass through each other.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Julia με 35 έννοιες128 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.