2016-11-23 1 views
2

Während ich mit Haskell und Conduit spielte, stieß ich auf ein Verhalten, das ich schwer erklären kann. Lassen Sie mich zunächst eine Liste aller Module und Spracherweiterungen, die mein Problem zu ladende müssen zu reproduzieren:Typeclass Constraints, Polymorphismus und Maniok-Conduit

{-# LANGUAGE FlexibleContexts #-} 

import Conduit       -- conduit-combinators 
import Data.Csv      -- cassava 
import Data.Csv.Conduit    -- cassava-conduit 
import qualified Data.ByteString as BS -- bytestring 
import Data.Text (Text)    -- text 
import Control.Monad.Except   -- mtl 
import Data.Foldable 

Zuerst habe ich die allgemeinste CSV-Parsing Leitung:

pipeline :: (MonadError CsvParseError m, FromRecord a) 
     => ConduitM BS.ByteString a m() 
pipeline = fromCsv defaultDecodeOptions NoHeader 

Dann wollte ich Ausgang die Anzahl der Elemente in jeder Reihe meiner CSV-Datei - ich weiß, dass das ziemlich albern und nutzlos ist und dass es eine Milliarde anderer Möglichkeiten gibt, diese Art von Dingen zu tun, aber das war nur ein Spielzeugtest. So

Ich öffnete GHCi und versuchte dies:

ghci> :t pipeline .| mapC length 

Wie erwartet, dies nicht funktioniert, weil die Einschränkung FromRecord a garantiert nicht, dass aFoldable ist. So definierte ich die folgende Leitung:

pipeline2 :: (MonadError CsvParseError m, FromField a) 
      => ConduitM BS.ByteString [a] m() 
pipeline2 = fromCsv defaultDecodeOptions NoHeader 

Dies ist eine rechtliche Definition, weil FromField a => FromField [a] ist eine Instanz FromRecord nach der Maniok Dokumentation.

An dieser Stelle bin ich glücklich und hoffnungsvoll, weil [] eine Instanz von Foldable ist. Also, noch einmal, ich öffne GHCi, und ich versuche:

ghci> :t pipeline2 .| mapC length 

Aber ich bekomme:

<interactive>:1:1: error: 
    • Could not deduce (FromField a0) arising from a use of ‘pipeline2’ 
     from the context: MonadError CsvParseError m 
     bound by the inferred type of 
       it :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 
     at <interactive>:1:1 
     The type variable ‘a0’ is ambiguous 
     These potential instances exist: 
     instance FromField a => FromField (Either Field a) 
      -- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’ 
     instance FromField BS.ByteString 
      -- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’ 
     instance FromField Integer 
      -- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’ 
     ...plus 9 others 
     ...plus 11 instances involving out-of-scope types 
     (use -fprint-potential-instances to see them all) 
    • In the first argument of ‘(.|)’, namely ‘pipeline2’ 
     In the expression: pipeline2 .| mapC length 

Also mein Verständnis ist, dass meine pipeline2 angegeben ist nicht genug.

Aber jetzt, wenn ich versuche, eine triviale Leitung mit einem (fast) gleichen Typs zu schmieden:

pipeline3 :: (MonadError CsvParseError m, FromField a) 
      => ConduitM a [a] m() 
pipeline3 = awaitForever $ \x -> yield [x] 

Wieder ich GHCi öffnen und versuchen:

ghci> :t pipeline3 .| mapC length 

Diesmal bekomme ich:

Diesmal versteht GHCi, dass ich die Definition vonnicht noch weiter spezifizieren muss.

Also meine Frage: Warum gibt es ein Problem mit pipeline2? Gibt es eine Möglichkeit, die allgemeinste "Pipeline" zu definieren, ohne die Art der Ausgabe des Conduits näher zu spezifizieren? Ich dachte, dass eine Liste von FromField Objekte würde ausreichen.

Es fühlt sich an, als ob ich einen wichtigen Punkt über Typklassen und wie man Funktionen komponiert, oder hier Conduit Objekte, in einer polymorphen Weise fehlt.

Vielen Dank für Ihre Antworten!

Antwort

2

Der Fehler, den Sie bekam ...

• Could not deduce (FromField a0) arising from a use of ‘pipeline2’ 
    from the context: MonadError CsvParseError m 
    bound by the inferred type of 
      it :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 
    at <interactive>:1:1 
    The type variable ‘a0’ is ambiguous 

... sagt, dass a0 nicht eindeutig ist, was es unmöglich macht, verwendet werden, um herauszufinden, welche Instanz von FromField sollte. Was macht es mehrdeutig? Die Fehlermeldung erwähnt auch die abgeleitete Art des Ausdrucks:

it :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 

Es gibt keine a0 in dieser Art. Dies führt zu Mehrdeutigkeiten, da es keine Spezialisierung dieses Typs gibt, die die FromField-Instanz angeben kann - es gibt nicht genug Material für die Typüberprüfung. In Ihrem dritten Beispiel auf der anderen Seite ...

pipeline3 .| mapC length 
    :: (FromField a, MonadError CsvParseError m) => ConduitM a Int m() 

... die Art des Feldes im Gesamttyp nicht angezeigt, und so wird die Mehrdeutigkeit abgewendet.

Es ist hervorzuheben, dass mit pipeline2 per se nichts falsch ist. Das Problem tritt nur auf, weil length nützliche Informationen aus dem Gesamt-Typ eliminiert. Im Gegensatz dazu diese zum Beispiel funktioniert gut:

GHCi> :t pipeline2 .| mapC id 
pipeline2 .| mapC id 
    :: (MonadError CsvParseError m, FromField a) => 
    ConduitM BS.ByteString [a] m() 

Um pipeline2 mit length zu verwenden, benötigen Sie den Typ des Feldes durch eine Typanmerkung angeben:

GHCi> -- Arbitrary example. 
GHCi> :t (pipeline2 :: MonadError CsvParseError m => ConduitM BS.ByteString [Int] m()) .| mapC length 
(pipeline2 :: MonadError CsvParseError m => ConduitM BS.ByteString [Int] m()) .| mapC length 
    :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 

Alternativen zu umfassen die Anmerkung der TypeApplications Erweiterung mit (Kredit ben Antwort für mich daran zu erinnern) ...

GHCi> :set -XTypeApplications 
GHCi> :t pipeline2 @_ @Int .| mapC length 
pipeline2 @_ @Int .| mapC length 
    :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 

... und Angabe th e Feldtyp über ein Proxy-Argument.

{-# LANGUAGE ScopedTypeVariables #-} 
{-# LANGUAGE FlexibleContexts #-} 

import Data.Proxy 
-- etc. 

rowLength :: forall m a. (MonadError CsvParseError m, FromField a) 
    => Proxy a -> ConduitM BS.ByteString Int m() 
rowLength _ = p2 .| mapC length 
    where 
    p2 :: (MonadError CsvParseError m, FromField a) 
     => ConduitM BS.ByteString [a] m() 
    p2 = pipeline2 
GHCi> :t rowLength (Proxy :: Proxy Int) 
rowLength (Proxy :: Proxy Int) 
    :: MonadError CsvParseError m => ConduitM BS.ByteString Int m() 
2

pipeline3 ist eine Leitung typisiert wie ConduitM a [a] m() (ignoriert die Einschränkungen für jetzt). Wenn Sie also length darüber abbilden, erhalten Sie ConduitM a Int m(); Die a ist immer noch in dem ersten Typparameter vorhanden, und daher kann die FromField a-Einschränkung bestehen bleiben, die darauf wartet, an Verwendungsstellen instanziiert zu werden.

pipeline2 ist eine Rohrleitung typisiert wie ConduitM BS.ByteString [a] m(). Wenn Sie nun length darüber abbilden, erhalten Sie ConduitM BS.ByteString Int m(). Es gibt keine a irgendwo diesen Typ, so dass die FromField a Instanz kann nicht auf Nutzung Websites ausgewählt werden. Stattdessen muss es sofort ausgewählt werden. Aber nichts in pipeline2 .| mapC length sagt was a sein sollte. Deshalb beschwert es sich, dass a mehrdeutig ist.

Soweit ich weiß (nicht vertraut mit Conduits), sollte das das einzige Problem mit Ihrer ersten Definition sein. FromRecord garantiert nicht Foldable, aber es hat Instanzen, die Foldable sind; Sie müssen nur den Typ festhalten, der verwendet wird, weil length es nicht tut. Sie könnten eine Ausdruckssignatur unter pipeline verwenden, wenn Sie sie verwenden, die TypeApplication Erweiterung, eine weniger polymorphe Definition (die keine Neuimplementierung wie pipeline2 sein muss; Sie könnten pipeline' = pipeline haben, wenn Sie die richtige Signatur unter pipeline' hätten).

+0

Danke, dass ich von 'TypeApplications' erinnert. Ich hob das für meine Antwort auf. – duplode