Tipos

Os tipos são usados no FatScript para combinar dados e comportamentos, atuando como modelos para a criação de novas réplicas.

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:

No entanto, é necessário importar o pacote type para acessar os membros de protótipo de cada tipo.

Tipos Adicionais

Os tipos nativos do FatScript são enriquecidos com uma coleção de tipos extras que expandem as funcionalidades básicas de seus tipos nativos. Criados em FatScript puro, esses tipos adicionais atendem a várias necessidades de programação avançada e facilitam padrões de projeto comuns.

Além disso, você encontrará tipos de domínio específico incorporados nas bibliotecas, como Worker na biblioteca async, FileInfo em file, HttpRequest (entre outros) em http, CommandResult em system etc.

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 no seguinte exemplo:

# Definição de tipo com valors padrão
Carro = (km: Number = 0, cor: Text = 'branco', opcional = null)

Unicidade Global

O FatScript possui um único meta-espaço global, exigindo que os nomes dos tipos sejam únicos em todo o seu programa e em quaisquer bibliotecas incluídas. Tentar definir um tipo que compartilha um nome com um tipo existente, mesmo que em um escopo diferente, aciona um AssignError. No entanto, se a nova definição for idêntica à existente, ela será simplesmente ignorada.

Para examinar os tipos presentes no meta-espaço global, o comando _<-fat.std; sdk.getTypes; se mostra útil. Esta função enumera todos os tipos definidos e detalha seus locais de definição com marcadores source:line:column. Este recurso ajuda na navegação e compreensão da estrutura do seu código e suas dependências.

É prudente evitar nomes já utilizados pelos tipos de biblioteca fat.std ao definir novos tipos.

Embora o FatScript não imponha um protocolo estrito de nomes para o desenvolvimento de bibliotecas, é recomendado adotar uma estratégia de nomes que evite conflitos. Uma prática comum envolve prefixar os nomes dos tipos com algum identificador único que reflita o nome da sua biblioteca, reduzindo assim a probabilidade de choques de nomes.

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, argumentos usando a sequência das propriedades
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 é uma palavra-chave 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

Qualquer coisa pode ser comparada com a palavra reservada Type, que identifica se se refere a um tipo:

Number == Type  # true

Type também pode ser usado para especificar que um método recebe um parâmetro de tipo:

combine = (t: Type, val: Any): Any -> ...

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, Type ou Method

Restrições de tipo

No FatScript, você pode declarar restrições de tipo para parâmetros 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.

Mixin (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 ou mixin.

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 (parâmetros 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) define a, b e c

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

No FatScript, o símbolo * é usado para conversão de tipo, permitindo que você trate um tipo de dado como outro sem alterar os dados subjacentes. Essa capacidade é especialmente útil para especificar explicitamente o tipo ou para tratar valores como tipos compatíveis, por exemplo:

time.format(Epoch * 1688257765448)  # trata o número como um valor de Época Unix

Aceitação flexível de tipos

FatScript oferece flexibilidade na aceitação de tipos por meio da inclusão de um tipo base. Este sistema permite a criação de tipos inter-relacionados que podem ser utilizados de forma intercambiável em métodos ou como elementos de uma Lista.

Por exemplo, consideremos os tipos A, B, e C. Se os tipos B e C incorporam o tipo A de maneira exclusiva em suas definições, eles são considerados como compartilhando as mesmas características derivadas de A, tornando B e C tipos compatíveis sob a base de A.

Aqui está como isso parece no código:

A = (_)
B = (A, b = true)
C = (A, c = true)

# method1 aceita ambos tipos B e C
method1 = (a: A) -> 'valid'

# essa lógica também se aplica a listas
mixedList: List/A = [ B(), C() ]

a flexibilidade do tipo só é possível se o tipo de dados é baseado em Scope

Advertência

Este sistema permite que um método projetado para aceitar um objeto do tipo B também possa aceitar um objeto do tipo C devido à base comum em A:

method2 = (x: B) -> 'valid'
method2(C())  # retorna 'valid' (inesperadamente?)

Embora o sistema flexível seja geralmente útil, ele pode ser inadequado quando é necessária uma correspondência exata de tipos. Nesses casos, poderia-se verificar explicitamente o tipo dentro do método, por exemplo, usando x == B para aceitar apenas objetos do tipo B.

Para restringir a flexibilidade de tipo e garantir correspondência exata, deve-se incluir StrictType na definição do tipo:

C = (A, StrictType, c = true)  # C agora exige correspondência estrita de tipo

Esta modificação impede que C seja utilizado onde A ou B são aceitos, mesmo que ambos compartilhem do mesmo tipo base A.

Tipos compostos

No FatScript, tipos compostos permitem que você defina estruturas de dados complexas compostas por tipos mais simples para restringir aceitação de parâmetros em métodos e atribuições. 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:

  1. ListOfNumbers = List/Number, define um tipo composto ListOfNumbers, que é uma lista que só pode conter números.

  2. Matrix = List/List/Number, define um tipo composto Matrix, que é uma lista de listas que só pode conter números.

  3. MethodReturningListOfNumbers = Method/ListOfNumbers, define um tipo composto MethodReturningListOfNumbers, que é um método que retorna um ListOfNumbers.

  4. NumericScope = Scope/Number define um tipo composto NumericScope, que é um escopo cujas entradas podem ser apenas do tipo número.

Veja também

results matching ""

    No results matching ""