Подтвердить что ты не робот

Haskell: Путаница с собственными типами данных. Синтаксис записи и уникальные поля

Я только что раскрыл эту путаницу и хотел бы подтвердить, что это то, что есть. Если, конечно, я просто что-то пропустил.

Скажем, у меня есть эти объявления данных:

data VmInfo = VmInfo {name, index, id :: String} deriving (Show)
data HostInfo = HostInfo {name, index, id :: String} deriving (Show)

vm = VmInfo "vm1" "01" "74653"
host = HostInfo "host1" "02" "98732"

То, что я всегда думал и что кажется настолько естественным и логичным, таково:

vmName = vm.name
hostName = host.name

Но это, очевидно, не работает. Я получил это.


Вопросы

Итак, мои вопросы.

  • Когда я создаю тип данных с синтаксисом записи, должен ли я убедиться, что все поля имеют уникальные имена? Если да - почему?

  • Есть ли какой-то чистый способ или что-то подобное "оператору разрешения области", например :: или . и т.д., так что Haskell отличает, какой тип данных name (или любой другой нет уникальные поля) принадлежит и возвращает правильный результат?

  • Каков правильный способ справиться с этим, если у меня есть несколько объявлений с одинаковыми именами полей?


В качестве дополнительной заметки.

В общем, мне нужно возвращать типы данных, аналогичные приведенному выше примеру. Сначала я вернул их в виде кортежей (в то время мне казалось правильным способом). Но кортежи с трудом работают, поскольку невозможно просто выделить отдельные части сложного типа, как с помощью списков, используя "!!". Итак, следующее, что я подумал о словарях/хешах. Когда я пробовал использовать словари, я подумал, что тогда нужно иметь собственные типы данных? Типы воспроизведения/обучения данных я столкнулся с тем, что привело меня к вышеуказанному вопросу. Поэтому мне кажется, что легче использовать словари вместо собственных типов данных, так как я могу использовать те же поля для разных объектов.


Не могли бы вы рассказать об этом и рассказать мне, как это делается в реальном мире?

4b9b3361

Ответ 1

Синтаксис записи Haskell немного взломан, но имя записи появляется как функция, и эта функция должна иметь уникальный тип. Таким образом, вы можете делиться именами полей записей среди конструкторов одного типа данных, но не среди отдельных типов данных.

Каков правильный способ справиться с этим, если у меня есть несколько объявлений с одинаковыми именами полей?

Вы не можете. Вы должны использовать разные имена полей. Если вы хотите, чтобы перегруженное имя выбиралось из записи, вы можете попробовать использовать класс типа. Но в основном имена полей в Haskell не работают так, как они говорят, C или Pascal. Вызов "синтаксиса записи" мог быть ошибкой.

Но кортежи трудно работать, поскольку невозможно извлечь отдельные части сложного типа

На самом деле, это может быть довольно легко с помощью сопоставления с образцом. Пример

smallId :: VmInfo -> Bool
smallId (VmInfo { vmId = n }) = n < 10

Что касается того, как это делается в "реальном мире", программисты Haskell склонны в значительной степени полагаться на то, что тип каждого поля находится во время компиляции. Если вы хотите, чтобы тип поля изменялся, программист Haskell вводит параметр типа для переноса различной информации. Пример

data VmInfo a = VmInfo { vmId :: Int, vmName :: String, vmInfo :: a }

Теперь вы можете иметь VmInfo String, VmInfo Dictionary, VmInfo Node или все, что хотите.

Сводка: каждое имя поля должно принадлежать к уникальному типу, а опытные программисты Haskell работают с системой статического типа, а не пытаются обойти его. И вы определенно хотите узнать о соответствии шаблонов.

Ответ 2

Есть еще несколько причин, почему это не работает: строчные имена и конструкторы данных, доступ к элементам стиля OO-языка с .. В Haskell эти функции доступа к члену фактически являются свободными функциями, то есть vmName = name vm, а не vmName = vm.name, поэтому они не могут иметь одинаковые имена в разных типах данных.

Если вам действительно нужны функции, которые могут работать как для объектов VmInfo, так и HostInfo, вам нужен класс типа, например

class MachineInfo m where
  name :: m -> String
  index :: m -> String    -- why String anyway? Shouldn't this be an Int?
  id :: m -> String

и создавать экземпляры

instance MachineInfo VmInfo where
  name (VmInfo vmName _ _) = vmName
  index (VmInfo _ vmIndex _) = vmIndex
  ...
instance MachineInfo HostInfo where
  ...

Тогда name machine будет работать, если machine является VmInfo, а также если он HostInfo.

Ответ 3

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

В настоящее время вы можете выполнять уникальные имена полей или определять каждый тип в своем собственном модуле и использовать имя, соответствующее модулю.

Ответ 4

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

Узнайте больше о семействе типов объективов и функций здесь: http://lens.github.io/tutorial.html

В качестве примера того, как они выглядят, это фрагмент из примера Pong, найденного на приведенной выше странице github:

data Pong = Pong
  { _ballPos :: Point
  , _ballSpeed :: Vector
  , _paddle1 :: Float
  , _paddle2 :: Float
  , _score :: (Int, Int)
  , _vectors :: [Vector]

  -- Since gloss doesn't cover this, we store the set of pressed keys
  , _keys :: Set Key
  }

-- Some nice lenses to go with it
makeLenses ''Pong

Это делает объективы доступными к членам без символов подчеркивания через магию TemplateHaskell.

Позже, пример их использования:

-- Update the paddles
updatePaddles :: Float -> State Pong ()
updatePaddles time = do
  p <- get

  let paddleMovement = time * paddleSpeed
      keyPressed key = p^.keys.contains (SpecialKey key)

  -- Update the player paddle based on keys
  when (keyPressed KeyUp) $ paddle1 += paddleMovement
  when (keyPressed KeyDown) $ paddle1 -= paddleMovement

  -- Calculate the optimal position
  let optimal = hitPos (p^.ballPos) (p^.ballSpeed)
      acc = accuracy p
      target = optimal * acc + (p^.ballPos._y) * (1 - acc)
      dist = target - p^.paddle2

  -- Move the CPU paddle towards this optimal position as needed
  when (abs dist > paddleHeight/3) $
    case compare dist 0 of
      GT -> paddle2 += paddleMovement
      LT -> paddle2 -= paddleMovement
      _ -> return ()

  -- Make sure both paddles don't leave the playing area
  paddle1 %= clamp (paddleHeight/2)
  paddle2 %= clamp (paddleHeight/2)

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

Ответ 5

Да, вы не можете иметь две записи в одном модуле с одинаковыми именами полей. Имена полей добавляются в область видимости модуля как функции, поэтому вы должны использовать name vm, а не vm.name. У вас могут быть две записи с одинаковыми именами полей в разных модулях и импортировать один из модулей, квалифицированных как какое-то имя, но это, вероятно, неудобно для работы.

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

data VMInfo = VMInfo String String String

(Обратите внимание, что значение VMInfo должно быть заглавным.)

Теперь вы можете получить доступ к полям VMInfo по шаблону:

myFunc (VMInfo name index id) = ... -- name, index and id are bound here