Translating Imperative Java to Functional Java (A Game)
Asked Answered
L

7

13

I'm learning a lot more about Java 8 and its functional capabilities, and I wanted to do some more practice with it. Say, for example, I have the following imperative code which is for wrapping a circle around the bounds of the screen:

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. How could I go about trying to "Functionalize" it? Maybe some pseudo-code? It seems to me that mutability and state seem inherent in this example.
  2. Is functional programming not a good fit for game development? I love the both, so I'm trying to combine them.
Laryngotomy answered 10/10, 2015 at 20:24 Comment(0)
V
7

There is nothing inherent about the requirement for mutability in this example. The imperative approach is to modify an existing circles by applying side-effects which alter the state of an existing circle.

The functional approach is to have an immutable data structure and create a function that takes data from the first structure and creates a new structure. In your example, a functional approach would have the circle being immutable, i.e. no setX() or setY() methods.

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

Using Java8's functional features, you could then imagine mapping a list of circles to wrapped circles:

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

The imperative and functional approaches have different advantages, the functional approach, for example, is intrisicaly threadsafe because of the immutability so you should be able to more readily parallelise this kind of code. For instance, one could equally safely write:

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

I don't think that functional programming is necessarily badly suited to game development but, although it has be done, it's certainly not a standard approach so you won't get the same level of library support if you're using a functional language.

As dfeuer states in his answer, Java's functional features are pretty primitive - you don't have support for algebraic data types, pattern matching, etc which will make it much easier to express problems in a functional style (at least once you get used to those idioms). I agree that at least reading a bit about Haskell, which has an excellent tutorial: http://learnyouahaskell.com/chapters would be a good way to get started. Unlike Scala, which is very much a multiparadigm language, you won't have OOP features to fall back on while you're learning the new style.

Virtu answered 4/11, 2015 at 11:47 Comment(4)
Doesn't circles.stream().map(circ -> wrapCircleAroundBounds(circ)) just return a new stream? Do I eventually have to modify the circles list? Reassign it maybe?Laryngotomy
@Laryngotomy You can use .collect(Collectors.toList()) to convert the stream back into a list if that is the form you need the result in. In functional programming you don't tend to modify or reassign things, you would usually just store the result of the computation to a new List.Virtu
You wrapCircleAroundBounds relies on outside values of width and height and is not pure functional therefore.Penland
@MaximPlevako That's a pretty good point. Will update my answer.Virtu
F
3

For your first point: You "functionalize" your example by thinking about what the code ought to achieve. And this is, you have a circle, and want to compute another circle based on some conditions. But for some reason your imperative upbringing makes you assume that the input circle and the output circle should be stored in the same memory locations!

For being functional, the first thing is to forget memory locations and embrace values. Think of every type the same way you think of int or java.lang.Integer or the other numeric types.

For an example, assume some newbie shows you some code like this:

double x = 3.765;
sin(x);
System.out.println("The square root of x is " + x);

and complains that sin doesn't seem to work. What would you think then?

Now consider this:

Circle myCircle = ....;
wrapAroundBoundsOfScreen(myCircle);
System.out.println("The wrapped-around circle is now " + myCircle);

You will have climbed the first step to functional programming when the latter code seems as absurd to you as the former. And yes, this does mean not to use certain features of the imperative language you are using, or use them extremely sparingly.

Fa answered 1/11, 2015 at 22:9 Comment(1)
Thanks, awesome answer! Also, your Frege language looks amazing, nice work! @FaLaryngotomy
S
2

Here not much 'functionalization' applicable. But at least we can fight with mutability. First of all pure functions. This will help to separate logic. Make it clear and easy to test. Answer the question: what is your code do? It accepts some params and returns two params new x and y. Next samples will be written with pseudo scala.

So you need a function that will be invoked two times for both x and y calculation.

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> so far no matter where you want to apply functional style: as you need to do some business logic it's good to go with functional approach.

But do not try overcomplicate it as it does not help. So what I would not do for this sample is next (pseudo scala goes next):

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.

That how functional programs can looks like. I've created the higher-order function (that accepts other functions as an arguments and invoke it inside). With this functions, i've invoked one be one operations you have to do... But such functional style does not really help. At all. You should apply it properly only in a places where it's simplify the code.

Sticker answered 1/11, 2015 at 22:36 Comment(3)
So, do you think that this situation does not lend itself well to functional programming? Imperative better suits it?Laryngotomy
I think you should write clean code. I've just shown ugly functional sample (second part). But it can be a little bit clean up and be better or same as written in imperative style.Sticker
This is not idiomatic functional code, it is imperative code with first class functions. You are passing in a function that does the moving but that function is of unit type, i.e. it's a side effect. Purely Functional code avoids side effects entirely, that means X and Y should not be being mutated anywhere.Virtu
C
1

You can write functional code in just about any programming language, but you can't easily learn functional programming in any language. Java in particular makes functional programming sufficiently painful that people who wanted to do functional programming in the JVM came up with Clojure and Scalaz. If you want to learn the functional way of thinking (what problems it deals with naturally and how, what problems are more awkward and how it manages them, etc.), I strongly recommend that you spend some time with a functional or mostly-functional language. Based on a combination of language quality, ease of sticking to functional idioms, learning resources, and community, my top pick would be Haskell and my next would be Racket. Others will of course have other opinions.

Collator answered 13/10, 2015 at 19:27 Comment(0)
M
1

How could I go about trying to "Functionalize" it? Maybe some pseudo-code? It seems to me that mutability and state seem inherent in this example.

You could try to limit the mutability to a few functions, and also use final variables inside the functions (which forces you to use expressions rather than statements). Here's one possible way:

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

Aside, I would make wrapCircle a method of the Circle class, to get:

circle.wrapCircle(width, height);

Or I could go one step further and define a getWrappedCircle method, that returns me a new circle instance:

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

.. depending on how you intend to structure the rest of the code.

Tip: Use final keyword as often as you can in Java. It automatically lends to a more functional style.

Is functional programming not a good fit for game development? I love the both, so I'm trying to combine them.

Pure functional programming is slower, because it requires lots of copying / cloning of data. If performance is important, then you could definitely try a mixed approach, as shown above.

I would suggest using as much immutability as possible, followed by benchmarking, and then converting to mutability in only the performance critical sections.

Madlin answered 8/11, 2015 at 7:31 Comment(0)
P
1

Functional programming fits game development (why would not it?). The question is usually more about performance and memory consumption or even if any functional game engine can beat an existing non-functional one in those metrics. You are not the only person who loves functional programming and game development. Seems like John Carmack does too, watch his keynotes about the topics at Quakecon 2013 starting from 02:05. His notes here and here even give insight on how a functional game engine can be structured.

Setting theoretical foundation aside, there are usually two concepts perceived inherent in functional programming by a newcomer and from a practical prospect. They are data immutability and state absence. The former means that data never changes and the latter means every task is performed as if for the first time with no prior knowledge. Considering that, you imperative code has two problems: the setters mutate the circle position and the code relies on outside values (a global state) of width and height. To fix them make your function return a new circle on each update and take the screen resolutions as arguments. Let's apply the first clue from the video and pass a reference to the static snapshot of the world and a reference to an entity being "updated" (it is simply this here) to an update function:

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

Now the tricky part is how to affect the state of the world and other entities. You can follow the second clue from the video and use event passing mechanism to pass such changes to and fro so the rest of the game knows about all the effects.

Obviously, you can keep only events and rely completely on them even when changing your circle positions. So, if you introduce sort of an id to your entities you will be able to pass MoveEntity(id, newPosition).

Penland answered 8/11, 2015 at 13:42 Comment(0)
S
0

OK, it's time for us all to get over how new and shiny Java 8's functional features look. "Functionalizing" something is really not a valid goal to have.

However, the original code here has a good ol' object-oriented problem:

When you say circle.getPosition().setX(...), you are messing with the internal state of the circle (its position) without involving the object itself. That breaks encapsulation. If the circle class were properly designed, then the getPosition() method would return a copy of the position or an immutable position so that you couldn't do this.

That is the problem you really need to fix with this code...

How, then, should you do that?

Well, you could certainly come up with some functional interface in Circle, but honestly your code will be more readable if you just have circle.move(double x, double y);

Stitching answered 7/11, 2015 at 22:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.