where to put general-purpose utility functions

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|

where to put general-purpose utility functions

Joey Hess
I'm finding a rather unusual problem as I write haskell..

Unlike every other language I've used, large portions of my haskell code
are turning out to be general-purpose, reusable code. Fully 20% of the
haskell code I've written for git-annex is general purpose. Now, I came out
of a decade of perl with maybe 1% reusable code. So I'm sure this is a
credit to haskell, and not to me.

My problem now is that as I start new projects, I want to have my haskell
utility functions available, and copying them around is not ideal. So, put
them on hackage. But where, exactly? It already has several grab bag utility
libraries. The only one with much traction is MissingH. Using the others
makes a program have an unusual dependency, which while only a cabal
install away, would make work for distributions that want to package the
program. I've ruled out using a couple on that basis. Doesn't encourage me
to add another one.

My 2000+ lines of reusable code are a grab-bag of generic utility
functions. Looking them over (see Appendix), I could try to get portions
into existing libraries on hackage, but it's unlikely I'd find a home
for most of them, so I'm still left with this problem of what to do.

I wonder if the model used for xmonad-contrib, of a big library package,
that is very open to additions from contributors, would be helpful here?

John, any interest in moving MissingH in this direction? I get the
impression it's not otherwise changing much lately, and parts of it are
becoming naturally obsolete, maybe this could inject some life into it.
Any other thoughts you have on grab-bag utility libraries on hackage
also appreciated.

----

Appendix: A sample of a a few of the better functions from my utility library.

  Some quite generic monadic control functions, few of them truely unique:

  whenM :: Monad m => m Bool -> m () -> m ()   -- also >>?
  unlessM :: Monad m => m Bool -> m () -> m () -- also >>!
  firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
 
  A module that exports functions conflicting with partial
  functions in the Prelude, to avoid them being accidentially
  used. And provides some alternatives (which overlap somewhat with Safe):
 
  headMaybe :: [a] -> Maybe a
  readMaybe :: Read a => String -> Maybe a
  beginning :: [a] -> [a]

  Various path manipulation functions such as:
 
  dirContains :: FilePath -> FilePath -> Bool
  dotfile :: FilePath -> Bool
  absPath :: FilePath -> IO FilePath

  Other stuff:

  separate :: (a -> Bool) -> [a] -> ([a], [a])
  catchMaybeIO :: IO a -> IO (Maybe a)
  readSize :: [Unit] -> String -> Maybe ByteSize -- parses "100 kb" etc
  format :: Format -> Variables -> String
  findPubKeys :: String -> IO GpgKeyIds
  boolSystem :: FilePath -> [CommandParam] -> IO Bool
  withTempFile :: Template -> (FilePath -> Handle -> IO a) -> IO a

--
see shy jo

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe

signature.asc (845 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Mike Burns
On 2012-01-21 16.20.25 -0400, Joey Hess wrote:
> My problem now is that as I start new projects, I want to have my haskell
> utility functions available, and copying them around is not ideal. So, put
> them on hackage. But where, exactly?

Instead of putting all of them in one package, how about you group them
into multiple relevant packages. That way we can search for them more
easily, have smaller, more meaningful imports, and so on. Sometimes they
may make sense as pull requests against other packages, instead.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Tristan Ravitch
In reply to this post by Joey Hess
You might find many of these on hackage in various forms already.. it
might be easier to just depend on some of those libraries.

On Sat, Jan 21, 2012 at 04:20:25PM -0400, Joey Hess wrote:
>
>   Some quite generic monadic control functions, few of them truely unique:
>
>   whenM :: Monad m => m Bool -> m () -> m ()   -- also >>?
>   unlessM :: Monad m => m Bool -> m () -> m () -- also >>!
>   firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)

IfElse (http://hackage.haskell.org/packages/archive/IfElse/0.85/doc/html/Control-Monad-IfElse.html)
has a few of these.

>   Various path manipulation functions such as:
>   absPath :: FilePath -> IO FilePath

Is this different from canonicalizePath in directory?

>   Other stuff:
>
>   separate :: (a -> Bool) -> [a] -> ([a], [a])

Is this partition from Data.List?

>   format :: Format -> Variables -> String

This looks like it might be similar to HStringTemplate

>   withTempFile :: Template -> (FilePath -> Handle -> IO a) -> IO a

temporary (http://hackage.haskell.org/packages/archive/temporary/1.1.2.3/doc/html/System-IO-Temp.html)
has a few variants of this one

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe

attachment0 (196 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Simon Hengel
In reply to this post by Joey Hess
>   headMaybe :: [a] -> Maybe a

Is this the same as Data.Maybe.maybeToList?

>   readMaybe :: Read a => String -> Maybe a

This has been added to base recently [1].

Cheers,
Simon

[1] https://github.com/ghc/packages-base/commit/0e1a02b96cfd03b8488e3ff4ce232466d6d5ca77

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Christoph Breitkopf
One thing I found useful when looking if a function already exists under a different name is to use Hayoo to search for the type, i.e.:


- Chris

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Thiago Negri
There is also Hoogle, pretty equivalent I guess.

http://www.haskell.org/hoogle/

Thiago.

2012/1/21 Christoph Breitkopf <[hidden email]>:

> One thing I found useful when looking if a function already exists under a
> different name is to use Hayoo to search for the type, i.e.:
>
> http://holumbus.fh-wedel.de/hayoo/hayoo.html#0:(a%20-%3E%20Bool)%20-%3E%20%5Ba%5D%20-%3E%20(%5Ba%5D%2C%5Ba%5D)
>
> - Chris
>
> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Christoph Breitkopf
In reply to this post by Christoph Breitkopf
On Sat, Jan 21, 2012 at 11:08 PM, Christoph Breitkopf <[hidden email]> wrote:
One thing I found useful when looking if a function already exists under a different name is to use Hayoo to search for the type, i.e.:


Uh - please ignore the bogus link - I had blindly assumed that it would show my search results.

Just use http://holumbus.fh-wedel.de/hayoo/hayoo.html to search for (a -> Bool) -> [a] -> ([a],[a]), for eaxample, and it will find break, span, partiion, etc.

- Chris


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

David Fox-7
In reply to this post by Joey Hess
I try to create a workflow for this sort of thing.  I create a package
with a name like set-extra, with one module Data.Set.Extra and an
alternative Data.Set module that exports both the old Data.Set and the
symbols in Data.Set.Extra.  Then I email the maintainers of the
Containers package with a suggestion.  After a while I upload
set-extra to hackage if I need to use set-extra in another hackage
package.

On Sat, Jan 21, 2012 at 12:20 PM, Joey Hess <[hidden email]> wrote:

> I'm finding a rather unusual problem as I write haskell..
>
> Unlike every other language I've used, large portions of my haskell code
> are turning out to be general-purpose, reusable code. Fully 20% of the
> haskell code I've written for git-annex is general purpose. Now, I came out
> of a decade of perl with maybe 1% reusable code. So I'm sure this is a
> credit to haskell, and not to me.
>
> My problem now is that as I start new projects, I want to have my haskell
> utility functions available, and copying them around is not ideal. So, put
> them on hackage. But where, exactly? It already has several grab bag utility
> libraries. The only one with much traction is MissingH. Using the others
> makes a program have an unusual dependency, which while only a cabal
> install away, would make work for distributions that want to package the
> program. I've ruled out using a couple on that basis. Doesn't encourage me
> to add another one.
>
> My 2000+ lines of reusable code are a grab-bag of generic utility
> functions. Looking them over (see Appendix), I could try to get portions
> into existing libraries on hackage, but it's unlikely I'd find a home
> for most of them, so I'm still left with this problem of what to do.
>
> I wonder if the model used for xmonad-contrib, of a big library package,
> that is very open to additions from contributors, would be helpful here?
>
> John, any interest in moving MissingH in this direction? I get the
> impression it's not otherwise changing much lately, and parts of it are
> becoming naturally obsolete, maybe this could inject some life into it.
> Any other thoughts you have on grab-bag utility libraries on hackage
> also appreciated.
>
> ----
>
> Appendix: A sample of a a few of the better functions from my utility library.
>
>  Some quite generic monadic control functions, few of them truely unique:
>
>  whenM :: Monad m => m Bool -> m () -> m ()   -- also >>?
>  unlessM :: Monad m => m Bool -> m () -> m () -- also >>!
>  firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
>
>  A module that exports functions conflicting with partial
>  functions in the Prelude, to avoid them being accidentially
>  used. And provides some alternatives (which overlap somewhat with Safe):
>
>  headMaybe :: [a] -> Maybe a
>  readMaybe :: Read a => String -> Maybe a
>  beginning :: [a] -> [a]
>
>  Various path manipulation functions such as:
>
>  dirContains :: FilePath -> FilePath -> Bool
>  dotfile :: FilePath -> Bool
>  absPath :: FilePath -> IO FilePath
>
>  Other stuff:
>
>  separate :: (a -> Bool) -> [a] -> ([a], [a])
>  catchMaybeIO :: IO a -> IO (Maybe a)
>  readSize :: [Unit] -> String -> Maybe ByteSize -- parses "100 kb" etc
>  format :: Format -> Variables -> String
>  findPubKeys :: String -> IO GpgKeyIds
>  boolSystem :: FilePath -> [CommandParam] -> IO Bool
>  withTempFile :: Template -> (FilePath -> Handle -> IO a) -> IO a
>
> --
> see shy jo
>
> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Jean-Marie Gaillourdet-3
In reply to this post by Joey Hess
Hi,

On 21.01.2012, at 21:20, Joey Hess wrote:

> My problem now is that as I start new projects, I want to have my haskell
> utility functions available, and copying them around is not ideal. So, put
> them on hackage. But where, exactly? It already has several grab bag utility
> libraries. The only one with much traction is MissingH. Using the others
> makes a program have an unusual dependency, which while only a cabal
> install away, would make work for distributions that want to package the
> program. I've ruled out using a couple on that basis. Doesn't encourage me
> to add another one.
>
> My 2000+ lines of reusable code are a grab-bag of generic utility
> functions. Looking them over (see Appendix), I could try to get portions
> into existing libraries on hackage, but it's unlikely I'd find a home
> for most of them, so I'm still left with this problem of what to do.
>
> I wonder if the model used for xmonad-contrib, of a big library package,
> that is very open to additions from contributors, would be helpful here?
>
> John, any interest in moving MissingH in this direction? I get the
> impression it's not otherwise changing much lately, and parts of it are
> becoming naturally obsolete, maybe this could inject some life into it.
> Any other thoughts you have on grab-bag utility libraries on hackage
> also appreciated.

Personally, I've always been avoiding those grab-bags of functionality like MissingH and other libraries. Not because I think they don't provide anything useful. But, because their level of maintenance is not clear to me. A rather large library of utility functions tends to need many dependencies on other hackage packages. That makes the question of maintenance even more important.

As others have pointed out some of your functions may already exist in some widely used package. And other might be easy to be replaced by some idiom. Don't underestimate the depth of Haskell and it's well thought libraries. I am regularly amazed by finding some new way to combine seemingly trivial functions to do some non-trivial task. Every time that happens I can remove some of my utility functions.

Therefore, I would reuse my own collection of utility code as a separate repository to be included as a sub repository in other projects. Mercurial and Git support that very well, I am not sure about darcs' support for that. This approach allows you to avoid copy&paste reuse and it allows you to evolve your personal collection at your speed without worrying for backwards compatibility or API changes.

Publishing a library on hackage comes --- at least in an ideal world --- with some commitment to document it, keep it compiling and working with a set of compiler and library permutations, fix bugs and so on. In short it comes with a commitment to maintain it. At least for some time. If you would just like to drop some pile of code in hope someone will find it useful. Do that, but perhaps there might be better places for that than hackage.

Cheers,
  Jean



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Joey Hess
In reply to this post by Tristan Ravitch
David Fox wrote:
> I try to create a workflow for this sort of thing.  I create a package
> with a name like set-extra, with one module Data.Set.Extra and an
> alternative Data.Set module that exports both the old Data.Set and the
> symbols in Data.Set.Extra.  Then I email the maintainers of the
> Containers package with a suggestion.  After a while I upload
> set-extra to hackage if I need to use set-extra in another hackage
> package.

Thanks, that's the most helpful hint.
It matches MissingH's use of .Utils modules too.

Jean-Marie Gaillourdet wrote:
> Personally, I've always been avoiding those grab-bags of functionality like
> MissingH and other libraries. Not because I think they don't provide anything
> useful. But, because their level of maintenance is not clear to me. A rather
> large library of utility functions tends to need many dependencies on other
> hackage packages. That makes the question of maintenance even more important.

It's not clear to me either. I used MissingH starting out because I
personally know and trust John and/or he cowrote RWH.
Don't know that I would have otherwise.

(And I only use Data.String.Utils, System.Cmd.Utils, and Data.Bits.Utils
from it.)

> As others have pointed out some of your functions may already exist in some
> widely used package. And other might be easy to be replaced by some idiom. Don't
> underestimate the depth of Haskell and it's well thought libraries. I am
> regularly amazed by finding some new way to combine seemingly trivial functions
> to do some non-trivial task. Every time that happens I can remove some of my
> utility functions.

Well, this is certianly true, on the other hand then you end up with a
pattern of repeatedly combining some trivial functions in a certian way,
and it then makes sense to formalize that. It's better to have `fromMaybe`
than to repeatedly use `id` with `maybe`.

Tristan Ravitch wrote:
> >   whenM :: Monad m => m Bool -> m () -> m ()   -- also >>?
> >   unlessM :: Monad m => m Bool -> m () -> m () -- also >>!
> >   firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
>
> IfElse (http://hackage.haskell.org/packages/archive/IfElse/0.85/doc/html/Control-Monad-IfElse.html)
> has a few of these.

whenM is in a dozen packages, the others fewer but scattered here and
there. I also found >>? and >>! somewhere on hackage once but not sure
where.

> >   Various path manipulation functions such as:
> >   absPath :: FilePath -> IO FilePath
>
> Is this different from canonicalizePath in directory?

Yes; it doesn't require the path to exist.

> >   Other stuff:
> >
> >   separate :: (a -> Bool) -> [a] -> ([a], [a])
>
> Is this partition from Data.List?

No; it's like break but does not include the separating character in the
snd list.

> >   format :: Format -> Variables -> String
>
> This looks like it might be similar to HStringTemplate

This particular format allows for things like "${foo} ${bar;10} ${baz;-10}\n"
so it's sort of printf like, but not entirely.

> >   withTempFile :: Template -> (FilePath -> Handle -> IO a) -> IO a
>
> temporary (http://hackage.haskell.org/packages/archive/temporary/1.1.2.3/doc/html/System-IO-Temp.html)
> has a few variants of this one

Indeed, however all its functions can fail if getTemporaryDirectory
fails; this one puts the temp file in "." in that case.

Simon Hengel wrote:
> >   headMaybe :: [a] -> Maybe a
>
> Is this the same as Data.Maybe.maybeToList?

Rather listToMaybe.. it is the same as that in fact.
Though I also have a lastMaybe that does not have an equivilant in Data.Maybe.

> >   readMaybe :: Read a => String -> Maybe a
>
> This has been added to base recently [1].

Great! Although there are multiple ways to choose to implement this.
I found it useful to make it succeed even if not all the string was
consumed, or when there are multiple valid results. I've renamed
mine readish.

--
eee shy jo

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe

signature.asc (845 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

David Fox-7
On Mon, Jan 23, 2012 at 1:01 PM, Joey Hess <[hidden email]> wrote:

>> >   Other stuff:
>> >
>> >   separate :: (a -> Bool) -> [a] -> ([a], [a])
>>
>> Is this partition from Data.List?
>
> No; it's like break but does not include the separating character in the
> snd list.

I like  let (hd, _ : tl) = break prd lst in...

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

David Fox-7
On Tue, Jan 24, 2012 at 1:00 PM, David Fox <[hidden email]> wrote:

> On Mon, Jan 23, 2012 at 1:01 PM, Joey Hess <[hidden email]> wrote:
>
>>> >   Other stuff:
>>> >
>>> >   separate :: (a -> Bool) -> [a] -> ([a], [a])
>>>
>>> Is this partition from Data.List?
>>
>> No; it's like break but does not include the separating character in the
>> snd list.
>
> I like  let (hd, _ : tl) = break prd lst in...

Oh, wait.  That won't always work. :(

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

Chris Wong
>> I like  let (hd, _ : tl) = break prd lst in...
>
> Oh, wait.  That won't always work. :(
>

second (drop 1) . break prd list?

:)

> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/haskell-cafe

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

John Goerzen-3
In reply to this post by Joey Hess
Hi Joey,

Apologies for such a late reply.  I don't know if others have replied on
-cafe; it has become too high-volume for me to follow of late.  There
are several options.  MissingH is one.  I have accepted patches there
for a long time.  Another is that now that Hackage/Cabal foster easy
inclusion of small packages, you might want to roll your own.  
(Occasionally I have hesitation about drive-by commits to MissingH
because I get asked to support the code later at times.)

-- John

On 01/21/2012 02:20 PM, Joey Hess wrote:

> I'm finding a rather unusual problem as I write haskell..
>
> Unlike every other language I've used, large portions of my haskell code
> are turning out to be general-purpose, reusable code. Fully 20% of the
> haskell code I've written for git-annex is general purpose. Now, I came out
> of a decade of perl with maybe 1% reusable code. So I'm sure this is a
> credit to haskell, and not to me.
>
> My problem now is that as I start new projects, I want to have my haskell
> utility functions available, and copying them around is not ideal. So, put
> them on hackage. But where, exactly? It already has several grab bag utility
> libraries. The only one with much traction is MissingH. Using the others
> makes a program have an unusual dependency, which while only a cabal
> install away, would make work for distributions that want to package the
> program. I've ruled out using a couple on that basis. Doesn't encourage me
> to add another one.
>
> My 2000+ lines of reusable code are a grab-bag of generic utility
> functions. Looking them over (see Appendix), I could try to get portions
> into existing libraries on hackage, but it's unlikely I'd find a home
> for most of them, so I'm still left with this problem of what to do.
>
> I wonder if the model used for xmonad-contrib, of a big library package,
> that is very open to additions from contributors, would be helpful here?
>
> John, any interest in moving MissingH in this direction? I get the
> impression it's not otherwise changing much lately, and parts of it are
> becoming naturally obsolete, maybe this could inject some life into it.
> Any other thoughts you have on grab-bag utility libraries on hackage
> also appreciated.
>
> ----
>
> Appendix: A sample of a a few of the better functions from my utility library.
>
>    Some quite generic monadic control functions, few of them truely unique:
>
>    whenM :: Monad m =>  m Bool ->  m () ->  m ()   -- also>>?
>    unlessM :: Monad m =>  m Bool ->  m () ->  m () -- also>>!
>    firstM :: Monad m =>  (a ->  m Bool) ->  [a] ->  m (Maybe a)
>
>    A module that exports functions conflicting with partial
>    functions in the Prelude, to avoid them being accidentially
>    used. And provides some alternatives (which overlap somewhat with Safe):
>
>    headMaybe :: [a] ->  Maybe a
>    readMaybe :: Read a =>  String ->  Maybe a
>    beginning :: [a] ->  [a]
>
>    Various path manipulation functions such as:
>
>    dirContains :: FilePath ->  FilePath ->  Bool
>    dotfile :: FilePath ->  Bool
>    absPath :: FilePath ->  IO FilePath
>
>    Other stuff:
>
>    separate :: (a ->  Bool) ->  [a] ->  ([a], [a])
>    catchMaybeIO :: IO a ->  IO (Maybe a)
>    readSize :: [Unit] ->  String ->  Maybe ByteSize -- parses "100 kb" etc
>    format :: Format ->  Variables ->  String
>    findPubKeys :: String ->  IO GpgKeyIds
>    boolSystem :: FilePath ->  [CommandParam] ->  IO Bool
>    withTempFile :: Template ->  (FilePath ->  Handle ->  IO a) ->  IO a
>

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: where to put general-purpose utility functions

John Meacham
In reply to this post by Joey Hess
That is one of the wonderful things about haskell, most languages have
a negative correlation between codesize and productivity, however with
haskell there is a strong positive correlation. You can re-use so much
that as your code base grows it becomes easier to add new features
rather than harder.

I solve that problem by having a darcs repository for all my utility
modules, then when I want to use them in a project, I just 'darcs
pull' them into the current repository and they appear. as long as a
patch doesn't cross the boundry between the utility modules and the
rest of the codebase I can push changes upstream to the utility
repository from my specific project, or pull in new improvements to
the utility functions. I can also make local modifications to the
utility modules and just not push the local modifications upstream if
needed. The cool thing is that the darcs history has the merged
history of my projects, and tagging with a version number or release
will snapshot both your program and the exact version of the utility
routines used at that time.

The ability to have multiple "sibling" darcs repositories is a really
powerful feature. quite handy.

I had a similar system before darcs where I used a shared RCS
directory between projects, but that wasn't nearly as seamless or
integrated.

Plus it makes it easier when collaborating, as far as anyone is
concerned, the project is a single darcs repository they can get and
build and create patches for. The fact that behind the scenes it is
actually a collection of repositories spawning patches. modifying
them, and exchanging them in a bizarre parody of bacterial plasmid
exchange* is completely transparent.

    John

* stolen prose, I just liked the sound of it.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe