Mengapa bahasa yang dikelola dengan memori seperti Java, Javascript, dan C # mempertahankan kata kunci `baru`?


139

Kata newkunci dalam bahasa seperti Java, Javascript, dan C # menciptakan instance baru dari sebuah kelas.

Sintaks ini tampaknya telah diwarisi dari C ++, di mana newdigunakan secara khusus untuk mengalokasikan instance baru dari kelas di heap, dan mengembalikan pointer ke instance baru. Dalam C ++, ini bukan satu-satunya cara untuk membangun objek. Anda juga dapat membuat objek di stack, tanpa menggunakan new- dan pada kenyataannya, cara membangun objek ini jauh lebih umum di C ++.

Jadi, berasal dari latar belakang C ++, newkata kunci dalam bahasa seperti Java, Javascript, dan C # tampak alami dan jelas bagi saya. Kemudian saya mulai belajar Python, yang tidak memiliki newkata kunci. Dalam Python, sebuah instance dibangun hanya dengan memanggil konstruktor, seperti:

f = Foo()

Pada awalnya, ini terasa agak aneh bagi saya, sampai terlintas di benak saya bahwa tidak ada alasan bagi Python untuk memilikinya new, karena semuanya adalah sebuah objek sehingga tidak perlu disatukan antara berbagai sintaks konstruktor.

Tapi kemudian saya berpikir - apa gunanya newdi Jawa? Kenapa kita harus bilang Object o = new Object();? Kenapa tidak adil Object o = Object();? Dalam C ++ pasti ada kebutuhan new, karena kita perlu membedakan antara mengalokasikan di heap dan mengalokasikan di stack, tetapi di Jawa semua objek dibangun di heap, jadi mengapa bahkan memiliki newkata kunci? Pertanyaan yang sama bisa ditanyakan untuk Javascript. Dalam C #, yang saya tidak terlalu kenal, saya pikir newmungkin memiliki beberapa tujuan dalam hal membedakan antara tipe objek dan tipe nilai, tapi saya tidak yakin.

Bagaimanapun, menurut saya banyak bahasa yang muncul setelah C ++ hanya "mewarisi" newkata kunci - tanpa benar-benar membutuhkannya. Ini hampir seperti kata kunci peninggalan . Kami tampaknya tidak membutuhkannya untuk alasan apa pun, namun itu ada di sana.

Pertanyaan: Apakah saya benar tentang ini? Atau ada beberapa alasan kuat yang newperlu ada di C ++ - bahasa yang dikelola dengan memori yang diinspirasi seperti Java, Javascript dan C # tetapi tidak Python?


13
Pertanyaannya agak mengapa bahasa apa pun memiliki newkata kunci. Tentu saja saya ingin membuat variabel baru, kompiler bodoh! Sebuah baik bahasa akan menurut saya memiliki sintaks seperti f = heap Foo(), f = auto Foo().

7
Poin yang sangat penting untuk dipertimbangkan adalah seberapa akrab suatu bahasa pada pemrogram baru. Java secara eksplisit dirancang agar terlihat familier bagi programmer C / C ++.

1
@Lundin: Ini benar-benar hasil dari teknologi kompiler. Kompiler modern bahkan tidak memerlukan petunjuk; itu akan mencari tahu di mana harus meletakkan objek berdasarkan penggunaan aktual dari variabel itu. Yaitu ketika ftidak luput dari fungsinya, alokasikan ruang stack.
MSalters

3
@Lundin: Itu bisa, dan kenyataannya adalah JVM modern. Ini hanya masalah membuktikan bahwa GC dapat mengumpulkan objek fketika fungsi melampirkan kembali.
MSalters

1
Tidak konstruktif untuk bertanya mengapa suatu bahasa dirancang seperti itu, terutama jika desain itu meminta kita untuk mengetik empat karakter tambahan tanpa manfaat yang jelas? Apa yang saya lewatkan?
MatrixFrog

Jawaban:


94

Pengamatan Anda benar. C ++ adalah binatang yang rumit, dan newkata kunci itu digunakan untuk membedakan antara sesuatu yang dibutuhkan deletekemudian dan sesuatu yang akan secara otomatis direklamasi. Di Jawa dan C #, mereka menjatuhkan deletekata kunci karena pengumpul sampah akan mengurusnya untuk Anda.

Masalahnya kemudian mengapa mereka menyimpan newkata kunci? Tanpa berbicara dengan orang-orang yang menulis bahasa itu agak sulit untuk dijawab. Tebakan terbaik saya tercantum di bawah ini:

  • Secara semantik benar. Jika Anda terbiasa dengan C ++, Anda tahu bahwa newkata kunci membuat objek di heap. Jadi, mengapa mengubah perilaku yang diharapkan?
  • Ini menarik perhatian pada fakta bahwa Anda adalah instantiating objek daripada memanggil metode. Dengan rekomendasi gaya kode Microsoft, nama metode dimulai dengan huruf kapital sehingga dapat terjadi kebingungan.

Ruby berada di suatu tempat di antara Python dan Java / C # dalam penggunaannya new. Pada dasarnya Anda instantiate objek seperti ini:

f = Foo.new()

Itu bukan kata kunci, ini metode statis untuk kelas. Apa artinya itu adalah bahwa jika Anda menginginkan singleton, Anda dapat mengganti implementasi default new()untuk mengembalikan instance yang sama setiap waktu. Ini belum tentu direkomendasikan, tetapi itu mungkin.


5
Sintaks Ruby terlihat bagus dan konsisten! Kata kunci baru dalam C # juga digunakan sebagai batasan tipe generik untuk tipe dengan konstruktor default.
Steven Jeuris

3
Iya. Kami tidak akan berbicara tentang obat generik. Versi generik Java dan C # sangat tumpul. Tidak mengatakan C ++ lebih mudah untuk dipahami. Generik benar-benar hanya berguna untuk bahasa yang diketik secara statis. Bahasa yang diketik secara dinamis seperti Python, Ruby, dan Smalltalk tidak membutuhkannya.
Berin Loritsch

2
Delphi memiliki sintaksis yang mirip dengan Ruby, kecuali bahwa konstruktor biasanya (tetapi hanya oleh konvensi) yang disebut Buat (`f: = TFoo.Create (param1, param2 ...) '
Gerry

Di Objective-C, allocmetode (kira-kira sama dengan Ruby new) juga hanya metode kelas.
mipadi

3
Dari perspektif desain bahasa, kata kunci buruk karena berbagai alasan, terutama Anda tidak dapat mengubahnya. Di sisi lain menggunakan kembali konsep metode lebih mudah, tetapi perlu perhatian saat menguraikan dan menganalisis. Smalltalk dan kerabatnya menggunakan pesan, C ++ dan itskin di sisi lain kata kunci
Gabriel Ščerbák

86

Singkatnya, Anda benar. Kata kunci baru berlebihan dalam bahasa seperti Java dan C #. Berikut adalah beberapa wawasan dari Bruce Eckel yang merupakan anggota C ++ Standard Comitee pada 1990-an dan kemudian menerbitkan buku-buku tentang Jawa: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

perlu ada beberapa cara untuk membedakan objek tumpukan dari objek tumpukan. Untuk mengatasi masalah ini, kata kunci baru diambil dari Smalltalk. Untuk membuat objek stack, Anda cukup mendeklarasikannya, seperti pada Cat x; atau, dengan argumen, Cat x ("mittens") ;. Untuk membuat objek tumpukan, Anda menggunakan yang baru, seperti pada Cat baru; atau Kucing baru ("sarung tangan") ;. Mengingat kendala, ini adalah solusi yang elegan dan konsisten.

Masukkan Java, setelah memutuskan bahwa semuanya C ++ dilakukan dengan buruk dan terlalu rumit. Ironisnya di sini adalah bahwa Jawa dapat dan memang membuat keputusan untuk membuang alokasi tumpukan (dengan sengaja mengabaikan bencana primitif, yang telah saya bahas di tempat lain). Dan karena semua objek dialokasikan pada heap, tidak perlu membedakan antara stack dan alokasi heap. Mereka bisa dengan mudah mengatakan Cat x = Cat () atau Cat x = Cat ("mittens"). Atau bahkan lebih baik, inferensi tipe tergabung untuk menghilangkan pengulangan (tapi itu - dan fitur lain seperti penutupan - akan memakan waktu "terlalu lama" sehingga kita terjebak dengan versi Java yang biasa-biasa saja; inferensi tipe telah dibahas tetapi saya akan peluang awam itu tidak akan terjadi. Dan seharusnya tidak, mengingat masalah dalam menambahkan fitur baru ke Jawa).


2
Stack vs Heap ... berwawasan luas, tidak akan pernah memikirkannya.
WernerCD

1
"inferensi tipe telah dibahas tetapi saya akan mengatakan kemungkinan itu tidak akan terjadi.", :) Yah, pengembangan Java masih berjalan maju. Menarik bahwa ini adalah satu-satunya unggulan untuk Java 10 sekalipun. Kata varkunci sama sekali tidak sebagus autodi C ++, tapi saya harap ini akan meningkat.
patrik

15

Ada dua alasan yang bisa saya pikirkan:

  • new membedakan antara objek dan primitif
  • The newsintaks sedikit lebih mudah dibaca (IMO)

Yang pertama sepele. Saya pikir tidak sulit untuk membedakan keduanya. Yang kedua adalah contoh dari poin OP, yang newmerupakan semacam redundan.

Mungkin ada konflik namespace; mempertimbangkan:

public class Foo {
   Foo() {
      // Constructor
   }
}

public class Bar {
   public static void main(String[] args) {
      Foo f = Foo();
   }

   public Foo Foo() {
      return Foo();
   }
}

Anda bisa, tanpa terlalu banyak imajinasi, dengan mudah berakhir dengan panggilan rekursif tanpa batas. Kompiler harus menegakkan kesalahan "Nama fungsi sama dengan objek". Ini benar-benar tidak ada salahnya, tetapi jauh lebih mudah digunakan new, yang menyatakan, Go to the object of the same name as this method and use the method that matches this signature.Jawa adalah bahasa yang sangat sederhana untuk diurai, dan saya menduga ini membantu di daerah itu.


1
Bagaimana hal ini berbeda dengan rekursi tak terbatas lainnya?
DeadMG

Itu ilustrasi yang cukup baik dari hal buruk yang bisa terjadi, meskipun pengembang yang melakukan itu akan memiliki beberapa masalah mental serius.
Tjaart

10

Java, untuk satu, masih memiliki dikotomi C ++: tidak cukup segala sesuatu adalah obyek. Java memiliki tipe bawaan (mis., charDan int) yang tidak harus dialokasikan secara dinamis.

Itu tidak berarti bahwa newitu benar-benar diperlukan di Jawa - set tipe yang tidak perlu dialokasikan secara dinamis adalah tetap dan diketahui. Ketika Anda mendefinisikan suatu objek, kompiler dapat mengetahui (dan, pada kenyataannya, memang tahu) apakah itu nilai sederhana charatau objek yang harus dialokasikan secara dinamis.

Saya akan menahan diri (lagi-lagi) untuk tidak memberikan pendapat tentang apa artinya ini tentang kualitas desain Java (atau kekurangannya, seperti halnya).


Lebih penting lagi, tipe primitif tidak memerlukan panggilan konstruktor sama sekali - mereka juga ditulis sebagai literal, berasal dari operator, atau dari beberapa fungsi primitive-return. Anda sebenarnya juga membuat string (yang ada di heap) tanpa a new, jadi ini bukan alasannya.
Paŭlo Ebermann

9

Di JavaScript Anda membutuhkannya karena konstruktornya terlihat seperti fungsi normal, jadi bagaimana seharusnya JS tahu bahwa Anda ingin membuat objek baru jika bukan karena kata kunci baru?


4

Menariknya, VB memang membutuhkannya - perbedaan antara

Dim f As Foo

yang mendeklarasikan variabel tanpa menugaskannya, setara dengan Foo f;dalam C #, dan

Dim f As New Foo()

yang mendeklarasikan variabel dan memberikan instance baru dari kelas, yang setara dengan var f = new Foo();dalam C #, adalah perbedaan substantif. Di pre-.NET VB, Anda bahkan dapat menggunakan Dim f As Fooatau Dim f As New Foo- karena tidak ada kelebihan pada konstruktor.


4
VB tidak memerlukan versi singkat, itu hanya singkatan. Anda juga dapat menulis versi yang lebih panjang dengan tipe dan konstruktor Dim f As Foo = New Foo (). Dengan Option Infer On, yang merupakan hal terdekat dengan var C #, Anda dapat menulis Dim f = New Foo () dan kompiler menyimpulkan tipe f.
MarkJ

2
... dan Dim f As Foo()menyatakan sebuah array yang dari Foos. Oh sukacita! :-)
Heinzi

@ MarkJ: Di vb.net, singkatan itu setara. Di vb6, tidak. Di vb6, Dim Foo As New Barakan secara efektif membuat Fooproperti yang akan membangun a Barjika dibaca saat (yaitu Nothing). Perilaku ini tidak terbatas terjadi sekali saja; pengaturan Foountuk Nothingdan kemudian membacanya akan membuat yang baru Bar.
supercat

4

Saya suka banyak jawaban dan hanya ingin menambahkan ini:
Itu membuat pembacaan kode lebih mudah
Bukan karena situasi ini akan sering terjadi, tetapi pertimbangkan Anda ingin metode dalam nama kata kerja yang berbagi nama yang sama dengan kata benda. C # dan bahasa lain secara alami memiliki kata objectdilindungi undang-undang. Tetapi jika tidak maka akan Foo = object()menjadi hasil dari pemanggilan metode objek atau akankah itu menjadi contoh objek baru. Semoga kata tanpa newkata kunci memiliki perlindungan terhadap situasi ini, tetapi dengan memiliki persyaratan newkata kunci sebelum memanggil konstruktor, Anda mengizinkan adanya metode dengan nama yang sama sebagai objek.


4
Boleh dibilang, harus tahu ini pertanda buruk.
DeadMG

1
@DeadMG: "Bisa dibilang" berarti "Saya akan berdebat sampai bar ditutup" ...
Donal Fellows

3

Ada banyak hal peninggalan dalam banyak bahasa pemrograman. Kadang-kadang ada di sana dengan desain (C ++ dirancang agar kompatibel dengan mungkin dengan C), kadang-kadang, hanya ada Untuk contoh yang lebih mengerikan, pertimbangkan seberapa jauh switchpernyataan C yang rusak disebarkan.

Saya tidak tahu Javascript dan C # cukup baik untuk mengatakan, tapi saya tidak melihat alasan newdi Jawa kecuali bahwa C ++ memilikinya.


Saya pikir JavaScript dirancang untuk "terlihat seperti Java" sebanyak mungkin, bahkan ketika itu melakukan sesuatu yang sangat tidak seperti Jawa. x = new Y()dalam JavaScript tidak instantiate yang Ykelas, karena JavaScript tidak memiliki kelas. Tapi sepertinya Java sehingga membawa newkata kunci maju dari Jawa, yang tentu saja meneruskannya dari C ++.
MatrixFrog

+1 untuk sakelar yang rusak (dengan harapan Anda memperbaikinya: D).
maaartinus

3

Dalam C # itu memungkinkan jenis terlihat dalam konteks di mana mereka mungkin dikaburkan oleh anggota. Ini memungkinkan Anda untuk memiliki properti dengan nama yang sama dengan tipenya. Pertimbangkan yang berikut ini,

class Address { 
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

class Person {
    public string Name { get; set; }
    public Address Address { get; set; }

    public void ChangeStreet(string newStreet) {
        Address = new Address { 
            Street = newStreet, 
            City = Address.City,
            State = Address.State,
            Zip = Address.Zip
        };
    }
}

Perhatikan penggunaan newmemungkinkan untuk menjadi jelas bahwa Addressini Addressbukan tipe Addressanggota. Ini memungkinkan anggota untuk memiliki nama yang sama dengan tipenya. Tanpa ini, Anda perlu awalan nama jenis untuk menghindari tabrakan nama, seperti CAddress. Ini sangat disengaja karena Anders tidak pernah menyukai notasi hungaria atau yang serupa dan ingin C # dapat digunakan tanpa itu. Fakta itu juga akrab adalah bonus ganda.


w00t, dan Address kemudian dapat diturunkan dari antarmuka yang disebut Address .. saja, tidak. Jadi itu tidak banyak manfaatnya, bisa menggunakan kembali nama yang sama dan mengurangi dari keterbacaan umum kode Anda. Jadi dengan menjaga I untuk antarmuka, tetapi menghapus C dari kelas, mereka hanya menciptakan bau tidak enak tanpa manfaat praktis.
gbjbaanb

Saya ingin tahu bahwa mereka tidak menggunakan "Baru", bukan "baru". Seseorang bisa memberi tahu mereka ada juga huruf kecil, yang semacam ini memecahkan masalah ini.
maaartinus

Semua kata yang dilindungi undang-undang dalam bahasa turunan C, seperti C #, adalah huruf kecil. Tata bahasanya membutuhkan kata khusus di sini untuk menghindari ambiguitas sehingga penggunaan "baru" bukan "Baru".
chuckj

@ gbjbaanb Tidak ada bau. Itu selalu benar-benar jelas apakah Anda merujuk pada jenis atau properti / anggota atau fungsi. Bau sebenarnya adalah ketika Anda memberi nama namespace sama dengan kelas. MyClass.MyClass
Stephen

0

Menariknya, dengan munculnya penerusan yang sempurna, non-penempatan newjuga tidak diperlukan di C ++. Jadi, bisa dibilang, itu tidak lagi diperlukan dalam bahasa yang disebutkan di atas.


4
Apa yang Anda meneruskan ke ? Pada akhirnya, entah bagaimana std::shared_ptr<int> foo();harus menelepon new int.
MSalters

0

Saya tidak yakin apakah desainer Java mempertimbangkan hal ini, tetapi ketika Anda menganggap objek sebagai rekaman rekursif , maka Anda dapat membayangkan bahwa Foo(123)itu tidak mengembalikan objek tetapi fungsi yang fixpoint-nya adalah objek yang sedang dibuat (mis. Fungsi yang akan mengembalikan objek jika diberikan sebagai argumen objek yang sedang dibangun). Dan tujuannya newadalah untuk "mengikat simpul" dan mengembalikan fixpoint itu. Dengan kata lain newakan membuat "objek-to-be" sadar akan hal itu self.

Pendekatan semacam ini dapat membantu memformalkan pewarisan misalnya: Anda dapat memperluas fungsi-objek dengan fungsi-objek lain dan akhirnya memberikan mereka kesamaan selfdengan menggunakan new:

cp = new (Colored("Blue") & Line(0, 0, 100, 100))

Di sini &menggabungkan dua fungsi objek.

Dalam hal ini newdapat didefinisikan sebagai:

def new(objectFunction) {
    actualObject = objectFunction(actualObject)
    return actualObject
}

-2

Untuk mengizinkan 'nihil'.

Seiring dengan alokasi berdasarkan tumpukan, Java menganut penggunaan objek nil. Bukan hanya sebagai artefak, tetapi sebagai fitur. Ketika objek dapat memiliki status nil, maka Anda harus memisahkan deklarasi dari inisialisasi. Maka kata kunci baru.

Dalam C ++ garis seperti

Person me;

Akan langsung memanggil konstruktor default.


4
Um, apa yang salah dengan Person me = Nilatau memiliki Person memenjadi Nil secara default dan menggunakan Person me = Person()untuk non-Nil? Maksud saya adalah bahwa sepertinya Nil bukan merupakan faktor dalam keputusan ini ...
Andres F.

Anda benar juga. Person me = Person (); akan bekerja dengan baik.
Kris Van Bael

@AndresF. Atau bahkan lebih sederhana dengan Person memenjadi nihil dan Person me()memanggil konstruktor default. Jadi, Anda akan selalu membutuhkan tanda kurung untuk memohon apa pun, dan dengan demikian menyingkirkan satu ketidakteraturan C ++.
maaartinus

-2

Alasan mengapa Python tidak membutuhkannya adalah karena sistem tipenya. Pada dasarnya, ketika Anda melihat foo = bar()dengan Python, tidak masalah apa pun bar()metode yang sedang dipanggil, kelas yang sedang dipakai, atau objek functor; dalam Python, tidak ada perbedaan mendasar antara functor dan fungsi (karena metode adalah objek); dan Anda bahkan bisa berdebat bahwa kelas yang dipakai adalah kasus khusus dari functor. Dengan demikian, tidak ada alasan untuk membuat perbedaan buatan dalam apa yang terjadi (terutama karena manajemen memori sangat tersembunyi di Python).

Dalam bahasa dengan sistem pengetikan yang berbeda, atau di mana metode dan kelas bukan objek, ada kemungkinan perbedaan konseptual yang lebih kuat antara membuat objek dan memanggil fungsi atau functor. Jadi, bahkan jika kompiler / juru bahasa tidak memerlukan newkata kunci, dapat dimengerti bahwa itu dapat dipertahankan dalam bahasa yang belum melanggar semua batasan yang dimiliki Python.


-4

Sebenarnya di Jawa ada perbedaan saat menggunakan Strings:

String myString = "myString";

berbeda dengan

String myString = new String("myString");

Andaikan string "myString"tidak pernah digunakan sebelumnya, pernyataan pertama membuat objek String tunggal di kumpulan String (area khusus heap) sedangkan pernyataan kedua membuat dua objek, satu di area tumpukan normal untuk objek dan lainnya di string pool . Sangat jelas bahwa pernyataan kedua tidak berguna dalam skenario khusus ini, tetapi diizinkan oleh bahasa.

Ini berarti bahwa baru menjamin bahwa objek dibuat di area khusus heap, dialokasikan untuk instantiasi objek normal, tetapi mungkin ada cara lain untuk instantiasi objek tanpa itu; yang di atas adalah contoh, dan ada ruang tersisa untuk diperpanjang.


2
Tapi ini bukan pertanyaannya. Pertanyaannya adalah "mengapa tidak String myString = String("myString");?" (perhatikan tidak adanya newkata kunci)
Andres F.

@AndresF. Agak jelas imho ... itu sah untuk memiliki metode bernama String yang mengembalikan sebuah String dalam kelas String dan harus ada perbedaan antara memanggilnya dan memanggil konstruktor. Sesuatu seperticlass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman

3
Tapi itu tidak jelas. Pertama, memiliki metode reguler yang dinamai Stringbertentangan dengan konvensi gaya Java, jadi Anda bisa mengasumsikan metode apa pun yang dimulai dengan huruf besar adalah konstruktor. Dan kedua, jika kita tidak dibatasi oleh Java, maka Scala memiliki "kelas kasus" di mana konstruktornya terlihat persis seperti yang saya katakan. Jadi di mana masalahnya?
Andres F.

2
Maaf, saya tidak mengikuti argumen Anda. Anda secara efektif berdebat sintaks , tidak semantik, dan lagi pula pertanyaannya adalah tentang newuntuk bahasa dikelola . Saya telah menunjukkan bahwa newsama sekali tidak diperlukan (misalnya Scala tidak memerlukannya untuk kelas kasus) dan juga bahwa tidak ada ambiguitas (karena Anda benar-benar tidak dapat memiliki metode yang disebutkan MyClassdi kelas MyClass). Tidak ada ambiguitas untuk String s = String("hello"); tidak mungkin hal lain selain konstruktor. Dan akhirnya, saya tidak setuju: konvensi kode ada untuk meningkatkan dan mengklarifikasi sintaksis, yang merupakan alasan Anda!
Andres F.

1
Jadi itu argumenmu? "Desainer Java membutuhkan newkata kunci karena jika tidak sintaksis Java akan berbeda"? Saya yakin Anda dapat melihat kelemahan argumen itu;) Dan ya, Anda terus membahas sintaks, bukan semantik. Juga, kompatibilitas dengan apa? Jika Anda mendesain bahasa baru, seperti Java, Anda sudah tidak kompatibel dengan C dan C ++!
Andres F.

-8

Dalam C # baru penting untuk membedakan antara objek yang dibuat pada stack vs heap. Seringkali kita membuat objek pada stack untuk kinerja yang lebih baik. Lihat, ketika sebuah objek pada stack keluar dari ruang lingkup, ia langsung menghilang ketika stack terurai, seperti primitif. Tidak perlu bagi pengumpul sampah untuk melacaknya, dan tidak ada overhead tambahan dari manajer memori untuk mengalokasikan ruang yang diperlukan pada heap.


5
sayangnya Anda membingungkan C # dengan C ++.
gbjbaanb
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.