13

Я изучаю намного больше о Java 8 и его функциональных возможностях, и я хотел бы сделать еще несколько практических действий с ним. Скажем, к примеру, у меня есть следующий императивный код, который для упаковки круг вокруг границ экрана:Перевод императивной Java на функциональную Java (игра)

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. Как я могу идти о попытке «функционализации» это? Может быть, какой-то псевдокод? Мне кажется, что изменчивость и состояние кажутся присущими этому примеру.
  2. Функциональное программирование не подходит для развития игры? Мне нравятся оба, поэтому я пытаюсь их объединить.

ответ

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

повелительном и функциональные подходы имеют различные преимущества, функциональный подход, например, intrisicaly threadsafe из-за неизменности, поэтому вы должны иметь возможность более легко распараллеливать этот тип кода. Например, можно было бы одинаково смело писать:

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

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

Как утверждает в своем ответе dfeuer, функциональные функции Java довольно примитивны - у вас нет поддержки алгебраических типов данных, сопоставления образцов и т. Д., Что значительно облегчит выражение проблем в функциональном стиле (по крайней мере один раз вы привыкаете к этим идиомам). Я согласен с тем, что, по крайней мере, немного читаю о Haskell, у которого есть отличный учебник: http://learnyouahaskell.com/chapters - хороший способ начать работу. В отличие от Scala, который является многоязычным языком, у вас не будет возможностей OOP, чтобы отпасть, когда вы изучаете новый стиль.

+0

Не 'круги.stream(). Map (circ -> wrapCircleAroundBounds (circ))' просто вернуть новый поток? Должен ли я в конечном итоге изменить список «кругов»? Переставить его, может быть? – Michael

+0

@Michael Вы можете использовать '.collect (Collectors.toList())', чтобы преобразовать поток обратно в список, если это форма, в которой вам нужен результат. В функциональном программировании вы не склонны изменять или переназначать вещи, вы обычно просто сохраняете результат вычисления в новом списке. – TheInnerLight

+0

Вы 'wrapCircleAroundBounds' полагаются на внешние значения' width' и 'height' и поэтому не являются чисто функциональными. –

1

Вы можете написать функциональный код практически в любом языке программирования, но вы не можете легко научиться функционального программирования на любом языке. Java, в частности, делает функциональное программирование достаточно болезненным, что люди, которые хотели выполнять функциональное программирование в JVM, придумали Clojure и Scalaz. Если вы хотите изучить функциональный способ мышления (с какими проблемами он сталкивается естественно и как, какие проблемы более неудобны и как они управляют ими и т. Д.), Я настоятельно рекомендую вам провести некоторое время с функциональным или главным образом функциональным язык. Основываясь на сочетании качества языка, простоты использования функциональных идиом, учебных ресурсов и сообщества, моим лучшим выбором будет Haskell, а мой следующий будет Racket. Разумеется, у других будут другие мнения.

3

Для вашего первого пункта: вы «функционируете» на своем примере, думая о том, чего должен достичь код. И это, у вас есть круг, и вы хотите вычислить другой круг, основанный на некоторых условиях. Но по какой-то причине ваше императивное воспитание заставляет вас предположить, что входной круг и выходной круг должны храниться в одних и тех же ячейках памяти!

Для того, чтобы быть функциональным, необходимо забыть места памяти и принять значения. Думайте о каждом типе так же, как вы думаете о int или java.lang.Integer или других цифровых типах.

Для примера предположим, что некоторые новичку показывает некоторый код, как это:

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

и жалуется, что sin не похоже на работу. Что бы вы тогда подумали?

Теперь рассмотрим это:

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

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

+0

Спасибо, удивительный ответ! Кроме того, ваш язык Frege выглядит потрясающе, приятная работа! @Ingo – Michael

2

Здесь не очень «функционализация» применима. Но по крайней мере мы можем бороться с mutability. Прежде всего pure functions. Это поможет отделить logic. Сделайте его понятным и легким для тестирования. Ответьте на вопрос: что делает ваш код? Он принимает некоторые параметры и возвращает два параметра new x и y. Следующие образцы будут написаны с помощью 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> до сих пор не важно, где вы хотите применить функциональный стиль: как вам нужно сделать некоторые бизнес-логики это хорошо идти с функциональным подходом.

Но не пытайтесь перекомплементировать его, поскольку это не помогает. Так что я бы не сделать для этого образца является следующим (псевдо Скала идет рядом):

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. 

То, как функциональные программы могут выглядит. Я создал функцию более высокого порядка (которая принимает другие функции в качестве аргументов и вызывает ее внутри). С помощью этих функций я вызывал одну операцию, которую вы должны выполнить ... Но такой функциональный стиль на самом деле не помогает. Вообще. Вы должны применять его правильно только в тех местах, где это упрощает код.

+0

Итак, как вы думаете, эта ситуация не подходит для функционального программирования? Императив лучше подходит? – Michael

+0

Я думаю, вы должны написать чистый код. Я только что показал уродливый функциональный образец (вторая часть). Но это может быть немного чистым и быть лучше или то же, что написано в императивном стиле. – Rumoku

+0

Это не идиоматический функциональный код, это императивный код с функциями первого класса. Вы передаете функцию, которая выполняет перемещение, но эта функция имеет тип единицы, т. Е. Это побочный эффект. Чисто Функциональный код полностью исключает побочные эффекты, это означает, что X и Y не должны мутировать нигде. – TheInnerLight

0

OK, пришло время всем нам разобраться, как выглядят новые и блестящие функциональные возможности Java 8. «Функционализация» - это действительно нецелесообразная цель.

Однако исходный код здесь имеет хороший ПР»объектно-ориентированной задачи:

Когда вы говорите circle.getPosition() Setx (...), вы баловаться с внутренним состоянием окружности. (его положение) без участия самого объекта. Это нарушает инкапсуляцию. Если класс круга был правильно спроектирован, метод getPosition() вернул бы копию позиции или неизменяемой позиции, чтобы вы не могли этого сделать.

Это проблема вам действительно нужно исправить с помощью этого кода ...

Как тогда, вы должны сделать это?

Ну, конечно, вы можете придумать какой-то функциональный интерфейс в Circle, но, честно говоря, ваш код будет более читабельным, если у вас просто есть circle.move (double x, double y);

1

Как я могу попытаться «Функционализировать» его? Может быть, какой-то псевдокод? Мне кажется, что изменчивость и состояние кажутся присущими этому примеру в .

Вы можете попытаться ограничить изменчивость несколькими функциями, а также использовать конечные переменные внутри функций (что заставляет вас использовать выражения, а не заявления). Вот один из возможных способов:

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. Он автоматически поддается более функциональному стилю.

Функциональное программирование не подходит для разработки игр? Мне нравятся оба, поэтому я пытаюсь их объединить.

Чистое функциональное программирование происходит медленнее, поскольку для этого требуется много копирования/клонирования данных. Если производительность важна, вы можете определенно попробовать смешанный подход, как показано выше.

Я бы предложил использовать как можно больше неизменности, а затем провести бенчмаркинг, а затем перейти к изменчивости только в критических разделах.

1

Функциональное программирование подходит для разработки игр (почему бы и нет?). Вопрос, как правило, связан скорее с производительностью и потреблением памяти, или даже если любой функциональный движок игры может превзойти существующий нефункциональный в этих показателях. Вы не единственный человек, который любит функциональное программирование и разработку игр. Похоже, что Джон Кармак тоже делает это, наблюдая за его лейтмотивами по темам на Quakecon 2013 starting from 02:05. Его заметки here и here даже дают представление о том, как может функционировать функциональный игровой движок.

Исходя из теоретической основы, обычно есть две концепции, воспринимаемые новичком в функциональном программировании и с практической точки зрения. Они являются данные неизменяемости и отсутствие государства. Первое означает, что данные никогда не меняются, а последнее означает, что каждая задача выполняется так, как если бы она впервые не имела никаких предварительных знаний. Учитывая, что вы императивный код имеет две проблемы: сеттеры мутировать позиция круг и код зависит от внешних значений (глобальная состояния) в width и height. Чтобы исправить их, ваша функция возвращает новый круг для каждого обновления и принимает разрешения экрана в качестве аргументов. Давайте применим the first clue from the video и передать ссылку на статический снимок мира и ссылки на сущности быть «обновленной» (это просто this здесь) к функции обновления:

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

Теперь сложная часть, как влияют на состояние мира и других субъектов. Вы можете следить за second clue from the video и использовать механизм прохождения событий, чтобы передавать такие изменения туда и обратно, поэтому остальная часть игры знает обо всех эффектах.

Очевидно, что вы можете сохранять только события и полностью полагаться на них даже при изменении положения круга. Итак, если вы представите свой идентификатор своим сущностям, вы сможете пройти MoveEntity(id, newPosition).

+0

Удивительный ответ, спасибо! – Michael