Explication des combinateurs pour l'ouvrier

93

Qu'est-ce qu'un combinateur ??

Est-ce "une fonction ou une définition sans variables libres" (comme défini sur SO)?

Ou que diriez-vous de ceci: selon John Hughes dans son article bien connu sur les flèches, "un combinateur est une fonction qui construit des fragments de programme à partir de fragments de programme" , ce qui est avantageux parce que "... le programmeur utilisant des combinateurs construit une grande partie du programme automatiquement, plutôt que d’écrire chaque détail à la main ". Il poursuit en disant que mapet filtersont deux exemples courants de tels combinateurs.

Quelques combinateurs qui correspondent à la première définition:

Quelques combinateurs qui correspondent à la deuxième définition:

  • carte
  • filtre
  • plier / réduire (vraisemblablement)
  • l'un des >> =, compose, fmap ?????

Je ne suis pas intéressé par la première définition - cela ne m'aiderait pas à écrire un vrai programme (+1 si vous me convaincez que je me trompe). Veuillez m'aider à comprendre la deuxième définition . Je pense que mapper, filtrer et réduire sont utiles: ils me permettent de programmer à un niveau supérieur - moins d'erreurs, un code plus court et plus clair. Voici quelques-unes de mes questions spécifiques sur les combinateurs:

  1. Quels sont les autres exemples de combinateurs tels que map, filter?
  2. Quels combinateurs les langages de programmation implémentent-ils souvent?
  3. Comment les combinateurs peuvent-ils m'aider à concevoir une meilleure API?
  4. Comment concevoir des combinateurs efficaces?
  5. À quoi sont similaires les combinateurs dans un langage non fonctionnel (par exemple, Java), ou qu'utilisent ces langages à la place des combinateurs?

Mettre à jour

Grâce à @CA McCann, j'ai maintenant une meilleure compréhension des combinateurs. Mais une question reste un point de friction pour moi:

Quelle est la différence entre un programme fonctionnel écrit avec, et un autre écrit sans, une utilisation intensive de combinateurs?

Je soupçonne que la réponse est que la version lourde du combinateur est plus courte, plus claire, plus générale, mais j'apprécierais une discussion plus approfondie, si possible.

Je recherche également plus d'exemples et d'explications de combinateurs complexes (c'est-à-dire plus complexes que fold) dans les langages de programmation courants.

Matt Fenwick
la source
4
Je considère les fonctions d'ordre supérieur map / filter / fold et je les utilise tout le temps. Je n'ai toujours aucune idée de ce qu'est un combinateur.
1
@pst - J'aurais accepté il y a 5 jours, mais maintenant je ne peux pas discuter avec John Hughes
Matt Fenwick

Réponses:

93

Je ne suis pas intéressé par la première définition - cela ne m'aiderait pas à écrire un vrai programme (+1 si vous me convaincez que je me trompe). Veuillez m'aider à comprendre la deuxième définition. Je pense que mapper, filtrer et réduire sont utiles: ils me permettent de programmer à un niveau supérieur - moins d'erreurs, un code plus court et plus clair.

Les deux définitions sont fondamentalement la même chose. Le premier est basé sur la définition formelle et les exemples que vous donnez sont des combinateurs primitifs - les plus petits blocs de construction possibles. Ils peuvent vous aider à écrire un vrai programme dans la mesure où, avec eux, vous pouvez construire des combinateurs plus sophistiqués. Pensez aux combinateurs comme S et K comme le langage machine d'un hypothétique «ordinateur combinatoire». Les ordinateurs réels ne fonctionnent pas de cette façon, bien sûr, donc dans la pratique, vous aurez généralement des opérations de plus haut niveau implémentées dans les coulisses d'une autre manière, mais la base conceptuelle est toujours un outil utile pour comprendre la signification de ces niveaux supérieurs. opérations.

La deuxième définition que vous donnez est plus informelle et concerne l'utilisation de combinateurs plus sophistiqués, sous la forme de fonctions d'ordre supérieur qui combinent d'autres fonctions de diverses manières. Notez que si les blocs de construction de base sont les combinateurs primitifs ci-dessus, tout ce qui est construit à partir d'eux est une fonction d'ordre supérieur et un combinateur également. Dans un langage où d'autres primitives existent, cependant, vous avez une distinction entre les choses qui sont ou ne sont pas des fonctions, auquel cas un combinateur est généralement défini comme une fonction qui manipule d'autres fonctions de manière générale, plutôt que d'opérer sur des non- faire fonctionner les choses directement.

Quels sont les autres exemples de combinateurs tels que map, filter?

Beaucoup trop pour les énumérer! Les deux transforment une fonction qui décrit le comportement sur une valeur unique en une fonction qui décrit le comportement sur une collection entière. Vous pouvez également avoir des fonctions qui ne transforment que d' autres fonctions, telles que leur composition de bout en bout ou la division et la recombinaison d'arguments. Vous pouvez avoir des combinateurs qui transforment les opérations en une seule étape en opérations récursives qui produisent ou consomment des collections. Ou toutes sortes d'autres choses, vraiment.

Quels combinateurs les langages de programmation implémentent-ils souvent?

Cela va beaucoup varier. Il y a relativement peu de combinateurs complètement génériques - principalement les primitifs mentionnés ci-dessus - donc dans la plupart des cas, les combinateurs auront une certaine conscience de toutes les structures de données utilisées (même si ces structures de données sont construites à partir d'autres combinateurs de toute façon), dans lesquelles cas, il y a typiquement une poignée de combinateurs "entièrement génériques" et ensuite les diverses formes spécialisées que quelqu'un a décidé de fournir. Il existe un nombre ridicule de cas où (des versions convenablement généralisées de) mapper, plier et déplier suffisent à faire presque tout ce que vous pourriez souhaiter.

Comment les combinateurs peuvent-ils m'aider à concevoir une meilleure API?

Exactement comme vous l'avez dit, en pensant en termes d'opérations de haut niveau et de la manière dont celles-ci interagissent, au lieu de détails de bas niveau.

Pensez à la popularité des boucles de style «pour chaque» par rapport aux collections, qui vous permettent d'abstraire les détails de l'énumération d'une collection. Ce ne sont que des opérations de mappage / pliage dans la plupart des cas, et en en faisant un combinateur (plutôt qu'une syntaxe intégrée), vous pouvez faire des choses telles que prendre deux boucles existantes et les combiner directement de plusieurs façons - imbriquer l'une dans l'autre, faites les uns après les autres, et ainsi de suite - en appliquant simplement un combinateur, plutôt que de jongler avec tout un tas de code.

Comment concevoir des combinateurs efficaces?

Tout d'abord, réfléchissez aux opérations qui ont du sens sur les données utilisées par votre programme. Pensez ensuite à la manière dont ces opérations peuvent être combinées de manière générique de manière significative, ainsi qu'à la manière dont les opérations peuvent être décomposées en éléments plus petits qui sont reliés entre eux. L'essentiel est de travailler avec des transformations et des opérations , pas des actions directes . Lorsque vous avez une fonction qui fait juste un peu de fonctionnalité compliquée de manière opaque et ne crache qu'une sorte de résultat pré-digéré, vous ne pouvez pas faire grand-chose avec cela. Laissez les résultats finaux au code qui utilise les combinateurs - vous voulez des choses qui vous mènent du point A au point B, pas des choses qui s'attendent à être le début ou la fin d'un processus.

À quoi sont similaires les combinateurs dans un langage non fonctionnel (par exemple, Java), ou qu'utilisent ces langages à la place des combinateurs?

Ahahahaha. C'est drôle, vous devriez demander, parce que les objets sont vraiment des choses d'ordre supérieur en premier lieu - ils ont des données, mais ils transportent également un tas d'opérations, et beaucoup de ce qui constitue une bonne conception de la POO se résume à "les objets devraient agissent généralement comme des combinateurs et non comme des structures de données ".

Donc probablement la meilleure réponse ici est qu'au lieu de choses de type combinateur, ils utilisent des classes avec beaucoup de méthodes getter et setter ou des champs publics, et une logique qui consiste principalement à faire une action opaque et prédéfinie.

CA McCann
la source
Très bonne réponse! Voyons si je comprends bien - les combinateurs ne sont pas spéciaux, mais font plutôt partie intégrante de la construction d'un système logiciel maintenable? J'essaie toujours de digérer la déclaration "fragments de programme" de Hughes, dans le contexte de votre message.
Matt Fenwick
@Matt Fenwick: Je suis à peu près sûr qu'il utilise simplement des "fragments de programme" pour désigner le genre de transformations / opérations dont je parle, en particulier s'il s'agit de "Flèches", qui soulignent encore plus cette approche. De plus, je ne dirais pas qu'il s'agit de construire des systèmes maintenables exactement - davantage de systèmes de construction hautement modulaires et découplés d'une manière particulière , avec les avantages que cela implique.
CA McCann
Connaissez-vous de bonnes ressources pour lire sur l'utilisation pratique des combinateurs? Merci beaucoup!
Matt Fenwick
@Matt Fenwick: Rien de spécifique à ça de ma tête, désolé. Cependant, cela a tendance à être une approche naturelle pour les programmes écrits dans un style très fonctionnel, il suffit donc de passer du temps avec des langages qui encouragent fortement cela (par exemple, Haskell ou Clojure), et de regarder à quel point le code propre et idiomatique est écrit dans ce langage , est un très bon moyen de se faire une idée du style.
CA McCann