Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Scala's Cake Pattern be implemented in Haskell?

Tags:

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?

like image 286
tibbe Avatar asked Oct 18 '12 04:10

tibbe


2 Answers

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

like image 69
tibbe Avatar answered Oct 30 '22 02:10

tibbe


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.

like image 39
JJJ Avatar answered Oct 30 '22 03:10

JJJ