2015-06-24 4 views
3

Я застреваю, пытаясь выбрать один экземпляр из многих во время выполнения. На самом деле это своего рода Backend.Выберите поведение экземпляра во время выполнения

Я могу сделать это, если я выбираю один экземпляр во время компиляции.

ОБНОВЛЕНО вероятно я хочу некоторые похожи на Database.Persist (это определение полностью поведение, но много примеров: MongoDB, SQLite, PostgreSQL, ...). Но для меня это слишком сложно.

ОБНОВЛЕНО Использование GADTs работ, но я думаю, что существует лучший способ (полный код внизу).

В каком-то языке ООП моя проблема более или менее

interface IBehavior { void foo(); } 

class AppObject { IBehavior bee; void run(); } 

... 
    var app = new AppObject { bee = makeOneOrOtherBehavior(); } 
.... 

Я пробовал много способов (и множество расширений: D), но ни один не работает.

Неофициально Я хочу определить один class с определенным поведением и использовать это общее определение в каком-либо приложении, после него выберите во время выполнения один из instance.

Родовое поведение (не реальный код)

class Behavior k a where 
    behavior :: k -> IO() 
    foo :: k -> a -> Bool 
    ... 

(я думаю k необходимо, так как каждый instance может понадобиться их собственный контекст/данные, другие ограничения, такие как key/value могут существовать)

Два экземпляра

data BehaviorA 
instance Behavior BehaviorA where 
    behavior _ = print "Behavior A!" 

data BehaviorB 
instance Behavior BehaviorB where 
    behavior _ = print "Behavior B!" 

мое приложение использует это поведение (здесь начинаются хаос)

data WithBehavior = 
    WithBehavior { foo :: String 
       , bee :: forall b . Behavior b => b 
       } 

run :: WithBehavior -> IO() 
run (WithBehavior {..}) = print foo >> behavior bee 

Я хочу выбрать во время выполнения

selectedBee x = case x of 
        "A" -> makeBehaviorA 
        "B" -> makeBehaviorB 
        ... 
withBehavior x = makeWithBehavior (selectedBee x) 

, но я потерял в лабиринте расширений, зависимостей типа и другие :(

Я не могу установить правильный тип для selectedBee функция.

Любая помощь будет оценена! :)

(Использование GADTs, но без дополнительных a параметров типа!)

{-# LANGUAGE RecordWildCards #-} 
{-# LANGUAGE GADTs #-} 

import System.Environment 
import Control.Applicative 

class Behavior k where 
    behavior' :: k -> IO() 

data BehaviorInstance where 
    BehaviorInstance :: Behavior b => b -> BehaviorInstance 

behavior :: BehaviorInstance -> IO() 
behavior (BehaviorInstance b) = behavior' b 

data BehaviorA = BehaviorA 
instance Behavior BehaviorA where 
    behavior' _ = print "Behavior A!" 
makeBehaviorA :: BehaviorInstance 
makeBehaviorA = BehaviorInstance BehaviorA 

data BehaviorB = BehaviorB 
instance Behavior BehaviorB where 
    behavior' _ = print "Behavior B!" 
makeBehaviorB :: BehaviorInstance 
makeBehaviorB = BehaviorInstance BehaviorB 

data WithBehavior = 
    WithBehavior { foo :: String 
       , bee :: BehaviorInstance 
       } 

run :: WithBehavior -> IO() 
run (WithBehavior {..}) = print foo >> behavior bee 

main = do 
    n <- head <$> getArgs 
    let be = case n of 
      "A" -> makeBehaviorA 
      _ -> makeBehaviorB 
    run $ WithBehavior "Foo Message!" be 
+0

Я думаю, что ваш лучший выбор, вероятно, является пакетом 'reflection' (не используйте' Given' для сохранения вашего здравомыслия). – dfeuer

+0

Получаю, что 'WithBehavior' имеет тип' String -> WithBehavior'. Каков тип 'withBehavior'? что такое 'makeWithBehavior'? В конце концов, это развалилось, и предполагаемая операция попала в неопределенные и нетипизированные символы. –

+0

@dfeuer, используя 'data BehaviorWrapper, где {BehaviorWrapper :: Behavior b => b -> BehaviorWrapper}' может быть сделано, но я хочу, чтобы избежать обертки типов :) – josejuan

ответ

6

Зачем использовать класс? Вместо этого, представляют собой класс типов в качестве типа записи, с «экземплярами» являются значениями этого типа:

data Behavior k a = Behavior 
    { behavior :: IO() 
    , foo :: k -> a -> Bool 
    } 

behaviorA :: Behavior String Int 
behaviorA = Behavior 
    { behavior = putStrLn "Behavior A!" 
    , foo = \a b -> length a < b 
    } 

behaviorB :: Behavior String Int 
behaviorB = Behavior 
    { behavior = putStrLn "Behavior B!" 
    , foo = \a b -> length a > b 
    } 

selectBehavior :: String -> Maybe (Behavior String Int) 
selectBehavior "A" = Just behaviorA 
selectBehavior "B" = Just behaviorB 
selectBehavior _ = Nothing 

main :: IO() 
main = do 
    putStrLn "Which behavior (A or B)?" 
    selection <- getLine 
    let selected = selectBehavior selection 
    maybe (return()) behavior selected 
    putStrLn "What is your name?" 
    name <- getLine 
    putStrLn "What is your age?" 
    age <- readLn -- Don't use in real code, you should actually parse things 
    maybe (return()) (\bhvr -> print $ foo bhvr name age) selected 

(я не компилируется этот код, но он должен работать)

Классы типов предназначены для полностью разрешен во время компиляции. Вы пытаетесь заставить их быть разрешенными во время выполнения. Вместо этого подумайте о том, как вы действительно указываете его в ООП: у вас есть тип и функция, которая возвращает некоторое значение этого типа на основе его аргументов. Затем вы вызываете метод такого типа. Единственное различие заключается в том, что с решением ООП значения, возвращаемые функцией выбора, не имеют точного типа, который говорит функция, поэтому вы возвращаете BehaviorA или BehaviorB вместо IBehavior. С Haskell вы должны фактически вернуть значение, которое точно соответствует типу возврата.

Единственное, что версия ООП позволяет сделать Haskell не отливают ваш IBehavior назад к BehaviorA или BehaviorB, и это часто считается небезопасным в любом случае. Если вы получаете значение, тип которого задается интерфейсом, вы всегда должны ограничивать себя только тем, что позволяет этот интерфейс. Хаскелл заставляет это, в то время как ООП использует его только по соглашению. Для более полного объяснения этого шаблона обратитесь к сообщению this.

+0

Да @bheklilr Я знаю эти прямые ООП aproach (и работает!), Он помнит меня. Стиль OOP в стиле C: D Но ваши 'k' - это один и тот же тип (' String') и 'selectBehavior 'возвращает только один фиксированный тип (тогда тоже с' class'works тоже) – josejuan

+0

@josejuan Правильно, если вам нужно, чтобы он был более гибким, тогда вам придется применять другие методы, я включил эти параметры типа данных, потому что вы их использовали в некоторых местах в вашем примере кода, но не в других. Вам придется возвращать тот же самый тип там, это ограничение, но не так уж плохо, есть способы вокруг него. Тем не менее, ваша главная проблема здесь заключается в попытке напрямую перевести OOP в FP с помощью классных таблиц, и это просто не то, что для класса. – bheklilr

4

Почему вы вводя эти типы BehaviorA, BehaviorB направить на? Это выглядит как плохой перевод с Java, если только не существует определенного преимущества для диспетчеризации на основе типов, а не значений; но, похоже, это вызывает у вас проблемы.

Вместо этого, как насчет канавки класса типа и просто используя запись «методов»?

data Behavior a = Behavior { behavior :: IO(), ... } 
behaviorA = Behavior { behavior = print "Behavior A!" } 
behaviorB = Behavior { behavior = print "Behavior B!" } 
selectedBee x = case x of 
        "A" -> behaviorA 
        "B" -> behaviorB 
data WithBehavior a = WithBehavior { foo :: String 
            , bee :: Behavior a } 
run :: WithBehavior a -> IO() 
run (WithBehavior {..}) = print foo >> behavior bee 

(я не знаю точно, что вы хотели с WithBehavior, так как ваш Behavior класса потерял одного из своих двух аргументов где-то по пути. Может быть, вы хотите универсально или экзистенциально количественный типа вместо.)

+0

Извините меня @ReidBarton (и другие) - это моя ошибка (я плохо объясняю). Каждый экземпляр поведения (альтернативный) имеет свой собственный контекст (тип данных). «Database.Persist» может быть хорошим примером, который я ищу, но слишком сложным для меня. – josejuan

+0

Это не имеет значения. Вы можете добавить параметры типа в «Behavior», если это необходимо, или просто использовать контекст в реализации вашего «Поведения» таким образом, чтобы он не отображался в типе. Классы типов обеспечивают удобство и согласованность, и здесь не имеет значения, когда вы даже не знаете, как писать свою программу вообще. –

+0

@ josejuan, лучший способ, чем версия с использованием GADT в редактировании, - это версия в моем ответе или ответ bheklilr (с параметрами типа «Behavior», удаленные, поскольку вы их не используете). Они эквивалентны. –

Смежные вопросы