Tiplere göre örüntü eşleştirme deyimsel veya zayıf tasarım mı?


18

Görünüşe göre F # kodu genellikle model tiplerine karşı eşleşir. Kesinlikle

match opt with 
| Some val -> Something(val) 
| None -> Different()

yaygın görünüyor.

Ancak bir OOP perspektifinden bakıldığında, bu, tipik olarak kaşlarını çatmış olan bir çalışma zamanı tür kontrolüne dayanan kontrol akışına çok benziyor. Bunu hecelemek için, OOP'de muhtemelen aşırı yüklemeyi kullanmayı tercih edersiniz:

type T = 
    abstract member Route : unit -> unit

type Foo() = 
    interface T with
        member this.Route() = printfn "Go left"

type Bar() = 
    interface T with
        member this.Route() = printfn "Go right"

Bu kesinlikle daha fazla kod. OTOH, OOP-y zihnime yapısal avantajları var gibi görünüyor:

  • yeni bir biçime genişletmek Tkolaydır;
  • Güzergah seçme kontrol akışının kopyasını bulmak konusunda endişelenmem gerekmiyor; ve
  • rota seçimi, bir kez elimde olduğunda Foo, asla Bar.Route()uygulanması için endişelenmem gerektiği anlamında değişmez

Görmediğim türlere göre desen eşleştirmenin avantajları var mı? Deyimsel kabul edilir mi yoksa yaygın olarak kullanılmayan bir özellik mi?


4
İşlevsel bir dili OOP perspektifinden görmek ne kadar mantıklı? Her neyse, desen eşleştirmenin gerçek gücü iç içe geçmiş desenlerle birlikte gelir. Sadece en dıştaki yapıcıyı kontrol etmek mümkündür, ancak hiçbir şekilde tüm hikaye.
Ingo

Bu - But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.- çok dogmatik geliyor. Bazen, op'larınızı hiyerarşinizden ayırmak isteyebilirsiniz: belki 1) bir hiyerarşiye op ekleyemezsiniz b / c hiyerarşiye sahip değilsiniz; 2) op olmasını istediğiniz sınıflar hiyerarşinizle eşleşmez; 3) op'yu hiyerarşinize ekleyebilirsiniz, ancak çoğu müşterinin kullanmadığı bir hiyerarşinin API'sı ile hiyerarşinizin API'sini karıştırmak istemezsiniz.

4
Sadece açıklığa kavuşturmak için Someve Nonetürleri değildir. Her ikisi de türleri olan yapıcılardır forall a. a -> option ave forall a. option a(üzgünüm, tür ek açıklamaları için sözdiziminin ne olduğundan emin değilim) F #).

Yanıtlar:


21

OOP sınıfı hiyerarşilerinin F #'daki ayrımcı sendikalarla çok yakından ilişkili olduğu ve örüntü eşleştirmenin dinamik tip testleriyle çok yakından ilişkili olduğu konusunda haklısınız. Aslında, F # bu şekilde ayrımcı sendikaları .NET'e derler!

Genişletilebilirlik ile ilgili olarak, sorunun iki tarafı vardır:

  • OO yeni alt sınıflar eklemenize izin verir, ancak yeni (sanal) işlevler eklemeyi zorlaştırır
  • FP yeni işlevler eklemenize izin verir, ancak yeni sendika vakaları eklemeyi zorlaştırır

Bununla birlikte, F #, desen eşleşmesinde bir vakayı kaçırdığınızda size bir uyarı verecektir, bu nedenle yeni sendika vakaları eklemek aslında o kadar da kötü değildir.

Kök seçiminde yinelenenleri bulma konusunda - F #, yinelenen bir eşleşmeniz olduğunda size bir uyarı verecektir, örneğin:

match x with
| Some foo -> printfn "first"
| Some foo -> printfn "second" // Warning on this line as it cannot be matched
| None -> printfn "third"

"Güzergah seçiminin değişmez" olması da sorunlu olabilir. Örneğin, bir işlevin uygulanmasını Foove Barvakaları arasında paylaşmak , ancak Zoovaka için başka bir şey yapmak istiyorsanız, desen eşlemeyi kullanarak bunu kolayca kodlayabilirsiniz:

match x with
| Foo y | Bar y -> y * 20
| Zoo y -> y * 30

Genel olarak, FP daha önce türlerin tasarlanmasına ve daha sonra fonksiyonların eklenmesine odaklanmaktadır. Bu nedenle, türlerinizi (etki alanı modeli) tek bir dosyada birkaç satıra sığdırabilmeniz ve daha sonra etki alanı modelinde çalışan işlevleri kolayca ekleyebilmeniz gerçekten yarar sağlar.

İki yaklaşım - OO ve FP oldukça tamamlayıcıdır ve her ikisinin de avantajları ve dezavantajları vardır. Zor olan şey (OO perspektifinden geliyor) F #'ın genellikle FP stilini varsayılan olarak kullanmasıdır. Ancak, yeni alt sınıflar eklemeye gerçekten daha fazla ihtiyaç varsa, her zaman arabirimleri kullanabilirsiniz. Ancak çoğu sistemde aynı şekilde türler ve işlevler eklemeniz gerekir, bu yüzden seçim gerçekten çok önemli değildir - ve F #'da ayrımcı sendikalar kullanmak daha iyidir.

Daha fazla bilgi için bu harika blog dizisini öneriyorum .


3
Doğru olmanıza rağmen, bunun OO vs FP kadar çok bir konu olmadığını ve nesnelerin toplam türlerine karşı bir konu olduğunu eklemek isterim. OOP'un onlara olan takıntısı bir yana, onları işlevsel hale getiren nesneler hakkında hiçbir şey yok. Ve yeterli sayıda çembere atlarsanız, ana akım OOP dillerinde de toplam türleri uygulayabilirsiniz (hoş olmayacak olsa da).
Doval

1
"Ve yeterince çembere atlarsanız, ana akım OOP dillerinde de toplam türleri uygulayabilirsiniz (hoş olmayacak olsa da)." -> Sanırım F # sum türlerinin .NET'in tip sisteminde nasıl kodlandığına benzer bir şeyle
sonuçlanacaksınız

8

Kalıp eşleşmesinin (esasen bir süper şarjlı anahtar deyimi) ve dinamik gönderimin benzerliklere sahip olduğunu doğru bir şekilde gözlemlediniz. Ayrıca, bazı dillerde çok keyifli bir sonuçla bir arada var olurlar. Ancak, küçük farklılıklar vardır.

Yalnızca sabit sayıda alt tür olan bir türü tanımlamak için tür sistemini kullanabilirsiniz:

// pseudocode
data Bool = False | True
data Option a = None | Some item:a
data Tree a = Leaf item:a | Node (left:Tree a) (right:Tree a)

Asla başka bir alt türü olmayacaktır Boolveya Optionbu nedenle alt sınıf yararlı görünmemektedir (Scala gibi bazı dillerde bunun üstesinden gelebilecek bir alt sınıf kavramı vardır - bir sınıf mevcut derleme biriminin dışında "son" olarak işaretlenebilir, ancak alt türler bu derleme birimi içinde tanımlanabilir).

Böyle bir türün alt Optiontürleri artık statik olarak bilindiği için , kalıp eşleşmemizde bir vakayı ele almayı unutursak derleyici uyarı verebilir. Bu, bir desen eşleşmesinin, bizi tüm seçenekleri ele almaya zorlayan özel bir downcast gibi olduğu anlamına gelir.

Ayrıca, (OOP için gerekli olan) dinamik yöntem dağıtımı da bir çalışma zamanı türü denetimi gerektirir, ancak farklı türde. Bu nedenle, bu tip kontrolünü bir kalıp eşleşmesi yoluyla veya dolaylı olarak bir yöntem çağrısı yoluyla yapmamız oldukça önemsizdir.


"Bu, bir desen eşleşmesinin bizi tüm seçenekleri ele almaya zorlayan özel bir mahzun gibi olduğu anlamına gelir" - aslında, inanıyorum (sadece değerlerle veya iç içe yapı ile değil, sadece yapıcılarla eşleştirdiğiniz sürece) izomorfik üst sınıfa soyut bir sanal yöntem yerleştirme.
Jules

2

F # patern eşleşmesi tipik olarak sınıflardan ziyade ayrımcı bir birliktelikle yapılır (ve bu nedenle teknik olarak bir tür denetimi yoktur). Bu, bir desen eşleşmesindeki durumlar için açıklanmadığınızda derleyicinin size bir uyarı vermesini sağlar.

Dikkat edilmesi gereken başka bir şey, işlevsel bir tarzda, verileri verilerden ziyade işlevselliğe göre organize ettiğinizdir, bu nedenle desen eşleşmeleri, farklı işlevleri sınıflara dağılmak yerine tek bir yerde toplamanızı sağlar. Bunun avantajı, değişikliklerinizi yapmanız gereken yerin hemen yanında diğer vakaların nasıl ele alındığını da görebilmenizdir.

Yeni bir seçenek eklemek şöyle görünür:

  1. Ayrılmış sendikanıza yeni bir seçenek ekleyin
  2. Eksik kalıp eşleşmeleriyle ilgili tüm uyarıları düzeltin

2

Kısmen, işlevsel programlamada daha sık görürsünüz çünkü kararları daha sık almak için türleri kullanırsınız. Muhtemelen rastgele daha fazla veya daha az örnek seçtiğinizi anlıyorum, ancak desen eşleştirme örneğinize OOP eşdeğeri daha sık şöyle görünecektir:

if (opt != null)
    opt.Something()
else
    Different()

Başka bir deyişle, OOP'ta null kontroller gibi rutin şeylerden kaçınmak için polimorfizm kullanmak nispeten nadirdir. Tıpkı bir OO programcısının her küçük durumda boş bir nesne oluşturmadığı gibi , işlevsel bir programcı her zaman bir işlevi aşırı yüklemez, özellikle de desen listenizin kapsamlı olduğu garanti edildiğinde. Yazı sistemini daha fazla durumda kullanırsanız, alışık olmadığınız şekillerde kullanıldığını göreceksiniz.

Tersine, OOP örneğinize eşdeğer deyimsel fonksiyonel programlama büyük olasılıkla desen eşleşmesini kullanmaz, ancak çağrı koduna argüman olarak iletilecek işlevlere fooRouteve barRouteişlevlere sahip olur. Birisi bu durumda desen eşleşmesi kullansaydı, tıpkı türleri değiştiren biri OOP'de yanlış kabul edilir gibi, genellikle yanlış kabul edilir.

Öyleyse, desen eşleme ne zaman iyi fonksiyonel programlama kodu olarak kabul edilir? Sadece türlere bakmaktan daha fazlasını yaparken ve gereksinimleri genişletirken daha fazla vaka eklemeyi gerektirmez. Örneğin, Some valyalnızca opttürün olup olmadığını kontrol etmekle kalmaz Some, aynı zamanda valdiğer tarafında tür güvenli kullanım için altta yatan türe de bağlanır ->. Büyük olasılıkla hiçbir zaman üçüncü bir davaya ihtiyaç duymayacağınızı biliyorsunuz, bu yüzden iyi bir kullanım.

Örüntü eşleme, yüzeysel olarak nesne yönelimli bir anahtar deyimine benzeyebilir, ancak özellikle daha uzun veya iç içe geçmiş desenlerde çok daha fazlası vardır. Kötü tasarlanmış bir OOP koduna eşdeğer olduğunu bildirmeden önce yaptığı her şeyi dikkate aldığınızdan emin olun. Çoğu zaman, bir miras hiyerarşisinde temiz bir şekilde temsil edilemeyen bir durumu özlü bir şekilde ele alır.


Bunu bildiğinizi biliyorum, ve cevabınızı yazarken muhtemelen aklınızı kaçırdı, ancak not edin Someve Nonetür değildir, bu yüzden türlerde desen eşleşmesi olmazsınız. On desen maç yapıcıları arasında aynı tip . Bu "anlık" istemek gibi bir şey değildir.
Andres
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.