2017-02-07 27 views
2

Я новичок в Haskell, и я ищу модель фондовой биржи. Он предназначен для библиотеки, поэтому специфика должна определяться пользователем. Способ, которым я намереваюсь использовать это, состоит в том, чтобы пользователи определяли такие вещи.Обмен моделями в 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 

Я пробовал следующее, но сразу же столкнулся с некоторыми ограничениями классов типов. Ниже приведен код и сообщение об ошибке. В частности, каковы альтернативы параметризации классов типов с несколькими типами?

Вот код для библиотеки

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 

и сообщение об ошибке:

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 

В реализациях Strategy функция run примет абстракцию t сообщение 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

Это будет «работать», но вы столкнетесь с другими проблемами при попытке использовать любые методы «BookMessage». Я не уверен, что 'b' должен быть там ... – luqui

+0

@luqui:' BookMessage' расширяет 'ExchangeMessage':' b' - это тип 'Exchange', который поступает из' ExchangeMessage'. –

ответ

4

Первым делом, принять предложение GHC и позволить MultiParamTypeCLasses в верхней части файла.

{-# LANGUAGE MultiParamTypeClasses #-} 

Это очень распространенное расширение, и оно устранит непосредственную проблему.

Однако, по-видимому, возникают некоторые проблемы с моделированием, и если вы продолжите этот проект, вы наверняка столкнетесь с некоторыми проблемами, которых вы не ожидали. Я могу рассказать обо всех деталях вашего кода, но я не уверен, что это будет очень полезно. Вместо этого я просто укажу вам в правильном направлении, я думаю, что нужно использовать data записей вместо типов. Классические классы Haskell не соответствуют классам в других языках 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' Но я хотел бы запустить общие методы на них, и относиться к ним, как я бы лечить абстрактный класс в объектно-ориентированном программировании , Какова аналогия с этим в Haskell? –

+0

@ak. это похоже на то, что я хорошо использую классный знак, я думаю, вы просто переусердствовали в своей модели. Начните бетон, и поскольку вы заметите фактор дублирования в классах, который обеспечит ваши абстракции хорошо мотивированными. Трудно дать конкретный совет, не видя дублирования, которое вы пытаетесь избежать. – luqui

+0

Я добавил немного больше информации в вопрос об использовании этих классов типов. В основном я создаю библиотеку, где я определяю некоторые абстрактные интерфейсы, а особенности входят в пользовательские приложения. –

3

Прежде всего, вы, вероятно, не сможет написать класса для ExchangeMessage. Причина в том, что функция exchange должна иметь возможность вернуть любой тип e. Если вы хотите сохранить этот путь, вам нужно предоставить способ создания произвольного обмена!

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

Это возможно только подпись для build, так как все вы можете узнать из обмена является то, что он имеет Time вы можете запросить, и это, наверное, бесполезно.

Я бы подумал, что лучший дизайн - это конкретные типы для всех тех классов, которые вы определили. Например:

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

Тогда, как только вы написали функции, которые работают с этими конкретными типами, вы можете:

  • записи functionsof типа MyExchange -> Exchange, например, чтобы адаптировать функции ожидая Exchange
  • использовать классные линзы, чтобы напрямую писать функции, которые будут потреблять любые типы

В целом, fo В таких приложениях, если вы хотите быть похожими на типы, я бы предложил использовать фантомные типы для ваших валют, так что вы статически применяете, например, вы можете вычислять сумму двух сумм с использованием одной и той же валюты. Использование typeclasses для имитации привычек OO не приведет к созданию API-интерфейсов, которые хороши для использования или четкого кода.

+0

@bartabelle: Было бы полезно, по крайней мере, иметь класс типа, который определяет функцию «MyExchange -> Exchange» или «MyExchangeMessage -> 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