2017-02-21 94 views
5

Как я прочитал несколько разделов, в History of Haskell, я наткнулся на:Какие типы проблем помогают «более высокоподобному полиморфизму» решить лучше?

Однако более-kinded полиморфизм имеет независимую утилиту: это вполне возможно, и иногда очень полезно, чтобы объявить типы данных параметризованных более высоких видов, такие как:

data ListFunctor f a = Nil | Cons a (f a) 

Зная «основные» АТД я был немного озадачен здесь, моим «угадать», что часть в круглых скобках предлагает «параметрический»/«динамический» унарного конструктор данныхf? Итак, любой конструктор данных типа * -> *, который может «принять» тип a? Я считаю, что правильно, или я неправильно интерпретирую синтаксис? Я знаю, что я «просто догадываюсь», но я надеюсь получить интуицию «программист-программист» по этой возможности здесь, какой-то примерный сценарий (или пользующийся огромным преимуществом);) в основном я могу себе представить (просто не в чем точный способ), что позволяет больше гибкости в тех «небольших встроенных универсальных рекурсивных конфигурационных языках» -ADT, которые Haskell делает с таким удовольствием, чтобы сформулировать и написать evals для .. закрыть?

В GHCi, :i ListFunctor на вышесказанном дает:

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

Так что, похоже, что «вывод» из более четких data декларации.

ответ

11

Да, f может быть любым конструктором типа унарного типа.

Например, ListFunctor [] Int или ListFunctor Maybe Char являются добропорядочными.

f также может быть любым конструктором n-арного типа с частично примененными аргументами (n-1).

Например, ListFunctor ((->) Bool) Int или ListFunctor (Either()) Char являются представителями сословий.

Основная система оплаты довольно проста. Если F :: * -> * -> ... -> *, то F ожидает аргументов типа. Если G :: (* -> *) -> *, то G ожидает любую вещь вида * -> *, включая конструктор унитарного типа и частичные приложения, как показано выше. И так далее.

Проблема, которая хорошо решена более высокими видами, это параметры конфигурации. Предположим, у нас есть запись

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

Теперь, параметры конфигурации можно найти в файле и/или передается через командную строку и/или в переменных окружения. Во время разбора всех этих источников настроек нам нужно справиться с тем, что не все источники определяют все параметры. Следовательно, нам нужно более слабый тип, чтобы представляет подмножества параметров конфигурации:

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

Это ужасно, так как он дублирует все варианты! У нас возникнет соблазн удалить тип Opt и использовать только слабый TempOpt, чтобы уменьшить беспорядок. Однако, делая это, нам нужно будет использовать некоторый частичный аксессуар, например fromJust, каждый раз, когда нам нужно получить доступ к значению опции в нашей программе, даже после начальной части обработки конфигурации.

Мы можем вместо этого прибегать к более высоким видам:

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

Нет больше дублирования. После того, как мы установили настройки конфигурации, мы получаем статическую гарантию того, что настройки всегда присутствуют. Теперь мы можем использовать аксессуар runIdentity, чтобы получить их вместо опасных fromJust.

+0

Отличное объяснение и приятный «реальный» сценарий, чтобы понять потенциал этой емкости --- очень благодарен @chi! – metaleap