2011-08-02 2 views
14

Пусть У меня есть Vector тип данных определяется следующим образом:Haskell: Предпочитаете соответствие шаблону или члену доступа?

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) 

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

+3

Просто для полноты: есть также другая форма поиска по шаблону с использованием полей записи: 'vecAddA (Vector {х = ух, у = уу, г = VZ}) (Vector {х = WX, у = wy, y = wz}) = ... ' – hvr

ответ

12

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

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

fun x = a x + 1 

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

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

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

+1

На оборотной стороне, если у вас есть данные Foo = A {f :: Int} | B {f :: Int} ', тогда было бы более сжато написать одну строку, используя функцию' f', чтобы извлечь значение для любого из этих случаев (если логика для любого случая одинакова), вместо того, чтобы писать две строки в шаблон соответствуют обоим случаям. –

+0

Да, это, вероятно, хорошая идея для типа типа 'data SomeData = TextData {length :: Int, text :: String} | BinaryData {length :: Int, blob :: [Word8]} '), где два аргумента конструктора разделяют как тип, так и цель. – valderman

+0

@ Дэн Бертон: На ​​флип-флэшиде, если использовать ту же логику для обоих, разумно для такого типа, подумайте о том, чтобы переписать его как 'data FooCase = A | B' и 'данные Foo = Foo {fooCase :: FooCase, f :: Int}', что также дает вам простой способ проверить A против B, не беспокоясь об остальном. –

4

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

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

+3

Согласен, нет никакой функциональной разницы между этими двумя, и я не (на данном этапе) обеспокоен производительностью. Меня больше интересует, будет ли более опытный Haskeller смотреть на один из них и думать «это странный способ его написания». – stusmith

7

Еще один минорный аргумент «реального мира»: в целом, это не очень хорошая идея иметь такие короткие имена записей записи, поскольку короткие имена, такие как 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) 

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

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

Что касается второго пункта: «Вы хотите оставаться гибким, чтобы впоследствии изменить запись, например, добавить больше полей». Это также может быть поводом для привязки (позиционного) соответствия шаблона, так как тогда вам нужно пройти каждый шаблон, чтобы сделать так, чтобы вы не забыли обновить свои функции wrt обработка дополнительных полей (например, подумайте о случаях, написанных вручную в NFData) – hvr

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), просто чтобы проиллюстрировать, что я имел в виду от производительности s язь.

+0

+1 Вы правы. Я даже пропустил это сам при первом прочтении. Я привел несколько более ясный пример в своем ответе. – hammar

2

В kizzx2 pointed out, есть тонкое различие в строгости между 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 семантически эквивалентны.

+0

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

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