🕵️ TL;DR — Обман или невежество, каждый раз, когда кто-то в вашей жизни пытается что-то скрыть, он, вероятно, что-то скрывает. В нашем случае не намеренно.



вступление

Ну, это неудобно. Во время учебы в университете я обнаружил, что чье-то тривиальное использование этого слова в значительной степени коррелирует с непониманием конкретной темы. Когда я обнаружил это (с большим количеством самореализации в процессе), я попытался изменить то, как я говорил, чтобы избежать этой ловушки. Очень часто в доказательствах и объяснениях пристальный взгляд на неуместное тривиально или же четко, показало фундаментальное непонимание знаний писателей. Мне не стыдно признаться, что я попал именно в эту ловушку в последней части этой серии.

Конкретно и, к сожалению, моя любимая строчка в последней статье «Но тогда нам пришлось бы беспокоиться об определении карты и суммы для Vector3, и внезапно я потерял интерес». продемонстрировал полное невежество с моей стороны. Сегодня мы рассмотрим классы типов и, надеюсь, в конце разработаем более чистый код.



Классы типов

Мы собирались поговорить о цветах в конце прошлой статьи, и первое, что мы должны понять, это то, что цвет — это просто Vector3. Теперь нашей непосредственной мыслью должно быть: «Как нам избежать переопределения функций, которые мы только что определили?». Ответ на этот вопрос — классы типов.

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

Мы строим на этом Переполнение стека статья.

class NumVector v where
  (>+) :: Num a => v a -> v a -> v a
  (>-) :: Num a => v a -> v a -> v a
  prod :: Num a => v a -> v a -> v a
  (>*) :: Num a => a -> v a -> v a
  (>.) :: Num a => v a -> v a -> a
  neg  :: Num a => v a -> v a

data Vector a = Vec { vecList :: [a] }
              deriving (Show, Eq, Ord)

instance NumVector Vector where
  (>+) (Vec u) (Vec v) = Vec $ zipWith (+) u v
  (>-) (Vec u) (Vec v) = Vec $ zipWith (-) u v
  (>*) k (Vec v) = Vec $ map (k*) v
  prod (Vec u) (Vec v) = Vec $ zipWith (*) u v
  (>.) u v = sum $ vecList (u `prod` v)
  neg = (>*) (-1)


cross :: Num a => Vector a -> Vector a -> Vector a
cross (Vec [a,b,c]) (Vec [x,y,z]) = Vec [b*z + c*y, -(a*z + c*x), a*y + b*x]
Войти в полноэкранный режим

Выйти из полноэкранного режима

Здесь есть что распаковать (без каламбура). Во-первых, мы определяем класс типов; это говорит нам о том, что экземпляр NumVector должны реализовать: сложение, вычитание, умножение и скалярное произведение. Почему не перекрестное произведение? Я слышу, как ты кричишь, ну оказывается н-мерное перекрестное произведение как-то тяжело.

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

okay = Vec [1,2,3] >. Vec [1,2,3]
wtf = Vec [1,2,3] >. Vec [1,2,3,4,5,6]

main = do
    print okay -- Outputs: 14
    print wtf  -- Outputs: 14
Войти в полноэкранный режим

Выйти из полноэкранного режима

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

Затем мы определяем наши операции, которые мы обещали для типов, которые создают экземпляры. NumVector, это все довольно стандартно. Наконец, мы определяем векторное произведение, но только для 3-мерные векторы.

Если мы попробуем, например:

main = do
    print $ Vec [1,2,3] `cross` Vec [1,2,3]   -- Vec {vecList = [12,-6,4]}
    print $ Vec [1,2,3] `cross` Vec [1,2,3,4] -- main: main.hs:18:1-76: Non-exhaustive patterns in function cross
Войти в полноэкранный режим

Выйти из полноэкранного режима

нам говорят, что мы не определили cross за 4-мерные векторы.

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

clamp :: (Num a, Ord a) => Vector a -> Vector a
clamp (Vec u) = Vec $ map (min 1) $ map (max 0) u

main = print $ clamp (Vec [0.1,-100,4.5]) -- Outputs: Vec {vecList = [0.1,0.0,1.0]}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Это потенциально более сложная функция, поэтому давайте остановимся здесь и проанализируем ее. map позволяет нам взять итератор и функцию и применить функцию к каждому элементу в итераторе. max а также min довольно понятно, единственное, что следует отметить, это частичное применение, например, (min 1). $ это самая интересная часть, я просто обожаю эту функцию, я думаю, что это очень круто. Он определяется следующим образом:

$ :: (a -> b) -> a -> b
Войти в полноэкранный режим

Выйти из полноэкранного режима

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

main = do
    print $ Vec [1,2,3] -- Vec {vecList = [1,2,3]}
    print (Vec [1,2,3]) -- Vec {vecList = [1,2,3]}
Войти в полноэкранный режим

Выйти из полноэкранного режима

дать нам тот же результат. Однако в clamp выше, это более полезно, чем чисто эстетика. Только так мы можем определить эту функцию как безточечный. Окончательно Vec просто «переупаковывает» наши операции обратно в Vector.

Я думаю, что это естественная точка остановки, мы в основном вернулись к тому, с чего начали, но с гораздо большим количеством знаний. Кроме того, я искренне надеюсь, что мы начали закладывать основу для более чистого кода в будущем.