2017-02-01 19 views
2

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

Вот определение типа данных, с типом фантома, который отслеживает координатной оси и две функции для построения значения:

data X 
data Y 

newtype Coordinate axis = Coordinate Int64 deriving (Show) 

newX :: Int64 -> Coordinate X 
newX = Coordinate 

newY :: Int64 -> Coordinate Y 
newY = Coordinate 

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

slideByInt :: Coordinate a -> Int64 -> Coordinate a 
slideByInt (Coordinate x) y = Coordinate $ x + y 

slideByCoord :: Coordinate a -> Coordinate a -> Coordinate a 
slideByCoord (Coordinate x) (Coordinate y) = Coordinate (x + y) 

Это все прекрасно работает, и это мешает мне перепутать X и оси Y в функциях, которые манипулируют координаты.

Мой вопрос: как бы я обертываю slideByInt и slideByCoord функциональность за классом, так что я могу иметь только функцию slide. Это составляет:

class Slide a where 
    slide :: Coordinate x -> a -> Coordinate x 

instance Slide Int64 where 
    slide (Coordinate x) y = Coordinate (x + y) 

instance Slide (Coordinate x) where 
    slide (Coordinate x) (Coordinate y) = Coordinate (x + y) 

, но это не так, типа безопасной, как функции автономных: slide (newX 1) (newY 1) не должен набрать проверить! Как можно было бы исправить это, в некотором смысле, как я могу сделать экземпляр для двух координат менее разрешительным, чем это?

Я пробовал с кучей расширений (InstanceSigs, FunctionalDependencies, ограничения типов ...), но ничего не компилируется, и сложно сказать, полностью ли это, или мне просто нужно немного изменить мой код.

Спасибо ...

+2

Это интересный вопрос о трюке типа. Тем не менее, в вашем конкретном случае использования вы можете подумать о том, чтобы отбросить класс «Слайд», предоставив «Coordinate x» экземпляр «Num» и записывая «newX 2 + newX 3» или даже «2 + 3 :: Coordinate X» , – duplode

+0

@duplode Я переводил код Python и хотел бы сохранить тот же API, но добавьте некоторую безопасность типа сверху.Теперь, когда я вижу ответ, на самом деле это не выглядит ужасно сложным (по сравнению с некоторыми другими вещами, которые я видел:>). – kmelva

ответ

5

Рассмотрим, что этот класс декларации с указанием:

class Slide a where 
    slide :: Coordinate x -> a -> Coordinate x 

для любого типаx, экземпляр Slide обещаний, которые дали Coordinate x и a, это даст вы обратно Coordinate x. Прямо есть твоя проблема. Вы не хотите любойx все время.

Я думаю, что самый простой способ добиться того, что вы хотите со вторым параметром класса типа для типа координат:

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances #-} 

class Slide x a where 
    slide :: Coordinate x -> a -> Coordinate x 

instance Slide X Int64 where 
    slide = slideByInt 

instance Slide Y Int64 where 
    slide = slideByInt 

instance Slide X (Coordinate X) where 
    slide = slideByCoord 

instance Slide Y (Coordinate Y) where 
    slide = slideByCoord 

Последние два экземпляра на самом деле может быть заменен на этом более общем случае:

{-# LANGUAGE TypeFamilies #-} 

instance (a ~ b) => Slide a (Coordinate b) where 
    slide = slideByCoord 

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

+0

Спасибо, это то, что я придумал несколько минут назад, с помощью канала #haskell. Кажется, все отлично! Btw, 'FlexibleInstances' также требуются, не так ли? – kmelva

+0

Мой вариант использования заключается в преобразовании некоторого кода Python в Haskell, я хотел сохранить API таким же, но добавить некоторую безопасность типов. Еще раз спасибо ... – kmelva

+1

добавил недостающие расширения – user2297560