Función directa - Direct function

Una función directa ( dfn , pronunciado "dee fun") es una forma alternativa de definir una función y un operador (una función de orden superior ) en el lenguaje de programación APL . Un operador directo también puede llamarse dop (pronunciado "dee op"). Fueron inventados por John Scholes en 1996. Son una combinación única de programación de arreglos , función de orden superior y programación funcional , y son un avance distintivo importante de APL de principios del siglo XXI sobre versiones anteriores.

Un dfn es una secuencia de expresiones posiblemente protegidas (o simplemente una protección) entre {y }, separadas por o nuevas líneas, donde denota el argumento izquierdo y el derecho, y denota recursividad (función de autorreferencia). Por ejemplo, la función PTprueba si cada fila de es un triplete pitagórico (probando si la suma de cuadrados es igual al doble del cuadrado del máximo).

   PT {(+/*2)=2×(/)*2}
   PT 3 4 5
1
   x
 4  5  3
 3 11  6
 5 13 12
17 16  8
11 12  4
17 15  8
   PT x
1 0 1 0 0 1

La función factorial como dfn:

   fact {0=⍵:1  × -1}
   fact 5
120
   fact¨ 10    ⍝ fact applied to each element of 0 to 9
1 1 2 6 24 120 720 5040 40320 362880

Descripción

Las reglas para dfns se resumen en la siguiente "tarjeta de referencia":

{ function } {⍺⍺ operator ⍵⍵} :   Guardia
  argumento de la izquierda ⍺⍺  operando izquierdo ::  guardia de error
  argumento correcto ⍵⍵  operando derecho   argumento izquierdo predeterminado 
  autorreferencia   ∇∇  autorreferencia   s  resultado tímido

Un dfn es una secuencia de expresiones posiblemente protegidas (o simplemente una protección) entre {y }, separadas por o nuevas líneas.

expression
guard: expression
guard:

Las expresiones y / o guardas se evalúan en secuencia. Un guardia debe evaluar a 0 o 1; su expresión asociada se evalúa si el valor es 1. Un dfn termina después de la primera expresión no protegida que no termina en asignación , o después de la primera expresión protegida cuya protección se evalúa como 1, o si no hay más expresiones. El resultado de un dfn es el de la última expresión evaluada. Si la última expresión evaluada termina en una asignación, el resultado es "tímido", no se muestra automáticamente en la sesión.

Los nombres asignados en un dfn son locales por defecto, con alcance léxico .

denota el argumento de la función izquierda y la derecha; ⍺⍺denota el operando izquierdo y ⍵⍵el derecho. Si ⍵⍵aparece en la definición, entonces dfn es un operador diádico ; si solo ⍺⍺ocurre pero no ⍵⍵, entonces es un operador monádico; si no ocurre nada ⍺⍺o ⍵⍵, entonces dfn es una función.

La sintaxis especial se utiliza para dar un valor predeterminado al argumento de la izquierda si un dfn se llama monádicamente, es decir, se llama sin argumento de la izquierda. No se evalúa de otra manera. expressionexpression

denota recursividad o autorreferencia por la función y ∇∇denota autorreferencia por parte del operador. Tal denotación permite la recursividad anónima .

La captura de errores se proporciona a través de error guardias, . Cuando se genera un error, el sistema busca dinámicamente a través de las funciones de llamada una protección contra errores que coincida con el error. Si se encuentra uno, el entorno de ejecución se desenrolla a su estado inmediatamente anterior a la ejecución del error-guard y la expresión asociada del error-guard se evalúa como resultado del dfn. errnums::expression

En los artículos citados se encuentran disponibles descripciones, explicaciones y tutoriales adicionales sobre dfns.

Ejemplos de

Los ejemplos aquí ilustran diferentes aspectos de dfns. Se encuentran ejemplos adicionales en los artículos citados.

Argumento izquierdo predeterminado

La función se suma a ( i o −1 ) veces . {+0j1×}0j1

   3 {+0j1×} 4
3J4
   ∘.{+0j1×} ¯2+⍳5
¯2J¯2 ¯2J¯1 ¯2 ¯2J1 ¯2J2
¯1J¯2 ¯1J¯1 ¯1 ¯1J1 ¯1J2
 0J¯2  0J¯1  0  0J1  0J2
 1J¯2  1J¯1  1  1J1  1J2
 2J¯2  2J¯1  2  2J1  2J2

El significado de esta función se puede ver de la siguiente manera:

Los números complejos se pueden construir como pares ordenados de números reales, de manera similar a cómo los números enteros se pueden construir como pares ordenados de números naturales y los números racionales como pares ordenados de números enteros. Para números complejos, juega el mismo papel que para números enteros y racionales.{+0j1×}-÷

Además, de forma análoga a ⇔ monádico ( negar ) y ⇔ monádico ( recíproco ), es útil una definición monádica de la función, que se efectúa especificando un valor predeterminado de 0 para : si , entonces ⇔ ⇔ . -0-÷1÷j{0 +0j1×}j 0 j 0+0j1×

   j{0  +0j1×}

   3 j 4 ¯5.6 7.89
3J4 3J¯5.6 3J7.89

   j 4 ¯5.6 7.89
0J4 0J¯5.6 0J7.89

   sin 1
   cos 2
   Euler {(*j ) = (cos ) j (sin )}

   Euler (¯0.5+?100) j (¯0.5+?100)
1 1 1 1 1 1 1 1 1 1

La última expresión ilustra la fórmula de Euler en diez números aleatorios con partes reales e imaginarias en el intervalo .

Recursividad única

La construcción ternaria del conjunto de Cantor comienza con el intervalo [0,1] y en cada etapa elimina el tercio medio de cada subintervalo restante:

El conjunto de orden de Cantor definido como dfn:

   Cantor {0=⍵:,1  ,1 0 1 ∘.  -1}

   Cantor 0
1
   Cantor 1
1 0 1
   Cantor 2
1 0 1 0 0 0 1 0 1
   Cantor 3
1 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1

Cantor 0 a Cantor 6 representados como barras negras:

Cantor establecido en siete iteraciones.svg

La función calcula un vector de bits de longitud para que el bit (para y ) sea 1 si y solo si es primo . sieve i0ii<i

sieve{
  4⍵:⍵0 0 1 1
  r0.5*n
  p2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p(1+(n≤×p)1)p
  b 0@1  {(m)>m1  mn×≢} 1,p
  {r<qb1:bb[]1  b[q,q×⍸bn÷q]0   ,q}p
}

   10 10  sieve 100
0 0 1 1 0 1 0 1 0 0
0 1 0 1 0 0 0 1 0 1
0 0 0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 0 0 1
0 0 0 1 0 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0

   bsieve 1e9
   b
1000000000
   (10*⍳10) (+)0 1 b
0 4 25 168 1229 9592 78498 664579 5761455 50847534

La última secuencia, el número de primos menores que potencias de 10, es un segmento inicial de OEISA006880 . El último número, 50847534, es el número de primos menores que . Se llama número de Bertelsen, descrito memorablemente por MathWorld como "un nombre erróneo al que se le ha dado erróneamente el valor erróneo de ".

sieveusa dos métodos diferentes para marcar compuestos con 0, ambos efectuados usando dfns locales anónimos: el primero usa el tamiz de Eratóstenes en una máscara inicial de 1 y un prefijo de los primos 2 3 ... 43, usando el operador de inserción ( pliegue derecho ). (La longitud del prefijo se obtiene en comparación con la función primorial ). El segundo encuentra el número primo nuevo más pequeño que queda en ( ) y establece en 0 el bit y bits a veces los números en los 1 bits restantes en un segmento inicial de ( ) . Este segundo dfn usa la recursividad de cola. ×pqbqb1qqbbn÷q

Recursión de cola

Por lo general, la función factorial se define de forma recursiva (como arriba ), pero se puede codificar para explotar la recursividad de la cola mediante el uso de un argumento de la izquierda del acumulador:

fac{1  =0:⍺  (×)  -1}

De manera similar, el determinante de una matriz compleja cuadrada usando eliminación gaussiana se puede calcular con recursividad de cola:

det{                ⍝ determinant of a square complex matrix
  1                ⍝ product of co-factor coefficients so far
  0=≢⍵:⍺             ⍝ result for 0-by-0
  (i j)()⊤⊃⍒|,   ⍝ row and column index of the maximal element
  k⍳≢
  (×[i;j]ׯ1*i+j)  [k~i;k~j] - [k~i;j] ∘.× [i;k~j]÷[i;j]
}

Recursividad múltiple

Una partición de un número entero no negativo es un vector de números enteros positivos tal que , donde el orden en no es significativo. Por ejemplo, y son particiones de 4, y y y se consideran la misma partición. n = +v2 22 1 12 1 11 2 11 1 2

La función de partición cuenta el número de particiones. La función es de interés en la teoría de números , estudiada por Euler , Hardy , Ramanujan , Erdős y otros. La relación de recurrencia

derivado del teorema del número pentagonal de Euler . Escrito como dfn:

   pn   {1⍵:0  -+¨rec }
   rec  { - (÷2 (×1) ¯1 1 ∘.+ 3×) 1+⍳⌈0.5*×2÷3}

   pn 10
42
   pn¨ 13    ⍝ OEIS A000041
1 1 2 3 5 7 11 15 22 30 42 56 77

El paso básico establece que para , el resultado de la función es 1 si ⍵ es 0 o 1 y 0 en caso contrario. El paso recursivo es muy recursivo. Por ejemplo, daría como resultado que la función se aplique a cada elemento de , que son: 1⍵:010pn 200rec 200

   rec 200
199 195 188 178 165 149 130 108 83 55 24 ¯10
198 193 185 174 160 143 123 100 74 45 13 ¯22

y requiere más tiempo que la edad del universo para calcular (la función se llama a sí misma). El tiempo de cálculo se puede reducir mediante la memorización , aquí implementada como el operador directo (función de orden superior) : pn 200M

M{
  f⍺⍺
  i2+'⋄'t2↓,⎕cr 'f'
  '{T←(1+⍵)⍴¯1 ⋄ ',(it),'¯1≢T[⍵]:⊃T[⍵] ⋄ ⊃T[⍵]←⊂',(it),'⍵}⍵'
}

   pn M 200
3.973E12
   0  pn M 200  ⍝ format to 0 decimal places
 3972999029388

Este valor de concuerda con el calculado por Hardy y Ramanujan en 1918. pn M 200

El operador de memo Mdefine una variante de su función de operando ⍺⍺para usar un caché T y luego lo evalúa. Con el operando pnla variante es:

{T(1+)¯1  {1⍵:0  ¯1T[]:T[]  T[]⊂-+¨rec }}

Operador directo (dop)

La ordenación rápida en una matriz funciona eligiendo un "pivote" al azar entre sus celdas principales, luego clasificando las celdas principales ordenadas que preceden estrictamente al pivote, las celdas principales iguales al pivote y las celdas principales ordenadas que siguen estrictamente el pivote, como determinado por una función de comparación ⍺⍺. Definido como operador directo (dop) Q:

   Q{1≥≢⍵:⍵  ( ⌿⍨0>s)(⌿⍨0=s) ⌿⍨0<s ⍺⍺ ?≢}

   ⍝ precedes            ⍝ follows            ⍝ equals
   2 (×-) 8              8 (×-) 2             8 (×-) 8
¯1                    1                    0

   x 2 19 3 8 3 6 9 4 19 7 0 10 15 14

   (×-) Q x
0 2 3 3 4 6 7 8 9 10 14 15 19 19

Q3es una variante que cataloga las tres partes encerradas por la función en lugar de las partes per se . Las tres partes generadas en cada paso recursivo son evidentes en la estructura del resultado final. La aplicación de la función derivada del Q3mismo argumento varias veces da resultados diferentes porque los pivotes se eligen al azar. El recorrido en orden de los resultados produce la misma matriz ordenada.

   Q3{1≥≢⍵:⍵  ( ⌿⍨0>s)(⌿⍨0=s)⍪⊂ ⌿⍨0<s ⍺⍺ ?≢}

   (×-) Q3 x
┌────────────────────────────────────────────┬─────┬┐
│┌──────────────┬─┬─────────────────────────┐│19 19││
││┌──────┬───┬─┐│6│┌──────┬─┬──────────────┐││     ││
│││┌┬─┬─┐│3 34││ ││┌┬─┬─┐│9│┌┬──┬────────┐│││     ││
│││││02││    ││ ││││78││ │││10│┌──┬──┬┐││││     ││
│││└┴─┴─┘│    ││ ││└┴─┴─┘│ │││  ││1415││││││     ││
││└──────┴───┴─┘│ ││       │││  │└──┴──┴┘││││     ││
││               ││       │└┴──┴────────┘│││     ││
││               │└──────┴─┴──────────────┘││     ││
│└──────────────┴─┴─────────────────────────┘│     ││
└────────────────────────────────────────────┴─────┴┘
   (×-) Q3 x
┌───────────────────────────┬─┬─────────────────────────────┐
│┌┬─┬──────────────────────┐│7│┌────────────────────┬─────┬┐│
│││0│┌┬─┬─────────────────┐││ ││┌──────┬──┬────────┐│19 19│││
│││ │││2│┌────────────┬─┬┐│││ │││┌┬─┬─┐│10│┌──┬──┬┐││     │││
│││ │││ ││┌───────┬─┬┐│6│││││ │││││89││  ││1415││││     │││
│││ │││ │││┌┬───┬┐│4│││ │││││ │││└┴─┴─┘│  │└──┴──┴┘││     │││
│││ │││ │││││3 3│││ │││ │││││ ││└──────┴──┴────────┘│     │││
│││ │││ │││└┴───┴┘│ │││ │││││ │└────────────────────┴─────┴┘│
│││ │││ ││└───────┴─┴┘│ │││││                              
│││ │││ │└────────────┴─┴┘│││                              
│││ │└┴─┴─────────────────┘││                              
│└┴─┴──────────────────────┘│                              
└───────────────────────────┴─┴─────────────────────────────┘

La formulación anterior no es nueva; véase, por ejemplo, la Figura 3.7 del clásico Diseño y análisis de algoritmos informáticos . Sin embargo, a diferencia del programa pidgin ALGOL de la Figura 3.7, Qes ejecutable y el orden parcial utilizado en la clasificación es un operando, los ejemplos anteriores. (×-)

Dfns con operadores y trenes

Los dfns, especialmente los dfns anónimos, funcionan bien con operadores y trenes. El siguiente fragmento resuelve un rompecabezas de "Perlas de programación": dado un diccionario de palabras en inglés, aquí representadas como la matriz de caracteres a, busque todos los conjuntos de anagramas.

   a            {[]}1 a        ({[]}1 {} ) a
pats         apst                ┌────┬────┬────┐
spat         apst                patsteasstar
teas         aest                spatsate    
sate         aest                tapsetas    
taps         apst                pastseat    
etas         aest                    eats    
past         apst                    tase    
seat         aest                    east    
eats         aest                    seta    
tase         aest                └────┴────┴────┘
star         arst
east         aest
seta         aest

El algoritmo funciona ordenando las filas individualmente ( ), y estas filas ordenadas se utilizan como claves ("firma" en la descripción de Programming Pearls) para que el operador clave agrupe las filas de la matriz. La expresión de la derecha es un tren , una forma sintáctica empleada por APL para lograr una programación tácita . Aquí, es una secuencia aislada de tres funciones tales que ⇔ , de donde la expresión de la derecha es equivalente a . {[]}1 a(f g h) (f ) g (h )({[]}1 a) {} a

Alcance léxico

Cuando un dfn interno (anidado) se refiere a un nombre, se busca mirando hacia afuera a través de dfns adjuntos en lugar de hacerlo hacia abajo en la pila de llamadas . Se dice que este régimen emplea un alcance léxico en lugar del alcance dinámico habitual de APL . La distinción se hace evidente solo si se realiza una llamada a una función definida en un nivel externo. Para las llamadas internas más habituales, los dos regímenes son indistinguibles.

Por ejemplo, en la siguiente función which, la variable tyse define tanto en whichsí misma como en la función interna f1. Cuando f1llama hacia afuera f2y f2hace referencia a ty, encuentra el externo (con valor 'lexical') en lugar del definido en f1(con valor 'dynamic'):

which{
  ty'lexical'
  f1{ty'dynamic'  f2 }
  f2{ty,}
  f1 
}

   which ' scope'
lexical scope

Guardia de error

La siguiente función ilustra el uso de protecciones contra errores:

plus{
  tx'catch all'   0::tx
  tx'domain'     11::tx
  tx'length'      5::tx
  +
}      
   2 plus 3              ⍝ no errors
5
   2 3 4 5 plus 'three'  ⍝ argument lengths don't match
length
   2 3 4 5 plus 'four'   ⍝ can't add characters
domain
   2 3 plus 3 45        ⍝ can't add vector to matrix
catch all

En APL, el error número 5 es "error de longitud"; el número de error 11 es "error de dominio"; y el número de error 0 es un "catch all" para los números de error del 1 al 999.

El ejemplo muestra el desenrollado del entorno local antes de que se evalúe la expresión de un error-guard. El nombre local txse establece para describir el ámbito de su siguiente protección contra errores. Cuando ocurre un error, el entorno se desenrolla para exponer txel valor estáticamente correcto.

Dfns versus tradfns

Dado que las funciones directas son dfns, las funciones APL definidas de la manera tradicional se denominan tradfns, que se pronuncian "trad funs". Aquí, dfns y tradfns se comparan considerando la función sieve: A la izquierda hay un dfn (como se define arriba ); en el medio hay un tradfn que utiliza estructuras de control ; a la derecha hay un tradfn usando gotos ( ) y etiquetas de línea .

sieve←{
  4≥⍵:⍵⍴0 0 1 1
  r←⌊0.5*⍨n←⍵
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×⍀p)⍳1)↑p
  b← 0@1 ⊃ {(m⍴⍵)>m⍴⍺↑1 ⊣ m←n⌊⍺×≢⍵}⌿ ⊖1,p
  {r<q←b⍳1:b⊣b[⍵]←1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ ∇ ⍵,q}p
}

∇ b←sieve1 n;i;m;p;q;r
  :If 4≥n ⋄ b←n⍴0 0 1 1 ⋄ :Return ⋄ :EndIf
  r←⌊0.5*⍨n
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×⍀p)⍳1)↑p
  b←1
  :For q :In p ⋄ b←(m⍴b)>m⍴q↑1 ⊣ m←n⌊q×≢b ⋄ :EndFor
  b[1]←0
  :While r≥q←b⍳1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ p⍪←q ⋄ :EndWhile
  b[p]←1
∇

∇ b←sieve2 n;i;m;p;q;r
  →L10 ⍴⍨ 4<n ⋄ b←n⍴0 0 1 1 ⋄ →0
 L10:
  r←⌊0.5*⍨n
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×\p)⍳1)↑p
  i←0 ⋄ b←1
 L20:
  b←(m⍴b)>m⍴p[i]↑1 ⊣ m←n⌊p[i]×≢b
  →L20 ⍴⍨ (≢p)>i←1+i
  b[1]←0
 L30:
  →L40 ⍴⍨ r<q←b⍳1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ p⍪←q ⋄ →L30
 L40:
  b[p]←1
∇
  • Un dfn puede ser anónimo ; se debe nombrar un tradfn.
  • Un dfn se nombra por asignación ( ); un tradfn se nombra incrustando el nombre en la representación de la función y aplicando ⎕fx(una función del sistema) a esa representación.
  • Un dfn es más útil que un tradfn como operando (consulte los elementos anteriores: se debe nombrar un tradfn; un tradfn se nombra incrustando ...).
  • Los nombres asignados en un dfn son locales por defecto; los nombres asignados en un tradfn son globales a menos que se especifiquen en una lista de locales.
  • Los locales en un dfn tienen alcance léxico ; Los locales en un tradfn tienen un alcance dinámico , visible en funciones llamadas a menos que estén sombreados por su lista de locales.
  • Los argumentos de un dfn se nombran y y los operandos de un dop se nombran ⍺⍺y ⍵⍵; los argumentos y operandos de un tradfn pueden tener cualquier nombre, especificado en su línea principal.
  • El resultado (si lo hay) de un dfn no tiene nombre; el resultado (si lo hay) de un tradfn se nombra en su encabezado.
  • Un valor predeterminado para ⍺ se especifica más claramente que para el argumento izquierdo de un tradfn.
  • La recursividad en un dfn se efectúa invocando o ∇∇o su nombre; la recursividad en un tradfn se efectúa invocando su nombre.
  • El control de flujo en un dfn se efectúa mediante guardias y llamadas a funciones; que en un tradfn es por estructuras de control y (goto) y etiquetas de línea.
  • La evaluación de una expresión en un dfn que no termina en una asignación provoca el retorno del dfn; la evaluación de una línea en un tradfn que no termina en asignación o goto muestra el resultado de la línea.
  • Un dfn devuelve al evaluar una expresión que no termina en una asignación, al evaluar una expresión protegida o después de la última expresión; un tradfn devuelve (ir a) la línea 0 o una línea no existente, o al evaluar una estructura de control, o después de la última línea.:Return
  • El control de flujo más simple en un dfn hace que sea más fácil detectar e implementar la recursividad de cola que en un tradfn.
  • Un dfn puede llamar a un tradfn y viceversa ; un dfn puede definirse en un tradfn y viceversa .

Historia

Kenneth E. Iverson , el inventor de APL, no estaba satisfecho con la forma en que se definían las funciones de usuario (tradfns). En 1974, ideó una "definición de función formal" o "definición directa" para su uso en la exposición. Una definición directa tiene dos o cuatro partes, separadas por dos puntos:

name : expression
name : expression0 : proposition : expression1

Dentro de una definición directa, denota el argumento de la izquierda y el argumento de la derecha. En primera instancia, el resultado de expressiones el resultado de la función; en el segundo caso, el resultado de la función es expression0si se propositionevalúa como 0 o expression1si se evalúa como 1. Las asignaciones dentro de una definición directa son dinámicamente locales . Se encuentran ejemplos del uso de la definición directa en la Conferencia del Premio Turing de 1979 y en libros y documentos de solicitud.

La definición directa era demasiado limitada para su uso en sistemas más grandes. Las ideas fueron desarrolladas por múltiples autores en múltiples trabajos, pero los resultados fueron difíciles de manejar. De estos, la "definición alternativa de la función APL" de Bunda en 1987 se acercó más a las instalaciones actuales, pero tiene fallas en los conflictos con los símbolos existentes y en el manejo de errores que habrían causado dificultades prácticas, y nunca se implementó. Los principales destilados de las diferentes propuestas fueron que (a) la función que se está definiendo es anónima, con posterior denominación (si es necesario) efectuada por asignación; (b) la función se denota mediante un símbolo y, por lo tanto, permite la recursividad anónima .

En 1996, John Scholes de Dyalog Limited inventó las funciones directas (dfns). Las ideas se originaron en 1989 cuando leyó un número especial de The Computer Journal sobre programación funcional. Luego procedió a estudiar programación funcional y se sintió fuertemente motivado ("enfermo de deseo", como Yeats ) para llevar estas ideas a APL. Inicialmente actuó con sigilo porque le preocupaba que los cambios pudieran considerarse demasiado radicales y una complicación innecesaria del lenguaje; otros observadores dicen que operaba con sigilo porque los colegas de Dyalog no estaban tan enamorados y pensaban que estaba perdiendo el tiempo y causando problemas a la gente. Los Dfns se presentaron por primera vez en el Foro de proveedores Dyalog en la Conferencia APL '96 y se publicaron en Dyalog APL a principios de 1997. La aceptación y el reconocimiento tardaron en llegar. Todavía en 2008, en Dyalog at 25 , una publicación que celebraba el 25 aniversario de Dyalog Limited, apenas se mencionaban los dfns (mencionados dos veces como "funciones dinámicas" y sin elaboración). A partir de 2019, los dfns se implementan en Dyalog APL, NARS2000 y ngn / apl. También juegan un papel clave en los esfuerzos por explotar las capacidades informáticas de una unidad de procesamiento de gráficos (GPU).

Referencias

enlaces externos