<- function(...) {
super_produit <- list(...)
dots Reduce(`*`, dots)
}<- function(n) {
fact do.call(super_produit, as.list(1L:n))
}fact(10)
[1] 3628800
En R, on définit une fonction de cette manière :
La fonction super_calcul
a plusieurs paramètres : a
, b
et multiplication
. On donne à ces paramètres des valeurs qu’on appelle arguments. Toutefois, dans le langage courant, et peut-être dans cette formation si je ne fais pas trop attention, on utilise parfois indistinctement les deux mots.
On appelle une fonction de cette manière :
Les arguments s’évaluent dans l’ordre qu’ils sont stipulés lors de la définition, on peut donc écrire de manière équivalente :
On remarque que dans la définition de fonction, on avait écrit multiplication = TRUE
. Cela correspond à un argument par défaut. Si on omet de lui donner une valeur, sa valeur par défaut sera TRUE
comme on peut le vérifier ci-dessous.
Si la valeur que l’on veut utilise est différente de l’argument par défaut, on est obligé de la saisir.
On peut, lors de l’appel, nommer explicitement certains arguments et pas d’autres. Si on procède ainsi, les arguments non-nommés sont attribués de gauche à droite, parmi ceux qui ne sont pas nommés. Ainsi, tous les appels ci-dessous sont équivalents.
Si un paramètre n’a pas de valeur par défaut, une valeur doit obligatoirement être assignée lors de l’appel de fonction. C’est pourquoi le code ci-dessous renvoie une erreur.
La dernière remarque n’est pas complètement vraie. Du vieux code persiste dans R, renseignant des arguments par défaut dans le code même de la fonction, à l’aide de if (missing(argument)) {}
. Ceci est toutefois, en général, considéré comme une mauvaise pratique.
On vient de constater de manière implicite un aspect de R : par défaut, la valeur retournée par une fonction est la dernière valeur évaluée lors de son exécution (qui dépend éventuellement de structures telles que if () else {}
). Une manière explicite de renvoyer une valeur de retour est d’invoquer return()
. Appeler return()
met fin à l’exécution de la fonction. Tout ce qui vient après n’est jamais rappelé.
calcula()
renvoie 2L, tandis que calculb()
renvoie 3L. On aurait pu également constater que, dans calcula
, si on remplace 3L par stop()
, le stop n’empêche pas la fonction de retourner convenablement. En effet, il arrive après le return.
Lorsque le nombre de paramètres d’une fonction peut être variable, on peut utiliser les ...
qu’on appelle “dots”. list(...)
permet de retrouver ces arguments sous forme de liste dans le corps de la fonction.
Dans le code précédent, on a pu définir une fonction de somme sans figer à l’avance le nombre de termes de la somme.
On a utilisé dans super_somme
une fonction particulière, Reduce
, qui est typique des langages fonctionnels. L’idée est ici que l’on calcule 2 + 3 + 7 + 1
. On verra plus en détail ce type d’opérations dans la partie dédiée au paradigme fonctionnel.
Dans le cas présent, sum
existe déjà et peut déjà s’appliquer à un vecteur. On n’a écrit super_somme
qu’à fin d’illustration.
Imaginons que l’on veuille appeler une fonction ...
sur une liste. On souhaite que chaque élément de la liste devienne un argument. On peut par exemple faire, sur notre fonction super_somme
.
La fonction do.call
est en réalité plus générale que cela. Elle permet également d’utiliser des arguments nommés, comme on peut le voir dans l’exemple suivant :
Les fonctions en R peuvent tout-à-fait être récursives. Par exemple, même si factorial()
existe déjà dans R, on pourrait très bien la redéfinir via :
R permet également une petite astuce pour éviter d’avoir à utiliser le nom d’une fonction dans son propre corps (cela permet de pouvoir renommer la fonction à un seul endroit). Le code précédent est équivalent à :
Depuis R 4.4.0 sortie fin avril 2024 (pas avant !), on peut utiliser la fonction Tailcall
pour faire des récursivités terminales ; qui sont moins pratiques à lire mais plus efficaces. L’interface n’est pas encore figée mais cela ressemble aujourd’hui à :
Question 1
Ecrire une fonction super_produit <- function(...)
qui calcule le produit de tous les arguments sous les dots
, et l’utiliser pour écrire une nouvelle version de la fonction fact
.
<- function(...) {
super_produit <- list(...)
dots Reduce(`*`, dots)
}<- function(n) {
fact do.call(super_produit, as.list(1L:n))
}fact(10)
[1] 3628800
Question 2
Écrire une fonction dots usine_a_gaz
qui renvoie :
NULL
s’il n’y a aucun argument."bonjour"
si on lui soumet en unique argument un vecteur de type character (is.character
permet de faire le test).is.numeric
."échec"
dans tous les autres cas.On pourra éventuellement s’aider des petites astuces, dont ...length()
, lisibles dans la page d’aide des ...
. Celle-ci est accessible en tapant help(dots)
.
<- function(...) {
usine_a_gaz <- ...length()
longueur if (longueur == 0) NULL
else if (longueur >= 2L) longueur
else if (is.character(..1)) "bonjour"
else if (is.numeric(..1)) sum(..1)
else "échec"
}usine_a_gaz()
NULL
usine_a_gaz(1,3)
[1] 2
usine_a_gaz(c("a","b"))
[1] "bonjour"
usine_a_gaz(c(1, 2))
[1] 3
usine_a_gaz(c(1L, 2L))
[1] 3
usine_a_gaz(list())
[1] "échec"
On peut également utiliser des return()
, auquel cas les else
deviennent inutiles puisque return
fait directement retourner la fonction.
<- function(...) {
usine_a_gaz <- ...length()
longueur if (longueur == 0) return(NULL)
if (longueur >= 2L) return(longueur)
if (is.character(..1)) return("bonjour")
if (is.numeric(..1)) return(sum(..1))
"échec"
}usine_a_gaz()
NULL
usine_a_gaz(1,3)
[1] 2
usine_a_gaz(c("a","b"))
[1] "bonjour"
usine_a_gaz(c(1, 2))
[1] 3
usine_a_gaz(c(1L, 2L))
[1] 3
usine_a_gaz(list())
[1] "échec"
Question 3
Réécrire la fonction super_somme
en utilisant une syntaxe récursive. On pourra, par exemple, s’aider de la fonction head
, qui permet de retourner une sous-liste contenant les n premiers éléments d’une liste.
<- function(...) {
super_somme <- list(...)
dots <- length(dots)
longueur if (longueur == 0L) 0
else dots[[longueur]] + do.call(
# Rajouter quelque chose dans le do.call qui rappelle super_somme sur les longueur - 1L premiers éléments
)
}super_somme(2, 3, 7, 1)
<- function(...) {
super_somme <- list(...)
dots <- length(dots)
longueur if (longueur == 0L) 0
else dots[[longueur]] + do.call(super_somme, head(dots, longueur - 1L))
}super_somme(2, 3, 7, 1)
[1] 13
...length()
en tant que substitut pour length(list(...))
.Recall
en tant que substitut pour super_somme
dans le do.call
.