Используя ряд новых функций языка в 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
может оказаться полезным.