-
-
Save pbrisbin/bca300b913bcb0f1b533 to your computer and use it in GitHub Desktop.
-- Within an implementation of the TODO app from LYAH, there was something like | |
-- this... | |
data Todo = Todo | |
newtype Add = Add (Either String Todo) | |
newtype Remove = Remove (Either String ()) | |
newtype Complete = Complete (Either String Todo) | |
class Respond a where | |
-- or something, not important | |
respond a :: a -> IO () | |
instance Respond Add where | |
respond (Add (Right _ t) = putStrLn $ "Added " ++ show t | |
respond (Add (Left e _) = error e | |
instance Respond Remove | |
respond (Add (Right _ ()) = putStrLn "Todo removed!" | |
respond (Add (Left e _) = error e | |
instance Respond Complete | |
respond (Add (Right _ t) = putStrLn $ "Completed " ++ show t | |
respond (Add (Left e _) = error e | |
-- My suggestion: | |
data Action = Add Todo | Remove | Complete Todo | |
type Response = Either String Action | |
respond :: Response -> IO () | |
respond (Right (Add t)) = putStrLn $ "Added " ++ show t | |
respond (Right Remove) = putStrLn $ "Todo removed!" | |
respond (Right (Complete t)) = putStrLn $ "Completed " ++ show t | |
respond (Left e) = error e | |
-- I feel this kind of over-engineering happens to Rubyists specifically, | |
-- because they don't realize Sum types are available, since they're lacking in | |
-- Ruby (and most languages). |
I agree pretty much entirely with your sentiment. I think that the difference really comes down to what type of application you're writing. I think it's unlikely when writing a non-library application that you will have a group of values that are similar enough to fit in a type class, but desperate enough that they can't be constructors of a single (sum) type.
This is regardless of scale. For non-library applications, you don't have the issue of defining behavior for types you a) don't know about yet and b) won't be adding yourself in the future. That is when I would for type classes.
Going with a simpler sum type and using pattern exhaustive-ness warnings to ensure correctness when adding new constructors is the way to go 99% of the time (huge, IMO disclaimer here).
Yeah, I agree that a sum type is better in this situation. I'm looking forward to developing larger applications in Haskell, where I believe type classes will play a bigger part.
Sum types are awesome, but are closed. It makes it easy to add new behaviors on existing types but difficult to add new types, since you need to be able to modify the type and you need to modify every existing function for that type.
Typeclasses are probably more natural to Rubyists because they exhibit the same behaviors as Ruby classes: it's easy to add new types which implement a typeclass's interface, but adding new functions to a type class means invalidating every existing instance. That's pretty much what happens when you add a new method to a polymorphic set of Ruby classes: you need to hunt down every "instance" and add the method.
In small systems like a "todo" application, I think adding new types is unlikely, so sum types are much more convenient and expressive. In full applications, adding types is a major concern, so I'm guessing typeclasses will play a bigger role there.