¿Por qué existen tan pocos idiomas con un 'operador' de tipo variable?

46

Lo digo de esta manera:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

Pero también de esta manera:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

No parece que ningún idioma tenga esto, ¿hay una buena razón por la que no lo tienen?

kgongonowdoe
fuente
28
En general, lo que desea hacer estaría cubierto por todos los lenguajes que admitan alguna forma de meta programación con algunos tipos de lambdas, cierres o funciones anónimas que serían la forma común de implementar tales características. Con idiomas donde los métodos son ciudadanos de primera clase, podría usarlos más o menos idénticos a las variables. Aunque no está exactamente en esa sintaxis simple que usa aquí, ya que en la mayoría de dichos lenguajes debe quedar claro que realmente desea llamar al método almacenado en la variable.
thorsten müller
77
@MartinMaat Los lenguajes funcionales hacen esto mucho.
Thorbjørn Ravn Andersen
77
En Haskell, los operadores son funciones como cualquier otra función. el tipo de (+)es Num a => a -> a -> aIIRC. también se puede definir funciones para que puedan ser escritas infija ( a + ben lugar de (+) a b)
Sara
55
@enderland: Tu edición cambió por completo el objetivo de la pregunta. Pasó de preguntar si existen idiomas, a preguntar por qué existen tan pocos. Creo que su edición dará como resultado muchos lectores confundidos.
Bryan Oakley
55
@enderland: si bien es cierto, cambiar completamente el tema solo sirve para confundir. Si está fuera de tema, la comunidad votará para cerrarlo. Ninguna de las respuestas (en el momento en que escribo esto) tiene sentido para la pregunta tal como está escrita.
Bryan Oakley

Respuestas:

103

Los operadores son solo funciones con nombres divertidos, con alguna sintaxis especial.

En muchos lenguajes, tan variados como C ++ y Python, puede redefinir operadores anulando métodos especiales de su clase. Luego, los operadores estándar (p +. Ej. ) Funcionan de acuerdo con la lógica que proporciona (p. Ej., Concatenando cadenas o agregando matrices o lo que sea).

Dado que tales funciones que definen el operador son solo métodos, puede pasarlas como lo haría con una función:

# python
action = int.__add__
result = action(3, 5)
assert result == 8

Otros lenguajes le permiten definir directamente nuevos operadores como funciones y usarlos en forma infija.

-- haskell
plus a b = a + b  -- a normal function
3 `plus` 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 `action` 3 == 4 -- True

Desafortunadamente, no estoy seguro de si PHP admite algo así, y si lo admite sería algo bueno. Use una función simple, es más legible que $foo $operator $bar.

9000
fuente
2
@tac: Sí, esto es bueno e incluso puede ser portado a otros idiomas :) Con respecto a Haskell, lo que más me falta es que este departamento es el $operador que evita los paréntesis (especialmente los anidados múltiples), pero esto solo puede funcionar con -variables, excluyendo, por ejemplo, Python y Java. La composición de la función unitaria de OTOH se puede hacer muy bien .
9000
66
Nunca he sido fanático de la sobrecarga de operadores porque sí, un operador es solo una función con sintaxis especial, pero hay un contrato implícito que generalmente va con operadores que no van con funciones. "+", por ejemplo, tiene ciertas expectativas (precedencia del operador, conmutatividad, etc.) y ir en contra de esas expectativas es una ruta segura para confundir a las personas y generar errores. Una razón por la que, aunque me encanta javascript, hubiera preferido que distingan entre + para la adición y la concatenación. Perl lo tenía justo allí.
fool4jesus
44
Tenga en cuenta que Python tiene un operatormódulo estándar que le permitirá escribir action = operator.addy que funcione para cualquier tipo que defina +(no solo int).
dan04
3
En Haskell +trabaja en la clase de tipo Num para que pueda implementar +cualquier tipo de datos nuevo que cree, pero de la misma manera que hace cualquier otra cosa, por ejemplo, fmap para un functor. ¡Haskell es una opción tan natural para permitir la sobrecarga del operador que tendrían que trabajar duro para no permitirlo!
Martin Capodici
17
No estoy seguro de por qué las personas se están obsesionando con la sobrecarga. La pregunta original no se trata de sobrecargar. Me parece que se trata de operadores como valores de primera clase. Para escribir $operator1 = +y luego usar una expresión, ¡no necesita usar la sobrecarga del operador en absoluto !
Andres F.
16

Hay muchos idiomas que permiten algún tipo de metaprogramación . En particular, me sorprende no ver una respuesta sobre la familia de idiomas Lisp .

De wikipedia:

La metaprogramación es la escritura de programas informáticos con la capacidad de tratar los programas como sus datos.

Más adelante en el texto:

Lisp es probablemente el lenguaje por excelencia con facilidades de metaprogramación, tanto por su precedencia histórica como por la simplicidad y el poder de su metaprogramación.

Idiomas Lisp

Sigue una introducción rápida a Lisp.

Una forma de ver el código es como un conjunto de instrucciones: haz esto, luego haz eso, luego haz lo otro ... ¡Esta es una lista! Una lista de cosas que debe hacer el programa. Y, por supuesto, puede tener listas dentro de listas para representar bucles, etc.

Si representamos una lista que contiene los elementos a, b, c, d como esta: (ABCD) obtenemos algo que se parece a una llamada de función Lisp, donde aes la función, y b, c, dson los argumentos. De hecho, el típico "¡Hola Mundo!" programa podría escribirse así:(println "Hello World!")

Por supuesto b, co dpodrían ser listas que evalúen algo también. Lo siguiente: (println "I can add :" (+ 1 3) )luego imprimiría "" Puedo agregar: 4 ".

Entonces, un programa es una serie de listas anidadas, y el primer elemento es una función. ¡La buena noticia es que podemos manipular las listas! Entonces podemos manipular lenguajes de programación.

La ventaja de Lisp

Los Lisps no son tanto lenguajes de programación como un conjunto de herramientas para crear lenguajes de programación. Un lenguaje de programación programable.

Esto no solo es mucho más fácil en Lisps para crear nuevos operadores, sino que también es casi imposible escribir algunos operadores en otros idiomas porque los argumentos se evalúan cuando se pasan a la función.

Por ejemplo, en un lenguaje tipo C, supongamos que desea escribir un ifoperador usted mismo, algo así como:

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

En este caso, ambos argumentos serán evaluados e impresos, en un orden dependiente del orden de la evaluación de los argumentos.

En Lisps, escribir un operador (lo llamamos macro) y escribir una función es casi lo mismo y se usa de la misma manera. La principal diferencia es que los parámetros de una macro no se evalúan antes de pasarlos como argumentos a la macro. Esto es esencial para poder escribir algunos operadores, como el ifanterior.

Idiomas del mundo real

Muestra cómo exactamente está un poco fuera de alcance aquí, pero le animo a que intente programar en un Lisp para obtener más información. Por ejemplo, podrías echar un vistazo a:

  • Scheme , un viejo Lisp bastante "puro" con un núcleo pequeño
  • Common Lisp, un Lisp más grande con un sistema de objetos bien integrado y muchas implementaciones (está estandarizado por ANSI)
  • Racket a Lisp mecanografiado
  • Clojure mi favorito, los ejemplos anteriores fueron el código Clojure. Un Lisp moderno que se ejecuta en la JVM. También hay algunos ejemplos de macros Clojure en SO (pero este no es el lugar correcto para comenzar. Primero miraría 4clojure , braveclojure o clojure koans )).

Ah, y por cierto, Lisp significa LISt Processing.

En cuanto a tus ejemplos

Voy a dar ejemplos usando Clojure a continuación:

Si puede escribir una addfunción en Clojure (defn add [a b] ...your-implementation-here... ), puede nombrarla +así (defn + [a b] ...your-implementation-here... ). De hecho, esto es lo que se hace en la implementación real (el cuerpo de la función está un poco más involucrado pero la definición es esencialmente la misma que escribí anteriormente).

¿Qué pasa con la notación infija? Bueno, Clojure usa una prefixnotación (o polaca), por lo que podríamos hacer una infix-to-prefixmacro que convertiría el código prefijado en código Clojure. ¡Lo que en realidad es sorprendentemente fácil (en realidad es uno de los ejercicios macro en los clojure koans)! También se puede ver en la naturaleza, por ejemplo, ver macro de Incanter$= .

Aquí está la versión más simple de los koans explicados:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

Para llevar el punto aún más lejos, algunas citas de Lisp :

“Parte de lo que distingue a Lisp es que está diseñado para evolucionar. Puede usar Lisp para definir nuevos operadores Lisp. A medida que las nuevas abstracciones se vuelven populares (programación orientada a objetos, por ejemplo), siempre resulta fácil implementarlas en Lisp. Al igual que el ADN, ese lenguaje no pasa de moda ”.

- Paul Graham, ANSI Common Lisp

“Programar en Lisp es como jugar con las fuerzas primordiales del universo. Se siente como un rayo entre las yemas de los dedos. Ningún otro idioma se siente cercano ".

- Glenn Ehrlich, camino a Lisp

nha
fuente
1
Tenga en cuenta que la metaprogramación, si bien es interesante, no es necesaria para admitir lo que pregunta el OP. Cualquier lenguaje con soporte para funciones de primera clase es suficiente.
Andres F.
1
Ejemplo de la pregunta de OP: (let ((opp # '+)) (print (aplicar opp' (1 2))))
Kasper van den Berg el
1
¿Ninguna mención de Common Lisp?
coredump
3
Recuerdo que en un panel de discusión sobre idiomas, Ken estaba hablando de precedencia en APL y concluyó con "¡Casi nunca uso paréntesis!" Y alguien de la audiencia gritó, "¡eso es porque Dick los usó a todos!
JDługosz
2
En un lenguaje de estilo C ++, podría reimplementar if, pero necesitaría envolver los argumentos theny elsecon lambdas. PHP y JavaScript tienen function(), C ++ tiene lambdas, y existe una extensión de Apple para C con lambdas.
Damian Yerrick
9

$test = $number1 $operator1 $number2 $operator2 $number3;

La mayoría de las implementaciones de idiomas tienen un paso en el que un analizador analiza su código y crea un árbol a partir de él. Entonces, por ejemplo, la expresión 5 + 5 * 8se analizaría como

  +
 / \
5   *
   / \
  8   8

gracias al conocimiento del compilador sobre precedencia. Si lo alimentó con variables en lugar de operadores, no conocería el orden correcto de las operaciones antes de ejecutar el código. Para la mayoría de las implementaciones, eso sería un problema grave, por lo que la mayoría de los idiomas no lo permiten.

Por supuesto, podría concebir un lenguaje en el que el analizador analice lo anterior como una secuencia de expresiones y operadores, para clasificar y evaluar en tiempo de ejecución. Presumiblemente no hay mucha aplicación para esto.

Muchos lenguajes de script permiten la evaluación de expresiones arbitrarias (o al menos expresiones aritméticas arbitrarias como en el caso de expr) en tiempo de ejecución. Allí podría combinar sus números y operadores en una sola expresión y dejar que el lenguaje lo evalúe. En PHP (y muchos otros) esa función se llama eval.

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

También hay idiomas que permiten la generación de código en tiempo de compilación. La expresión mixin en D viene a la mente, donde creo que se podría escribir algo así como

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

Aquí operator1y operator2tendrían que ser constantes de cadena que se conocen en tiempo de compilación, por ejemplo, parámetros de plantilla. number1, number2y number3se dejaron como variables de tiempo de ejecución normales.

Otras respuestas ya discutieron las diversas formas en que un operador y una función son más o menos lo mismo, dependiendo del idioma. Pero, por lo general, existe una diferencia sintáctica entre un símbolo de operador infijo incorporado +y un nombre llamado invocable operator1. Dejaré los detalles a esas otras respuestas.

MvG
fuente
+1 Debería comenzar con "es posible en PHP con la eval()construcción del lenguaje " ... Técnicamente proporciona exactamente el resultado deseado en la pregunta.
Armfoot
@Armfoot: Es difícil saber dónde está el foco de la pregunta. El título enfatiza el aspecto de "operador de tipo variable", y evalno responde a ese aspecto, ya que para evallos operadores son solo cadenas. Por lo tanto, comencé con una explicación de por qué las variables de tipo operador causarían problemas, antes de comenzar a discutir alternativas.
MvG
Entiendo sus puntos, pero considere que al meter todo en una cadena, básicamente está insinuando que los tipos de variables ya no son relevantes (ya que PHP se usó para ejemplificar, este parece ser el enfoque de la pregunta), y al final, usted puede colocarlos de la misma manera que si algunas de esas variables fueran de "operador de tipo" mientras obtenían el mismo resultado ... Por eso creo que su sugerencia proporciona la respuesta más precisa a la pregunta.
Armfoot
2

Algol 68 tenía exactamente esa característica. Su ejemplo en Algol 68 se vería así:

int número1 = 5;                              ¢ (Tipo 'Int') ¢
op operador1 = int ( int un , b ) un + b ; ¢ (Escriba 'Operador' inexistente) ¢
prio operator1 = 4;
int numero2 = 5;                              ¢ (tipo 'int') ¢
op operador2 = int ( int un , b ) un * b ;  ¢(Escriba 'Operador' inexistente) ¢
prio operator2 = 5;
int numero3 = 8;                              ¢ (Escriba 'Int') ¢

int prueba = number1 operador1 number2 operador2 number3 ; ¢ 5 + 5 * 8. ¢

var_dump ( prueba );

Su segundo ejemplo se vería así:

int numero4 = 9;
op operator3 = bool ( int un , b ) un < b ; operador
prio3 = 3; if number1 $ operator3 number4 then ¢ 5 <9 (true) ¢ print ( true ) fi


Notará que los símbolos del operador están definidos y asignan cuerpos de métodos que contienen la operación deseada. Todos los operadores y sus operandos están tipificados y se les puede asignar prioridades a los operadores para que la evaluación se realice en el orden correcto. También puede notar que hay una ligera diferencia en la fuente entre el símbolo del operador y un símbolo variable.

En realidad, aunque el lenguaje se escribe usando fuentes, las máquinas de la época no podían manejar las fuentes (cinta de papel y tarjetas perforadas), y se utilizaba la eliminación . El programa probablemente se ingresará como:

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

También puedes jugar juegos interesantes con el lenguaje cuando puedes definir tus propios símbolos para operadores, que exploté una vez, hace muchos años ... [2].


Referencias

[1] Introducción informal a Algol 68 por CHLindsey y SG van der Meulen, Holanda del Norte, 1971 .

[2] Algol 68 Phrases, una herramienta para ayudar a la redacción del compilador en Algol 68 , BC Tompsett, Conferencia Internacional sobre las Aplicaciones de Algol 68, en la Universidad de East Anglia, Norwich, Reino Unido, 1976 ..

Brian Tompsett - 汤 莱恩
fuente