- Whether you believe the library should be accepted into Boost Yes, conditionally. * Conditions for acceptance I think the documentation and naming issues I raise below, and those filed on GitHub by me and others should be addressed before acceptance. Also, the ADL issues that Steven Watanabe alluded to previously need to be fixed. After that, I think it should be re-submitted. - Your name Zach Laine - Your knowledge of the problem domain. I do generic programming regularly, and have done for years. You are strongly encouraged to also provide additional information: - What is your evaluation of the library's: * Design I think it's good. I particularly like the fact that it attempts to keep everything constexpr- and SFINAE-friendly wherever possible. I *really* like reveal() and failure(). Too few TMP-heavy libraries pay attention to providing good error messages. I have no problem with the constness requirements on Callables used throughout Fit, as was discussed previously. I even consider this a feature, allowing me to detect non-const Callables, which are almost always an error on my part. This is especially true since std::ref() and mutable_() let me explicitly choose how to handle mutable Callables, in a way that is also explicit at the call site. There are a number of adaptors that I find have obscure names: by() is a projection; why not call it project()? compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress() should be foldr(). flow() is a super obscure name! Why is this not reverse_compose() or similar? indirect() could more easily be understood if it were called deref() or dereference(). I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry(). conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_(). * Implementation I only briefly looked at the implementation, and only a few adaptors at that. What I saw did not leave me any concerns in particular. However, I *am* concerned by Steven Watanabe's claim that the library is ADL-unsafe. He did not state exactly why that I could tell, but this needs to be addressed if so. * Documentation The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write. The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like. This will justify their use for those users that want them, and show those that don't what they can write instead. Others have written during the review that they don't understand why this library is useful. I think this is simply a documentation failure. I find the printing example from the Quick Start compelling, but perhaps Paul could show how it would have to be done if hand-written (even if only partially). Also, Paul has told me offline that there are a small number of primitive operations on which everything else is built (pack, unpack, and apply, IIRC). Perhaps show how those can be used to implement the other adapters, and how hard they are to write yourself. Showing how at least some of the library can be used as fundamental building blocks would go a long way. Some specifics. I have read the online docs before, and I put several bugs into the GitHub page for what I consider insufficient examples or explanations. Some new points, though: I think alias() is in bad need of a motivating example. I don't understand the distinction between lazy() and protect() just from the descriptions. In particular, this is what protect()'s docs say: '' The protect function adaptor can be used to make a bind expression be treated as a normal function instead. Both bind and lazy eargerly evaluates nested bind expressions. The protect adaptor masks the type so bind or lazy no longer recognizes the function as bind expression and evaluates it. '' I don't know what "masks the type" means here. This really needs an example. * Tests I did not run the tests, and just sort of spot-checked them. They seem a bit thin. In particular, one of the ones I looked at was conditional(). This one seems like it is needs more coverage, including thorny ones that must be kept working when maintenance occurs. There are probably others adapters that need similar treatment. Also, consider this -- Steven pointed out this as a failure case: boost::fit::conditional(identity(), identity())(0); and Paul says that it is supposed to fail, and that this failure is a feature, not a bug. If so, a minimal use like this that fails to compile needs to be added as a fail-compile test (and of course this intention should be documented). * Usefulness I think the library is very useful. There seem to be some things in there that use of which might be deemed a bit clever (not the good kind), like infix(), but then there are things like conditional() that I can see being *extremely* useful. - Did you attempt to use the library? If so: Yes. * Which compiler(s) Clang 3.6 * What was the experience? Any problems? I found I could gin up quick uses of things like conditional() (the most interesting adaptor for my purposes) in a short period of time. It was quite straightforward. - How much effort did you put into your evaluation of the review? About 5 or 6 hours. Zach
AMDG On 03/09/2016 07:49 AM, Zach Laine wrote:
However, I *am* concerned by Steven Watanabe's claim that the library is ADL-unsafe. He did not state exactly why that I could tell, but this needs to be addressed if so.
There are two issues (I pointed them out as I ran across them in the line-by-line part of my review.) - There are a unqualified calls in many places where ADL is not intended. - The detail namespace is an associated namespace of quite a few components. In Christ, Steven Watanabe
On Wed, Mar 9, 2016 at 10:03 AM, Steven Watanabe
AMDG
On 03/09/2016 07:49 AM, Zach Laine wrote:
However, I *am* concerned by Steven Watanabe's claim that the library is ADL-unsafe. He did not state exactly why that I could tell, but this
needs
to be addressed if so.
There are two issues (I pointed them out as I ran across them in the line-by-line part of my review.) - There are a unqualified calls in many places where ADL is not intended. - The detail namespace is an associated namespace of quite a few components.
Ah, I missed that I guess. Thanks, Steven. Zach
Le 09/03/2016 15:49, Zach Laine a écrit : Thanks Zach for your review, I will just comment for the moment the following. I don't remember if Paul has confirmed in this ML that the following should work boost::fit::conditional(identity(), identity())(0); but he has created an issue in github. https://github.com/pfultz2/Fit/issues/123 -Conditional should accept mutiple functions of the same type Best, Vicente
- Whether you believe the library should be accepted into Boost
Yes, conditionally.
* Conditions for acceptance
I think the documentation and naming issues I raise below, and those filed on GitHub by me and others should be addressed before acceptance. Also, the ADL issues that Steven Watanabe alluded to previously need to be fixed. After that, I think it should be re-submitted.
- Your name
Zach Laine
- Your knowledge of the problem domain.
I do generic programming regularly, and have done for years.
You are strongly encouraged to also provide additional information: - What is your evaluation of the library's: * Design
I think it's good. I particularly like the fact that it attempts to keep everything constexpr- and SFINAE-friendly wherever possible.
I *really* like reveal() and failure(). Too few TMP-heavy libraries pay attention to providing good error messages.
I have no problem with the constness requirements on Callables used throughout Fit, as was discussed previously. I even consider this a feature, allowing me to detect non-const Callables, which are almost always an error on my part. This is especially true since std::ref() and mutable_() let me explicitly choose how to handle mutable Callables, in a way that is also explicit at the call site.
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress() should be foldr().
flow() is a super obscure name! Why is this not reverse_compose() or similar?
indirect() could more easily be understood if it were called deref() or dereference().
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
* Implementation
I only briefly looked at the implementation, and only a few adaptors at that. What I saw did not leave me any concerns in particular.
However, I *am* concerned by Steven Watanabe's claim that the library is ADL-unsafe. He did not state exactly why that I could tell, but this needs to be addressed if so.
* Documentation
The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like. This will justify their use for those users that want them, and show those that don't what they can write instead.
Others have written during the review that they don't understand why this library is useful. I think this is simply a documentation failure. I find the printing example from the Quick Start compelling, but perhaps Paul could show how it would have to be done if hand-written (even if only partially). Also, Paul has told me offline that there are a small number of primitive operations on which everything else is built (pack, unpack, and apply, IIRC). Perhaps show how those can be used to implement the other adapters, and how hard they are to write yourself. Showing how at least some of the library can be used as fundamental building blocks would go a long way.
Some specifics. I have read the online docs before, and I put several bugs into the GitHub page for what I consider insufficient examples or explanations. Some new points, though:
I think alias() is in bad need of a motivating example.
I don't understand the distinction between lazy() and protect() just from the descriptions. In particular, this is what protect()'s docs say:
'' The protect function adaptor can be used to make a bind expression be treated as a normal function instead. Both bind and lazy eargerly evaluates nested bind expressions. The protect adaptor masks the type so bind or lazy no longer recognizes the function as bind expression and evaluates it. ''
I don't know what "masks the type" means here. This really needs an example.
* Tests
I did not run the tests, and just sort of spot-checked them. They seem a bit thin. In particular, one of the ones I looked at was conditional(). This one seems like it is needs more coverage, including thorny ones that must be kept working when maintenance occurs. There are probably others adapters that need similar treatment.
Also, consider this -- Steven pointed out this as a failure case:
boost::fit::conditional(identity(), identity())(0);
and Paul says that it is supposed to fail, and that this failure is a feature, not a bug. If so, a minimal use like this that fails to compile needs to be added as a fail-compile test (and of course this intention should be documented).
* Usefulness
I think the library is very useful. There seem to be some things in there that use of which might be deemed a bit clever (not the good kind), like infix(), but then there are things like conditional() that I can see being *extremely* useful.
- Did you attempt to use the library? If so:
Yes.
* Which compiler(s)
Clang 3.6
* What was the experience? Any problems?
I found I could gin up quick uses of things like conditional() (the most interesting adaptor for my purposes) in a short period of time. It was quite straightforward.
- How much effort did you put into your evaluation of the review?
About 5 or 6 hours.
Zach
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
- Whether you believe the library should be accepted into Boost
Yes, conditionally.
* Conditions for acceptance
I think the documentation and naming issues I raise below, and those filed on GitHub by me and others should be addressed before acceptance. Also, the ADL issues that Steven Watanabe alluded to previously need to be fixed. After that, I think it should be re-submitted.
- Your name
Zach Laine
- Your knowledge of the problem domain.
I do generic programming regularly, and have done for years.
You are strongly encouraged to also provide additional information: - What is your evaluation of the library's: * Design
I think it's good. I particularly like the fact that it attempts to keep everything constexpr- and SFINAE-friendly wherever possible.
I *really* like reveal() and failure(). Too few TMP-heavy libraries pay attention to providing good error messages.
I have no problem with the constness requirements on Callables used throughout Fit, as was discussed previously. I even consider this a feature, allowing me to detect non-const Callables, which are almost always an error on my part. This is especially true since std::ref() and mutable_() let me explicitly choose how to handle mutable Callables, in a way that is also explicit at the call site.
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you write: `sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`, I read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress() should be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results: foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world" However, compress and reverse_compress work like this: compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello" I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would prefer to use `fold` and `reverse_fold`.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing: reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers); With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it.
indirect() could more easily be understood if it were called deref() or dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep the name consistent with other similar constructs.
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead.
* Implementation
I only briefly looked at the implementation, and only a few adaptors at that. What I saw did not leave me any concerns in particular.
However, I *am* concerned by Steven Watanabe's claim that the library is ADL-unsafe. He did not state exactly why that I could tell, but this needs to be addressed if so.
* Documentation
The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is part of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation.
This will justify their use for those users that want them, and show those that don't what they can write instead.
I could show an alternative without the macros, but I would prefer to put that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats.
Others have written during the review that they don't understand why this library is useful. I think this is simply a documentation failure. I find the printing example from the Quick Start compelling, but perhaps Paul could show how it would have to be done if hand-written (even if only partially). Also, Paul has told me offline that there are a small number of primitive operations on which everything else is built (pack, unpack, and apply, IIRC). Perhaps show how those can be used to implement the other adapters, and how hard they are to write yourself. Showing how at least some of the library can be used as fundamental building blocks would go a long way.
Some specifics. I have read the online docs before, and I put several bugs into the GitHub page for what I consider insufficient examples or explanations. Some new points, though:
I think alias() is in bad need of a motivating example.
I don't understand the distinction between lazy() and protect() just from the descriptions. In particular, this is what protect()'s docs say:
'' The protect function adaptor can be used to make a bind expression be treated as a normal function instead. Both bind and lazy eargerly evaluates nested bind expressions. The protect adaptor masks the type so bind or lazy no longer recognizes the function as bind expression and evaluates it. ''
I don't know what "masks the type" means here. This really needs an example.
Protect works just like protect in Boost.Bind, but I do agree I need an example.
* Tests
I did not run the tests, and just sort of spot-checked them. They seem a bit thin. In particular, one of the ones I looked at was conditional(). This one seems like it is needs more coverage, including thorny ones that must be kept working when maintenance occurs. There are probably others adapters that need similar treatment.
What kind of test cases are missing for in conditional?
Also, consider this -- Steven pointed out this as a failure case:
boost::fit::conditional(identity(), identity())(0);
and Paul says that it is supposed to fail, and that this failure is a feature, not a bug. If so, a minimal use like this that fails to compile needs to be added as a fail-compile test (and of course this intention should be documented).
Actually, Steven has convinced me to change this. I can see now why this is useful. A compile-fail will probably still be useful for the `match` adaptor.
* Usefulness
I think the library is very useful. There seem to be some things in there that use of which might be deemed a bit clever (not the good kind), like infix(), but then there are things like conditional() that I can see being *extremely* useful.
- Did you attempt to use the library? If so:
Yes.
* Which compiler(s)
Clang 3.6
* What was the experience? Any problems?
I found I could gin up quick uses of things like conditional() (the most interesting adaptor for my purposes) in a short period of time. It was quite straightforward.
- How much effort did you put into your evaluation of the review?
About 5 or 6 hours.
Thanks, Zach for your review.
Zach
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Le 10/03/2016 01:12, Paul Fultz II a écrit :
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you write: `sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`, I read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name.
`by` is a comparator while `projet` is a projection.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress() should be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results:
foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world"
It would be good to use a binary function that has two different types to see the difference in behavior.
However, compress and reverse_compress work like this:
compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello"
I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would prefer to use `fold` and `reverse_fold`. You are folding the parameter pack. I like those. and I would like also fold_left, fold_right.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing:
reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers);
With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it. Would pipe works better?
indirect() could more easily be understood if it were called deref() or dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep the name consistent with other similar constructs.
Could you tell us what indirect is in range-v3, what is the signature and the semantics. I don't see it is used for the same purposes, but maybe I'm wrong. ref(flv) is a callable that wraps an lv indirect(ptr) is a callable that wraps a ptr and deref it before calling deref/dereference will only relate the dereference action, but not the call. deref_on_call is too long?
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor. Maybe you could explain why curry is not a good name here? What is the difference between partial an currying a function.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I agree conditional is not a good name.
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead. This function is related to match. The difference is that one select the best matching overload and those must be exclusive and the other the first matching overload and the match can be inclusive. I would like to see the semantics on the name, but I have not a concrete proposal.
We need to take in account that this is an adaptor, so there is no call, so `invoke_first` or call_first_off will not work. Those functions creates another function object that applies a different algorithm to select the overloaded functions when called. I've used overload -> match first_overload -> conditional I prefer those, but I'm not yet happy with. Naming is difficult.
* Documentation
The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is part of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation. I agree hat it is absolutely needed to show the exact expansion, but it is clear that the user wants to know what is behind the scenes.
This will justify their use for those users that want them, and show those that don't what they can write instead.
I could show an alternative without the macros, but I would prefer to put that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats. I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Best, Vicente
On Thursday, March 10, 2016 at 12:34:26 AM UTC-6, Vicente J. Botet Escriba wrote:
Le 10/03/2016 01:12, Paul Fultz II a écrit :
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you
write:
`sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`, I read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name. `by` is a comparator while `projet` is a projection.
By is a projection in the example of `construct`.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress()
should
be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results:
foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world" It would be good to use a binary function that has two different types to see the difference in behavior.
Yes, you could do `flip(_+_)` to get the same effect. However, I want the callback to be consistent with compress reverse_compress, so it always is called with f(state, element). Also, `fold` and `reverse_fold` are commonly used names in C++, so I would like to keep it more consistent with those usage. If the user wants `fold_right`, it can be easily written, like this: `auto fold_right = compose(compress, flip)`.
However, compress and reverse_compress work like this:
compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello"
I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would prefer to use `fold` and `reverse_fold`. You are folding the parameter pack. I like those. and I would like also fold_left, fold_right.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing:
reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers);
With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it. Would pipe works better?
pipe could work, I don't know how others feel about such a name.
indirect() could more easily be understood if it were called deref() or dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep the name consistent with other similar constructs.
Could you tell us what indirect is in range-v3, what is the signature and the semantics. I don't see it is used for the same purposes, but maybe I'm wrong.
It dereferences the values in a range. They both are functors that will dereference the parametrized type.
ref(flv) is a callable that wraps an lv indirect(ptr) is a callable that wraps a ptr and deref it before calling
Well, it can be used with anything that is dereferenceable. So it works with boost::optional as well.
deref/dereference will only relate the dereference action, but not the call.
deref_on_call is too long?
Yes it is.
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor. Maybe you could explain why curry is not a good name here? What is the difference between partial an currying a function.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I agree conditional is not a good name.
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead. This function is related to match. The difference is that one select the best matching overload and those must be exclusive and the other the first matching overload and the match can be inclusive. I would like to see the semantics on the name, but I have not a concrete proposal.
We need to take in account that this is an adaptor, so there is no call, so `invoke_first` or call_first_off will not work. Those functions creates another function object that applies a different algorithm to select the overloaded functions when called.
I've used overload -> match first_overload -> conditional
I would prefer `match` and `linear`.
* Documentation
The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a
very
good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is
I prefer those, but I'm not yet happy with. Naming is difficult. part
of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation. I agree hat it is absolutely needed to show the exact expansion, but it is clear that the user wants to know what is behind the scenes.
I strongly disagree. Documentation is about documenting the interface, not the implementation. Mainly because the implementation could change or vary between platforms while the interface would remain the same. If the user wants to know about the implementation, they can look at the source code or in the case of macros, look at the preprocessed output.
This will justify their use for those users that want them, and show those that don't
they can write instead.
I could show an alternative without the macros, but I would prefer to
what put
that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats. I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Being able to declare the functions as global variables is a key feature of the library. The example in the quick start guide shows how to implement many things without the need for a lot of template boilerplate. For those users that prefer not to use global function objects(and I have yet seen a compelling reason not to use them) can look at the 'Advanced' section. I realize now, that I need to spend more discussing the advantages of using global function objects(more composability), and address some of the misperceived issues with these global function objects(they can be in a namespace to avoid name conflicts and are not mutable so it won't be a problem in mutilthreaded environments).
Best, Vicente
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 3/10/2016 1:46 PM, Paul Fultz II wrote:
On Thursday, March 10, 2016 at 12:34:26 AM UTC-6, Vicente J. Botet Escriba wrote:
Le 10/03/2016 01:12, Paul Fultz II a écrit :
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you
write:
`sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`, I read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name. `by` is a comparator while `projet` is a projection.
By is a projection in the example of `construct`.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress()
should
be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results:
foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world" It would be good to use a binary function that has two different types to see the difference in behavior.
Yes, you could do `flip(_+_)` to get the same effect. However, I want the callback to be consistent with compress reverse_compress, so it always is called with f(state, element). Also, `fold` and `reverse_fold` are commonly used names in C++, so I would like to keep it more consistent with those usage. If the user wants `fold_right`, it can be easily written, like this: `auto fold_right = compose(compress, flip)`.
However, compress and reverse_compress work like this:
compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello"
I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would prefer to use `fold` and `reverse_fold`. You are folding the parameter pack. I like those. and I would like also fold_left, fold_right.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing:
reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers);
With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it. Would pipe works better?
pipe could work, I don't know how others feel about such a name.
indirect() could more easily be understood if it were called deref() or dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep the name consistent with other similar constructs.
Could you tell us what indirect is in range-v3, what is the signature and the semantics. I don't see it is used for the same purposes, but maybe I'm wrong.
It dereferences the values in a range. They both are functors that will dereference the parametrized type.
ref(flv) is a callable that wraps an lv indirect(ptr) is a callable that wraps a ptr and deref it before calling
Well, it can be used with anything that is dereferenceable. So it works with boost::optional as well.
deref/dereference will only relate the dereference action, but not the call.
deref_on_call is too long?
Yes it is.
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor. Maybe you could explain why curry is not a good name here? What is the difference between partial an currying a function.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I agree conditional is not a good name.
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead. This function is related to match. The difference is that one select the best matching overload and those must be exclusive and the other the first matching overload and the match can be inclusive. I would like to see the semantics on the name, but I have not a concrete proposal.
We need to take in account that this is an adaptor, so there is no call, so `invoke_first` or call_first_off will not work. Those functions creates another function object that applies a different algorithm to select the overloaded functions when called.
I've used overload -> match first_overload -> conditional
I would prefer `match` and `linear`.
* Documentation
The Quick Start section is good at showing a compelling motivating case for using the library. The result at the end of the section looks like a
very
good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is
I prefer those, but I'm not yet happy with. Naming is difficult. part
of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation. I agree hat it is absolutely needed to show the exact expansion, but it is clear that the user wants to know what is behind the scenes.
I strongly disagree. Documentation is about documenting the interface, not the implementation. Mainly because the implementation could change or vary between platforms while the interface would remain the same. If the user wants to know about the implementation, they can look at the source code or in the case of macros, look at the preprocessed output.
This will justify their use for those users that want them, and show those that don't
they can write instead.
I could show an alternative without the macros, but I would prefer to
what put
that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats. I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Being able to declare the functions as global variables is a key feature of the library. The example in the quick start guide shows how to implement many things without the need for a lot of template boilerplate. For those users that prefer not to use global function objects(and I have yet seen a compelling reason not to use them) can look at the 'Advanced' section.
I realize now, that I need to spend more discussing the advantages of using global function objects(more composability), and address some of the misperceived issues with these global function objects(they can be in a namespace to avoid name conflicts and are not mutable so it won't be a problem in mutilthreaded environments).
You do need to explain why "global variables" is a key feature of the library. I cannot understand from your docs why there is any disadvantage instantiating Callables locally as opposed to globally other than the usual fact that a local object can go out of scope. If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here. If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables. Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
On Thursday, March 10, 2016 at 5:26:38 PM UTC-6, Edward Diener wrote:
On 3/10/2016 1:46 PM, Paul Fultz II wrote:
On Thursday, March 10, 2016 at 12:34:26 AM UTC-6, Vicente J. Botet
wrote:
Le 10/03/2016 01:12, Paul Fultz II a écrit :
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you
write:
`sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`,
I
read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name. `by` is a comparator while `projet` is a projection.
By is a projection in the example of `construct`.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress()
should
be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results:
foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world" It would be good to use a binary function that has two different types to see the difference in behavior.
Yes, you could do `flip(_+_)` to get the same effect. However, I want the callback to be consistent with compress reverse_compress, so it always is called with f(state, element). Also, `fold` and `reverse_fold` are commonly used names in C++, so I would like to keep it more consistent with those usage. If the user wants `fold_right`, it can be easily written, like
`auto fold_right = compose(compress, flip)`.
However, compress and reverse_compress work like this:
compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello"
I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would
to
use `fold` and `reverse_fold`. You are folding the parameter pack. I like those. and I would like also fold_left, fold_right.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing:
reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers);
With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it. Would pipe works better?
pipe could work, I don't know how others feel about such a name.
indirect() could more easily be understood if it were called deref()
or
dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep
Escriba this: prefer the
name consistent with other similar constructs.
Could you tell us what indirect is in range-v3, what is the signature and the semantics. I don't see it is used for the same purposes, but maybe I'm wrong.
It dereferences the values in a range. They both are functors that will dereference the parametrized type.
ref(flv) is a callable that wraps an lv indirect(ptr) is a callable that wraps a ptr and deref it before calling
Well, it can be used with anything that is dereferenceable. So it works with boost::optional as well.
deref/dereference will only relate the dereference action, but not the call.
deref_on_call is too long?
Yes it is.
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor. Maybe you could explain why curry is not a good name here? What is the difference between partial an currying a function.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I agree conditional is not a good name.
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead. This function is related to match. The difference is that one select the best matching overload and those must be exclusive and the other the first matching overload and the match can be inclusive. I would like to see the semantics on the name, but I have not a concrete proposal.
We need to take in account that this is an adaptor, so there is no call, so `invoke_first` or call_first_off will not work. Those functions creates another function object that applies a different algorithm to select the overloaded functions when called.
I've used overload -> match first_overload -> conditional
I would prefer `match` and `linear`.
I prefer those, but I'm not yet happy with. Naming is difficult.
* Documentation
The Quick Start section is good at showing a compelling motivating
case
for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is part of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation. I agree hat it is absolutely needed to show the exact expansion, but it is clear that the user wants to know what is behind the scenes.
I strongly disagree. Documentation is about documenting the interface, not the implementation. Mainly because the implementation could change or vary between platforms while the interface would remain the same. If the user wants to know about the implementation, they can look at the source code or in the case of macros, look at the preprocessed output.
This will justify their use for those users that want them, and show those that don't
they can write instead.
I could show an alternative without the macros, but I would prefer to
what put
that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats. I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Being able to declare the functions as global variables is a key feature of the library. The example in the quick start guide shows how to implement many things without the need for a lot of template boilerplate. For those users that prefer not to use global function objects(and I have yet seen a compelling reason not to use them) can look at the 'Advanced' section.
I realize now, that I need to spend more discussing the advantages of using global function objects(more composability), and address some of the misperceived issues with these global function objects(they can be in a namespace to avoid name conflicts and are not mutable so it won't be a problem in mutilthreaded environments).
You do need to explain why "global variables" is a key feature of the library. I cannot understand from your docs why there is any disadvantage instantiating Callables locally as opposed to globally other than the usual fact that a local object can go out of scope.
We can define functions by simply composing other functions together and we
don't need to write awkward syntax nor template boilerplate for it. For
example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay,
constructstd::tuple());
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple()
{
return by(decay, constructstd::tuple());
}
Of course this requires the user to write `make_tuple()(xs...)`, which I
don't
think any user would expect this, especially since `std::make_tuple` doesn't
work like that. So instead, it can be wrapped in a template function:
template
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important. Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of the library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were: 1) Namespacing - This is not a problem because all global function objects can be placed in a namespace. 2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects. I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
On 3/10/2016 7:50 PM, Paul Fultz II wrote:
On Thursday, March 10, 2016 at 5:26:38 PM UTC-6, Edward Diener wrote:
On 3/10/2016 1:46 PM, Paul Fultz II wrote:
On Thursday, March 10, 2016 at 12:34:26 AM UTC-6, Vicente J. Botet
wrote:
Le 10/03/2016 01:12, Paul Fultz II a écrit :
On Wednesday, March 9, 2016 at 8:50:01 AM UTC-6, Zach Laine wrote:
There are a number of adaptors that I find have obscure names:
by() is a projection; why not call it project()?
I chose `by` because of how it is used linguistically. That is you
write:
`sort(v.begin(), v.end(), by(&Employee::Name, _<_))` which I read "sort by employee name". Also, if I write `by(decay, constructstd::tuple())`,
I
read "construct tuple by decay". In haskell, it uses `on` for this adaptor, which is what I originally called it. `project` is a more straighforward name. `by` is a comparator while `projet` is a projection.
By is a projection in the example of `construct`.
compress() is defined in terms of "fold", with a link to the Wikipedia page for same; why not call it foldl()? Similarly, reverse_compress()
should
be foldr().
Not `foldr`. This is because `foldr` is symetrical. For example, `foldl` and `foldr` should produce the same results:
foldl(_+_, std::string())("Hello", "-", "world"); // "Hello-world" foldr(_+_, std::string())("Hello", "-", "world"); // "Hello-world" It would be good to use a binary function that has two different types to see the difference in behavior.
Yes, you could do `flip(_+_)` to get the same effect. However, I want the callback to be consistent with compress reverse_compress, so it always is called with f(state, element). Also, `fold` and `reverse_fold` are commonly used names in C++, so I would like to keep it more consistent with those usage. If the user wants `fold_right`, it can be easily written, like
`auto fold_right = compose(compress, flip)`.
However, compress and reverse_compress work like this:
compress(_+_, std::string())("Hello", "-", "world"); // "Hello-world" reverse_compress(_+_, std::string())("Hello", "-", "world"); // "world-Hello"
I was reluctant to call it `fold` as it seems to imply some data structure to fold over, whereas this is simply an adaptor. I used the word compress as its a another name used for fold. However, it seems more people would
to
use `fold` and `reverse_fold`. You are folding the parameter pack. I like those. and I would like also fold_left, fold_right.
flow() is a super obscure name! Why is this not reverse_compose() or similar?
I actually got the name from underscore.js. The reason why I chose this name instead of `reverse_compose` is because when this is used with pipable functions it seems confusing:
reverse_compose( filter([](int i) { return i < 3; }), transfrom([](int i) { return i*i; }) )(numbers);
With the word 'reverse' there, it almost looks as if it computes the pipeline in reverse order, which it doesn't. So I would prefer a name without reverse in it. Would pipe works better?
pipe could work, I don't know how others feel about such a name.
indirect() could more easily be understood if it were called deref()
or
dereference().
Well, `indirect` is how it is commonly called in libraries such as Boost.Range, range-v3, and PStade libraries. So I would like to keep
Escriba this: prefer the
name consistent with other similar constructs.
Could you tell us what indirect is in range-v3, what is the signature and the semantics. I don't see it is used for the same purposes, but maybe I'm wrong.
It dereferences the values in a range. They both are functors that will dereference the parametrized type.
ref(flv) is a callable that wraps an lv indirect(ptr) is a callable that wraps a ptr and deref it before calling
Well, it can be used with anything that is dereferenceable. So it works with boost::optional as well.
deref/dereference will only relate the dereference action, but not the call.
deref_on_call is too long?
Yes it is.
I don't feel as strongly about partial(), but I think it might be clearer if it were called partial_apply() or curry().
Hmm, I don't think a name with `apply` in it is good name for an adaptor. Maybe you could explain why curry is not a good name here? What is the difference between partial an currying a function.
conditional() should be called invoke_first(), call_first_of(), or similar. I find it too easy to confuse with if_().
I agree conditional is not a good name.
I see, I was trying to describe an adaptor where you could put functions with conditions in it. Other people, seem to prefer a name like `linear`, so I might use that instead. This function is related to match. The difference is that one select the best matching overload and those must be exclusive and the other the first matching overload and the match can be inclusive. I would like to see the semantics on the name, but I have not a concrete proposal.
We need to take in account that this is an adaptor, so there is no call, so `invoke_first` or call_first_off will not work. Those functions creates another function object that applies a different algorithm to select the overloaded functions when called.
I've used overload -> match first_overload -> conditional
I would prefer `match` and `linear`.
I prefer those, but I'm not yet happy with. Naming is difficult.
* Documentation
The Quick Start section is good at showing a compelling motivating
case
for using the library. The result at the end of the section looks like a very good start on a "Scrap Your Boilerplate" solution for simple, dump-style printing. It's relatively easy to read and write.
The documentation is what needs the most work, from what I've seen. The Quick Start is quite nice, but then there are some things that are underexplained in the subsequent Overview and Basic Concepts sections. For instance, why are these macros used everywhere and what do they do? They should be used for the first time after showing what the expanded code looks like.
I don't think the docs should show the expansion of the macros, that is part of the implementation and not interface. I could show an "idea" what is is expanding to, with explanation of what else it is doing beyond the simple explanation. I agree hat it is absolutely needed to show the exact expansion, but it is clear that the user wants to know what is behind the scenes.
I strongly disagree. Documentation is about documenting the interface, not the implementation. Mainly because the implementation could change or vary between platforms while the interface would remain the same. If the user wants to know about the implementation, they can look at the source code or in the case of macros, look at the preprocessed output.
This will justify their use for those users that want them, and show those that don't
they can write instead.
I could show an alternative without the macros, but I would prefer to
what put
that in the Advance section. In the introduction of the library, I would rather show the constructs that can be easily used without a need to explain a bunch of caveats. I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Being able to declare the functions as global variables is a key feature of the library. The example in the quick start guide shows how to implement many things without the need for a lot of template boilerplate. For those users that prefer not to use global function objects(and I have yet seen a compelling reason not to use them) can look at the 'Advanced' section.
I realize now, that I need to spend more discussing the advantages of using global function objects(more composability), and address some of the misperceived issues with these global function objects(they can be in a namespace to avoid name conflicts and are not mutable so it won't be a problem in mutilthreaded environments).
You do need to explain why "global variables" is a key feature of the library. I cannot understand from your docs why there is any disadvantage instantiating Callables locally as opposed to globally other than the usual fact that a local object can go out of scope.
We can define functions by simply composing other functions together and we don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ? I thought I would be able to write locally: auto make_tuple(by(decay,constructstd::tuple())); but evidently you are saying that does not work.
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple() { return by(decay, constructstd::tuple()); }
Of course this requires the user to write `make_tuple()(xs...)`, which I don't think any user would expect this, especially since `std::make_tuple` doesn't work like that. So instead, it can be wrapped in a template function:
template
constexpr auto make_tuple(Ts&&... xs) -> declype(by(decay, constructstd::tuple())(std::forward<Ts>(xs)...)) { return by(decay, constructstd::tuple())(std::forward<Ts>(xs)...); } Which is a lot of boilerplate to compose some simple functions.
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important.
Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of the library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
1) Namespacing - This is not a problem because all global function objects can be placed in a namespace.
2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects.
I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
We can define functions by simply composing other functions together and
we
don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all. If `std::make_tuple` was assigned to a local variable then no one could ever call the function. So you use global function objects when you want to make the function available for consumption by others. And the advantage of defining `make_tuple` this way over writing it as it is written currently in the standard library, is that we can avoid all the template boilerplate involved with this.
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple() { return by(decay, constructstd::tuple()); }
Of course this requires the user to write `make_tuple()(xs...)`, which I don't think any user would expect this, especially since `std::make_tuple`
work like that. So instead, it can be wrapped in a template function:
template
constexpr auto make_tuple(Ts&&... xs) -> declype(by(decay, constructstd::tuple())(std::forward<Ts>(xs)...)) { return by(decay, constructstd::tuple())(std::forward<Ts>(xs)...); } Which is a lot of boilerplate to compose some simple functions.
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important.
Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of
doesn't the
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
1) Namespacing - This is not a problem because all global function objects can be placed in a namespace.
2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects.
I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 3/11/2016 12:10 AM, Paul Fultz II wrote:
We can define functions by simply composing other functions together and
we
don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
std::tuple>`. I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all. If `std::make_tuple` was assigned to a local variable then no one could ever call the function.
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ? I don't mind if you like your programming style but I doubt I am the first or last programmer who prefers variables created and used when needed. In fact I have a very strong suspicion that "my" programming style is that of the majority of C++ programmers and that only a minority of programmers like global variables even if they can be global within a namespace scope. So what I am essentially saying is that while I understand you like your style and your macros for creating "global" callables, inferring that this is central to using your library, when it is not AFAICS, is going to lose you end-users as well as misrepresent your library. I see much more plusses to your library if the result of using your adapters, decorators, functions, and utilities can be instantiated anywhere and used in ways that any variable in C++ can be used. if however there are real technical limitations, as opposed to "I like this style because I it is easier for me to use with Fit", of using Fit functionality to create "local" objects I think you need to spell out what those limitations are.
So you use global function objects when you want to make the function available for consumption by others. And the advantage of defining `make_tuple` this way over writing it as it is written currently in the standard library, is that we can avoid all the template boilerplate involved with this.
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple() { return by(decay, constructstd::tuple()); }
Of course this requires the user to write `make_tuple()(xs...)`, which I don't think any user would expect this, especially since `std::make_tuple`
work like that. So instead, it can be wrapped in a template function:
template
constexpr auto make_tuple(Ts&&... xs) -> declype(by(decay, constructstd::tuple())(std::forward<Ts>(xs)...)) { return by(decay, constructstd::tuple())(std::forward<Ts>(xs)...); } Which is a lot of boilerplate to compose some simple functions.
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important.
Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of
doesn't the
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
1) Namespacing - This is not a problem because all global function objects can be placed in a namespace.
2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects.
I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
Le 11/03/2016 07:18, Edward Diener a écrit :
On 3/11/2016 12:10 AM, Paul Fultz II wrote:
We can define functions by simply composing other functions together and
we
don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
std::tuple>`. I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all. If `std::make_tuple` was assigned to a local variable then no one could ever call the function.
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ? I don't mind if you like your programming style but I doubt I am the first or last programmer who prefers variables created and used when needed. In fact I have a very strong suspicion that "my" programming style is that of the majority of C++ programmers and that only a minority of programmers like global variables even if they can be global within a namespace scope. So what I am essentially saying is that while I understand you like your style and your macros for creating "global" callables, inferring that this is central to using your library, when it is not AFAICS, is going to lose you end-users as well as misrepresent your library. I see much more plusses to your library if the result of using your adapters, decorators, functions, and utilities can be instantiated anywhere and used in ways that any variable in C++ can be used. if however there are real technical limitations, as opposed to "I like this style because I it is easier for me to use with Fit", of using Fit functionality to create "local" objects I think you need to spell out what those limitations are. Eduard, I don't know if you are aware of the Eric's paper [1] about customization points. In this papers Eric presents the technique use by Fit to define global function objects. These global variables have no modifiable state and are just like functions, but are high order functions. Eric received a very good feedback from C++ committee and AFAIK the range-v3 library [2] and the Range TS [3] are based on this technique.
I believe that it will be better if Paul show what is behind those macros by writing what the user could do without. E.g. the static_const ODR helper lets the user just write |constexpr auto const & begin = boost::static_const<__detail::__begin_fn>;| Paul goes one step far, by defining some macros that help to do that. Best, Vicente [1] N4381 - Suggested Design for Customization Points http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html [2] https://github.com/ericniebler/range-v3 [3] N4569 - Proposed Ranges TS working draft http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf
On Friday, March 11, 2016 at 1:02:15 AM UTC-6, Vicente J. Botet Escriba wrote:
Le 11/03/2016 07:18, Edward Diener a écrit :
On 3/11/2016 12:10 AM, Paul Fultz II wrote:
We can define functions by simply composing other functions together and
we
don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
std::tuple>`. I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all. If `std::make_tuple` was assigned to a local variable then no one could ever call the function.
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ? I don't mind if you like your programming style but I doubt I am the first or last programmer who prefers variables created and used when needed. In fact I have a very strong suspicion that "my" programming style is that of the majority of C++ programmers and that only a minority of programmers like global variables even if they can be global within a namespace scope. So what I am essentially saying is that while I understand you like your style and your macros for creating "global" callables, inferring that this is central to using your library, when it is not AFAICS, is going to lose you end-users as well as misrepresent your library. I see much more plusses to your library if the result of using your adapters, decorators, functions, and utilities can be instantiated anywhere and used in ways that any variable in C++ can be used. if however there are real technical limitations, as opposed to "I like this style because I it is easier for me to use with Fit", of using Fit functionality to create "local" objects I think you need to spell out what those limitations are. Eduard, I don't know if you are aware of the Eric's paper [1] about customization points. In this papers Eric presents the technique use by Fit to define global function objects. These global variables have no modifiable state and are just like functions, but are high order functions. Eric received a very good feedback from C++ committee and AFAIK the range-v3 library [2] and the Range TS [3] are based on this technique.
I believe that it will be better if Paul show what is behind those macros by writing what the user could do without. E.g. the static_const ODR helper lets the user just write
I think I should add a section on "Declaring functions" that discusses this. In the Quick Start guide or introduction, it would be better to describe it as a declaring a function(which can refer to the "Declaring functions" section) instead of using the term global variable as it can be interpreted differently.
|constexpr auto const & begin = boost::static_const<__detail::__begin_fn>;|
Paul goes one step far, by defining some macros that help to do that.
This is to help portability and initialization for all platforms.
Best, Vicente
[1] N4381 - Suggested Design for Customization Points http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html [2] https://github.com/ericniebler/range-v3 [3] N4569 - Proposed Ranges TS working draft http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On Friday, March 11, 2016 at 12:19:15 AM UTC-6, Edward Diener wrote:
On 3/11/2016 12:10 AM, Paul Fultz II wrote:
We can define functions by simply composing other functions together
we
don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
std::tuple>`. I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all. If `std::make_tuple` was assigned to a local variable
and then no
one could ever call the function.
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ?
Its not about programming style. Its about showing what the library is fully
capable of doing. For example, say you are library writer and have just
written a tuple class:
template
I don't mind if you like your programming style but I doubt I am the first or last programmer who prefers variables created and used when needed.
Have you ever used `fopen`? That is an object that gets defined at compile- time, not when you use it. That is when you write: void foo() { std::cout << "foo"; } You have just created a global function object, it occupies memory, you can take its address: auto* address_of_foo = &foo; Futhermore, this object wasn't initialized when you used it, it was initialized at compile-time. The `BOOST_FIT_STATIC_FUNCTION` works exactly the same way: struct foo_fn { void operator()() const { std::cout << "foo"; } }; BOOST_FIT_STATIC_FUNCTION(foo) = foo_fn(); So, `foo` gets initialized at compile-time, and you can take its address: auto* address_of_foo = &foo; So I dont see how you could say that `void foo()` is acceptable and `BOOST_FIT_STATIC_FUNCTION(foo)` since they work in the same way.
In fact I have a very strong suspicion that "my" programming style is that of the majority of C++ programmers and that only a minority of programmers like global variables even if they can be global within a namespace scope.
Perhaps, I should use different terminolgy to describe `BOOST_FIT_STATIC_FUNCTION`. Maybe it might be better to say that it creates a function(which it does) rather than say it create a global variable, since some developers may associate global variable with other things that are not related to `BOOST_FIT_STATIC_FUNCTION`.
So what I am essentially saying is that while I understand you like your style and your macros for creating "global" callables, inferring that this is central to using your library, when it is not AFAICS, is going to lose you end-users as well as misrepresent your library. I see much more plusses to your library if the result of using your adapters, decorators, functions, and utilities can be instantiated anywhere and used in ways that any variable in C++ can be used. if however there are real technical limitations, as opposed to "I like this style because I it is easier for me to use with Fit", of using Fit functionality to create "local" objects I think you need to spell out what those limitations are.
Its not a technical limitation, but I want to make sure to show the full depth of the capability of what the library can do. If you do not want to use the full capability, then thats ok, but there is no there is nothing problematic or dangerous defining functions using `BOOST_FIT_STATIC_FUNCTION`.
So you use global function objects when you want to make the function available for consumption by others. And the advantage of defining `make_tuple` this way over writing it as it is written currently in the standard library, is that we can avoid all the template boilerplate involved with this.
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple() { return by(decay, constructstd::tuple()); }
Of course this requires the user to write `make_tuple()(xs...)`, which
don't think any user would expect this, especially since `std::make_tuple` doesn't work like that. So instead, it can be wrapped in a template function:
template
constexpr auto make_tuple(Ts&&... xs) -> declype(by(decay, constructstd::tuple())(std::forward<Ts>(xs)...)) { return by(decay, constructstd::tuple())(std::forward<Ts>(xs)...); } Which is a lot of boilerplate to compose some simple functions.
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather
acting like your library does not work correctly somehow other than with global variables.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important.
Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of
I than the
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
1) Namespacing - This is not a problem because all global function objects can be placed in a namespace.
2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects.
I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Edward Diener wrote:
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ?
No. Your assignment is to define a make_tuple function in a header, say "make_tuple.hpp", so that other people can use it. This is supposedly a part from a library that allows the user to manipulate tuples. I don't understand your confusion and your reference to programming styles.
On 3/11/2016 4:52 AM, Peter Dimov wrote:
Edward Diener wrote:
I don't understand this. I have a 'make_tuple' callable above and if I want to use it somewhere else I pass it to whatever functionality needs it. You, instead want things to be global so that they can be used by other functionality without passing anything around. Isn't this just an argument about programming style ?
No.
Your assignment is to define a make_tuple function in a header, say "make_tuple.hpp", so that other people can use it. This is supposedly a part from a library that allows the user to manipulate tuples. I don't understand your confusion and your reference to programming styles.
My confusion comes from the fact that I did not realize that Fit was actually defining a function. I thought instead that Fit was taking a Callable and ghenerating another Callable when using Fit's functionality, in much the same way that boost::bind does. I know that the macro in Fit is called BOOST_FIT_STATIC_FUNCTION ( or BOOST_FIT_STATIC_LAMBDA ) but the term 'function' in the Fit documentation is so often used when Callable is the actual correction designation that I thought that this was one more instance of this usage. Thanks for straightening me out with your explanation.
Le 11/03/2016 06:10, Paul Fultz II a écrit :
We can define functions by simply composing other functions together and we don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple()); What is the type of by(decay,constructstd::tuple()) ?
It is something like `by_adaptor
std::tuple>`. I thought I would be able to write locally:
auto make_tuple(by(decay,constructstd::tuple()));
but evidently you are saying that does not work.
That is not what I am saying at all. It can work, the functions can be used locally, like in the first example. However, for `make_tuple` this is not useful at all.
Let people consider what is useful or not. Naming the local functions could make the code more readable sometimes.
If `std::make_tuple` was assigned to a local variable then no one could ever call the function. So you use global function objects when you want to make the function available for consumption by others. And the advantage of defining `make_tuple` this way over writing it as it is written currently in the standard library, is that we can avoid all the template boilerplate involved with this. This is style question that should be out of the scope of the library. The library must show what can be done with it.
Vicente
Le 11/03/2016 01:50, Paul Fultz II a écrit :
On Thursday, March 10, 2016 at 5:26:38 PM UTC-6, Edward Diener wrote:
On 3/10/2016 1:46 PM, Paul Fultz II wrote:
On Thursday, March 10, 2016 at 12:34:26 AM UTC-6, Vicente J. Botet
wrote:
I will put all of them in the advanced section. I would no mention them in the introduction as the user can create the HOF locally or use a factory.
Being able to declare the functions as global variables is a key feature of the library. The example in the quick start guide shows how to implement many things without the need for a lot of template boilerplate. For those users that prefer not to use global function objects(and I have yet seen a compelling reason not to use them) can look at the 'Advanced' section.
I realize now, that I need to spend more discussing the advantages of using global function objects(more composability), and address some of the misperceived issues with these global function objects(they can be in a namespace to avoid name conflicts and are not mutable so it won't be a problem in mutilthreaded environments). You do need to explain why "global variables" is a key feature of the
Escriba library. I cannot understand from your docs why there is any disadvantage instantiating Callables locally as opposed to globally other than the usual fact that a local object can go out of scope.
We can define functions by simply composing other functions together and we don't need to write awkward syntax nor template boilerplate for it. For example, we can simply write `make_tuple` like this:
BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, constructstd::tuple());
Alternatively, you could write a factory function for this instead:
constexpr auto make_tuple() { return by(decay, constructstd::tuple()); }
This function should be names maker_tuple at it builds an adaptor :)
Of course this requires the user to write `make_tuple()(xs...)`, which I don't think any user would expect this, especially since `std::make_tuple` doesn't work like that. So instead, it can be wrapped in a template function:
template
constexpr auto make_tuple(Ts&&... xs) -> declype(by(decay, constructstd::tuple())(std::forward<Ts>(xs)...)) { return by(decay, constructstd::tuple())(std::forward<Ts>(xs)...); } Which is a lot of boilerplate to compose some simple functions.
These are the kind of things that can be described in an advanced section?
If there really is some other reason it is completely lost to me. If you would like to point to me in your doc where you explain the use of "global variables" as being a key feature of your library or as being necessary to use the functionality of your library, I would be glad to read about it and ask further about it here.
It is not a necessary feature, but it is an important feature nonetheless, and I need to spend more time explaining its usefulness.
I believe all agree that the feature is useful for those that want to declare global HOFs. Some don't like global variables representing functions other don't like macros. The library is useful for those people too.
If, OTOH, it is just your preference to use global objects as opposed to the various forms of local instantiation, I really wish you would just say that rather than acting like your library does not work correctly somehow other than with global variables.
This is not the case. You can declare local variables for these HOF or define functions that return these HOF. What is not simple with the current language is to declare them as global variables, and this is the design rationale for those macros.
The composability of the adaptors and functions applies to both global and local functions. The adaptors provide a simple way to create functions to be passed locally, such as creating a comparator for std::sort(ie `std::sort(first, last, by(&employee::name, _<_))`). However, they can also be used to define global/free functions in a much simpler way as well(such as writing `std::make_tuple`), which I think is equally important.
Furthermore, in a quick start guide or introduction, I want to be able to demonstrate the capabilities of the library in a way to show its usefulness, and why someone would choose to use this library. Perhaps, you are ok with awkward syntax or template boilerplate in writing your functions, and would prefer to only use the adaptors for local functions. However, there are others who will find writing global function objects with the adaptors very useful, so I want to be able to show those capabilities in the introduction of the library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
1) Namespacing - This is not a problem because all global function objects can be placed in a namespace. This is obvious. There is no issue here.
2) Global state - This is not a problem either, because `BOOST_FIT_STATIC_FUNCTION` declares the function object const with constexpr. So there can be no mutable or changing state internally in the object, and it must be constructed without side effects.
I think addressing these concerns in the documentation will help put users at ease when using global function objects, so they have no problem taking advantage of the adaptors to build function objects globally.
Maybe I have missed something but I have the intuition that many others have missed it likewise, from the responses of others about this issue.
I agree with your intuition, and I believe the documentation should spend more time discussing these issues. I hope my email has made it somewhat clearer to you.
Best, Vicente
On 03/11/2016 01:50 AM, Paul Fultz II wrote:
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
The destruction order of global objects in different translation units is undefined. This means that I cannot use a global fit function in the destructor of one of my own global objects because the former may have been destroyed before the latter.
On Friday, March 11, 2016 12:30 PM, Bjorn Reese
wrote: On 03/11/2016 01:50 AM, Paul Fultz II wrote:
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
The destruction order of global objects in different translation units is undefined. This means that I cannot use a global fit function in the destructor of one of my own global objects because the former may have
been destroyed before the latter.
This is not a problem, because it is initialized at compile-time, that means
it is a literal type:
http://en.cppreference.com/w/cpp/concept/LiteralType
So besides the constructor not having side-effects, the destructor must be
trivial, as well. For example, this cannot compile:
struct foo_fn
{
template
On Fri, 11 Mar 2016 19:41:22 +0000 (UTC)
paul Fultz
On Friday, March 11, 2016 12:30 PM, Bjorn Reese
wrote: On 03/11/2016 01:50 AM, Paul Fultz II wrote:
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were:
The destruction order of global objects in different translation units is undefined. This means that I cannot use a global fit function in the destructor of one of my own global objects because the former may have
been destroyed before the latter.
This is not a problem, because it is initialized at compile-time, that means it is a literal type:
http://en.cppreference.com/w/cpp/concept/LiteralType
So besides the constructor not having side-effects, the destructor must be trivial, as well. For example, this cannot compile:
struct foo_fn { template
auto operator()(Ts&&...) const {} ~foo_fn() { std::cout << "Hello"; } };
BOOST_FIT_STATIC_FUNCTION(foo) = foo_fn();
int main() { foo(); }
So with trivial destructors, the order of destruction doesn't matter. Furthermore, on most compilers(that is everything but MSVC), the function is the same across all translation units. So there is only one function in the executable, not one per translation unit. Taking the address of the function will yield the same address across all translation units. This is the same way functions work.
The following program throws an exception and aborts in Clang 3.4 and
Gcc 4.8, both on Ubuntu 14.04
#include <iostream>
#include <stdexcept>
#include
Le 13/03/2016 03:35, Lee Clagett a écrit :
On Fri, 11 Mar 2016 19:41:22 +0000 (UTC) paul Fultz
wrote: On Friday, March 11, 2016 12:30 PM, Bjorn Reese
wrote: On 03/11/2016 01:50 AM, Paul Fultz II wrote:
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were: The destruction order of global objects in different translation units is undefined. This means that I cannot use a global fit function in the destructor of one of my own global objects because the former may have been destroyed before the latter. This is not a problem, because it is initialized at compile-time, that means it is a literal type:
http://en.cppreference.com/w/cpp/concept/LiteralType
So besides the constructor not having side-effects, the destructor must be trivial, as well. For example, this cannot compile:
struct foo_fn { template
auto operator()(Ts&&...) const {} ~foo_fn() { std::cout << "Hello"; } };
BOOST_FIT_STATIC_FUNCTION(foo) = foo_fn();
int main() { foo(); }
So with trivial destructors, the order of destruction doesn't matter. Furthermore, on most compilers(that is everything but MSVC), the function is the same across all translation units. So there is only one function in the executable, not one per translation unit. Taking the address of the function will yield the same address across all translation units. This is the same way functions work.
The following program throws an exception and aborts in Clang 3.4 and Gcc 4.8, both on Ubuntu 14.04
#include <iostream> #include <stdexcept> #include
#include class log_ { public: log_() : active_(true) { std::cout << "constructing log" << std::endl; // or some equally terrible global state changing // code such as a file open }
~log_() { std::cout << "destructing log" << std::endl; active_ = false; }
void operator()(const char* const msg) const { if (active_) { std::cout << msg << std::endl; } else { throw std::runtime_error{"not active"}; } } private: bool active_; };
FIT_STATIC_FUNCTION(log) = fit::static_
{}; struct one_ { ~one_() { log("destructing one"); } };
struct two_ { two_() { log("constructing two"); } };
const one_ one{}; const two_ two{};
int main() { return 0; }
Both Gcc and Clang print:
constructing log constructing two destructing log terminate called after throwing an instance of 'std::runtime_error' what(): not active
The generated program conforms to the C++ standard because `log_` was created as a function local static, and the call to `operator()` after destruction can do _anything_ (undefined behavior). Also, `log_` can be constructed before `one_` and `two_` (and therefore destructed after both) as well, and still conform to the C++ specifications. Or at least that is my understanding of static construction/destruction, which is complex in C++.
Lee
Tanks Lee for this example. I believe that static_ needs to require that the the parameter is is_trivially_destructible [1]. In [2] we can read " The |static_| adaptor is a static function adaptor that allows any default-constructible function object to be static-initialized.". It seems the implementation doesn't do the check. Please, could you add an issue on Github? Vicente [1] http://www.cplusplus.com/reference/type_traits/is_trivially_destructible/ [2] http://pfultz2.github.io/Fit/doc/html/static/index.html
On Sunday, March 13, 2016 at 5:43:43 AM UTC-5, Vicente J. Botet Escriba wrote:
Le 13/03/2016 03:35, Lee Clagett a écrit :
On Fri, 11 Mar 2016 19:41:22 +0000 (UTC) paul Fultz
javascript:> wrote: On Friday, March 11, 2016 12:30 PM, Bjorn Reese
javascript:> wrote: On 03/11/2016 01:50 AM, Paul Fultz II wrote:
library. I do, however, need to discuss some of the misperceived issues with using global function objects in the documentation. As the issues raised in the review were: The destruction order of global objects in different translation units is undefined. This means that I cannot use a global fit function in the destructor of one of my own global objects because the former may have been destroyed before the latter. This is not a problem, because it is initialized at compile-time, that means it is a literal type:
http://en.cppreference.com/w/cpp/concept/LiteralType
So besides the constructor not having side-effects, the destructor must be trivial, as well. For example, this cannot compile:
struct foo_fn { template
auto operator()(Ts&&...) const {} ~foo_fn() { std::cout << "Hello"; } };
BOOST_FIT_STATIC_FUNCTION(foo) = foo_fn();
int main() { foo(); }
So with trivial destructors, the order of destruction doesn't matter. Furthermore, on most compilers(that is everything but MSVC), the function is the same across all translation units. So there is only one function in the executable, not one per translation unit. Taking the address of the function will yield the same address across all translation units. This is the same way functions work.
The following program throws an exception and aborts in Clang 3.4 and Gcc 4.8, both on Ubuntu 14.04
#include <iostream> #include <stdexcept> #include
#include class log_ { public: log_() : active_(true) { std::cout << "constructing log" << std::endl; // or some equally terrible global state changing // code such as a file open }
~log_() { std::cout << "destructing log" << std::endl; active_ = false; }
void operator()(const char* const msg) const { if (active_) { std::cout << msg << std::endl; } else { throw std::runtime_error{"not active"}; } } private: bool active_; };
FIT_STATIC_FUNCTION(log) = fit::static_
{}; struct one_ { ~one_() { log("destructing one"); } };
struct two_ { two_() { log("constructing two"); } };
const one_ one{}; const two_ two{};
int main() { return 0; }
Both Gcc and Clang print:
constructing log constructing two destructing log terminate called after throwing an instance of 'std::runtime_error' what(): not active
The generated program conforms to the C++ standard because `log_` was created as a function local static, and the call to `operator()` after destruction can do _anything_ (undefined behavior). Also, `log_` can be constructed before `one_` and `two_` (and therefore destructed after both) as well, and still conform to the C++ specifications. Or at least that is my understanding of static construction/destruction, which is complex in C++.
Lee
Tanks Lee for this example.
I believe that static_ needs to require that the the parameter is is_trivially_destructible [1].
In [2] we can read " The |static_| adaptor is a static function adaptor that allows any default-constructible function object to be static-initialized.".
It seems the implementation doesn't do the check. Please, could you add an issue on Github?
The `static_` adaptor is designed to initialize functions that may not have
trivial or constexpr constructors. Ideally `static_<F>` should be the
equivalent to this:
template
Vicente
[1] http://www.cplusplus.com/reference/type_traits/is_trivially_destructible/ [2] http://pfultz2.github.io/Fit/doc/html/static/index.html
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (9)
-
Bjorn Reese
-
Edward Diener
-
Lee Clagett
-
paul Fultz
-
Paul Fultz II
-
Peter Dimov
-
Steven Watanabe
-
Vicente J. Botet Escriba
-
Zach Laine