2014-10-07 16 views
7

Der Großteil des Haskell-Codes, den ich sehe, verwendet direkte Strukturen wie Listen und Bäume. Zum Beispiel ist es üblich, dass ein Haskeller zu schreiben:Was entspricht OCamls Modulen in Haskell?

fillRect :: Color → Bounds → Image → Image 

Dieses Muster hat ein Problem: wenn später auf dem Programmierer die Definition von „Bild“ zu ändern, beschließt oder eine andere Datenstruktur verwendet, dann wird er have to Refactor jedes einzelne Stück Code mit ihm. In OCaml können Sie einfach ein Modul verwenden, das eine Schnittstelle zu einem Image angibt, und sich später für bestimmte Implementierungen entscheiden.

Was ist die Haskell-Alternative zu den OCaml-Modulen?

+0

Wie bedeutet diese Art Unterschrift, dass 'fillRect' Implementierungsdetails des Typs' Image' verwendet (oder sogar Zugriff darauf hat)? Wie würden Sie diese Signatur in OCaml anders schreiben (abgesehen von den offensichtlichen syntaktischen Unterschieden und den unterschiedlichen Namenskonventionen)? – sepp2k

+6

[Dies ist das Haskell-Analog] (http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/) von dem, was Sie beschreiben. Es ist nicht so leistungsfähig wie Ocaml-Module (zumindest nicht ohne viele Erweiterungen), aber es deckt die häufigsten Anwendungsfälle ab. Machen Sie die Aufzeichnung von Funktionen polymorph über den Typ, der diese Operationen unterstützt. –

+2

Rucksack ist ein Versuch, irgendeine Form von Modul in Haskell zu bekommen - http://plv.mpi-sws.org/backpack/ – nlucaroni

Antwort

10

Es gibt mehrere Alternativen; Keine passt genau zu ML-Modulen, aber jeder Aspekt.

  • Parametrischer Polymorphismus. Anstatt das Modul und die darin enthaltene Funktion auf Image zu parametrieren, parametrieren Sie den abstrakten Image Typ - - auf etwas, das die bestimmte "Art" des Bildes angibt. Das wäre also eine Signatur wie

    fillRect_ppm :: ImageImplemetation i => Colour -> Bounds -> Image i -> Image i 
    

    wo ImageImplemetation eine Art Klasse, die so etwas wie Wandlungs- und/oder Backend-Funktionen gibt.
    Mit solch einer Lösung können Sie nicht wirklich ersetzen die Image Art vollständig, aber Sie können den Typ selbst beliebig flexibel machen. Wahrscheinlich teilen alle Betontypen, die Sie für Image verwenden, tatsächlich einige Felder, also ist es besser, sie nicht flexibler als nötig zu machen: verschiedene Instanzen von ImageImplemetation müssen nur implementieren, was verschiedene zwischen ihnen ist. Wenn die Implementierungen sehr ähnlich sind, möchten Sie vielleicht nur einige Details wie den Farbraum parametrisieren.
    Natürlich müssen Sie mit dieser Lösung ein wenig vorausplanen, aber nur für eine gemeinsame Schnittstelle. Sie können immer noch beliebige Typ als Argument später setzen, sofern Sie die erforderlichen Instanzen definieren.

  • Ähnlich (und möglich zu mischen), könnten Sie nur eine Typklasse für Typen haben, die Bilddaten halten können.

    fillRect_tcl :: Image img => Colour -> Bounds -> img -> img 
    

    Die Image Klasse würde einige Primitiven, wie direkt definieren, wie Linien zu zeichnen, und fillRect_tcl würden diese bei der Umsetzung nutzen. Gut, weil fillRect_tcl mit einem solchen Typ sofort funktioniert. Was ein bisschen schlecht ist, ist, dass die Image Klasse ziemlich unbeholfen groß sein muss (es ist bevorzugt, Typklassen für "mathematische" Sachen mit einfachen und sehr klaren Gesetzen zu verwenden).

    class Image img where 
        ... 
        fillRect_tcm :: Colour -> Bounds -> img -> img 
        ... 
    

    nicht so schön ist, müssen Sie vollständig neu definieren, dass die Methode für jedes img Beispiel:

    • Vielleicht ist die Klasse selbst Rechtecke als Methode haben könnte.

  • Haskells Modulsystem kann viel nicht tun, aber es ist immer noch manchmal genug, um Sie von zu speichern alles Refactoring. Zum Beispiel, wenn Image aus einem Modul kam Data.Image, Sie

    getan alle Funktionen in diesem Modul definiert
    import qualified Data.Image as IM 
    
    fillRect_qfi :: Colour -> Bounds -> IM.Image -> IM.Image 
    

    und verwenden können, auch durch Qualifier. Wenn Sie sich irgendwann entscheiden, die Implementierung zu wechseln, können Sie einfach den Import ändern.
    Natürlich ist dies nicht gut für Wechsel zwischen Typen regelmäßig, aber ganz okay für einmalige Änderungen.

ganz passend zu Ihrem Beispiel: diagrams verwendet sowohl die ersten Punkte; z.B. rect verwendet eine Klasse TrailLike, um seine Primitive zu implementieren, und diese Klasse enthält als "endgültige" Diagramme den Typ QDiagram, der im Backend für das Rendering parametrisiert wird.