Trouble splitting up source into multiple files, when using data abstraction.

Hi all, I’ve defined a typeclass, LogTree, and would like to put each instance definition in its own source file, in order that LogTree.hs not grow to a ridiculous length. I’m attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don’t make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain “helper” functions, instead. However, the individual instance definitions do need access to these data constructors, but they’re in a different source file. Is this possible? That is, is it possible to provide different export lists to “friendly” vs. “unknown” client code? Thanks, -db

Hi David,
I think you can do something like providing a LogTree.Internal module which
you use internally and which exports everything you need, and making your
LogTree module which re-exports only the safe subset which "unknown" code
would then import. I don't think there is a way of stopping anyone from
importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
Hi all,
I’ve defined a typeclass, *LogTree*, and would like to put each instance definition in its own source file, in order that *LogTree.hs *not grow to a ridiculous length. I’m attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don’t make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain “helper” functions, instead. However, the individual instance definitions *do* need access to these data constructors, but they’re in a different source file.
*Is this possible? That is, is it possible to provide different export lists to “friendly” vs. “unknown” client code?*
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

It's possible to restrict LogTree.Internal to be visible only within the
package (see cabal's other-modules field), but please don't do so. You
will inevitably fail at providing every necessary function or accommodating
all reasonable uses of your library. Then some user will either write
their own, use something else or, if you're lucky, fork your library and
send you a merge request. It will happen (unless you have no other users).
As a recent example, I was using a library that had an internal data
structure, 'A' (completely unexposed) as part of an exposed data structure
'B'. Both had 'deriving Generic' clauses. However, the Generic instance
of 'B' was mostly useless because if you wanted to use the Generic instance
to declare e.g. 'instance Binary B', it's also necessary to create a Binary
instance for 'A'. Which was impossible for users, because 'A' was
completely unexposed. When I pointed this out, the library's author
graciously accepted a patch to expose the type constructor 'A' (I'm not
entirely happy with this solution either, but for now we can't think of
anything better).
You could take this as an implementation flaw of Generic as it's currently
implemented (which it possibly is), however I would hope you also take it
as a demonstration of how data abstraction can interact with many different
language features in often-subtle ways. I think erring on the side of
allowing users maximum freedom is the best choice for now (preferably
stashed in modules/functions with names like 'internal' or 'unsafe').
John
On Sun, Apr 13, 2014 at 7:02 AM, Luke Clifton
Hi David,
I think you can do something like providing a LogTree.Internal module which you use internally and which exports everything you need, and making your LogTree module which re-exports only the safe subset which "unknown" code would then import. I don't think there is a way of stopping anyone from importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
wrote: Hi all,
I've defined a typeclass, *LogTree*, and would like to put each instance definition in its own source file, in order that *LogTree.hs *not grow to a ridiculous length. I'm attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don't make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain "helper" functions, instead. However, the individual instance definitions *do* need access to these data constructors, but they're in a different source file.
*Is this possible? That is, is it possible to provide different export lists to "friendly" vs. "unknown" client code?*
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

A concern, to be sure. On the flip side, it might be better to know what
you are actually exposing (also meaning fewer things would constitute a
breaking change).
On Apr 13, 2014 5:00 PM, "John Lato"
It's possible to restrict LogTree.Internal to be visible only within the package (see cabal's other-modules field), but please don't do so. You will inevitably fail at providing every necessary function or accommodating all reasonable uses of your library. Then some user will either write their own, use something else or, if you're lucky, fork your library and send you a merge request. It will happen (unless you have no other users).
As a recent example, I was using a library that had an internal data structure, 'A' (completely unexposed) as part of an exposed data structure 'B'. Both had 'deriving Generic' clauses. However, the Generic instance of 'B' was mostly useless because if you wanted to use the Generic instance to declare e.g. 'instance Binary B', it's also necessary to create a Binary instance for 'A'. Which was impossible for users, because 'A' was completely unexposed. When I pointed this out, the library's author graciously accepted a patch to expose the type constructor 'A' (I'm not entirely happy with this solution either, but for now we can't think of anything better).
You could take this as an implementation flaw of Generic as it's currently implemented (which it possibly is), however I would hope you also take it as a demonstration of how data abstraction can interact with many different language features in often-subtle ways. I think erring on the side of allowing users maximum freedom is the best choice for now (preferably stashed in modules/functions with names like 'internal' or 'unsafe').
John
On Sun, Apr 13, 2014 at 7:02 AM, Luke Clifton
wrote: Hi David,
I think you can do something like providing a LogTree.Internal module which you use internally and which exports everything you need, and making your LogTree module which re-exports only the safe subset which "unknown" code would then import. I don't think there is a way of stopping anyone from importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
wrote: Hi all,
I've defined a typeclass, *LogTree*, and would like to put each instance definition in its own source file, in order that *LogTree.hs *not grow to a ridiculous length. I'm attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don't make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain "helper" functions, instead. However, the individual instance definitions *do* need access to these data constructors, but they're in a different source file.
*Is this possible? That is, is it possible to provide different export lists to "friendly" vs. "unknown" client code?*
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On 14 April 2014 10:22, David Thomas
A concern, to be sure. On the flip side, it might be better to know what you are actually exposing (also meaning fewer things would constitute a breaking change).
There is the convention that "if it's in a .Internal module, then you're on your own: this module doesn't contribute towards breaking changes in the API".
On Apr 13, 2014 5:00 PM, "John Lato"
wrote: It's possible to restrict LogTree.Internal to be visible only within the package (see cabal's other-modules field), but please don't do so. You will inevitably fail at providing every necessary function or accommodating all reasonable uses of your library. Then some user will either write their own, use something else or, if you're lucky, fork your library and send you a merge request. It will happen (unless you have no other users).
As a recent example, I was using a library that had an internal data structure, 'A' (completely unexposed) as part of an exposed data structure 'B'. Both had 'deriving Generic' clauses. However, the Generic instance of 'B' was mostly useless because if you wanted to use the Generic instance to declare e.g. 'instance Binary B', it's also necessary to create a Binary instance for 'A'. Which was impossible for users, because 'A' was completely unexposed. When I pointed this out, the library's author graciously accepted a patch to expose the type constructor 'A' (I'm not entirely happy with this solution either, but for now we can't think of anything better).
You could take this as an implementation flaw of Generic as it's currently implemented (which it possibly is), however I would hope you also take it as a demonstration of how data abstraction can interact with many different language features in often-subtle ways. I think erring on the side of allowing users maximum freedom is the best choice for now (preferably stashed in modules/functions with names like 'internal' or 'unsafe').
John
On Sun, Apr 13, 2014 at 7:02 AM, Luke Clifton
wrote: Hi David,
I think you can do something like providing a LogTree.Internal module which you use internally and which exports everything you need, and making your LogTree module which re-exports only the safe subset which "unknown" code would then import. I don't think there is a way of stopping anyone from importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
wrote: Hi all,
I’ve defined a typeclass, LogTree, and would like to put each instance definition in its own source file, in order that LogTree.hs not grow to a ridiculous length. I’m attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don’t make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain “helper” functions, instead. However, the individual instance definitions do need access to these data constructors, but they’re in a different source file.
Is this possible? That is, is it possible to provide different export lists to “friendly” vs. “unknown” client code?
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com http://IvanMiljenovic.wordpress.com

yup! its accepted convention by the community that *.Internal Modules are not subject to ANY API stability guarantees (kinda like the GHC apis :) ) On Sun, Apr 13, 2014 at 8:25 PM, Ivan Lazar Miljenovic < ivan.miljenovic@gmail.com> wrote:
On 14 April 2014 10:22, David Thomas
wrote: A concern, to be sure. On the flip side, it might be better to know what you are actually exposing (also meaning fewer things would constitute a breaking change).
There is the convention that "if it's in a .Internal module, then you're on your own: this module doesn't contribute towards breaking changes in the API".
On Apr 13, 2014 5:00 PM, "John Lato"
wrote: It's possible to restrict LogTree.Internal to be visible only within the package (see cabal's other-modules field), but please don't do so. You
will
inevitably fail at providing every necessary function or accommodating all reasonable uses of your library. Then some user will either write their own, use something else or, if you're lucky, fork your library and send you a merge request. It will happen (unless you have no other users).
As a recent example, I was using a library that had an internal data structure, 'A' (completely unexposed) as part of an exposed data structure 'B'. Both had 'deriving Generic' clauses. However, the Generic instance of 'B' was mostly useless because if you wanted to use the Generic instance to declare e.g. 'instance Binary B', it's also necessary to create a Binary instance for 'A'. Which was impossible for users, because 'A' was completely unexposed. When I pointed this out, the library's author graciously accepted a patch to expose the type constructor 'A' (I'm not entirely happy with this solution either, but for now we can't think of anything better).
You could take this as an implementation flaw of Generic as it's currently implemented (which it possibly is), however I would hope you also take it as a demonstration of how data abstraction can interact with many different language features in often-subtle ways. I think erring on the side of allowing users maximum freedom is the best choice for now (preferably stashed in modules/functions with names like 'internal' or 'unsafe').
John
On Sun, Apr 13, 2014 at 7:02 AM, Luke Clifton
wrote: Hi David,
I think you can do something like providing a LogTree.Internal module which you use internally and which exports everything you need, and
making
your LogTree module which re-exports only the safe subset which "unknown" code would then import. I don't think there is a way of stopping anyone from importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
wrote: Hi all,
I’ve defined a typeclass, LogTree, and would like to put each instance definition in its own source file, in order that LogTree.hs not grow
to a
ridiculous length. I’m attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don’t make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain “helper” functions, instead. However, the individual instance definitions do need access to these data constructors, but they’re in a different source file.
Is this possible? That is, is it possible to provide different export lists to “friendly” vs. “unknown” client code?
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com http://IvanMiljenovic.wordpress.com _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Fair enough! Seems still a good practice to get any functionality you're
using into the stable API, but certainly being able to get things done in
the meanwhile is not a bad thing.
On Apr 13, 2014 5:28 PM, "Carter Schonwald"
yup!
its accepted convention by the community that *.Internal Modules are not subject to ANY API stability guarantees (kinda like the GHC apis :) )
On Sun, Apr 13, 2014 at 8:25 PM, Ivan Lazar Miljenovic < ivan.miljenovic@gmail.com> wrote:
On 14 April 2014 10:22, David Thomas
wrote: A concern, to be sure. On the flip side, it might be better to know what you are actually exposing (also meaning fewer things would constitute a breaking change).
There is the convention that "if it's in a .Internal module, then you're on your own: this module doesn't contribute towards breaking changes in the API".
On Apr 13, 2014 5:00 PM, "John Lato"
wrote: It's possible to restrict LogTree.Internal to be visible only within
package (see cabal's other-modules field), but please don't do so. You will inevitably fail at providing every necessary function or accommodating all reasonable uses of your library. Then some user will either write
the their
own, use something else or, if you're lucky, fork your library and send you a merge request. It will happen (unless you have no other users).
As a recent example, I was using a library that had an internal data structure, 'A' (completely unexposed) as part of an exposed data structure 'B'. Both had 'deriving Generic' clauses. However, the Generic instance of 'B' was mostly useless because if you wanted to use the Generic instance to declare e.g. 'instance Binary B', it's also necessary to create a Binary instance for 'A'. Which was impossible for users, because 'A' was completely unexposed. When I pointed this out, the library's author graciously accepted a patch to expose the type constructor 'A' (I'm not entirely happy with this solution either, but for now we can't think of anything better).
You could take this as an implementation flaw of Generic as it's currently implemented (which it possibly is), however I would hope you also take it as a demonstration of how data abstraction can interact with many different language features in often-subtle ways. I think erring on the side of allowing users maximum freedom is the best choice for now (preferably stashed in modules/functions with names like 'internal' or 'unsafe').
John
On Sun, Apr 13, 2014 at 7:02 AM, Luke Clifton
wrote: Hi David,
I think you can do something like providing a LogTree.Internal module which you use internally and which exports everything you need, and
making
your LogTree module which re-exports only the safe subset which "unknown" code would then import. I don't think there is a way of stopping anyone from importing your LogTree.Internal module though.
On Sun, Apr 13, 2014 at 9:42 PM, David Banas
wrote: Hi all,
I've defined a typeclass, LogTree, and would like to put each
instance
definition in its own source file, in order that LogTree.hs not grow to a ridiculous length. I'm attempting to use data abstraction, in order to future-proof the user interface to this class. So, for instance, I don't make all of the data constructors defined in LogTree accessible, via the module export list, but rather force the user to use certain "helper" functions, instead. However, the individual instance definitions do need access to these data constructors, but they're in a different source file.
Is this possible? That is, is it possible to provide different export lists to "friendly" vs. "unknown" client code?
Thanks, -db
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com http://IvanMiljenovic.wordpress.com _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Sun, Apr 13, 2014 at 9:42 AM, David Banas
*Is this possible? That is, is it possible to provide different export lists to “friendly” vs. “unknown” client code?*
Not directly. The usual convention is you put all your definitions in a .Internal module which is understood to be intended only for restricted use, although there is currently no way to enforce this; the public module(s) then imports that and re-exports the public interface. It might be interesting to have some way to enforce this, possibly by a generalization of the Safe mechanism. It's not clear how well this can be enforced, in any case; if it's available across modules at all, there will be some way for "untrusted" code to get at it. I'm not sure anyone wants to pay the costs of complexity (in particular of cross-platform, which is already somewhat dicey at times) or limited flexibility to provide harder guarantees; in particular, there are times when you really do need access to another package's internals, with the result that some packages that used to hide their internals completely are now starting to export Internals modules. -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
participants (7)
-
Brandon Allbery
-
Carter Schonwald
-
David Banas
-
David Thomas
-
Ivan Lazar Miljenovic
-
John Lato
-
Luke Clifton