2015-10-07 3 views
3

Я ищу пары функцийзадействуя значение (Enum а, Bounded а) => а

previous :: (Enum a, Bounded a) => a -> a 
next  :: (Enum a, Bounded a) => a -> a 

Такое, что previous является pred :: Enum a => a -> a, если полученное значение будет ограничен пределами, и цикл это на другую сторону в противном случае (симметрично для next и succ).

Пример

data X = A | B | C deriving (Bounded, Enum) 
next A = B 
next B = C 
next C = A 

Это было бы легко, если бы я добавить Eq a => a ограничение, как описано в этих similarquestions. Но это ограничение кажется ненужным, поскольку тип Bounded a => a для меня звучит так, как будто он должен быть в состоянии определить, находится ли он внутри или нет. В succ и pred сами функции имеют некоторый контроль над этим, но я предпочел бы не использовать исключения в моем иначе чистом коде:

Prelude> succ (maxBound :: Int) 
*** Exception: Prelude.Enum.succ{Int}: tried to take `succ' of maxBound 

Как succ/pred испытание против границ, не требуя Eq a => a (или даже Bounded a => a)? Могу ли я воспроизвести это поведение в своей собственной функции? Или существует другой способ написать функцию с таким поведением и не требует ограничения Eq?

+1

'succ' задается некоторой реализацией, которая может определить, действительна она или нет. – Ryan

ответ

5

Enum класс определяет две функции

toEnum :: Enum a => Int -> a 
    fromEnum :: Enum a => a -> Int 

Эти функции могут быть использованы для сравнения в Enum к другому, если диапазон этого Enum вписывается в размер Int. Если вы согласны принять это небольшое ограничение, вы можете написать свои функции следующим образом.

{-# LANGUAGE ScopedTypeVariables #-} 

previous :: forall a . (Enum a, Bounded a) => a -> a 
previous x | from x > from minBound = pred x 
      | otherwise    = maxBound where from :: a -> Int; from = fromEnum 

next :: forall a . (Enum a, Bounded a) => a -> a 
next x | from x < from maxBound = succ x 
     | otherwise    = minBound where from :: a -> Int; from = fromEnum 

Эти функции делают несколько предположений; что с учетом (Enum a, Bounded a) каждое значение a будет находиться между minBound и maxBound включительно и что {to/from}Enum сохранить этот заказ в отношении заказа на целые числа. Это справедливо для простых перечислений:

>import Control.Arrow 
>map (next &&& previous) [A,B,C] 
[(B,C),(C,A),(A,B)] 
+0

Это выглядит великолепно! Эти ограничения всегда улавливаются системой типов? Попробовав GHCi, 'previous 10' дает ошибку времени компиляции, а' previous (10 :: Int) 'отлично работает. – Mephy

+0

Нет, они не являются. Если экземпляр 'Enum' имеет больше элементов, чем размер' Int', то 'from/toEnum' просто ломаются. 'succ' и' pred' могут по-прежнему работать. – user2407038

+0

'previous 10' является допустимым выражением, тип которого будет« Num a, Enum a, Bounded a => a', однако GHCI не может распечатать его, потому что он не знает, какой тип выбрать для 'a'. Обычно он выбирает 'Integer', но это не' Bounded'. Если вы наберете 'default (Int)' в свой сеанс GHCI, тогда он попробует по умолчанию вместо 'Int', поэтому' previous 10' будет по умолчанию 'Int' и будет работать без сигнатуры типа. – user2407038

1

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

instance Enum Ordering where 
    succ LT = EQ 
    succ EQ = GT 
    succ GT = error "Prelude.Enum.Ordering.succ: bad argument" 

    pred GT = EQ 
    pred EQ = LT 
    pred LT = error "Prelude.Enum.Ordering.pred: bad argument" 

В противном случае, как вы говорите, это, кажется, наиболее вероятно, что вы собираетесь необходимо учитывать значение Eq, поскольку в принципе должно быть какое-то понятие сравнения (чтобы сравнить границы с текущим значением), если полное пространство типа не может быть легко перечислимо.

+0

И как может 'succ' сделать это без' (Bounded a, Eq a) => a'? Это случай «примитивы может нарушить правила»? – Mephy

+0

Я думаю, это просто, что 'succ' не требует каких-либо ограничений или проверки равенства; это требует только того, чтобы существовала функция 'a -> a'. В буквальном смысле это могло бы (бесполезно) быть тем же, что и функция id. – syrion

+3

@Mephy: В экземпляре 'Enum' для заданного типа' succ' определен специально для _that one type_, а не вообще, и может использовать _anything_ об этом одном типе. Например, здесь 'succ' определяется для' Ordering'. Любая функция «Ordering» - это честная игра. 'succ' может быть _used_ в целом, но он _implemented_ один раз для каждого типа. –

4

В чем проблема? Если вы можете добавить Eq класс тоже легко:

module CycleEnum where 

data XEnum = A1 | A2 | A3 deriving (Bounded, Enum, Eq, Show) 

cyclePrev :: (Eq a, Enum a, Bounded a) => a -> a 
cyclePrev x = if x == minBound then maxBound else pred x 

cycleNext :: (Eq a, Enum a, Bounded a) => a -> a 
cycleNext x = if x == maxBound then minBound else succ x 

Без Eq, это немного сложнее ...

module CycleEnum where 

data XEnum = A1 | A2 | A3 deriving (Bounded, Enum, Show) 

isSingle :: [a] -> Bool 
isSingle [x] = True 
isSingle _ = False 

isLastElement :: Enum a => a -> Bool 
isLastElement x = isSingle $ enumFrom x 

isFirstElement :: (Enum a, Bounded a) => a -> Bool 
isFirstElement x = isSingle $ enumFromTo minBound x 

cyclePrev :: (Enum a, Bounded a) => a -> a 
cyclePrev x = if isFirstElement x then maxBound else pred x 

cycleNext :: (Enum a, Bounded a) => a -> a 
cycleNext x = if isLastElement x then minBound else succ x 

Обратите внимание, что я написал isSingle с сопоставлением с образцом, а не по телефону length; потому что вычисление длины списка может быть очень неэффективным, если вы просто хотите знать, содержит ли список только один элемент.

+0

Это решение имеет, по-видимому, те же ограничения, что и ответ пользователя2407038 ('cycleNext 10' дает ошибку времени компиляции,' cyclenext (10 :: Int) 'отлично работает), но не требует' -XScopedTypeVariables'. Спасибо за вклад. – Mephy

Смежные вопросы