Spec-Zone.ru › Haskell 9

6.11.6. Неявные параметры

ImplicitParams
Since:

6.8.1

Разрешает определение функций, ожидающих неявных параметров.

Неявные параметры реализованы, как описано в [Lewis2000], и включаются опцией ImplicitParams. (Большая часть следующего, пока довольно неполного, документации от Джеффа Луиса.)

[Lewis2000]

«Неявные параметры: динамическое область действия с статическими типами», Дж. Луис, М.Б. Шильдс, Э. Майер, Дж. Лоунчбери, 27-й симпозиум ACM по принципам программирования (POPL’00), Бостон, январь 2000 г.

Переменная называется динамически связанной, когда она связана контекстом вызова функции, и статически связанной, когда она связана контекстом вызываемой функции. В Haskell все переменные статически связаны. Динамическое связывание переменных восходит к Lisp, но впоследствии было отброшено в более современных реализациях, таких как Scheme. Динамическое связывание может быть очень запутанным в языке без типов, и, к сожалению, типизированные языки, особенно языки с типами Hindley-Milner, такие как Haskell, поддерживают только статическое область действия переменных.

Однако, с помощью простого расширения системы классов типов Haskell, мы можем поддерживать динамическое связывание. В основном, мы выражаем использование динамически связанной переменной как ограничение на тип. Эти ограничения приводят к типам вида (?x::t') => t, что означает «эта функция использует динамически связанную переменную ?x типа t'». Например, следующее выражает тип функции сортировки, неявно параметризованной функцией сравнения с именем cmp.

sort :: (?cmp :: a -> a -> Bool) => [a] -> [a]

Ограничения динамического связывания — это просто новый вид предиката в системе классов типов.

Неявный параметр встречается в выражении, используя специальную форму ?x, где x — любое допустимое идентификатор (например, ord ?x — это допустимое выражение). Использование этого конструкта также вводит новое ограничение динамического связывания в тип выражения. Например, следующее определение показывает, как мы можем определить неявно параметризованную функцию сортировки в терминах явно параметризованной функции sortBy:

sortBy :: (a -> a -> Bool) -> [a] -> [a]

sort   :: (?cmp :: a -> a -> Bool) => [a] -> [a]
sort    = sortBy ?cmp

6.11.6.1. Ограничения типов неявных параметров

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

least   :: (?cmp :: a -> a -> Bool) => [a] -> a
least xs = head (sort xs)

Без лишних усилий параметр ?cmp распространяется, чтобы стать параметром least также. С явными параметрами по умолчанию параметры всегда должны явно распространяться. С неявными параметрами по умолчанию — всегда.

Ограничение типа неявного параметра отличается от других ограничений классов типов следующим образом: все использования конкретного неявного параметра должны иметь один и тот же тип. Это означает, что тип (?x, ?x) равен (?x::a) => (a,a), а не (?x::a, ?x::b) => (a, b), как в случае ограничений классов типов.

Вы не можете иметь неявный параметр в контексте объявления класса или экземпляра. Например, оба эти объявления являются недопустимыми:

class (?x::Int) => C a where ...
instance (?x::a) => Foo [a] where ...

Причина: именно какой неявный параметр вы получаете зависит от точного места вызова функции. Но «вызов» объявлений экземпляров выполняется за кулисами компилятором, поэтому сложно понять, где именно это происходит. Самое простое — запретить проблемные типы.

Ограничения неявных параметров не приводят к неоднозначности. Например, рассмотрим:

f :: (?x :: [a]) => Int -> Int
f n = n + length ?x

g :: (Read a, Show a) => String -> String
g s = show (read s)

Здесь g имеет неоднозначный тип и отклоняется, но f в порядке. Связывание для ?x в месте вызова f достаточно однозначно и устанавливает тип a.

6.11.6.2. Связывания неявных параметров

Неявный параметр связывается, используя стандартные let или where формы связывания. Например, мы определяем функцию min, связывая cmp.

min :: Ord a => [a] -> a
min  = let ?cmp = (<=) in least

Группа связываний неявных параметров может появиться в любом месте, где может появиться обычная группа связываний Haskell, за исключением верхнего уровня. То есть они могут появиться в let (включая список с пониманием, или обозначение do, или условные выражения с образцами), или в where части. Обратите внимание на следующие моменты:

  • Группа связываний неявного параметра должна быть набором простых связей к переменным неявного стиля (без связей со стилевыми функциями и без типов); эти связи не являются полиморфными или рекурсивными.
  • Вы не можете смешивать связывания неявных параметров с обычными связями в одном выражении let; вместо этого используйте две вложенные let.
  • Вы можете поместить несколько связываний неявного параметра в одну группу связывания; но они не обрабатываются как взаимно рекурсивные группы (как обычные let связи). Вместо этого они обрабатываются как нерекурсивная группа, одновременно связывающая все неявные параметры. Связи не являются вложенными и могут быть переупорядочены без изменения смысла программы. Например, рассмотрим:

    f t = let { ?x = t; ?y = ?x+(1::Int) } in ?x + ?y
    

    Использование ?x в связи для ?y не «видит» связь для ?x, поэтому тип f равен

    f :: (?x::Int) => Int -> Int
    

6.11.6.3. Неявные параметры и полиморфная рекурсия

Рассмотрим эти два определения:

len1 :: [a] -> Int
len1 xs = let ?acc = 0 in len_acc1 xs

len_acc1 [] = ?acc
len_acc1 (x:xs) = let ?acc = ?acc + (1::Int) in len_acc1 xs

------------

len2 :: [a] -> Int
len2 xs = let ?acc = 0 in len_acc2 xs

len_acc2 :: (?acc :: Int) => [a] -> Int
len_acc2 [] = ?acc
len_acc2 (x:xs) = let ?acc = ?acc + (1::Int) in len_acc2 xs

Единственное различие между двумя группами заключается в том, что во второй группе len_acc задан тип. В первом случае len_acc1 мономорфен в своей правой части, поэтому неявный параметр ?acc не передается в рекурсивный вызов. Во втором случае, так как len_acc2 имеет тип, рекурсивный вызов выполняется к полиморфной версии, которая принимает ?acc как неявный параметр. Итак, мы получаем следующие результаты в GHCi:

Prog> len1 "hello"
0
Prog> len2 "hello"
5

Добавление типа существенно изменяет результат! Это довольно неинтуитивное явление, на которое стоит обратить внимание.

6.11.6.4. Гарантии области действия неявных параметров

GHC всегда берет самое вложенное связывание неявного параметра из контекста для поиска значения. Рассмотрим следующий код:

let ?f = 1 in let ?f = 2 in ?f

Это выражение всегда вернет 2.

Другой пример этого правила — сопоставление по конструкторам с ограничениями. Например:

data T where
  MkT :: (?f :: Int) => T

f :: T -> T -> Int
f MkT MkT = ?f

Здесь GHC всегда возьмет ?f из последнего соответствия.

6.11.6.5. Неявные параметры и мономорфизм

GHC применяет ужасное ограничение мономорфизма (раздел 4.5.5 от Haskell Report) к неявным параметрам. Например, рассмотрим:

f :: Int -> Int
f v = let ?x = 0     in
      let y = ?x + v in
      let ?x = 5     in
      y

Так как связь для y попадает под ограничение мономорфизма, она не обобщается, поэтому тип y — просто Int, а не (?x::Int) => Int. Следовательно, (f 9) возвращает результат 9. Если вы добавите тип для y, тогда y получит тип (?x::Int) => Int, поэтому появление y в теле let увидит внутреннее связывание ?x, поэтому (f 9) вернет 14.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/exts/implicit_parameters.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API