0

次から始めましょう

data A = A String deriving Show
data B = B String deriving Show

class X a where
    spooge :: a -> Q

[ Some implementations of X for A and B ]

ここで、それぞれ show' と read' という名前の show と read のカスタム実装があり、Show をシリアル化メカニズムとして利用しているとします。show' と read' に型を持たせたい

show' :: X a => a -> String
read' :: X a => String -> a

だから私は次のようなことができます

f :: String -> [Q]
f d = map (\x -> spooge $ read' x) d

データがあった可能性のある場所

[show' (A "foo"), show' (B "bar")]

要約すると、共通の型クラスを共有するさまざまな型のものをシリアル化したいので、逆シリアル化されたものでそれらの個別の実装を自動的に呼び出すことができます。

さて、次のようなラッパー型を生成するテンプレート haskell を記述できることに気付きました。

data XWrap = AWrap A | BWrap B deriving (Show)

ラップされた型をシリアライズして、型情報が保存されることを保証し、少なくとも XWrap を取り戻すことができるようにします...しかし、haskell ninja-ery を使用するより良い方法はありますか?

編集

さて、もっとアプリケーション固有にする必要があります。これは API です。ユーザーは、自分の As、B、および f を適切と思われるように定義します。XWrapやスイッチなどを更新する残りのコードをハッキングしてほしくありません。私が妥協しても構わないと思っているのは、すべての A、B などの 1 つのリストを何らかの形式で作成することです。なんで?

これがアプリケーションです。Aは「FTPサーバーからファイルをダウンロードする」です。Bは「flacからmp3に変換」です。A には、ユーザー名、パスワード、ポートなどの情報が含まれています。B にはファイル パス情報が含まれます。多くの A と B が存在する可能性があります。数百。喜んでプログラムにコンパイルしてくれる人がたくさんいます。2はほんの一例です。A と B は X であり、X を「チケット」と呼びます。Q は IO () です。Spooge は runTicket です。チケットを関連するデータ型に読み込んでから、ディスク上のものから読み取ったものに対して runTicket を実行する汎用コードを記述したいと考えています。ある時点で、シリアル化されたデータに型情報を詰め込む必要があります。

4

4 に答える 4

4

私はまず、そこにいるすべての幸せなリスナーに非常に良い方法を強調したいと思いXWrap ます。多くの場合、TemplateHaskellを使用して作成するよりも速く自分で作成できます。

あなたは、「少なくとも」を取り戻すことができると言います。それは、型をXWrap回復できなかったか、型クラスをそれらで使用できなかったかのようにです。違います!あなたも定義することができますABXWrap

separateAB :: [XWrap] -> ([A],[B])

それらを混ぜ合わせたくない場合は、別々にシリアル化する必要があります。

これはhaskellninja-eryよりも優れています。多分あなたは任意のインスタンスを処理する必要はないかもしれません、多分あなたが作ったものだけです。


元のタイプを本当に戻す必要がありますか?デシリアライズされたデータだけが必要なために存在型を使用したい場合は、それ自体spoogeをシリアライズするか、シリアル化するQ中間データ型を使用して、デシリアライズして本当に優れたスプージングPoisedToSpoogeに必要なすべてのデータを提供してください。それもインスタンスにしてみませんか?X

Xに変換するメソッドをクラスに追加できますPoisedToSpooge

toPoisedToSpooge舌から上手くつまずくような楽しいものと言えますね。:)

とにかく、これはあなたの型システムの複雑さを取り除くと同時に、で厄介な曖昧な型を解決するでしょう

f d = map (\x -> spooge $ read' x) d  -- oops, the type of read' x depends on the String

read'で置き換えることができます

stringToPoisedToSpoogeToDeserialise :: String -> PoisedToSpooge -- use to deserialise

定義します

f d = map (\x -> spooge $ stringToPoisedToSpoogeToDeserialise x) -- no ambiguous type

もちろん、もっと簡潔に書くことができます

f = map (spooge.stringToPoisedToSpoogeToDeserialise)

私はここであなたのコードをより簡潔にすることを提案することの皮肉を認識していますが。:)

于 2012-10-14T03:10:34.757 に答える
4

本当に必要なものが異種リストである場合は、存在型を使用してください。シリアル化が必要な場合は、Cereal + ByteString を使用します。動的型付けが必要な場合は、これが実際の目標だと思いますが、Data.Dynamic を使用してください。これがあなたが望むものではない場合、または私に拡大してほしい場合は、シャープキーを押してください.

あなたの編集に基づいて、サンクのリストが機能しない理由はわかりません。IO ()「FTP サーバーからファイルをダウンロードする」操作と「flac から MP3 に変換する」操作の両方を表すのに、どのような方法で失敗しますか?

于 2012-10-14T01:59:08.883 に答える
1

デシリアライズされたチケットでは、実行するよりも多くのことを実行したいと思います。そうでない場合は、ユーザーに大量のチケットなどを提供するように依頼することもできますが、String -> IO() 賢い方法はまったく必要ありません。

もしそうなら、やったー!このような高度な言語機能を推奨するのが適切だと思うことはあまりありません。

class Ticketable a where
    show' :: a -> String
    read' :: String -> Maybe a
    runTicket :: a -> IO ()
    -- other useful things to do with tickets

これはすべて、のタイプに依存しread'ます。read' :: Ticket a => String -> a無効なデータで実行できるのはクラッシュだけなので、あまり役に立ちません。タイプをread' :: Ticket a => String -> Maybe aこれに変更すると、ディスクから読み取ってすべての可能性を試すか、完全に失敗する可能性があります(または、パーサーを使用することもできます:parse :: Ticket a => String -> Maybe (a,String)。)

GADTを使用して、構文なしで、より適切なエラーメッセージを含むExistentialQuantificationを提供しましょう。

{-# LANGUAGE GADTs #-}

data Ticket where
   MkTicket :: Ticketable a => a -> Ticket

showT :: Ticket -> String
showT (MkTicket a) = show' a

runT :: Ticket -> IO()
runT (MkTicket a) = runTicket a

MkTicketコンストラクターがコンテキストTicketable aを無料で提供する方法に注目してください。GADTは素晴らしいです。

TicketとTicketableのインスタンスを作成するのは良いことですが、あいまいなタイプがa隠されているため、それは機能しません。チケット可能なタイプを読み取る関数を取得して、それらにチケットを読み取らせるようにしましょう。

ticketize :: Ticketable a => (String -> Maybe a) -> (String -> Maybe Ticket)
ticketize = ((.).fmap) MkTicket  -- a little pointfree fun

シリアル化されたデータを分離するなど、通常とは異なるセンチネル文字列を使用することも "\n-+-+-+-+-+-Ticket-+-+-+-Border-+-+-+-+-+-+-+-\n"、個別のファイルを使用することもできます。この例では、区切り文字として「\n」を使用します。

readTickets :: [String -> Maybe Ticket] -> String -> [Maybe Ticket]
readTickets readers xs = map (foldr orelse (const Nothing) readers) (lines xs) 

orelse :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b)
(f `orelse` g) x = case f x of
     Nothing -> g x
     just_y  -> just_y

Justそれでは、sを取り除き、 sを無視しましょうNothing

runAll :: [String -> Maybe Ticket] -> String -> IO ()
runAll ps xs = mapM_ runT . catMaybes $ readTickets ps xs

あるディレクトリの内容を印刷するだけの簡単なチケットを作りましょう

newtype Dir = Dir {unDir :: FilePath} deriving Show
readDir xs = let (front,back) = splitAt 4 xs in
        if front == "dir:" then Just $ Dir back else Nothing

instance Ticketable Dir where
    show' (Dir p) = "dir:"++show p
    read' = readDir
    runTicket (Dir p) = doesDirectoryExist p >>= flip when 
         (getDirectoryContents >=> mapM_ putStrLn $ p)

そしてさらに些細なチケット

data HelloWorld = HelloWorld deriving Show
readHW "HelloWorld" = Just HelloWorld
readHW _ = Nothing
instance Ticketable HelloWorld where
    show' HelloWorld = "HelloWorld"
    read' = readHW
    runTicket HelloWorld = putStrLn "Hello World!"

そしてそれをすべてまとめます:

myreaders = [ticketize readDir,ticketize readHW]

main = runAll myreaders $ unlines ["HelloWorld",".","HelloWorld","..",",HelloWorld"]
于 2012-10-14T22:10:19.177 に答える
0

を使用するだけEitherです。ユーザーはそれを自分でラップする必要さえありません。あなたはあなたのデシリアライザーにあなたのためにそれを包んでEitherもらいます。シリアル化プロトコルが正確にはわかりませんが、どの種類の要求を検出する方法があると想定しています。次の例では、最初のバイトで2つの要求が区別されると想定しています。

deserializeRequest :: IO (Either A B)
deserializeRequest = do
    byte <- get1stByte
    case byte of
        0 -> do
            ...
            return $ Left $ A <A's fields>
        1 -> do
            ...
            return $ Right $ B <B's fields>

そうすれば、型クラスを作成する必要さえありませんspooge。それを次の関数にするだけですEither A B

spooge :: Either A B -> Q
于 2012-10-14T03:32:08.817 に答える