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
.
L.do-l
tem 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
'sdo-l
deve ser chamado. A adição de umInt
e aReal
será aReal
....
(é certo que, embora faça sentido no código de produção, aqui pode causar confusão)role foo {}.new
sendo convertido automaticamente em algo assimanon 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.new
trocadilho é totalmente .Respostas:
Você obtém isso se você prefixar a declaração do método com
multi
:Com isso, seu programa exibe:
Eu acho que a intenção do projeto era apoiar duas noções de composição polimórfica:
Sem o
multi
, a aplicação se refere apenas à existência de um método com o nome correto; parâmetros são ignorados.Com o
multi
, 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 umint
, em vez deInt
para 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 stubbedmulti
.Tendo o padrão como polimorfismo apenas de nome? Poderíamos ter escolhido a
multi
semântica como padrão e fazer com que os usuários escrevessem umonly
prefixo 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
RoleToClassApplier
que 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.fonte
multi
nesse esboço não é intuitiva, mas agora, graças à sua explicação, ela realmente faz sentido.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 (
$yada
basicamente significa$is-stubbed
):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
RoleToClassApplier
classe. No entanto, pode ser difícil. Por exemplo, considere:Devemos considerar o método stubbed implementado corretamente? Mas alguém poderia dizer mais tarde
class C is Letter
e 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:
Isso não o capturará na compilação, mas se a qualquer momento outro método
L
precisar chamardo-l(Int, Int)
- mesmo que a classe de composição implemente outras assinaturas -, ela será chamada e o erro detectado rapidamente.fonte