LINUX.ORG.RU

Как сделать несколько типов с одними и теми же конструкторами в Haskell?

 


1

2

Файл Test.hs:

data RestrictionMechanismName = Seccomp | AppArmor | SeLinux | Namespaces | Chroot | Capabilities | CGroups

data LinuxSecurityModule = AppArmor | SeLinux

Из-за строки
data LinuxSecurityModules = AppArmor | SeLinux

`stack runhaskell Test.hs` выдаёт ошибку
Test.hs:3:29:
    Multiple declarations of ‘AppArmor’
    Declared at: Test.hs:1:43
                 Test.hs:3:29

Test.hs:3:40:
    Multiple declarations of ‘SeLinux’
    Declared at: Test.hs:1:54
                 Test.hs:3:40

Но по смыслу AppArmor и SeLinux это и RestrictionMechanismName и LinuxSecurityModule. Как выход пока сделал функцию:
linuxSecurityModules :: [RestrictionMechanismName]
linuxSecurityModules = [AppArmor, SeLinux]

Есть ли возможность в Haskell сделать, чтобы значение принадлежало нескольким типам по-другому для этого примера?


Засунь их в разные модули + qualified import.

Foo.hs:

module Foo where

data Foo = Baz deriving Show

Bar.hs:

module Bar where

data Bar = Baz deriving Show

Main.hs:

import qualified Foo as F
import qualified Bar as B

main = do
  print F.Baz
  print B.Baz

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 2)
Ответ на: комментарий от theNamelessOne

Да, может быть решением. Какие ещё другие есть варианты кроме:
а) функции
б) разнесения по модулям
?

ProtoH
() автор топика

Но по смыслу AppArmor и SeLinux это и RestrictionMechanismName и LinuxSecurityModule

хреновый у вас смысл, а добавить куда-нибуть префикс, например: ... | ModAppArmor?

либо RestrictionMechanismName = ... | Module LinuxSecurityModule

anonymous
()
Ответ на: комментарий от ProtoH

Про функцию не понял, но вариантов сходу других надумать не могу. Конструктор, по сути, — это сгенерированная компилятором функция, которая возвращает значение своего типа. Ты можешь создать две функции с одинаковым именем в одном модуле? Нет.

Правда, можно ещё сделать, как подсказывает анон сверху:

data RestrictionMechanismName = Seccomp 
                              | Namespaces 
                              | Chroot 
                              | Capabilities 
                              | CGroups
                              | Module LinuxSecurityModule

data LinuxSecurityModule = AppArmor | SeLinux
theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от ProtoH

Какие ещё другие есть варианты кроме:

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

iVS ★★★★★
()
Последнее исправление: iVS (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

Ты можешь создать две функции с одинаковым именем в одном модуле? Нет.

В последнем GHC можно давать одинаковые имена полям в record.

iVS ★★★★★
()
Ответ на: комментарий от theNamelessOne

Вариант анона годный, только вот в паттерн-матчинге слишком много писать. Вообще, по этой причине очень плохо давать длинные имена конструкторам.

iVS ★★★★★
()
Последнее исправление: iVS (всего исправлений: 1)

Все не так, если множество закрытое, то:

data RMN [RMT] where
   SeLinux :: RMN [Linux]
   Christ :: RMN [Linux, BSD, OSX]
   ...

data RMT = Linux | BSD | OSX ...

Если открытое, то через экзестенкциальнве типы.

qnikst ★★★★★
()
Ответ на: комментарий от ProtoH

Когда AppArmor (например) описывается в рамках языка из контекста понятно (ну написавшему код): RestrictionMechanismName или LinuxSecurityModule. Но это разные роли, а значит, в обшем случае, и разные обертки. Неплохо себя спрoсить: «какой _тип_ должен быть у AppArmor»?

anonymous
()
Ответ на: комментарий от anonymous

Ну если спросить «какой тип должен быть у AppArmor», то можно ответить, что смотря как посмотреть - тип может быть разный, может рассматриваться как элемент LinuxSecurityModule в одном контексте, а в другом как RestrictionMechanism. Собственно ищется ответ на вопрос, как реализовать на Haskell то, что в ООП сделалось бы через реализацию интерфейса:

class AppArmor implements RestrictionMechanism, LinuxSecurityModule

ProtoH
() автор топика
Ответ на: комментарий от qnikst

Насколько я понимаю этот пример использует GADT. Так ли это? Как читается такое определение? Где больше почитать про эту технику?

ProtoH
() автор топика
Ответ на: комментарий от ProtoH

да, GADTs и DataKinds, наверное на haskell wiki и по ссылкам оттуда. Так же в блоге Эйзенберга, вообще я не помню, чтобы видел где-то структурировано в одном месте все и сразу. Про вопрос сейчас ещё подробнее напишу, а то с телефона до этого писал.

qnikst ★★★★★
()

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

Однако, на мой взгляд такая проблема тут возникать не должна. Как я понял задачу пишутся классы для описания разным механизмов позволяющих ограничить права пользователя в разных системах, т.е. есть Seccomp | AppArmor | SeLinux | Namespaces | Chroot | Capabilities | CGroups (на мой взгляд это немного странная компания, ну ладно). И далее есть их «классификация». Тут возможны несколько вариантов, во-первых нужно сразу разграничить это open universe или closed, в первом случае это type classes + type families, во втором случае можно обойтись простым ADT или GADT.

Рассмотрим второй случай, пусть мы можем перечислить все типы RestrictionMechanism (далее RM), тогда достаточно ADT, как это сделано. Но тут мешается классификация, если классификация непрекращающаяся, то опять же все просто - разносим каждый механизм в свою группу:

data SecurityModules = AppArmor | SeLinux
data ResouceControl  = CGroups
data Contrainers     = Seccomp | Namespaces | CGroups
и объединять это в одну ADT
data RM = RMSM SecurityModules | RMRC ResourceControl | RMRC Containers

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

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

data Tags = Kernel | UserSpace | Container | SecurityModule | ResourceUsage

далее мы можем иметь тип с кайдом [Tags] -> * т.е. мы передаем тайплевел список свойств, пример уходит от задачи, но это временно

data RM (ts :: [Tags]) = RM a
appArmour :: AppArmourParams -> RM ['Kernel, 'SecurityModule]

если у нас мир закрытый, то мы можем объединить все в один GADT

data RM (s :: [Tags]) where
  AppArmour :: RM ['Kernel, 'SecurityModule]
  Selinux   :: RM ['Kernel, 'ResourceUsage]

далее можно ввести typefamily:

type family In a b where
   In a (a ': as) = ()
   In a (b ': bs) = In a bs

тогда можно писать фунции вида

fib :: In SecurityModule a => RM a -> IO ()
fib _ = launchMissiles

Проблема в том, что тут Ghc тупой и заставит матчить невозможные случаи, поэтому если возможно сделать один тип где можно обойтись без TF то будет лучше, т.е. если вместо s :: [Tags] может быть s::'CleverType, тогда при выборе констуктора CleverType, ghc сам откинет невозможные случаи.

Итого, если идея тут в том, что дополнительную информацию можно вынести на typelevel, объединить все в GADT. Вопрос когда нужно это, а когда - разделение ADT, это уже зависит от задачи, а действительно ли типы должны тут объединяться на одном уровне. По задаче, к сожалению этого не угадать.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Спасибо, что так подробно написал, буду разбираться.

ProtoH
() автор топика

Спасибо всем, кто ответил в этой теме. Прошу считать вопрос решённым.

ProtoH
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.