20

Scala の多くの新しい言語機能を使用すると、構成可能なコンポーネントシステムを実装し、いわゆる Cake パターンを使用してコンポーネントを作成できます。

Cake パターンで使用される Scala 機能のいくつかには、対応する Haskell 機能があります。たとえば、Scala の暗黙型は Haskell の型クラスに対応し、Scala の抽象型メンバーは Haskell の関連型に対応しているように見えます。これは、Cake パターンが Haskell で実装できるかどうか、またそれがどのようになるか疑問に思います。

Cake パターンは Haskell で実装できますか? そのような実装では、Scala 機能はどの Haskell 機能に対応していますか? Cake パターンを 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 には次のものがあります。

  • 抽象値と型メンバーを持つクラス (Agda のように、ML ファンクターと従属レコードを思い起こさせます)。

  • パス依存型。

  • 自動クラス線形化。

  • これ

  • セルフタイプ。

  • サブタイピング。

  • 暗黙。

  • ...

それは、Haskell にあるものとはまったく異なります。

于 2012-10-21T11:26:51.527 に答える
3

いくつかの解決策があります。「明らかな」ものは、​​自由に組み合わせることができる特定の型クラス (たとえばLoader、ゲームの場合) の複数のインスタンスを持つことですが、私の意見では、そのような設計は OO 言語により適しています。PlayerGUI

箱から出して考えて、Haskell の基本的なビルディング ブロックが関数であることを認識すると (D'oh!)、次のようになります。

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

play :: Game -> IO ()

この設計により、たとえば人間のプレイヤーをボットに置き換えるのは非常に簡単です。これが複雑になりすぎる場合は、Readerモナドを使用すると役立つ場合があります。

于 2012-10-18T07:14:52.413 に答える