J'analyse votre code dans la section Analyser votre code . Avant cela, je présente quelques sections amusantes de matériel bonus.
Une doublure Une lettre 1
say e; # 2.718281828459045
Cliquez sur le lien ci-dessus pour voir l'article extraordinaire de Damian Conway sur l'informatique e
à Raku.
L'article est très amusant (après tout, c'est Damian). C'est une discussion très compréhensible de l'informatique e
. Et c'est un hommage à la réincarnation par Raku du bicarbonate de la philosophie TIMTOWTDI adoptée par Larry Wall. 3
En entrée, voici une citation d'environ la moitié de l'article:
Étant donné que ces méthodes efficaces fonctionnent toutes de la même manière - en sommant (un sous-ensemble initial de) une série infinie de termes - il serait peut-être préférable que nous ayons une fonction pour le faire pour nous. Et il serait certainement préférable que la fonction puisse déterminer par elle-même exactement la quantité de ce sous-ensemble initial de la série qu'elle doit réellement inclure afin de produire une réponse précise ... plutôt que de nous obliger à peigner manuellement les résultats de plusieurs essais pour découvrir cela.
Et, comme souvent à Raku, il est étonnamment facile de construire exactement ce dont nous avons besoin:
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
Analyser votre code
Voici la première ligne, générant la série:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
La fermeture ( { code goes here }
) calcule un terme. Une fermeture a une signature, implicite ou explicite, qui détermine le nombre d'arguments qu'elle acceptera. Dans ce cas, il n'y a pas de signature explicite. L'utilisation de $_
( la variable "topic" ) entraîne une signature implicite qui nécessite un argument lié à $_
.
L'opérateur de séquence ( ...
) appelle à plusieurs reprises la fermeture sur sa gauche, en passant le terme précédent comme argument de fermeture, pour construire paresseusement une série de termes jusqu'à l'extrémité à sa droite, qui dans ce cas est un *
raccourci pour Inf
aka infini.
Le sujet du premier appel à la clôture est 1
. La fermeture calcule et renvoie donc 1 / (1 * 1)
les deux premiers termes de la série as 1, 1/1
.
Le sujet du deuxième appel est la valeur du précédent 1/1
, c'est- 1
à- dire à nouveau. Ainsi, la fermeture calcule et retourne 1 / (1 * 2)
, étendant la série à 1, 1/1, 1/2
. Tout a l'air bien.
La prochaine fermeture calcule 1 / (1/2 * 3)
ce qui est 0.666667
. Ce terme devrait être 1 / (1 * 2 * 3)
. Oops.
Faire correspondre votre code à la formule
Votre code est censé correspondre à la formule:
Dans cette formule, chaque terme est calculé en fonction de sa position dans la série. Le k ème terme de la série (où k = 0 pour le premier 1
) est juste réciproque factoriel k .
(Donc, cela n'a rien à voir avec la valeur du terme précédent. Ainsi $_
, qui reçoit la valeur du terme précédent, ne doit pas être utilisé dans la fermeture.)
Créons un opérateur de suffixe factoriel:
sub postfix:<!> (\k) { [×] 1 .. k }
( ×
est un opérateur de multiplication d'infixes, un alias Unicode plus joli de l'infixe ASCII habituel *
.)
C'est un raccourci pour:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(J'ai utilisé une notation pseudo métasyntaxique à l'intérieur des accolades pour indiquer l'idée d'ajouter ou de soustraire autant de termes que nécessaire.
Plus généralement, mettre un opérateur infixe op
entre crochets au début d'une expression forme un opérateur préfixe composite qui est l'équivalent de reduce with => &[op],
. Voir Méta-opérateur de réduction pour plus d'informations.
Nous pouvons maintenant réécrire la fermeture pour utiliser le nouvel opérateur factoriel postfix:
my @e = 1, { state $a=1; 1 / $a++! } ... *;
Bingo. Cela produit la bonne série.
... jusqu'à ce que ce ne soit pas le cas, pour une raison différente. Le problème suivant est la précision numérique. Mais examinons cela dans la section suivante.
Un liner dérivé de votre code
Peut-être compresser les trois lignes en une seule:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
s'applique au sujet, qui est défini par le given
. ( ^10
est un raccourci pour 0..9
, donc le code ci-dessus calcule la somme des dix premiers termes de la série.)
J'ai éliminé $a
de la fermeture le calcul du prochain mandat. Un solitaire $
est identique à (state $)
un scalaire à état anonynique. J'ai fait un pré-incrément au lieu d'un post-incrément pour obtenir le même effet que vous avez fait en l'initialisant $a
à 1
.
Il nous reste maintenant le dernier (gros!) Problème, que vous avez signalé dans un commentaire ci-dessous.
À condition qu'aucun de ses opérandes ne soit un Num
(un flottant, et donc approximatif), l' /
opérateur retourne normalement un 100% précis Rat
(une précision limitée rationnelle). Mais si le dénominateur du résultat dépasse 64 bits, ce résultat est converti en un Num
- qui échange les performances contre la précision, un compromis que nous ne voulons pas faire. Nous devons en tenir compte.
Pour spécifier une précision illimitée ainsi qu'une précision de 100%, il suffit de contraindre l'opération à utiliser l' FatRat
art. Pour le faire correctement, il suffit de faire (au moins) l'un des opérandes être un FatRat
(et aucun autre être un Num
):
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
J'ai vérifié cela à 500 chiffres décimaux. Je m'attends à ce qu'il reste précis jusqu'à ce que le programme plante en raison du dépassement d'une limite de la langue Raku ou du compilateur Rakudo. (Voir ma réponse à Cannot unbox 65536 bit large bigint into native integer for some discussion of that.)
Notes de bas de page
1 Raku a quelques constantes mathématiques importantes intégrées, y compris e
, i
et pi
(et son alias π
). Ainsi, on peut écrire l'identité d'Euler dans Raku un peu comme on dirait dans les livres de mathématiques. Avec crédit à l'entrée Raku de RosettaCode pour l'identité d'Euler :
# There's an invisible character between <> and iπ character pairs!
sub infix:<> (\left, \right) is tighter(&infix:<**>) { left * right };
# Raku doesn't have built in symbolic math so use approximate equal
say e**iπ + 1 ≅ 0; # True
2 L'article de Damian est à lire absolument. Mais ce n'est qu'un des nombreux traitements admirables qui font partie des 100+ correspondances pour un google pour le "raku" euler's number "' .
3 Voir TIMTOWTDI vs TSBO-APOO-OWTDI pour l'une des vues les plus équilibrées de TIMTOWTDI écrites par un fan de python. Mais il y a des inconvénients à aller trop loin avec TIMTOWTDI. Pour refléter ce dernier "danger", la communauté Perl a inventé TIMTOWTDIBSCINABTE , humoristique, long et illisible - Il y a plus d'une façon de le faire, mais parfois la cohérence n'est pas une mauvaise chose non plus, prononcé "Tim Toady Bicarbonate". Curieusement , Larry a appliqué du bicarbonate au design de Raku et Damian l'applique à l'informatique e
dans Raku.