Haskellで設計を行っている時に、どうしてもDRY原則に反し、煩雑になってしまうコードが有ります。
特に、型クラスを使っている時にこの問題が発生することがあります。
実際の状況はかなり複雑で書ききれないので、重要な部分だけを取り出して問題形式にしました。
どのようにすればこの問題を解決できるでしょうか?
また、根本的に問題の捉え方が誤っているなどがあればご指摘いただければ幸いです。

{-
以下の条件で実装をするものとする。
まず、データ型X, Y, Zがあり、データ型は頻繁に追加されることが予想される。
(つまり、拡張性を高く保っておく必要がある)
あなたは、標準出力から、一行目にデータ型の種類を指定され、二行目にパース可能なデータが与えられる。
そのデータをパースし、データに対してある動作B(サンプル実装ではfuncBとした)を行ったあとに、
所与の動作A(funcA)を実行する必要がある。
サンプル実装では、test関数にその処理をまとめたが、これは明らかにDRY原則に反している。
サンプル実装をどのように改善すればDRY原則を守ることができるか。
(サンプル実装の中身はすべて書き換え可、動作Bはundefinedでよい)
-}
module Problem where

----------------------------------------------------------------------
-- 条件

data X = X
data Y = Y
data Z = Z

class Parsable a where
  parse :: String -> a
instance Parsable X where
  parse = undefined
instance Parsable Y where
  parse = undefined
instance Parsable Z where
  parse = undefined

class ClassA a where
  funcA :: a -> IO ()
instance ClassA X where
  funcA = undefined
instance ClassA Y where
  funcA = undefined
instance ClassA Z where
  funcA = undefined

----------------------------------------------------------------------
-- サンプル実装

class ClassB a where
  funcB :: a -> IO ()
instance ClassB X where
  funcB = undefined
instance ClassB Y where
  funcB = undefined
instance ClassB Z where
  funcB = undefined

test = do
  t <- getLine
  d <- getLine
  case t of
   "X" -> do
     let obj = parse d :: X
     funcB obj
     funcA obj
   "Y" -> do
     let obj = parse d :: Y
     funcB obj
     funcA obj
   "Z" -> do
     let obj = parse d :: Z
     funcB obj
     funcA obj