Boost.Hana - Type Classes, data types and categories (tags).
Hi Louis,
There is a lot of very good work on your library.
I have some comments in relation on how I have implemented Type Classes
in my prototype.
About datatype<T>. The library proposes type classes and data types. The
name datatype_t<> is confusing as the datatype<T> meta-functions must
not return one of the defined data types. I see it much more as a
category, which is used to map the data type to a category used as
parameter of the instantiation
TC::Instance
Vicente J. Botet Escriba
Hi Louis,
There is a lot of very good work on your library.
Thank you!
I have some comments in relation on how I have implemented Type Classes in my prototype.
You wrote a type class emulation in C++? Could you please share the link; I'd like to have a look!
About datatype<T>. The library proposes type classes and data types. The name datatype_t<> is confusing as the datatype<T> meta-functions must not return one of the defined data types. I see it much more as a category, which is used to map the data type to a category used as parameter of the instantiation
TC::Instance
.
IIUC, you're pointing out that `datatype_t
In addition, I wonder if the category associated shouldn't depend on the type class we want to map.
TC::Instance
>
I'm not sure why that would be useful. Do you have a use case in mind?
About naming: Haskell doesn't have namespaces and all the functions are at the same scope. Your library makes use of a lot of non-member functions, and so name clashing must be avoided. What about moving all the non-member functions of the Type Class to a specific namespace? E.g.
struct Monad { ... };
namespace monad { using namespace applicatives; ... bind() };
The use would need to use the type class namespace explicitly
auto x = monad::bind(m, f)
or introduce the namespace of the type class
using namespace monad; auto x = bind(m, f);
I had not thought about the issue because right now, name clashing is avoided simply by not having two functions with the same name. This has worked well so far. Since this is the simplest way to do things, I'd rather keep it that way. Of course, if I need to introduce a function whose name would clash, then I will strongly consider your suggestion; thank you for that. Regards, Louis
Le 17/09/14 22:49, Louis Dionne a écrit :
Vicente J. Botet Escriba
writes: Hi Louis,
There is a lot of very good work on your library. Thank you!
I have some comments in relation on how I have implemented Type Classes in my prototype. You wrote a type class emulation in C++? Could you please share the link; I'd like to have a look! The ideas are similar. The form changes.
https://github.com/ptal/expected https://github.com/ptal/expected/tree/master/include/boost/functional
About datatype<T>. The library proposes type classes and data types. The name datatype_t<> is confusing as the datatype<T> meta-functions must not return one of the defined data types. I see it much more as a category, which is used to map the data type to a category used as parameter of the instantiation
TC::Instance
. IIUC, you're pointing out that `datatype_t ` is usually not the same as `decltype(object)`, which is inconsistent with the way the term "Data type" is used in Hakell. If so, you are right about the inconsistency. Regardless of this particular issue, I'll have to rename some stuff in Hana as others have pointed out. I haven't done it yet since I haven't found names that are incredibly better than "data type" so far, but I'm adding "category" to the list. Another candidate is "tag", which would have the additional benefit of being familiar to Fusion and MPL people.
Why not.
In addition, I wonder if the category associated shouldn't depend on the type class we want to map.
TC::Instance
> I'm not sure why that would be useful. Do you have a use case in mind?
After more thoughts the user can always specialize TC::Instance<T> inheriting from TC::Instance<CatA>. struct TC::Instance<T> : TC::Instance<CatA> {};
About naming: Haskell doesn't have namespaces and all the functions are at the same scope. Your library makes use of a lot of non-member functions, and so name clashing must be avoided. What about moving all the non-member functions of the Type Class to a specific namespace? E.g.
struct Monad { ... };
namespace monad { using namespace applicatives; ... bind() };
The use would need to use the type class namespace explicitly
auto x = monad::bind(m, f)
or introduce the namespace of the type class
using namespace monad; auto x = bind(m, f); I had not thought about the issue because right now, name clashing is avoided simply by not having two functions with the same name. This has worked well so far. Since this is the simplest way to do things, I'd rather keep it that way. Of course, if I need to introduce a function whose name would clash, then I will strongly consider your suggestion; thank you for that.
You are welcome, Vicente
Le 17/09/14 23:30, Vicente J. Botet Escriba a écrit :
Le 17/09/14 22:49, Louis Dionne a écrit :
Vicente J. Botet Escriba
writes: In addition, I wonder if the category associated shouldn't depend on the type class we want to map.
TC::Instance
> I'm not sure why that would be useful. Do you have a use case in mind? After more thoughts the user can always specialize TC::Instance<T> inheriting from TC::Instance<CatA>.
struct TC::Instance<T> : TC::Instance<CatA> {};
A deeper analysis of your code let me think that you have already
something similar to what I was requesting.
#define
BOOST_HANA_TYPECLASS(NAME) \
/** @cond
*/ \
template
Vicente J. Botet Escriba
Le 17/09/14 23:30, Vicente J. Botet Escriba a écrit :
Le 17/09/14 22:49, Louis Dionne a écrit :
Vicente J. Botet Escriba
writes: In addition, I wonder if the category associated shouldn't depend on the type class we want to map.
TC::Instance
> I'm not sure why that would be useful. Do you have a use case in mind? After more thoughts the user can always specialize TC::Instance<T> inheriting from TC::Instance<CatA>.
struct TC::Instance<T> : TC::Instance<CatA> {};
I think I don't understand what you are trying to achieve here. What exactly are CatA and T? What effect do you want to achieve in doing such a type class instantiation (instantiation in Hana/Haskell terms).
A deeper analysis of your code let me think that you have already something similar to what I was requesting.
[...]
The previous specialization let the data type class define directly how is it could be seen as an instance of a given type class NAME. I see however that this has been used only fro the Record type class.
boost/hana/record/macros.hpp: struct hana_Record : ::boost::hana::Record::mcd { \
So here you have the use_case you were requesting.
Given a data type D, it is possible to instantiate any unary type class TC from inside D's definition as follows: struct D { struct hana_TC { // type class definition }; }; This is true for all unary type classes. However, since the instantiation of the Record type class is very straightforward, a macro is provided to make it even easier. Note that the ability to instantiate a type class from inside a data type's definition is not documented right now. Regards, Louis
On 17 Sep 2014 at 20:49, Louis Dionne wrote:
I had not thought about the issue because right now, name clashing is avoided simply by not having two functions with the same name. This has worked well so far. Since this is the simplest way to do things, I'd rather keep it that way. Of course, if I need to introduce a function whose name would clash, then I will strongly consider your suggestion; thank you for that.
namespaces can be used for disambiguation, but they are actually much more useful and powerful than that. You can map namespaces into other namespaces, mash up namespaces into customised variants and with template aliasing you can now easily metaprogram namespacing too, so you could have the results of a Hana operation cause one set of things to be mapped into some destination namespace as against another set of things. And then, you see, a bind() function can actually have metaprogrammed meaning and implementation which is not only neato, but very intuitive for the end user. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Niall Douglas
On 17 Sep 2014 at 20:49, Louis Dionne wrote:
I had not thought about the issue because right now, name clashing is avoided simply by not having two functions with the same name. This has worked well so far. Since this is the simplest way to do things, I'd rather keep it that way. Of course, if I need to introduce a function whose name would clash, then I will strongly consider your suggestion; thank you for that.
namespaces can be used for disambiguation, but they are actually much more useful and powerful than that. You can map namespaces into other namespaces, mash up namespaces into customised variants and with template aliasing you can now easily metaprogram namespacing too, so you could have the results of a Hana operation cause one set of things to be mapped into some destination namespace as against another set of things. And then, you see, a bind() function can actually have metaprogrammed meaning and implementation which is not only neato, but very intuitive for the end user.
I'm not sure I understand you. I do understand how we can map namespaces into other namespaces: namespace A { } namespace B = A; I also understand how we can mash up namespaces: namespace A { } namespace B { } namespace mash { using namespace A; using namespace B; } But I don't understand how template aliases come into play and give me more flexibility. Would you care to give a basic example of what you meant? Thanks, Louis
On Sat, Sep 20, 2014 at 5:03 PM, Louis Dionne
Niall Douglas
writes: namespaces can be used for disambiguation, but they are actually much more useful and powerful than that. You can map namespaces into other namespaces, mash up namespaces into customised variants and with template aliasing you can now easily metaprogram namespacing too, so you could have the results of a Hana operation cause one set of things to be mapped into some destination namespace as against another set of things. And then, you see, a bind() function can actually have metaprogrammed meaning and implementation which is not only neato, but very intuitive for the end user.
I'm not sure I understand you. I do understand how we can map namespaces into other namespaces:
namespace A { } namespace B = A;
I also understand how we can mash up namespaces:
namespace A { } namespace B { } namespace mash { using namespace A; using namespace B; }
But I don't understand how template aliases come into play and give me more flexibility. Would you care to give a basic example of what you meant?
Thanks, Louis
I'm not sure what Niall is referring to, but namespaces come into handy when using the swap trick described in effective STL: template<typename T> library_function(T& a, T& b) { using std::swap; swap(a,b); // will use specialized swap in namespace of T if available } Benedek
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Benedek Thaler
On Sat, Sep 20, 2014 at 5:03 PM, Louis Dionne
wrote: [...] I'm not sure what Niall is referring to, but namespaces come into handy when using the swap trick described in effective STL:
template<typename T> library_function(T& a, T& b) { using std::swap; swap(a,b); // will use specialized swap in namespace of T if available }
Yes, ADL can prove itself useful. However, Hana uses tag dispatching like Boost.Fusion and Boost.MPL for customization because it deals with objects of heterogeneous types. Since the actual C++ type of some objects can be impossible or very cumbersome to pattern match, I don't think ADL is the right way to go in our case. Regards, Louis
On 20 Sep 2014 at 17:13, Benedek Thaler wrote:
I'm not sure what Niall is referring to, but namespaces come into handy when using the swap trick described in effective STL:
template<typename T> library_function(T& a, T& b) { using std::swap; swap(a,b); // will use specialized swap in namespace of T if available }
I'm not a fan of publicly exposed ADL usage personally. It's too brittle - the non-specialised swap may get called inadvertently and performance suffer in a highly unobvious way to detect. For example, if T is not perfectly a type in namespace X (e.g. it's a trivial descendent), any X::swap() will *not* get invoked even when it would make absolute sense. I am personally a huge fan of when expected optimisations fail to apply, it should never be silent. Preferably a compile time error, but I am not adverse to an abort() either. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
On 20 Sep 2014 at 15:03, Louis Dionne wrote:
I had not thought about the issue because right now, name clashing is avoided simply by not having two functions with the same name. This has worked well so far. Since this is the simplest way to do things, I'd rather keep it that way. Of course, if I need to introduce a function whose name would clash, then I will strongly consider your suggestion; thank you for that.
namespaces can be used for disambiguation, but they are actually much more useful and powerful than that. You can map namespaces into other namespaces, mash up namespaces into customised variants and with template aliasing you can now easily metaprogram namespacing too, so you could have the results of a Hana operation cause one set of things to be mapped into some destination namespace as against another set of things. And then, you see, a bind() function can actually have metaprogrammed meaning and implementation which is not only neato, but very intuitive for the end user.
I'm not sure I understand you. I do understand how we can map namespaces into other namespaces:
namespace A { } namespace B = A;
I also understand how we can mash up namespaces:
namespace A { } namespace B { } namespace mash { using namespace A; using namespace B; }
But I don't understand how template aliases come into play and give me more flexibility. Would you care to give a basic example of what you meant?
The way I look at it is this: templated types are templated
namespaces with restrictions on what you can put in them (aside: I
have never understood why you can't template namespaces and use
namespaces for inheritance, including types and other namespaces),
while namespaces let you draw arbitrary lines around segments of API
and mux them as appropriate into different default visibility
according to point of use.
Let us take an example:
namespace A
{
template<class T> using vector = select_vector_impl
participants (4)
-
Benedek Thaler
-
Louis Dionne
-
Niall Douglas
-
Vicente J. Botet Escriba