2016-12-19 1 views
1

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

type Voxel = (VoxelId, Position). 

где VoxelId просто псевдоним для Int. Поскольку я ожидаю некоторую линейную алгебру по линии, я хотел, чтобы Position представлял трехмерный вектор. Библиотека, которую я использую (matrix), использует только тип данных Matrix, но с позицией, имеющей базовый тип Matrix, упрощает дальнейшие вычисления, поэтому я в порядке.

Однако, это не имеет особого смысла для Position быть ничем иным, кроме 3-мерного вектора, поэтому я хотел бы, чтобы ограничить Position для матриц 3x1, поэтому, когда я использую специально Position в подписи функции, я m гарантированно ожидает либо матрицу 3x1, либо ошибку компиляции.

В C++ или другой язык OO я мог бы сделать что-то вроде этого:

class Matrix 
{ 
    Matrix(int rows, int columns) 
    {} 
} 

class Position : public Matrix 
{ 
    Position() 
    : Matrix (3, 1) 
    {} 
} 

и использовать Position, когда я ожидаю только 3x1 матрицы и ссылки на Матрица повсюду.

Предположим Matrix из библиотеки я использую имеет функцию конструктора

matrix :: (rows) -> (columns) -> Matrix 

Как я могу повторить это поведение в Haskell без переопределения для Position все операции Matrix орудий?

+1

Нет смысла строить неизменный, но пустой вектор в Haskell. Что вы можете сделать, это определить собственный «умный конструктор» (базовая функция), который принимает только, например. '(Double, Double, Double)' и возвращает матрицу. Если вы хотите получить больше статических гарантий, поместите полученную матрицу в оболочку 'newtype' (обратите внимание, что для этого потребуется также определить все операции с матрицами на обертке или разворачивать каждый раз). – chi

+0

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

+0

Затем вам приходится вручную разворачивать каждый раз или использовать безопасные принуждения. Или используйте конструктор dumber, как показано ниже. – chi

ответ

1

В Haskell нет подтипов, как в ООП.

Вы можете определить у вас есть тип

newtype Position = P { unP :: Matrix ... } 
    deriving (Show) -- , etc. 

данных и смарт-конструктор

position :: Double -> Double -> Double -> Position 
position x1 x2 x3 = 
    Position (newMatrix 1 3 [[x1,x2,x3]]) -- pseudo code 

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

Предполагая, что вы не скрываете представления, обратите внимание, что Position и Matrix ... - это два разных типа. Поэтому вы не можете передать Position тому, что хочет Matrix. Таким образом, вместо

matrixMultiply somePosition someMatrix 

нужно сделать

matrixMultiply (unP somePosition) someMatrix 

и получить Matrix результат. Если результат должен быть Position вместо этого, можно/нужно определить пользовательскую функцию умножения:

posMultiply :: Position -> Matrix ... -> Position 
posMultiply m _ | wrongSize m = error "matrix has the wrong size!" 
posMultiply m (Position p) = Position (matrixMultiply m p) 

В зависимости от того, насколько статических гарантий вы хотите, это может стать немного громоздким. Во многих случаях safe coercions может смягчить боль.

Простейшей альтернативой может быть наличие конструктора dumber, который не использует настраиваемый тип. Это простая функция

position :: Double -> Double -> Double -> Matrix ... 
position x1 x2 x3 = 
    newMatrix 1 3 [[x1,x2,x3]] -- pseudo code 
+0

Спасибо за ответ, я думаю, что у меня есть идея, как справиться с этой проблемой сейчас, хотя я должен признать, что я не являюсь большим поклонником любого из этих решений (они определенно лучше, чем все, что я придумал). Я помню разговор Саймона Пейтона о компрометации системы типов в Хаскелле, где он сказал что-то вроде: «в Haskell легко добавить новые функциональные возможности для существующего типа, а на императивных языках легко дать новые типа, но в обоих случаях обратный становится действительно беспорядочным », и я полагаю, что это тот случай, который показывает, насколько верно это утверждение. –

+0

@ReverentLapwing Да, согласен. Это известно как ["проблема с выражением"] (https://en.wikipedia.org/wiki/Expression_problem). – chi