Scala的Cake Pattern可以在Haskell中实现吗?

时间:2012-10-18 04:34:43

标签: haskell module cake-pattern

使用Scala中的一些较新的语言功能,可以实现一个可组合的组件系统,并使用所谓的Cake Pattern创建组件,Martin Odersky在论文Scalable Component Abstractionsrecent talk中描述了这种模式。 }。

Cake Pattern中使用的一些Scala功能具有相应的Haskell功能。例如,Scala implicits对应于Haskell类型类,Scala的抽象类型成员似乎对应于Haskell的关联类型。这让我想知道Cake Pattern是否可以在Haskell中实现以及它看起来像什么。

Cake模式可以在Haskell中实现吗? Scala功能在这样的实现中对应哪些Haskell功能?如果Cake模式无法在Haskell中实现,那么缺少哪些语言功能呢?

3 个答案:

答案 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)

有几种解决方案。 “显而易见”的是给定类型类的几个实例(比如游戏中的LoaderPlayerGUI)可以自由组合,但在我看来这样的设计更适合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可能会有所帮助。