5 ms·
I really like your description. One thing i don't understand though, is why generalising these operations is considered a good thing? In my day to day I spend
by anko 7y ago
I really like your description. One thing i don't understand though, is why generalising these operations is considered a good thing?
In my day to day I spend a bit of time writing functional code, and a lot of time reviewing it, and when you generalise in this manner it hides a lot of the details of the algorithmic complexity. Is this operation happening in a future? or is on a list? You could argue that the type signature will let you know, but quite often it's inferred.
Suddenly I find myself needing an IDE just to do code reviews. People make arguments about naming variables and suddenly we're back to using hungarian notation.
I also find it's easy to make mistakes - you do a flatmap instead of a map and the Nones just vanish. Or you're composing so many generic functions that the intent of the code just disappears.
I guess i just wanted to see if it's just me.
- hopia 7y agoYou can write your own definitions for your types for instances of Functor, Applicative etc. explicitly. Are you worried about the performance when you refer to algorithmic complexity, or what exactly?
- anko 7y agoWell performance is certainly the main factor, but it's really about the Big O's of a function. https://en.wikipedia.org/wiki/Big_O_notation https://en.wikipedia.org/wiki/Big_O_notation
- rovolo 7y ago> is why generalising these operations is considered a good thing? > Is this operation happening in a future? or is on a list? You could argue that the type signature will let you know, but quite often it's inferred. 1) You are generalizing the input of a function to multiple types. Many of the functions you'd write wouldn't care whether you're using a List or a Future, they would accept either. It's just like declaring your Java method to take a Collection instead of an ArrayList. 2) You are getting more specific in the output of the function. If you write `filter`, it would be nice to get a `LinkedList` back if you pass in a `LinkedList`. With more common type systems, the best you'll be able to do writing the function once is a return type of `Collection` or `Iterable`. Your method may perform horribly if a LinkedList is passed in instead of an ArrayList. If your operation really depends on a specific behavior the Functor doesn't specify, you should be using a different type than Functor.
- sweeneyrod 7y ago> Many of the functions you'd write wouldn't care whether you're using a List or a Future, they would accept either. I can't imagine a situation where that would be true, except if you're writing a monad library. You could say that you might want to interchange a list of futures for a future of lists, but that's two interdependent things being swapped not one.
- Silhouette 7y agoYou've made at least two comments now mentioning this idea of interchange, but there's more to all of this than just that. The Haskell standard libraries are full of generic functions that can be applied to many practically useful types as long as those types provide certain basic guarantees. For example, there is a useful little function replicate :: Int -> a -> Seq a that allows us to build a sequence of n of some value. But what if we don't have a raw value, but instead some Applicative wrapper around it, and instead of just getting a sequence of individually wrapped values, we want that Applicative structure around the sequence? That is, we want some function f that will give us f 3 (Just 1) == Just (fromList [1, 1, 1]) but f 3 Nothing == Nothing with a Maybe, and f 3 (Right 'x') == Right (fromList "xxx") but f 3 (Left "error message") == Left "error message" with an Either, and similarly for any other Applicative. Well, such a function also exists in Data.Sequence: replicateA :: Applicative f => Int -> f a -> f (Seq a) Because it works with any Applicative, it can just as well work out all of the possible outcomes when we roll three dice: replicateA 3 [1, 2, 3, 4, 5, 6] or repeat the action of any Monad three times: replicateA 3 $ putStrLn "Hello, world!" or repeat whatever behaviour some Applicative you haven't even written yet will have next week. The rabbit hole gets as deep as you like. For example, we can easily compose a variety of other standard functions to count how many ways there are to reach each possible total in that dice example: map (head &&& length) $ group $ sort $ fmap sum $ replicateA 3 [1, 2, 3, 4, 5, 6] -- [(3,1),(4,3),(5,6),(6,10), ...] Many of these functions have quite general types, for example sum working on any Foldable and fmap working on any Functor. Because a list is an Applicative and any Applicative is also a Functor, and since the individual results of the replicateA will be Seqs and any Seq is also a Foldable, everything works fine, and the list structure we started with is preserved all the way through.