Was ist die funktionale Programmieralternative zu einer Schnittstelle?


15

Wenn ich in einem "funktionalen" Stil programmieren möchte, durch was würde ich eine Schnittstelle ersetzen?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Vielleicht ein Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

Der einzige Grund, warum ich überhaupt eine Schnittstelle benutze, ist, dass ich immer bestimmte Eigenschaften / Methoden zur Verfügung haben möchte.


Bearbeiten: Einige Details darüber, was ich denke / versuche.

Angenommen, ich habe eine Methode, die drei Funktionen übernimmt:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

Mit einer Instanz von Barkann ich diese Methode verwenden:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

Aber das ist ein bisschen schmerzhaft, wie ich bardreimal in einem einzigen Anruf erwähnen muss . Außerdem habe ich nicht wirklich vor, dass Anrufer Funktionen aus verschiedenen Instanzen bereitstellen

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

In C # gibt interfacees eine Möglichkeit, damit umzugehen. aber das scheint ein sehr objektorientierter Ansatz zu sein. Ich frage mich, ob es eine funktionalere Lösung gibt: 1) Übergeben Sie die Funktionsgruppe zusammen, und 2) stellen Sie sicher, dass die Funktionen richtig zueinander in Beziehung stehen.


Das würdest du nicht. Schnittstellen für Datentypen sind vollkommen in Ordnung (obwohl Sie unveränderliche Objekte bevorzugen würden).
Telastyn

1
Kapitel 2 der SICP befasst sich ziemlich genau damit.
user16764

7
Nach dem erneuten Lesen Ihrer Frage bin ich gespannt, welche spezifischen Funktionen Sie erreichen möchten. Was Sie anscheinend fragen, ist, wie Sie nebeneffektives Programmieren gegen eine Instanz in einem funktionalen Stil ausführen, der keinen Sinn ergibt.
Jimmy Hoffa

Die Antwort wäre sprachabhängig. In Clojure können Sie clojure.org/protocols verwenden , wobei der einzige weiche Bereich die Arten von Parametern sind, mit denen die Funktionen arbeiten müssen - sie sind ein Objekt - das ist alles, was Sie wissen.
Job

1
Machen Sie es einfach: Eine Struktur, die diese Methodenzeiger enthält, sowie eine Funktion, um sie von einer Objektinstanz aus zu initialisieren. Warum Haskell? ;)
mlvljr

Antworten:


6

Behandeln Sie funktionale Programmierung nicht als dünne Schicht gegenüber imperativer Programmierung. Es gibt viel mehr als nur einen syntaktischen Unterschied.

In diesem Fall haben Sie eine GetIDMethode, die die Eindeutigkeit von Objekten impliziert. Dies ist kein guter Ansatz zum Schreiben von Funktionsprogrammen. Vielleicht können Sie uns das Problem mitteilen, das Sie lösen möchten, und wir können Ihnen aussagekräftigere Ratschläge geben.


3
"Sie müssen auf Russisch denken ."
4.

Meinetwegen; Mein eigentliches Problem ist nicht besonders interessant. Ich habe bereits Probleme mit C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay, wenn Sie sich den Code wirklich ansehen möchten), aber es würde Spaß machen, dies in einem funktionaleren Stil zu tun benutze immer noch C #.
4.

1
@ Ðаn Ist das wirklich ein Firefox (Film) Zitat (weil das genial ist, wenn es ist)? Oder wird es woanders benutzt?
icc97

2
Wo ich zustimme, dass es ein vollständiger Paradigmenwechsel von Imperitiv zu Funktionaler Programmierung ist, gibt es viele Größenordnungen mehr Codezeilen, die imperitiv geschrieben sind. Viele der Eckfälle, die für große Systeme geschrieben wurden, wurden mit imperitiver Programmierung gefunden. Es gibt viele gute Praktiken in der imperitiven Programmierung, und zu wissen, ob diese Fähigkeiten übersetzt werden können oder ob sie in FP keine Rolle spielen, ist eine gute Frage. Es ist durchaus möglich, schrecklichen Code in FP zu schreiben, daher sollten solche Fragen auch die guten Teile von FP hervorheben.
ICC97

11

Haskell und seine Derivate haben Typklassen, die Schnittstellen ähneln. Es hört sich so an, als würden Sie sich fragen, wie die Verkapselung erfolgen soll. Dies ist eine Frage, die sich auf Typsysteme bezieht. Das hindley Milner-Typensystem ist in funktionalen Sprachen üblich, und es verfügt über Datentypen, die dies in verschiedenen Sprachen auf unterschiedliche Weise für Sie tun.


5
+1 für Typenklassen - Der Hauptunterschied zwischen einer Haskell-Typenklasse und einer Java-Schnittstelle besteht darin, dass die Typenklasse dem Typ zugeordnet wird, nachdem beide separat deklariert wurden. Sie können einen alten Typ über eine neue "Schnittstelle" genauso einfach verwenden wie eine alte "Schnittstelle", um auf einen neuen Typ zuzugreifen. Zum Ausblenden von Daten verbergen Sie die Implementierung des Typs in einem Modul. Zumindest nach Bertrand Meyer von Eiffel Berühmtheit , eine OOP Klasse ist eine Art Modul.
Steve314

5

Es gibt verschiedene Möglichkeiten, einer Funktion den Umgang mit mehreren Eingaben zu ermöglichen.

Erste und häufigste: Parametrischer Polymorphismus.

Dadurch kann eine Funktion auf beliebige Typen einwirken:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

Was nett ist, aber nicht den dynamischen Versand bietet, den OO-Schnittstellen haben. Dafür hat Haskell Typenklassen, Scala Implikationen usw

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Zwischen diesen beiden Mechanismen können Sie alle möglichen komplexen und interessanten Verhaltensweisen Ihrer Typen ausdrücken.


1
Sie können Tipps zur Hervorhebung von sintax hinzufügen, wenn die Sprache in der Antwort nicht mit der Sprache in der Frage übereinstimmt. Siehe zum Beispiel meinen Bearbeitungsvorschlag.
Hugomg

1

Die Grundregel lautet, dass in der FP-Programmierung die gleichen Funktionen ausgeführt werden wie in der OO-Programmierung die Objekte. Sie können ihre Methoden aufrufen (naja, die "call" -Methode) und sie reagieren gemäß einigen gekapselten internen Regeln. Insbesondere können Sie in jeder anständigen FP-Sprache "Instanzvariablen" in Ihrer Funktion mit Closures / lexikalischem Scoping verwenden.

var make_OO_style_counter = function(){
   return {
      counter: 0
      increment: function(){
          this.counter += 1
          return this.counter;
      }
   }
};

var make_FP_style_counter = function(){
    var counter = 0;
    return fucntion(){
        counter += 1
        return counter;
    }
};

Die nächste Frage ist nun, was Sie unter einer Schnittstelle verstehen. Ein Ansatz ist die Verwendung von nominalen Schnittstellen (entspricht der Schnittstelle, wenn dies angegeben ist). Diese hängt normalerweise stark von der von Ihnen verwendeten Sprache ab. Lassen Sie sie daher für letztere. Die andere Art, eine Schnittstelle zu definieren, ist die strukturelle Art, zu sehen, welche Parameter empfangen und zurückgegeben werden. Dies ist die Art von Schnittstelle, die Sie normalerweise in dynamischen Sprachen sehen, die vom Typ Ente stammen, und sie passt sehr gut zu allen FP: Eine Schnittstelle besteht nur aus den Typen der Eingabeparameter für unsere Funktionen und den Typen, die sie zurückgeben, sodass alle Funktionen mit den übereinstimmen richtige Typen passen zur Schnittstelle!

Daher besteht die einfachste Möglichkeit, ein Objekt darzustellen, das mit einer Schnittstelle übereinstimmt, darin, einfach eine Gruppe von Funktionen zu haben. Normalerweise umgehen Sie die Hässlichkeit, wenn Sie die Funktionen einzeln übergeben, indem Sie sie in eine Art Protokoll packen:

var my_blarfable = {
 get_name: function(){ ... },
 set_name: function(){ ... },
 get_id:   function(){ ... }
}

do_something(my_blarfable)

Mit nackten Funktionen oder Aufzeichnungen von Funktionen können Sie die meisten Ihrer häufigsten Probleme auf "fettfreie" Weise lösen, ohne jede Menge Boilerplate. Wenn Sie etwas Fortgeschritteneres benötigen, bieten Ihnen manchmal Sprachen zusätzliche Funktionen. Ein Beispiel, das erwähnt wird, sind Haskell-Typklassen. Typklassen ordnen einen Typ im Wesentlichen einem dieser Funktionsdatensätze zu und ermöglichen das Schreiben von Dingen, sodass die Wörterbücher implizit sind und bei Bedarf automatisch an innere Funktionen übergeben werden.

-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
    blarg_name :: String,
    blarg_id   :: Integer
}

do_something :: BlargDict -> IO()
do_something blarg_dict = do
   print (blarg_name blarg_dict)
   print (blarg_id   blarg_dict)

-- Typeclass version   
class Blargable a where
   blag_name :: a -> String
   blag_id   :: a -> String

do_something :: Blargable a => a -> IO
do_something blarg = do
   print (blarg_name blarg)
   print (blarg_id   blarg)

Eine wichtige Anmerkung zu Typklassen ist jedoch, dass die Wörterbücher den Typen und nicht den Werten zugeordnet sind (wie dies in den Wörterbuch- und OO-Versionen der Fall ist). Dies bedeutet, dass Sie im Typensystem keine "Typen" mischen können [1]. Wenn Sie eine Liste von "Blargables" oder eine Binärfunktion für Blargables benötigen, werden bei Typeclasses alle Werte auf den gleichen Typ beschränkt, während Sie bei der Dictionary-Methode Blargables mit unterschiedlichen Ursprüngen erhalten (welche Version besser ist, hängt stark davon ab, was Sie sind) tun)

[1] Es gibt fortgeschrittene Möglichkeiten, "existenzielle Typen" zu erstellen, aber normalerweise ist es die Mühe nicht wert.


0

Ich denke, es wird sprachspezifisch sein. Ich komme aus einem lispy Hintergrund. In vielen Fällen brechen Schnittstellen mit dem Zustand das Funktionsmodell bis zu einem gewissen Grad. So ist beispielsweise in CLOS LISP weniger funktional und einer imperativen Sprache näher. Im Allgemeinen sind erforderliche Funktionsparameter in Kombination mit Methoden höherer Ebenen wahrscheinlich das, wonach Sie suchen.

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.