Перевод интерфейса OO на Haskell

Моя конкретная проблема на самом деле не связана с общим переводом объектно-ориентированного интерфейса на Haskell. Это просто лучшее название, которое я мог придумать. Тем не менее, я уверен, что моя проблема возникла из-за все еще плохого понимания моделирования кода с помощью Haskell и из-за мышления, все еще находящегося в стране парадигм объектно-ориентированного программирования (все еще новичок в haskell, понимаете).

Я пишу Mastermind (вариативное) моделирование для проверки пригодности нескольких стратегий Mastermind. На самом деле, я уже делал это в Java и Lua , и поэтому эта версия Haskell - просто упражнение для меня, чтобы научиться программировать на Haskell. Вы можете проверить файл readme версии Lua / Java, если вас интересует, чего я пытаюсь достичь в итоге.

А теперь о моей конкретной проблеме (вкратце и в объектно-ориентированных терминах): Я хочу предоставить интерфейс для стратегий, чтобы я мог взаимозаменяемо поместить стратегию, которая придерживается этого интерфейса, в рекурсию моделирования (цикл) и после этого получать некоторые данные о производительности стратегии. Кроме того, я хочу, чтобы стратегия сохраняла произвольное состояние, и я не хочу заботиться о том, какое состояние поддерживает каждая стратегия. Но именно это решение - действительно важное - все усложнило. Другое требование, которое конкретно привело к проблеме, описанной ниже, заключается в том, что имя стратегии может быть указано в качестве аргумента командной строки, а затем моделирование запускается с этой конкретной стратегией.

Сначала я считал, что класс типа подходит для этих требований, но не придумав реальной идеи, как моделировать код таким образом, я отказался от этой идеи. Затем я решил использовать ADT, использовал его с тех пор и относительно далеко продвинулся с кодом - до сих пор.


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

Вот сокращенный и адаптированный отрывок из моего кода:

-- reduced & simplified example
import Control.Monad.State

type Code = [Int]

data Answer = Answer { 
    blacks :: Int, 
    whites :: Int 
    } deriving (Eq, Show)

-- As you can see I decided for a type variable a that
-- represents the arbitrary state a strategy might carry
-- around. I wonder if this is the right way to do it.
-- | This is the interface for a strategy. A strategy must provide a method 
-- that, given a mastermind answer, returns the next guess, an initial state 
-- and the very first guess.
data Strategy a = Strategy {
    firstGuess :: Int -> Code,
    initialize :: Int -> a, -- a "constructor" in disguise
    guess      :: Answer -> State a Code
    }

dummy = Strategy {
    firstGuess   = firstGuess',
    initialize   = initialize', 
    guess        = guess'
    }

-- | The very first guess is always T0,T1,...,Tx, where x is the code length.
firstGuess' :: Int -> Code
firstGuess' length = [0..length-1]

-- | Memorize the code length
initialize' :: Int -> Int
initialize' = id

-- | Always return the same guess
guess' :: Answer -> State Int Code
guess' = const $ liftM firstGuess' get

-- HERE IS THE PROBLEM
-- I need this function since I'll get the name of a strategy
-- as a string from the command line and need to dispatch the
-- correct strategy to the simulation. Note, that there would
-- be of course more pattern matches for different strategies
-- with different accompanying states a.
nameToStrategy :: String -> Strategy a
nameToStrategy "dummy" = dummy

Выполнение файла дает следующее сообщение об ошибке:

Prelude> :l Problem.hs
[1 of 1] Compiling Main             ( Problem.hs, interpreted )

Problem.hs:37:25:
    Couldn't match expected type `a' against inferred type `Int'
      `a' is a rigid type variable bound by
          the type signature for `nameToStrategy' at Problem.hs:36:37
      Expected type: Strategy a
      Inferred type: Strategy Int
    In the expression: dummy
    In the definition of `nameToStrategy':
        nameToStrategy "dummy" = dummy
Failed, modules loaded: none.

Я в порядке может интуитивно понять проблему. Проблема, похоже, в том, что nameToStrategy не может просто вернуть стратегию с некоторым состоянием a . Переменная типа должна быть Более глубокий вопрос заключается в том, как лучше смоделировать мои требования к интерфейсу с произвольным состоянием в Haskell.

Вот сокращенный и адаптированный отрывок из моего кода:

-- reduced & simplified example
import Control.Monad.State

type Code = [Int]

data Answer = Answer { 
    blacks :: Int, 
    whites :: Int 
    } deriving (Eq, Show)

-- As you can see I decided for a type variable a that
-- represents the arbitrary state a strategy might carry
-- around. I wonder if this is the right way to do it.
-- | This is the interface for a strategy. A strategy must provide a method 
-- that, given a mastermind answer, returns the next guess, an initial state 
-- and the very first guess.
data Strategy a = Strategy {
    firstGuess :: Int -> Code,
    initialize :: Int -> a, -- a "constructor" in disguise
    guess      :: Answer -> State a Code
    }

dummy = Strategy {
    firstGuess   = firstGuess',
    initialize   = initialize', 
    guess        = guess'
    }

-- | The very first guess is always T0,T1,...,Tx, where x is the code length.
firstGuess' :: Int -> Code
firstGuess' length = [0..length-1]

-- | Memorize the code length
initialize' :: Int -> Int
initialize' = id

-- | Always return the same guess
guess' :: Answer -> State Int Code
guess' = const $ liftM firstGuess' get

-- HERE IS THE PROBLEM
-- I need this function since I'll get the name of a strategy
-- as a string from the command line and need to dispatch the
-- correct strategy to the simulation. Note, that there would
-- be of course more pattern matches for different strategies
-- with different accompanying states a.
nameToStrategy :: String -> Strategy a
nameToStrategy "dummy" = dummy

Выполнение файла дает следующее сообщение об ошибке:

Prelude> :l Problem.hs
[1 of 1] Compiling Main             ( Problem.hs, interpreted )

Problem.hs:37:25:
    Couldn't match expected type `a' against inferred type `Int'
      `a' is a rigid type variable bound by
          the type signature for `nameToStrategy' at Problem.hs:36:37
      Expected type: Strategy a
      Inferred type: Strategy Int
    In the expression: dummy
    In the definition of `nameToStrategy':
        nameToStrategy "dummy" = dummy
Failed, modules loaded: none.

Я в порядке может интуитивно понять проблему. Проблема, похоже, в том, что nameToStrategy не может просто вернуть стратегию с некоторым состоянием a . Переменная типа должна быть Более глубокий вопрос заключается в том, как лучше смоделировать мои требования к интерфейсу с произвольным состоянием в Haskell.

Вот сокращенный и адаптированный отрывок из моего кода:

-- reduced & simplified example
import Control.Monad.State

type Code = [Int]

data Answer = Answer { 
    blacks :: Int, 
    whites :: Int 
    } deriving (Eq, Show)

-- As you can see I decided for a type variable a that
-- represents the arbitrary state a strategy might carry
-- around. I wonder if this is the right way to do it.
-- | This is the interface for a strategy. A strategy must provide a method 
-- that, given a mastermind answer, returns the next guess, an initial state 
-- and the very first guess.
data Strategy a = Strategy {
    firstGuess :: Int -> Code,
    initialize :: Int -> a, -- a "constructor" in disguise
    guess      :: Answer -> State a Code
    }

dummy = Strategy {
    firstGuess   = firstGuess',
    initialize   = initialize', 
    guess        = guess'
    }

-- | The very first guess is always T0,T1,...,Tx, where x is the code length.
firstGuess' :: Int -> Code
firstGuess' length = [0..length-1]

-- | Memorize the code length
initialize' :: Int -> Int
initialize' = id

-- | Always return the same guess
guess' :: Answer -> State Int Code
guess' = const $ liftM firstGuess' get

-- HERE IS THE PROBLEM
-- I need this function since I'll get the name of a strategy
-- as a string from the command line and need to dispatch the
-- correct strategy to the simulation. Note, that there would
-- be of course more pattern matches for different strategies
-- with different accompanying states a.
nameToStrategy :: String -> Strategy a
nameToStrategy "dummy" = dummy

Выполнение файла дает следующее сообщение об ошибке:

Prelude> :l Problem.hs
[1 of 1] Compiling Main             ( Problem.hs, interpreted )

Problem.hs:37:25:
    Couldn't match expected type `a' against inferred type `Int'
      `a' is a rigid type variable bound by
          the type signature for `nameToStrategy' at Problem.hs:36:37
      Expected type: Strategy a
      Inferred type: Strategy Int
    In the expression: dummy
    In the definition of `nameToStrategy':
        nameToStrategy "dummy" = dummy
Failed, modules loaded: none.

Я в порядке может интуитивно понять проблему. Проблема, похоже, в том, что nameToStrategy не может просто вернуть стратегию с некоторым состоянием a . Переменная типа должна быть конкретным, поскольку если я изменю тип nameToStrategy на String -> Strategy Int , все будет в порядке. Но это не решение моей проблемы.

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


Правка : Мне все-таки удалось включить предложения sclv в код, и теперь это намного лучше. Код для стратегий более ясен, поскольку мне больше не нужен особый случай для первого предположения, и я могу использовать охранников, чтобы лучше различать случай правильного и неправильного предположения. Основная обработка симуляции не так элегантна, как sclv ' s, поскольку я поместил stepState (и функции, использующие stepState ) в монаду ввода-вывода для измерения времени вычислений и, таким образом, получил некоторый «монадический синтаксический шум». Возможность легко смоделировать пару шагов моделирования (что раньше было практически невозможно) помогла мне найти взаимный рекурсивный бесконечный цикл (, эту ошибку было странно понять ). В общем, теперь код кажется более дискретным. Излишне говорить, что мне больше не нужен хак unsafeCoerce для отправки имен в стратегии (или, лучше, «упакованные стратегии»). Я надеюсь, что функциональный образ мышления когда-нибудь тоже придет ко мне естественным образом.

7
задан Eugen 1 April 2011 в 17:33
поделиться