Die Objekt-Funktionsimpedanz-Fehlanpassung

9

In OOP empfiehlt es sich, mit Schnittstellen zu kommunizieren, nicht mit Implementierungen. Also schreiben Sie beispielsweise so etwas (von Seq ich meine scala.collection.immutable.Seq :)):

%Vor%

nicht etwa wie folgt:

%Vor%

In reinen funktionalen Programmiersprachen wie Haskell haben Sie jedoch keinen Subtyp-Polymorphismus und stattdessen Ad-hoc-Polymorphismus über Typklassen. So haben Sie zum Beispiel den Listdatentyp und eine monadische Instanz für die Liste. Sie müssen sich keine Gedanken über die Verwendung einer Schnittstelle / abstrakten Klasse machen, weil Sie kein solches Konzept haben.

In hybriden Sprachen, wie Scala, haben Sie beide Klassen (durch ein Muster, tatsächlich, und nicht erstklassige Bürger wie in Haskell, aber ich schweife ab) und Subtyp Polymorphismus. In scalaz , cats und so weiter haben Sie natürlich monadische Instanzen für konkrete Typen, nicht für die abstrakten.

Endlich die Frage: Angesichts dieses Hybridismus von Scala respektierst du immer noch die OOP-Regel, um mit Interfaces zu sprechen oder einfach mit konkreten Typen zu sprechen, um Funktoren, Monaden usw. direkt zu nutzen zu einem konkreten Typ konvertieren, wann immer Sie sie brauchen? Anders gesagt, ist es in Scala immer noch eine gute Übung, mit Interfaces zu sprechen, auch wenn Sie FP statt OOP nutzen wollen? Wenn nicht, was wäre, wenn Sie sich für List entschieden hätten und später festgestellt hätten, dass eine Vector die bessere Wahl gewesen wäre?

P.S .: In meinen Beispielen habe ich eine einfache Methode verwendet, aber das gleiche gilt für benutzerdefinierte Typen. Zum Beispiel:

%Vor%     
lambdista 09.05.2016, 12:55
quelle

2 Antworten

1

Was ich hier angreifen würde, ist Ihr Konzept "concrete vs. interface". Sieh es dir so an: Jeder Typ hat eine Schnittstelle, im allgemeinen Sinn des Begriffs "Schnittstelle". Ein "konkreter" Typ ist nur ein begrenzender Fall.

Sehen wir uns Haskell-Listen aus diesem Blickwinkel an. Was ist die Schnittstelle einer Liste? Nun, Listen sind ein algebraischer Datentyp , und alle diese Datentypen haben die gleiche allgemeine Form von Schnittstelle und Vertrag:

  1. Sie können Konstrukte Instanzen des Typs erstellen, indem Sie seine Konstruktoren nach ihren Aritäten und Argumenttypen verwenden;
  2. Sie können Instanzen des Typs beobachten, indem Sie anhand ihrer Aritäten und Argumenttypen nach ihren Konstruktoren suchen;
  3. Konstruktion und Beobachtung sind Umkehrungen - wenn Sie ein Muster mit einem Wert vergleichen, erhalten Sie genau das, was Sie hineingelegt haben.

Wenn Sie es mit diesen Begriffen betrachten, denke ich, dass die folgende Regel in beiden Paradigmen ziemlich gut funktioniert:

  • Wählen Sie Typen aus, deren Schnittstellen und Verträge mit Ihren Anforderungen genau übereinstimmen.
    • Wenn ihr Vertrag schwächer ist als Ihre Anforderungen, dann werden sie keine Invarianten pflegen, die Sie brauchen;
    • Wenn ihre Verträge stärker sind als Ihre Anforderungen, können Sie sich unbeabsichtigt mit den "zusätzlichen" Details verbinden und Ihre Fähigkeit einschränken, das Programm später zu ändern.

Sie fragen also nicht mehr, ob ein Typ "konkret" oder "abstrakt" ist - egal, ob er Ihren Anforderungen entspricht.

    
Luis Casillas 10.05.2016 18:54
quelle
0

Das sind meine zwei Cent zu diesem Thema. In Haskell haben Sie Datentypen (ADTs). Sie haben sowohl Listen (verknüpfte Listen) als auch Vektoren (int-indizierte Arrays), aber sie haben keinen gemeinsamen Supertyp. Wenn Ihre Funktion eine Liste annimmt, können Sie ihr keinen Vektor übergeben.

Da es sich bei Scala um eine hybride OOP-FP-Sprache handelt, haben Sie auch einen Subtyp-Polymorphismus, so dass es Ihnen nicht egal ist, ob der Clientcode List oder Vector übergibt, sondern nur Seq (möglicherweise unveränderlich). und du bist fertig.

Ich denke, um auf diese Frage zu antworten, müssen Sie sich eine andere Frage stellen: "Will ich FP in toto umarmen?". Wenn die Antwort ja lautet, sollten Sie nicht Seq oder eine andere abstrakte Oberklasse im Sinne von OOP verwenden. Die Ausnahme von dieser Regel ist natürlich die Verwendung eines Merkmals / einer abstrakten Klasse beim Definieren von ADTs in Scala. Beispielsweise:

%Vor%

In diesem Fall würde man natürlich Tree[A] als einen Typ benötigen und dann z. B. Mustervergleich verwenden, um zu bestimmen, ob es entweder Empty oder Node[A] ist.

Ich denke, mein Gefühl für dieses Thema wird durch das rote Buch () bestätigt Funktionale Programmierung in Scala ). Dort verwenden sie nie Seq , aber List , Vector und so weiter. Auch haskellers interessieren sich nicht für diese Probleme und verwenden Listen, wann immer sie Linked-List-Semantik und -Vektoren benötigen, wann immer sie Int-Indexed-Array-Semantik benötigen.

Wenn Sie andererseits OOP verwenden und Scala als besseres Java verwenden möchten, dann sollten Sie der OOP-Best Practice folgen, um talk mit Interfaces zu kommunizieren nicht zu Implementierungen.

Wenn Sie denken: "Ich würde mich eher für hauptsächlich funktional entscheiden, dann sollten Sie Erik Meijers Der Fluch der ausgeschlossenen Mitte .

    
lambdista 12.05.2016 12:31
quelle