List of showables: OOP beats Haskell?

后端 未结 8 1242
无人及你
无人及你 2020-12-24 06:46

I want to build a list of different things which have one property in common, namely, they could be turned into string. The object-oriented approach is straightforward: defi

8条回答
  •  醉梦人生
    2020-12-24 07:23

    My answer is fundamentally the same as ErikR's: the type that best embodies your requirements is [String]. But I'll go a bit more into the logic that I believe justifies this answer. The key is in this quote from the question:

    [...] things which have one property in common, namely, they could be turned into string.

    Let's call this type Stringable. But now the key observation is this:

    • Stringable is isomorphic to String!

    That is, if your statement above is the whole specification of the Stringable type, then there is a pair functions with these signatures:

    toString :: Stringable -> String
    toStringable :: String -> Stringable
    

    ...such that the two functions are inverses. When two types are isomorphic, any program that uses either of the types can be rewritten in terms of the other without any change to its semantics. So Stringable doesn't let you do anything that String doesn't let you do already!

    In more concrete terms, the point is that this refactoring is guaranteed to work no matter what:

    1. At every point in your program where you turn an object into a Stringable and stick that into a [Stringable], turn the object into a String and stick that into a [String].
    2. At every point in your program that you consume a Stringable by applying toString to it, you can now eliminate the call to toString.

    Note that this argument generalizes to types more complex than Stringable, with many "methods". So for example, the type of "things that you can turn into either a String or an Int" is isomorphic to (String, Int). The type of "things that you can either turn into a String or combine them with a Foo to produce a Bar" is isomorphic to (String, Foo -> Bar). And so on. Basically, this logic leads to the "record of methods" encoding that other answers have brought up.

    I think the lesson to draw from this is the following: you need a specification richer than just "can be turned into a string" in order to justify using any of the mechanisms you brought up. So for example, if we add the requirement that Stringable values can be downcast to the original type, an existential type now perhaps becomes justifiable:

    {-# LANGUAGE GADTs #-}
    
    import Data.Typeable
    
    data Showable = Showable
        Showable :: (Show a, Typeable a) => a -> Stringable
    
    downcast :: Typeable a => Showable -> Maybe a
    downcast (Showable a) = cast a
    

    This Showable type is not isomorphic to String, because the Typeable constraint allows us to implement the downcast function that allows us to distinguish between different Showables that produce the same string. A richer version of this idea can be seen in this "shape example" Gist.

提交回复
热议问题