Используя ряд новых функций языка в Scala, можно реализовать систему составных компонентов и создать компоненты, используя так называемый шаблон торта, описанный Мартином Одерским в статье Масштабируемые абстракции компонентов, а также в недавнем обсуждении
Может ли Scala Cake Pattern быть реализован в Haskell?
Ответ 1
Олег предоставил очень подробный ответ здесь: http://okmij.org/ftp/Haskell/ScalaCake.hs
Ответ 2
Взяв this в качестве примера, мне кажется, что следующий код довольно схож:
{-# LANGUAGE ExistentialQuantification #-}
module Tweeter.Client where
import Data.Time
import Text.Printf
import Control.Applicative
import Control.Monad
type User = String
type Message = String
newtype Profile = Profile User
instance Show Profile where
show (Profile user) = '@' : user
data Tweet = Tweet Profile Message ZonedTime
instance Show Tweet where
show (Tweet profile message time) =
printf "(%s) %s: %s" (show time) (show profile) message
class Tweeter t where
tweet :: t -> Message -> IO ()
class UI t where
showOnUI :: t -> Tweet -> IO ()
sendWithUI :: Tweeter t => t -> Message -> IO ()
sendWithUI = tweet
data UIComponent = forall t. UI t => UIComponent t
class Cache t where
saveToCache :: t -> Tweet -> IO ()
localHistory :: t -> IO [Tweet]
data CacheComponent = forall t. Cache t => CacheComponent t
class Service t where
sendToRemote :: t -> Tweet -> IO Bool
remoteHistory :: t -> IO [Tweet]
data ServiceComponent = forall t. Service t => ServiceComponent t
data Client = Client UIComponent CacheComponent ServiceComponent Profile
client :: (UI t, Cache t, Service t) => t -> User -> Client
client self user = Client
(UIComponent self)
(CacheComponent self)
(ServiceComponent self)
(Profile user)
instance Tweeter Client where
tweet (Client (UIComponent ui)
(CacheComponent cache)
(ServiceComponent service)
profile)
message = do
twt <- Tweet profile message <$> getZonedTime
ok <- sendToRemote service twt
when ok $ do
saveToCache cache twt
showOnUI ui twt
И для фиктивной реализации:
module Tweeter.Client.Console where
import Data.IORef
import Control.Applicative
import Tweeter.Client
data Console = Console (IORef [Tweet]) Client
console :: User -> IO Console
console user = self <$> newIORef [] where
-- Tying the knot here, i.e. DI of `Console' into `Client' logic is here.
self ref = Console ref $ client (self ref) user
instance UI Console where
showOnUI _ = print
-- Boilerplate instance:
instance Tweeter Console where
tweet (Console _ supertype) = tweet supertype
instance Cache Console where
saveToCache (Console tweets _) twt = modifyIORef tweets (twt:)
localHistory (Console tweets _) = readIORef tweets
instance Service Console where
sendToRemote _ _ = putStrLn "Sending tweet to Twitter HQ" >> return True
remoteHistory _ = return []
test :: IO ()
test = do
x <- console "me"
mapM_ (sendWithUI x) ["first", "second", "third"]
putStrLn "Chat history:"
mapM_ print =<< localHistory x
-- > test
-- Sending tweet to Twitter HQ
-- (2012-10-21 15:24:13.428287 UTC) @me: first
-- Sending tweet to Twitter HQ
-- (2012-10-21 15:24:13.428981 UTC) @me: second
-- Sending tweet to Twitter HQ
-- (2012-10-21 15:24:13.429596 UTC) @me: third
-- Chat history:
-- (2012-10-21 15:24:13.429596 UTC) @me: third
-- (2012-10-21 15:24:13.428981 UTC) @me: second
-- (2012-10-21 15:24:13.428287 UTC) @me: first
Однако это самый простой случай. В Scala у вас есть:
-
Классы с элементами абстрактного значения и типа (напоминают ML-функторы и зависимые записи, как в Agda).
-
Типы, зависящие от пути.
-
Автоматическая линеаризация классов.
-
this и super.
-
Selftypes.
-
подтипа.
-
Implicits.
-
...
Это просто, ну, отличное от того, что у вас есть в Haskell.
Ответ 3
Существует несколько решений. "Очевидным" является наличие нескольких экземпляров для заданных классов типов (например, Loader, Player, GUI для игры), которые могут быть объединены свободно, но, на мой взгляд, такой дизайн лучше подходит для OO- языки.
Если вы придумаете коробку и узнаете, что фундаментальные строительные блоки в Haskell являются функциями (D'oh!), вы приходите к чему-то вроде этого:
data Game = Game
{ load :: String -> IO [Level]
, player1 :: Level -> IO Level
, player2 :: Level -> IO Level
, display :: Level -> IO ()
}
play :: Game -> IO ()
С этой конструкцией очень легко заменить, например. человеческие игроки ботов. Если это становится слишком сложным, использование монады Reader может оказаться полезным.