ダイナミックディスパッチの聖杯

大きな問題静的型付けボトルネックは、異種コレクションと可変機能です。 したがって、入力データが1つのADTピースにある場合、RPCライブラリでアプローチが頻繁に発生し、メソッドには同じフラットタイプ「[Foo]-> IO Foo」があり、その実装は逆シリアル化/シリアル化をコピーして貼り付けるため、不便でエラーが発生します、含む ランタイム。



この問題の解決策は、Haskellの実際の使用の最初からほとんど気になり、最後の夜、インスピレーションは6.5ミリオレッグにもなり、占いセッションとghciとの会話の後、すべてがうまくいきました。











リスト/辞書Text-> Methodを作成するとします。 そしてすぐに残念:



methods :: [(String, ????)] methods = [ ("add", \xy -> return (x + y)) , ("reverse", \s -> return (reverse s)) ]
      
      







すべてのメソッドには異なるタイプがあります。 これは、それらを不透明なボックスに入れることで解決できます。



 data BaconBox = forall a. a methods :: [(String, BaconBox)]
      
      







しかし、そのような箱は開梱できません。 開梱して何ができるかは明確ではありません。 これは、その中に存在できる型のクラスが必要であることを意味し、関数を通常の形式に戻す方法を記述します。 入力データを逆シリアル化し、結果をシリアル化する準備ができている関数。



この例では、「プロトコル」読み取り/表示が使用されます。 最高ではありませんが、残りは同じです。 データ型はそれぞれ文字列です。



 class Tossable t where toss :: [String] -> t -> IO String data BaconBox = forall a. (Tossable a) => BaconBox a
      
      







このような定義を使用すると、トス関数が定義されているボックス内に任意の型を配置できることがコンパイラーにとってもすでに明らかです。 ここでは、2番目の問題にスムーズに進みます。 結局のところ、私は呼び出されたメソッドに引数と数量を持たせたいと思っています。 つまり これにより、マーシャリングプロシージャが定義されているボックスにRPCハンドラーを簡単に配置できます。



OlegとHaskel wikiには似たような例があります-任意のタイプの任意の数の引数を持つprintf: www.haskell.org/haskellwiki/Varargs 。 しかし、これはまったく正しくありません。 しかし、まったく同じではありません! awesome.gif



このトリックは、基本形式と引数の折りたたみという2つのインスタンスに基づいています。



基本フォームは、データが収集されたときに何をするかを決定します。 また、メソッドの出力タイプのクラスも定義します。



 instance (Show a) => Tossable (IO a) where toss [] f = fmap show f toss _ _ = fail "  "
      
      







すべての引数が使用され、手元に既製の値がある場合(この場合、これは実行する必要があるアクションです)、つまり すべての引数が関数に適用されます-それを実行し、すぐに結果をシリアル化します。 残りの引数は捨てることも、誓うこともできます-私は、正確さを推測するよりも間違いを投げることを好みます。



議論を折りたたむために、非常に興味深いインスタンスが使用され、その全体的な栄光の中で、一般的な機能的アプローチと特に正しい型システムの力を実証しています。 ここでは、入力引数のクラスが固定されており、RPCメソッドの呼び出しで使用される型のデシリアライザーを確実に成功させることができます。



 instance (Read a, Tossable t) => Tossable (a -> t) where toss [] _ = fail " " toss (a:as) f = toss as (f (read a))
      
      







魔法の定義にもかかわらず、何が起こっているかは非常に簡単です。 型が関数(a-> t)であり、まだ引数がある場合、関数が期待する型に従って次の引数を逆シリアル化し、それに適用します。



結果が基本形式の形式である場合、関数に引数を適用した後、関数が再び判明する場合が良いです-手順を繰り返します。



 doAnd :: Bool -> Bool -> IO Bool doAnd ab = return (a && b) doSum3 :: Double -> Double -> Double -> IO Bool doSum3 xyz = return (x + y + z) main = do toss [] (doAnd True True) >>= print toss ["True"] (doAnd True) >>= print toss ["True", "True"] doAnd >>= print toss ["42", "2.71828", "3.14159"] doSum3 >>= print
      
      







出来上がり! この関数は、引数を静かにアンパックしてフィードし、結果をパックします。



最後のタッチ-ボックスからのメソッドの動的なディスパッチが残ります。 これを行うには、ボックスに少しメタデータを追加し、検索機能を追加します。



 data BaconBox = forall a. (Tossable a) => BaconBox String a tossBacon :: (Show a) => [BaconBox] -> String -> [String] -> IO a tossBacon [] _ _ = fail "  " tossBacon (BaconBox bn bf : bs) name args | bn == name = toss args bf | otherwise = tossBacon bs name args
      
      







コンパイラがパターンマッチング以外の方法で実存コンテナを開くときに爆発しないように、特別な関数が必要です。 FP-worldの問題...



 -- , ,    bacon :: [BaconBox] bacon = [ BaconBox "bool.and" doAnd , BaconBox "num.sum3" doSum3 ] main :: IO () main = do (method:args) <- getArgs tossBacon bacon method args >>= print
      
      







そのため、任意の(ただし入力と出力は厳密に制限されている)型の関数を内部で突いて名前で呼び出すことができ、マーシャリングルーチン作業を自動的に実行できるコンテナーがあります。 コードが少ないほど、バグが少なくなります。 やった!



All Articles