使用Scala中的一些较新的语言功能,可以实现一个可组合的组件系统,并使用所谓的Cake Pattern创建组件,Martin Odersky在论文Scalable Component Abstractions和recent talk中描述了这种模式。 }。
Cake Pattern中使用的一些Scala功能具有相应的Haskell功能。例如,Scala implicits对应于Haskell类型类,Scala的抽象类型成员似乎对应于Haskell的关联类型。这让我想知道Cake Pattern是否可以在Haskell中实现以及它看起来像什么。
Cake模式可以在Haskell中实现吗? Scala功能在这样的实现中对应哪些Haskell功能?如果Cake模式无法在Haskell中实现,那么缺少哪些语言功能呢?
答案 0 :(得分:5)
奥列格在这里提供了非常详细的答案:http://okmij.org/ftp/Haskell/ScalaCake.hs
答案 1 :(得分:4)
以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中)。
路径依赖类型。
自动类线性化。
此和超级。
Selftypes。
分型
Implicits。
...
这与Haskell的内容完全不同。
答案 2 :(得分:3)
有几种解决方案。 “显而易见”的是给定类型类的几个实例(比如游戏中的Loader
,Player
,GUI
)可以自由组合,但在我看来这样的设计更适合OO语言。
如果你想出框中并认识到Haskell中的基本构建块是函数(D'哦!),你会得到这样的结论:
data Game = Game
{ load :: String -> IO [Level]
, player1 :: Level -> IO Level
, player2 :: Level -> IO Level
, display :: Level -> IO ()
}
play :: Game -> IO ()
采用这种设计,很容易更换,例如机器人的人类玩家。如果这太复杂,使用Reader
monad可能会有所帮助。