私はこれを刺しました。結果はで、美しいではありませんが、動作します。 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
を使用して、CString
、CFoo
、および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))
。 liftM2
、liftM3
などの機能をすべて<$>
と<*>
のチェーンで置き換えることができます。ここでも同じことができればうれしいです。
私の最初の考えはliftC
を演算子に変えて、各引数の間に散在させようとしていました。だから、これは次のようになります:
func <^> x <^> y <^> z
まあ...私たちはそれほどできません。タイプが機能しないためです。 withCType
のIO
一部は、これが困難に
(<^>) :: 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)
や余分な構文のビットを持つ::私は...
また、慣用括弧(またはSHEを使用したい場合)、 '(| cFunc(contCString string))(cont(withCFoo foo))(cont(withCBar bar))|)' – copumpkin
これが偽装の「Cont」だけであることを認識すると、他のグッズを無料で手に入れることができます。たとえば、これらのCPSスタイルのアロケータの任意のコレクションが必要であったとしましょう。リストや他の値のコレクションを一度に取得するために 'sequence'、' traverse'などを使うことができます。 – copumpkin
もう一度、haskellは失望しません。とてもエレガントで美しい:) – ivokosir