23個の道は遥か

どうも色々と考え違いが出てくるなぁ。
まぁ、良いか。

  • Adapter

これはいかばかりか一般的過ぎるアイデアなのではなかろうか?
既存の実装を用いて必要とするインターフェースを実装する、
というのをAdapterパターンということにしておこう。

class Banner{
public:
  Banner(string s);
  void showWithParen(){ cout<<'('<

これが例で、そのままHaskellで書くと、

class Print p where
  printWeek   :: p -> IO ()
  printStrong :: p -> IO ()

data Banner = Banner String
showWithParen (Banner s) = putStrLn $ "("++s++")"
showWithAster (Banner s) = putStrLn $ "*"++s++"*"

data PrintBanner1 = PB1 Banner
instance Print PrintBanner1 where
  printWeek   (PB1 s) = showWithParen s
  printStrong (PB1 s) = showWithAster s

data PrintBanner2 = PB2 Banner
instance Print PrintBanner2 where
  printWeek   (PB2 s) = showWithAster s
  printStrong (PB2 s) = showWithParen s

newBanner1 = PB1 . Banner
newBanner2 = PB2 . Banner

main = do
  let pb1 = newBanner1 "Hello"
  let pb2 = newBanner2 "Hello"
  putStrLn "with PB1"
  weekAndStrong pb1
  putStrLn "with PB2"
  weekAndStrong pb2

PrintBannerを二通り実装してみた。
それぞれ逆の挙動をするものを作ってみた。


実装が複数有る場合は別だが、
「一皮かぶせて再利用」なときにはそうでないケースも多いと思うので、
そういう場合Haskellならモジュールを使って適当に実装できる。

 -- メイン
import PrintBanner

main = do
  let pb = newPrintBanner "Hello"
  printWeek   pb
  printStrong pb

 -- PrintBanner
module PrintBanner(
  PrintBanner,
  newPrintBanner,
  printWeek,
  printStrong,  
) where

import Banner
type PrintBanner = Banner
newPrintBanner   = newBanner
printWeek   = showWithParen
printSTrong = showWithAster

 -- Banner
(略)

まぁ、なんとでもなりますわなぁ。
メインがBannerを一切参照していないというのがミソなのだろう。
さらに、もっと違った形での一般化としても実装できる。
参照する領域を限定することが出来るのであれば、
単純に関数を渡すだけでも。

foo printWeek printStrong = ...

main = foo showWithAster showWithParen

やはりまぁ、なんとでもなりますわなぁ。

  • Template Method

まず、例から。

class AbstractDisplay{
public:
  void open()=0;
  void close()=0;
  void print()=0;

  void display(){
    open();
    for (i=0;i<5;i++) print();
    close();
  }
};

display()を呼ぶとopen()一回、print()五回、close()一回を順に呼び出すようなクラス。
それぞれの挙動はサブクラスで実装。

class CharDisplay : public{
public:
  CharDisplay(char c);
  void open()   { cout<<'('; }
  void close()  { cout<<')'<

こんなのとか。
いつものように、ひとまずこれを素直にHaskellで実装してみる。

class AbstractDisplay a where
  open    :: a -> IO ()
  print   :: a -> IO ()
  close   :: a -> IO ()

  display :: a -> IO ()
  display a = do
    open a
    replicateM_ 5 $ print a
    close a

data CharDisplay = CharDisplay Char
instance AbstractDisplay CharDisplay where
  open  _ = putStr "<<"
  print (CharDisplay c) = putChar c
  close _ = putStrLn ">>"

data StringDisplay = StringDisplay String
instance AbstractDisplay StringDisplay where
  open  = printLine
  print (StringDisplay s) = putStrLn $ "|"++s++"|"
  close = printLine
printLine (StringDisplay s) = do
  putChar '+'
  replicateM_ (length s) $ putChar '-'
  putStrLn "+"

main = do
  display (CharDisplay   'H')
  display (StringDisplay "Hello, World.")

上では(めんどくさかったので)書いてないStringDisplayも実装。
今回はとてもすんなり書けた。
しかし、Haskellならばわざわざクラスを使わんでも、

display o p c d = do
  o d
  replicateM_ 5 $ p d
  c d

charDisplay = display open print close where
  open  _ = putStr "<<"
  print   = putChar
  close _ = putStrLn ">>"

stringDisplay = display open print close where
  open  = printLine
  print s = putStrLn $ "|"++s++"|"
  close = printLine
  printLine s = do
    putChar '+'
    replicateM_ (length s) $ putChar '-'
    putStrLn "+"

main = do
  charDisplay   'H'
  stringDisplay "Hello, World."

適当にこれでもいいだろう。
こういうのは何というか、templateメソッドというか、
単なる共通部分の抽出だし、普通の関数型プログラマなら
(そういうことを行うのがいたって自然なので)常に行っていることではなかろうか。

  • Factory Method

Template Methodのファクトリー版。
C++版みたいなのを書くのがめんどくさい…
ので、Haskellだけでご勘弁。

class Product p where
  use      :: p -> IO ()
  getOwner :: p -> String

class Factory f where
  createProduct   :: Product p => f p -> String -> IO p
  registerProduct :: Product p => f p -> p -> f p

  create :: Product p => f p -> String -> IO (p,f p)
  create f owner = do
    p    <- createProduct   f owner
    let g = registerProduct f p
    return (p,g)

data IDCard = IDCard String
instance Product IDCard where
  use (IDCard s) = putStrLn $ "use "++s++"'s card."
  getOwner (IDCard s) = s
newIDCard s = do
  putStrLn $ "make "++s++"'s card."
  return $ IDCard s

data IDCardFactory a = IDCF [String] (String -> IO a)
instance Factory IDCardFactory where
  createProduct   (IDCF  _ c) s = c s
  registerProduct (IDCF ow c) p = IDCF (getOwner p:ow) c
newIDCardFactory = IDCF [] newIDCard

main = test newIDCardFactory

test :: (Factory f,Product p) => f p -> IO ()
test f = do
  (p1,f) <- create f "hoge"
  (p2,f) <- create f "huga"
  (p3,f) <- create f "fugo"
  use p1
  use p2
  use p3

testのようなものを記述できるというのが
FactoryMethodの本質なのだろうが、
これも普通に関数で記述できるはずである。
まぁ、でも、こういうのを書いててちょっと面白いような気もする。