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?
3 Antworten
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.
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
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 ()
.
Neue Fragen
haskell
Haskell ist eine funktionale Programmiersprache mit starker statischer Typisierung, verzögerter Auswertung, umfassender Unterstützung für Parallelität und Parallelität sowie einzigartigen Abstraktionsfunktionen.