C # bis F #: Funktionales Denken vs. Polymorphie

7

Angenommen, ich hätte zwei Klassen:

%Vor%

Wir haben hier die Beschreibungen von zwei geometrischen Formen zusammen mit einer Operation in beiden.

Und hier ist mein Versuch in F #:

%Vor%

Angenommen, ich habe eine Beobachtung über diese beiden Klassen gemacht (beide haben Height !) und ich wollte eine Operation extrahieren, die für alles Sinn macht, das eine height -Eigenschaft hat. In C # könnte ich also eine Basisklasse herausziehen und die Operation dort wie folgt definieren:

%Vor%

Beachten Sie, wie einzelne Unterklassen ihre eigenen Ideen bezüglich der Implementierung dieser Operation haben können.

Näher am Punkt, könnte ich irgendwo eine Funktion haben, die entweder ein Dreieck oder einen Zylinder akzeptieren kann:

%Vor%

.. und diese Funktion kann entweder ein Dreieck oder einen Zylinder akzeptieren.

Ich habe Probleme, die gleiche Denkweise auf F # anzuwenden.

Ich habe etwas über funktionale Sprachen gelesen. Das herkömmliche Denken ist, dass es vorzuziehen ist, algebraische Werttypen anstelle von vererbten Klassen auszudrücken. Das Denken geht davon aus, dass es besser ist, größere Typen aus kleineren Bausteinen zu komponieren oder zu bauen, als mit einer abstrakten Klasse zu beginnen und sich von dort zu verengen.

In F # möchte ich in der Lage sein, eine Funktion zu definieren, die ein Argument verwendet, von dem bekannt ist, dass es eine Height-Eigenschaft hat, und irgendwie damit arbeiten kann (d. h. die Basisversion von CanSuperManJumpOver ). Wie soll ich diese Typen in einer funktionalen Welt strukturieren, um dies zu erreichen? Macht meine Frage in einer funktionalen Welt überhaupt Sinn? Kommentare zum Denken sind sehr willkommen.

    
CSJ 16.02.2014, 15:32
quelle

2 Antworten

12

Meiner Meinung nach ist das C # -Design, das Sie beschreiben, grundsätzlich falsch - Sie haben einen Typ ShapeWithHeight mit einer virtuellen Methode CanSuperManJumpOver definiert, aber die Methode kann nicht für eine der konkreten Instanzen (2D-Dreieck) und Sie implementiert werden muss stattdessen eine Ausnahme auslösen.

Einer der wichtigsten Grundsätze beim Modellieren einer Domäne in F # ist, dass ungültige Zustände nicht darstellbar sein sollten (siehe great article for more ). Ihr Entwurf unterbricht dies - weil Sie ein Dreieck konstruieren und eine Operation darauf ausführen können, aber die Operation ist ungültig.

Also, die erste Sache, die Sie berücksichtigen sollten, ist, was ist eigentlich die Domäne, die Sie modellieren möchten? (Das ist etwas schwer zu erraten, aber lassen Sie es mich versuchen ...) Nehmen wir an, Sie haben einige Objekte und Superman kann über 3D-Formen springen, aber nicht über 2D-Formen. Sie könnten eine diskriminierte Vereinigung verwenden, um zwischen diesen beiden Arten von Formen zu unterscheiden:

%Vor%

Der Trick ist, dass wir jetzt für alle 3D-Formen die Höhe direkt im Fall Shape3D haben - so können Sie immer die Höhe einer 3D-Form erhalten (unabhängig davon, welche spezifische Form es hat - hier nur Zylinder). Bei 2D-Formen habe ich die Höhe nicht berücksichtigt, weil sie dies möglicherweise tun oder nicht haben ...

Dann können Sie eine Sprungfunktion schreiben, die auf eine Form passt und die drei verschiedenen Fälle behandelt - die Form kann nicht springen, die Form ist zu klein oder die Form ist hoch genug:

%Vor%

Zusammenfassend: Wenn Sie eine abstrakte Klasse mit einigen Eigenschaften in C # haben, ist die nächste Sache in F # (wenn Sie das funktionale Design anstelle des OO-Designs verwenden möchten), einen Typ zu verwenden, der die allgemeinen Eigenschaften speichert ( Shape ) und enthält den Wert eines anderen Typs ( Shape3DInfo ), der die für jede Unterklasse spezifischen Details angibt.

    
Tomas Petricek 16.02.2014, 15:45
quelle
10

In einem funktionalen Programmierparadigma würden Sie mit den Funktionen beginnen und die Typen daraus herausarbeiten. Der Ausgangspunkt wäre also die Funktion

%Vor%

Sie würden nur über Polymorphie zwischen Dreiecken und Zylindern nachdenken, wenn Sie eine Frage haben, bei der die CanSupermanJumpOver-Funktion auf beide angewendet werden muss. Grundsätzlich konzentriert sich das OO-Paradigma darauf, die verwendeten internen Datenstrukturen zu verbergen. Das funktionale Paradigma konzentriert sich auf die Kapselung komplexer Prozesslogik, aber die Datenstrukturen sind transparent. Der Trick besteht darin, beide Ansätze zu kombinieren, ohne die Vorteile beider zu beeinträchtigen. Das ist etwas, mit dem ich mich in der letzten Zeit herumgeschlagen habe.

    
Rattle 16.02.2014 15:58
quelle