Özellikler yakın zamanda Mojo'ya tanıtıldı, bu yüzden onları deneyeyim diye düşündüm. Şu anda yerleşik özellikler arasında CollectionElement , Copyable , Destructable , Intable , Movable , Sized ve Stringable yer alıyor ("-able" son eki bu adlandırma kurallarında yer alıyor gibi görünüyor!).
Özellikler yapılar üzerinde çalışır. Bir özelliği uygulamak için, özellikle eşleşen bir yapıya bir yöntem eklemeniz yeterlidir; daha sonra özellik adını parametre olarak iletin:
@value struct Duck(Quackable): fn quack(self): print("Quack!")
Mojo'daki @value
dekoratörü __init__()
, __copyinit__()
ve __moveinit__()
gibi yaşam döngüsü yöntemlerini yapıya ekleyerek, bunları kendimiz eklemek zorunda kalmadığımız için hayatımızı biraz basitleştirir.
Mojo'daki özellikler henüz yöntemler için varsayılan uygulamaları desteklememektedir, dolayısıyla yukarıdaki Quackable
yönteminin gövdesinde ...
bulunmaktadır. Python'dakiyle aynı etkiye sahip olacak pass
da kullanabilirsiniz.
Farklı isme rağmen Mojo'daki özelliklere yönelik temel yaklaşım bana Go arayüzlerini hatırlatıyor. Go'da bir Duck
yapısı tanımlayacak ve şöyle bir Quackable
arayüzü uygulayacaksınız:
type Quackable interface { quack() }
Ve bu arayüzü karşılayan bir yapı oluşturmak için:
type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") }
Bunu bir Mojo uygulamasıyla karşılaştırın:
trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!")
Mojo versiyonunun daha da kısa olduğunu düşünüyorum; quack()
yöntemi tanımını Duck
yapı tipinin içine yerleştiriyor. Yapılar üzerinde çalışmanın yanı sıra, Mojo'daki özellikler, tıpkı Go'da olduğu gibi, bir implements
anahtar sözcüğü gerektirmez.
İlginç bir şekilde, Mojo belgelerinde varsayılan uygulamalara henüz izin verilmediğine, yani bunlara gelecekte izin verilebileceğine dikkat çekiliyor. Bu, Mojo'nun, bir arayüzü uygulamaya değil, tatmin eden yapılara odaklanan Go'dan farklı bir yaklaşım izleyebileceği anlamına gelir.
Go'da varsayılan yöntem uygulamalarına gerek yoktur ve arayüz/özellik uygulaması Go'da mevcut olmayan bir kavram olduğundan benzer bir etki gömme ile elde edilir. Mojo'da işler farklı olabilir.
Özelliklerin ve arayüzlerin değeri, kodu yeniden kullanılabilir kılmaktır. Mesela Mojo’da özellik tiplerini kabul eden fonksiyonlar yazabilirsiniz…
fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack()
Ve sonra girdiyle aynı özelliği uygulayan farklı yapıları aktarın; her şey işe yarayacak! Örneğin, burada Quackable
uygulayan bir Goose
yapısı var:
@value struct Goose(Quackable): fn quack(self): print("Honk!")
Ve burada, hem Goose
hem de Duck
make_it_quack
çağırmak için (unutmayın, programınıza giriş noktası olarak Mojo'daki main
işlevine ihtiyacınız vardır):
def main(): make_it_quack(Duck()) make_it_quack(Goose())
Bunun çıktısı olacak
Quack! Honk!
make_it_quack
işlevine Quackable
özelliğini uygulamayan bir şeyi (örneğin StealthCow
aktarmaya çalışırsam program derlenmez:
@value struct StealthCow(): pass
make_it_quack(StealthCow())
Aşağıdaki hata mesajı daha açıklayıcı olabilirdi; belki Mojo ekibi bunu geliştirebilir?
error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified
Goose
quack
yöntemini kaldırırsam da aynısı olur; burada güzel, açıklayıcı bir hata alıyoruz:
struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented
Bu tür statik tip kontrolünün saf Python yaklaşımına göre avantajı, hataları kolayca yakalayabilmemiz ve kodun derlenmeyeceği için hataların üretime gönderilmesini önleyebilmemizdir.
Mojo'daki özellikler zaten kalıtımı desteklemektedir, dolayısıyla "Quackable" özelliğimiz "Audible" özelliğini şu şekilde genişletebilir:
trait Audible: fn make_sound(self): ...
trait Quackable(Audible): fn quack(self): ...
Bu, Duck
yapısının Quackable
özelliğine uyum sağlamak için hem quack
hem de make_sound
uygulaması gerektiği anlamına gelir.
Bu, Go'daki "Arayüz yerleştirme" kavramına benzer; burada diğer arayüzlerden miras alan yeni bir arayüz oluşturmak için ana arayüzleri şu şekilde gömebilirsiniz:
type Quackable interface { Audible // includes methods of Audible in Quackable's method set }
Özellikler ayrıca bir yapının örneğini oluşturmadan çalışan statik yöntemleri de kabul eder:
trait Swims: @staticmethod fn swim(): ... @value struct Duck(Quackable, Swims): fn quack(self): print("Quack!") @staticmethod fn swim(): print("Swimming")
fn make_it_swim[T: Swims](): T.swim()
Bunun gibi statik bir yöntem çağırırsınız:
def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim()
Hangisi çıktı verecek:
Quack! Honk! Swimming
Son yöntem çağrısında Duck örneğinin oluşturulmadığına dikkat edin. Python'daki statik yöntemler bu şekilde çalışır ve nesne yönelimli programlamadan biraz ayrılır. Mojo bu Python işlevselliğini temel alır.
İlginç bir şekilde, herhangi bir türün geçişine izin veren ve Go Generics'in tanıtılmasından önce Go topluluğu arasında popüler olan boş arayüzlü Go hilesi interface{}
, Mojo tipi fn
işleviyle çalışmayacaktır.
Yapınız, __len__
veya __str__
gibi yaşam döngüsü yöntemlerinden en az birini uygulamalıdır; bu durumda, özellik türlerini kabul eden işlevlerle kullanılmak üzere Sized
veya Stringable
ile uyumlu olmasını sağlar.
Bu aslında gerçek bir sınırlama değildir ve Mojo'da çok mantıklıdır, çünkü Mojo'da dinamik yazmaya ihtiyacınız varsa, daha esnek olan def
işlevine geri dönebilir ve ardından bilinmeyenlerle çalışmak için olağan Python büyüsünü uygulayabilirsiniz. türleri.
Daha katı Mojo fn
işlevleri aynı zamanda DynamicVector
gibi türleri kullanan genel yapılar üzerinde de çalışır; bununla ilgili daha fazlasını buradan okuyun.
Gördüğüm diğer bir sınırlama, özelliklerden ziyade yapılarla ve bunların yöntemleriyle ilgilidir ve Temiz Mimarinin uygulanmasının/kodun farklı parçalara ayrılmasının önünde potansiyel bir engeldir.
Go vs. Mojo yapı yöntemi tanımına sahip önceki örneklerden birini düşünün:
type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") }
@value struct Duck(Quackable): fn quack(self): print("Quack!")
Mojo örneği, Python benzeri bir söz dizimini takip ederek, yöntem tanımını doğrudan yapının içine yerleştirir; Go sürümü ise onu yapının kendisinden ayırmasına izin verir. Bu versiyonda eğer çok tür ve metod içeren çok uzun bir yapıya sahipsem, okunması biraz daha zor olacaktır.
Ancak bu kritik bir fark değil, sadece dikkat edilmesi gereken bir şey.
Mojo özellikleri, dilin ilk günlerinde olmasına rağmen zaten oldukça kullanışlıdır. Go felsefesi tamamen basitliğe odaklanmış ve özellikleri minimumda tutmaya çalışsa da, gelecekte Mojo özelliklerine daha fazla işlevsellik eklendiğini göreceğiz, bu da onları daha da güçlü hale getirecek ve tonlarca farklı kullanım senaryosunu mümkün kılacak.