Ich nahm einen Stich bei diesem. Das Ergebnis ist nicht schöne, aber es funktioniert. Der TL; DR ist, dass bis zum Ende, wir Ihre Funktion wie diese schreiben können, vorausgesetzt, ich keine lähmenden Fehler:
haskellFunc string foo bar = cFunc <^ string <^> foo ^> bar
Wir brauchen ein paar GHC Erweiterungen für diese zu arbeiten, aber sie sind ziemlich zahm:
{-# LANGUAGE MultiParamTypeClasses #-}
-- So that we can declare an instance for String,
-- aka [Char]. Without this extension, we'd only
-- be able to declare an instance for [a], which
-- is not what we want.
{-# LANGUAGE FlexibleInstances #-}
Zuerst definiere ich eine typeclass die gemeinsame Natur der CString
darzustellen, CFoo
und CBar
, unter Verwendung von withCType
als einheitliche Namen für withC___
:
-- I use c as the type variable to indicate that
-- it represents the "C" version of our type.
class CType a c where
withCType :: a -> (c -> IO b) -> IO b
Dann werden einige Dummy-Typen und Instanzen, so dass ich konnte dies in Isolation typecheck:
-- I'm using some dummy types I made up so I could
-- typecheck this answer standalone.
newtype CString = CString String
newtype CInt = CInt Int
newtype CChar = CChar Char
instance (CType String CString) where
-- In reality, withCType = withCString
withCType str f = f (CString str)
instance (CType Int CInt) where
withCType str f = f (CInt str)
instance (CType Char CChar) where
withCType str f = f (CChar str)
Mein erster Gedanke war, dass wir so etwas wie dieses haben würde, dass wir unsere Funktionen auf dem zugrunde liegenden C verwenden würde aufrufen Typen ...
liftC :: CType a c => (c -> IO b) -> (a -> IO b)
liftC cFunc x = withCType x cFunc
Aber das lässt uns nur Funktionen eines Arguments heben. Wir möchten Funktionen von mehreren Argumenten heben ...
liftC2 :: (CType a c, CType a' c') => (c -> c' -> IO b) -> (a -> a' -> IO b)
liftC2 cFunc x y = withCType x (\cx -> withCType y (cFunc cx))
Das funktioniert gut, aber es wäre toll, wenn wir nicht für jede arity einer von denen zu definieren, brauchten wir suchen. Wir wissen bereits, dass Sie alle die liftM2
, liftM3
usw. Funktionen mit Ketten von <$>
und <*>
ersetzen können, und es wäre schön, das gleiche zu tun.
So war mein erster Gedanke, zu versuchen liftC
in einen Bediener zu drehen, und es zwischen jedem Argumente durchsetzen. So würde es in etwa so aussehen:
Nun ... wir können das nicht ganz tun. Weil die Typen nicht funktionieren. Bedenken Sie:
(<^>) :: CType a c => (c -> IO b) -> (a -> IO b)
cFunc <^> x = withCType x cFunc
Der IO
Teil withCType
dies erschwert. Um dies gut zu verketten, müssten wir eine andere Funktion des Formulars (c -> IO b)
zurückbekommen, aber stattdessen bekommen wir das IO
Rezept zurück, um das zu produzieren. Das Ergebnis des Aufrufs des obigen <^>
für eine "binäre" Funktion ist beispielsweise IO (c -> IO b)
. Das ist beunruhigend.
Wir können dies umgehen, indem wir drei verschiedene Operatoren ... von denen einige in IO
arbeiten und von denen einige nicht arbeiten, und sie an der richtigen Position in einer Aufrufkette verwenden. Das ist nicht sehr gepflegt oder nett. Aber es funktioniert. Es muss ein sauberer Weg sein, diese gleiche Sache zu tun ...
-- Start of the chain: pure function to a pure
-- value. The "pure value" in our case will be
-- the "function expecting more arguments" after
-- we apply its first argument.
(<^) :: CType a c => (c -> b) -> (a -> IO b)
cFunc <^ x = withCType x (\cx -> return (cFunc cx))
-- Middle of the chain: we have an IO function now,
-- but it produces a pure value -- "gimme more arguments."
(<^>) :: CType a c => IO (c -> b) -> a -> IO b
iocFunc <^> x = iocFunc >>= (<^ x)
-- End of the chain: we have an IO function that produces
-- an IO value -- no more arguments need to be provided;
-- here's the final value.
(^>) :: CType a c => IO (c -> IO b) -> a -> IO b
iocFunc ^> x = withCType x =<< iocFunc
Wir haben dieses seltsame franken wie diese verwenden können (das Hinzufügen von mehr <^>
s für höhere arity Funktionen):
main = do
x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a'
print x
cFunc :: CString -> CInt -> CChar -> IO()
cFunc _ _ _ = pure()
Dies ist etwas unelegant. Ich würde gerne einen saubereren Weg sehen, um das zu erreichen. Und ich liebe nicht die Symbole I für diejenigen Betreiber wählte ...
Oder wenn wir jemals idiomatische Klammern bekommen (oder SHE verwenden wollen), '(| cFunc (cont (withCString string)) (cont (mitCFoo foo)) (cont (withCBar bar)) |)' – copumpkin
Ein weiterer Vorteil von zu erkennen, dass dies nur "Cont" in Verkleidung ist, dass Sie andere Leckereien kostenlos bekommen. Sagen wir zum Beispiel, dass Sie eine beliebige Sammlung dieser CPS-artigen Allokatoren benötigen: Sie könnten einfach 'sequence',' traverse' oder ähnliches verwenden, um eine Liste oder eine andere Sammlung von Werten auf einmal zu erhalten. – copumpkin
Noch einmal, Haskell enttäuscht nicht. So elegant und schön :) – ivokosir