2017-05-04 7 views
0

は、コードスニペットです:haskellで同じクラスの戻り値を処理する方法は?ここ

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE FlexibleContexts   #-} 
{-# LANGUAGE LambdaCase    #-} 
{-# LANGUAGE MultiParamTypeClasses  #-} 
{-# LANGUAGE TypeFamilies    #-} 
module Main 
where 

import   Control.Exception 
import   System.IO 

main :: IO() 
main = putStrLn "Hello World" 

class IConn a where 
    execute :: a -> IO() 

    delete :: a -> IO() 

data ConnA = ConnA 

instance IConn ConnA where 
    execute _ = putStrLn "Execute A" 

    delete _ = putStrLn "Delete A" 

data ConnB = ConnB 

instance IConn ConnB where 
    execute _ = putStrLn "Execute B" 

    delete _ = putStrLn "Delete B" 

class IConn (Conn b) => IBackend b where 

    type Conn b :: * 

    create :: b -> IO (Conn b) 

    withConn :: b -> Int -> Int -> (Conn b -> IO a) -> IO a 
    withConn b l u f = do 
    putStrLn $ "low: " ++ show l 
    putStrLn $ "up: " ++ show u 
    bracket (create b) delete f 

data BackendA = BackendA 

data BackendB = BackendB 

instance IBackend BackendA where 
    type Conn BackendA = ConnA 
    create _ = return ConnA 

instance IBackend BackendB where 
    type Conn BackendB = ConnB 
    create _ = return ConnB 

data Backend = forall b. IBackend b => Backend b 

func :: IConn c => c -> IO() 
func c = do 
    putStrLn "Beginning of func." 
    execute c 
    putStrLn "end of func." 

createBackend :: String -> IO Backend 
createBackend "A" = return $ Backend BackendA 
createBackend "B" = return $ Backend BackendB 

test :: String -> IO() 
test name = 
    createBackend name >>= \case 
    Backend imp 
    -> withConn imp 10 100 func 

私はデータBackendcreateBackendによって返さIBackendをラップしていない場合は、createBackend関数はコンパイルされません。しかし、今私はtestのケースステートメントを使用してBackendからunbox IBackendの関数を使用する必要があります。ちょっと面倒です。 testまたはcreateBackendの機能を改善するためのご意見はありますか?

+1

'createBackend'と' test'は、引数として文字列を取る必要がありますか?可能ならば、タイプレベルであっても必要なバックエンドを識別するシングルトンのGADT型 'SBackend b 'を使うようにして、存在するラッパー' Backend'はもはや必要なくなります。これは、ユースケースによっては可能かもしれません。 – chi

答えて

0

BackendのインスタンスをIBackendにすると、改善する方法が1つあります。その後test関数は次のようになります。

instance IBackend Backend where 
    type Conn Backend = ? 
    create (Backend imp) = create imp 
    withConn (Backend imp) = withConn imp 

しかし、我々は、関連するタイプConnに問題がある:

test name = do 
    imp <- createBackend name 
    withConn imp 10 100 func 

[OK]を、のは、インスタンス化を試してみましょう。接続のために存在するタイプを作るためにそれを解決する1つの方法。

data WrapConn = forall c . IConn c => WrapConn c 

instance IConn WrapConn where 
    execute (WrapConn c) = execute c 
    delete (WrapConn c) = delete c 

instance IBackend Backend where 
    type Conn Backend = WrapConn 
    create (Backend imp) = WrapConn <$> create imp 
    withConn (Backend imp) x y f = withConn imp x y (f . WrapConn) 

もう一つの方法は、あなたが新しいバックエンドを追加するときに、あなたの関数createBackendが更新されることを理解することです。 @chiを書いたように、あなたはGADTの魔女によってバックエンド名のタイプを置き換えることができ構築物についてのパラメータを持つバックエンドの種類を表しますそれは、次のように:

data Backend imp where 
    ImpA :: Backend BackendA 
    ImpB :: Backend BackendB 
    ImpC :: OptionsC -> Backend BackendB 
    ... 

createBackend :: Backend imp -> IO imp 
createBackend ImpA = return BackendA 
createBackend ImpB = return BackendB 
createBackend (ImpC options) = ... 
... 

test :: IBackend imp => Backend imp -> IO() 
test b = do 
    imp <- createBackend b 
    withConn imp 10 100 func 
関連する問題