Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
teaching:primitivas [2008/06/15 20:02] – etanter | teaching:primitivas [2016/06/13 13:54] (current) – etanter | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ~~NOTOC~~ | ||
+ | |||
+ | ====== Como definir un lenguaje con conjunto de primitivas extensible ====== | ||
+ | |||
+ | ===== Pasos preliminares ===== | ||
+ | |||
+ | |||
+ | Consideren el siguiente conjunto de primitivas, para operaciones sobre números y booleanos. | ||
+ | |||
+ | < | ||
+ | ;;; primitives | ||
+ | (define *primitives* | ||
+ | `((+ ,+) | ||
+ | (- ,-) | ||
+ | (* ,*) | ||
+ | (/ ,/) | ||
+ | (= ,=) | ||
+ | (< ,< | ||
+ | (> ,> | ||
+ | (zero? | ||
+ | (not ,not) | ||
+ | (and , | ||
+ | (or ,(lambda args (apply (lambda (x y) (or x y)) args))) | ||
+ | )) | ||
+ | </ | ||
+ | |||
+ | La definición usa operadores especiales de quoting/ | ||
+ | |||
+ | Para entender lo que crea esto, simplemente se puede ver el valor de *primitives*: | ||
+ | |||
+ | < | ||
+ | > *primitives* | ||
+ | (list | ||
+ | (list '+ (lambda args ...)) | ||
+ | (list '- (lambda args ...)) | ||
+ | (list '* (lambda args ...)) | ||
+ | (list '/ (lambda args ...)) | ||
+ | (list '= (lambda args ...)) | ||
+ | (list '< (lambda args ...)) | ||
+ | (list '> (lambda args ...)) | ||
+ | (list 'zero? (lambda args ...)) | ||
+ | (list 'not (lambda args ...)) | ||
+ | (list 'and (lambda args ...)) | ||
+ | (list 'or (lambda args ...))) | ||
+ | </ | ||
+ | |||
+ | O sea, *primitives* es lo que se llama una //lista de asociacion// | ||
+ | |||
+ | La función básica para hacer lookup en este map es assq. assq retorna el par correspondiente. | ||
+ | < | ||
+ | > (assq '+ *primitives*) | ||
+ | (list '+ (lambda args ...)) | ||
+ | </ | ||
+ | |||
+ | Si queremos obtener la lambda asociada, tenemos que extraer el segundo elemento del par: | ||
+ | < | ||
+ | > (cadr (assq '+ *primitives*)) | ||
+ | (lambda args ...) | ||
+ | </ | ||
+ | |||
+ | Fijese que las lambdas estan definidas usando la sintaxis: | ||
+ | < | ||
+ | (lambda args ...) | ||
+ | </ | ||
+ | en vez de: | ||
+ | < | ||
+ | (lambda (args) ...) | ||
+ | </ | ||
+ | La diferencia es que en este último caso (el caso conocido), para llamar la función, hay que dar un solo parametro. En el primer caso, uno puede invocar la función con un numero // | ||
+ | < | ||
+ | > ((lambda args (length args)) 1 2 3) | ||
+ | 3 | ||
+ | > ((lambda (args) (length args)) '(1 2 3)) | ||
+ | 3 | ||
+ | </ | ||
+ | |||
+ | Luego, podemos aplicar esa lambda usando la funcion apply de Scheme. apply toma una lambda y una lista de argumentos y aplica la funcion con estos argumentos. | ||
+ | |||
+ | Ejemplos: | ||
+ | < | ||
+ | > (apply (cadr (assq '+ *primitives*)) '(4 5)) | ||
+ | 9 | ||
+ | > (apply (cadr (assq '+ *primitives*)) '(4 5 6 7)) | ||
+ | 22 | ||
+ | > (apply (cadr (assq '< *primitives*)) '(4 5)) | ||
+ | true | ||
+ | > (apply (cadr (assq 'not *primitives*)) '(#t)) | ||
+ | false | ||
+ | > (apply (cadr (assq 'and *primitives*)) '(#t #t)) | ||
+ | true | ||
+ | </ | ||
+ | |||
+ | //(Detalle: nuestra implementación de and y or no es óptima porque siempre evaluará sus argumentos. Como solucionaría esto?)// | ||
+ | |||
+ | Esto es muy conveniente, | ||
+ | |||
+ | < | ||
+ | ;; primitives for output | ||
+ | (write | ||
+ | (printf | ||
+ | (newline ,(lambda args (apply newline args))) | ||
+ | </ | ||
+ | |||
+ | Ejemplo: | ||
+ | < | ||
+ | > (apply (cadr (assq 'write *primitives*)) ' | ||
+ | " | ||
+ | </ | ||
+ | |||
+ | Es mas, podemos introducir listas en el lenguaje introduciendo las pocas primitivas necesarias para manipular listas: | ||
+ | < | ||
+ | ;; primitives for supporting lists | ||
+ | (car , | ||
+ | (cdr , | ||
+ | (cons ,(lambda args (apply cons args))) | ||
+ | (list ,(lambda args args)) | ||
+ | (null? | ||
+ | </ | ||
+ | |||
+ | Ejemplo: | ||
+ | < | ||
+ | > (apply (cadr (assq 'cdr *primitives*)) '((1 2 3 4))) | ||
+ | (list 2 3 4) | ||
+ | </ | ||
+ | |||
+ | Defina *primitives* en la zona de definiciones de DrScheme y prueba los distintos ejemplos. | ||
+ | |||
+ | ===== Definición del lenguaje ===== | ||
+ | |||
+ | Ahora, como se integra eso en la definición de su lenguaje, y su implementación? | ||
+ | |||
+ | Para que la definición de su lenguaje sea realmente extensible usando ese mecanismo, tiene que definir una expresion para aplicacion de primitiva: | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | El parser tiene que encargarse de ver si el primer símbolo en una lista es una primitiva (usando assq, que retorna false si no encuentra la llave buscada). De ser asi, tiene que crear un nodo prim-app. | ||
+ | |||
+ | La evaluación de un prim-app la hace, obviamente, el interprete. El interprete se encarga de aplicar la primitiva usando apply, exactamente como hemos visto en los ejemplos anteriores. (Claramente, | ||