将命令式Java翻译成功能性Java(游戏)

时间:2015-10-10 20:24:57

标签: java functional-programming java-8 imperative-programming declarative-programming

我学习了很多关于Java 8及其功能的知识,我想用它做更多的练习。比方说,我有以下命令性代码,用于围绕屏幕边界包裹一个圆圈:

if (circle.getPosition().getX() > width + circle.getRadius()){
    circle.getPosition().setX(-circle.getRadius());
}else if (circle.getPosition().getX() < -circle.getRadius()){
    circle.getPosition().setX(width + circle.getRadius());
}
if (circle.getPosition().getY() > height + circle.getRadius()){
    circle.getPosition().setY(-circle.getRadius());
}else if (circle.getPosition().getY() < -circle.getRadius()){
    circle.getPosition().setY(height + circle.getRadius());
}
  1. 我怎样才能尝试&#34;功能化&#34;它?也许有些伪代码?在我看来,在这个例子中,可变性和状态似乎是固有的。
  2. 功能性编程不适合游戏开发吗?我喜欢这两者,所以我试图将它们结合起来。

7 个答案:

答案 0 :(得分:7)

在这个例子中,对可变性的要求并不固有。必要的方法是通过应用改变现有圆的状态的副作用来修改现有的圆。

功能方法是拥有一个不可变的数据结构,并创建一个从第一个结构获取数据并创建新结构的函数。在您的示例中,函数式方法将使圆是不可变的,即没有setX()或setY()方法。

private Circle wrapCircleAroundBounds(Circle circle, double width, double height) {
    double newx = (circle.getPosition().getX() > width + circle.getRadius()) ? -circle.getRadius() : width + circle.getRadius()
    double newy = (circle.getPosition().getY() > height + circle.getRadius()) ? -circle.getRadius() : height + circle.getRadius()
    return new Circle(newx, newy)
}

使用Java8的功能,您可以想象将圆圈列表映射到包裹的圆圈:

circles.stream().map(circ -> wrapCircleAroundBounds(circ, width, height))

命令式和功能性方法具有不同的优点,例如,功能方法由于不变性而具有内在的线程安全性,因此您应该能够更容易地并行化这种代码。例如,人们可以同样安全地写:

circles.parallelStream().map(circ -> wrapCircleAroundBounds(circ, width, height))

我认为函数式编程不一定非常适合游戏开发,但是,虽然它已经完成,但它肯定不是一种标准方法,所以如果你使用的话,你将无法获得相同级别的库支持。功能语言。

正如dfeuer在他的回答中所述,Java的功能特性非常原始 - 您不支持代数数据类型,模式匹配等,这样可以更容易地以功能样式表达问题(至少在您习惯那些成语)。我同意至少阅读一下Haskell,它有一个很好的教程:http://learnyouahaskell.com/chapters将是一个很好的入门方式。与Scala不同,Scala是一种多范式语言,在学习新风格时,你不会有OOP功能。

答案 1 :(得分:3)

关于你的第一点:你&#34;功能化&#34;通过思考代码应该实现的内容来实现你的榜样。这就是说,你有一个圆圈,并希望根据某些条件计算另一个圆圈。但由于某种原因,你的命令式教养让你认为输入圆和输出圆应存储在相同的存储位置!

为了实现功能,首先要忘记内存位置并拥抱值。您可以按照与creditCardInformation: function(creditCardHolderName,creditCardNumber,creditCardExpirationDate,creditCa‌​rdCvvCode,creditCardBillingAddress,creditCardBillingCity,creditCardBillingState,c‌​reditCardBillingCountry)int或其他数字类型相同的方式考虑每种类型。

举个例子,假设一些新手给你看了这样的代码:

java.lang.Integer

并抱怨double x = 3.765; sin(x); System.out.println("The square root of x is " + x); 似乎无法正常工作。那你觉得怎么样?

现在考虑一下:

sin

当后一个代码对你来说像前者一样荒谬时,你将爬上功能编程的第一步。是的,这意味着不要使用您正在使用的命令式语言的某些功能,或者非常谨慎地使用它们。

答案 2 :(得分:2)

这里没有太多“功能化”适用。但至少我们可以与mutability战斗。 首先是pure functions。这有助于区分logic。使其清晰易用。 回答这个问题:你的代码是做什么的?它接受一些参数并返回两个参数xy。 下一个样本将使用pseudo scala编写。

因此,您需要一个为x和y计算调用两次的函数。

def (xOrY: Int, widthOrHeight: Int, radius: Int): Int = {

if (x > widthOrHeight + radius) -1*radius else  widthOrHeight + radius
// do your calculation here - return x or y values.

}

P.S&GT;到目前为止,无论你想在哪里应用功能风格:因为你需要做一些业务逻辑,所以最好采用功能方法。

但是不要试图过度复杂化它,因为它没有帮助。 所以我不会为这个样本做下一步(伪scala接下来):

def tryToMakeMove(conditions: => Boolean, move: => Unit) = if (conditions) move()
/// DO NOT DO IT AT HOME :)
tryToMakeMove(circle.getPosition().getX() > width + circle.getRadius(),  circle.getPosition().setX(-circle.getRadius())).andThen()
   tryToMakeMove(circle.getPosition().getX() < -circle.getRadius()), circle.getPosition().setX(width + circle.getRadius()))
).andThen ... so on.

功能程序的外观如何。我已经创建了高阶函数(它接受其他函数作为参数并在其中调用它)。 有了这个功能,我已经调用了一个你需要做的操作...... 但这种功能风格并没有真正帮助。完全没有。您应该只在简化代码的地方正确应用它。

答案 3 :(得分:1)

您可以使用任何编程语言编写功能代码,但不能轻易地学习任何语言的函数编程。特别是Java使函数式编程变得非常痛苦,以至于那些想在JVM中进行函数式编程的人想出了Clojure和Scalaz。如果你想学习思维的功能性思维(它自然处理哪些问题,如何处理问题,哪些问题更尴尬以及如何管理它们等等),我强烈建议你花一些时间来实现功能性或大部分功能性语言。基于语言质量,易于坚持功能习语,学习资源和社区的结合,我的首选将是Haskell,我的下一个将是Racket。其他人当然会有其他意见。

答案 4 :(得分:1)

  

我怎样才能尝试&#34;功能化&#34;它?也许一些   伪代码?在我看来,可变性和状态似乎是固有的   这个例子。

您可以尝试将可变性限制为几个函数,并在函数内部使用最终变量(这会强制您使用表达式而不是语句)。这是一种可能的方式:

Position wrapCircle(Circle circle, int width, int height) {
  final int radius = circle.getRadius();
  final Position pos = circle.getPosition();
  final int oldX = pos.getX();
  final int x = (oldX > width + radius) ? -radius : (
        (oldX < -radius) ? (width + radius) : oldX);

  final int y = // similar

  return new Position(x, y);
}

circle.setPosition(wrapCircle(circle, width, height));

除此之外,我会将wrapCircle类作为Circle类的方法,以获得:

circle.wrapCircle(width, height);

或者我可以更进一步定义一个getWrappedCircle方法,它会返回一个新的圆形实例:

Circle getWrappedCircle(width, height) {
  newCircle = this.clone();
  newCircle.wrapCircle(width, height);
  return newCircle();
}

..取决于您打算如何构建其余代码。

提示:尽可能多地使用final关键字在Java中。它会自动赋予更实用的风格。

  

功能编程不适合游戏开发吗?我喜欢这两者,所以我试图将它们结合起来。

纯函数编程速度较慢,因为它需要大量复制/克隆数据。如果性能很重要,那么你肯定可以尝试混合方法,如上所示。

我建议尽可能多地使用不变性,然后进行基准测试,然后仅在性能关键部分转换为可变性。

答案 5 :(得分:1)

功能编程适合游戏开发(为什么不呢?)。问题通常更多是关于性能和内存消耗,或者即使任何功能性游戏引擎可以击败这些指标中现有的非功能性游戏引擎。您不是唯一热爱功能编程和游戏开发的人。看起来John Carmack也是如此,在Quakecon 2013 starting from 02:05上观看关于主题的主题演讲。他的笔记herehere甚至可以提供有关如何构建功能性游戏引擎的见解。

除了理论基础之外,通常还有两个概念被新手和实际前景所感知。它们是数据不变性状态缺失。前者意味着数据永远不会改变,后者意味着每个任务都是在没有先验知识的情况下首次执行。 考虑到这一点,你的命令式代码有两个问题:setter mutate 圆圈位置,代码依赖于width的外部值(全局状态)和{ {1}}。要修复它们,您的函数会在每次更新时返回一个新的圆圈,并将屏幕分辨率作为参数。让我们应用the first clue from the video并传递对世界静态快照的引用以及对正在更新的实体的引用&#34; (这里只是height)更新功能:

this

现在棘手的部分是如何影响世界和其他实体的状态。您可以按照second clue from the video并使用事件传递机制来往返传递此类更改,以便游戏的其余部分了解所有效果。

显然,即使更改圆圈位置,您也只能保留事件并完全依赖它们。因此,如果您为实体引入了一种ID,则可以传递class Circle extends ImmutableEntity { private int radius; public Circle(State state, Position position, int radius) { super(state, position); this.radius = radius; } public int getRadius() { return radius; } @Override public ImmutableEntity update(World world) { int updatedX = getPosition().getX(); if (getPosition().getX() > world.getWidth() + radius){ updatedX = -radius; } else if (getPosition().getX() < -radius){ updatedX = world.getWidth() + radius; } int updatedY = getPosition().getX(); if (getPosition().getY() > world.getHeight() + radius){ updatedY = -radius; } else if (getPosition().getY() < -radius) { updatedY = world.getHeight() + radius; } return new Circle(getState(), new Position(updatedX, updatedY), radius); } } class Position { private int x; private int y; //here can be other quantities like speed, velocity etc. public Position(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } class State { /*...*/ } abstract class ImmutableEntity { private State state; private Position position; public ImmutableEntity(State state, Position position) { this.state = state; this.position = position; } public State getState() { return state; } public Position getPosition() { return position; } public abstract ImmutableEntity update(World world); } class World { private int width; private int height; public World(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } }

答案 6 :(得分:0)

好的,现在是时候让我们所有人了解Java 8的新功能和新功能。 “功能化”某事实际上并不是一个有效的目标。

但是,这里的原始代码有一个很好的面向对象的问题:

当你说circle.getPosition()。setX(...)时,你正在弄乱圆的内部状态(它的位置)而不涉及对象本身。这破坏了封装。如果圆类已正确设计,则getPosition()方法将返回位置的副本或不可变位置,以便您无法执行此操作。

是您真正需要使用此代码修复的问题......

那么,你应该怎么做?

嗯,你当然可以在Circle中提出一些功能界面,但老实说,如果你只有circle.move(double x,double y),你的代码会更具可读性;