Tipos
Os tipos são usados no FatScript para combinar dados e comportamentos, atuando como modelos para a criação de novas réplicas (instâncias).
Nomeação
Os nomes de tipo são sensíveis a maiúsculas e minúsculas, devendo começar com uma letra maiúscula.
A convenção recomendada para identificadores de tipo é PascalCase
.
Tipos Nativos
O FatScript fornece vários tipos nativos:
- Any - qualquer coisa
- Void - nada
- Boolean - primitivo
- Number - primitivo
- Text - primitivo
- Method - função ou lambda
- List - como uma matriz ou pilha
- Scope - como um objeto ou dicionário
- Error - sim, para erros
No entanto, é necessário importar o pacote type para acessar os membros de protótipo de cada tipo.
Tipos Personalizados
Além de usar os tipos fornecidos pela linguagem ou por uma biblioteca externa, você também pode criar seus próprios tipos ou estender os existentes com novos comportamentos.
Declaração
Para definir um tipo personalizado no FatScript, você pode usar uma simples declaração de atribuição. A definição de tipo pode ser envolvida em parênteses ou chaves. Ambas as sintaxes são válidas e têm o mesmo efeito. Você também pode opcionalmente definir valores padrão para as propriedades do tipo, como mostrado nos seguintes exemplos:
# Definição de tipo usando chaves
Carro = { km: Number, cor: Text }
# Definição de tipo usando parênteses com valors padrão
Carro = (km = 0, cor = 'branco')
Unicidade Global
Embora a definição de tipo seja armazenada no escopo onde é declarada, o nome do tipo deve ser único no seu programa. Se você tentar definir um tipo com o mesmo nome de um já existente, mesmo em um escopo diferente, um AssignError
será gerado, a menos que a definição seja idêntica, caso em que ela será ignorada.
Uso
Para criar instâncias de um tipo personalizado, chame o nome do tipo como se fosse um método, opcionalmente passando valores para as propriedades:
# Uso do tipo com padrões
carro = Carro()
# saída: { km: Number = 0, cor: Text = 'branco' }
# Uso do tipo, definindo uma das propriedades
carroVermelho = Carro(cor = 'vermelho')
# saída: { km: Number = 0, cor: Text = 'vermelho' }
# Uso do tipo, totalmente qualificado
carroVelho1 = Carro(cor = 'azul', km = 38000)
# substitui ambos os valores
# Uso do tipo, args usando sequência de props
carroVelho2 = Carro(41000, 'verde')
# substitui valores usando a ordem da definição do tipo
Por padrão, os tipos personalizados retornam um escopo de suas propriedades. No entanto, se você definir um método apply
, o tipo poderá retornar um valor diferente. Por exemplo, aqui está um tipo personalizado Soma
com um método apply
que retorna a soma de suas propriedades a
e b
:
Soma = (a: Number, b: Number, apply = -> a + b)
Soma(1, 2) # saída: 3
observe que os métodos apply têm acesso direto às propriedades da instância
Neste exemplo, o tipo base de saída do apply
é um número, não um escopo. Isso também significa que as propriedades originais do tipo personalizado são perdidas durante a instanciação e não podem ser acessadas novamente.
Membros do protótipo
Esses são um tipo especial de método, armazenados dentro da definição do tipo:
TipoComMembrosDePrototipo = {
~ a: Number
~ b: Number
setA = (novoA: Number) -> $self.a = novoA
setB = (novoB: Number) -> $self.b = novoB
soma = (): Number -> $self.a + $self.b
}
Neste exemplo, setA
, setB
e soma
são membros do protótipo. Observe que precisamos usar $self
, que é um comando embutido que fornece uma referência ao escopo da própria instância (ou método), para que nós pudéssemos ganhar acesso às propriedades.
Checando tipos
Se você não sabe qual é o tipo de uma entrada, pode simplesmente verificar comparando com um nome de tipo:
lugar = 'restaurante'
lugar == Number # false
lugar == Text # true
alternativamente, use o método
typeOf
da biblioteca sdk para extrair o nome do tipo
Alias de tipo
No FatScript, você pode criar subtipos atribuindo um nome diferente a um tipo existente. Isso significa que o novo tipo herdará todas as propriedades do tipo base. Aqui está um exemplo:
_ <- fat.type.Text
Id = Text # cria um alias
Observe que os aliases de tipo são hierárquicos e podem ser usados para classificar valores enquanto ainda herdam o mesmo comportamento. No entanto, embora o alias seja considerado igual ao tipo base, as instâncias do novo tipo não são consideradas iguais ao tipo base.
Para verificar se um valor é uma instância de um alias de tipo ou do tipo base, você pode usar o operador de comparação de menor-ou-igual <=
. Isso permite que você aceite qualquer tipo na cadeia de aliases, até o tipo base. Aqui está um exemplo:
Id == Text # verdadeiro, já que Id é um alias de Text
x = Id(123) # id: Id = '123'
x == Text # falso, no entanto x é do tipo Id, não Text
x == Id # verdadeiro, como o esperado x é do tipo Id
x <= Text # verdadeiro, já que x é do tipo Id, que é um alias de Text
Essa funcionalidade permite uma validação refinada em tipos específicos, mantendo a flexibilidade de usar diferentes aliases para o mesmo tipo subjacente.
limitação: não é possível criar alias para
Any
,Void
,List
ouMethod
Restrições de tipo
No FatScript, você pode declarar restrições de tipo para argumentos de método. Quando um método é chamado, o argumento é verificado automaticamente em relação à restrição de tipo. Se o argumento não for do tipo esperado ou um de seus subtipos, um TypeError
é gerado.
Se a restrição de tipo for um tipo base, qualquer subtipo desse tipo também será aceito como argumento. No entanto, se a restrição de tipo for um subtipo, somente argumentos que correspondam ao subtipo serão aceitos. Aqui está um exemplo:
generalista = (x: Text) -> x
restritivo = (x: Id) -> x
Neste exemplo, o método generalista
aceita argumentos Text
e Id
, porque Id
é um subtipo de Text
. O método restritivo
aceita apenas argumentos Id
e não Text
, porque Id
é um subtipo de Text
, mas não o contrário.
É importante enfatizar que os tipos personalizados são derivados de Scope
. Nesse contexto, Scope
seria o tipo generalista para, por exemplo, o tipo personalizado Carro
.
Inclusões de tipo (avançado)
Ao definir um tipo, você pode adicionar os recursos de um tipo existente simplesmente mencionando-o na definição de tipo. Isso é chamado de inclusão de tipo.
Por exemplo, para criar um novo tipo CarroAlugado
com as propriedades de Carro
e uma propriedade adicional preco
, você pode escrever:
CarroAlugado = {
# Inclusões
Carro
# Propriedade adicional
preco: Number
}
CarroAlugado(50) # { cor: Text = 'branco', km: Number = 0, preco: Number = 50 }
Se uma propriedade não estiver definida no novo tipo, ela herdará o valor padrão do tipo incluído. No exemplo acima, as propriedades cor
e km
do Carro
estão presentes no CarroAlugado
, com seus valores padrão.
Herdando métodos de protótipo
Suponha que continuemos a partir do exemplo anterior do tipo TipoComMembrosDePrototipo
que tem duas propriedades a
e b
, e três métodos de protótipo setA
, setB
e soma
. Para criar um novo tipo ComMaisMembros
que adiciona uma propriedade c
, um método setC
e substitui o método soma
, você pode escrever:
ComMaisMembros = {
# Inclusões
TipoComMembrosDePrototipo
# Propriedades (argumentos da instância)
~ a: Number
~ b: Number
~ c: Number
# Membros de protótipo (métodos)
setC = (novoC: Number) -> $self.c = novoC
soma = (): Number -> $self.a + $self.b + $self.c
}
redeclarando as propriedades permite que o novo tipo também aceite argumentos no momento da instanciação, por exemplo:
ComMaisMembros(1, 2, 3)
definea
,b
ec
Ao criar uma nova instância de ComMaisMembros
, todos os quatro métodos de protótipo setA
, setB
, setC
e soma
estarão disponíveis.
Observe que se houver uma redefinição de uma propriedade ou método no novo tipo, a nova definição terá precedência.
Conversão de tipos
Em FatScript, o símbolo *
funciona como um operador de conversão de tipo, permitindo que você converta um tipo de dado em outro. Essa funcionalidade é especialmente útil quando você precisa especificar explicitamente o tipo ou realizar conversões entre tipos compatíveis, por exemplo:
time.format(Epoch * 1688257765448) # converte o número para Unix Epoch
Aceitação flexível de tipos
FatScript oferece flexibilidade na aceitação de tipos implementando um sistema baseado na inclusão de tipos. Isso cria tipos inter-relacionados que podem ser usados de forma intercambiável em um método ou como itens de uma Lista.
Quando você define um tipo, é possível incorporar um ou mais tipos adicionais dentro dessa definição. Por exemplo, os tipos A
, B
, e C
. Se os tipos B
e C
incluem o tipo A
em suas definições, eles são vistos como compartilhando o mesmo conjunto de características derivadas de A
. Isso significa que B
e C
são considerados tipos irmãos sob o guarda-chuva de A
.
Este sistema permite que um método que foi projetado para aceitar um objeto do tipo B
também seja capaz de aceitar um objeto do tipo C
, e vice-versa. Isso ocorre pelo fato de que ambos os tipos B
e C
compartilham uma base comum no tipo A
.
Aqui está como isso parece no código:
A = (_)
B = (A, b = true)
C = (A, c = true)
# o método1 aceita tanto B quanto C, porque ambos incluem A
method1 = (a: A) -> 'valid'
# o método2 aceita C, já que B e C incluem o mesmo conjunto de tipos
# (tornando-os tipos irmãos)
method2 = (x: B) -> 'valid'
# essa lógica também se aplica a tipos de Lista, como visto com mixedList
mixedList: List/A = [ B(), C() ]
a flexibilidade do tipo só é possível se o tipo de dados é baseado em
Scope
Advertência
Você pode ter que verificar explicitamente o tipo, por exemplo, x == B
dentro do corpo do método se você quiser lidar apenas com B
, mas não com C
em seu método. Ou você pode criar um alias, por exemplo, D = A
e usar C = (D, c = true)
como inclusão de tipo para evitar completamente o comportamento flexível.
Tipos compostos
No FatScript, tipos compostos permitem que você defina estruturas de dados complexas compostas por tipos mais simples. Eles são representados usando barras /
para separar os tipos na definição do tipo composto.
Vamos ver alguns exemplos e entender como os tipos compostos funcionam:
ListOfNumbers = List/Number
, define um tipo compostoListOfNumbers
, que é uma lista que só pode conter números.Matrix = List/List/Number
, define um tipo compostoMatrix
, que é uma lista de listas que só pode conter números.MethodReturningListOfNumbers = Method/ListOfNumbers
, define um tipo compostoMethodReturningListOfNumbers
, que é um método que retorna umListOfNumbers
.NumericScope = Scope/Number
define um tipo compostoNumericScope
, que é um escopo cujas entradas podem ser apenas do tipo número.