5 ms·
I'd like to take this opportunity to flesh out some ideas I've had regarding the difference between the concepts "generic" and "general". "Oh, I think we need
by tome 9y ago
I'd like to take this opportunity to flesh out some ideas I've had regarding the difference between the concepts "generic" and "general". "Oh, I think we need the ability to do this kind of thing someday" falls under "general", giving more and more functionality to a component. "Oh, there are several things about this piece that we don't actually need to know" falls under "generic", allowing the code to generally be simpler. In Haskell
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
is more generic than
traverse :: Int -> IO Float -> [Int] -> IO [Float]
and therefore strictly better (unless there are reasons the latter could be optimized but not the former).
- TheCoelacanth 9y agoThey are definitely some downsides to the first definition. For instance, the error messages will often be much harder to read. For the second one you might get something straight forward like it expected `[Int]` but the actual type was `Int`. For the first one you will probably get something about not being able to deduce some long constraint for some long type. That might be possible to improve with better tooling, but for now it is a very real downside. I think that especially for application code (as opposed to library code), it is a mistake to make your type signature far more generic than you need right away, especially because it is usually easy to switch to the more generic type declaration later without changing much existing code.
- tome 9y agoThis is a good point. I think there's some merit in doing traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b) mySpecialTraverse :: Int -> IO Float -> [Int] -> IO [Float] mySpecialTraverse = traverse in some instances.