Skip to content

Instantly share code, notes, and snippets.

@pbrisbin
Created June 4, 2014 18:02
Show Gist options
  • Save pbrisbin/bca300b913bcb0f1b533 to your computer and use it in GitHub Desktop.
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).
@jferris
Copy link

jferris commented Jun 4, 2014

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.

@pbrisbin
Copy link
Author

pbrisbin commented Jun 4, 2014

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment