2017-01-28 13 views
0

Ich versuche einen sehr einfachen Argument Parser gemustert aus this example. Das erste Argument sollte ein Double sein, das zweite sollte eine ganze Zahl sein, und wenn sie nicht beide dieser Typen sind, würde ich gerne die Standardargumente für beide im else angeben. Hier ist, was ich habe:Einfache Art Fehler beim Parsen des Ergebnisses von getArgs

parseArgs :: [String] -> (Double, Integer) 
parseArgs args = do 
    if length(args) == 2 then do 
    let v1 = read (args !! 0) :: Double 
    let v2 = read (args !! 1) :: Integer 
    return (v1, v2) 
    else do 
    let v1 = read ("0.5") :: Double 
    let v2 = read ("5") :: Integer 
    return (v1, v2) 

Ich verstehe, gibt es anspruchsvollere Wege zu analysieren Argumente mit einem optparse-inspirierten Design mit Applicative, aber ich bin noch nicht da.

Hier ist der Fehler, den ich bekommen:

myscript.hs:186:5-31: error: … 
    • Couldn't match type ‘(Double, Integer)’ with ‘Integer’ 
     Expected type: (Double, Integer) 
     Actual type: (Double, (Double, Integer)) 
    • In a stmt of a 'do' block: return (v1, v2) 

ich das nicht verstehen. Wenn ich auf die Signatur von getArgs schaue, sehe ich nichts Seltsames, was darauf hindeutet, dass ich mein Int nicht zurückbekomme oder dass es (Double,Integer) anstatt nur Integer zurückgibt.

Wie kann ich das richtig machen?

+2

'return' ist eine Funktion von' a -> ma', wobei 'm 'ist eine Monade; es ist nicht Teil der 'do'-Syntax. Schreiben Sie einfach '(v1, v2)', um den Wert '(v1, v2)' zu erhalten. – Ryan

+0

Danke. Ich habe nur return hinzugefügt, weil, als ich nur die let-Anweisungen hatte, der Compiler mir gesagt hat, dass die letzte Zeile eines do-Blocks ein Ausdruck sein muss, und dass er kompilieren konnte. Ich verstehe dieses Verhalten jetzt besser basierend auf den Antworten unten. – Mittenchops

+1

Beachten Sie, dass 'read' einen Fehler auslöst (und das Programm abstürzt), wenn die angegebenen Zeichenfolgen nicht in den entsprechenden Typ umgewandelt werden können. Wenn Sie das verbessern wollen, empfehle ich ['readMaybe'] (https://hackage.haskell.org/package/base-4.9.1.0/docs/Text-Read.html#v:readMaybe) von' Text .Read', das 'Nothing' ausgibt, wenn es fehlschlägt. – duplode

Antwort

5

Es sieht für mich, dass Sie einige Erfahrung in der „Imperativ“ Welt, wo return ein Stichwort zu sein scheint Inhalt aus einer Funktion zurückzukehren.

In Haskell wird jedoch die return Anweisung verwendet, um Monaden zu definieren/zu verwenden. Das selbe für den do Block übrigens. Ihr Typ (Int,Double) kann als ein Monadic-Typ verwendet werden (Respekt an @duplode dafür). Aber in diesem Kontext sieht es nicht sehr danach aus, dass Sie Monaden verwenden möchten/müssen, da dies wie eine einfache Funktion aussieht.

So können Sie das Problem mit lösen:

parseArgs :: [String] -> (Double, Integer) 
parseArgs args = 
    if length(args) == 2 then 
    let v1 = read (args !! 0) :: Double 
     v2 = read (args !! 1) :: Integer 
    in (v1, v2) 
    else 
    let v1 = read ("0.5") :: Double 
     v2 = read ("5") :: Integer 
    in (v1, v2) 

So verwenden Sie in zu sagen, dass Sie die v1 verwenden und v2 usw. in einem Ausdruck. Trotzdem ist es nicht sehr "Haskell-ish". Ein besserer Weg, dies zu tun, ist der Einsatz von Wachen (Drop die if-else):

parseArgs :: [String] -> (Double, Integer) 
parseArgs args | length args == 2 = 
        let v1 = read (args !! 0) :: Double 
         v2 = read (args !! 1) :: Integer 
        in (v1, v2) 
       | otherwise = 
        let v1 = read ("0.5") :: Double 
         v2 = read ("5") :: Integer 
        in (v1, v2) 

Schließlich ich nicht wirklich sehen, warum Sie all diese let Anweisungen verwenden, und die Typen ohnehin angeben. Sie können es einfach umschreiben:

parseArgs :: [String] -> (Double, Integer) 
parseArgs args | length args == 2 = (read (args !! 0), read (args !! 1)) 
       | otherwise = (read "0.5", read "5") 

Jetzt sind wir immer noch nicht fertig. Wegen args hat Länge zwei, dh es hat die Form [a,b]. Wir können dieses Muster im Kopf verwenden:

parseArgs :: [String] -> (Double, Integer) 
parseArgs [a,b] = (read a, read b) 
parseArgs _  = (read "0.5", read "5") 

Der Vorteil ist, dass Sie nicht mehr (!!) zu verwenden, brauchen das i -te Element zu erhalten: Überprüfung und Anpassung wird gleichzeitig so getan zu sprechen.

Eine letzte Verbesserung, die ich vorschlagen, ist die read im zweiten Fall zu verzichten: Sie einfach 0.5 eingeben und 5:

parseArgs :: [String] -> (Double, Integer) 
parseArgs [a,b] = (read a, read b) 
parseArgs _  = (0.5, 5) 
+2

Es gibt tatsächlich eine 'Monoid a => Monad ((,) a)' Instanz für Paare, die genauso funktioniert wie 'Writer'. Das erklärt den Counterintuitve-Typ-Fehler, den der OP erhalten hat, wobei der von GHC gemeldete "tatsächliche Typ" ein verschachteltes Paar ist. – duplode

+1

@ duplode: ah das erklärt. Dennoch ist es nicht sinnvoll, hier Monaden zu verwenden, da dies eine reine Funktion ist. Vielen Dank. –

+1

In der Tat - das OP braucht definitiv keine Schriftsteller Monade hier. – duplode

6

Sie müssen nicht do Notation hier verwenden, da dies für die Behandlung einiger monadischen Typ ist. Sie können nur auf der Eingangsliste übereinstimmen:

parseArgs :: [String] -> (Double, Integer) 
parseArgs [d, i] = (read d, read i) 
parseArgs _ = (0.5, 5) 
Verwandte Themen