2017-02-07 4 views
2

私はハスケルを新しくしており、証券取引所のモデルを探しています。これはライブラリであり、したがってユーザによって定義されるべきである。私がこれを使う方法は、ユーザーにこのようなことを定義させることです。Haskellのモデリング交換

data MyExchange = MyExchange { name :: ExchangeName 
          , base :: Currency 
          , quote :: Currency } 
          deriving (Eq, Show) 

instance Exchange MyExchange 

data MyExchangeBookMessage = 
     MyExchangeBookMessage { time :: Time 
           , exchange :: MyExchange 
           , price :: Price 
           , side :: Side 
           , amount :: Maybe Amount } 
           deriving (Eq, Show) 

instance ExchangeBookMessage MyExchangeBookMessage 

私は以下を試しましたが、直ちにタイプクラスのいくつかの制限がありました。以下はコードとエラーメッセージです。具体的には、複数の型を持つ型クラスをパラメータ化する代替手段は何ですか?

src/Lib.hs:22:1: error: 
    • Too many parameters for class ‘ExchangeMessage’ 
     (Use MultiParamTypeClasses to allow multi-parameter classes) 
    • In the class declaration for ‘ExchangeMessage’ 

後、私はこのようなタイプのクラスを実装できるようにしたいと思います:

class Strategy s where 
    run (Message m, Action a) => s -> m -> a 
ここ

module Lib where 

data Side = Buy | Sell deriving (Eq, Show) 

newtype Amount = Amount Rational deriving (Eq, Show) 

newtype Price = Price Rational deriving (Eq, Show) 

newtype Currency = Currency String deriving (Eq, Show) 

newtype Time = Time Integer deriving (Eq, Show) 

type ExchangeName = String 

class Exchange a where 
    name :: a -> ExchangeName 
    base :: a -> Currency 
    quote :: a -> Currency 


class Message a where 
    time :: a -> Time 

class (Message a, Exchange e) => ExchangeMessage a e where 
    exchange :: a -> e 

class ExchangeMessage a b => BookMessage a b where 
    price :: a -> Price 
    side :: a -> Side 
    amount :: a -> Maybe Amount 

、エラーメッセージライブラリのコードです

Strategyの実装では、run関数は抽象的なメッセージmを生成し、関連するMessageデータコンストラクタとパターンを一致させ、特定のアクションを返します。

私はいくつかのScalaコードを移植しています。 Scalaで私が一番下に、具体的なケースクラスを持つ形質の階層を使用した:ビジネスの

trait Exchange { 
    def name: String 
    def base: Currency 
    def quote: Currency 
} 

case class MyExchange(base: Currency, quote: Currency) { 
    val name = "my-exchange" 
} 

trait Message { 
    def time: Long 
} 

trait ExchangeMessage extends Message { 
    def exchange: Exchange 
} 

trait BookMessage extends ExchangeMessage { 
    def price: Double 
    def side: Side 
    def amount: Option[Double] 
} 

case class MyBookMessage(time: Long, price: Double, side: Side, amount: Option[Double]) { 
    def exchange: Exchange = MyExchange(...) 
} 
+1

「MultiParamTypeClassesを使用して複数パラメータクラスを使用できるようにする」という提案があります。それを有効にするには、ファイルの最初の行に '{ - #LANGAUGE MultiParamTypeClasses# - }'を置きます。 – luqui

+0

これはうまくいくでしょうが、 'BookMes​​sage'のメソッドを使用しようとすると、他の問題に遭遇します。 'b'がそこに存在するはずです... – luqui

+0

@luqui:' BookMes​​sage'は 'ExchangeMes​​sage'を拡張しています:' b'は 'ExchangeMes​​sage'から来る' Exchange'の型です。 –

答えて

4

まず順序は、GHCの提案を取ると、ファイルの先頭にMultiParamTypeCLassesを有効にします。

{-# LANGUAGE MultiParamTypeClasses #-} 

これは非常に一般的な拡張機能であり、直接の問題を解決します。

モデリングの問題があるようですが、この設計を進めると、予想外の問題が発生します。私はあなたのコードが意味することのすべての詳細に行くことができますが、私はそれが非常に役に立つとは思わない。代わりに私はちょうど正しい方向にあなたを指し示すでしょう、と私は思います。これは、型抜きの代わりにdataレコードを使用することです。ハスケルのタイプメーターは、他のオブジェクト指向のクラスには対応していないので、多くの初心者が混乱します。あなたのためにすべてを簡素化し、これはより多くのあなたのモデルよりOOクラスのような役割を果たします

data Exchange = Exchange 
    { name :: ExchangeName 
    , base :: Currency 
    , quote :: Currency 
    } 

data Message = Message 
    { time :: Time } 

-- etc. 

:しかし、私は、あなたがこのようにそれをモデルにしたいと思います。例えば、レコードはあなたが仮想メソッドのアナログを取得する方法である、フィールドとして機能し、他の複雑なデータ構造を持つことができることに留意してください:すべての

data MessageLogger = MessageLogger 
    { log :: String -> IO() } 
+0

異なる交換所からの具体的な交換とメッセージには、さまざまなフィールドが含まれている可能性があります。 'データMyExchange1 = MyExchange1 ExchangeName通貨通貨OPENTIME CloseTime' そして 'データMyExchange2 = MyExchange2 ExchangeName通貨通貨Precision' しかし、私はOOPで抽象クラスを扱うと同じように、私は彼らに共通のメソッドを実行し、それらを扱うしたいと思います。ハスケルでのこれとの類推は何ですか? –

+0

@ak。それはtypeclassの良い使用のように聞こえる、私はあなたのモデルでそれをやりすぎたと思う。コンクリートを開始し、クラスに重複要因を気付くと、抽象化がうまく動くようになります。特に避けようとしている複製を見ずに具体的なアドバイスをするのは難しいです。 – luqui

+0

これらのタイプクラスの使用に関する質問に、さらに詳しい情報を追加しました。基本的に私はいくつかの抽象的なインターフェースを定義するライブラリを構築しており、その詳細はユーザーのアプリケーションに入っています。 –

3

まず、あなたはおそらく書くことができませんExchangeMessageのクラスインスタンス。その理由は、exchange関数がのいずれかを返すことができることです。タイプeです。このようにしたい場合は、任意の交換を構築する方法を提供する必要があります!

class Exchange a where 
    name :: a -> ExchangeName 
    base :: a -> Currency 
    quote :: a -> Currency 
    build :: Time -> a 

あなたが交換局から知ることができるすべては、それはあなたが問い合わせることができTimeを持っているということであり、それはおそらく役に立たないので、これは、buildのための唯一の可能な署名です。

あなたが定義したすべてのクラスに具体的な型を持たせるのがより良い設計だと思います。たとえば、次のように続いて

data Exchange = Exchange { getName :: ExchangeName 
         , getBase :: Currency 
         , getQuote :: Currency 
         } deriving (Show, Eq) 

、あなたはこれらの具体的なタイプで動作機能を書いた後、次のいずれかを実行できます。たとえば

  • 書き込みfunctionsofタイプMyExchange -> ExchangeExchange
  • を期待しての機能を適応させます
  • は、高級なレンズを使用して、任意のタイプを消費する機能を直接書くことができる。

あなたが同じ種類の通貨を使って2つの金額の合計しか計算できないように静的に強制するように、あなたの通貨にファントムタイプを使用することをお勧めします。 OOの習慣を模倣するためにタイプメスを使用しても、使用するのがいいAPI​​や明確なコードは作成されません。

+0

@bartabelle:少なくとも、 'MyExchange - > Exchange'または' MyExchangeMes​​sage - > Message'関数を定義する型クラスを持つことは有益でしょうか?なぜなら、私は '[Message]'というメッセージのリストをとり、何らかの方法でそれらを折りたたみ、折り畳みの述語がメッセージを特定のデータコンストラクタとパターン一致させたいということです。これは意味がありますか? –

+0

そのタイプのクラスが有用かもしれません、あなたの特定のユースケースについて本当にコメントすることはできません。ただし、新しいタイプのクラスを作成することはルール以外の例外です。たとえば、[astar API](https://hackage.haskell.org/package/astar-0.3.0.0/docs/Data-Graph-AStar.html)を参照してください。 )。 'HasNeighbors'、' Localized'、 'Distanceable'のような型抜きがないことに気づくでしょう。単にプレーン関数であり、APIは本当に良いです。 – bartavelle