Помогите с пониманием функционального объекта или функтора в Java - PullRequest
23 голосов
/ 10 сентября 2011

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

Ответы [ 4 ]

38 голосов
/ 10 сентября 2011

Функциональный объект - только это.Нечто, являющееся одновременно и объектом, и функцией.

В сторону: вызов объекта функции «функтором» является серьезным злоупотреблением этим термином: другой вид «функторов» является центральным понятием в математике, и одинэто имеет непосредственное значение в информатике (см. «Функторы Хаскеля»).Этот термин также используется немного по-другому в 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. Это называется «лямбда» после механизма абстракции функций.в лямбда-исчислении.Лямбда-исчисление является (возможно) старейшим языком программирования и теоретической основой большей части компьютерных наук.Когда Алонзо Черч (один из главных основателей информатики) изобрел его, он использовал греческую букву лямбда для функций, отсюда и название.

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

14 голосов
/ 10 сентября 2011

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

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

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

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

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

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

6 голосов
/ 04 марта 2014

От проверок каждый раз, до Функторов, до Java 8 Лямбд (вроде)

Задача

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

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

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

По общему признанию, это не самый лучший пример, поскольку 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() и close() ...

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 .Так как же это сделать в версиях Java до 8?

Функторы

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

Так как же мы можем внедрить 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 это не a Flushable или не a Closeable, что должно быть сохранено?

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

A ничего не делать Функтор ...

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

}

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

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

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

Реализация функторов с помощью Enum

Оставляя наш пример 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 (который является интерфейсом, но также может быть абстрактным классом):

interface MoveInDirection  {
   void move(int steps);
}

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

class MoveNorth implements MoveInDirection  {
   public void move(int steps)  {
      System.out.println("Moved " + steps + " steps north.");
   }
}

Использование этого Функтора завершается простым вызовом:

CardinalDirection.WEST.move(3);

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

Moved 3 steps west.

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

/**
   <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
Moved 3 steps west.
Moved 2 steps north.
Moved 15 steps east.

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

1 голос
/ 27 января 2016

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

f.apply(x)

Inverse

x.map(f)

Звоните x функтор

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