Aplicação Parcial

    A aplicação parcial nos permite fornecer alguns dos argumentos a um construtor, função ou comportamento, e recuperar algo que nos permita fornecer o resto dos argumentos mais tarde.

Um caso simples

    Um caso simples é criar uma função de "callback". Por exemplo, a função "callback":

class Foo
  var _f: F64 = 0

  fun ref addmul(add: F64, mul: F64): F64 =>
    _f = (_f + add) * mul

class Bar
  fun apply() =>
    let foo: Foo = Foo
    let f = foo~addmul(3)
    f(4)

    Este é um exemplo um pouco idiota, mas esperemos que a idéia seja clara. Aplicamos parcialmente a função addmul em foo, ligando o receptor a foo e o argumento de adição a 3. Recuperamos um objeto, f, que tem um método de aplicação que leva um argumento multíplice. Quando ele é chamado, ele por sua vez chama foo.addmul(3, mul). Podemos também ligar todos os argumentos:

let f = foo~addmul(3, 4)
f()

    Ou mesmo nenhum dos argumentos:

let f = foo~addmul()
f(3, 4)

Argumentos fora de ordem

    A aplicação parcial com argumentos nomeados permite argumentos vinculantes em qualquer ordem, não apenas da esquerda para a direita. Por exemplo, a aplicação parcial com argumentos nomeados:

let f = foo~addmul(where mul = 4)
f(3)

    Aqui, nós amarramos o argumento multíplice, mas deixamos sem restrições.

A aplicação parcial é apenas uma lambda

    Debaixo do capô, estamos montando um objeto literalmente para aplicação parcial, como se você mesmo tivesse escrito uma lambda. Ele captura pseudônimos de alguns dos escopos lexicais como campos e tem uma função de aplicação que leva algum, possivelmente reduzido, número de argumentos. Isto é realmente feito como açúcar, reescrevendo a árvore de sintaxe abstrata para que a aplicação parcial seja literalmente um objeto, antes da geração do código. Isso significa que a aplicação parcial resulta em uma classe anônima e retorna uma referência. Se você precisar de outra capacidade de referência, você pode embrulhar a aplicação parcial em uma expressão de recuperação. Isso também significa que não podemos consumir campos únicos para uma lambda, pois o método de aplicação pode ser chamado muitas vezes.

Aplicação parcial de uma aplicação parcial

    Como a aplicação parcial resulta em um objeto com um método de aplicação, podemos aplicar parcialmente o resultado!

let f = foo~addmul()
let f2 = f~apply(where mul = 4)
f2(3)

    Genéricos e Capacidades de Referência

      Nos exemplos apresentados anteriormente, definimos explicitamente a capacidade de referência para val:

    class Foo[A: Any val]

      Se a capacidade for deixada fora do parâmetro tipo, então a classe ou função genérica pode aceitar qualquer capacidade de referência. Isto pareceria:

    class Foo[A: Any]

      Ela pode ser encurtada porque Any é a restrição padrão, deixando-nos com ela:

    class Foo[A]

      Este é o aspecto do exemplo mostrado anteriormente, mas com qualquer capacidade de referência aceita:

    // Note - this won't compile
    class Foo[A]
      var _c: A
    
      new create(c: A) =>
        _c = c
    
      fun get(): A => _c
    
      fun ref set(c: A) => _c = c
    
    actor Main
      new create(env:Env) =>
        let a = Foo[U32](42)
        env.out.print(a.get().string())
        a.set(21)
        env.out.print(a.get().string())

      Infelizmente, isto não é compilado. Para que uma classe genérica possa ser compilada, ela deve ser compilável para todos os tipos possíveis e capacidades de referência que satisfaçam as restrições no parâmetro de tipo. Neste caso, isso é qualquer tipo com qualquer capacidade de referência. A classe funciona para a capacidade de referência específica da val, como vimos anteriormente, mas como funciona para a ref? Vamos expandi-la e ver:

    // Nota - Isso não compila
    class Foo
      var _c: String ref
    
      new create(c: String ref) =>
        _c = c
    
      fun get(): String ref => _c
    
      fun ref set(c: String ref) => _c = c
    
    actor Main
      new create(env:Env) =>
        let a = Foo(recover ref String end)
        env.out.print(a.get().string())
        a.set(recover ref String end)
        env.out.print(a.get().string())

      Isto não é compilado. O compilador reclama que o get() não devolve realmente uma ref. String, mas esta->String ref. Obviamente precisamos simplesmente mudar a assinatura do tipo para corrigir isto, mas o que está acontecendo aqui? este->String ref. é um tipo de seta. Um tipo de seta com "this->" diz para usar a capacidade do receptor real (ref em nosso caso), não a capacidade do método (que, por padrão, é encaixotado aqui). De acordo com a adaptação do ponto de vista, isto será ref->ref que é ref. Sem este tipo de seta, veríamos apenas o campo _c como caixa porque estamos em um método de caixa. Portanto, vamos aplicar o que acabamos de aprender:

    class Foo
      var _c: String ref
    
      new create(c: String ref) =>
        _c = c
    
      fun get(): this->String ref => _c
    
      fun ref set(c: String ref) => _c = c
    
    actor Main
      new create(env:Env) =>
        let a = Foo(recover ref String end)
        env.out.print(a.get().string())
        a.set(recover ref String end)
        env.out.print(a.get().string())

      Isso compila e funciona, portanto a ref é válida agora. O verdadeiro teste, porém, é o iso. Vamos converter a classe em iso e caminhar através do que é necessário para que seja compilada. Em seguida, revisitaremos nossa classe genérica para que ela funcione:

    Uma classe específica de Iso

    // Nota - Isso não compila
    class Foo
      var _c: String iso
    
      new create(c: String iso) =>
        _c = c
    
      fun get(): this->String iso => _c
    
      fun ref set(c: String iso) => _c = c
    
    actor Main
      new create(env:Env) =>
        let a = Foo(recover iso String end)
        env.out.print(a.get().string())
        a.set(recover iso String end)
        env.out.print(a.get().string())

      Isto não é compilado. O primeiro erro é:

    main.pony:5:8: right side must be a subtype of left side
        _c = c
           ^
        Info:
        main.pony:4:17: String iso! is not a subtype of String iso: iso! is not a subtype of iso
          new create(c: String iso) =>
                    ^

      O erro está nos dizendo que estamos aliasing the String iso - O ! in iso! significa que é um alias de uma iso existente. Olhando para o código mostra o problema:

    new create(c: String iso) =>
      _c = c

      Temos c como uma iso e estamos tentando atribuí-la a _c. Isto cria dois pseudônimos para o mesmo objeto, algo que a iso não permite. Para fixá-lo para o caso iso, temos que consumir o parâmetro. O construtor correto deve ser:

    new create(c: String iso) =>
      _c = consume c

      Existe uma questão semelhante com o método de conjunto. Aqui também precisamos consumir a variável c que é passada para dentro:

    fun set(c: String iso) => _c = consume c

      Agora temos uma versão de Foo que está funcionando corretamente para iso. Observe como a aplicação do tipo seta ao método get também funciona para a iso. Mas aqui o resultado é diferente, aplicando a adaptação do ponto de vista que obtemos do ref->iso (com ref sendo a capacidade do receptor, o objeto Foo referenciado por a) a iso. Através da magia da recuperação automática do receptor, podemos chamar o método de string sobre ele:

    class Foo
      var _c: String iso
    
      new create(c: String iso) =>
        _c = consume c
    
      fun get(): this->String iso => _c
    
      fun ref set(c: String iso) => _c = consume c
    
    actor Main
      new create(env:Env) =>
        let a = Foo(recover iso String end)
        env.out.print(a.get().string())
        a.set(recover iso String end)
        env.out.print(a.get().string())

      Uma classe genérica de capacidade

        Agora que temos iso funcionando, sabemos como escrever uma classe genérica que funciona para iso e funcionará também para outras capacidades:

      class Foo[A]
        var _c: A
      
        new create(c: A) =>
          _c = consume c
      
        fun get(): this->A => _c
      
        fun ref set(c: A) => _c = consume c
      
      actor Main
        new create(env:Env) =>
          let a = Foo[String iso]("Hello".clone())
          env.out.print(a.get().string())
      
          let b = Foo[String ref](recover ref "World".clone() end)
          env.out.print(b.get().string())
      
          let c = Foo[U8](42)
          env.out.print(c.get().string())

        É muito trabalho conseguir uma classe ou método genérico para trabalhar em todos os tipos de capacidade, em particular para iso. Há maneiras de restringir o genérico a subconjuntos de capacidades e esse é o tópico da próxima seção.