How do I make an heterogeneous list in Haskell? (originally in Java)

前端 未结 4 1658
南旧
南旧 2021-02-10 13:13

How to convert following Java implementation into Haskell?

The major purpose here is having a list that contains various elements which are sub-type of a particular in

相关标签:
4条回答
  • 2021-02-10 13:40

    This is possible but probably not desirable. There is such language extension as 'existential types' that allows dynamic polymorphism.

    The idea of existential type is following:

    data Foo = Foo a
    

    Note that type variable "a" doesn't appear on the left side of ADT declaration. Simple example of possible implementation of dynamic polymorphic list and map function:

    {-# LANGUAGE UnicodeSyntax, Rank2Types, GADTs, ConstraintKinds #-}
    
    import Data.Constraint
    
    -- List datatype:
    data PList α where
       Nil  ∷ PList α
       (:*) ∷ α a ⇒ a → PList α → PList α
    
    infixr 6 :*
    
    -- Polymorphic map:
    pmap ∷ (∀ a. α a ⇒ a → b) → PList α → [b]
    pmap _ Nil      = []
    pmap f (a :* t) = f a : pmap f t
    
    main = let
            -- Declare list of arbitrary typed values with overloaded instance Show:
            l ∷ PList Show
            l = "Truly polymorphic list " :* 'a' :* 1 :* (2, 2) :* Nil
        in do
            -- Map show to list:
            print $ pmap show l
    

    Output:

    ["\"Truly polymorphic list \"","'a'","1","(2,2)"]
    

    Another example using this technique:

    class Show a ⇒ Named a where
        name ∷ a → String
    
    instance Named Int where
        name a = "I'm Int and my value is " ++ show a
    
    instance Named Char where
        name a = "I'm Char and my value is " ++ show a
    
     main = let
            -- Declare list of arbitrary typed values with overloaded instance Named:
            l2 :: PList Named
            l2 = 'a' :* (1 ∷ Int) :* Nil
        in do 
            print $ pmap name l2
            print $ pmap show l2
    

    Output:

    ["I'm Char and my value is 'a'","I'm Int and my value is 1"]
    ["'a'","1"]
    
    0 讨论(0)
  • 2021-02-10 13:56

    Simple answer: don't! Haskell is not an OO language, and it's not much good pretending it is and just trying to translate inheritance patterns to a mixture of type classes and ADTs.

    Your List<Foo> in Java is quite substantially different from a Foo a => [a] in Haskell: such a signature actually means forall a . Foo a => [a]. The a is basically an extra argument to the function, i.e. it can be chosen from the outside what particular Foo instance is used here.

    Quite the opposite in Java: there you don't have any control of what types are in the list at all, only know that they implement the Foo interface. In Haskell, we call this an existential type, and generally avoid it because it's stupid. Ok, you disagree – sorry, you're wrong!
    ...No, seriously, if you have such an existential list, the only thing you can ever do1 is execute the bar action. Well, then why not simply put that action in the list right away! IO() actions are values just like anything else (they aren't functions; anyway those can be put in lists just as well). I'd write your program

    xs :: [IO ()]
    xs = [bar Bar1, bar Bar2]
    


    That said, if you absolutely insist you can have existential lists in Haskell as well:

    {-# LANGUAGE ExistentialQuantification #-}
    
    data AFoo = forall a. Foo a => AFoo a
    
    xs :: [AFoo]
    xs = [AFoo Bar1, AFoo Bar2]
    
    main = mapM_ (\(AFoo f) -> bar f) xs
    

    As this has become quite a rant: I do acknoledge that OO style is for some applications more convenient than Haskell's functional style. And existentials do have their valid use cases (though, like chunksOf 50, I rather prefer to write them as GADTs). Only, for lots of problems Haskell allows for far more concise, powerful, general, yet in many ways simpler solutions than the "if all you have's a hammer..." inheritance you'd use in OO programming, so before using existentials you should get a proper feeling for Haskell's "native" features.


    1Yeah, I know you can also do "typesafe dynamic casts" etc. in Java. In Haskell, there's the Typeable class for this kind of stuff. But you might as well use a dynamic language right away if you take such paths.

    0 讨论(0)
  • 2021-02-10 14:03

    Your translation has an important drawback. Whereas in your Java version, you could easily add a Bar3 that also supports the Foo interface, you cannot easily achieve the same without touching the Bar type in the Haskell version. So this is rather not the version you are looking for.

    In a way you are looking for a heterogeneous list. Other questions are covering this aspect already.

    What you really want though is to get rid of the need of a type class altogether. Instead have a data type representing the behavior of Foo:

    data Foo = Foo { bar :: IO () }
    

    Then you can construct your list of object satisfying the Foo interface as a [Foo].

    0 讨论(0)
  • 2021-02-10 14:04

    To migrate from OOP to Haskell, read this:

    http://www.haskell.org/haskellwiki/OOP_vs_type_classes

    0 讨论(0)
提交回复
热议问题