Variables globales ? Où ça ?
Version imprimable
Variables globales ? Où ça ?
Ben, je n'ai pas vu un seul var/let, pour déclarer tes variables...
Pendant ce temps-là, j'ai encore réussi à raccourcir ma solution à 38 chars, avec un var ^^
Rha j'ai un truc qui fait 40 caractères mais qui ne marche pas avec arguments…
39 :D
Code:f=m=>(...a)=>b=>m.bind(...a).apply(8,b)
Désolé je ne trouve pas mieux que 39, je vais faire une pause et y revenir peut-être ce soir.
Au moins, ce défi m'a aidé à mieux comprendre l'opérateur ... que je maîtrisais mal :)
Et ton f alors? ^^
Je pense que c'est la meilleure, jusqu'ici... concernant la mienne, j'sais pas trop ce que je testais mais j'crois que j'me suis emmêlé les pinceaux dans mes versions... car au reboot du pc, l'une marchait avec array/arguments, l'autre avec des params bindés... :koi:
Pour parfaire la tienne, mais qui ne peut, non-plus, respecter le "no globals", sans dépasser la limite de taille en même temps, je propose celle-ci :
f=m=>(i,...a)=>b=>m.apply(i,a.concat(b))
Parfaire ? Ta version est plus longue, *peut mieux faire* :mouarf2: et on s'en fiche du f, c'est juste le nom donné à la fonction pour tester les exemples. Ça devrait même pas compter dans le décompte :langue:
Pour aller plus loin dans la recette du curry :
si F=>F.bind.bind(F) est la recette du curry en 2 temps, c'est à dire curry2(func)(...args1)(...args2) === func.call(...args1, ...args2)
- Quelle est la solution la plus courte pour le curry en 3 temps, c'est-à-dire curry3(func)(...args1)(...args2)(..args3) === func.call(...args1, ...args2, ..args3) ?
curry3("".substring)("hello world!")(6)(11) === "world";
- Quelle est le solution la plus courte pour le curry à n temps, c'est-à-dire curryN(n)(func)(...args1)(...args2) (...) (...argsN) === func.call(...args1, ..., ...argsN)
curryN(1)("".substring)("hello world", 6, 11) === "world";
curryN(2)("".substring)("hello world", 6)(11) === "world";
curryN(2)("".substring)("hello world")(6, 11) === "world";
curryN(3)("".substring)("hello world!")(6)(11) === "world";
curryN(3)("".substring)("hello world!",6)()(11) === "world";
curry curry ! curryyy ! :lahola:
Hum, oui, il y avait visiblement un truc que j'avais pas saisi avec le spread... merci aussi et bien joué :D
Vous oubliez un détail : ni le spread ni concat ne marchent avec un objet arguments.
C'est à cause de ça que j'ai pas pu me débarrasser de mon apply ;)Code:
1
2
3
4
5 (function () { var result = substringFromHello(arguments); console.log(result); console.assert("world" === result, "failed"); }(6, 11));
Si on oublie cette contrainte du arguments on peut descendre à 33 en reprenant la solution de Sylvain :
En effet rien ne nous oblige à distinguer le premier argument (anciennement c) du reste des arguments (r).Code:f=F=>(...p)=>a=>F.call(...p,...a)
Avec le test case de mon précédent post, j'obtiens cette erreur sous Firefox :
Il semblerait que le spread appelle en interne le même mécanisme d'itération que for..of. Dans SpiderMonkey tout du moins.Citation:
TypeError: a[Symbol.iterator] is not a function
Avec Chrome… Pas d'erreur. Ok, si je suis le seul ici à utiliser Firefox, je comprends mieux pourquoi personne n'avait remarqué ce problème avec arguments avant moi. Il s'agit d'un bug de Firefox.
Note au passage, les erreurs dûes au spread sous V8 sont plus difficiles à comprendre :
Code:(function (nonIterable) { console.log(...nonIterable); }(42))
En attendant, on est sur un double score de 33 (V8) / 39 (SpiderMonkey).Citation:
Uncaught TypeError: undefined is not a function(…)
Voilà voilà. Désolé. Vraiment.
Là ça me fait réfléchir. Qu'est-ce qui se passerait si on ne donnait pas le nombre de temps (l'arité) en paramètre ? A priori on ne saurait pas à quel moment renvoyer une fonction ou la valeur finale. Mais en utilisant un Proxy, je pense qu'on peut renvoyer un objet qui apparaît comme une valeur mais qui peut également se comporter comme une fonction.
Mais bref ^^ Je vais déjà tâcher de répondre à tes deux questions.
Ah ben oui c'est évident comme optimisation, je ne sais pas comment j'ai pu louper ça... Parfois on ne voit pas les choses qui sautent aux yeux.
L'opérateur spread gère bien les arguments de fonction, c'est d'ailleurs l'exemple de démo le plus couramment utilisé. Mais son implémentation est encore balbutiante sur les navigateurs. Mieux vaut tester le code transpilé par Babel.
Ce qui serait à la fois très très cool et très très idiot :mrgreen: Pour un usage "pratique" d'une telle fonction, l'arité est toujours prédéfinie. Sinon on ne sait plus ce qu'on manipule.
J'ai choisi de passer l'arité en argument initial parce que ça simplifie de mon point de vue le code la fonction derrière. On supposera qu'il s'agit toujours d'un nombre entier supérieur ou égal à 1.
Voilà ma solution :
Non minifié
curryN minifié:Code:
1
2
3
4
5
6
7
8
9
10 var curry3= function(F){ var n = 3; var argsStack = []; return function R(...args){ argsStack.push(...args); return --n ? R : F.call(...argsStack) } }
Code:var curryN = n=>(F,s=[],R=(...a)=>(s.push(...a),--n?R:F.call(...s)))=>R
Et une version encore plus récursive, mais pas forcément plus courte:
Code:var curryN = n=>(f,...s)=>(...a)=>--n?curryN(n)(f,...s,...a):f.call(...s,...a)
J'ai trouvé des trucs pour curryN. Curieusement, j'ai deux solutions légèrement différentes mais qui font la même longueur :
Ce qui montre que ce n'est pas forcément gagnant de remplacer function par var.Code:
1
2 n=>f=>{var r=(n,...a)=>n?(...b)=>r(--n,...a,...b):f.call(...a);return r(n)} n=>f=>function r(n,...a){return n?(...b)=>r(--n,...a,...b):f.call(...a)}(n)
Je n'ai pas pensé à utiliser les valeurs d'arguments par défaut du coup on peut peut-être trouver plus court en mélangeant nos deux versions.
Au fait j'ai essayé Babel, c'est impressionnant :) Mais il doit y avoir des trucs qu'il ne peut pas faire, non ?
J'pense avoir mieux, les gars...
Qu'en pensez-vous?Code:var c,curryN=c=i=>(f,...a)=>i?c(--i).bind(i,f,...a):f.call(...a)
En non-minifié :
Code:
1
2
3
4
5
6
7
8
9 var curryN; curryN = function (arity) { return function (fn, ...args) { return arity ? curryN(--arity).bind(arity, fn, ...args) : fn.call(...args); }; };
EDIT:
En bonus, par rapport aux vôtres, la mienne marche aussi avec arity = 0
Vous aimez l'épicé? J'crois que ça va vous plaire... ^^
Cette version a la même longueur que celle de SylvainPV... mais elle est réutilisable et supporte des usages supplémentaires :
DémoCode:var c,curryN=c=(...a)=>((i,f,...b)=>i--?c(i,f,...b):f.call(...b)).bind(a,...a)
Curry thaï? :lol:
EDIT :
Même si, perso, je trouverais plus logique et plus élégant que le arity = 0 ressemble à ceci :
Ce qui donnerait, en version courte ne permettant pas d'aliaser curryN :Code:curryN(0, "".substring, "hello world!", 6, 11)
DémoCode:var c,curryN=c=(i,f,...a)=>i?c.bind(i,--i,...(f?[f,...a]:a)):f.call(...a)
Ou en version longue, permettant d'aliaser curryN :
DémoCode:var c,curryN=c=(i,f,...a)=>i<1?f.call(...a):c.bind(i,...(i?[--i]:[]),...(f?[f,...a]:a))
Ben si l'arité est nulle par définition c'est plus du Curry vu qu'on ne retourne plus de fonction. Et ça ne présente pas plus d'un intérêt qu'un f.call ^^
Pas bête le bind, j'oublie toujours que les arguments bound sont concaténés et non remplacés. Du coup je pense que ta solution curryN = i=>(f,...a)=>i?curryN(i-1).bind(f,f,...a):f.call(...a) est la meilleure.
Difficile de savoir quoi mettre comme premier argument du bind, le contexte des fonctions curry n'a en principe pas d'importance vu qu'on finit par un .call.
@Lcf: pas compris ce que tu voulais dire par "aliaser"
C'était un peu l'idée, que l'on puisse aussi s'en servir comme d'un .call... avec une légère différence tout de même, c'est que dans l'usage avec une méthode d'un objet, c'est qu'on peut éviter l'accès à la méthode (en tant que telle) et/ou de devoir connaître l'objet en question. ;)
La plus courte, oui... mais à mon sens, pas la meilleure, perso, je choisirais tout de même une des versions réutilisables...
En effet, si on ne peut se resservir d'une des fonctions résultantes, connaissant déjà les premiers arguments spécifiés, pour faire un second appel, on n'en utilise son potentiel qu'à moitié.
Pour ça que j'ai mis le arity, mais j'aurais pu mettre n'importe quoi, tant que ça fait sauter le param du contexte.
Bah, faire une copie de curryN, quoi...
Code:
1
2
3 var alias; alias = curryN();
Je ne comprends pas pourquoi tu dis que cette version n'est pas réutilisable. Toutes les fonctions sont locales donc regenérées.
Dans tes dernières versions tu as réduit de 1 le décompte des appels en ajoutant l'arité avec le reste des paramètres, ce qui n'a pas beaucoup de sens selon moi. En repartant des tests d'origine, ça fonctionne très bien et c'est réutilisable : démo
Oui, juste...
En revanche, dans mes dernières versions, l'arité est réduite de 1, pour permettre curryN(0, "".substring, "hello world!", 6, 11)Car bon, lui disant qu'il y a 0 appels, avoir un appel après, spécifiant les params, j'trouvais ça étrange...
Après, c'est juste un point de vue. ;)
Moi c'est le fait que tu veuilles un curry à arité zéro que je trouve étrange :D En fait, c'est juste notre manière de compter qui diffère. Je ne compte pas les appels pour passer l'arité et la fonction à curryfier. Un curry à deux temps transforme une fonction F en une fonction G où il faut passer les arguments en deux appels : F(x,y) == G(x)(y) ; du coup avec cette définition, un curry zéro signifierait qu'on ne passe pas du tout les paramètres, ce qui est un peu bizarre tu en conviendras ^^
Oui, oui, je comprends bien... pour ça que j'm'en suis d'abord tenu à tes contraintes...
Ensuite, repensant à la suggestion de Watilin, j'me suis dit qu'on devait pouvoir trouver un compromis entre les deux et j'en ai fait ma propre version. :)
Et tu as bien trouvé puisqu'on avait tous les deux oublié le bind ;)
J'avais pensé à un moyen de ne pas avoir à préciser l'arité, qui était d'appeler la fonction sans aucun argument pour savoir quand "fermer" le currying et appeler la fonction. Mais ça ne marche pas avec la solution à base de bind, et d'une manière générale je ne suis pas sûr que ce soit une bonne idée d'avoir une arité indéfinie.
:cfou: :bravo: à tous !
Il m'a fallu du temps pour tester, disséquer et comprendre (j'espère :aie:) tous vos codes. Je ne sais pas si c'est l'âge ou la maladie, mais j'ai de la peine à suivre.
Mention très bien pour @SylvainPV :king:
Pour comprendre la recette du curry, j'ai dû l'écrire de cette manière :
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 <script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script> <script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script> <script type="module"> let curryN = ( n ) => { return ( F, s = [], R = ( ...args ) => { console.log( F, s, args ); return ( s.push( ...args ), --n ? R : F.call( ...s ) ); } ) => { return R; } }; console.log( curryN( 3 )( "".substring )( "hello world!" )( 6 )( 11 ) === "world" ); /* substring() { [native code] } [] ["hello world!"] substring() { [native code] } ["hello world!"] [6] substring() { [native code] } ["hello world!", 6] [11] true */ </script>
Pour autant que je puisse en juger, on a plusieurs très bons golfeurs ici. Ce n'est pas un sport très courant donc je suis content que ce topic ait autant de succès et qu'il soit up de temps à autre :) Je ne m'attendais pas à ça lorsque je l'ai ouvert.
Pas d'inquiétudes si tu n'arrives pas à suivre, c'est tout à fait normal :mrgreen: La minification du code complique énormément sa compréhension, le seul moyen de comprendre est d'indenter et de renommer les variables. Moi-même je ne comprends plus mes propres codes postés ici l'année dernière.
En parlant de golf, je suis tombé par hasard là-dessus il y a quelques jours, et si ça peut te rassurer danielhagnoul, nous avons l'air de petits joueurs à l'esprit lamentablement sain à côté de ces gens :mrgreen:
N'empêche, j'préfère tout de même mon chain, qui porte certainement d'autres noms, au curry...
Ou, pour Daniel :troll: :Code:var c,chain=c=(i,a,r)=>(f,...p)=>(r=>a>1?c(r,a-1):r)(f.call(i,...p))
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 var chain; chain = function (instance, arity) { return function (fct, ...args) { var result; result = fct.call(instance, ...args); return arity > 1 ? chain(result, arity - 1) : result; }; };
Je ne comprends pas pourquoi tu utilises un argument d'arité avec une fonction de chaînage. La curryfication permet de produire des fonctions intermédiaires réutilisables, mais dans le cas du chaînage de méthodes, ça n'a aucun intérêt vu que les exécutions des fonctions auront toujours le même résultat. Autant garder directement une référence au résultat intermédiaire. Aussi, pourquoi invoquer toutes les fonctions sur la même instance, d'autant qu'elle est cachée en fermeture ? Typiquement, le chaînage de méthodes est très utilisé en prog fonctionnelle où l'on aime travailler avec des données immuables. Avec cette fonction, c'est impossible et on ne peut ni filtrer, ni étendre, ni modifier le contexte comme on le ferait avec jQuery, underscore ou même de simples Array ou Promise.
Généralement, le chaînage de méthodes suit plutôt cette approche:
tiré de http://leaverou.github.io/chainvas/ de Lea Verou ; c'est la même approche utilisée par jQuery, underscore et bien d'autres bibliothèquesCode:
1
2
3
4
5
6
7 // Make a method chainable chainable: function(method) { return function() { var ret = method.apply(this, arguments) return ret === undefined? this : ret; } }
L'arité sert, comme pour le curryN, à savoir quand retourner la valeur finale, c'est tout, sinon, on retourne une fonction.
Tu as raison, on peut aisément s'en débarrasser, en fait...
Soit :Code:var c,chain=c=i=>(f,...p)=>f?c(f.call(i,...p)):i;
Il y a tout de même deux différences avec ton code, il n'y a pas de référence à this, dans le mien... je n'aime pas les this, ça se minifie pas et ça marche pas avec les arrow-functions.Code:
1
2
3
4
5
6
7
8
9 var chain; chain = function (instance) { return function (fct, ...args) { return fct ? chain(fct.call(instance, ...args)) : instance; }; };
Par contre, si, on peut tout à fait filtrer avec et, pour des traitements récurrents, comme une réponse en AJAX, sur un chat, par exemple, c'est super pratique, et sans besoin de binding...
Exemple
Sais pas vous, mais j'adore sa lisibilité :P
Oui c'est déjà mieux comme ça. Mais ça fonctionne uniquement parce que tu utilises déjà des API chaînables: replace, substring, split etc... Si tu utilises des méthodes non chaînables comme celle du prototype Date par exemple, tu ne pourras pas réutiliser la valeur de retour de la même manière. C'est justement le but de la lib de Lea: transformer les fonctions mutables en fonctions chaînables. Elle l'a appliqué sur les API du DOM et des Canvas à cet effet.
En effet c'est lisible, mais c'est plutôt rigide : en fait tu mets en variables des appels de fonctions avec un certain nombre de paramètres « pré-passés ». Donc au final tu ne fais qu'augmenter la taille du code pour avoir un syntaxe d'appel différente à la fin. Ou alors j'ai raté quelque chose.
Ce qui est intéressant, c'est qu'on voit bien ici une conséquence de la venue de l'opérateur spread : il a rendu ténue la frontière entre les tableaux et les listes d'arguments.
Moui mais, bon, moi, ce que j'y aime bien, c'est la séparation entre les instructions de traitement... et le traitement, lui-même...
Concernant le poids du code, je pense qu'au contraire, ça peut sacrément le réduire, de par la réutilisation des instructions de traitement.
En effet, sur des appels directs de méthodes, tu ne sais quasiment pas les minifier, à moins de prévoir des fonctions qui seront réutilisables, pour chaque combinaison de cas.
Moi, le spread, j'adore... en revanche, je trouve le rest parameter un peu trop limité...
À mon sens, on devrait pouvoir le placer n'importe où, dans la liste d'arguments... avec pour seule règle, son unicité.
Genre :
Allez, pour le fun, j'ai fait une grosse révision de mon chain, il tient malheureusement plus dans un tweet mais, bon...Code:
1
2
3 void function (a, ...b, c) { // code }(1, 2, 3, 4);
DémoCode:var c,chain=c=i=>(f,...a)=>((t=typeof f)=>t==='function'?(f.call(i,...a),c(i)):t==='string'?(i[f](...a),c(i)):t==='object'?(a=[...f,...a],c(a.shift().call(i,...a))):i)();
Et la version optimisée (en perfs) :
DémoCode:var chain=c=>(c=((e,t)=>i=>(f,...a)=>(t=typeof f,t in e)?e[t](i,f,a):i)({function:(i,f,a)=>(f.call(i,...a),c(i)),string:(i,f,a)=>(i[f](...a),c(i)),object:(i,f,a)=>c(f.shift().call(i,...f,...a))}))()
Coucou, ça fait un an, allez, nouveau défi ! :D
L’objectif est, étant donné un élément DOM positionné, de connaître sa position verticale « réelle », c’est-à-dire la somme de son offsetTop et de tous ceux de ses offsetParents. Nous aurons donc une fonction qui prend en paramètre un élément DOM et qui retourne un entier. Syntaxe ES2015 autorisée, évidemment.
J’ai une solution qui fait exactement 100 caractères :)
Code:getOffsetTop = e=>scrollY+e.getBoundingClientRect().top
C’est trop simple ! :P
Moi ce qui m’intéressait c’est le processus de remontée de parent en parent. Cette conversation avec patricktoulon m’a rappelé que ce n’est pas quelque chose de complètement trivial. Et encore moins quand on est limité en espace…
Mais en tout cas, merci pour ta solution que je ne connaissais pas ;)
Je salut le retour...
en 3
:dehors:
Concernant ta discussion avec patrick, je pense immédiatement à la méthode closest de DOM4 : https://dom.spec.whatwg.org/#dom-element-closest
Le polyfill est assez simple: https://plainjs.com/javascript/trave...y-selector-39/
Ok mais alors il faut compter les clics ! (Edit: je répondais à NoSmoking)
Bon hey, je me rends compte que j’avais la tête dans le truc et que je n’ai pas vu la solution la plus simple. Sinon je descends à 60 caractères.
Alors je vais rajouter une contrainte : il faut utiliser yield ;)