Restrição de assinatura em papéis no raku

8

Talvez esteja faltando alguma coisa, mas eu gostaria de saber se existe um bom motivo para esse código ser compilado

role L {
  method do-l (Int, Int --> Int ) { ... }
}

class A does L {
  method do-l (Int $a, Real $b --> Str) {
    .Str ~ ": Did you expect Int?" with $a + $b
  }
}

my $a = A.new;

say $a.do-l: 2, 3.323

Isso produzirá

5.323: Did you expect Int?

Fiquei curioso para saber se alguém conhece uma maneira de o compilador lançar pelo menos algum aviso com a assinatura implementada da função L.

margolari
fonte
2
Qual é a sua produção esperada? O que você mostrou é o que eu esperaria. A L.do-ltem um método que não corresponde, e em qualquer caso, o método de classe tem precedência, então em ambos bases, A's do-ldeve ser chamado. A adição de um Inte a Realserá a Real.
user0721090601 11/04
1
Ah, agora eu vejo. Na verdade, você está perguntando sobre o operador yada ...(é certo que, embora faça sentido no código de produção, aqui pode causar confusão)
user0721090601 11/04
Você não pode instanciar funções se um método é stub, mas não há problema em instanciar classes. `classe L {método yadda {…}}; dizer L.new.raku`
jjmerelo
2
"Você não pode instanciar funções se um método for stubbed" Os leitores podem interpretar mal esse comentário. Você não pode instanciar funções, ponto final. Eles são apenas fragmentos de uma classe. Se você tentar instanciar um papel, raku presume que você pretende instanciar uma classe vazia que desempenha esse papel e faz isso por você. Naturalmente, isso falha se a função contiver um método stubbed, porque a imposição é o objetivo e o significado de um método stubbed em uma função. Por outro lado, stubbing em uma classe significa apenas "ainda não escrevi esse código".
raiph 12/04
2
@jjmerelo Sim, é um trocadilho de tipo ("subverte ou contorna o sistema de tipos"). O que estamos falando aqui ( role foo {}.newsendo convertido automaticamente em algo assim anon class foo does role foo {}.new) é tão transparente que as pessoas podem não perceber que é um trocadilho, daí o meu comentário anterior. É bom que esse trocadilho, sozinho, permita a alternativa primária à herança e à delegação, ou seja, organizar e construir tipos usando a composição , mas, no entanto, é importante que as pessoas não percam de vista que o .newtrocadilho é totalmente .
raiph 13/04

Respostas:

6

lançar algum aviso com a assinatura implementada da função L.

Você obtém isso se você prefixar a declaração do método com multi:

role L {
  multi method do-l (Int, Int --> Int ) { ... }
}

Com isso, seu programa exibe:

===SORRY!=== Error while compiling ...
Multi method 'do-l' with signature :(A: Int $, Int $, *%_ --> Int)
must be implemented by A because it is required by a role ...

Gostaria de saber se existe um bom motivo para esse código ser compilado [sem o multi]

Eu acho que a intenção do projeto era apoiar duas noções de composição polimórfica:

  • Sem omulti , a aplicação se refere apenas à existência de um método com o nome correto; parâmetros são ignorados.

  • Com omulti , a aplicação abrange também o nome e todos os parâmetros ( ou alguns ).

Minha opinião pessoal sobre se há uma boa razão para:

  • Apoiando dois sabores do polimorfismo de método? Às vezes, impor uma adesão estrita à assinatura é útil. Às vezes atrapalha.

  • Distinguindo-os via multi? A imposição total da assinatura requer que as classes / funções de implementação tenham um método com exatamente a mesma assinatura. Mas e se uma implementação de classe / função quer lidar com um int, em vez de Intpara um parâmetro? Raku se compromete. Desde que uma classe / função de implementação tenha um método exatamente compatível, ela também pode ter variações. A maneira perfeita de transmitir isso é prefixar um método stubbed multi.

  • Tendo o padrão como polimorfismo apenas de nome? Poderíamos ter escolhido a multisemântica como padrão e fazer com que os usuários escrevessem um onlyprefixo se quisessem apenas polimorfismo de nome. Mas isso reverteria a situação usual (ou seja, ignorando os métodos stubbed). De maneira mais geral, a intenção é que o Raku forneça uma ampla variedade de restrições para seus recursos, do relaxado ao rígido, e escolha um padrão para qualquer recurso que seja julgado corretamente com base no feedback dos usuários ao longo dos anos.

E se o padrão não parecer certo? E se a gama existente de restrições não for suficiente? E se um grupo pensa que devemos ir para a esquerda e outro pensa que devemos ir para a direita?

Raku possui (imo) mecanismos de governança notáveis ​​para apoiar a evolução da linguagem orientada pelo usuário. No nível superior, existem elementos como a arquitetura da trança . No nível inferior, existem elementos como tipos versionáveis . No meio, existem elementos como o RoleToClassApplierque medeia o processo de aplicação de uma função a uma classe, que é o ponto em que um método necessário precisa ser encontrado ou a construção da classe falhará. Em resumo, se o idioma não funcionar da maneira que você deseja, incluindo restrições, você pode, pelo menos em princípio, alterá-lo da mesma maneira.

raiph
fonte
1
Uau, essa é exatamente a informação que eu estava procurando, gostaria de ter lido sobre isso nos raku docs para papéis, ou talvez tenha perdido, se não estiver lá, certamente ajudaria muitas pessoas a escrever papéis concisos, já que a princípio a visão que tem que colocar multinesse esboço não é intuitiva, mas agora, graças à sua explicação, ela realmente faz sentido.
margolari 12/04
1
@ margolari É bom saber que você recebeu as informações necessárias. Veja também github.com/Raku/doc/issues/3330
raiph
2

Suponho que aqui você esteja perguntando por que não há nenhum aviso com relação à punição. De fato, normalmente um método stubbed deve ser implementado - mas é isso.

Você pode ver como os papéis são compostos em classe aqui na fonte de Rakudo ( $yadabasicamente significa $is-stubbed):

if $yada {
    unless has_method($!target, $name, 0)
            || $!target.HOW.has_public_attribute($!target, $name) {
        my @needed;
        for @!roles {
            for nqp::hllize($_.HOW.method_table($_)) -> $m {
                if $m.key eq $name {
                    nqp::push(@needed, $_.HOW.name($_));
                }
            }
        }
        nqp::push(@stubs, nqp::hash('name', $name, 'needed', @needed, 'target', $!target));
    }
}

Então você pode ver que a lógica é apenas para ver se existe um método com o mesmo nome. Definitivamente, é possível escrever um módulo que atualize essa lógica aumentando o método apply () ou substituindo completamente a RoleToClassApplierclasse. No entanto, pode ser difícil. Por exemplo, considere:

class Letter { }
class A is Letter { } 
class B is Letter { }

role Foo {
  method foo (Letter) { ... }
}

class Bar does Foo { 
  method foo (A) { 'a' }
  method foo (B) { 'b' }
}

Devemos considerar o método stubbed implementado corretamente? Mas alguém poderia dizer mais tarde class C is Lettere de repente não foi implementado. (Obviamente, poderíamos dizer que a melhor lógica seria exigir, no mínimo, o idêntico ou o supertipo, mas isso é mais restritivo do que o necessário para linguagens dinâmicas, IMO).

Não existe, AFAICT, um método chamado de funções durante a composição que também faz referência à classe (na verdade, não existe nenhum método chamado add_method), portanto não há como escrever sua própria verificação dentro da função. (mas eu posso estar errado, talvez raiph, lizmat ou jnthn possam me corrigir).

Minha recomendação nesse caso seria, em vez de removê-la, simplesmente adicionar uma declaração de dado:

role L {
  method do-l(Int $a, Int $b --> Int) {
    die "SORRY! Classes implementing role L must support the signature (Int, Int)";
  }
}

Isso não o capturará na compilação, mas se a qualquer momento outro método Lprecisar chamar do-l(Int, Int)- mesmo que a classe de composição implemente outras assinaturas -, ela será chamada e o erro detectado rapidamente.

user0721090601
fonte
Err, eu não pensei em multi fazer uma enorme diferença como raiph encontrado - eu deveria atualizar a resposta à luz disso
user0721090601