Python ile fonksiyonel programlama yapabilirsek, belirli bir işlevsel programlama diline ihtiyacımız var mı? [kapalı]


22

Jeneratör ve lambda kullanarak Python ile fonksiyonel programlama yapabiliriz. Aynı şeyi Ruby ile de başarabilirsin.

Öyleyse soru şudur: Erlang, Haskell ve Scheme gibi özel fonksiyonel programlama dillerine neden ihtiyacımız var? Bu özel fonksiyonel programlama dillerinin sağladığı farklı bir şey var mı? Fonksiyonel programlama için neden Python'u kullanamıyoruz?


30
hepsi python oluşturulmadan önce bile buralardaydı
Mahmoud Hossam

51
Ford Pinto bir arabadır. Neden Ferraris gibi hızlı arabalara ihtiyacımız var?
Martin York

11
Sınıfları ve şablonları kullanarak, C ++ 'da herhangi bir OO yapabiliriz. Neden Java ve Python oluşturuldu? Ne ekliyorlar?
9000

19
Tüm programlama dilleri (sadece bazı akademik araştırma dilleri sans) Turing'e eşdeğerdir, yani dilde ne yapabilirsiniz? Başka bir dilde yapabilirsiniz. Yani, düşünce bu trende takip, sadece tam dil Turing birine ihtiyacınız - sendmail.cf gibi söz;) okmij.org/ftp/Computation/#sendmail-Turing
Maglob

17
Bu dillerden herhangi birini tanıyorsanız, Python'un işlevsel programlamayı iyi yaptığını iddia edemezsiniz. Öyle değil. FP-ish şeylerinin payını içerecek kadar iyi yapıyor, ama daha iyi değil.

Yanıtlar:


20

Soruyu takdir ediyorum, çünkü şahsen hem Python hem de işlevsel programlama tarzının büyük bir hayranıyım. Python'da uzun bir geçmişe sahibim ve son zamanlarda Haskell'i öğrenmeye başladım, bu yüzden burada, bu diller arasındaki farklar hakkındaki kişisel deneyimlerime dayanarak, fonksiyonel bir bakış açısıyla bazı noktalar var.

Saflık

Fonksiyonların saflığını umursamıyor olsanız bile (yani yan etkilere sahip olmuyorsanız), prensip olarak kod okumanın ve bunun nedeninin ne kadar kolay olduğu üzerinde pratik bir etkisi vardır. Kendi Python işlevlerinizde saflığı korusanız bile, derleyicinin saflığı güçlendirmesinde ve en önemlisi de saflık ve değişmez veri yapıları üzerine inşa edilmiş standart kütüphaneye sahip olmanızda büyük bir fark vardır.

performans

Uygulama etki alanınızın ne olduğuna bağlı olarak performansı önemseyebilirsiniz veya umursamazsınız, ancak statik yazım ve garantili saflık, derleyiciye Python ve diğer dinamik dillerle karşılaştırıldığında daha fazla çalışmasını sağlar (PyPy'nin harika olduğunu kabul etmek zorunda kalmam gerekmesine rağmen) içlerinde ve örneğin LuaJIT mucizevi sınırındaki).

Tail-call Optimizasyonu

Performansla ilgili, ancak biraz farklı. Çalışma zamanı performansını çok fazla umursamasanız bile, kuyruk çağrısı optimizasyonuna sahip olmamak (özellikle kuyruk özyineleme için) algoritmaları Python'da yığın sınırlarına basmadan uygulayabileceğiniz yöntemleri sınırlar.

Sözdizimi

Bu, Python'u sadece işlevsel stilde kullanmak yerine "gerçek" işlevsel dillere bakmaya başlamamın en büyük nedeni. Python'un genel olarak çok etkileyici bir sözdizimine sahip olduğunu düşünmeme rağmen, işlevsel kodlamaya özgü bazı zayıf noktalar var. Örneğin:

  • Lambda fonksiyonları için sözdizimi oldukça ayrıntılı ve içerebilecekleri konusunda sınırlıdır
  • Fonksiyon kompozisyonu yani hiçbir sözdizimsel şeker f = g . hvsf = lambda *arg: g(h(*arg))
  • Kısmi uygulama için sözdizimsiz şeker yok, f = map gvs.f = functools.partial(map, g)
  • Yani yüksek mertebeden işlevlerinde infix operatörlerini kullanmaya yönelik hiçbir sözdizimsel şeker sum = reduce (+) lstvssum = reduce(operator.add, lst)
  • İşlev argümanları için yinelenen bitiş koşullarını ve bazı sınır durumlarını çok okunabilir sözdizimi ile ifade etmeyi kolaylaştıran kalıp eşleştirmesi veya korumaları yoktur.
  • Destekler, işlev çağrıları için hiçbir zaman isteğe bağlı değildir ve iç içe çağrılar için sözdizimsel şeker yoktur. Sanırım bu bir zevk meselesi ama özellikle işlevsel kodda, zincir işlev çağrıları için yaygın olduğunu biliyorum ve bu gösterime aşina olduğunuzda y = func1 $ func2 $ func3 xokumayı daha kolay buluyorum y = func1(func2(func3(x))).

28

Bunlar en önemli farklar:

Haskell

  • Tembel değerlendirme
  • Makine kodunu derler
  • Statik yazım, fonksiyonların saf olmasını sağlar
  • Tür çıkarımı

Haskell ve Erlang

  • Desen eşleştirme

Erlang

  • Eşzamanlılığın aktör modeli, hafif süreçler

düzen

  • Makrolar

Bütün diller

  • gerçek kapanışlar (yakutun kapanışları vardır, python'un tartışılabilir olup olmadığı, yorumlara bakın)
  • işlevsel bir programlama stiline uygun (standart koleksiyonlar, harita, filtre, katlama vb.) standart bir kütüphane
  • kuyruk özyineleme (bu işlevsiz bazı dillerde de bulunabilir)

Ayrıca, OO ve fonksiyonel programlamayı yeni bir şekilde birleştiren SML, Ocaml ve F # ve Scala gibi ML ailesindeki dillere bakmalısınız. Bütün bu dillerin benzersiz ilginç özellikleri var.


3
+1 iyi posta. Erlang'daki Haskell ve Hafif ağırlık işlemlerinde tip çıkarımı ekleyebilirsiniz.
Jonas

1
Python'da harita, filtre ve katlama (azaltma) var. "Gerçek kapanışlar" ile ilgili olarak: Gerçek bir kapatmayı tek bir ifadeden başka bir şey içerebilecek bir kapanış olarak tanımlarsanız, Haskell'in de gerçek kapanışları olmaz (ama elbette Haskell'in ifadeleri olmayan az şeyleri vardır ...) . Bununla birlikte, değişmez veri yapıları ile ilgili nokta, bunun için iyi bir + 1'dir. Ayrıca: kuyruk özyinelemesi (ve genellikle daha az özyinelemeli).
sepp2k

2
"Statik yazım işlevlerin saf olmasını sağlar" için +1. Saf ve saf olmayan işlevler arasında ayrım yapan bir tip sisteme sahip olmak çok güzel. (C ++ 'nın const do snot sayısı. :)
Macke

1
@btilly: Kapsamında olan bir değişkene atamadıysanız, kapatmayı düşünmezdim. Aksi takdirde, aynı numarayı kullanabildiğiniz için Java’nın da kapandığını söylemek zorunda kalırız.
Kim

3
Bir kapanışta, bir değişkene normalde kullandığım şekilde erişebilirim. Bu, Haskell'in ve Ruby'nin kapanışları için geçerlidir, ancak Python veya Java'nın zavallı yerine geçmez. Belki başka biri bizi erlang hakkında aydınlatabilir, o kadar iyi bilmiyorum.
Kim

19

"İşlevsel bir dilin" tam olarak ne olduğunu tanımlamak zor - listelediğiniz dillerin dışında, sadece Haskell tamamen işlevseldir (diğerleri bir tür hibrit yaklaşımı benimser). İşlevsel programlama için çok faydalı olan bazı dil özellikleri vardır, ancak Ruby ve Python'da FP için çok iyi ortamlar olması için yeterli ortamı yoktur. Önem sırasına göre kişisel kontrol listem:

  1. Birinci sınıf işlevler ve kapanışlar (Ruby, Python ve listelediğiniz diğerlerinin tümü buna sahiptir).
  2. Garantili kuyruk çağrısı optimizasyonu (Erlang, Haskell, Scala ve Scheme'de buna sahiptir, ancak Python, Ruby veya Clojure (henüz) yoktur).
  3. Dilde ve standart kütüphanelerde değişmezlik desteği (bu, listelediğiniz tüm "fonksiyonel dillerin" sahip olduğu büyük bir dildir (Scheme hariç) ancak Ruby ve Python yoktur).
  4. Referans düzeyinde saydam (veya saf) işlevler için dil düzeyinde destek (bildiğim kadarıyla şu anda yalnızca Haskell var).

(1) ihtiyacı açık olmalıdır - birinci sınıf fonksiyonlar olmadan yüksek dereceli fonksiyonlar son derece zordur. İnsanlar Ruby ve Python hakkında FP için iyi diller hakkında konuştukları zaman, genellikle bundan söz ederler. Bununla birlikte, bu özel özellik, bir dili FP için iyi yapmak için yeterlidir.

(2) Şema icat edildiğinden beri FP için geleneksel bir gereklilik olmuştur. TCO olmadan, FP'nin temel taşlarından biri olan derin özyinelemeyle programlamak mümkün değildir, çünkü yığın taşması elde edersiniz. Buna sahip olmayan tek "işlevsel" (popüler tanım) dil, Clojure'dir (JVM'nin kısıtlamaları nedeniyle), ancak Clojure, TCO'yu simüle etmek için çeşitli kesimlere sahiptir. (FYI, Ruby TCO uygulamaya özeldir , ancak Python özel olarak desteklememektedir .) TCO'nun garanti edilmesi gerekliliği, uygulamaya özel ise derin özyinelemeli işlevlerin bazı uygulamalarla kırılması, bu nedenle gerçekten hepsini kullan.

(3) modern fonksiyonel dillerin (özellikle Haskell, Erlang, Clojure ve Scala) Ruby ve Python'un sahip olmadığı bir başka büyük şey. Çok fazla ayrıntıya girmeden, garantili değişmezlik, özellikle eşzamanlı durumlarda tüm böcek sınıflarını ortadan kaldırır ve kalıcı veri yapıları gibi düzenli şeylere izin verir . Dil düzeyinde destek olmadan bu avantajlardan yararlanmak çok zor.

(4) benim için tamamen işlevsel diller (melez dillerin aksine) hakkındaki en ilginç şey. Aşağıdaki son derece basit Ruby işlevini göz önünde bulundurun:

def add(a, b)
  a + b
end

Bu, saf bir işleve benziyor, ancak operatörün aşırı yüklenmesi nedeniyle, parametreyi değiştirebilir veya konsola yazdırma gibi yan etkilere neden olabilir. Birisinin +operatörü yan etkisi olması için aşırı yüklemesi muhtemel değildir , ancak dil hiçbir garanti vermez. (Aynısı Python için de geçerlidir, ancak bu özel örnekte olmasa da)

Öte yandan, tamamen işlevsel bir dilde, işlevlerin referans olarak şeffaf olduğu dil düzeyinde garantiler vardır. Bunun sayısız avantajı vardır: saf fonksiyonlar kolayca not edilebilir; herhangi bir küresel devlete güvenmeden kolayca test edilebilirler; ve işlev içindeki değerler eşzamanlılık sorunları hakkında endişe duymadan tembel ya da paralel olarak değerlendirilebilir. Haskell bundan tam anlamıyla yararlanıyor, ancak yapıp yapmadıklarını bilmek için diğer fonksiyonel diller hakkında yeterince bilgim yok.

Tüm söylenenler, FP tekniklerini neredeyse her dilde (hatta Java) kullanmak mümkün. Örneğin, Google’ın MapReduce’u işlevsel fikirlerden ilham alıyor, ancak bildiğim kadarıyla büyük projeleri için herhangi bir “işlevsel” dil kullanmıyorlar (sanırım çoğunlukla C ++, Java ve Python kullanıyorlar).


2
Ayrıntılı açıklama için +1 - FP dış görevlisi olarak bile anladım. Teşekkürler! :-)
Péter Török,

Şimdiye kadar bu soruya en değerli cevap. Gerçekler ve birçok bilgi üzerine kurulmuştur. İyi iş.
wirrbel

Scala'nın kuyruk özyinelemesi vardır ve Scheme'de olduğu gibi özyinelemeli bir çağrının kuyruk pozisyonunda olması durumunda otomatik olarak yapılır (açıkça istenmesi gereken Clojure'den farklı olarak). Eğer derleyici kontrol edebilirsiniz böylece bile bir ek açıklama var olacak kuyruk özyinelemeli kod oluşturmak. Sahip olmadığı şey, Scheme'nin daha genelleştirilmiş TCO'su. Bunu bildiğinizi varsayıyorum ama diğer birçok konu hakkında çok fazla ayrıntıya girdiğinizden beri, bu garip bir işten çıkarma / ihmal gibi görünüyordu.
saat

@ itsbruce Bu gönderi oldukça eski, IIRC Scala o zamana sahip değildi (ya da sadece yanlış olmuş olabilirim;). Güncellenmiş.
shosti

Scala'yı başlangıçtan beri kullanmıyorum, ancak 2008'de ilgimi çekerken kuyruk özyinelemesine sahipti ;-) Bu konuyu soran insanları bu özel SO sorusuna yönlendiriyorum çünkü bazı iyi cevaplar var ve ben sadece bu tuhaflık fark etti, bu yüzden bütünlüğü için yorum yaptı.
saat

11

Bahsettiğiniz diller çok farklı.

Python ve Ruby dinamik olarak yazılmış diller olsa da, Haskell statik olarak yazılmıştır. Erlang eşzamanlı bir dildir ve Aktör modelini kullanır ve bahsettiğiniz diğer dillerden çok farklıdır.

Python ve Ruby birçok zorunlu yapıya sahipken Haskell gibi daha saf bir işlevsel dilde, her şey bir şey döndürür veya başka bir deyişle her şey bir işlevdir.


@kRON: Tip sistemi bir dilin önemli bir özelliği ve “Bu özel fonksiyonel programlama dillerinin sağladığı farklı bir şey var mı?” diye sordu. Elbette, Aktör modelini diğer dillerle kullanabilirsiniz, ancak Erlang dilde yerleşiktir. Erlang, hafif süreçleri kullanır ve dağıtılmış programlama için yerleşik bir dil yapısına sahiptir - bu nedenle "eşzamanlı" bir dildir.
Jonas

8

Her zamanki gibi partiye geç kaldık ama yine de bir şeyler söyleyeceğim.

İşlevsel bir programlama dili, işlevsel programlamaya izin veren bir dil değildir . Eğer bu tanımdan geçersek, o zaman hemen hemen her yerde herhangi bir dil işlevsel bir programlama dilidir. (Bu aynı zamanda OOP için de geçerlidir. İsterseniz C'deki bir OOP stilinde yazabilirsiniz. Böylece, mantığınıza göre, C bir OOP dilidir.)

İşlevsel bir programlama dili yapan şey, programlamanıza izin verdiği şey değil , kolayca programlamanıza izin verdiği şeydir . Anahtar bu.

Bu yüzden, Python'un (son derece anemik kapanma benzeri olaylar olan) lambdaları vardır ve size "harita" ve "katlama" gibi işlevsel kütüphanelerde göreceğiniz birkaç kütüphane işlevi sunar. Bununla birlikte, onu işlevsel bir programlama dili yapmak için yeterli değildir, çünkü sürekli olarak uygun bir işlevsel tarzda programlanması imkansızdır (ve dil kesinlikle bu stili zorlamaz!). Temelinde Python, durum ve durum manipülasyon işlemleriyle ilgili zorunlu bir dildir ve bu, işlevsel bir dilin ifade ve anlatım değerlendirme anlamıyla çelişmektedir.

Öyleyse neden Python (ya da Ruby (ya da seçtiğiniz dili ekleyiniz)) "işlevsel programlama" yapabiliyorsa işlevsel programlama dillerine sahibiz? Çünkü Python ve diğerleri düzgün işlevsel programlama yapamıyor. Bu yüzden.


6

Sen olabilir (örneğin bkz Java işlevsel programlama yapmak http://functionaljava.org/ ). Nesneye yönelik programlama C de yapabilirsiniz . Bu o kadar aptalca değil.

Gerçekten de , kesinlikle Erlang, Haskell, Scheme veya herhangi bir özel programlama diline ihtiyacımız yok, ancak hepsi farklı yaklaşımları ve farklı takdiri temsil ediyor, bu da bazı görevleri daha kolay ve daha da zorlaştırıyor. Ne kullanmanız gerektiği, neye ulaşmak istediğinize bağlıdır.


4

Bu soru sonsuz sayıda dile ve paradigmaya uygulanabilir.

  • Herkes C ++ kullandığından, neden başka genel amaçlı dillere ihtiyacımız var?
  • Java çok iyi bir OO dili olduğundan, neden diğer OO dilleri var?
  • Perl harika bir betik dili olduğundan, neden python'a ihtiyacımız var?
  • Yatta, yatta, yatta

Çoğu, belirli bir nedenden ötürü tüm diller mevcut değilse. Onlar var, çünkü birisinin şu anki dili doldurmadığı ya da yetersiz doldurma ihtiyacı vardı. (Bu, elbette her dil için geçerli değildir, ancak iyi bilinen dillerin çoğu için geçerli olduğunu düşünüyorum.) Örneğin, python aslında Amoeba OS [ 1 , 2 ] ile arayüz oluşturmak üzere geliştirildi ve Erlang yardımcı olmak için yaratıldı. telefon uygulamalarının geliştirilmesinde [ 3 ]. Öyleyse, "Neden başka bir işlevsel dile ihtiyacımız var?" Sorusuna bir cevap. basitçe olabilir çünkü [tasarım dilini bilen birinin adını yazın] python'un yaptığı şekli beğenmedi.

Bu, cevabın ne olduğunu düşündüğümüzü özetliyor. Fonksiyonel bir dille yapabileceğiniz python ile her şeyi yapabiliyor olsanız da, gerçekten ister miydiniz? C içinde yapabileceğiniz her şeyi, montajda yapabilirsiniz, ama ister misiniz? Farklı diller her zaman farklı şeyler yapmakta en iyisi olacaktır ve olması gerektiği gibi.


2

İşlevsel programlama, belirli dil özellikleri ile ilgili olduğu kadar bir tasarım paradigması ile de ilgilidir. Veya başka bir deyişle, lambdalar ve bir harita işlevi, işlevsel bir programlama dili oluşturmaz. Python ve Ruby, işlevsel programlama dillerinden ilham alan bazı özelliklere sahiptir, ancak yine de çok zor bir şekilde kod yazıyorsunuz. (C'ye benzer: C'ye OO benzeri kod yazabilirsiniz, ancak kimse C'yi OO dili olarak düşünmez.)

Bakın: işlevsel programlama sadece lambdalar ya mapda daha üst düzey fonksiyonlarla ilgili değildir. Bu tasarımla ilgili . "Doğru" bir fonksiyonel programlama dilinde yazılmış bir program, fonksiyonların bileşimi aracılığıyla problemleri çözer. Ruby veya Python ile yazılmış programlar FP benzeri özellikler kullanabilse de, genellikle bir dizi oluşturulmuş işlev gibi okumazlar.


0

Bahsettiğiniz her fonksiyonel dil belli bir nişe uyuyor ve her birini teşvik eden idiomatik kalıplar, büyük bir yardımcı modül kütüphanesi oluşturmadıkça Python'da gerçekleştirilmesi imkansız olacak belirli işler için onları çok uygun hale getiriyor. Böyle bir niş mükemmeliyetin en belirgin olanı, Erlang'ın eşzamanlılık modelidir. Diğerleri de benzer güçlere sahiptir.


0

Lambda-calculus, LISP ve Scheme'de mevcut olan her konsept Python'da mevcuttur, yani evet, içinde işlevsel programlama yapabilirsiniz. Uygun olup olmadığı bağlam ve zevk meselesidir.

Python'da (Ruby, Scala) bir LISP (veya diğer fonksiyonel dil) tercümanını kolayca yazabilirsiniz (bu miktar ne kadardır?). Python için tamamen işlevsel olan bir tercüman yazabilirsiniz, ancak bu çok fazla iş gerektirecektir. Günümüzde "işlevsel" diller bile çok paradigmadır.

Bu eski ve güzel kitap , işlevsel programlamanın özünün ne olduğu hakkındaki cevapların çoğuna (hepsi olmasa da) sahiptir.


@Arkaaito Fakat lambda matematiği, LISP ve Scheme'de mevcut olan her konseptin Python'da mevcut olduğunu kabul ediyor musunuz?
Mark C,

Her lisp konseptinin Python'da bulunduğundan emin misiniz ? Makrolar, kod-veridir?
SK-mantık

@ SK-mantık Makrolar lambda hesaplarında değildir, ancak evet, bu isimde olmasa da Python'da bulunurlar. Python, kod için verieval() için gerekli olan bir işlev sunar , ancak daha da ileri gider: çalışma ortamının çoğunu, LISP'deki gibi değiştirmenize olanak sağlar.
Apalala

@ Alopala, dinamik diller eval()bir çalışma zamanı metaprogramming. Bazen yararlıdır, ancak son derece pahalıdır. Lisp makroları farklıdır, derleme zamanı metaprogramlamasıdır. Python'da kullanılabilir bir biçimde mevcut değildir.
SK-mantık

@ SK-mantık Bu tür makrolar , programlar makroları değiştiremediklerinden (hatta varlıklarını bile bilmediklerinden) kod-veri öncüllerini bozarlar . Python'da, programlar çalışma zamanında kendilerine ait ayrıştırma ağaçlarına gereksinim duyuyorlarsa erişebiliyor. Makrolar (statik ön işleme) hiç işlevsel değil.
Apalala

-5

Çünkü Python işlevsel olmayan bir tarzda da programlanabilir ve bu, FP uzmanı için yeterince iyi değil. Ancak daha pragmatik kodlayıcılar, dogmatik olmadıkça işlevsel stilin yararlarından yararlanabilirler:

“İşlevsel programcı bir ortaçağ keşişine benziyor, kendini erdemli hale getireceği umuduyla yaşamın zevklerini inkar ediyor. Maddi faydalarla daha fazla ilgilenenler için, bu “avantajlar” çok ikna edici değil. İşlevsel programcılar, maddi faydaların olduğunu savunuyorlar… [ama] bu açıkça saçma. Atama ifadelerinin çıkarılması bu kadar büyük yararlar sağladıysa, FORTRAN programcıları bunu yirmi yıldır yapıyor olacaktı. Ne kadar kötü olursa olsun, özellikleri atlayarak bir dili daha güçlü hale getirmek mantıklı bir imkansızlıktır. ”

- John Hughes, İşlevsel programlama neden önemlidir?


6
Katılıyorum. Olmadan herhangi bir dil gotokorkunç.
Anon.

2
@Anon: Ya da büyük kardeşi call-with-current-continuation.
Chris Jester-Young,

4
Mason, alıntı yaptığın kağıt tam tersini söylüyor. Alıntı, yalnızca farklı bir hikaye anlatan bir makalenin birkaç paragrafının bir parçasıdır. Aslında, her iki paragrafı da bir bütün olarak belirtirseniz, açıkça başka bir anlam gösterirler.
Marco Mustapic

2
@Mason: evet, yazar modülerleşmeyi iddia ediyor ve tembel değerlendirme gerçek faydalar. Ancak asıl teklifinizin bağlam dışına çıkmadığına dikkat edin - yazarın FP'ye karşı bir şey talep ettiğini iddia ediyor gibi görünüyorsunuz, aslında bir makaleden bir paragrafı aktardığınızda, FP'nin harika olduğu sonucuna varmıştınız. Az daha çoktur". Makalenin başlığı, yazarın amacını oldukça net bir şekilde göstermektedir: "neden işlevsel programlama önemlidir" ;-) Daha saf olan FP konuşma sürelerinin "sıkıntı verici" olduğu iddiasını kesinlikle desteklemiyor.
Andres F.

6
@Mason Wheeler: Hibrit diller, Python'u bir loooooong streç ile aşıyor. Örneğin, Lisp 1959'a kadar uzanır ve aslında çok paradigma bir dildir. Programlamaya işlevsel yaklaşımları, prosedürel yaklaşımları ve nesne yönelimli yaklaşımları tamamen destekler. Üstte doğru makro paketleri sayesinde mantık programlamasını da kolayca yapabilirsiniz. Şema da Python'u (ve bu makaleyi) öneriyor. 1975'e kadar uzanıyor. Belki bir zamanlar bu programlama dilleri zaman çizelgesine bir göz atmalısınız .
SADECE MY
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.