List of showables: OOP beats Haskell?

后端 未结 8 1278
无人及你
无人及你 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:15

    The HList-style solutions would work, but it is possible to reduce the complexity if you only need to work with lists of constrained existentials and you don't need the other HList machinery.

    Here's how I handle this in my existentialist package:

    {-# LANGUAGE ConstraintKinds, ExistentialQuantification, RankNTypes #-}
    
    data ConstrList c = forall a. c a => a :> ConstrList c
                      | Nil
    infixr :>
    
    constrMap :: (forall a. c a => a -> b) -> ConstrList c -> [b]
    constrMap f (x :> xs) = f x : constrMap f xs
    constrMap f Nil       = []
    

    This can then be used like this:

    example :: [String]
    example
      = constrMap show
                  (( 'a'
                  :> True
                  :> ()
                  :> Nil) :: ConstrList Show)
    

    It could be useful if you have a large list or possibly if you have to do lots of manipulations to a list of constrained existentials.

    Using this approach, you also don't need to encode the length of the list in the type (or the original types of the elements). This could be a good thing or a bad thing depending on the situation. If you want to preserve the all of original type information, an HList is probably the way to go.

    Also, if (as is the case of Show) there is only one class method, the approach I would recommend would be applying that method to each item in the list directly as in ErikR's answer or the first technique in phadej's answer.

    It sounds like the actual problem is more complex than just a list of Show-able values, so it is hard to give a definite recommendation of which of these specifically is the most appropriate without more concrete information.

    One of these methods would probably work out well though (unless the architecture of the code itself could be simplified so that it doesn't run into the problem in the first place).

    Generalizing to existentials contained in higher-kinded types

    This can be generalized to higher kinds like this:

    data AnyList c f = forall a. c a => f a :| (AnyList c f)
                     | Nil
    infixr :|
    
    anyMap :: (forall a. c a => f a -> b) -> AnyList c f -> [b]
    anyMap g (x :| xs) = g x : anyMap g xs
    anyMap g Nil       = []
    

    Using this, we can (for example) create a list of functions that have Show-able result types.

    example2 :: Int -> [String]
    example2 x = anyMap (\m -> show (m x))
                        (( f
                        :| g
                        :| h
                        :| Nil) :: AnyList Show ((->) Int))
      where
        f :: Int -> String
        f = show
    
        g :: Int -> Bool
        g = (< 3)
    
        h :: Int -> ()
        h _ = ()
    

    We can see that this is a true generalization by defining:

    type ConstrList c = AnyList c Identity
    
    (>:) :: forall c a. c a => a -> AnyList c Identity -> AnyList c Identity
    x >: xs  = Identity x :| xs
    infixr >:
    
    constrMap :: (forall a. c a => a -> b) -> AnyList c Identity -> [b]
    constrMap f (Identity x :| xs) = f x : constrMap f xs
    constrMap f Nil                = []
    

    This allows the original example from the first part of this to work using this new, more general, formulation with no changes to the existing example code except changing :> to >: (even this small change might be able to be avoided with pattern synonyms. I'm not totally sure though since I haven't tried and sometimes pattern synonyms interact with existential quantification in ways that I don't fully understand).

提交回复
热议问题