Um tuplo é uma lista finita e ordenada de elementos, e é imutável. Os tuplos exigem que todas as posições tenham um tipo fixo. Isto significa, por sua vez, que o compilador sabe que tipo está em cada posição. Os tipos usados num tuplo podem ser diferentes em cada posição, mas têm de ser conhecidos em tempo de compilação.
Dependendo de se os tipos dos valores do tuplo podem ser interpretados em tempo de compilação, o tuplo pode ser criado de formas diferentes. Se os valores forem conhecidos em tempo de compilação, o tuplo pode ser criado com a sintaxe de literal de tuplo; caso contrário, têm de ser declarados explicitamente. Também é importante que os tipos dos valores correspondam aos tipos especificados no tuplo e que o número de valores corresponda ao número de tipos indicados. Eis um exemplo de definição através da sintaxe de literal de tuplo:
tuple = {1, "foo", 'c'} # Tuple(Int32, String, Char)
Também é possível criar um tuplo com a classe Tuple.
tuple = Tuple(Int32, String, Char).new(1, "foo", 'c')
Em alternativa, podes especificar explicitamente o tipo da variável atribuída ao tuplo.
tuple : Tuple(Int32, String, Char) = {1, "foo", 'c'}
Especificar explicitamente o tipo do tuplo pode ser útil, pois permite definir que uma posição deve conter um tipo união. Isto significa que uma posição pode conter vários tipos.
tuple : Tuple(Int32 | String, String, Char) = {1, "foo", 'c'}
Podes criar um tuplo a partir de um array com o método from da classe Tuple.
Isto exige que o tipo do tuplo seja especificado.
array = [1, "foo", 'c']
tuple = Tuple(Int32, String, Char).from(array)
Podes converter um tuplo num array com o método to_a.
O tipo dos elementos do array resultante é a união dos tipos de cada campo do tuplo.
tuple = {1, "foo", 'c'}
array = tuple.to_a
array # => [1, "foo", 'c']
Tal como os arrays, os tuplos são indexados a partir de zero, ou seja, o primeiro elemento está no índice 0.
No entanto, ao contrário dos arrays, o tipo de cada elemento é fixo e conhecido em tempo de compilação; por isso, ao indexar um tuplo, o tipo do elemento é específico da posição.
Para aceder a um elemento de um tuplo, podes usar o operador [].
array = [1, "foo", 'c']
array[0] # => 1
typeof(array[0]) # => Int32 | String | Char
tuple = {1, "foo", 'c'}
tuple[0] # => 1
typeof(tuple[0]) # => Int32
Outra diferença em relação ao acesso a elementos de arrays é que, se o índice for especificado, o compilador verifica se o índice está dentro dos limites do tuplo. Isto significa que recebes um erro em tempo de compilação em vez de um erro em tempo de execução.
tuple = {1, "foo", 'c'}
tuple[3]
# => Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)
No entanto, se o índice estiver guardado numa variável, o compilador não consegue verificar se o índice está dentro dos limites do tuplo em tempo de compilação e dá, em vez disso, um erro em tempo de execução.
Podes obter um subtuplo de um tuplo usando o operador [] com um intervalo.
O que é devolvido é um novo tuplo com os elementos do intervalo indicado.
O intervalo tem de ser indicado em tempo de compilação; caso contrário, o compilador não consegue saber os tipos dos elementos do subtuplo.
Isto significa que o intervalo tem de ser um literal de intervalo e não pode estar atribuído a uma variável.
tuple = {1, "foo", 'c'}
subtuple = tuple[0..1] # Tuple(Int32, String)
i = 0..1
tuple[i]
# Error: Tuple#[](Range) can only be called with range literals known at compile-time
Os tuplos são úteis quando queres agrupar um número fixo de valores cujos tipos são conhecidos em tempo de compilação. Isto porque os tuplos ocupam menos memória e são mais rápidos do que os arrays, graças à sua imutabilidade. Outro caso de uso é devolver vários valores a partir de um método. Isto é particularmente útil se os valores tiverem tipos diferentes, uma vez que cada posição do tuplo pode ter um tipo diferente.
Não deves usar tuplos quando precisas de uma estrutura de dados que possa crescer ou encolher de tamanho ou que tenha de ser modificada com frequência.
Os símbolos são identificadores com nome que podem ser usados para referir um valor.
Os símbolos são criados através de um literal de símbolo, que consiste em colocar o caráter : antes de um nome, por exemplo, :foo.
Também podem ser escritos com aspas, por exemplo, :"foo", o que permite, por exemplo, espaços no nome.
:foo # => :foo
:"foo boo" # => :"foo boo"
:foo == :"foo" # => true
Os símbolos são usados em muitos sítios da linguagem, incluindo como chaves em namedtuples e para representar nomes de métodos e variáveis.
Os símbolos em Crystal são bastante diferentes dos de Ruby. Em Crystal, um símbolo é um tipo de constante e, por isso, é atribuído em tempo de compilação. Isto significa que os símbolos não podem ser criados dinamicamente, algo que é possível em Ruby.
Em Crystal, os símbolos são representados como Int32s, o que os torna muito eficientes.
Os símbolos podem ser convertidos em strings, mas não o contrário. Isto porque os símbolos são criados em tempo de compilação e as strings são criadas em tempo de execução.
:foo.to_s # => "foo"
Enquanto te preparavas para fazer bolachas para os teus amigos, descobriste que tens de converter algumas das medidas usadas na receita. Como só conheces o sistema métrico, precisas de arranjar uma forma de converter as medidas de culinária mais comuns dos Estados Unidos em mililitros (mL), para te facilitar a vida.
Usa esta tabela de conversão na tua solução:
| Unidade a converter | volume | em mililitros (mL) |
|---|---|---|
| mL | 1 | 1 |
| chávena americana | 1 | 240 |
| onça líquida americana | 1 | 30 |
| colher de chá americana | 1 | 5 |
| colher de sopa americana | 1 | 15 |
Como tens talento para a programação e estás em formação, decides usar os mililitros como unidade de transição, para facilitar a conversão de qualquer unidade da lista para qualquer outra (mesmo para ela própria).
Implementa o método KitchenCalculator.get_volume.
Dado um par de volume, na forma de tuplo, deve devolver apenas a componente numérica.
KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0
Implementa o método KitchenCalculator.to_milliliter.
Dado um par de volume, na forma de tuplo, deve converter o volume em mililitros, usando a tabela de conversão.
KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}
Implementa o método KitchenCalculator.from_milliliter.
Dado um par de volume, na forma de tuplo, e a unidade pretendida, deve converter o volume para a unidade pretendida, usando a tabela de conversão.
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}
Implementa o método KitchenCalculator.convert.
Dado um par de volume, na forma de tuplo, e a unidade pretendida, deve converter o volume indicado para a unidade pretendida.
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
Inscreve-te no Exercism para aprenderes e dominares Crystal com 26 conceitos142 exercícios, e mentoria humana real, tudo grátis.