Structs
Uma Struct é semelhante a uma classe. Há um par de diferenças muito importantes. Você usará aulas em todo o seu código Pony. Você raramente usará Structs. Vamos uma breve introdução ao básico de Structs.
Structs são "classes para FFI".
Uma Struct é um mecanismo de classe usado para passar dados para frente e para trás com o código C através da Interface de Função Estrangeira do Pony.
Como as classes, as Structs em Pony podem conter tanto campos quanto métodos. Ao contrário das classes, as Structs em Pony têm o mesmo layout binário que as Structs C e podem ser usadas de forma transparente nas funções C. Os structs não possuem um descritor de tipo, o que significa que não podem ser usados em tipos algébricos ou em características/interfaces de implementos.
O que se passa em uma Struct?
O mesmo que uma classe! Uma Struct é composta de alguma combinação de:
Campos
- Construtores
- Funções
- Campos
Os campos de Struct são definidos da mesma forma que para as classes Pony, usando embed, let e var. Um campo embed está embutido em seu objeto pai, como uma Struct C dentro da Struct C. Um campo var/let é um ponteiro para um objeto alocado separadamente. Por exemplo:
struct Inner
var x: I32 = 0
struct Outer
embed inner_embed: Inner = Inner
var inner_var: Inner = Inner
Construtores
Os construtores de Structs, como os construtores de classes, têm nomes. Tudo o que você aprendeu anteriormente sobre os construtores de classes Pony se aplica aos construtores de Structs.
struct Pointer[A]
"""
A Pointer[A] is a raw memory pointer. It has no descriptor and thus can't be
included in a union or intersection, or be a subtype of any interface. Most
functions on a Pointer[A] are private to maintain memory safety.
"""
new create() =>
"""
A null pointer.
"""
compile_intrinsic
new _alloc(len: USize) =>
"""
Space for len instances of A.
"""
compile_intrinsic
Aqui temos dois construtores. Um que cria um novo Ponteiro nulo, e outro que cria um Ponteiro com espaço para muitas instâncias do tipo para o qual o Ponteiro está apontando. Não se preocupe se você não seguir tudo o que você está vendo no exemplo acima. A parte importante é que ele deve basicamente se parecer com o exemplo de construtor de classes que vimos anteriormente.
Funções
Como as classes, as Structs em Pony também podem ter funções. Tudo o que você sabe sobre funções nas classes Pony também se aplica às Structs.
As Structs desempenham um papel importante nas interações do Pony com o código escrito usando C. Vamos vê-las novamente na seção C-FFI do tutorial. Provavelmente não veremos muito sobre Structs até lá.
Tipo Pseudônimos
Um pseudônimo é apenas uma forma de dar um nome diferente a um tipo. Isto pode parecer um pouco bobo: afinal, os tipos já têm nomes! Entretanto, o Pony pode expressar alguns tipos complicados, e pode ser conveniente ter uma forma curta de falar sobre eles. Daremos alguns exemplos de uso de pseudônimos só para ter uma noção do que são.
Enumerações
Uma maneira de usar pseudônimos do tipo é expressar uma enumeração. Por exemplo, imagine que queremos dizer algo que deve ser Vermelho, Azul ou Verde. Poderíamos escrever algo como isto:
primitive Red
primitive Blue
primitive Green
type Colour is (Red | Blue | Green)
Há dois novos conceitos aí dentro. O primeiro é o tipo alias, introduzido com o tipo de palavra-chave. Isso só significa que o nome que vem depois do tipo será traduzido pelo compilador para o tipo que vem depois é.
O segundo novo conceito é o tipo que vem depois é. Não é um tipo único! Ao invés disso, é um tipo de união. Você pode ler o símbolo | como ou neste contexto, então o tipo é "Vermelho ou Azul ou Verde".
Um tipo de sindicato é uma forma de tipo de mundo fechado. Ou seja, diz todo tipo que pode ser um membro do mesmo. Em contraste, a subtipagem orientada a objetos é geralmente um mundo aberto, por exemplo, em Java, uma interface pode ser implementada por qualquer número de classes.
Você também pode declarar constantes como em C ou Go como esta:
primitive Red fun apply(): U32 => 0xFF0000FF
primitive Green fun apply(): U32 => 0x00FF00FF
primitive Blue fun apply(): U32 => 0x0000FFFF
type Colour is (Red | Blue | Green)
ou os nomes os espalhem desta forma
primitive Colours
fun red(): U32 => 0xFF0000FF
fun green(): U32 => 0x00FF00FF
Você também pode querer iterar sobre o enumerador assim para imprimir seu nome para fins de depuração.
primitive ColourList
fun apply(): Array[Colour] =>
[Red; Green; Blue]
for colour in ColourList().values() do
end
Tipos complexos
Se um tipo é complicado, pode ser bom dar-lhe um nome mnemônico. Por exemplo, se quisermos dizer que um tipo deve implementar mais de uma interface, poderíamos dizer:
interface
fun name():
interface
fun age():
interface
fun feeling():
type Person is (HasName & HasAge & HasFeelings)
Este uso de tipos complexos se aplica a traços, não apenas interfaces:
trait HasName
fun name(): String => "Bob"
trait HasAge
fun age(): U32 => 42
trait HasFeelings
fun feeling(): String => "Great!"
type Person is (HasName & HasAge & HasFeelings)
Há outro novo conceito aqui: o tipo tem um "&" dentro dele. Isto é semelhante ao "|" de um tipo de união: significa que este é um tipo de interseção. Ou seja, é algo que deve ser todo de HasName, HasAge e HasFeelings. Mas o uso do tipo aqui é exatamente o mesmo que o exemplo de enumeração acima, é apenas fornecer um nome para um tipo que de outra forma é um pouco tedioso de digitar repetidamente.
Outro exemplo, desta vez da biblioteca padrão, são os SetIs. Aqui está a definição real:
type SetIs[A] is HashSet[A, HashIs[A!]]
Novamente há algo novo aqui. Após o nome SetIs vem o nome A entre parênteses retos. Isso porque SetIs é um tipo genérico. Ou seja, você pode dar a um SetIs outro tipo como parâmetro, para fazer tipos específicos de conjunto. Se você tiver usado Java ou C#, isto será bastante familiar. Se você usou C++, o conceito equivalente são os templates, mas eles funcionam de forma bem diferente. E mais uma vez o uso do tipo apenas fornece uma maneira mais conveniente de se referir ao tipo que estamos usando como alias:
HashSet[A, HashIs[A!]]
Esse é outro tipo genérico. Isso significa que um SetIs é realmente uma espécie de HashSet. Outro conceito se infiltrou, que é ! tipos. Este é um tipo que é o pseudônimo de outro tipo. Isso é algo complicado que você só precisa quando escreve tipos genéricos complexos, por isso vamos deixar para mais tarde.
Mais um exemplo, novamente da biblioteca padrão, é o tipo de mapa que é muito usado. Na verdade, é um tipo de pseudônimo. Aqui está a verdadeira definição de Mapa:
type Map[K: (Hashable box & Comparable[K] box), V] is HashMap[K, V, HashEq[K]]
Ao contrário de nosso exemplo anterior, o primeiro parâmetro do tipo, K, tem um tipo associado a ele. Trata-se de uma restrição, o que significa que quando se parametriza um Mapa, o tipo que se passa para K deve ser um subtipo da restrição. Além disso, observe que a caixa aparece no tipo. Esta é uma capacidade de referência. Isto significa que há uma certa classe de operações que precisamos ser capazes de fazer com um K. Cobriremos isto com mais detalhes mais tarde. Assim como nossos outros exemplos, tudo isso realmente significa que o Mapa é realmente uma espécie de HashMap.
Observação
Os pseudônimos são usados para muitas coisas, mas isto lhe dá a idéia geral. Basta lembrar que um alias é sempre uma conveniência: você poderia substituir cada uso do alias do tipo pelo tipo completo depois do is. Na verdade, é exatamente isso que o compilador faz.
Tipo Expressões
Os tipos dos quais falamos até agora também podem ser combinados em expressões de tipo. Se você está acostumado à programação orientada a objetos, talvez não os tenha visto antes, mas eles são comuns na programação funcional. Uma expressão de tipo também é chamada de tipo de dados algébricos.
Há três tipos de expressão de tipo: tuples, uniões e interseções.
Tuple
Um tipo tuple é uma seqüência de tipos. Por exemplo, se quiséssemos algo que fosse uma string seguida de um U64, escreveríamos isto:
cvar x: (String, U64)
x = ("hi", 3)
x = ("bye", 7)
Todas as expressões de tipo são escritas entre parênteses, e os elementos de um tuple são separados por uma vírgula. Também podemos desestruturar um tuple usando atribuição:
(var y, var z) = x
Ou podemos acessar os elementos de um tuple diretamente:
var y = x._1
var z = x._2
Note que não há como atribuir a um elemento de um tuple. Em vez disso, você pode simplesmente reatribuir todo o tuple, desta forma:
x = ("wombat", x._2)
Por que usar um tuple em vez de uma classe? Tuples são uma forma de expressar uma coleção de valores que não tem nenhum código associado ou comportamento esperado. Basicamente, se você precisa apenas de uma rápida coleção de coisas, talvez para retornar mais de um valor de uma função, por exemplo, você pode usar um tuple.
Uniões
Um tipo de união é escrito como um tuple, mas usa um | (pronuncia-se "ou" ao ler o tipo) em vez de um , entre seus elementos. Quando um tuple representa uma coleção de valores, uma união representa um único valor que pode ser qualquer um dos tipos especificados.
As uniões podem ser usadas para toneladas de coisas que requerem múltiplos conceitos em outras linguagens. Por exemplo, valores opcionais, enumerações, valores de marcadores, e muito mais.
var x: (String | None)
Aqui temos um exemplo de utilização de um sindicato para expressar um tipo opcional, onde x pode ser uma string, mas também pode ser Nenhuma.
Intersecções
Um cruzamento usa um & (pronuncia-se "e" ao ler o tipo) entre seus elementos. Ela representa exatamente o oposto de uma união: é um valor único que é todos os tipos especificados, ao mesmo tempo!
Isto pode ser muito útil para combinar traços ou interfaces, por exemplo. Aqui está algo da biblioteca padrão:
type Map[K: (Hashable box & Comparable[K] box), V] is HashMap[K, V, HashEq[K]]
É um tipo bastante complexo, mas vejamos a restrição de K. É (Hashable box & Comparable[K] box)
, que significa que K é Hashable e é Comparable[K], ao mesmo tempo.
Combinando expressões do tipo
As expressões de tipo podem ser combinadas em tipos mais complexos. Aqui está outro exemplo da biblioteca padrão:
var _array: Array[((K, V) | _MapEmpty | _MapDeleted)]
Aqui temos uma matriz onde cada elemento é ou um tuple de (K, V) ou um _MapEmpty ou a _MapDeleted. Como todo tipo de expressão tem parênteses ao seu redor, eles são na verdade fáceis de ler uma vez que você se apodera dele. Entretanto, se você usa uma expressão de tipo complexo com freqüência, pode ser bom fornecer um pseudônimo de tipo para ela.
type Number is (Signed | Unsigned | Float)
type Signed is (I8 | I16 | I32 | I64 | I128)
type Unsigned is (U8 | U16 | U32 | U64 | U128)
type Float is (F32 | F64)
Esses são todos os tipos de pseudônimos usados pela biblioteca padrão. O número é um alias de tipo para uma expressão de tipo que contém outros alias de tipo? Sim! Divertido, e conveniente.