Идрис: арифметика для ограниченного двойника

Я новичок в Идрисе. Мне нужно создать данные, описывающие ограниченное число. Итак, я сделал такие данные с помощью такого конструктора:

data BoundedDouble : (a, b : Double) -> Type where
  MkBoundedDouble : (x : Double) -> 
                    {auto p : a <= x && x <= b = True} -> 
                    BoundedDouble a b

Кажется, создается Double между a и b. А вот простой пример использования:

test : BoundedDouble 0.0 1.0
test = MkBoundedDouble 0.0 

Оно работает. Но теперь я хочу реализовать интерфейс Num для BoundedDouble. Я пробовал это:

Num (BoundedDouble a b) where
  (MkBoundedDouble x) + (MkBoundedDouble y) = 
    MkBoundedDouble (ifThenElse (x + y > b) 
                      (x + y - (b - a))
                      (ifThenElse (x + y < a)
                        (x + y + (b - a))
                        (x + y)))

Но это не работает, я догадываюсь, почему, но я не могу этого объяснить. Как мне реализовать дополнение?

Я точно не знаю, что мне делать или читать, чтобы понять это.


person O.Phillips    schedule 31.05.2016    source источник


Ответы (1)


Здесь есть две проблемы. Double арифметика определяется с помощью примитива функций. Идрис не может даже доказать, что a <= b = True -> b <= c = True -> a <= c = True (что, кстати, даже не выполняется постоянно - так что это не вина Идриса). Нет никаких доказательств для a <= b = True, кроме как просто проверить это, то, что вы пробовали с ifThenElse.

При работе с такими слепыми доказательствами во время выполнения (так что просто … = True) _ 6_ весьма полезен. ifThenElse (a <= x) … … разветвляется при логической проверке, но код в ветвях не знает о результате проверки. С choose (a <= x) вы получите результат для ветвей, с Left prf и prf : So (a <= x) или Right prf и prf : So (not (a <= x)).

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

import Data.So

data BoundedDouble : (a, b : Double) -> Type where
    MkBoundedDouble : (x : Double) 
                      -> {auto high : So (a <= x)}
                      -> {auto low : So (x <= b)}
                      -> BoundedDouble a b

Num (BoundedDouble a b) where
    (+) (MkBoundedDouble u) (MkBoundedDouble v) =
           let x = u + v
           in case (choose (a <= x), choose (x <= b)) of
             (Left _, Left _) => MkBoundedDouble x
             (Right _, _) => ?holeMin
             (_, Right _) => ?holeMax

Это уже проверки типов, но в нем есть дыры. Мы хотим установить ?holeMin на MkBoundedDouble a и ?holeMax на MkBoundedDouble b. Однако MkBoundedDouble сейчас нужны два доказательства: high и low. В случае ?holeMax это будут x = b So (a <= b) и So (b <= b). Опять же, Идрис не знает, что b <= b на каждые b : Double. Поэтому нам нужно будет снова выбрать, чтобы получить эти доказательства:

             (_, Right _) => case (choose (a <= b), choose (b <= b)) of
                  (Left _, Left _) => MkBoundedDouble b
                  _ => ?what

Поскольку Идрис не видит этого b <= b, функция будет частичной. Мы могли бы обмануть и использовать, например, MkBoundedDouble u в ?what, поэтому функция будет проверять тип и надеяться, что этого действительно никогда не произойдет.

Также есть возможность убедить контролера типов с силой, что b <= b всегда истинно:

data BoundedDouble : (a, b : Double) -> Type where
    MkBoundedDouble : (x : Double) 
                      -> {auto rightSize : So (a <= b)}
                      -> {auto high : So (a <= x)}
                      -> {auto low : So (x <= b)}
                      -> BoundedDouble a b

DoubleEqIsSym : (x : Double) -> So (x <= x) 
DoubleEqIsSym x = believe_me (Oh)

Num (BoundedDouble a b) where
    (+) (MkBoundedDouble u) (MkBoundedDouble v) =
           let x = u + v
           in case (choose (a <= x), choose (x <= b)) of
             (Left _, Left _) => MkBoundedDouble x
             (Right _, _) => MkBoundedDouble a {high=DoubleEqIsSym a}
             (_, Right _) => MkBoundedDouble b {low=DoubleEqIsSym b}

Или мы могли бы быть еще безопаснее и поместить доказательства для верхней и нижней границ в конструктор данных, чтобы мы могли использовать их в ?holeMin и ?holeMax. Это было бы:

import Data.So

data BoundedDouble : (a, b : Double) -> Type where
    MkBoundedDouble : (x : Double) 
                      -> {auto rightSize : So (a <= b)}
                      -> {auto leftId : So (a <= a)}
                      -> {auto rightId : So (b <= b)}
                      -> {auto high : So (a <= x)}
                      -> {auto low : So (x <= b)}
                      -> BoundedDouble a b

Num (BoundedDouble a b) where
    (+) (MkBoundedDouble u) (MkBoundedDouble v) =
           let x = u + v
           in case (choose (a <= x), choose (x <= b)) of
             (Left _, Left _) => MkBoundedDouble x
             (Right _, _) => MkBoundedDouble a
             (_, Right _) => MkBoundedDouble b

Вы видите, что даже то, что конструктор упакован доказательствами, они не усложняют реализацию. И они должны быть стерты в реальном коде времени выполнения.

Однако в качестве упражнения вы можете попробовать реализовать Num для

data BoundedDouble : (a, b : Double) -> Type where
    MkBoundedDouble : (x : Double)
                      -> {auto rightSize : So (a <= b)}
                      -> {auto high : So (a <= x)}
                      -> {auto low : So (x <= b)} 
                      -> BoundedDouble a b
    Min : {auto rightSize : So (a <= b)} -> BoundedDouble a b
    Max : {auto rightSize : So (a <= b)} -> BoundedDouble a b

К сожалению, ресурсов для Идриса пока не так много. Помимо учебника в разработке находится книга, которую я рекомендую. Это дает более доступные упражнения, чем работа с примитивными типами. :-)

person xash    schedule 01.06.2016
comment
Большое спасибо, ваш ответ очень полезен! Но не могли бы вы объяснить еще раз, зачем нам нужны доказательства So (b <= b) и So (a <= a)? - person O.Phillips; 01.06.2016