Как построить рекурсивные предикаты/функции в Alloy

Я пытаюсь сгенерировать в Alloy два набора классов, например, классы до приложения рефакторинга и классы после приложения рефакторинга. Предположим, в первом наборе у нас есть следующие классы:

ALeft -> BLeft -> CLeft    
                  Class1
                  Class2 -> Class3
                         -> Class4

это означает, что ALeft является родителем BLeft, который, в свою очередь, является родителем CLeft, Class1 и Class2, который, в свою очередь, является родителем Class3 и Class4.

С другой стороны, следуя тем же рассуждениям, мы имеем во втором множестве следующую группу классов:

ARight -> BRight -> CRight
                    Class1'
                    Class2' -> Class3'
                            -> Class4'

Поскольку каждый набор представляет одни и те же классы, но в разном хронологическом порядке (разных состояниях), необходимо гарантировать, что соответствующие эквивалентности, такие как Class1 и Class1', эквивалентны, что означает, что они имеют одни и те же поля, методы и т. д. (учитывайте, что рефакторинг происходит только в классах B и C). По тем же причинам Class2 и Class2', Class3 и Class3', Class4 и Class4' также эквивалентны. Кроме того, у нас должна быть эквивалентность методов в классах Left и Right. Например, если у нас есть метод класса Left, например:

public int leftClassMethod(){
    new ALeft().other();
}

Затем должен быть соответствующий метод класса Right, например:

public int rightClassMethod(){
    new ARight().other();
}

Как предложил Лоик (в этом списке для обсуждения), эквивалентность этих классов начала гарантироваться, но мы должны дополнить приведенный ниже предикат classAreTheSame, чтобы также гарантировать эквивалентность их методов. Рассмотрим модель ниже:

abstract sig Id {}

sig ClassId, MethodId,FieldId extends Id {}

one sig public, private_, protected extends Accessibility {}

abstract sig Type {}

abstract sig PrimitiveType extends Type {}

one sig Long_, Int_ extends PrimitiveType {}

sig Class extends Type {
    id: one ClassId,
    extend: lone Class,
    methods: set Method,
    fields: set Field,
}

sig Method {
    id : one MethodId,
    param: lone Type,
    return: one Type,
    acc: lone Accessibility,
    b: one Block
}

sig Block {
    statements: one SequentialComposition
}

sig SequentialComposition {
    first: one StatementExpression,
    rest: lone SequentialComposition
}

abstract sig Expression {}
abstract sig StatementExpression extends Expression {}

sig MethodInvocation extends StatementExpression{
    pExp: lone PrimaryExpression, 
    id_methodInvoked: one Method
}

sig AssignmentExpression extends StatementExpression {
    pExpressionLeft: one FieldAccess,
    pExpressionRight: one {Expression - newCreator - VoidMethodInvocation - PrimaryExpression - AssignmentExpression }
}

abstract sig PrimaryExpression extends Expression {}

sig this_, super_ extends PrimaryExpression {}

sig newCreator extends PrimaryExpression {
    id_cf : one Class
}

sig FieldAccess {
    pExp: one PrimaryExpression,
    id_fieldInvoked: one Field
}

sig Left,Right extends Class{}

one sig ARight, BRight, CRight extends Right{}

one sig ALeft, BLeft, CLeft extends Left{}

pred law6RightToLeft[] {
    twoClassesDeclarationInHierarchy[]
}

pred twoClassesDeclarationInHierarchy[] {
     no disj x,y:Right | x.id=y.id
     Right.*extend & Left.*extend=none
     one r: Right | r.extend= none
     one l:Left| l.extend=none

     ARight.extend=none
     ALeft.extend=none
     BRight in CRight.extend
     BLeft in CLeft.extend
     ARight in BRight.extend
     ALeft in BLeft.extend
     #(extend.BRight) > 2
     #(extend.BLeft) > 2
     #(extend.ARight) = 1
     #(extend.ALeft) = 1
     CLeft.id=CRight.id

     all m:Method | m in ((*extend).ALeft).methods => m !in ((*extend).ARight).methods
     all m:Method | m in ((*extend).ARight).methods => m !in ((*extend).ALeft).methods
     some Method
     all r:Right | all l:Left|  (r.extend= none and l.extend=none) implies classesAreTheSameAndSoAreTheirCorrespondingSons[r,l]
}

pred classesAreTheSameAndSoAreTheirCorrespondingSons[right,left: Class]{
    classesAreTheSame[right,left]
    all r: right.^~extend | one l :left.^~extend | classesAreTheSame[r,l] and classesAreTheSame[r.extend ,l.extend]
    all l:left.^~extend | one r :right.^~extend | classesAreTheSame[r,l] and  classesAreTheSame[r.extend ,l.extend]
}

pred classesAreTheSame[r,l: Class]{
    r.id=l.id
    r.fields=l.fields

    #r.methods = #l.methods
    all mr: r.methods | one ml: l.methods | mr.id = ml.id && mr.b != ml.b 
    all mr: l.methods | one ml: r.methods | mr.id = ml.id && mr.b != ml.b 

    all r1: r.methods, r2: l.methods | r1.id = r2.id => 
        equalsSeqComposition[r1.b.statements, r2.b.statements]
}

pred equalsSeqComposition[sc1, sc2: SequentialComposition]{
    equalsStatementExpression[sc1.first, sc2.first] 
    //#sc1.(*rest) = #sc2.(*rest)
}

pred equalsStatementExpression [s1, s2: StatementExpression]{
    s1 in AssignmentExpression => (s2 in AssignmentExpression && equalsAssignment[s1, s2])
    s1 in MethodInvocation => (s2 in MethodInvocation && equalsMethodInvocation[s1, s2])
    s1 in VoidMethodInvocation => (s2 in VoidMethodInvocation && equalsVoidMethodInvocation[s1, s2])
}

pred equalsAssignment [ae, ae2:AssignmentExpression]{
    equalsPrimaryExpression[(ae.pExpressionLeft).pExp, (ae2.pExpressionLeft).pExp]
    equalsPExpressionRight[ae.pExpressionRight, ae2.pExpressionRight]
}

pred equalsPrimaryExpression[p1, p2:PrimaryExpression]{
    p1 in newCreator  => p2 in newCreator && equalsClassesId [p1.id_cf, p2.id_cf]
    p1 in this_ => p2 in this_ 
    p1 in super_ => p2 in super_
}

pred equalsPExpressionRight[e1, e2:Expression]{
    e1 in LiteralValue => e2 in LiteralValue 
    e1 in MethodInvocation => e2 in MethodInvocation && equalsMethodInvocation[e1, e2]
}

pred equalsMethodInvocation[m1, m2:MethodInvocation]{
    equalsPrimaryExpression[m1.pExp, m2.pExp]
    m1.id_methodInvoked.id = m2.id_methodInvoked.id 
    m1.param = m2.param
}

pred equalsVoidMethodInvocation[m1, m2:VoidMethodInvocation]{
    equalsPrimaryExpression[m1.pExp, m2.pExp]
    m1.id_voidMethodInvoked.id = m2.id_voidMethodInvoked.id
    m1.param = m2.param
}

run law6RightToLeft for 10 but 17 Id, 17 Type, 17 Class

Моя идея состояла в том, чтобы идентифицировать соответствующие методы (leftClassMethod() и rightClassMethod()) через их идентификаторы (которые гарантированно будут одинаковыми в соответствии с моделью). Однако предикат equalsSeqComposition не работает, и ситуация ухудшается, когда я пытаюсь включить в сравнение оставшееся отношение сигнатуры SequentialComposition (прокомментировано выше в предикате equalsSeqComposition). Это последнее сравнение еще более сложно, так как Alloy не допускает рекурсии и те же уровни наследования, что и упорядочение, теряются при использовании транзитивного замыкания. Любая идея, как я могу представить это в Alloy?


person Tarciana    schedule 09.08.2015    source источник


Ответы (1)


В Alloy можно вызывать функции и рекурсивно предикатировать, только если глубина рекурсии не превышает 3 см.: Программирование рекурсивных функций в сплаве

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

Я бы переписал ваш предикат classesAreTheSameAndSoAreTheirCorrespondingSons следующим образом:

pred classesAreTheSameAndSoAreTheirCorrespondingSons[right,left: Class]{
    classesAreTheSame[right,left]
    all r: right.^~extend | one l :left.^~extend | classesAreTheSame[r,l] and classesAreTheSame[r.extend ,l.extend]
    all l:left.^~extend | one r :right.^~extend | classesAreTheSame[r,l] and  classesAreTheSame[r.extend ,l.extend]
}

Этот предикат обеспечивает, чтобы два заданных класса right и left были одинаковыми, и что любые классы r/l, наследующие (прямо или косвенно) right/left, имеют одну противоположную часть в классах l/r, наследующих (прямо или косвенно) left/right , соответственно.

Функция checkclassesAreTheSame[r.extend ,l.extend] предназначена для проверки того, что r и l находятся на одном и том же уровне наследования, поскольку упорядочение теряется при использовании транзитивного замыкания.

Вот небольшая модель, которую я разработал, чтобы решить вашу проблему:

abstract sig Class {
    id: Int,
    extend: lone Class
}{
   this not in this.^@extend
}

sig Left,Right extends Class{}

fact{
     no disj x,y:Right | x.id=y.id
     Right.*extend & Left.*extend=none 
     one r: Right | r.extend= none 
     one l:Left| l.extend=none
     all r:Right | all l:Left|  (r.extend= none and l.extend=none) implies classesAreTheSameAndSoAreTheirCorrespondingSons[r,l]    
}

pred classesAreTheSameAndSoAreTheirCorrespondingSons[right,left: Class]{
    classesAreTheSame[right,left]
    all r: right.^~extend | one l :left.^~extend | classesAreTheSame[r,l] and classesAreTheSame[r.extend ,l.extend]
    all l:left.^~extend | one r :right.^~extend | classesAreTheSame[r,l] and  classesAreTheSame[r.extend ,l.extend]
}

pred classesAreTheSame[r,l: Class]{
    r.id=l.id
}
run {} for exactly 10 Class

Удачи в остальном ;)

person Loïc Gammaitoni    schedule 11.08.2015
comment
Привет, Лоик, во-первых, я хотел бы поблагодарить вас за ваш ответ и внимание. Тем не менее, я уже думал о решении, подобном вашему, но оно не сработало в моем случае, потому что Alloy Analyzer считал в каком-то конкретном сгенерированном экземпляре r правым.~extend.~extend, а l - левым.~extend; это означает, что экземпляр Alloy сравнивал r и l на разных родительских уровнях, что, по-видимому, возможно, когда мы пишем код выше. Таким образом, не могли бы вы объяснить мне, как ваш код избежит аналогичного решения Alloy? Заранее спасибо, - person Tarciana; 17.08.2015
comment
Как сказано в моем ответе, выполнение проверки classAreTheSame[r.extend,l.extend] в дополнение к проверке classAreTheSame[r,l] обеспечивает равенство на двух родительских уровнях для каждого класса, что предотвращает ситуацию, о которой вы упоминаете - person Loïc Gammaitoni; 18.08.2015
comment
Привет, Лоик, еще раз спасибо. Но у меня все еще есть несколько вопросов: а) в чем разница между this.^@extend и this.^extend в подписи класса? б) почему не нужно также ставить no disj x,y: Left | x.id = y.id в самом деле? c) Есть ли разница между right.^~extend и (^extend).right? Заранее спасибо за внимание. - person Tarciana; 05.09.2015
comment
а) в факте подписи ссылки на поля не обрабатываются, как в других частях модели, поскольку они ссылаются на значение поля для данного экземпляра подписи, к которому присоединен факт. Если вы хотите сослаться на поле так, как будто вы находитесь за пределами факта подписи, вы экстериоризируете ссылку, используя операнд @. - person Loïc Gammaitoni; 07.09.2015
comment
б) факт относится к classAreTheSame, так что он вызывает отношение 1 к 1 между классами левых и классами правых. Поскольку classAreTheSame гарантируют, что идентификатор правильного класса совпадает с идеей его аналога, тогда достаточно ограничить только идентификаторы правильного класса. В моем небольшом примере без добавления disj x,y: Left | x.id = y.id, таким образом, не нужен, но его добавление не вызовет проблем - person Loïc Gammaitoni; 07.09.2015
comment
в) Существует, поскольку оператор точечного соединения не является коммутативным. - person Loïc Gammaitoni; 07.09.2015
comment
Привет Лоик, спасибо за ответы. Как вы можете видеть выше, я изменил свой первоначальный вопрос, включив в него еще один вопрос, касающийся сравнения блоков методов (один для правого и другой для левого класса). Я также добавил некоторые подписи (которые будут использоваться в этом сравнении) к описанной исходной метамодели. Не могли бы вы помочь мне снова? - person Tarciana; 17.09.2015