Saltar al contenido principal

Tipos de datos

JARU es un lenguaje de tipado dinámico: el tipo va con el valor, no con la variable. Una misma variable puede guardar un entero y, más adelante, una cadena, y no hace falta decir de antemano qué tipo tendrá.

Todos los valores son de primera clase: se pueden guardar en variables, pasar a funciones y devolver desde ellas.

TipoDescripciónEjemplo
boolVerdadero o falso.true, false
nilLa ausencia de valor.nil
integerNúmeros enteros de 32 bits.123, 0x1F
floatNúmeros decimales de doble precisión.0.456, 1e3
stringCadenas de texto, entre comillas simples o dobles.'Goku', "Krilin"
listColección ordenada de elementos, que crece y mengua.[1, 2, 3, 4]
mapColección de pares clave-valor.{"clave1": "valor1", "clave2": "valor2"}
arrayColección de tamaño fijo, de una o de varias dimensiones.{1, 2, 3, 4}

Las funciones, las clases, sus instancias y los structs también son valores: los cuentan Funciones, Clases y objetos y Structs.

Bool​

El tipo booleano representa un valor lógico: true (verdadero) o false (falso). También puedes escribir on y off: son los mismos valores (on == true es true).

var esMayorDeEdad = true
var tienePermiso = false

Los booleanos salen de las comparaciones y se usan en las estructuras de control: if, while, for...

var edad = 18
if (edad >= 18) then
print("Es mayor de edad")
else
print("Es menor de edad")
end

En el ejemplo, edad se compara con 18: si es mayor o igual, se imprime "Es mayor de edad"; si no, "Es menor de edad". Qué otros valores cuentan como verdaderos o falsos en un if está en Qué es falso.

Nil​

nil es la ausencia de valor. Lo devuelve una función que termina sin return, y puedes asignarlo para decir que una variable todavía no guarda nada:

var objetivo = nil
if (objetivo == nil) then
println("Sin objetivo")
end

Una variable declarada sin valor no vale nil, sino 0: var x es lo mismo que var x = 0.

nil tiene un solo método:

MétodoQué devuelve
toString()La cadena "nil".

Cualquier otro método da el error ERR_ATTRIBUTE_NOT_FOUND (Nil has no method 'size'.). Pasa a menudo cuando una función devuelve nil y llamas a un método sobre lo que ha devuelto.

Integer​

Los enteros son números sin decimales, de 32 bits y con signo: van de -2147483648 a 2147483647.

var x = 10
var mascara = 0xFF

Un número escrito sin punto decimal ni exponente es un entero. Si no cabe en 32 bits, el compilador lo guarda como decimal: 3000000000 es un decimal. Los hexadecimales, en cambio, se quedan en 32 bits: 0xFFFFFFFF es el entero -1.

Desbordamiento​

Una suma, una resta o una multiplicación de enteros que se sale del rango no da error: da la vuelta y se queda con los 32 bits de abajo.

var grande = 2147483647
println(grande + 1) // -2147483648
println(grande * 2) // -2
var mil = 100000
println(mil * mil) // 1410065408
println(mil * 1.0 * mil) // 10000000000

Si una cuenta puede pasar de dos mil millones, hazla con decimales: basta con que uno de los números lo sea, como en la última línea.

Métodos de los enteros​

Los métodos se llaman sobre una variable, no sobre un número escrito: n.toHex() funciona y (255).toHex() no compila (ver Sintaxis básica).

MétodoQué devuelveCon n = 255
isEven()true si es par.false
isOdd()true si es impar, también con los negativos.true
toBinary([cifras])Una cadena con el número en binario. cifras, de 1 a 32, rellena con ceros por la izquierda."11111111"; con 12, "000011111111"
toHex([cifras])Una cadena con el número en hexadecimal, en mayúsculas y sin 0x. cifras, de 1 a 8, rellena con ceros."FF"; con 4, "00FF"
toBool()false si es 0, y true si no.true
toByte()Los 8 bits de abajo: un entero de 0 a 255.255 (300 da 44 y -1 da 255)
toFloat()El mismo número, como decimal.255 (decimal)
toInt()El mismo entero.255
toString()Una cadena con el número."255"
toUnsigned()Su valor sin signo: un número negativo pasa a decimal, con sus 32 bits leídos sin signo.255 (-1 da 4294967295, decimal)
  • Si el número necesita más cifras de las que pides, salen todas: n.toBinary(4) da "11111111". Pedir más de 32 cifras binarias o de 8 hexadecimales es como pedir el máximo.
  • Con un número negativo, toBinary() y toHex() enseñan sus 32 bits: -1 da 32 unos y "FFFFFFFF".
  • Si cifras no es un entero, el error es ERR_TYPE_MISMATCH; con más argumentos de los que admite cada método, ERR_INVALID_ARG_NUMBER.
var n = 255
println(n.isOdd()) // true
println(n.toHex()) // FF
println(n.toHex(4)) // 00FF
println(n.toBinary(12)) // 000011111111
var negativo = -1
println(negativo.toHex()) // FFFFFFFF
println(negativo.toByte()) // 255
var grande = 300
println(grande.toByte()) // 44

toUnsigned() sirve para un valor de 32 bits con el bit de arriba puesto, como 0xDEADBEEF leído de un sensor: como entero sale negativo, y toUnsigned() da su valor sin signo. No cabe en un entero de JARU, así que sale un decimal, como un número escrito que no cabe en 32 bits:

var leido = 0xDEADBEEF
println(leido) // -559038737
println(leido.toUnsigned()) // 3735928559

Float​

Los decimales son números en coma flotante de doble precisión (64 bits, IEEE 754): guardan unas 15 cifras significativas. Un número escrito con punto decimal o con exponente es un decimal: 3.14, 1.0, 2.5e-3.

var x = 3.14

Cómo se escriben​

println escribe un decimal con las cifras justas para que, al volver a leerlo, sea el mismo número: 0.1 sale 0.1, y 0.1 + 0.2, que no es exactamente 0.3, sale 0.30000000000000004. toString() lo redondea a 15 cifras. Un decimal sin parte fraccionaria sale sin punto: 10.0 se escribe 10, aunque siga siendo un decimal.

Los decimales que escribes en el código no pierden cifras, aunque sean muy pequeños o muy grandes: 1.23456789e-10 y 1e300 llegan tal cual al programa. Solo uno menor que 2.2e-308, sin contar el 0, se queda en 0.

var suma = 0.1 + 0.2
println(suma) // 0.30000000000000004
println(suma.toString()) // 0.3
var diez = 10.0
println(diez, " ", type(diez) == tFloat) // 10 true

Métodos de los decimales​

MétodoQué devuelveEjemplo
toInt()El entero, quitando los decimales: trunca hacia cero.3.75 da 3, y -3.75 da -3
toBool()false si es 0.0, y true si no.0.1 da true
toFloat()El mismo decimal.3.75 da 3.75
toString()Una cadena con hasta 15 cifras significativas.0.1 + 0.2 da "0.3"
  • Fuera del rango de los enteros, toInt(), como int(), se queda con los 32 bits de abajo: 3000000000.0 da -1294967296.
  • Los decimales no tienen isEven(), toHex() ni los demás métodos de los enteros: llamarlos da ERR_ATTRIBUTE_NOT_FOUND (Float has no method 'isEven'.). Pásalos antes a entero con toInt().

NaN e infinito​

Una operación sin resultado numérico, como sqrt(-1), da NaN (not a number). Y un resultado demasiado grande para un decimal da infinito.

  • nan == nan es false, y nan != nan es true. Para saber si un valor es NaN, compáralo consigo mismo: v != v solo es verdadero con NaN.
  • NaN es verdadero en un if.
  • Las listas y los mapas sí encuentran un NaN: una lista que lo tiene da true en contains().
  • Dividir por cero no da infinito ni NaN: lanza ERR_DIVISION_BY_ZERO, también con decimales.
  • Al imprimirlo, el infinito sale como inf. Cómo sale NaN depende de la plataforma: en Windows, -nan(ind).
var raiz = sqrt(-1)
println(raiz == raiz) // false
println(raiz != raiz) // true: es NaN
var grande = 1.0e100
var infinito = grande * grande * grande * grande
println(infinito) // inf

Enteros, decimales y división​

Si en una operación hay un decimal, el resultado es decimal: 1.5 + 2 da 3.5. Entre dos enteros, +, -, * y div dan un entero, y / da siempre un decimal, también cuando la división es exacta: 6 / 3 es el decimal 2.

Para una división entera, usa div: trunca hacia cero, así que 7 div 2 es 3 y -7 div 2 es -3. Úsalo sobre todo cuando el resultado vaya a ser un índice: una lista no admite un índice decimal (ERR_TYPE_MISMATCH, List index must be an integer.), y un decimal no tiene los métodos de los enteros.

println(7 / 2)       // 3.5
println(6 / 3) // 2 (es un decimal)
println(7 div 2) // 3
println(-7 div 2) // -3
var lista = [10, 20, 30, 40]
var medio = lista.size() div 2
println(lista[medio]) // 30

Un entero y un decimal que valen lo mismo son iguales: 1 == 1.0 es true. Para pasar de un tipo a otro tienes int(), float() y round(), en la Librería estándar, y los métodos toInt() y toFloat(). Todos los operadores están en Operadores.

Lo que cambió en la 0.9.7
  • / entre dos enteros se calcula en doble precisión. Antes se hacía en precisión simple: 1 / 3 daba 0.3333333432674408 e int(80 / (40 / 30)) daba 59. Ahora dan 0.3333333333333333 y 60.
  • div entre enteros es exacto en todo el rango de 32 bits; antes fallaba a partir de 16.777.216.
  • NaN ya no es igual a sí mismo, y 0.0 == -0.0 es true.
  • En el ESP32-P4, int() y toInt() con un decimal negativo daban 0. Ahora truncan igual en todas las plataformas.

String​

El tipo string guarda texto: una palabra, una frase o un párrafo. Se escribe entre comillas dobles o comillas simples:

var nombre = "Juan"
var frase = '¡Hola, mundo!'
var parrafo = "Este es un párrafo de ejemplo con varias líneas de texto."

Las cadenas tienen métodos. Por ejemplo, size() da la longitud de una cadena y find() busca un texto dentro de ella:

// Obtener la longitud de una cadena de caracteres
var nombre = "Juan"
println(nombre.size()) // Imprime 4

// Buscar un texto dentro de una cadena
var frase = "Hola, mundo"
println(frase.find("mundo")) // Imprime 6

Todos sus métodos, las comillas, los caracteres de escape y las trampas están en Cadenas.

List​

Una lista guarda una colección de valores, del mismo tipo o de tipos distintos. Se escribe con los valores entre corchetes [ ]:

var nombres = ["Juan", "Pedro", "Miguel"]
var edades = [25, 30, 35]
var mixta = ["Juan", 25, true]

Las listas se pueden cambiar después de crearlas. A cada elemento se llega con su índice entre corchetes, empezando por 0:

print(nombres[0]) // imprime Juan
nombres[1] = "Andrés" // cambia el valor de la posición 1 por "Andrés"

Las listas tienen métodos para las operaciones de siempre. Por ejemplo, append(valor) añade un elemento al final:

var nombres = ["Juan", "Pedro"]
nombres.append("Miguel") // la lista nombres ahora contiene ["Juan", "Pedro", "Miguel"]

Todos los métodos de las listas, foreach y cómo copiarlas están en Listas, mapas y arrays.

Map​

Un mapa guarda pares clave-valor, como un diccionario. Se escribe con los pares entre llaves {}:

var edades = {
"Juan": 25,
"Pedro": 30,
"Miguel": 35
}

También puedes crear un mapa vacío y añadirle los pares después:

var edades = {}
edades["Juan"] = 25
edades["Pedro"] = 30
edades["Miguel"] = 35

A un valor se llega con su clave entre corchetes []:

var edadDeJuan = edades["Juan"] // edadDeJuan contiene 25

Sus métodos (size(), keys(), values(), contains(clave), remove(clave)...) están en Listas, mapas y arrays.

Array​

Un array guarda una secuencia ordenada de elementos, como una lista, pero de tamaño fijo: una vez creado, no crece ni mengua.

Se escribe con los elementos, separados por comas, entre llaves {}:

var numeros = {1, 2, 3, 4, 5}
var nombres = {"Juan", "Pedro", "Miguel"}

También puedes crearlo vacío, con su tamaño, y asignar los valores después. Sus elementos empiezan valiendo 0:

var nombres[10]
nombres[8] = "Esto es una demo"

A un elemento se llega con su índice entre corchetes [], empezando por 0:

var numeros = {1, 2, 3, 4, 5}
var segundoNumero = numeros[1] // segundoNumero contiene 2

Sus métodos (size(), numdim(), dim(), fill(valor)...) están en Listas, mapas y arrays.

Arrays multidimensionales​

Un array puede tener varias dimensiones: var tabla[10][20] crea un array de 10 × 20 elementos. Se recorre con un índice por dimensión, tabla[i][j]. Es un solo array con dos índices, no un array de arrays: size() da el total de elementos (200), y dim(), el tamaño de cada dimensión ([10, 20]).

var array = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

// Acceder a un elemento específico en la matriz
println(array[1][1]) // Imprime 5

// Recorrer los elementos de la matriz
for (var i = 0; i < 3; i++)
for (var j = 0; j < 3; j++)
println(array[i][j])
end
end

Ejemplos de declaraciones de arrays con diferentes números de dimensiones:

// Array unidimensional
var array1D = {1, 2, 3, 4, 5}

// Array bidimensional
var array2D = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

// Array tridimensional
var array3D = {{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}

Qué es falso​

En un if, en un while y con !, and y or, estos valores cuentan como falsos:

  • false y nil;
  • el entero 0 y el decimal 0.0 (también -0.0);
  • la cadena vacía "";
  • una lista vacía [], un mapa vacío {} y un array de tamaño 0.

Todo lo demás es verdadero: "0", "false", " ", [0], NaN, las instancias, los structs, las funciones...

func describe(v)
if (v) then
return "verdadero"
end
return "falso"
end

println(describe(0)) // falso
println(describe("")) // falso
println(describe([])) // falso
println(describe("0")) // verdadero
println(describe([0])) // verdadero

Saber el tipo: type()​

type(valor) devuelve un número que dice de qué tipo es un valor. Compáralo con las constantes de tipo (tInteger, tFloat, tString, tList, tMap...), que están todas en la Librería estándar.

func queEs(v)
if (type(v) == tInteger) then
return "entero"
elsif (type(v) == tFloat) then
return "decimal"
elsif (type(v) == tString) then
return "cadena"
end
return "otra cosa"
end

println(queEs(7)) // entero
println(queEs(7 / 2)) // decimal
println(queEs("7")) // cadena
  • type es una palabra reservada, y siempre va con paréntesis.
  • Un entero y un decimal tienen tipos distintos aunque valgan lo mismo: 1 == 1.0 es true, pero type(1) == type(1.0) es false.
  • Las funciones y las lambdas dan tFunction. Un método sacado de una instancia sin llamarlo (var m = jugador.saluda) da tMethod. Una clase da tClass, y sus instancias, tInstance.
  • Los sprites, los Bytes y otros objetos de los módulos dan tAbstract.
  • Los valores de un enum son enteros: dan tInteger.
  • Para los structs no hay constante: type() da 23 con un struct y 24 con su declaración.
  • Un valor de error, el que devuelven algunas funciones cuando fallan (lo cuenta Errores y excepciones), da tError, como el error de un catch.

Todos los problemas conocidos de JARU, juntos, están en Problemas conocidos.