What types of problems help “higher polymorphism” solve better?

When I read some sections in Haskell History , I came across:

However, a higher type of polymorphism has independent utility: it is quite possible, and sometimes very useful, to declare data types parameterized by higher types, for example:

data ListFunctor fa = Nil | Cons a (fa) 

Knowing the "basic" ADTs, I was a little puzzled here, my "hunch" was that the part in parens offers a "parametric" / "dynamic" unary data constructor f ? So, any data constructor of type * -> * that can "accept" type a ? Am I correct, or am I misinterpreting the syntax? I know that I am “just guessing”, but I hope to get the “programmer-programmer” intuition for this opportunity here, some approximate scenario (or taking great advantage);) basically I can imagine (there’s just nothing exact way), which provides great flexibility in those "small built-in universal recursive configuration languages" -ADT that Haskell makes so nice to formulate and write evals for .. close?

In GHCi :i ListFunctor as above gives:

 type role ListFunctor representational nominal data ListFunctor (f :: * -> *) a = Nil | Cons a (fa) 

So, this is similar to what is being "deduced" from the crisper data declaration.

+5
polymorphism haskell
source share
1 answer

Yes, f can be any unary type constructor.

For example, ListFunctor [] Int or ListFunctor Maybe Char well known.

f can also be any n-ary type constructor with partially applied arguments (n-1).

For example, ListFunctor ((->) Bool) Int or ListFunctor (Either ()) Char well known.

The basic enrollment system is quite simple. If F :: * -> * -> ... -> * , then f expects type arguments. If G :: (* -> *) -> * , then G expects any thing of the form * -> * , including a unitary type constructor and partial applications, as shown above. And so on.


A problem that is well resolved by the higher types is configuration options. Suppose we have a record

 data Opt = Opt { opt1 :: Bool , opt2 :: String -- many other fields here } 

Now the configuration settings can be found in the file and / or passed on the command line and / or in the environment variables. During the analysis of all these sources of settings, we need to cope with the fact that not all sources determine all the parameters. Therefore, we need a weaker type representing subsets of configuration settings:

 data TempOpt = TempOpt { tempOpt1 :: Maybe Bool , tempOpt2 :: Maybe String -- many other fields here } -- merge all options in one single configuration, or fail finalize :: [TempOpt] -> Maybe Opt ... 

This is terrible as it duplicates all the parameters! We will be tempted to remove the Opt type and use the weaker TempOpt to reduce clutter. However, in doing this, we will need to use some partial accessor, for example fromJust every time we need to access the option value in our program even after the initial part of the configuration processing.

Instead, we can resort to higher views:

 data FOpt f = FOpt { opt1 :: f Bool , opt2 :: f String -- many other fields here } type Opt = FOpt Identity type TempOpt = FOpt Maybe -- as before: merge all options in one single configuration, or fail finalize :: [TempOpt] -> Maybe Opt ... 

More duplication. After finalize configuration settings, we get a static guarantee that the settings are always present. Now we can use runIdentity to get them instead of the dangerous fromJust .

+11
source share

All Articles