12

withCStringのような機能をチェーンする方法はありますか?これは、f :: Foo -> (CFoo -> IO a) -> IO aのような何かの の機能を意味します。withCStringのような関数をチェーンする方法はありますか?

haskellFunc string foo bar = 
    withCString string $ \ cString -> 
    withCFoo foo $ \ cFoo -> 
     withCBar bar $ \ cBar -> 
     cFunc cString cFoo cBar 

しかし、私のような何かやりたい:と

haskellFunc = (withCString |.| withCFoo |.| withCBar) cFunc 

は例えば、私のような何かをするだろう、機能cFunc :: CString -> CFoo -> CBar -> IO()

Usualyがあると言うことができますいくつかの適切な合成演算子|.|

私は多くのCバインディングを持つライブラリを書いていますが、この定型文はよく となります。私は何か間違っているのですか?

答えて

17

を私はそれらの演算子のために選択したシンボルを愛していないあなたはこれらのa -> (b -> IO c) -> IO cの機能を構成するためContinuation applicativeを使用することができます

haskellFunc' :: String -> Foo -> Bar -> IO() 
haskellFunc' string foo bar = flip runCont id $ 
    cFunc <<$>> withCString string <<*>> withCFoo foo <<*>> withCBar bar 
    where 
    f <<$>> x = f <$> cont x 
    f <<*>> x = f <*> cont x 
+1

また、慣用括弧(またはSHEを使用したい場合)、 '(| cFunc(contCString string))(cont(withCFoo foo))(cont(withCBar bar))|)' – copumpkin

+2

これが偽装の「Cont」だけであることを認識すると、他のグッズを無料で手に入れることができます。たとえば、これらのCPSスタイルのアロケータの任意のコレクションが必要であったとしましょう。リストや他の値のコレクションを一度に取得するために 'sequence'、' traverse'などを使うことができます。 – copumpkin

+1

もう一度、haskellは失望しません。とてもエレガントで美しい:) – ivokosir

1

残念ながら、あなたがしたいと思うような一般的な機能を実行することはできません。問題はHaskellの型システムにあります。あなたの例では、cFuncは3つの引数を取るので、あなたの便利関数を書くときに3つの引数をとったC関数が必要になります。任意の数の引数のcFuncを受け入れることができる関数を書く方法はありません。ハスケルの型システムはあまりに厳格です。しかし、それを念頭に置いて、異なる数の引数を持つcFuncのそれぞれについて、いくつかの異なる関数を書くことができます。これが努力する価値があるかどうかは、どのくらいの頻度でその種類のボイラープレートを使用する必要があるかによって決まります。

cApply2 :: (a' -> b' -> c) 
     -> (a -> (a' -> c)) 
     -> (b -> (b' -> c)) 
     -> a -> b -> c 
cApply2 cFunc withArg1 withArg2 arg1 arg2 = 
    withArg1 arg1 $ \cArg1 -> 
    withArg2 arg2 $ \cArg2 -> 
     cFunc cArg1 cArg2 

cApply3 :: (a' -> b' -> c' -> d) 
     -> (a' -> (a -> d)) 
     -> (b' -> (b -> d)) 
     -> (c' -> (c -> d)) 
     -> a -> b -> c -> d 
cApply3 cFunc withArg1 withArg2 withArg3 arg1 arg2 arg3 = 
    withArg1 arg1 $ \cArg1 -> 
    withArg2 arg2 $ \cArg2 -> 
     withArg3 arg3 $ \cArg3 -> 
     cFunc cArg1 cArg2 cArg3 

ここでC関数を使用できます。

haskellFunc :: String -> Foo -> Bar -> IO() 
haskellFunc = cApply3 cFunc withCString withCFoo withCBar 
4

私はこれを刺しました。結果はで、美しいではありませんが、動作します。 TL; DRは年末までに、私たちは私が壊滅的エラーが行われていないと仮定すると、このようなあなたの関数を書くことができ、ということです:私たちは仕事に、このためのいくつかのGHC拡張を必要とするが、彼らはかなり飼いならされている

haskellFunc string foo bar = cFunc <^ string <^> foo ^> bar 

を:

{-# 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 #-} 

まず、私はwithC___のための単一の名前としてwithCTypeを使用して、CStringCFoo、およびCBarの共通な性質を表現する型クラスを定義します。

-- 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 

その後、いくつかのダミーのタイプとインスタンス私が単独でこれをです。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) 

私の最初に考えたのは、我々は、基礎となるCの上に私たちの関数を呼び出すために使用したい、このようなものを持っているだろうということでしたタイプ...

liftC :: CType a c => (c -> IO b) -> (a -> IO b) 
liftC cFunc x = withCType x cFunc 

しかし、それは1つの引数の機能を持ち上げることができます。私たちは複数の引数の機能を持ち上げたいと思います...うまく動作しますが、我々は後にしているすべてのアリティのためのそれらのいずれかを定義する必要がなかった場合、それは素晴らしいことだ

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)) 

liftM2liftM3などの機能をすべて<$><*>のチェーンで置き換えることができます。ここでも同じことができればうれしいです。

私の最初の考えはliftCを演算子に変えて、各引数の間に散在させようとしていました。だから、これは次のようになります:

func <^> x <^> y <^> z 

まあ...私たちはそれほどできません。タイプが機能しないためです。 withCTypeIO一部は、これが困難に

(<^>) :: CType a c => (c -> IO b) -> (a -> IO b) 
cFunc <^> x = withCType x cFunc 

:これを考えてみましょう。これをうまく連鎖させるには、(c -> IO b)という別の関数を返す必要がありますが、その代わりにIOというレシピが返されます。たとえば、上記の<^>を「バイナリ」関数で呼び出すと、結果はIO (c -> IO b)になります。それはうんざりです。

3つの異なる演算子を提供することで、この問題を解決することができます。そのうちのいくつかはIOで動作し、その一部はコールチェーンの正しい位置で使用します。これは非常にきちんとしたものではありません。しかし、それは動作します。私たちは、(高アリティの機能のためのより多くの<^>秒を追加)このように、この奇妙なフランケンシュタインを使用することができます

-- 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 

...これと同じことを行うためのクリーンな方法が存在しなければならない:

main = do 
    x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a' 
    print x 

cFunc :: CString -> CInt -> CChar -> IO() 
cFunc _ _ _ = pure() 

これは、やや優しい。私はこれをよりクリーンな方法で見ることが大好きです。

import Control.Monad.Cont 

haskellFunc :: String -> Foo -> Bar -> IO() 
haskellFunc string foo bar = flip runCont id $ 
    cFunc <$> 
     cont (withCString string) <*> 
     cont (withCFoo foo) <*> 
     cont (withCBar bar) 

や余分な構文のビットを持つ::私は...

関連する問題