Функция, работающая с конечным, но произвольным числом разнородных элементов

Я работаю над библиотекой для изучения теоретико-игрового обучения. В этой настройке N агенты объединяются и взаимодействуют со средой. Каждый агент выводит модель взаимодействия. Модель одного агента зависит от его N-1 противников. Я написал код, определяющий эту модель для агентов 1 и агентов 2, и пытаюсь обобщить его. Вот часть кода, который у меня есть:

data System w x a s h h' = System { signaling :: SignalingWXAS w x a s
                                  , dynamic   :: DynamicXAS x a s
                                  , strategy  :: MockupStrategy x a s h h' }

data JointState w x a s h h' = JointState { worldState  :: w
                                          , state       :: x
                                          , mockupState :: MockupState a s h h' }

systemToMockup :: ( Bounded w, Ix w, Bounded x, Ix x
                  , Bounded a, Ix a, Bounded s, Ix s
                  , Bounded (h a), Ix (h a), Bounded (h' s), Ix (h' s)
                  , History h, History h'
                  ) => System w x a s h h' -> Mockup a s h h'
systemToMockup syst = mock
    where
      mock z   = norm $ statDist >>=? condit z >>= computeStatesAndAction >>= sig >>=$ extractSignal
      statDist = ergodicStationary $ transition syst
      condit z = just z . mockupState
      sig      = uncurryN $ signaling syst
      strat    = strategy syst
      computeStatesAndAction joint = do
        let w = worldState joint
        let x = state joint
        a <- strat x (mockupState joint)
        return (w, x, a)
      extractSignal (_, s) = s

и

data System2 w x1 a1 s1 h1 h1' x2 a2 s2 h2 h2' = System2 { signaling :: SignalingWXAS2 w x1 a1 s1 x2 a2 s2
                                                         , dynamic1  :: DynamicXAS x1 a1 s1
                                                         , dynamic2  :: DynamicXAS x2 a2 s2
                                                         , strategy1 :: MockupStrategy x1 a1 s1 h1 h1'
                                                         , strategy2 :: MockupStrategy x2 a2 s2 h2 h2' }

data JointState2 w x1 a1 s1 h1 h1' x2 a2 s2 h2 h2' = JointState2 { worldState   :: w
                                                                 , state1       :: x1
                                                                 , mockupState1 :: MockupState a1 s1 h1 h1'
                                                                 , state2       :: x2
                                                                 , mockupState2 :: MockupState a2 s2 h2 h2' }
systemToMockups2 syst = (mock1, mock2)
    where
      mock1 z1   = norm $ statDist >>=? condit1 z1 >>= computeStatesAndActions >>= sig >>=$ extractSignal1
      mock2 z2   = norm $ statDist >>=? condit2 z2 >>= computeStatesAndActions >>= sig >>=$ extractSignal2
      statDist   = ergodicStationary $ transition2 syst
      condit1 z1 = just z1 . mockupState1
      condit2 z2 = just z2 . mockupState2
      sig        = uncurryN $ signaling syst
      strat1     = strategy1 syst
      strat2     = strategy2 syst
      computeStatesAndActions joint = do
        let w  = worldState joint
        let x1 = state1 joint
        let x2 = state2 joint
        a1 <- strat1 x1 (mockupState1 joint)
        a2 <- strat2 x2 (mockupState2 joint)
        return (w, x1, a1, x2, a2)
      extractSignal1 (_, s, _) = s
      extractSignal2 (_, _, s) = s

Мне нужно определение функции для systemToMockupN, которое могло бы вместить любое конечное число агентов.

Агенты неоднородны, поэтому использование списков напрямую невозможно. Я не могу использовать кортежи, потому что заранее не знаю их размер. Я пытался использовать curryN, uncurryN и т. д., но не смог выполнить одну операцию над каждым элементом кортежа. Я безуспешно пытался построить функцию с переменным числом аргументов по типу printf.

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


person Nicolas Dudebout    schedule 20.09.2012    source источник
comment
Лучшее решение для использования разнородных коллекций — не делать этого. Предположительно эти агенты взаимодействуют с общей мировой средой. Можете ли вы полностью представить агент с точки зрения его операций в этой общей среде и/или операций с другими агентами, используя такое представление? Это обычный способ гомогенизировать такие типы.   -  person C. A. McCann    schedule 20.09.2012
comment
Эта проблема была бы намного проще, если бы все агенты имели один и тот же тип. Можете ли вы объяснить, почему с каждым агентом связаны разные типы?   -  person Heatsink    schedule 20.09.2012
comment
Вот один простой пример. Мы хотим смоделировать агентов, участвующих в так называемой интеллектуальной сети. У нас есть атомные электростанции, угольные электростанции, ветряные электростанции, дата-центры, экологически чистые потребители. Все эти агенты имеют очень разную динамику и разные способы воздействия на окружающую среду.   -  person Nicolas Dudebout    schedule 20.09.2012
comment
Предложение C.A.McCann очень сильное: не можете ли вы работать с обобщением, используя тип идеи Agent = World -> Position -> (World -> World) или аналогичную производную от него?   -  person AndrewC    schedule 20.09.2012
comment
У меня есть одна концепция гомогенизации. Агент — это объект, который принимает сигнал в качестве входных данных и генерирует действие. Окружающая среда — это объект, выполняющий N действия и генерирующий N сигналы. Агент может работать с любой средой, принимая правильное действие и генерируя правильный сигнал. Это не полностью гетерогенно, но пространства действия и сигнала являются произвольными конечными множествами.   -  person Nicolas Dudebout    schedule 20.09.2012
comment
Кроме того, если типы агентов легче ограничить, чем их действия в среде, вы также можете использовать подход, заключающийся в их объединении в качестве конструкторов одного типа данных, возможно, с использованием GADT для дополнительного манипулирования типами. Это просто переносит проблему с коллекции на сам тип агентов, который в зависимости от того, что вам нужно, может быть лучше или хуже.   -  person C. A. McCann    schedule 20.09.2012
comment
Из того, что вы говорите, я чувствую, что лучше всего начать с переноса проблемы на действия и сигналы, а затем посмотреть, что нужно сделать, чтобы эта часть работала хорошо. К сожалению, на подобные вопросы действительно сложно ответить, не видя гораздо больше вашего фактического кода, чем это практично на SO.   -  person C. A. McCann    schedule 20.09.2012
comment
Я предлагаю написать сигнатуры типов для ваших первых нескольких гипотетических функций systemToMockup, чтобы нам было легче вносить предложения.   -  person Gabriel Gonzalez    schedule 20.09.2012
comment
Я не ставил сигнатуры типов, потому что они основаны на других типах и несут с собой довольно много ограничений. Теперь я добавил сигнатуру типа первой функции.   -  person Nicolas Dudebout    schedule 20.09.2012
comment
@C.A.McCann, я пытался создать меньший пример с той же проблемой, но всегда получал примеры либо тривиальные, либо не отражающие то, что мне нужно. Для справки: код находится на github по следующему адресу: github. .com/dudebout/game-theoretic-learning/tree/master/GTL/. Два файла, которые я пытаюсь объединить, называются Consistency.hs и Consistency2.hs.   -  person Nicolas Dudebout    schedule 20.09.2012
comment
Я не жаловался на отсутствие деталей, хотя ссылки на github в любом случае полезны. На самом деле, я, вероятно, мог бы сказать вам, что меньший пример не будет работать. На самом деле, я сомневаюсь, что для такого рода вопросов вообще существует локализованное общее решение, и что вам нужно более крупномасштабное мета-решение, которое позволит вам переформулировать вещи таким образом, чтобы это работало легче. (Если это не очевидно, я сталкивался с подобными ситуациями в своем собственном коде...)   -  person C. A. McCann    schedule 20.09.2012


Ответы (5)


Обобщенные алгебраические типы данных (GADT).

Они позволяют объединить конечное число действительно разнородных типов данных в один и представляют собой современный способ создания экзистенциальных типов. Они находятся где-то между подходом data Agent = AH Human | AP Plant | .... и подходом HList. Вы можете сделать все ваши невероятно разнородные агенты экземплярами некоторого класса типов, а затем связать их вместе в файле AgentGADT. Убедитесь, что в вашем классе типов есть все, что вы когда-либо захотите сделать с агентом, потому что трудно получить данные обратно из GADT без функции с явным типом; вам понадобится getHumans [AgentGADT] -> [Human]? или updateHumans :: (Human->Human) -> [AgentGADT] -> [AgentGADT]? Это было бы проще с обычным абстрактным типом данных в моем другом посте.

Плюсы: вы можете иметь [AgentGADT] и работать единообразно, используя функции класса, при этом создавая странные и чудесно параметризованные типы данных. Минусы - трудно получить данные вашего агента, когда они есть.

Моим любимым вступительным текстом в Интернете был GADT для чайников.

person AndrewC    schedule 20.09.2012
comment
Судя по описанию, это может меня заинтересовать. Все, что меня волнует в любом агенте, это то, что он генерирует действие из сигнала и внутреннего состояния. - person Nicolas Dudebout; 20.09.2012
comment
В итоге я использовал GADT для реализации вложенных кортежей в стиле HList. - person Nicolas Dudebout; 16.10.2012

Гетерогенный. Я не думаю, что вы должны это делать, но вы просили.

Вы можете использовать существующую библиотеку для гетерогенных списков, например, HList. Это в любом случае тайно использует Template Haskell, но вам не нужно так пачкать руки, как если бы вы сделали это сами.

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

person AndrewC    schedule 20.09.2012
comment
Оригинальный HList в наши дни является скорее историческим артефактом, чем полезной библиотекой. Есть лучшие способы сделать то же самое. Кроме того, я не уверен, для чего нужна зависимость TH, потому что основная функциональность HList не использует ее. - person C. A. McCann; 20.09.2012
comment
@C.A.McCann: Тогда, пожалуйста, напишите о лучших способах! Мне все равно не нравится это решение! - person AndrewC; 21.09.2012
comment
Лучшие способы включают в себя огромное количество расширений GHC, добавленных с тех пор, как была создана исходная версия HList, в некоторых случаях специально для того, чтобы было удобнее делать такие вещи, как HList. Но я на самом деле не пробовал использовать некоторые из новейших вещей, поэтому я не могу много о них сказать. - person C. A. McCann; 21.09.2012
comment
Итак, вернемся к совету №1: не делайте разнородных списков. Опытные, знающие и уважаемые члены сообщества, такие как @C.A.McCann, избегают их; так и вы должны. :) - person AndrewC; 21.09.2012
comment
Безусловно, у гетерогенных списков есть свое применение. Но конкретно для этого вопроса я не думаю, что они подходят. - person C. A. McCann; 21.09.2012

Не становитесь гетерогенным. Это того не стоит. Стоит найти лучший способ. Вот один из способов избежать этого. (Есть и другие пути.) Может быть произвольное количество агентов, но, конечно же, не может быть произвольного количества типов агентов. (Действительно ли типы должны быть так параметризованы? Боюсь, ваша общность слишком дорого вам обходится.

    class AnAgent a where 
         liveABit :: World -> a -> (World,a)
         ...
         ...

    data NuclearPlant = ....
    data CoalPlant = ....
    data WidFarm = .....

    data DataCenter = .....

    data EnvFriendly = .....
    data PetrolHead = .....

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

    data Plant = PN NuclearPlant | PC CoalPlant | PW WindFarm
    data Human = HEF EnvFriendly | HPE PetrolHead

    data Agent = AP Plant | AH Human | AD DataCenter

Общее/гетерогенное лечение внутри групп/между группами:

    bump :: Agent -> Agent
    bump (Human h) = Human (breakleg h)
    bump a = a

Затем вы можете определить всех агентов, которых хотите, затем поместить их в [Agent] и определить хороший eachLiveABit :: World -> [Agent] -> (World,[Agent]) для обновления мира и его агентов. Вы можете сделать AnAgent экземпляров отдельных типов или групп агентов и построить до Agent (или, может быть, даже обойтись без класса типов и просто использовать обычные функции).

Это последует за преобразованием программы (Classes + Interesting Type System Features) -> (Types and Higher Order Functions), которое с эмоциональной точки зрения похоже на то, что вы немного отупели, но устраняет большую часть проблем.

person AndrewC    schedule 20.09.2012
comment
Я думал о том, чтобы сделать это таким образом, но я пытался написать код, который не зависит от сценария. Например, еще один сценарий, с которым я работаю, — это какой-то рынок с разными типами инвесторов. Мне пришлось бы переопределить data Agent для этого нового сценария. Хотя может получится, я подумаю. - person Nicolas Dudebout; 20.09.2012
comment
@NicolasDudebout: Можно ли с пользой параметризовать остальную часть кода, выбрав тип Agent для такого подхода? Если это так, это может работать очень хорошо. - person C. A. McCann; 20.09.2012
comment
Я не уверен, что понимаю, в чем вопрос. - person Nicolas Dudebout; 20.09.2012

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

data Mockup = Mockup1 <return type of systemToMockup>
            | Mockup2 <return type of systemToMockups2>

class SystemToMockup a where
    systemToMockup :: a -> Mockup

instance SystemToMockup (System1 ...) where
    <implementation of systemToMockup>

instance SystemToMockup (System2 ...) where
    <implementation of systemToMockups2>

Это довольно ограниченное решение, и я сомневаюсь, что оно сработает для вас, поскольку ваше приложение кажется довольно сложным.
В целом подход C. A. McCann намного лучше.

person mmh    schedule 20.09.2012
comment
Я хочу избежать необходимости переписывать код для каждого числа N. Это решение позволяет мне факторизовать вызов, но мне все еще нужно написать N версии. - person Nicolas Dudebout; 20.09.2012

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

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

data Thing1 = ...
data Thing2 = ...

data AThing = AThing {
  op1 :: ...
  op2 :: ...
}

class IsAThing a where
    mkAThing :: a -> AThing

instance IsAThing Thing1 where
    mkAThing a = AThing {
            op1 = op1_for_thing1 a,
            op2 = op2_for_thig1 a
        }

Затем, чтобы вызвать op1 на любом IsATThing:

op1 (mkAThing a) <...other args...>

Хотя в вашем случае вам нужен список AThing:

[mkAThing a2, mkAThing a2, ...]

Затем на любом элементе:

op1 <element of that list> <...other args...>
person singpolyma    schedule 20.09.2012
comment
У меня проблема с типом a. Этот тип варьируется для разных моих агентов. - person Nicolas Dudebout; 21.09.2012
comment
@NicolasDudebout в этом примере тоже все по-разному. Я привел пример для Thing1, но вся суть такого подхода в том, что тип может варьироваться :) - person singpolyma; 21.09.2012
comment
Но вы должны указать тип для op1 и op2. Мои типы будут разными. - person Nicolas Dudebout; 21.09.2012
comment
@NicolasDudebout типы аргументов, отличных от того, что указан в списке, и/или тип возвращаемого значения будут различаться? Или вы думаете, что необходимо включить тип a каждого из Thing1, Thing2 и т. д.? Этот тип не включен, потому что функция частично применяется к тому времени, когда вы доберетесь до этого места. - person singpolyma; 21.09.2012
comment
AThing в моем случае потребуется тип, потому что операция аналогична для всех агентов, но с другим типом. - person Nicolas Dudebout; 21.09.2012
comment
@NicolasDudebout Я думаю, вы упускаете из виду весь смысл этого. Дело в том, что если у вас есть набор функций, принимающих различные типы, то вы можете частично применить их к аргументу другого типа, а то, что останется, будет функция одного типа. singpolyma.net/2012/08/heterogenous-collections-in-haskell - person singpolyma; 21.09.2012
comment
Или, если у вас есть одна функция параметрически типизированная, но вам нужен только один тип, чтобы вы могли поместить ее в структуру данных, это также работает для этого случая. - person singpolyma; 21.09.2012
comment
На самом деле, если бы они не были разными типами, в этом не было бы смысла, ведь можно было бы просто составить их список :) - person singpolyma; 21.09.2012
comment
Моя функция op1 является стратегией и имеет тип strat :: State -> Action -> Strategy, где State, Action и Strategy — конечные пространства, которые не могут быть определены в библиотеке. Они даны по сценарию. В примере на вашем сайте у вас фиксированные наборы Int и Bool. - person Nicolas Dudebout; 21.09.2012
comment
@NicolasDudebout а, у вас полиморфный тип возврата? Да, это одна вещь, которую это не может сделать. Я думал, что упоминал об этом выше, но, похоже, нет. - person singpolyma; 21.09.2012