Моя конкретная проблема на самом деле не в общем переводе интерфейса OO на Haskell. Это просто лучшее название, которое я мог придумать. Тем не менее, я уверен, что моя проблема возникла из-за все еще плохого понимания кода моделирования с помощью Haskell и мышления, все еще находящегося в стране парадигм OO (вы все еще начинающий в 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
. Переменная типа должна быть
конкретный, так как, если я изменю тип nameToStrategy
на String -> Strategy Int
, все будет хорошо. Но это не решение моей проблемы.
Я подумал, что мне нужно расслабиться. Тем не менее, я не знаю, как это сделать. Я слышал о Data.Dynamic
и экзистенциальных типах, и они могут мне помочь. Тем не менее, я чувствую, что при лучшем моделировании моего кода они мне не понадобятся.
Редактировать : Мне все-таки удалось включить предложения sclv в код, и теперь это намного лучше. Код для стратегий более понятен, так как мне больше не нужен особый случай для первого предположения, и я могу использовать охрану, чтобы лучше различать случай правильного и неправильного предположения. Основная обработка симуляции не так элегантна, как версия sclv, поскольку я поместил stepState
(и функции, использующие stepState
) в монаду ввода-вывода, чтобы измерить время вычислений и, таким образом, иметь некоторый "монадический синтаксический шум". Возможность легко смоделировать пару шагов симуляции (что раньше было невозможно) помогла мне найти взаимно рекурсивный бесконечный цикл (, эту ошибку было странно понимать ). В целом, код теперь кажется более дискретным. Излишне говорить, что мне больше не нужен взлом unsafeCoerce
, чтобы отправлять имена в стратегии (или лучше «упакованные стратегии»). Я надеюсь, что когда-нибудь функциональный образ мышления придет ко мне тоже.