5 ms·
Coming from an SML background, one of the things that's always tripped me up about Haskell is that it doesn't have structures, signatures, and functors for grou
by z1mm32m4n 10y ago
Coming from an SML background, one of the things that's always tripped me up about Haskell is that it doesn't have structures, signatures, and functors for grouping code. Instead it has modules, which are not much more than groups of exported functions.
For Haskellers coming to SML, there's "A Crash Course on ML Modules"[1] which does a nice job of explaining things in the opposite direction.
Are there any resources like this out there that discuss how best to break down and write modular Haskell programs?
[1]: http://jozefg.bitbucket.org/posts/2015-01-08-modules.html http://jozefg.bitbucket.org/posts/2015-01-08-modules.html
(update: I realized the original article I linked to wasn't the one I thought it was!)
- T-R 10y agoThanks for the link. I'm actually under the impression that the situation in Haskell will be changing in the near future with the project called "Backpack"[1][2], which Simon Peyton Jones and Simon Marlow are both involved with. [1] http://plv.mpi-sws.org/backpack/ http://plv.mpi-sws.org/backpack/ [2] https://ghc.haskell.org/trac/ghc/wiki/Backpack https://ghc.haskell.org/trac/ghc/wiki/Backpack
- z1mm32m4n 10y agoOh cool! It looks like the first link is from 2014. Do they discuss their progress somewhere online? I'd love to see what they've done recently.
- sclv 10y agoThis is an interesting question. As much more of a Haskeller than an MLer I don't tend to feel the "need" to structure my programs explicitly modularly, and when I do I sort of instinctively use a combination of typeclasses and polymorphic higher-order functions to do so. More basically, just think "everywhere I would open a module, instead I can take a parameterized record of functions" (and furthermore, if a datatype uniquely determines that record, I can associate a typeclass to that datatype). There are limitations to this, but in fewer circumstances than you'd think -- mainly about sort of cross-modularity (aka the expression problem). There was a very nice discussion on lennart's blog about this in 2008, with a problem posed and some partial solutions (read bottom post to top): http://augustss.blogspot.com/2008_12_01_archive.html http://augustss.blogspot.com/2008_12_01_archive.html
- z1mm32m4n 10y agoThis is actually quite a far way towards what I was looking for! Thank you very much. > We have basically packaged up the dictionary and unpack it ourselves to get access to the operations. It's not pleasant, but it works. Would you say this method of using multi-parameter type classes and packaging/unpackaging related operations in a record is "idiomatic"? I admittedly haven't browsed all that much Haskell code, but I feel like I don't see it used that frequently.
- sclv 10y agoonce you end up with a typeclass and associate the methods the unpacking goes away and you're just working in a context parameterized by some typeclass. i expressed it via that route to help make the connection to modules more clear. (there are occasions when you don't take that last step too, which is why i also sort of pointed towards that route). lennart's post shows an example where this sort of falls down -- but the followup also shows a nice haskelly solution that works, mainly, except when we want to intermix. he also suggests explicit type arguments as a way to make things nicer -- those have now landed in GHC :-) the other relevant work in a broader sense that I should mention is regarding effectful contexts where idiomatically you declare a subclass of monad with the relevant operations, then instantiate it via the mtl or some other means, so you can swap out the IO backed "real" one or various harnesses or add in logging layers, etc. finally, i guess i should add that as a rule of thumb i've noticed that purity and laziness both help provide ways to give "modular separation of concerns" directly. in particular, the most obvious thing we can do is just have each function do one thing to a bit of data, and produce a different bit of data and that's innately modular. but when we're interleaving IO (for example with mutable datastructures) and concerned with _when_ computation happens (in a strict setting), then it feels we're paying for this too much because we get big intermediate structures. but if you get the knack of just using pure lazy structures directly, you can sort of "amortize out" the computation cost in a nice way and also the space cost (as conceptually some big data structures become produced "on demand"). of course if you get it wrong, blammo :-)
- wyager 10y agoAnything you can do with ML modules you can do with Haskell typeclasses, and you can do most practical typeclass things (maybe all typeclass things?) with ML modules. Some things are more or less convenient in one system or the other. I wouldn't object to having both! I believe that's what the purpose of the Backpack project is.
- nv-vn 10y agoYou can't parametrize type classes over one another in the same way. In practice, this often makes ML style modules much more expressive: for example, I can have a Hashable type class with one implementation per type that is used by a HashSet data type or module, but with MLs there could be a number of modules for the same type implementing the hash function, which can be used to construct the set. This also means things like Monoids or Semigroups greatly benefit, since now you can easily have more than just (+) for ints, for example. You can even use this to derive operations like folds from your base module where you'd never have to apply the arguments to a folder; instead you could do Sum.fold or Product.fold based on the different base modules.
- wyager 10y agoYou can do this in Haskell as well, it's just clunkier since you usually end up needing some combo of MultiParamTypeClasses and plumbing around extra types. The ML approach is better for this use case, but the Haskell approach can do the same with a bit more verbosity.
- nv-vn 10y agoOf course, but at that point one could throw in OCaml extensions (modular implicits + HKTs) to provide the Haskell functionality to OCaml.