Подтвердить что ты не робот

Помогите с пониманием функционального объекта или функтора в Java

Может кто-нибудь объяснить, что такое функтор, и предоставить простой пример?

4b9b3361

Ответ 1

Объект функции - это просто. То, что является одновременно объектом и функцией.

Помимо этого: вызов функционального объекта "функтор" является серьезным злоупотреблением термином: различные "функторы" - это центральное понятие в математике, которое имеет непосредственную роль в информатике (см. "Haskell Functors" "). Этот термин также используется несколько иначе в ML, поэтому, если вы не реализуете одну из этих концепций на Java (что вы можете!), Пожалуйста, прекратите использовать эту терминологию. Это делает простые вещи сложными.

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

Другими словами, вы не можете написать что-то вроде этого:

 public static void tentimes(Function f){
    for(int i = 0; i < 10; i++)
       f();
 }
 ...
 public static void main{
    ...
    tentimes(System.out.println("hello"));
    ...
 }

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

Итак, что мы делаем?

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

public interface Runnable{
    public void run();
}

теперь мы можем переписать мой пример сверху:

public static void tentimes(Runnable r){
    for(int i = 0; i < 10; i++)
       r.run();
}
...
public class PrintHello implements Runnable{
    public void run{
       System.out.println("hello")
    }
}
---
public static void main{
    ...
    tentimes(new PrintHello());
    ...
 }

Очевидно, этот пример надуман. Мы могли бы сделать этот код немного приятнее, используя анонимные внутренние классы, но это дает общую идею.

Здесь это разрывается: Runnable используется только для функций, которые не принимают никаких аргументов, и не возвращают ничего полезного, поэтому вы должны определить новый интерфейс для каждого задания. Например, интерфейс Comparator в ответе Мохаммада Фейсала. Предоставление более общего подхода, а также синтаксиса, является главной целью для Java 8 (следующая версия Java) и в значительной степени было включено для включения в Java 7. Это называется "лямбдой" после механизма абстракции функции в исчислении лямбда. Lambda Calculus - это (возможно) самый старый язык программирования и теоретическая основа значительной части компьютерных наук. Когда Алонсо Церковь (один из главных основателей информатики) придумала это, он использовал греческую букву лямбда для функций, отсюда и название.

Другие языки, включая функциональный язык (Lisp, ML, Haskell, Erlang и т.д.), большинство основных динамических языков (Python, Ruby, JavaScript и т.д.) и другие языки приложений (С#, Scala, Go, D и т.д.) Поддерживают некоторую форму "Lambda Literal". Даже С++ имеет их сейчас (начиная с С++ 11), хотя в этом случае они несколько сложнее, потому что С++ не имеет автоматического управления памятью и не сохранит ваш стек для вас.

Ответ 2

Функтор - это объект, который выполняет функцию.

У Java их нет, потому что функции не являются первоклассными объектами в Java.

Но вы можете аппроксимировать их интерфейсами, что-то вроде объекта Command:

public interface Command {
    void execute(Object [] parameters); 
}

Обновлено 18 марта 2017 года:

Поскольку я впервые написал этот JDK 8, он добавил lambdas. Пакет java.util.function имеет несколько полезных интерфейсов.

Ответ 3

Из всех проверок времени, в Функторы, на Java 8 Lambdas (вроде)

Проблема

Возьмите этот примерный класс, который адаптирует к Применяется в Писатель:

   import  java.io.Closeable;
   import  java.io.Flushable;
   import  java.io.IOException;
   import  java.io.Writer;
   import  java.util.Objects;
/**
   <P>{@code java WriterForAppendableWChecksInFunc}</P>
 **/
public class WriterForAppendableWChecksInFunc extends Writer  {
   private final Appendable apbl;
   public WriterForAppendableWChecksInFunc(Appendable apbl)  {
      if(apbl == null)  {
         throw  new NullPointerException("apbl");
      }
      this.apbl = apbl;
   }

      //Required functions, but not relevant to this post...START
         public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
         public Writer append(char c_c) throws IOException {
         public Writer append(CharSequence text) throws IOException {
         public Writer append(CharSequence text, int i_ndexStart, int i_ndexEndX) throws IOException  {
      //Required functions, but not relevant to this post...END

   public void flush() throws IOException {
      if(apbl instanceof Flushable)  {
         ((Flushable)apbl).flush();
      }
   }
   public void close() throws IOException {
      flush();
      if(apbl instanceof Closeable)  {
         ((Closeable)apbl).close();
      }
   }
}

Не все Appendable являются Flushable или Closeable, но те, которые есть, также должны быть закрыты и сброшены. Поэтому фактический тип объекта Appendable должен быть проверен при каждом вызове flush() и close(), и, когда это действительно так, type, он вызывается и вызывается функция.

По общему признанию, это не самый лучший пример, поскольку close() вызывается только один раз для каждого экземпляра, а flush() необязательно часто называли это. Кроме того, instanceof, хотя и отражает, не так уж плохо, учитывая этот конкретный пример использования. Тем не менее, концепция , которая должна что-то проверять каждый раз, когда вам нужно сделать что-то еще, является реальной и избегает этих "каждый раз"; проверки, когда это действительно имеет значение, дает значительные преимущества.

Переместить все "сверхмощные" проверяет конструктор

Итак, с чего вы начинаете? Как избежать этих проверок без ущерба для вашего кода?

В нашем примере самым простым шагом является переместить все теги instanceof в конструктор.

public class WriterForAppendableWChecksInCnstr extends Writer  {
   private final Appendable apbl;
   private final boolean isFlshbl;
   private final boolean isClsbl;
   public WriterForAppendableWChecksInCnstr(Appendable apbl)  {
      if(apbl == null)  {
         throw  new NullPointerException("apbl");
      }
      this.apbl = apbl;
      isFlshbl = (apbl instanceof Flushable);
      isClsbl = (apbl instanceof Closeable);
   }

         //write and append functions go here...

   public void flush() throws IOException {
      if(isFlshbl)  {
         ((Flushable)apbl).flush();
      }
   }
   public void close() throws IOException {
      flush();
      if(isClsbl)  {
         ((Closeable)apbl).close();
      }
   }
}

Теперь, когда эти "тяжелые" проверки выполняются только один раз, необходимо выполнить только булевские проверки с помощью flush() и close(). Хотя, безусловно, это улучшение, как можно полностью исключить эти проверки функций?

Если бы вы могли каким-то образом определить функцию , которая может быть сохранена классом, а затем используется с помощью flush() и закрыть()...

public class WriterForAppendableWChecksInCnstr extends Writer  {
   private final Appendable apbl;
   private final FlushableFunction flshblFunc;  //If only!
   private final CloseableFunction clsblFunc;   //If only!
   public WriterForAppendableWChecksInCnstr(Appendable apbl)  {
      if(apbl == null)  {
         throw  new NullPointerException("apbl");
      }
      this.apbl = apbl;

      if(apbl instanceof Flushable)  {
         flshblFunc = //The flushable function
      }  else  {
         flshblFunc = //A do-nothing function
      }
      if(apbl instanceof Closeable)  {
         clsblFunc = //The closeable function
      }  else  {
         clsblFunc = //A do-nothing function
      }
   }

          //write and append functions go here...

   public void flush() throws IOException {
      flshblFunc();                             //If only!
   }
   public void close() throws IOException {
      flush();
      clsblFunc();                              //If only!
   }
}

Но передача функций невозможна... по крайней мере, пока Java 8 Lambdas. Итак, как вы это делаете в до 8 версиях Java?

функторы

С Functor. Функтор - это, в основном, Лямбда, но тот, который завернут в объект. Хотя функции не могут передаваться в другие функции в качестве параметров, объекты могут. По сути, Функторы и Лямбда - это способы передать функции.

Итак, как мы можем реализовать Functor в нашем адаптере-адаптере? Мы знаем, что close() и flush() полезны только с объектами Closeable и Flushable. И что некоторые Appendable являются Flushable, некоторые Closeable, некоторые из них, некоторые из них.

Поэтому мы можем сохранить объект Flushable и Closeable в верхней части класса:

public class WriterForAppendable extends Writer  {
   private final Appendable apbl;
   private final Flushable  flshbl;
   private final Closeable  clsbl;
   public WriterForAppendable(Appendable apbl)  {
      if(apbl == null)  {
         throw  new NullPointerException("apbl");
      }

      //Avoids instanceof at every call to flush() and close()

      if(apbl instanceof Flushable)  {
         flshbl = apbl;              //This Appendable *is* a Flushable
      }  else  {
         flshbl = //??????           //But what goes here????
      }

      if(apbl instanceof Closeable)  {
         clsbl = apbl;               //This Appendable *is* a Closeable
      }  else  {
         clsbl = //??????            //And here????
      }

      this.apbl = apbl;
   }

          //write and append functions go here...

   public void flush() throws IOException {
      flshbl.flush();
   }
   public void close() throws IOException {
      flush();
      clsbl.close();
   }
}

"каждый раз" проверки теперь устранены. Но когда Appendable является not a Flushable или not a Closeable, что должно храниться?

Не делайте ничего Функторы

Ничего не делайте...

class CloseableDoesNothing implements Closeable  {
   public void close() throws IOException  {
   }
}
class FlushableDoesNothing implements Flushable  {
   public void flush() throws IOException  {
   }
}

... который может быть реализован как анонимный внутренний класс:

public WriterForAppendable(Appendable apbl)  {
   if(apbl == null)  {
      throw  new NullPointerException("apbl");
   }
   this.apbl = apbl;

   //Avoids instanceof at every call to flush() and close()
   flshbl = ((apbl instanceof Flushable)
      ?  (Flushable)apbl
      :  new Flushable()  {
            public void flush() throws IOException  {
            }
         });
   clsbl = ((apbl instanceof Closeable)
      ?  (Closeable)apbl
      :  new Closeable()  {
            public void close() throws IOException  {
            }
         });
}

//the rest of the class goes here...

}

Чтобы быть наиболее эффективными, эти do-nothing функторы должны быть реализованы как статические конечные объекты. И с этим, вот окончательная версия нашего класса:

package  xbn.z.xmpl.lang.functor;
   import  java.io.Closeable;
   import  java.io.Flushable;
   import  java.io.IOException;
   import  java.io.Writer;
public class WriterForAppendable extends Writer  {
   private final Appendable apbl;
   private final Flushable  flshbl;
   private final Closeable  clsbl;

   //Do-nothing functors
      private static final Flushable FLUSHABLE_DO_NOTHING = new Flushable()  {
         public void flush() throws IOException  {
         }
      };
      private static final Closeable CLOSEABLE_DO_NOTHING = new Closeable()  {
         public void close() throws IOException  {
         }
      };

   public WriterForAppendable(Appendable apbl)  {
      if(apbl == null)  {
         throw  new NullPointerException("apbl");
      }
      this.apbl = apbl;

      //Avoids instanceof at every call to flush() and close()
      flshbl = ((apbl instanceof Flushable)
         ?  (Flushable)apbl
         :  FLUSHABLE_DO_NOTHING);
      clsbl = ((apbl instanceof Closeable)
         ?  (Closeable)apbl
         :  CLOSEABLE_DO_NOTHING);
   }

   public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
      apbl.append(String.valueOf(a_c), i_ndexStart, i_ndexEndX);
   }
   public Writer append(char c_c) throws IOException {
      apbl.append(c_c);
      return  this;
   }
   public Writer append(CharSequence c_q) throws IOException {
      apbl.append(c_q);
      return  this;
   }
   public Writer append(CharSequence c_q, int i_ndexStart, int i_ndexEndX) throws IOException  {
      apbl.append(c_q, i_ndexStart, i_ndexEndX);
      return  this;
   }
   public void flush() throws IOException {
      flshbl.flush();
   }
   public void close() throws IOException {
      flush();
      clsbl.close();
   }
}

Этот конкретный пример исходит от этого вопроса на /fooobar.com/.... Полную рабочую и полностью документированную версию этого примера (включая функцию тестирования) можно найти в нижней части этого вопроса-сообщения (над ответом).

Реализация функторов с перечислением

Оставив пример Writer - Appendable, рассмотрим другой способ реализации функторов: с Enum.

В качестве примера это перечисление имеет функцию move для каждого основного направления:

public enum CardinalDirection  {
   NORTH(new MoveNorth()),
   SOUTH(new MoveSouth()),
   EAST(new MoveEast()),
   WEST(new MoveWest());

   private final MoveInDirection dirFunc;

   CardinalDirection(MoveInDirection dirFunc)  {
      if(dirFunc == null)  {
         throw  new NullPointerException("dirFunc");
      }
      this.dirFunc = dirFunc;
   }
   public void move(int steps)  {
      dirFunc.move(steps);
   }
}

Для его конструктора требуется MoveInDirectionобъект (который является интерфейсом, но может также быть абстрактным классом):

 интерфейс MoveInDirection {  void move (int steps);
}

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

 класс MoveNorth реализует MoveInDirection {  public void move (int steps) {     System.out.println( "Перемещено" + шаги + "шаги на север" );  }
}

Использование этого Functor выполняется с помощью этого простого вызова:

 CardinalDirection.WEST.move(3);

Что в нашем примере выводит это на консоль:

 Перемещено на 3 шага на запад.

И вот полный рабочий пример:

/**
   <P>Demonstrates a Functor implemented as an Enum.</P>

   <P>{@code java EnumFunctorXmpl}</P>
 **/
public class EnumFunctorXmpl  {
   public static final void main(String[] ignored)  {
       CardinalDirection.WEST.move(3);
       CardinalDirection.NORTH.move(2);
       CardinalDirection.EAST.move(15);
   }
}
enum CardinalDirection  {
   NORTH(new MoveNorth()),
   SOUTH(new MoveSouth()),
   EAST(new MoveEast()),
   WEST(new MoveWest());
   private final MoveInDirection dirFunc;
   CardinalDirection(MoveInDirection dirFunc)  {
      if(dirFunc == null)  {
         throw  new NullPointerException("dirFunc");
      }
      this.dirFunc = dirFunc;
   }
   public void move(int steps)  {
      dirFunc.move(steps);
   }
}
interface MoveInDirection  {
   void move(int steps);
}
class MoveNorth implements MoveInDirection  {
   public void move(int steps)  {
      System.out.println("Moved " + steps + " steps north.");
   }
}
class MoveSouth implements MoveInDirection  {
   public void move(int steps)  {
      System.out.println("Moved " + steps + " steps south.");
   }
}
class MoveEast implements MoveInDirection  {
   public void move(int steps)  {
      System.out.println("Moved " + steps + " steps east.");
   }
}
class MoveWest implements MoveInDirection  {
   public void move(int steps)  {
      System.out.println("Moved " + steps + " steps west.");
   }
}

Вывод:

 [C:\java_code] java EnumFunctorXmpl
Перемещено на 3 шага на запад.
Перемещено на 2 ступени на север.
Перемещено на 15 шагов на восток.

Я еще не начал работу с Java 8, поэтому я еще не могу написать раздел Lambdas:)

Ответ 4

Возьмем концепцию применения функций

f.apply(x)

Обратные

x.map(f)

Вызвать x функтор

interface Functor<T> {
    Functor<R> map(Function<T, R> f);
}