Ho fatto una pugnalata a questo. Il risultato non è bello, ma funziona. Il TL; DR è che, alla fine, possiamo scrivere la funzione come questo, supponendo che ho fatto nessun errore paralizzanti:
haskellFunc string foo bar = cFunc <^ string <^> foo ^> bar
Abbiamo bisogno di alcune estensioni GHC per far funzionare tutto questo, ma sono piuttosto noiosi:
{-# 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 #-}
prima cosa definire un typeclass per rappresentare la natura comune di CString
, CFoo
, e CBar
, utilizzando withCType
come unico nome per 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
Poi alcuni tipi fittizi e le istanze in modo che potessi TYPECHECK questo in isolamento:
-- 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)
Il mio pensiero iniziale era che avremmo qualcosa di simile che saremmo utilizzare per richiamare le nostre funzioni sul sottostante C tipi ...
liftC :: CType a c => (c -> IO b) -> (a -> IO b)
liftC cFunc x = withCType x cFunc
Ma questo ci consente di sollevare solo le funzioni di un argomento. Vorremmo sollevare funzioni di più argomenti ...
Che funziona bene, ma sarebbe bello se non avessimo bisogno di definire uno di quelli per ogni area che cerchiamo. Sappiamo già che è possibile sostituire tutte le funzioni liftM2
, liftM3
, ecc. Con catene di <$>
e <*>
, e sarebbe bello fare lo stesso qui.
Quindi il mio primo pensiero è stato quello di provare a trasformare liftC
in un operatore e intersperse tra ogni argomento. Quindi sarebbe qualcosa del genere:
func <^> x <^> y <^> z
Beh ... non possiamo proprio farlo. Perché i tipi non funzionano. Considerate questo:
(<^>) :: CType a c => (c -> IO b) -> (a -> IO b)
cFunc <^> x = withCType x cFunc
La IO
parte di withCType
rende questo difficile. Per fare in modo che questa catena si concatri, dovremmo recuperare un'altra funzione del modulo (c -> IO b)
, ma invece torniamo alla ricetta IO
per produrla. Il risultato di invocare il precedente <^>
su una funzione "binaria", ad esempio, è IO (c -> IO b)
. È preoccupante.
Possiamo incidere su questo fornendo tre diversi operatori ... alcuni dei quali funzionano in IO
e alcuni dei quali non funzionano e li utilizzano nella giusta posizione in una catena di chiamate. Questo non è molto bello o carino. Ma funziona. Ci deve essere un modo più pulito per fare la stessa cosa ...
-- 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
Possiamo usare questo Frankenstein strano come questo (l'aggiunta di più <^>
s per le funzioni più alto-arità):
main = do
x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a'
print x
cFunc :: CString -> CInt -> CChar -> IO()
cFunc _ _ _ = pure()
Questo è alquanto inelegante. Mi piacerebbe vedere un modo più pulito per ottenere questo. E non amo i simboli che ho scelto per gli operatori ...
O se mai otteniamo parentesi idiom (o si desidera utilizzare SHE), '(| cFunc (cont (withCString stringa)) (cont (withCFoo foo)) (cont (withCBar bar)) |)' – copumpkin
Un altro vantaggio di riconoscendo che questo è solo "Cont" travestito è che ottieni altri gadget gratis. Supponiamo ad esempio di aver bisogno di una raccolta arbitraria di questi allocatori in stile CPS: si potrebbe semplicemente usare 'sequence',' traverse' o simile per ottenere una lista o un'altra raccolta di valori tutto in una volta. – copumpkin
Ancora una volta, haskell non delude. Così elegante e bellissima :) – ivokosir