OO接口转换为Haskell [英] OO Interface translation to Haskell

查看:207
本文介绍了OO接口转换为Haskell的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的具体问题实际上并不是关于面向Haskell的面向对象接口的一般翻译。这只是我能想出的最好的标题。然而,我确信我的问题源于对Haskell建模代码的理解仍然很差,而且思维模式仍然位于面向对象模式的领域(仍然是一个Haskell初学者,您会发现)。

我正在写一个Mastermind(变化)模拟来测试几种策略的适应性。事实上,我已经在 Java Lua ,因此 Haskell版本只是我学习在Haskell编程的练习。你可以查看自己的Lua / Java版本的自述文件,如果你对我想要达到的目标感兴趣的话。



但是现在对于我的具体问题简而言之是OO术语):我想提供一个策略接口,以便我可以将一个遵循该接口的策略交换到模拟递归(循环)中,并在它完成后接收关于策略性能的一些数据。另外,我希望允许策略保持任意状态,我不想关心每种策略保持的状态。但是,这个决定 - 实际上是至关重要的 - 复杂一切。另一个要求,具体导致了下面描述的问题,一个策略名称可以作为命令行参数提供,然后模拟运行该特定策略。



起初,我认为这是一个适合这些要求的类型类,但是在没有想出如何用这种方法对代码进行建模的真正想法之后,我放弃了这个想法。然后,我决定使用一个ADT,从此使用它,并且与代码相距甚远 - 直到现在。






表面的问题是如何解决我在下面提供的问题。更深层次的问题是如何更好地模拟我对Haskell中任意状态接口的需求。



下面是我的代码中一个简化和改编的摘录:

   - 减少&简化示例
导入Control.Monad.State

类型代码= [Int]

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

- 正如你所看到的,我决定了一个类型变量a
- 表示任意状态a战略可能会带来
- around。我想知道这是否是正确的做法。
- |这是一个策略的界面。一个策略必须提供一个方法
- 在给出主谋答案的情况下,返回下一个猜测,初始状态
- 以及第一个猜测。
数据策略a =策略{
firstGuess :: Int - >代码,
initialize :: Int - > a, - 伪装
中的构造函数guess :: Answer - >州代码
}

dummy =策略{
firstGuess = firstGuess',
initialize = initialize',
guess = guess'
}

- |第一个猜测总是T0,T1,...,Tx,其中x是代码长度。
firstGuess':: Int - >代码
firstGuess'length = [0..length-1]

- |记住代码长度
initialize':: Int - > Int
初始化'= id

- |总是返回相同的猜测
guess'::答案 - > State Int Code
guess'= const $ liftM firstGuess'get

- 这是问题
- 我需要这个函数,因为我会得到一个策略的名称
- 作为命令行中的一个字符串,需要将
- 正确的策略分配给模拟。请注意,有
- 当然有更多的模式匹配不同的策略
- 不同的伴随状态a。
nameToStrategy :: String - >策略a
nameToStrategydummy= dummy

执行该文件会产生以下错误消息:

  Prelude> :l Problem.hs 
[1 of 1]编译Main(Problem.hs,解释)

Problem.hs:37:25:
无法匹配预期的类型` '反对推断类型'Int'
'a'是一个刚性类型变量,由
绑定,在Problem.hs:36:37
处为`nameToStrategy'类型签名期望类型:策略a
推断类型:策略Int
在表达式中:dummy
在`nameToStrategy'的定义中:
nameToStrategydummy= dummy
失败,已加载模块:无。

我可以直观地理解问题。问题似乎是, nameToStrategy 不能只返回一个具有状态 a 的策略。类型变量必须是
具体值,因为如果将 nameToStrategy 的类型更改为 String - > Strategy Int 一切都很好。但这不是我的问题的解决方案。



我想我需要放松这种类型。但是,我真的不知道该怎么做。我听说过 Data.Dynamic 和存在类型,这些可能会对我有所帮助。不过,我认为,通过更好地建模我的代码,我不需要那些代码。




编辑:我设法将sclv的建议融入代码中,现在好多了。策略的代码更清晰,因为我不再需要特殊情况下的第一个猜测了,我可以使用警卫来更好地区分正确和错误猜测的情况。由于我把 stepState (以及使用 stepState 的函数)放入了主要的模拟处理函数, IO Monad来测量计算时间,并因此产生一些monadic语法噪声。轻松模拟几个模拟步骤(之前实际上不可能)的能力帮助我找到了一个相互递归的无限循环(该错误很难理解)。总而言之,现在代码感觉更加离散。不用说,我不再需要 unsafeCoerce hack来将名字分派给策略(或更好的打包策略)。我希望有一天我的思维功能也会自然而然地呈现给我。

解决方案

好吧,让我们从头开始。一个纯粹的策略是一个知识状态产生猜测的函数。 州 - >猜。对于任何给定的状态,都有一些方法可以为其添加新的知识 - 答案 - >状态 - >状态

 数据Strategy state = Strategy {
initialState: :状态,
extractGuess :: state - >猜,
updateState ::答案 - >状态 - >状态
}

现在让我们看看构成这些函数时会发生什么。

  type Oracle = Guess  - >可能的答案 - 我们将编码成功为Nothing 

stepState :: Oracle - >策略状态 - >状态 - >可能状态
stepState oracle str state = fmap(\ans-> updateState str ans state)
oracle(extractGuess str state)

stepMany ::策略状态 - > ; Oracle - > [state]
stepMany str oracle = go(initialState str)
其中go state = case stepState oracle str状态
Nothing - > []
Just newState - > newState:go newState

所以 stepMany 是90我们想要的百分比,但它在这个讨厌的状态参数中仍然是多态的。这很容易解决 - 毕竟我们想要的步骤数量,而不是步骤本身的中间状态。

 键入packedStrategy = Oracle  - > Int 

packStrategy ::策略状态 - > PackedStrategy
packStrategy str oracle = length $ stepMany str oracle

现在您可以编写 [packStrategy stratOne,packStrategy stratTwo] 等等。一路上,我们发现了一些重要的东西 - 你关心的是你的策略只是从某个问题(由甲骨文代表)解决问题所需的步骤。产生这种策略的一种方式(不是唯一的方式)是提供一种方法来询问新知识(猜测)和更新知识的方式(更新状态)。

这不是唯一的答案,也许对于你的目的来说并不理想,但它应该有助于推动你思考函数和类型,而不是对象和能力。 / p>

My specific problem is actually not about the general translation of an OO interface to Haskell. This is just the best title I could come up with. Yet, I'm sure that my problem originates from a still poor understanding of modeling code with Haskell and a mindset still located in the land of OO paradigms (still a haskell beginner, you see).

I'm writing a Mastermind (variation) simulation to test the fitness of several Mastermind strategies. As a matter of fact, I already did that in Java and Lua and thus this Haskell version is just an exercise for me to learn to program in Haskell. You can check out the readme of the Lua/Java version if you are interested in what I'm trying to achieve in the end.

But now for my concrete problem (in short and in OO terms): I want to provide an interface for strategies so that I can interchangeably put a strategy that adheres to that interface into the simulation recursion (loop) and after it's done receive some data about the strategy's performance. Additionally, I want to allow the strategy to keep arbitrary state around and I don't want to care about what kind of state each strategy keeps around. But exactly this decision - which is actually essential - complicated everything. Another requirement, which concretely led to the problem describe below, is that a strategy name can be provided as a command line argument and then the simulation runs with that specific strategy.

At first I deemed a type class appropriate for these requirements but after not having come up with a real idea how to model the code this way I abandoned the idea. Then I decided for an ADT, used it ever since and came relatively far with the code - until now.


So, the superficial question is how to resolve the problem I provide below. The deeper question is how to better model my requirements for an interface with arbitrary state in Haskell.

Here is a reduced and adapted excerpt from my code:

-- 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

Executing the file yields the following error message:

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.

I kind of can intuitively comprehend the problem. The problem seems to be that nameToStrategy cannot just return a Strategy with some state a. The type variable must be concrete, since if I change the type of nameToStrategy to String -> Strategy Int everything's fine. But that is not a solution to my problem.

I figured I need to relax the type. However, I don't really know how to do it. I heard about Data.Dynamic and existential types and those might help me. Still, I feel that with a better modeling of my code I would not need those.


Edit: I managed to incorporate sclv's suggestions into the code after all and it is much better now. The code for the strategies is clearer since I don't need the special case for the first guess anymore and I can use guards to better distinguish between the case of a correct and an incorrect guess. The main simulation handling is not as elegant as sclv's version since I put stepState (and the functions using stepState) into the IO Monad to measure computation time and thus have some "monadic syntax noise". The ability to easily simulate a couple of simulation steps (which wasn't actually possible before) helped me in finding a mutual recursive infinite loop (that bug was weird to understand). All in all, the code feels more discrete now. Needless to say, I don't need the unsafeCoerce hack anymore to dispatch names to strategies (or better "packed strategies"). I hope the functional way of thinking someday will come naturally to me, too.

解决方案

Ok let's start from scratch. A pure strategy is a function that given a state of knowledge yields a guess. state -> Guess. For any given state, there's some way to add new knowledge to it -- Answer -> state -> state. Rather than an initial guess, we now just need an initial state.

data Strategy state = Strategy {
                 initialState :: state,
                 extractGuess :: state -> Guess,
                 updateState  :: Answer -> state -> state
         }

So now lets see what happns when we compose these functions.

type Oracle = Guess -> Maybe Answer -- we'll encode success as Nothing

stepState :: Oracle -> Strategy state -> state -> Maybe state
stepState oracle str state = fmap (\ans -> updateState str ans state) $ 
                                      oracle (extractGuess str state)

stepMany :: Strategy state -> Oracle -> [state]
stepMany str oracle = go (initialState str)
      where go state = case stepState oracle str state of
               Nothing -> []
               Just newState -> newState : go newState

So stepMany is 90% of what we want, but its still polymorphic in that pesky state param. That's easy enough to work around -- after all we want the number of steps, not the intermediate states of the steps themselves.

type packedStrategy = Oracle -> Int

packStrategy :: Strategy state -> PackedStrategy
packStrategy str oracle = length $ stepMany str oracle

And now you can write [packStrategy stratOne, packStrategy stratTwo] etc. And along the way, we've discovered something important -- what you care about from your strategy is just that it is a function from some problem (represented by an oracle) to the steps it takes to solve the problem. And one way (not the only way) to produce such stratgies is to provide a way to ask for new knowledge (to guess) and a way to update our knowledge (update state).

This is not the only answer, and maybe not ideal for your purposes, but it should help to move you towards thinking with functions and types rather than objects and capabilities.

这篇关于OO接口转换为Haskell的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆