I. La Bibliothèque lambda minuscule ▲
Dans les articles précédents, nous avons développé une petite bibliothèque de lambdas : une bibliothèque pour créer des fonctions anonymes sur place. (Les articles originaux sont ici et ici .) Depuis que je ferai référence revenir, voici la solution à ce jour:
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.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
#
include
<algorithm>
#
include
<boost/proto/proto.hpp>
using
namespace
boost;
//
A
type
recognized
by
the
Lambda
algorithm
below
//
as
a
placeholder
for
a
user-supplied
argument.
struct
arg1_tag {
}
;
typedef
proto
::
terminal<
arg1_tag>
arg1_term;
//
Forward-declare
our
custom
expression
wrapper.
template
<
typename
ProtoExpr>
struct
lambda_expr;
//
Define
a
lambda
domain
and
its
generator,
which
//
wraps
all
new
expressions
our
custom
wrapper.
struct
lambda_domain :
proto
::
domain<
proto
::
pod_generator<
lambda_expr>
>
{
}
;
//
A
Proto
algorithm
for
evaluating
lambda
//
expressions.
struct
Lambda :
proto
::
or_<
//
When
evaluating
the
arg1
terminal,
//
return
the
state.
proto
::
when<
arg1_term, proto
::
_state>
,
//
Otherwise,
do
the
"default"
thing.
proto
::
otherwise<
proto
::
_default<
Lambda >
>
>
{
}
;
//
A
lambda
is
an
expression
with
an
operator()
//
that
evaluates
the
lambda.
template
<
typename
ProtoExpr>
struct
lambda_expr
{
BOOST_PROTO_BASIC_EXTENDS
(
ProtoExpr, lambda_expr, lambda_domain)
BOOST_PROTO_EXTENDS_ASSIGN
(
)
BOOST_PROTO_EXTENDS_SUBSCRIPT
(
)
//
So
that
boost::result_of
can
calculate
//
the
return
type
of
this
lambda
expression.
template
<
typename
Sig>
struct
result;
template
<
typename
T, typename
A1>
struct
result<
T
(
A1)>
:
boost
::
result_of<
Lambda
(
T const
&
, A1 const
&
)>
{
}
;
//
Evaluate
the
lambda
expressions
template
<
typename
A1>
typename
result<
lambda_expr
(
A1)>
::
type
operator
(
)(
A1 const
&
a1) const
{
return
Lambda
(
)(
*
this
, a1);
}
}
;
lambda_expr<
arg1_term
::
type>
const
arg1 =
{
}
;
Le code ci-dessus définit un objet arg1 de telle sorte que les expressions qui impliquent créeent des fonctions lambda. Par exemple, nous pouvons à présent faire ceci :
int
main
(
)
{
int
data[] =
{
0
,1
,2
,3
,4
,5
,6
,7
,8
,9
}
;
//
Compute
the
squares
std
::
transform
(
data, data+
10
, data, arg1 *
arg1);
//
Write
the
result
to
std::cout
std
::
for_each
(
data, data+
10
, std
::
cout <
<
arg1 <
<
'
'
);
}
Le code ci-dessus affiche les éléments suivants:
0
1
4
9
16
25
36
49
64
81
Les expressions arg1 * arg1 et std
::
cout <
<
arg1 <
<
'
'
définissent les « fonctions » qui acceptent un argument et font quelque chose pour l'argument ; l'argument passé est remplacé par arg1 partout dans l'expression qui à son tour est évaluée selon les règles normales des expressions C++. Je dis « fonctions » parce que nous savons tous qu'ils ne sont pas vraiment des fonctions, ce sont des foncteurs -objects d'un type possédant une fonction membre operator
(
). Dans notre code, cette fonction membre est définie à la ligne 52.
L' operator
(
) à la ligne 52 accepte un seul argument. Toutes les fonctions lambda créées de cette façon acceptent seulement un argument. C'est nul! Les fonctions C++ ordinaires n'ont pas cette limitation, et les lambdas devraient être comme les fonctions ordinaires C++ autant que possible. Nous en avons besoin...
Standard ML et Proto
Les Fonctions en Standard ML n'acceptent qu'une seule valeur(1). Pour passer plus d'arguments, vous devez les mettre dans un tuple. Comme nous le verrons, Proto a une restriction similaire et la solution est la même.
II. Plus d'arguments lambda ! ▲
Si nous voulons gérer plus d'un argument, certains changements sont nécessaires.
- Nous avons besoin de plus de surcharges de
lambda_expr
::
operator
(
) qui prennent plusieurs arguments. - Nous avons besoin de plus d'espaces réservés en sus de arg1 pour stocker ces arguments supplémentaires.
- Nous avons également sans doute besoin de changer notre algorithme Lambda pour lier les arguments supplémentaires aux espaces supplémentaires réservés, mais la manière dont il faut le faire n'est pas encore définie.
Astuce : nous utiliserons la composition de fonctions du précédent tutoriel. (Oooh, présage.)
C'est ma liste de tâches à faire pour le reste du tutoriel. (1) et (2) devrait être assez facile ; (3) est délicat. Commençons depuis le haut avec (1).
II-A. Plus surcharges pour lambda_expr::operator() ▲
Tuples
Les tuples sont une généralisation des std
::
pair avec un nombre variable d'éléments. Ils viennent de la norme en C++0x et TR1. Ils existent également en Boost avec une interface similaire (mais pas identique). J'utiliserai la bibliothèque de Tuple de Boost parce que cela fonctionne partout, en étant sûr de montrer en quoi il diffère de la norme.
Le premier ordre du jour est de modifier lambda_expr
::
operator
(
) afin qu'elle accepte un nombre variable d'arguments. L'ancien lambda_expr
::
operator
(
) accepte un seul argument et est directement passé à l'algorithme Lambda comme un argument d'état. Nous allons maintenant avoir beaucoup d'arguments mais un seul argument d'état. Alors plaçons les tous dans un tuple et transmettons les plutôt. Le code ci-dessous montre comment:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
template
<
typename
ProtoExpr>
struct
lambda_expr
{
//
...
//
one-argument
operator()
for
evaluating
unary
lambdas
template
<
typename
A1>
typename
boost
::
result_of<
Lambda
(
lambda_expr const
&
, boost
::
tuple<
A1 const
&
>
const
&
)>
::
type
operator
(
)(
A1 const
&
a1) const
{
boost
::
tuple<
A1 const
&
>
const
state
(
a1);
return
Lambda
(
)(
*
this
, state);
}
//
two-argument
operator()
for
evaluating
binary
lambdas
template
<
typename
A1, typename
A2>
typename
boost
::
result_of<
Lambda
(
lambda_expr const
&
, boost
::
tuple<
A1 const
&
, A2 const
&
>
const
&
)>
::
type
operator
(
)(
A1 const
&
a1, A2 const
&
a2) const
{
boost
::
tuple<
A1 const
&
, A2 const
&
>
const
state
(
a1, a2);
return
Lambda
(
)(
*
this
, state);
}
//
...
more
overloads
for
additional
arguments
}
;
Déjà, nous pouvons voir quel gâchis cela va devenir. Pour faire face à N arguments, nous avons besoin de N surcharges de operator
(
), et cela est répétitif dans la plupart du temps. Le code ci-dessus est beaucoup plus agréable en C++0x.
Voici la version C++0x du jeu de surcharge ci-dessus en utilisant un modèle de fonction variadique pour réduire les répétitions de code, les références rvalue appropriées à la manipulation d'objets temporaires, et unifié la syntaxe d'initialisation d'« état » juste pour le plaisir.
template
<
typename
... A>
typename
std
::
result_of<
Lambda
(
lambda_expr const
&
, std
::
tuple<
A &
&
...>
const
&
)>
::
type
operator
(
)(
A &
&
... a) const
{
std
::
tuple<
A &
&
...>
const
state{
std
::
forward<
A>
(
a)...}
;
return
Lambda
(
)(
*
this
, state);
}
Les lignes 11 et 20 ci-dessus initialisent un tuple avec les arguments lambda et les transmettent à l'algorithme Lambda. Les types de retour noueux semble pire que ce qu'ils sont vraiment. Ils lisent: « Quel type d'objet l'algorithme Lambda retourne si je transmets un objet de type lambda_expr et tuple convenablement initialisé ? » Nous nous assurons de mettre const
et &
au bon endroit pour faire const-correct et éviter la copie inutile.
Je ne vais pas le montrer ici, mais nous avons également besoin d'ajouter N spécialisations partielles du modèle lambda_expr
::
result de sorte que les expressions lambda soient des foncteurs TR1 valides. (Vous ne pouvez donc pas attendre pour les modèles variadiques, ai-je raison ?) Vérifiez la solution finale pour les détails.
Le point (1) sur notre liste de tâches à faire est fait. Passons au point (2).
II-B. Plus d'espaces réservés▲
Arg1 est solitaire. Donnons-lui des amis arg2 et arg3 . Je vais le faire d'une façon curieuse qui aura de sens plus tard, au moment où nous serons sur le point (3).
Tout d'abord, nous allons faire disparaître arg1_tag et arg1_term et les remplacer par quelque chose de plus général : templates de classe pour les espaces réservés qui sont paramétré sur un indice de l'argument :
//
A
(mostly)
empty
type
that
can
be
used
to
define
lambda
//
placeholders.
The
template
parameter
is
a
compile-time
//
integer
that
represents
the
placeholder
number.
template
<
typename
Index >
struct
argN_tag
{
//
requires
that
Index
models
IntegralConstant
typedef
Index index;
}
;
//
A
helper
for
creating
placeholder
terminals
template
<
typename
Index >
struct
argN_term
:
proto
::
terminal<
argN_tag<
Index >
>
{
}
;
Le paramètre Index du template est un entier qui a été transformé en un type. Integral type wrappers viennent de la norme en C++0x et TR1 et portent le nom difficile à manier std
::
integral_constant. La bibliothèque Boost MPL en a de trop, de telle sorte qu'ils ont des noms comme mpl
::
int_, mpl
::
long_, etc. Je pourrais utiliser n'importe lequel ; ils signifient tous la même chose. Comme d'habitude, je vais m'en tenir à ce qui est dans Boost.
Voici comment nous définissons les espaces réservés aux arguments :
typedef
mpl
::
int_<
0
>
nil_t; //
or
std::integral_constant<int,
0>
typedef
mpl
::
int_<
1
>
one_t; //
etc.
typedef
mpl
::
int_<
2
>
two_t;
//
Define
the
argument
placeholders:
lambda_expr<
argN_term<
nil_t >
::
type >
const
arg1 =
{
}
;
lambda_expr<
argN_term<
one_t >
::
type >
const
arg2 =
{
}
;
lambda_expr<
argN_term<
two_t >
::
type >
const
arg3 =
{
}
;
Bien ! Nous avons vérifié les points (1) et (2) de notre liste de tâches à exécuter. Nous pouvons créer des expressions lambda ternaires comme arg1 +
arg2 +
arg3, et l'objet résultant actuellement possède un membre operator
(
) qui accepte trois arguments. Mais si nous essayons effectivement d'utiliser ce membre, nous trouverons des tas de problèmes ; nous n'avons pas encore lié les espaces réservés aux éléments du tuple. En avant.
II-C. Relier les espaces réservés aux éléments du tuple ▲
L'algorihme Lambda est le centre névralgique de notre bibliothèque lambda. Il évalue les expressions lambda, en substituant les arguments réels aux espaces réservés. Maintenant que nous transmettons un tuple d'arguments à Lambda, il a besoin d'être modifié pour en compte ce fait. Revenons à l'implémentation de Lambda. La substitution des arguments réels aux espaces réservés est assurée par la ligne 25 :
Actuel code Proto Sélectionnez 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.
|
Equivalent pseudo-code Sélectionnez 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34.
|
Si le pseudo-code ci-dessus n'a pas aidé et que Lambda demeure un mystère, consultez une bibliothèque de lambdas en 30 lignes - partie 1, ce qui devrait peut-être éclaircir.
Le pseudo-code de la droite devrait donner une idée sur ce que l'algorithme Proto de la gauche fait. (Je vais en dire plus en bas sur les étranges pseudo-opérateur « matches »). La ligne 25 (à gauche) se lit comme suit : chaque terminal arg1 est remplacé par l'état. Tout comme est une sorte de marqueur, il est également proto
::
_state. It stands in for et reçoit l'argument d'État de Lambda. (Seulement la manière dont Proto parvient à transporter l'argument d'état à travers toutes les chaînes d'invocations et l'affiche -comme par magie- au bon endroit au bon moment est comme un mystère pour l'instant).
Maintenant que l'argument d'état est un tuple, nous devons modifier Lambda . Pour des espaces réservés, devrait être indexé dans le tuple pour retourner le bon argument ; devrait être remplacé par l'élément d'indice 0 du tuple, arg2 doit retourner le 1er élément, etc. Sinon, de façon générale, en pseudo-code on a :
20.
21.
22.
23.
24.
25.
26.
27.
28.
//
PSEUDO-CODE
equivalent
for
the
Lambda
//
algorithm
to
the
left.
auto
Lambda
(
auto
expr, auto
state )
{
//
When
evaluating
the
arg1
terminal,
//
return
the
state.
if
(
expr matches? argN ) return
boost
::
get<
N-
1
>
(
state);
//
Otherwise,
do
the
"default"
thing.
//
...
}
Ci-dessus, le pseudo-code hand-wavy qui exprime notre intention : correspond au Nème espace réservé et retourne le (N-1)ème élément du tuple. Boost
::
get est une fonction qui récupère un élément d'un tuple. Nous voulons obtenir le (N-1)ème élément de ce tuple parce que arg1 correspond au 0ème élément du tuple, et ainsi de suite.
Cette partie devient compliquée, donc je vais d'abord montrer ce à quoi le code actuel ressemble puis décrire comment j'en suis arrivé là. Alors sans plus tarder, voici le nouvel algorithme Lambda (laissons tomber les qualifications proto
::
dès maintenant pour plus de lisibilité) :
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
//
A
Proto
algorithm
for
evaluating
lambda
//
expressions.
struct
Lambda
:
or_<
//
When
evaluating
the
argN
terminal,
//
return
the
N-1
element
of
the
state
tuple.
when<
argN_term<
_ >
, tuple_get
(
_value
(
_), _state) >
//
Otherwise,
do
the
"default"
thing.
, otherwise<
_default<
Lambda >
>
>
{
}
;
Sans aucun doute, ce code n'a pas de sens. Je n'ai pas encore dit ce que _ et _value sont (ils viennent de l'espace de nom proto
::
), ni montré l'implém
de tuple_get. Mais je suis certain que ce code est valide et fonctionne comme le pseudo-code ci-dessus. Cela signifie que la ligne 25 gère correctement tous les espaces réservés, et pas seulement arg1. Cela soulève deux questions :
- Comment est-ce que argN_term
<
_>
"correspond" convenablement à tous (et seulement) les espaces réservés ? - Comment est-ce-que
tuple_get
(
_value
(
_), _state) trouve et retourne l'élément correct du tuple ?
Mon utilisation de l'operateur inventé « assorti » dans mon pseudo-code aurait éveillé votre curiosité, surtout si vous êtes familier avec un langage fonctionnel, nous allons donc commencer par le point (1).
III. Pattern Matching ▲
Quand je dis les mots « pattern matching », qu'est-ce-qui vient à l'esprit ? Les expressions régulières ? Les modèles de mariée ? Si vous êtes un programmeur fonctionnel, le terme « pattern matching » devrait retenir toute votre attention. Haskell, par exemple, peut distribuer à des branches différentes en appariant une valeur contre une série de patterns. Par exemple, nous allons écrire une fonction Haskell pour vérifier la réponse de la vie, l'univers et tout :
2.
3.
4.
5.
6.
7.
-
-
Define a function "
isTheAnswer
"
that returns
-
-
True if
the argument is 42
, False otherwise.
isTheAnswer 42
=
True
isTheAnswer _ =
False
main =
do
print (
isTheAnswer 24
)
print (
isTheAnswer 42
)
Ce programme affiche:
True False
P vs NP
Il est plus facile de vérifier la réponse de la vie, l'univers et tout ce qui est à calculer.
On dirait que la fonction isTheAnswer est définie deux fois. Elle est vraiment définie une seule fois, mais avec des modèles différents. Le premier modèle est 42
, qui correspond à lui-même. Le second modèle est _, qui correspond à n'importe lequel ; est le modèle nommé caractère générique. Voici comment fonctionne : partout où elle est invoquée, le runtime Haskell effectue un test : est-ce l'argument 42 ? Si c'est le cas, la première application est sélectionnée. Si non, il « passe » à la deuxième implémentation. Les modèles sont jugés dans l'ordre où ils apparaissent dans le code. (Fait intéressant, n'ayant pas de modèle correspondant à l'argument, ç'aurait été une erreur d'exécution, mais dans notre cas c'est impossible parce que le modèle générique agit comme un passe-partout.)
Le pattern matching en Haskell est un sujet riche et intéressant. Pour plus d'informations, consultez cette section sur le filtrage dans l'excellent Tutoriel Haskell pour les programmeurs C.
Pourquoi ai-je abordé Haskell dans tout cela ? Proto fait trop du pattern matching ! Regardez à nouveau Lambda, et cette fois-ci je vais remplacer otherwise à la ligne 27 par l'implémentation de son l'équivalent when :
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
//
A
Proto
algorithm
for
evaluating
lambda
//
expressions.
struct
Lambda
:
or_<
//
When
evaluating
the
argN
terminal,
//
return
the
N-1
element
of
the
state
tuple.
when<
argN_term<
_ >
, tuple_get
(
_value
(
_), _state) >
//
Otherwise,
do
the
"default"
thing.
, when<
_, _default<
Lambda >
>
>
{
}
;
Lambda est un foncteur. Pour évaluer cette fonction, Proto utilise ple attern matching. Les modèles sont argN_term<
_ >
et _ sur les lignes 25 et 27. Proto les teste dans l'ordre dans lequel ils apparaissent dans le code, comme dans l'exemple de Haskell. Comme vous l'aurez deviné déjà, est le modèle générique de Proto qui correspond à tout.
Rappelons que les espaces réservés sont désormais définis comme suit :
typedef
mpl
::
int_<
0
>
nil_t;
typedef
mpl
::
int_<
1
>
one_t;
typedef
mpl
::
int_<
2
>
two_t;
lambda_expr<
argN_term<
nil_t >
::
type >
const
arg1 =
{
}
;
lambda_expr<
argN_term<
one_t >
::
type >
const
arg2 =
{
}
;
lambda_expr<
argN_term<
two_t >
::
type >
const
arg3 =
{
}
;
Lors de l'évaluation de avec arg1, Proto lie le type de arg1 avec les modèles de Lambda. En ignorant le wrapper lambda_expr (qui n'a aucun effet sur le pattern matching), le type de arg1 est argN_term<
nil_t >
::
type. Ce type lui est lié en opposition à argN_term<
_ >
. Qu'est-ce-qui se passe ?
Rappelons que argN_term est défini comme suit:
template
<
typename
Index>
struct
argN_term
:
proto
::
terminal<
argN_tag<
Index>
>
{
}
;
Quel que soit les comportements que a comme modèle, il hérite de terminal . La classe terminal est également l'endroit où a imbriqué le typedef ::
type. En fait, Proto demande réellement ce qui suit :
Est-ce que le type d'expression terminal<
argN_tag<
nil_t>
>
::
type corresponde au modèle terminal<
argN_tag<
_>
>
?
La réponse est oui, mais pourquoi ? Ici, terminal remplit deux rôles. D'une part, il est utilisé pour générer un type d'expression (via de l'accès au typedef imbriqué ::
type). D'autre part, il est utilisé comme un modèle correspondant à des types d'expression. Cette double nature de est un peu déroutant, mais il réduit le nombre de types nécessaires pour apprendre Proto.
Les constructeurs Haskell
Bartosz "Fermat" Milewski a trouvé une preuve vraiment merveilleux que la double nature du proto
::
terminal est analogue à l'utilisation secondaire de données Haskell constructeurs comme des modèles, mais cette marge est trop petite pour le contenir. Cherchez-le dans un futur article.
Mais encore nous nous demandons pourquoi partis en le type terminal<
argN_tag<
nil_t >
>
::
type correspond au modèle terminal<
argN_tag<
_ >
>
. Eh bien, on est un terminal et l'autre est un modèle pour un terminal correspondre sondes Proto autre:.! Ne leurs types de valeurs correspondent? C'est, ne argN_tag<
nil_t >
correspondance argN_tag<
_ >
? Oui, mais pourquoi? Proto a un ensemble de règles qui régissent la façon dont modèle produit assortis. Par exemple, n'importe quel type de valeur terminal lui-même correspond trivialement (par exemple int correspond int
), mais argN_tag<
nil_t>
et argN_tag<
_>
ne pas correspondre exactement.
Proto Pattern Matching
Pattern matching dans Proto est un sujet riche. Il est entièrement documenté ici , mais ce n'est pas une lecture facile. Nous verrons d'autres exemples de schémas Proto au cours de cette série d'articles.
La réponse est que Proto a une règle qui autorise les types de la valeur finale de la forme T<
X >
pour correspondre à des types comme T<
_ >
; à savoir les cas de la même matrice où le modèle se les paramètres correspondent. Proto ne sait pas quelque chose de spécial à propos de argN_tag , mais il peut voir que argN_tag<
nil_t>
et argN_tag<
_>
conforme à cette règle, si le match réussit. Et enfin on peut expliquer pourquoi j'ai défini argN_tag la façon dont je l'ai fait: je voulais être en mesure de facilement motif correspond son type en utilisant cette règle.
De cette façon, tous les espaces réservés, arg1, arg2, et arg3, et d'autres que nous peut décider d'ajouter plus tard avoir des types qui correspondent au modèle argN_term<
_ >
.
IV. Types de fonctions comme Actions ▲
Maintenant, qu'en est-il étrange tuple_get
(
_value
(
_), _state) qui est utilisé pour évaluer les espaces réservés lambda ? Il ressemble beaucoup à une expression, comme si nous faisons des appels de fonction, n'est-ce pas ? Cependant, il est utilisé comme un paramètre à la when modèle, de sorte qu'il ne peut pas être une expression. Il est, en fait, un type de fonction. Le when modèle interprète types de fonctions comme les appels de fonction. En d'autres termes, when utilise les types de fonctions en tant que langue spécifique à un domaine pour définir des actions. Voici comment lire cette proto-action-comme-un-fonction de type:
- Le type _ est un espace réservé à l'expression qui vient de correspondance.
- Le type _state est un espace réservé pour l'Etat.
- Tout de suite à gauche d'une parenthèse d'ouverture est un objet fonction TR1 style.
- Quelque chose entre parenthèses est un argument à l'objet de la fonction TR1 style.
En appliquant ces règles, proto
::
when tourne le type de fonction tuple_get
(
_value
(
_), _state) en un objet de fonction un peu comme le pseudo-code suivant :
//
PSEUDO-CODE
equivalent
of
tuple_get(_value(_),
_state)
auto
eval_argN_term
(
auto
expr, auto
state )
{
return
tuple_get
(
)(
_value
(
)(
expr), state);
}
Si vous voulez savoir comment when convertit les types de fonctions dans des objets fonctionnels équivalents, consultez le dernier article.
Ci-dessus, tuple_get
(
) et _value
(
) sont des objets de leurs types respectifs défaut construit. La génération de l'objet de fonction qui se passe entièrement au moment de la compilation. L'objet fonction est appelée lors de l'exécution en utilisant l'expression qui vient de correspondance et l'état actuel. En d'autres termes, eval_argN_term reçoit soit arg1, arg2 ou arg3 avec le tuple de l'Etat.
IV-A. Pourquoi _ reçoivent arg1 ? ▲
Dans le modèle argN_term<
_ >
, le _ finit correspondant nil_t. Dans l'action, _ reçoit arg1 . Pourquoi ne pas recevoir nil_t, la chose elle correspondait ? Considérons la règle suivante à partir d'un algorithme Proto :
//
What
gets
passed
to
foo?
when<
plus<
_,_>
, foo
(
_) >
Dans le modèle, _ montre plus d'une fois, mais seulement une fois dans l'action. Qu'est-ce que _ signifie dans l'action? La seule chose sensée pour Proto à faire est de passer toute expression qui correspondait à la configuration (un binaire, plus l'expression) à foo . Par souci de cohérence, _ dans une action signifie toujours «toute expression qui vient de correspondance".
Maintenant tout ce qui reste à expliquer est le sens de _value et tuple_get . _value est assez facile: il s'agit d'un objet de fonction qui accepte bornes Proto et renvoie la valeur à l'intérieur. La valeur à l'intérieur arg1 est un objet de type argN_tag< nil_t > , de sorte que c'est ce qui est transmis à tuple_get avec le tuple. Et maintenant, le travail de tuple_get doit être clair: il doit retourner le bon élément d'un tuple donné le tuple et une instance de argN_tag .
IV-B. La plupart du code Confondre jamais? ▲
Après avoir une action Proto comme tuple_get
(
yadda
(
yadda), yadda) lui a expliqué, un de mes collègues l'a décrite comme la pièce la plus déroutante de code qu'il ait jamais vu. Il ressemblait à quelque chose, mais il a trouvé que ce n'était pas la chose, mais il s'est avéré que c'était vraiment.
Dans le dernier article, j'ai fait une affaire pour les types de fonction d'être un peu langue spécifique au domaine de la composition de fonctions. Il en est de Proto. Autrefois considéré comme un peu de langue distincte, les actions Proto sont plus faciles à assimilez. À un certain moment vous renoncez à essayer de raisonner à travers comme C ++ et juste le voir comme Proto.
V. Détails, détails ▲
Par rapport à la composition d'appariement de formes et de la fonction capiteux des choses, la tâche finale semble plutôt prosaïque: écrire un objet de fonction nommée tuple_get que, lorsqu'il est passé d'une instance de argN_tag et un tuple, retourne l'élément correct du tuple. Nous avons juste besoin d'envelopper le bon boost::get appel de fonction. Ce qui suit est un objet fonction TR1 style qui fait justement cela, dans toute sa gloire prosaïque. Explication à suivre.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
//
A
TR1-style
function
object
that
accepts
an
argN_tag
//
instance
and
a
tuple
and
returns
the
proper
tuple
element.
struct
tuple_get : proto
::
callable
{
//
Nested
result
template
for
return
type
calculation
template
<
typename
Sig>
struct
result;
template
<
typename
This, typename
ArgNTag, typename
Tuple>
struct
result<
This
(
ArgNTag const
&
, Tuple const
&
)>
{
//
ArgNTag
is
an
instance
of
argN_tag,
which
has
a
nested
index
typedef
typedef
typename
ArgNTag
::
index N;
//
boost::tuples::element
is
a
trait
that
returns
the
type
of
the
Nth
element
typedef
typename
boost
::
tuples
::
element<
N
::
value, Tuple>
::
type type;
}
;
template
<
typename
ArgNTag, typename
Tuple>
typename
result<
tuple_get
(
ArgNTag const
&
, Tuple const
&
)>
::
type
operator
(
)(
ArgNTag const
&
, Tuple const
&
tup) const
{
typedef
typename
ArgNTag
::
index N;
//
boost::get
is
a
function
that
returns
the
Nth
element
of
a
tuple.
return
boost
::
get<
N
::
value>
(
tup);
}
}
;
Comme d'habitude avec les objets de la fonction TR1 de style, c'est un peu difficile de voir l'intention à travers tout le bruit syntaxique, mais là, il est sur la ligne 23 : un appel à boost
::
get qui récupère un élément d'un tuple.
Certains détails méritent d'être mentionnés. tuple_get accepte comme premier argument une instance de argN_tag, qui, si vous regardez waaaaay revenir là où nous l'avons défini ci-dessus présente un typedef imbriquée appelée index . C'est un type entier enveloppé représentant un indice de tuple. entiers de type gainé comme mpl
::
int_ et std
::
integral_wrapper avoir une constante statique appelée imbriquée value qui (sans surprise) est la valeur de l'entier enveloppé. Donc mpl
::
int_<
1
>
::
value est 1
. Mais c'est une constante de compilation, on peut donc l'utiliser comme un paramètre de modèle de boost
::
get , ce qui est important. Quoi qu'il en soit, c'est là ::
value provient des lignes 14 et 23.
Ligne 9 est la imbriquée spécialisée partielle result modèle, la façon idiomatique pour calculer le type de retour de tuple_get
::
operator
(
) . Cette TR1 result_of chant et de danse doivent se sentir vaguement familier maintenant. Ligne 14 a la compilation équivalent du boost
::
get appel. boost
::
tuples
::
element est un trait qui prend un nombre entier de compilation et un type de tuple, et «revenus» le type de l'élément stocké à l' index.
Les tuples Boost vs les tuples C++ 0x
Le modèle en Boost appelé boost
::
tuples
::
element est connu comme std
::
tuple_element dans TR1 et C++ 0x. C'est la seule différence pratique.
La seule autre chose à noter dans le tuple_get mise en œuvre est l'héritage de proto
::
callable . Je n'ai pas montré assez sur actions Proto pour expliquer pourquoi cela est nécessaire. Pour l'instant, je vais juste dire ceci : tous les objets de la fonction TR1 style utilisés pour composer des actions en utilisant les types de fonctions (comme get_tuple
(
yadda
(
yadda), yadda) ) doit hériter de callable directement ou indirectement. Il aide Proto sens du type de fonction. Mais je vais vous épargner et de retarder l'explication complète pour un autre article.
VI. Récapitulatif▲
Ici, encore une fois, est la nouvelle version améliorée Lambda algorithme :
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
//
A
Proto
algorithm
for
evaluating
lambda
//
expressions.
struct
Lambda
:
or_<
//
When
evaluating
the
argN
terminal,
//
return
the
N-1
element
of
the
state
tuple.
when<
argN_term<
_ >
, tuple_get
(
_value
(
_), _state) >
//
Otherwise,
do
the
"default"
thing.
, when<
_, _default<
Lambda >
>
>
{
}
;
Maintenant que nous savons ce qui se passe, nous pouvons apprécier la façon dont les parties travaillent ensemble: lors de la compilation la fonction envoi à l'aide de motifs, la composition de fonctions en utilisant les types de fonctions, et des objets de fonction TR1 de style. Chaque petit morceau, dit tuple_get, n'est pas difficile d'écrire ou assimilez à l'isolement. Les pièces s'emboîtent avec une syntaxe concise qui se sent naturel (ou viendra pour une sensation naturelle si vous restez avec lui). Au total, Lambda est assez puissant petit beastie; il peut évaluer les expressions lambda arbitrairement complexes avec un nombre quelconque d'espaces réservés arguments. Tout cela en 6 lignes de code. (OK, nous avons dû écrire un peu pour mettre en place ces 6 lignes, mais travailler avec moi.)
Cliquez ici pour voir la solution complète .
Je vous laisse avec un programme simple qui utilise notre bibliothèque lambda et l'expression lambda binaire arg1 +
arg2 avec les deux entrées séquence std
::
transform algorithme - quoi d'autre? - Générer les nombres de Fibonacci. Amusez-vous.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
#
include
<algorithm>
int
main
(
)
{
int
data[10
] =
{
0
,1
}
;
//
generate
the
Fibonacci
sequence
std
::
transform
(
data, data+
8
, data+
1
, data+
2
, arg1 +
arg2);
//
write
each
element
to
std::cout
std
::
for_each
(
data, data+
10
, std
::
cout <
<
arg1 <
<
'
'
);
}
VII. Remerciements▲
VII-A. Auteur▲
Merci à Bartosz Milewski , Walter Bright , et Andrei Alexandrescu pour leurs précieux commentaires sur ce post.