// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
include "../../libraries/src/Wrappers.dfy"
include "../../libraries/src/Collections/Sequences/Seq.dfy"
module Actions {
import opened Wrappers
import opened Seq
/*
* A trait that can execute arbitrary actions on input type A
* and return results of type R.
*/
trait {:termination false} Action
{
/*
* Contains the implementation of the given action
*/
method Invoke(a: A)
returns (r: R)
ensures Ensures(a, r)
/*
* Contains the assertions that should be true upon return of the Invoke method
*/
predicate Ensures(a: A, r: R)
}
/*
* A trait that can execute actions which can fail. Is invoked on inputs
* of type A, and returns Result types which contain type R on success
* or type E on failure.
*/
trait {:termination false} ActionWithResult
extends Action>
{
method Invoke(a: A)
returns (res: Result)
ensures Ensures(a, res)
}
/*
* Returns a sequence with elements of type R which is built by executing the input action
* to all items in the input sequence.
*/
method Map(
action: Action,
s: seq
)
returns (res: seq)
ensures |s| == |res|
ensures
forall i ::
&& 0 <= i < |s|
==>
action.Ensures(s[i], res[i])
{
var rs := [];
for i := 0 to |s|
invariant |s[..i]| == |rs|
invariant forall j ::
&& 0 <= j < i
==>
action.Ensures(s[j], rs[j])
{
var r := action.Invoke(s[i]);
rs := rs + [r];
}
return rs;
}
/*
* A specialized version of the Map method which allows actions that can fail.
*/
// TODO: Change R(0) -> R once https://github.com/dafny-lang/dafny/issues/1553 resolved
method MapWithResult(
action: ActionWithResult,
s: seq
)
returns (res: Result, E>)
ensures
res.Success?
==>
|s| == |res.value|
ensures
res.Success?
==>
(forall i ::
&& 0 <= i < |s|
==>
action.Ensures(s[i], Success(res.value[i])))
{
var rs := [];
for i := 0 to |s|
invariant |s[..i]| == |rs|
invariant forall j ::
&& 0 <= j < i
==>
action.Ensures(s[j], Success(rs[j]))
{
var r :- action.Invoke(s[i]);
rs := rs + [r];
}
return Success(rs);
}
/*
* A specialized version of the Map method whose action always returns sequences, which
* are flattened into a single final result. This flattening only happens once, rather
* than recursively; that is, if the action returns a sequence of sequences, the return
* of this method will also be a sequence of sequences.
*/
method FlatMap(
action: Action>,
s: seq
)
returns (res: seq)
ensures
forall i :: i in s
==>
&& exists fm :: action.Ensures(i, fm)
&& forall k | k in fm :: k in res
{
ghost var parts := [];
var rs := [];
for i := 0 to |s|
invariant |s[..i]| == |parts|
invariant forall j ::
&& 0 <= j < i
==>
&& action.Ensures(s[j], parts[j])
&& forall b | b in parts[j] :: b in rs
{
var r := action.Invoke(s[i]);
rs := rs + r;
parts := parts + [r];
}
return rs;
}
/*
* A specialized version of the FlatMap method whose action may fail.
*
* Also returns a ghost variable containing the unflattened version of
* the action's return sequences, which may be useful in helping callers
* prove things.
*/
method FlatMapWithResult(
action: ActionWithResult, E>,
s: seq
)
returns (res: Result, E>, ghost parts: seq>)
ensures
res.Success?
==>
&& |s| == |parts|
&& res.value == Flatten(parts)
&& (forall i :: 0 <= i < |s|
==>
&& action.Ensures(s[i], Success(parts[i]))
&& multiset(parts[i]) <= multiset(res.value)
)
{
parts := [];
var rs := [];
for i := 0 to |s|
invariant |s[..i]| == |parts|
invariant forall j ::
&& 0 <= j < i
==>
&& action.Ensures(s[j], Success(parts[j]))
&& multiset(parts[j]) <= multiset(rs)
invariant Flatten(parts) == rs
{
var r :- action.Invoke(s[i]);
rs := rs + r;
LemmaFlattenConcat(parts, [r]);
parts := parts + [r];
}
return Success(rs), parts;
}
/*
* Given an input action (which must return a boolean) and an input sequence,
* returns a sequence containing only those items from the input sequence which
* return true when the action is invoked on them.
*/
method Filter(
action: Action,
s: seq
)
returns (res: seq)
ensures |s| >= |res|
ensures
forall j ::
j in res
==>
&& j in s
&& action.Ensures(j, true)
{
var rs := [];
for i := 0 to |s|
invariant |s[..i]| >= |rs|
invariant forall j ::
j in rs
==>
&& j in s
&& action.Ensures(j, true)
{
var r := action.Invoke(s[i]);
if r {
rs := rs + [s[i]];
}
}
return rs;
}
/*
* Specialized version of Filter whose input action may fail.
*/
method FilterWithResult(
action: ActionWithResult,
s: seq
)
returns (res: Result, E>)
ensures
res.Success?
==>
&& |s| >= |res.value|
&& forall j ::
j in res.value
==>
&& j in s
&& action.Ensures(j, Success(true))
{
var rs := [];
for i := 0 to |s|
invariant |s[..i]| >= |rs|
invariant forall j ::
j in rs
==>
&& j in s
&& action.Ensures(j, Success(true))
{
var r :- action.Invoke(s[i]);
if r {
rs := rs + [s[i]];
}
}
return Success(rs);
}
/*
* Given an input action which may fail and an input sequence, executes the
* given action on each element of the sequence until either one succeeds or
* all have failed. If one succeeds, this method returns immediately with
* the successful attempt's result. If all fail, this method returns a single
* failure which aggregates all failures.
*/
method ReduceToSuccess(
action: ActionWithResult,
s: seq
)
returns (res: Result>)
ensures
res.Success?
==>
exists a | a in s :: action.Ensures(a, Success(res.value))
{
var errors := [];
for i := 0 to |s| {
var attempt := action.Invoke(s[i]);
if attempt.Success? {
return Success(attempt.value);
} else {
errors := errors + [attempt.error];
}
}
return Failure(errors);
}
}