20

使用 Scala 中的一些较新的语言特性,可以实现可组合的组件系统并使用所谓的 Cake Pattern 创建组件,Martin Odersky 在论文Scalable Component Abstractions最近的一次演讲中对此进行了描述。

Cake Pattern 中使用的一些 Scala 特性具有相应的 Haskell 特性。例如,Scala 隐式对应于 Haskell 类型类,Scala 的抽象类型成员似乎对应于 Haskell 的关联类型。这让我想知道蛋糕模式是否可以在 Haskell 中实现以及它会是什么样子。

蛋糕模式可以在 Haskell 中实现吗?在这样的实现中,Scala 特性对应于哪些 Haskell 特性?如果蛋糕模式不能在 Haskell 中实现,那么缺少哪些语言特性来实现这一点?

4

3 回答 3

5

奥列格在这里提供了一个非常详细的答案:http: //okmij.org/ftp/Haskell/ScalaCake.hs

于 2012-11-20T03:48:59.457 回答
4

以此为例在我看来,以下代码非常相似:

{-# 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 中)。

  • 路径相关类型。

  • 自动类线性化。

  • 超级

  • 自我类型。

  • 子类型化。

  • 隐含的。

  • ...

它只是,嗯,与你在 Haskell 中的不同。

于 2012-10-21T11:26:51.527 回答
3

有几种解决方案。“显而易见”的方法是为给定的类型类(例如,对于游戏)提供多个可以自由组合的实例LoaderPlayerGUI在我看来,这样的设计更适合 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 ()

使用这种设计,很容易用机器人代替人类玩家。如果这变得太复杂,使用Readermonad 可能会有所帮助。

于 2012-10-18T07:14:52.413 回答