modulos - palindromo en haskell
¿Se puede implementar el patrón de pastel de Scala en Haskell? (3)
Hay varias soluciones. El "obvio" es tener varias instancias para determinadas clases de tipos (por ejemplo, Loader
, Player
, GUI
para un juego) que pueden combinarse libremente, pero en mi opinión, tal diseño es más adecuado para los idiomas OO.
Si piensa fuera de la caja y reconoce que los bloques de construcción fundamentales en Haskell son funciones (¡D''oh!), Llegará a algo como esto:
data Game = Game
{ load :: String -> IO [Level]
, player1 :: Level -> IO Level
, player2 :: Level -> IO Level
, display :: Level -> IO ()
}
play :: Game -> IO ()
Con este diseño es muy fácil de reemplazar, por ejemplo, los jugadores humanos por robots. Si esto se vuelve demasiado complejo, usar la mónada Reader
podría ser útil.
Mediante el uso de una serie de funciones de lenguaje más nuevas en Scala, es posible implementar un sistema de componentes compositivos y crear componentes utilizando el llamado Patrón de Torta, descrito por Martin Odersky en el resumen de componentes escalables de papel y también en una reciente charla .
Varias de las funciones de Scala utilizadas en el Patrón de la torta tienen características correspondientes de Haskell. Por ejemplo, las implicaciones de Scala corresponden a clases de tipo Haskell y los miembros de tipo abstracto de Scala parecen corresponder a los tipos asociados de Haskell. Esto me hace preguntarme si el Patrón de Torta podría implementarse en Haskell y cómo se vería.
¿Se puede implementar el patrón de torta en Haskell? ¿A qué características de Haskell corresponden las características de Scala en dicha implementación? Si no se puede implementar Cake Pattern en Haskell, ¿qué características de idioma faltan para que esto sea posible?
Oleg proporcionó una respuesta muy detallada aquí: http://okmij.org/ftp/Haskell/ScalaCake.hs
Tomando this como ejemplo, me parece que el siguiente código es bastante 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
Y para la implementación dummy:
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
Sin embargo, este es el caso más simple. En Scala tienes:
Clases con valor abstracto y miembros de tipo (recuerda a los funtores de ML y los registros dependientes, como en Agda).
Tipos dependientes del camino.
Linealización automática de clases.
esto y super
Autotipos.
Subtitulado.
Implícitos
...
Es solo, bueno, diferente de lo que tienes en Haskell.