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

Haskell - любой способ квалифицировать или дизамешировать имена записей?

У меня есть два типа данных, которые используются для шаблонов hastache. В моем коде имеет смысл иметь два разных типа, как с полем с именем "имя". Это, конечно, вызывает конфликт. Похоже, что существует механизм, позволяющий устранить любые вызовы "имя", но фактическое определение вызывает проблемы. Есть ли способ обхода проблемы, например, разрешить имя поля записи?

data DeviceArray = DeviceArray
    { name :: String,
      bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

data TemplateParams = TemplateParams
    { arrays :: [DeviceArray],
      input :: DeviceArray }
    deriving (Eq, Show, Data, Typeable)

data MakefileParams = MakefileParams
    { name :: String }
    deriving (Eq, Show, Data, Typeable)

то есть. если поля теперь используются в коде, они будут "DeviceArray.name" и "MakefileParams.name"?

4b9b3361

Ответ 1

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

Если два поля четко различаются, вы всегда будете знать, что вы используете. Под "явным различием" здесь я имею в виду, что никогда не было бы обстоятельств, когда было бы целесообразно делать то же самое с любой областью. Учитывая это, излишняя двусмысленность на самом деле не является нежелательной, поэтому вам нужен либо квалифицированный импорт в качестве стандартного подхода, либо расширение полезна неоднозначности, если это больше по вашему вкусу. Или, как очень упрощенный (и слегка уродливый) вариант, просто вручную префикс полей, например. deviceArrayName вместо name.

Если два поля в некотором смысле одно и то же, имеет смысл уметь относиться к ним однородным образом; в идеале вы можете написать функцию полиморфную в поле выбора name. В этом случае один из вариантов использует класс типа для "именованных вещей" с функциями, которые позволяют вам получить доступ к полю name для любого подходящего типа. Главным недостатком здесь, помимо распространения тривиальных ограничений типа и возможных головных болей от Ограниченного Ограничения Мономорфизма, является то, что вы также теряете способность использовать синтаксис записи, который начинает побеждать всю точку.

Другим важным вариантом для подобных полей, который я еще не видел, является извлечение поля name в один параметризованный тип, например. data Named a = Named { name :: String, item :: a }. Сам GHC использует этот подход для расположения источников в деревьях синтаксиса, и, хотя он не использует синтаксис записи, идея одинаков. Недостатком здесь является то, что если у вас есть Named DeviceArray, доступ к полю bytes теперь требует прохождения двух уровней записей. Если вы хотите обновить поле bytes с помощью функции, вы застряли в чем-то вроде этого:

addBytes b na = na { item = (item na) { bytes = b + bytes (item na) } }

Тьфу. Есть способы немного смягчить проблему, но они все еще не идеи, на мой взгляд. Случаи, подобные этому, почему мне не нравится синтаксис записи вообще. Итак, в качестве окончательного варианта, магия Template Haskell и пакет fclabels:

{-# LANGUAGE TemplateHaskell #-}

import Control.Category
import Data.Record.Label

data Named a = Named 
    { _name :: String, 
      _namedItem :: a }
    deriving (Eq, Show, Data, Typeable)

data DeviceArray = DeviceArray { _bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

data MakefileParams = MakefileParams { _makefileParams :: [MakeParam] }
    deriving (Eq, Show, Data, Typeable)

data MakeParam = MakeParam { paramText :: String }
    deriving (Eq, Show, Data, Typeable)

$(mkLabels [''Named, ''DeviceArray, ''MakefileParams, ''MakeParam])

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

addBytes b = modL (namedItem >>> bytes) (b +)
nubParams = modL (namedItem >>> makefileParams) nub

Вы также можете назвать bytes что-то вроде bytesInternal, а затем экспортировать аксессор bytes = namedItem >>> bytesInternal, если хотите.

Ответ 2

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

Общими способами работы с этим является либо добавление префиксов к именам полей, например. daName, mpName или поместить их в отдельные модули, которые вы затем import qualified.

Ответ 3

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

Ответ 4

Есть несколько расширений GHC, которые могут помочь. Связанный один применим в вашем случае.

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

Ответ 5

Если вы хотите использовать имя в обоих, вы можете использовать класс, определяющий имя funcion. Например:

Class Named a where
    name :: a -> String

data DeviceArray = DeviceArray
    { deviceArrayName :: String,
      bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

instance Named DeviceArray where
    name = deviceArrayName

data MakefileParams = MakefileParams
    { makefileParamsName :: String }
    deriving (Eq, Show, Data, Typeable)

instance Named MakefileParams where
    name = makefileParamsName

И затем вы можете использовать name для обоих классов.