Expressões de Combinação

    Se quisermos comparar uma expressão com um valor, então usamos um "se". Mas se quisermos comparar uma expressão com muitos valores, isto se torna muito tedioso. O Pony oferece um poderoso recurso de correspondência de padrões, combinando a correspondência de valores e tipos, sem a necessidade de qualquer código especial.

Correspondência: o básico

    Aqui está um exemplo simples de uma expressão de fósforo que produz uma corda.

match x
| 2 => "int"
| 2.0 => "float"
| "2" => "string"
else
  "alguma coisa"
End

    Se você está acostumado a idiomas funcionais, isto deve ser muito familiar.
      Para os leitores mais familiarizados com a família de linguagens C e Java, pense nisto como uma declaração de mudança. Mas você pode ligar valores que não sejam apenas inteiros, como Strings. Na verdade, você pode ligar qualquer tipo que forneça uma função de comparação, incluindo suas próprias classes. E você também pode ligar o tipo de tempo de execução de uma expressão.
        Uma correspondência começa com a palavra-chave correspondente, seguida pela expressão correspondente, que é conhecida como o operando da correspondência. Neste exemplo, o operando é apenas a variável x, mas pode ser qualquer expressão.
          A maior parte da expressão de partida consiste em uma série de casos que combinamos. Cada caso consiste de um símbolo de cano ('|'), o padrão a ser comparado, uma seta ('=>') e a expressão para avaliar se o caso corresponde.
            Percorremos os casos um a um até encontrarmos um que corresponda. (Na prática, o compilador é muito mais inteligente do que isso e usa uma combinação de verificações sequenciais e tabelas de salto para ser o mais eficiente possível).
              Note que cada caso de partida tem uma expressão para avaliar e todos eles são independentes. Não há "queda" entre os casos, como ocorre em idiomas como C. Se o valor produzido pela expressão de correspondência não for usado, então os casos podem omitir a seta e a expressão a avaliar. Isto pode ser útil para excluir casos específicos antes de um caso mais geral.

            Outros casos

              Como em todas as estruturas de controle do Pony, o outro caso para uma expressão de correspondência é usado se não tivermos outro valor, ou seja, se nenhum de nossos casos corresponder. O outro caso, se houver, deve vir no final da partida, depois de todos os casos específicos.
                Se o valor em que a expressão de partida resulta for usada, então você precisa ter um outro caso, exceto nos casos em que o compilador reconhece que a partida é exaustiva e que o outro caso nunca poderá ser realmente alcançado. Se você omiti-lo, será adicionado um valor padrão que avalia a Nenhum. O compilador reconhece uma correspondência como exaustiva quando a união dos tipos para todos os padrões que correspondem apenas no tipo é um supertipo do tipo de expressão correspondente. Em outras palavras, quando seus casos cobrem todos os tipos possíveis para a expressão combinada, o compilador não adicionará um None implícito a sua declaração de concordância.

              Correspondência em valores

                A expressão de partida mais simples corresponde apenas ao valor.

              fun f(x: U32): String =>
                match x
                | 1 => "um"
                | 2 => "dois"
                | 3 => "tres"
                | 5 => "não é quatro"
                else
                  "alguma coisa"
                End

                Porque o valor que corresponde ao padrão é simplesmente o valor ao qual queremos corresponder, assim como uma declaração de interruptor C. O caso com o mesmo valor que o operando vence e usamos sua expressão.
                  O compilador chama a função eq() no operando, passando o padrão como o argumento. Isto significa que você pode usar seus próprios tipos como operandos e padrões de correspondência, desde que você defina uma função eq().

                class Foo
                  var _x: U32
                
                  new create(x: U32) =>
                    _x = x
                
                  fun eq(that: Foo): Bool =>
                    _x == that._x
                
                actor Main
                fun f(x: U32): String =>
                  match x
                  | 1 => "um"
                  | 2 => "dois"
                  | 3 => "tres"
                  | 5 => "não é quatro"
                  else
                    "alguma coisa"
                  End

                  Correspondência no tipo e valor

                    A correspondência no valor é boa se o operando e os padrões de caixa tiverem todos o mesmo tipo. Entretanto, a correspondência pode lidar com vários tipos diferentes. Cada padrão de caso é primeiramente verificado para ver se é o mesmo tipo que o tipo de tempo de execução do operando. Somente então os valores serão comparados.

                  fun f(x: (U32 | String | None)): String =>
                    match x
                    | None => "nada"
                    | 2 => "dois"
                    | 3 => "três"
                    | "5" => "não é quatro"
                    else
                      "alguma coisa"
                    End

                    Em muitos idiomas, o uso de informações do tipo runtime é muito caro e, portanto, geralmente é evitado sempre que possível.
                      No Pony, é barato. Realmente barato. A abordagem "programa inteiro" do Pony para a compilação significa que o compilador pode trabalhar o máximo possível em tempo de compilação. O custo de tempo de execução de cada tipo de verificação é geralmente uma comparação de ponteiro único. Além disso, é claro, quaisquer verificações que possam ser totalmente determinadas no tempo de compilação são. Portanto, para os upcasts não há custo algum de tempo de execução.
                        Quando são avaliados os padrões de casos para comparação de valores? Cada expressão de padrão de caso que corresponda ao tipo de operando de correspondência, precisa ser avaliada cada vez que a expressão de correspondência é avaliada até que um caso corresponda (outros padrões de caso são ignorados). Isto pode levar à criação não intencional de muitos objetos com o único objetivo de verificar a igualdade. Se os padrões de caso realmente precisam apenas diferenciar por tipo, devem ser usadas Capturas, estas se resumem a simples verificações de tipo em tempo de execução.
                          À primeira vista, é fácil confundir um padrão de correspondência de valor para uma verificação de tipo. Considere o seguinte exemplo:

                        class Foo is Equatable[Foo]
                        
                        actor Main
                          
                          fun f(x: (Foo | None)): String =>
                            match x
                            | Foo => "Foo"
                            | None => "barra"
                            else
                              ""
                            end
                        
                          new create(env: Env) =>
                            f(Foo)

                          Ambos os padrões de caso na verdade não verificam se o operando x é uma instância de Foo ou None, mas verificam a igualdade com a instância criada pela avaliação do padrão de caso (cada vez). Nenhum é primitivo e, portanto, há apenas uma instância, caso em que este padrão de valor faz o que se esperava, mas não é bem assim. Se None tivesse uma função de eq personalizada que não usasse igualdade de identidade, isto poderia levar a resultados surpreendentes.
                            Lembre-se de sempre usar Capturas se tudo o que você precisa é diferenciar por tipo. Só use correspondência de valor se você precisar de uma verificação de igualdade completa, seja para igualdade estrutural ou igualdade de identidade.

                          Capturas

                            Às vezes você quer poder combinar o tipo, para qualquer valor desse tipo. Para isso, você usa uma captura. Isto define uma variável local, válida apenas dentro do caso, contendo o valor do operando. Se o operando não for do tipo especificado, então o caso não corresponde.
                              As captações se parecem exatamente com declarações de variáveis dentro do padrão. Como as variáveis normais, elas podem ser declaradas como var ou let. Se não forem reatribuídas dentro da expressão do caso, é uma boa prática usar let.

                            fun f(x: (U32 | String | None)): String =>
                              match x
                              | None => "nada"
                              | 2 => "dois"
                              | 3 => "três"
                              | let u: U32 => "outro inteiro"
                              | let s: String => s
                              End

                              Posso omitir o tipo a partir de uma captura, como posso a partir de uma variável local? Infelizmente não. Uma vez que combinamos no tipo e valor, o compilador tem que saber de que tipo é o padrão, portanto não pode ser inferido.

                            Correspondência implícita nas capacidades no contexto de tipos de união

                              Nos tipos de união, quando fazemos a combinação de padrões em classes ou traços individuais, também fazemos a combinação implícita dos padrões nas capacidades correspondentes. No exemplo fornecido abaixo, se _x tem tipo estático (A iso | B ref | Nenhuma) e combina dinamicamente com A, então também sabemos que deve ser uma iso A.

                            class A
                              fun ref sendable() => 
                                None
                              
                            class B
                              fun ref update() => 
                                None
                            
                            actor Main
                              var _x: (A iso | B ref | None)
                            
                              new create(env: Env) =>
                                _x = None
                            
                              be f(a': A iso) =>
                                match (_x = None) // Tipo de expressão: (A iso^ | B ref | None)
                                | let a: A iso => f(consume a)
                                | let b: B ref => b.update()
                                End

                              Observe que não é possível usar uma expressão de partida para diferenciar apenas com base nas capacidades em tempo de execução, ou seja, não é possível:

                            class A
                              fun ref sendable() => 
                                None
                              
                            actor Main
                              var _x: (A iso | A ref | None)
                            
                              new create(env: Env) =>
                                _x = None
                            
                              be f() =>
                                match (_x = None)
                                | let a1: A iso => None
                                | let a2: A ref => None
                                End

                              não verifica a digitação.

                            Tupilos correspondentes

                              Se você quiser combinar em mais de uma operação de uma vez, então você pode simplesmente usar um tuple. Os casos só corresponderão se todos os elementos do tuple corresponderem.

                            fun f(x: (String | None), y: U32): String =>
                              match (x, y)
                              | (None, let u: U32) => "nada"
                              | (let s: String, 2) => s + " dois"
                              | (let s: String, 3) => s + " três"
                              | (let s: String, let u: U32) => s + " outro inteiro"
                              else
                                "alguma coisa"
                              End

                              Tenho que especificar todos os elementos em um tuple? Não, você não precisa. Qualquer elemento de um tuple em um padrão pode ser marcado como "não importa", usando um sublinhado ('_'). O primeiro e quarto casos em nosso exemplo não se importam realmente com o elemento U32, portanto, podemos ignorá-lo.

                            fun f(x: (String | None), y: U32): String =>
                              match (x, y)
                              | (None, _) => "nada"
                              | (let s: String, 2) => s + " dois"
                              | (let s: String, 3) => s + " três"
                              | (let s: String, _) => s + " outro inteiro"
                              else
                                "alguma coisa"
                              End

                              Guardas

                                Além da correspondência em tipos e valores, cada caso em uma correspondência também pode ter uma condição de guarda. Esta é simplesmente uma expressão, avaliada após a correspondência de tipo e valor, que deve dar o valor verdadeiro para que o caso corresponda. Se o vigilante for falso, então o caso não corresponde e passamos para o próximo da maneira usual.
                                  Os guardas são introduzidos com a palavra-chave if (foi onde até 0,2,1).
                                    Uma expressão de guarda pode usar qualquer variável capturada daquele estojo, o que permite o manuseio de faixas e funções complexas.

                                  fun f(x: (String | None), y: U32): String =>
                                    match (x, y)
                                    | (None, _) => "nada"
                                    | (let s: String, 2) => s + " dois"
                                    | (let s: String, 3) => s + " três"
                                    | (let s: String, let u: U32) if u > 14 => s + " outro inteiro grande"
                                    | (let s: String, _) => s + " outro inteiro pequeno"
                                    else
                                      "something else"
                                    End