Ich versuche, eine Funktion in haskell zu schreiben, die zwei Parameter und eine Funktion akzeptiert. Abhängig von den beiden Parametern wird es die gegebene Funktion ausführen oder nicht. Das Problem ist, dass die gegebenen Funktionen unterschiedliche Typensignaturen haben können. Im Pseudocode:

functionB:: String -> IO()


functionC:: String -> String -> IO() 

functionA :: String -> String ->(???)-> IO()
functionA parm1 parm2 f = if parm1 == parm2
                          then f
                          else hPutStrLn "error"

Die Funktion sollte mit der Funktion C oder B arbeiten können und wie Sie sehen können, haben B und C eine andere Typensignatur. Schaffst du das in Haskell? Wenn ja, wie lautet die Typensignatur der Funktion?

1
demathieu 1 Jän. 2016 im 21:05

3 Antworten

Beste Antwort

Es ist möglich, dass eine Funktion basierend auf einem Argument "zwei verschiedene Signaturen" hat. Die Funktion hat wirklich nur eine allgemeinste Signatur, aber Sie können sie fälschen, damit es so funktioniert.

functionA :: String -> String -> a -> a
functionA parm1 parm2 f = if parm1 == parm2 
                          then f
                          else putStrLn "error" -- uh oh

Die Unterschrift besagt, dass dies das Gleiche wie das dritte Argument zurückgeben wird. Damit:

functionA "foo" "bar" functionB :: String -> IO ()
functionA "foo" "bar" functionC :: String -> String -> IO ()

Mit anderen Worten, die erste Zeile enthält ein zusätzliches Argument (insgesamt vier) und die zweite Zeile zwei (insgesamt fünf).

Das Problem ist, dass es nicht funktioniert, weil putStrLn "error" keinen dieser Typen hat und wir es brauchen, um "beide" zu haben.

Eine Möglichkeit besteht darin, eine Typklasse zu erstellen, die die Operationen charakterisiert, die Sie für beide Typen benötigen. In diesem Fall möchten Sie vielleicht einen Fehler drucken?

{-# LANGUAGE FlexibleInstances #-}

class CanPrintError a where
    printError :: String -> a

instance CanPrintError (IO ()) where
    -- specialized to:
    -- printError :: String -> IO ()
    printError = putStrLn

instance (CanPrintError b) => CanPrintError (a -> b) where
    -- specialized to:
    -- printError :: (CanPrintError b) => String -> a -> b
    printError err _ = printError err

Beachten Sie, dass ich die zweite Instanz rekursiv gemacht habe, sodass CanPrintError nicht nur Instanzen für String -> IO () und String -> String -> IO () enthält, sondern auch Instanzen für alle Funktionen mit mehreren Argumenten, die mit {{X3} enden. } sowie

String -> Int -> (Int -> Maybe Bool) -> IO ()

Es ist möglich, es nur für die beiden fraglichen Typen zu machen, obwohl ich dadurch Ihre Motivationen in Frage stelle.

Jetzt fügen wir der Signatur von functionA nur die erforderliche Einschränkung hinzu:

functionA :: (CanPrintError a) => String -> String -> a -> a
functionA parm1 parm2 f = if parm1 == parm2
                          then f
                          else printError "error"

Sie können sich vorstellen, printError durch eine beliebige Operation zu ersetzen. Dies ist so ziemlich das, wofür Typklassen sind. :-)

Ich schlage jedoch vor, eine Frage mit genaueren Details Ihres Problems zu stellen, da es nach etwas riecht, das möglicherweise eine sauberere Lösung bietet.

3
luqui 2 Jän. 2016 im 03:58

Die Funktion const erstellt eine neue Funktion, die immer den ersten Parameter zurückgibt, unabhängig davon, was Sie an ihn übergeben. Die Typensignatur lautet:

const :: a -> b -> a

Dies bedeutet, dass wir Dinge schaffen können wie:

> const 4 "String"
4

> let fn = const (\x -> print x)
> :t fn
fn :: Show a => b -> a -> IO ()
> fn [1, 2, 3, 4, 5] (Just 3)
Just 3

In Ihrem Beispiel können Sie diese Funktion verwenden, um eine "const 'd" -Version von functionB an functionA zu übergeben:

functionB x = putStrLn x
functionC x y = putStrLn x >> putStrLn y 

functionA :: String -> String -> (String -> String -> IO()) -> IO()
functionA parm1 parm2 f = if parm1 == parm2
                          then f parm1 parm2
                          else putStrLn "error"

> functionA "Foo" "Foo" (const functionB)
Foo
> functionA "Foo" "Foo" functionC
Foo
Foo
> functionA "Foo" "Bar" undefined
error
3
Mokosha 2 Jän. 2016 im 22:57

Der angegebene Code würde nicht wie erwartet funktionieren. Wie oben erwähnt, erfordert hPutStrln ein Dateihandle, beabsichtigte man putStrLn? Wenn ja, ist die Signatur von putStrLn String -> IO () und somit putStrLn "error" nur IO (). Da die Zeile else f kein Argument für f enthält, muss die Typensignatur von f IO () sein - die Typensignatur wäre functionA :: String -> String -> IO () -> IO () (tatsächlich könnte es functionA :: Eq t => t -> t -> IO () -> IO () seit dem sein Es ist noch nichts erforderlich, was eine Zeichenfolge erfordert.

Das Problem ist, wenn Sie es nicht so machen können, dass f einen oder zwei Eingänge aufnehmen kann, ist es nur der eine oder andere. Sie könnten else f durch else f parm1 ersetzen und dann wäre f vom Typ String -> IO () oder durch else parm1 parm2 und dann wäre f vom Typ String -> String -> IO ().

0
beigel 1 Jän. 2016 im 20:32