Haskell: Предпочитаете совпадение шаблонов или доступ к членству?

Предположим, что у меня есть тип данных Vector, определенный следующим образом:

data Vector = Vector { x :: Double
                     , y :: Double
                     , z :: Double
                     }

Было бы более обычным определять функции против него с помощью доступа члена:

vecAddA v w
    = Vector (x v + x w)
             (y v + y w)
             (z v + z w)

Или используя сопоставление с образцом:

vecAddB (Vector vx vy vz) (Vector wx wy wz)
    = Vector (vx + wx)
             (vy + wy)
             (vz + wz)

(Извините, если у меня есть какая-либо из терминов неверно).

Ответы

Ответ 1

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

data Foo = A {a :: Int} | B {b :: String}

fun x = a x + 1

Если вы используете сопоставление образцов для работы с типом Foo, вы в безопасности; невозможно получить доступ к члену, которого не существует. Если вы используете функции доступа, с другой стороны, некоторые операции, такие как вызов fun (B "hi!") здесь, приведут к ошибке выполнения.

EDIT: хотя, конечно, вполне возможно забыть сопоставить какой-то конструктор, сопоставление шаблонов делает довольно явным то, что происходящее зависит от того, какой конструктор используется (вы также можете сказать компилятору обнаружить и предупредить вас о неполных шаблонах) тогда как использование функции больше намекает на то, что идет любой конструктор, IMO.

Аксессоры лучше всего сохраняются для случаев, когда вы хотите получить только один или несколько аргументов конструктора (потенциально много), и вы знаете, что он безопасен для их использования (нет риска использования аксессора на неправильном конструкторе, поскольку в примере.)

Ответ 2

Другой незначительный аргумент "реального мира". В целом, не так хорошо иметь такие короткие имена записей записи, поскольку короткие имена, такие как x и y, часто используются для локальных переменных.

Таким образом, "справедливое" сравнение было бы следующим:

vecAddA v w 
  = Vector (vecX v + vecX w) (vecY v + vecY w) (vecZ v + vecZ w)
vecAddB (Vector vx vy vz) (Vector wx wy wz) 
  = Vector (vx + wx) (vy + wy) (vz + wz)

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

  • Вам нужно только одно или два поля доступа (или изменить!) в более крупной записи
  • Вы хотите оставаться гибкими для изменения записи позже, например, добавить больше полей.

Ответ 3

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

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

Ответ 4

(Предупреждение, может быть, неправильно. Я все еще новичок Haskell, но здесь мое понимание)

Одна вещь, о которой другие люди не упомянули, заключается в том, что сопоставление шаблонов сделает функцию "строгой" в ее аргументе. (Http://www.haskell.org/haskellwiki/Lazy_vs._non-strict)

Чтобы выбрать, какой шаблон использовать, программа должна уменьшить аргумент до WHNF перед вызовом функции, тогда как использование функции доступа к синтаксису записи будет оценивать аргумент внутри функции.

Я не могу дать конкретных примеров (новичок), но это может иметь последствия для производительности, когда огромные кучи "thunks" могут накапливаться в рекурсивных, нестрогих функциях. (Это означает, что для простых функций, таких как извлечение значений, не должно быть разницы в производительности).

(Конкретные примеры очень приветствуются)

Короче

f (Just x) = x

на самом деле (используя BangPatterns)

f !jx = fromJust jx

Edit: Вышеприведенный пример не является хорошим примером строгости, потому что оба они действительно строгие из определения (f bottom = bottom), просто чтобы проиллюстрировать, что я имел в виду со стороны производительности.

Ответ 5

Как kizzx2 указал, существует тонкая разница в строгости между vecAddA и vecAddB

vecAddA ⊥ ⊥ = Vector ⊥ ⊥ ⊥
vecAddB ⊥ ⊥ = ⊥

Чтобы получить ту же семантику при использовании соответствия шаблону, нужно использовать неопровержимые шаблоны.

vecAddB' ~(Vector vx vy vz) ~(Vector wx wy wz)
    = Vector (vx + wx)
             (vy + wy)
             (vz + wz)

Однако в этом случае поля Vector, вероятно, должны быть строгими, чтобы начать с эффективности:

data Vector = Vector { x :: !Double
                     , y :: !Double
                     , z :: !Double
                     }

Со строгими полями vecAddA и vecAddB являются семантически эквивалентными.