-
ImplicitParams -
- Начиная с:
-
6.8.1
Позволяет определять функции, ожидающие неявные параметры.
Неявные параметры реализованы согласно описанию в [Lewis2000] и включаются с помощью параметра ImplicitParams. (Большая часть приведённой далее, всё ещё весьма неполной документации написана Джеффом Льюисом.)
«Неявные параметры: динамическая область видимости со статическими типами», J Lewis, MB Shields, E Meijer, J Launchbury, 27-й симпозиум ACM по принципам языков программирования (POPL’00), Бостон, январь 2000 г.
Переменная называется динамически связанной, если её связывает контекст вызова функции, и статически связанной, если её связывает контекст вызываемой функции. В Haskell все переменные связываются статически. Динамическое связывание переменных — понятие, восходящее к Lisp, но позднее отброшенное в более современных его вариантах, таких как Scheme. Динамическое связывание может вызывать большую путаницу в языке без типов, и, к сожалению, языки с типами, в частности языки с типами Хиндли — Милнера, такие как 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. (В случаеwhereвыхода нет, так как нельзя вложить предложенияwhere.) -
В одной группе связываний можно разместить несколько связываний неявных параметров, но они не рассматриваются как взаимно рекурсивная группа (в отличие от обычных связываний
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). Например, рассмотрим:
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.