Can Scala's Cake Pattern be implemented in Haskell?
Asked Answered
K

3

20

Using a number of newer language features in Scala it's possible to implement a composable component system and create components using the so called Cake Pattern, described by Martin Odersky in the paper Scalable Component Abstractions and also in a recent talk.

Several of the Scala features used in the Cake Pattern have corresponding Haskell features. For example, Scala implicits correspond to Haskell type classes and Scala's abstract type members seem to correspond to Haskell's associated types. This makes me wonder if the Cake Pattern could be implemented in Haskell and what it would look like.

Can the Cake Pattern be implemented in Haskell? Which Haskell features do the Scala features correspond to in such an implementation? If the Cake Pattern can't be implemented in Haskell, which language features are missing to make that possible?

Kinnard answered 18/10, 2012 at 4:34 Comment(3)
I wouldn't say that implicits are a part of the cake pattern. It's rather traits + selftypes + abstract type members.Pretonic
@EugeneBurmako my thinking here was that perhaps Haskell type classes act like traits/implicits (the distinction between the two Scala concepts is not clear to me.)Kinnard
@Kinnard - In what way are implicits comparable to Haskell traits? Apples vs oranges I would say.Prestigious
K
5

Oleg provided a very detailed answer here: http://okmij.org/ftp/Haskell/ScalaCake.hs

Kinnard answered 20/11, 2012 at 3:48 Comment(0)
R
4

Taking this as an example, it seems to me that the following code is quite similar:

{-# 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

And for dummy implementation:

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

However, this is the simplest case. In Scala you have:

  • Classes with abstract value and type members (reminds ML functors and dependent records, as in Agda).

  • Path-dependent types.

  • Automatic class linearization.

  • this and super.

  • Selftypes.

  • Subtyping.

  • Implicits.

  • ...

It is just, well, different from what you have in Haskell.

Rothstein answered 21/10, 2012 at 11:26 Comment(0)
P
3

There are several solutions. The "obvious" one is to have several instances for given type classes (say Loader, Player, GUI for a game) that can be combined freely, but in my opinion such a design is better suited for OO-languages.

If you think out the box and recognize that the fundamental building blocks in Haskell are functions (D'oh!), you come to something like this:

data Game = Game
  { load    :: String -> IO [Level]
  , player1 :: Level -> IO Level
  , player2 :: Level -> IO Level
  , display :: Level -> IO ()  
  }  

play :: Game -> IO ()

With this design it's very easy to replace e.g. human players by bots. If this gets too complex, using the Reader monad might be helpful.

Polyzoic answered 18/10, 2012 at 7:14 Comment(2)
To support the last point, you could link to f.cl.ly/items/2J0v0m0x180X1c1K2E2d/Runar-NEscala-DI.pdf.Fauces
I'm not quite sure how this relates to the Cake Pattern, where you have mutually dependent abstract components (see the SymbolTable example in the paper). In Objects and modules: two sides of the same coin? Odersky says that modules (ala ML) and objects are equivalent so I don't think the issue the Cake Pattern solves is only relevant in OO.Kinnard

© 2022 - 2024 — McMap. All rights reserved.