Ayuda con la comprensión de un objeto de función o funtor en Java


¿Puede alguien explicar qué es un funtor y proporcionar un ejemplo sencillo?

Author: jlss4e, 2011-09-10

4 answers

Un objeto de función es solo eso. Algo que es a la vez un objeto y una función.

Aparte: llamar a un objeto de función un "funtor" es un abuso grave del término: un tipo diferente de "funtores" son un concepto central en matemáticas, y uno que tiene un papel directo en ciencias de la computación (ver "Funtores Haskell"). El término también se usa de una manera ligeramente diferente en ML, así que a menos que esté implementando uno de estos conceptos en Java (¡lo cual puede!) por favor, deje de usar esta terminología. Hace que las cosas simples se complicen.

Volver a la respuesta: Java no tiene "funciones de primera clase", es decir, no se puede pasar una función como argumento a una función. Esto es cierto en múltiples niveles, sintácticamente, en la representación de código de bytes, y en que el sistema de tipos carece del "constructor de funciones"

En otras palabras, no puedes escribir algo como esto:

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

Esto es realmente molesto, ya que queremos poder hacer cosas como tener Usuario Gráfico Bibliotecas de interfaz donde puede asociar una función de "devolución de llamada" al hacer clic en un botón.

Entonces, ¿qué hacemos?

Bueno, la solución general (discutida por los otros posters) es definir una interfaz con un único método que podamos llamar. Por ejemplo, Java utiliza una interfaz llamada Runnable para este tipo de cosas todo el tiempo, se ve como:

public interface Runnable{
    public void run();
}

Ahora podemos reescribir mi ejemplo de arriba:

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

Obviamente, este ejemplo es artificial. Podríamos hacer este código un poco más agradable usando clases internas anónimas, pero esto tiene la idea general.

Aquí es donde esto se desglosa: Runnable solo se puede usar para funciones que no toman ningún argumento y no devuelven nada útil, por lo que termina definiendo una nueva interfaz para cada trabajo. Por ejemplo, la interfaz Comparator en la respuesta de Mohammad Faisal. Proporcionar un enfoque más general, y uno que toma sintaxis, es un objetivo importante para Java 8( La próxima versión de Java), y fue fuertemente empujado a ser incluido en Java 7. Esto se llama "lambda" después del mecanismo de abstracción de la función en el Cálculo Lambda. Lambda Calculus es a la vez (quizás) el lenguaje de programación más antiguo, y la base teórica de gran parte de la Ciencia de la Computación. Cuando Alonzo Church (uno de los principales fundadores de la informática) lo inventó, usó la letra griega lambda para las funciones, de ahí el nombre.

Otros lenguajes, incluyendo el lenguaje funcional (Lisp, ML, Haskell, Erlang, etc), la mayoría de los principales los lenguajes dinámicos (Python, Ruby, JavaScript, etc.) y los otros lenguajes de aplicación (C#, Scala, Go, D, etc.) soportan alguna forma de "Lambda Literal"."Incluso C++ los tiene ahora (desde C++11), aunque en ese caso son algo más complicados porque C++ carece de administración automática de memoria y no guardará su marco de pila para usted.

 37
Author: Philip JF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-22 11:36:11

Un funtor es un objeto que es una función.

Java no los tiene, porque las funciones no son objetos de primera clase en Java.

Pero puede aproximarlos con interfaces, algo así como un objeto de comando:

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

Actualizado el 18-Mar-2017:

Desde que escribí por primera vez este JDK 8 ha añadido lambdas. El java.útil.el paquete function tiene varias interfaces útiles.

 12
Author: duffymo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-18 12:02:51

De comprobaciones de cada vez, a Funtores, a Java 8 Lambdas (una especie de)

El problema

Tome esta clase de ejemplo, que adapta un Appendable en un 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();
      }
   }
}

No todos los Appendables son Flushable o Closeable, pero aquellos que lo son, también deben ser cerrados y enjuagados. Por lo tanto, el tipo real del objeto Appendable debe verificarse en cada llamada a flush() y close() y, cuando efectivamente ese tipo, se castea y se llama a la función.

Es cierto que este no es el mejor ejemplo, ya que close() solo se llama una vez por instancia, y flush() tampoco se llama necesariamente a menudo. Además, instanceof, aunque reflexivo, no es tan malo dado este ejemplo particular-uso. Aún así, el concepto de tener que verificar algo cada vez que necesita hacer otra cosa es real, y evitar estas comprobaciones "cada vez", cuando realmente importa, proporciona beneficios significativos.

Mueva todas las comprobaciones "heavy duty" al constructor

Entonces, ¿por dónde empezar? ¿Cómo evitas estas comprobaciones sin comprometer tu código?

En nuestro ejemplo, el paso más fácil es mover todas las comprobaciones instanceof al constructor.

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

Ahora que estas comprobaciones de "servicio pesado" solo se realizan una vez, solo se deben realizar comprobaciones booleanas por flush() y close(). Mientras que sin duda una mejora, cómo ¿pueden eliminarse por completo estas comprobaciones en función?

Si solo pudiera definir de alguna manera una función que podría ser almacenada por la clase y luego utilizada por flush() y 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!
   }
}

Pero no es posible pasar funciones} al menos no hasta Java 8 Lambdas. Entonces, ¿cómo se hace en las versiones pre-8 de Java?

Funtores

Con a Functor . Un Funtor es básicamente un Lambda, pero uno que está envuelto en un objeto. Mientras que las funciones no se pueden pasar a otras funciones como parámetros, los objetos sí pueden. Así que esencialmente, los Funtores y Lambdas son formas de pasar funciones.

Entonces, ¿cómo podemos implementar un Funtor en nuestro writer-adapter? Lo que sabemos es que close() y flush() sólo son útiles con Closeable y Flushable objetos. Y que algunos Appendables son Flushable, algunos Closeable, algunos ninguno, algunos ambos.

Por lo tanto, podemos almacenar un objeto Flushable y Closeable en la parte superior de la clase:

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

Las comprobaciones "cada vez" han sido eliminadas. Pero cuando el Appendable es no a Flushableo no a Closeable, ¿qué se debe almacenar?

No hacer nada Funtores

Un Funtor de no hacer nada...

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

...que se puede implementar como un interno anónimo clase:

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

}

Para ser más eficientes, estos funtores de no hacer nada deben implementarse como objetos finales estáticos. Y con eso, aquí está la versión final de nuestra clase:

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

Este ejemplo en particular viene de esta pregunta en stackoverflow. Una versión totalmente funcional y documentada de este ejemplo (incluida una función de prueba) se puede encontrar en la parte inferior de esa publicación de preguntas (por encima de la respuesta).

Implementando Funtores con una Enumeración

Dejando nuestro Writer-Appendable por ejemplo, echemos un vistazo a otra forma de implementar Funtores: con una enumeración.

Como ejemplo, esta enumeración tiene una función move para cada dirección cardinal:

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

Su constructor requiere un objeto MoveInDirection (que es una interfaz, pero también podría ser una clase abstracta):

interface MoveInDirection  {
   void move(int steps);
}

Hay, naturalmente, cuatro implementaciones concretas de esta interfaz, una por dirección. Aquí hay una implementación trivial para north:

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

El uso de este Funtor se realiza con esta simple llamada:

CardinalDirection.WEST.move(3);

Que, en nuestro ejemplo, muestra esto a la consola:

Moved 3 steps west.

Y aquí hay un ejemplo completo de trabajo:

/**
   <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.");
   }
}

Salida:

[C:\java_code]java EnumFunctorXmpl
Moved 3 steps west.
Moved 2 steps north.
Moved 15 steps east.

Todavía no he empezado con Java 8, así que no puedo escribir las Lambdas sección todavía :)

 6
Author: aliteralmind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-24 20:53:27

Tome el concepto de aplicación de la función

f.apply(x)

Inverso

x.map(f)

Llamar x a funtor

interface Functor<T> {
    Functor<R> map(Function<T, R> f);
}
 1
Author: user2418306,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-27 20:04:12