diff --git a/src/kernel_services/abstract_interp/lattice_bounds.ml b/src/kernel_services/abstract_interp/lattice_bounds.ml index c02dcd7ae8095bbcae9769b626d00f451ff70e3e..5fcb70859e1daaf17600794fab0dc7eb92d2f16c 100644 --- a/src/kernel_services/abstract_interp/lattice_bounds.ml +++ b/src/kernel_services/abstract_interp/lattice_bounds.ml @@ -20,14 +20,19 @@ (* *) (**************************************************************************) + + +(** Types definitions *) + type 'a or_bottom = [ `Value of 'a | `Bottom ] type 'a or_top = [ `Value of 'a | `Top ] type 'a or_top_bottom = [ `Value of 'a | `Bottom | `Top ] + + (** Common functions *) -module Common = -struct +module Common = struct (* Pretty-printing *) @@ -82,19 +87,22 @@ struct | `Value vx, `Value vy -> is_included vx vy (* Iterator *) + let iter f = function | `Bottom | `Top -> () | `Value v -> f v (* Conversion *) + let to_option = function | `Bottom | `Top -> None | `Value x -> Some x end + + module Bottom = struct - type 'a t = 'a or_bottom include Common @@ -110,41 +118,38 @@ module Bottom = struct (* Lattice operators *) - let join join x y = match x, y with - | `Value vx, `Value vy -> `Value (join vx vy) + let join join x y = + match x, y with + | `Value vx, `Value vy -> `Value (join vx vy) | `Bottom, (`Value _ as v) | (`Value _ as v), `Bottom | (`Bottom as v), `Bottom -> v - let join_list j l = List.fold_left (join j) `Bottom l - - let narrow narrow x y = match x, y with - | `Value vx, `Value vy -> narrow vx vy + let narrow narrow x y = + match x, y with + | `Value vx, `Value vy -> narrow vx vy | `Bottom, `Value _ | `Value _, `Bottom | `Bottom, `Bottom -> `Bottom - (* Iterators *) - - let bind f = function - | `Bottom -> `Bottom - | `Value v -> f v - - let fold ~bottom f = function - | `Bottom -> bottom - | `Value v -> f v - - let map f = function - | `Bottom -> `Bottom - | `Value v -> `Value (f v) + let join_list f = List.fold_left (join f) `Bottom (* Combination *) let zip x y = match x, y with - | `Bottom, #t | #t, `Bottom -> `Bottom + | `Bottom, _ | _, `Bottom -> `Bottom | `Value x, `Value y -> `Value (x,y) + (* Monadic operations *) + + include Monad.Make_based_on_bind_with_product (struct + type 'a t = 'a or_bottom + let return x = `Value x + let bind f = function `Bottom -> `Bottom | `Value x -> f x + let product l r = zip l r + end) + (** Conversion *) let of_option = function @@ -155,41 +160,19 @@ module Bottom = struct | `Bottom -> [] | `Value v -> [v] - let bot_of_list = function - | [] -> `Bottom - | l -> `Value l - - let list_of_bot = function - | `Bottom -> [] - | `Value l -> l - - let add_to_list elt list = match elt with + let add_to_list elt list = + match elt with | `Bottom -> list | `Value elt -> elt :: list let list_values l = List.fold_left (fun l elt -> add_to_list elt l) [] l - (** Operators *) - - module Operators = - struct - let (>>-) x f = bind f x - let (>>-:) x f = map f x - let (let+) x f = map f x - let (and+) = zip - let (let*) x f = bind f x - let (and*) = zip - end - (** Datatype construction *) + let counter = ref 0 - module Make_Datatype - (Domain: Datatype.S) - = - Datatype.Make ( - struct + module Make_Datatype (Domain: Datatype.S) = Datatype.Make (struct include Datatype.Serializable_undefined type t = Domain.t or_bottom let () = incr counter @@ -207,21 +190,18 @@ module Bottom = struct (* Bound lattice *) - module Bound_Lattice - (Lattice: Lattice_type.Join_Semi_Lattice) - = struct + module Bound_Lattice (Lattice: Lattice_type.Join_Semi_Lattice) = struct include Make_Datatype (Lattice) - let bottom = `Bottom let join = join Lattice.join let is_included = is_included Lattice.is_included end + end -module Top = -struct - type 'a t = 'a or_top + +module Top = struct include Common @@ -235,6 +215,12 @@ struct | `Value v -> v | `Top -> top + (** Conversion. *) + + let of_option = function + | None -> `Top + | Some x -> `Value x + (** Lattice *) let join join_value x y = @@ -247,52 +233,88 @@ struct | `Top, v | v, `Top -> v | `Value vx, `Value vy -> `Value (narrow_value vx vy) - (* Iterators *) + (** Combination *) - let bind f = function - | `Top -> `Top - | `Value v -> f v + let zip x y = + match x, y with + | `Top, _ | _, `Top -> `Top + | `Value x, `Value y -> `Value (x,y) - let fold ~top f = function - | `Top -> top - | `Value v -> f v + (** Monadic operators *) - let map f = function - | `Top -> `Top - | `Value v -> `Value (f v) + include Monad.Make_based_on_bind_with_product(struct + type 'a t = 'a or_top + let return x = `Value x + let bind f = function `Top -> `Top | `Value x -> f x + let product l r = zip l r + end) + + (** Datatype construction *) + + let counter = ref 0 + + module Make_Datatype (Domain: Datatype.S) = Datatype.Make (struct + include Datatype.Serializable_undefined + type t = Domain.t or_top + let () = incr counter + let name = Domain.name ^ "+top(" ^ string_of_int !counter ^ ")" + let reprs = `Top :: (List.map (fun v -> `Value v) Domain.reprs) + let structural_descr = Structural_descr.t_unknown + let hash = Common.hash Domain.hash + let equal = (Common.equal Domain.equal :> t -> t -> bool) + let compare = Common.compare Domain.compare + let rehash = Datatype.identity + let copy = map Domain.copy + let pretty = Common.pretty Domain.pretty + let mem_project = Datatype.never_any_project + end) + +end + + + +module TopBottom = struct + + type 'a t = 'a or_top_bottom + include Common (** Combination *) let zip x y = match x, y with + | `Bottom, #t | #t, `Bottom -> `Bottom | `Top, #t | #t, `Top -> `Top | `Value x, `Value y -> `Value (x,y) - (** Conversion. *) + (** Monadic operators. We have to redefines every operators to ensure + subtyping properties. *) - let of_option = function - | None -> `Top - | Some x -> `Value x + let return x = `Value x + let product l r = zip l r - (** Operators *) + let bind f = function + | `Bottom -> `Bottom + | `Top -> `Top + | `Value x -> f x - module Operators = - struct - let (>>-) x f = bind f x - let (>>-:) x f = map f x - let (let+) x f = map f x - let (and+) = zip - let (let*) x f = bind f x - let (and*) = zip + let map f = function + | `Bottom -> `Bottom + | `Top -> `Top + | `Value x -> `Value (f x) + + let flatten = function + | `Bottom | `Value `Bottom -> `Bottom + | `Top | `Value `Top -> `Top + | `Value `Value x -> `Value x + + module Operators = struct + let ( >>- ) (m : [< 'a t]) (f : 'a -> ([> 'b t] as 'c)) : 'c = bind f m + let ( let* ) (m : [< 'a t]) (f : 'a -> ([> 'b t] as 'c)) : 'c = bind f m + let ( and* ) (l : [< 'a t]) (r : [< 'b t]) : [> ('a * 'b) t] = product l r + let ( >>-: ) (m : [< 'a t]) (f : 'a -> 'b) : [> 'b t] = map f m + let ( let+ ) (m : [< 'a t]) (f : 'a -> 'b) : [> 'b t] = map f m + let ( and+ ) (l : [< 'a t]) (r : [< 'b t]) : [> ('a * 'b) t] = product l r end -end - - -module TopBottom = -struct - type 'a t = 'a or_top_bottom - - include Common (* Lattice operators *) @@ -306,40 +328,24 @@ struct | `Bottom, _ | _, `Bottom -> `Bottom | `Value vx, `Value vy -> (narrow_value vx vy :> 'a t) - (* Iterators *) - - let bind f = function - | `Top -> `Top - | `Bottom -> `Bottom - | `Value v -> f v - - let fold ~top ~bottom f = function - | `Top -> top - | `Bottom -> bottom - | `Value v -> f v - - let map f = function - | `Top -> `Top - | `Bottom -> `Bottom - | `Value v -> `Value (f v) - - (** Combination *) + (** Datatype construction *) - let zip x y = - match x, y with - | `Bottom, #t | #t, `Bottom -> `Bottom - | `Top, #t | #t, `Top -> `Top - | `Value x, `Value y -> `Value (x,y) + let counter = ref 0 - (** Operators *) + module Make_Datatype (Domain: Datatype.S) = Datatype.Make (struct + include Datatype.Serializable_undefined + type t = Domain.t or_top_bottom + let () = incr counter + let name = Domain.name ^ "+top_bottom(" ^ string_of_int !counter ^ ")" + let reprs = `Bottom :: `Top :: (List.map (fun v -> `Value v) Domain.reprs) + let structural_descr = Structural_descr.t_unknown + let hash = Common.hash Domain.hash + let equal = (Common.equal Domain.equal :> t -> t -> bool) + let compare = Common.compare Domain.compare + let rehash = Datatype.identity + let copy = map Domain.copy + let pretty = Common.pretty Domain.pretty + let mem_project = Datatype.never_any_project + end) - module Operators = - struct - let (>>-) x f = bind f x - let (>>-:) x f = map f x - let (let+) x f = map f x - let (and+) = zip - let (let*) x f = bind f x - let (and*) = zip - end end diff --git a/src/kernel_services/abstract_interp/lattice_bounds.mli b/src/kernel_services/abstract_interp/lattice_bounds.mli index bfee4d2752dfc13bdeecce6e7c849fd13f9e5867..1e01e1139886b1c49599408106e491191891699b 100644 --- a/src/kernel_services/abstract_interp/lattice_bounds.mli +++ b/src/kernel_services/abstract_interp/lattice_bounds.mli @@ -29,163 +29,123 @@ type 'a or_top_bottom = [ `Value of 'a | `Bottom | `Top ] module Bottom : sig - type 'a t = 'a or_bottom - (** Operators *) - module Operators : sig - (** This monad propagates `Bottom and or `Top if needed. *) - val (>>-) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c - - (** Use this monad if the following function returns a simple value. *) - val (>>-:) : [< 'a t] -> ('a -> 'b) -> [> 'b t] - - (* Binding operators, applicative syntax *) - val (let+) : [< 'a t] -> ('a -> 'b) -> [> 'b t] - val (and+) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] - - (* Binding operators, monad syntax *) - val (let*) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c - val (and*) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] - end + include Monad.S_with_product with type 'a t = 'a or_bottom (** Datatype constructor *) - module Make_Datatype - (Domain: Datatype.S) - : Datatype.S with type t = Domain.t or_bottom + module Make_Datatype (Domain: Datatype.S) : + Datatype.S with type t = Domain.t or_bottom (** Bounds a semi-lattice *) - module Bound_Lattice - (Lattice: Lattice_type.Join_Semi_Lattice) - : Lattice_type.Bounded_Join_Semi_Lattice with type t = Lattice.t or_bottom + module Bound_Lattice (Lattice: Lattice_type.Join_Semi_Lattice) : + Lattice_type.Bounded_Join_Semi_Lattice with type t = Lattice.t or_bottom (** Access *) - val is_bottom: 'a t -> bool - val non_bottom: 'a t -> 'a - val value: bottom: 'a -> 'a t -> 'a + val is_bottom : 'a t -> bool + val non_bottom : 'a t -> 'a + val value : bottom:'a -> 'a t -> 'a (** Datatype *) - val hash: ('a -> int) -> 'a t -> int - val equal: ('a -> 'a -> bool) -> 'a t -> 'a t -> bool - val compare: ('a -> 'a -> int) -> 'a t -> 'a t -> int + val hash : ('a -> int) -> 'a t -> int + val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int (** Pretty-printing *) - val pretty_bottom :Format.formatter -> unit (* for %t specifier *) - val pretty : - (Format.formatter -> 'a -> unit) -> - Format.formatter -> 'a t -> unit + val pretty_bottom : Format.formatter -> unit (* for %t specifier *) + val pretty : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit (* Lattice operators *) - val is_included: ('a -> 'b -> bool) -> 'a t -> 'b t -> bool - val join: ('a -> 'a -> 'a) -> 'a t -> 'a t -> 'a t - val join_list: ('a -> 'a -> 'a) -> 'a t list -> 'a t - val narrow: ('a -> 'a -> 'a t) -> 'a t -> 'a t -> 'a t + val is_included : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool + val join : ('a -> 'a -> 'a) -> 'a t -> 'a t -> 'a t + val narrow : ('a -> 'a -> 'a t) -> 'a t -> 'a t -> 'a t + val join_list : ('a -> 'a -> 'a) -> 'a t list -> 'a t (* Iterators *) - val iter: ('a -> unit) -> 'a t -> unit - val bind: ('a -> 'b t) -> 'a t -> 'b t - val fold: bottom:'b -> ('a -> 'b) -> 'a t -> 'b - val map: ('a -> 'b) -> 'a t -> 'b t - - (* Combination *) - val zip: 'a t -> 'b t -> ('a * 'b) t (* `Bottom if any is `Bottom *) + val iter : ('a -> unit) -> 'a t -> unit (** In a lattice where the elements are lists of non-bottom values, the empty list is the bottom case. *) (** Conversion *) - val to_option: 'a t -> 'a option - val of_option: 'a option -> 'a t - val to_list: 'a t -> 'a list - val bot_of_list: 'a list -> 'a list t - val list_of_bot: 'a list t -> 'a list - val list_values: 'a t list -> 'a list + val to_option : 'a t -> 'a option + val of_option : 'a option -> 'a t + val to_list : 'a t -> 'a list + val list_values : 'a t list -> 'a list (** [elt >:: list] adds [elt] to the [list] if it is not bottom. *) - val add_to_list: 'a t -> 'a list -> 'a list + val add_to_list : 'a t -> 'a list -> 'a list + end module Top : sig - type 'a t = 'a or_top - (** Operators *) - module Operators : sig - (** This monad propagates `Bottom and or `Top if needed. *) - val (>>-) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c + include Monad.S_with_product with type 'a t = 'a or_top - (** Use this monad if the following function returns a simple value. *) - val (>>-:) : [< 'a t] -> ('a -> 'b) -> [> 'b t] - - (* Binding operators, applicative syntax *) - val (let+) : [< 'a t] -> ('a -> 'b) -> [> 'b t] - val (and+) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] - - (* Binding operators, monad syntax *) - val (let*) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c - val (and*) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] - end + (** Datatype constructor *) + module Make_Datatype (Domain: Datatype.S) : + Datatype.S with type t = Domain.t or_top (** Access *) - val is_top: 'a t -> bool - val non_top: 'a t -> 'a - val value: top: 'a -> 'a t -> 'a + val is_top : 'a t -> bool + val non_top : 'a t -> 'a + val value : top:'a -> 'a t -> 'a (** Datatype *) - val hash: ('a -> int) -> 'a t -> int - val equal: ('a -> 'a -> bool) -> 'a t -> 'a t -> bool - val compare: ('a -> 'a -> int) -> 'a t -> 'a t -> int + val hash : ('a -> int) -> 'a t -> int + val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int (** Pretty-printing *) - val pretty_top :Format.formatter -> unit (* for %t specifier *) + val pretty_top : Format.formatter -> unit (* for %t specifier *) val pretty : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit (** Lattice operators *) - val join: ('a -> 'a -> 'a t) -> 'a t -> 'a t -> 'a t - val narrow: ('a -> 'a -> 'a) -> 'a t -> 'a t -> 'a t + val is_included : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool + val join : ('a -> 'a -> 'a t) -> 'a t -> 'a t -> 'a t + val narrow : ('a -> 'a -> 'a) -> 'a t -> 'a t -> 'a t (* Iterators *) - val iter: ('a -> unit) -> 'a t -> unit - val bind: ('a -> 'b t) -> 'a t -> 'b t - val fold: top:'b -> ('a -> 'b) -> 'a t -> 'b - val map: ('a -> 'b) -> 'a t -> 'b t - - (** Combination *) - val zip: 'a t -> 'b t -> ('a * 'b) t (* `Top if any is `Top *) + val iter : ('a -> unit) -> 'a t -> unit (** Conversion *) val to_option : 'a t -> 'a option val of_option : 'a option -> 'a t + end module TopBottom: sig - type 'a t = 'a or_top_bottom - (** Operators *) - (* In presence of simultaneous `Bottom and `Top in and+ / and*, everything - narrows down to `Bottom *) - module Operators : sig - (** This monad propagates `Bottom and or `Top if needed. *) - val (>>-) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c + include Monad.S_with_product with type 'a t = 'a or_top_bottom - (** Use this monad if the following function returns a simple value. *) - val (>>-:) : [< 'a t] -> ('a -> 'b) -> [> 'b t] + (** Datatype constructor *) + module Make_Datatype (Domain: Datatype.S) : + Datatype.S with type t = Domain.t or_top_bottom + + (** Operators. In presence of simultaneous `Bottom and `Top in and+ / and*, + everything narrows down to `Bottom. Every operators is redefined to + ensure subtyping properties. *) + module Operators : sig (* Binding operators, applicative syntax *) + val (>>-:) : [< 'a t] -> ('a -> 'b) -> [> 'b t] val (let+) : [< 'a t] -> ('a -> 'b) -> [> 'b t] val (and+) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] (* Binding operators, monad syntax *) + val (>>- ) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c val (let*) : [< 'a t] -> ('a -> ([> 'b t] as 'c)) -> 'c val (and*) : [< 'a t] -> [< 'b t] -> [> ('a * 'b) t] + end (** Datatype *) - val hash: ('a -> int) -> 'a t -> int - val equal: ('a -> 'a -> bool) -> 'a t -> 'a t -> bool - val compare: ('a -> 'a -> int) -> 'a t -> 'a t -> int + val hash : ('a -> int) -> 'a t -> int + val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int (** Pretty-printing *) val pretty : @@ -193,12 +153,8 @@ module TopBottom: sig Format.formatter -> 'a t -> unit (* Lattice operators *) - val join: ('a -> 'a -> [< 'a t]) -> 'a t -> 'a t -> 'a t - val narrow: ('a -> 'a -> [< 'a t]) -> 'a t -> 'a t -> 'a t + val is_included : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool + val join : ('a -> 'a -> [< 'a t]) -> 'a t -> 'a t -> 'a t + val narrow : ('a -> 'a -> [< 'a t]) -> 'a t -> 'a t -> 'a t - (* Iterators *) - val iter: ('a -> unit) -> 'a t -> unit - val bind: ('a -> 'b t) -> 'a t -> 'b t - val fold: top:'b -> bottom:'b -> ('a -> 'b) -> 'a t -> 'b - val map: ('a -> 'b) -> 'a t -> 'b t end diff --git a/src/libraries/monads/composition.ml b/src/libraries/monads/composition.ml new file mode 100644 index 0000000000000000000000000000000000000000..522d0b676f77b647cf2576c32b5355de198d2c3b --- /dev/null +++ b/src/libraries/monads/composition.ml @@ -0,0 +1,52 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +module type Axiom = sig + type 'a interior and 'a exterior + val swap : 'a exterior interior -> 'a interior exterior +end + +(* Using [Based_on_map] here, as the natural way to write the [bind] is through + [map] and [flatten]. *) +module Make + (Int : Monad.S) + (Ext : Monad.S) + (X : Axiom with type 'a interior = 'a Int.t and type 'a exterior = 'a Ext.t) + = Monad.Make_based_on_map (struct + type 'a t = 'a Int.t Ext.t + let return x = Ext.return (Int.return x) + let map f m = Ext.map (Int.map f) m + let flatten m = Ext.map X.swap m |> Ext.flatten |> Ext.map Int.flatten + end) + +(* As for the previous functor and for the exact same reason, we use + [Based_on_map_with_product]. *) +module Make_with_product + (Int : Monad.S_with_product) (Ext : Monad.S_with_product) + (X : Axiom with type 'a interior = 'a Int.t and type 'a exterior = 'a Ext.t) + = Monad.Make_based_on_map_with_product (struct + type 'a t = 'a Int.t Ext.t + let return x = Ext.return (Int.return x) + let map f m = Ext.map (Int.map f) m + let flatten m = Ext.map X.swap m |> Ext.flatten |> Ext.map Int.flatten + let product l r = Ext.product l r |> Ext.map (fun (l, r) -> Int.product l r) + end) diff --git a/src/libraries/monads/composition.mli b/src/libraries/monads/composition.mli new file mode 100644 index 0000000000000000000000000000000000000000..e2f9aba8e6e9f7ad328fb303ec75796bccf19a1e --- /dev/null +++ b/src/libraries/monads/composition.mli @@ -0,0 +1,94 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(** This module exposes two functors that, given a monad T called the + "interior monad" and a monad S called the "exterior monad", build + a monad of type ['a T.t S.t]. To be able to do so, one has to provide + a [swap] function that, simply put, swap the exterior monad out of + the interior one. In other word, this function allows fixing + "badly ordered" monads compositions, in the sens that they are + applied in the opposite order as the desired one. + + For example, one may want to combine the State monad and the Option + monad to represent a stateful computation that may fail. To do so, + one can either rewrite all the needed monadic operations, which may + prove difficult, or use the provided functors of this module. Using + the Option monad as the interior and the State monad as the exterior, + one can trivially provide the following swap function: + {[ + let swap (m : 'a State.t Option.t) : 'a Option.t State.t = + match m with + | None -> State.return None + | Some s -> State.map Option.return s + ]} + Note here that trying to reverse the order of the Option and State + monads makes the [swap] function way harder to write. Moreover, the + resulting function does not actually satisfy the required axioms. + + Indeed, all [swap] functions will not result in a valid composed monad. + To produce such a monad, the given [swap] function must verify the + following equations: + 1. ∀t: 'a T.t, [swap (T.map S.return t) ≣ S.return t] + 2. ∀s: 'a S.t, [swap (T.return s) ≣ S.map T.return s] + 3. ∀x: 'a S.t S.t T.t, [swap (T.map S.flatten x) ≣ S.flatten (S.map swap (swap x))] + 4. ∀x: 'a S.t T.t T.t, [swap (T.flatten x) ≣ S.map T.flatten (swap (T.map swap x))] + More details on this at the end of this file. + @since Frama-C+dev *) + +module type Axiom = sig + type 'a interior and 'a exterior + val swap : 'a exterior interior -> 'a interior exterior +end + +module Make + (Interior : Monad.S) + (Exterior : Monad.S) + (_ : Axiom with type 'a interior = 'a Interior.t + and type 'a exterior = 'a Exterior.t) + : Monad.S with type 'a t = 'a Interior.t Exterior.t + +module Make_with_product + (Interior : Monad.S_with_product) + (Exterior : Monad.S_with_product) + (_ : Axiom with type 'a interior = 'a Interior.t + and type 'a exterior = 'a Exterior.t) + : Monad.S_with_product with type 'a t = 'a Interior.t Exterior.t + + +(** {3 Notes} + + Monads composition is a notoriously difficult topic, and no general + approach exists. The one provided in this module is, in theory, + quite restrictive as the [swap] function, also called a distributive + law, has to satisfy the four presented axioms to guarantee that a + valid monad can be built. Roughly speaking, those axioms enforce the + idea that the distributive law must preserve all structures in the + two monads. + + Distributive laws, their application to monads composition and weakenings + of their axioms are a broad topic with profound implications in category + theory. Even if none of this formal knowledge is required to use this + module, one can check the following references to satisfy their curiosity. + + @see Jon Beck paper "Distributive laws" for more details on this topic. + @see Alexandre Goy thesis "On the compositionality of monads via weak + distributive laws" for details on how to relax some of those axioms. *) diff --git a/src/libraries/monads/list.ml b/src/libraries/monads/list.ml new file mode 100644 index 0000000000000000000000000000000000000000..414e994c6299aa7d5ab906eff9c3f8dc6f54c373 --- /dev/null +++ b/src/libraries/monads/list.ml @@ -0,0 +1,33 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +include Stdlib.List + +module Minimal = struct + type 'a t = 'a list + let return x = [ x ] + let map f xs = map f xs + let flatten xs = flatten xs + let product ls rs = combine ls rs +end + +include Monad.Make_based_on_map_with_product (Minimal) diff --git a/src/libraries/monads/list.mli b/src/libraries/monads/list.mli new file mode 100644 index 0000000000000000000000000000000000000000..9403f94d5dd26c435e73bf7346b04e83ea6aa678 --- /dev/null +++ b/src/libraries/monads/list.mli @@ -0,0 +1,28 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(** Extend the [list] type to a full fleshed monad. This monad can be used + to represent non-deterministic computations. + @since Frama-C+dev *) + +include module type of Stdlib.List +include Monad.S_with_product with type 'a t = 'a list diff --git a/src/libraries/monads/monad.ml b/src/libraries/monads/monad.ml new file mode 100644 index 0000000000000000000000000000000000000000..65bf2131e9ca1b48db331c6efd448039bbdf1f5f --- /dev/null +++ b/src/libraries/monads/monad.ml @@ -0,0 +1,158 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + + + +(* Complete signature *) +module type S = sig + type 'a t + val return : 'a -> 'a t + val flatten : 'a t t -> 'a t + val map : ('a -> 'b ) -> 'a t -> 'b t + val bind : ('a -> 'b t) -> 'a t -> 'b t + module Operators : sig + val ( >>- ) : 'a t -> ('a -> 'b t) -> 'b t + val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t + val ( >>-: ) : 'a t -> ('a -> 'b) -> 'b t + val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t + end +end + +(* Complete signature with a product *) +module type S_with_product = sig + type 'a t + val return : 'a -> 'a t + val flatten : 'a t t -> 'a t + val map : ('a -> 'b ) -> 'a t -> 'b t + val bind : ('a -> 'b t) -> 'a t -> 'b t + val product : 'a t -> 'b t -> ('a * 'b) t + module Operators : sig + val ( >>- ) : 'a t -> ('a -> 'b t) -> 'b t + val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t + val ( and* ) : 'a t -> 'b t -> ('a * 'b) t + val ( >>-: ) : 'a t -> ('a -> 'b) -> 'b t + val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t + val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t + end +end + + + +(* Minimal signature based on bind *) +module type Based_on_bind = sig + type 'a t + val return : 'a -> 'a t + val bind : ('a -> 'b t) -> 'a t -> 'b t +end + +(* Minimal signature based on bind with product *) +module type Based_on_bind_with_product = sig + type 'a t + val return : 'a -> 'a t + val bind : ('a -> 'b t) -> 'a t -> 'b t + val product : 'a t -> 'b t -> ('a * 'b) t +end + +(* Minimal definition based on map *) +module type Based_on_map = sig + type 'a t + val return : 'a -> 'a t + val map : ('a -> 'b) -> 'a t -> 'b t + val flatten : 'a t t -> 'a t +end + +(* Minimal signature based on map with product *) +module type Based_on_map_with_product = sig + type 'a t + val return : 'a -> 'a t + val map : ('a -> 'b) -> 'a t -> 'b t + val flatten : 'a t t -> 'a t + val product : 'a t -> 'b t -> ('a * 'b) t +end + + + +(* Extend a based on bind minimal monad *) +module Make_based_on_bind (M : Based_on_bind) = struct + type 'a t = 'a M.t + let return x = M.return x + let bind f m = M.bind f m + let flatten m = bind (fun x -> x) m + let map f m = bind (fun x -> return (f x)) m + module Operators = struct + let ( >>- ) m f = bind f m + let ( let* ) m f = bind f m + let ( >>-: ) m f = map f m + let ( let+ ) m f = map f m + end +end + +(* Extend a based on map minimal monad *) +module Make_based_on_map (M : Based_on_map) = struct + type 'a t = 'a M.t + let return x = M.return x + let map f m = M.map f m + let flatten m = M.flatten m + let bind f m = flatten (map f m) + module Operators = struct + let ( >>- ) m f = bind f m + let ( let* ) m f = bind f m + let ( >>-: ) m f = map f m + let ( let+ ) m f = map f m + end +end + +(* Extend a based on bind monad with a product *) +module Make_based_on_bind_with_product (M : Based_on_bind_with_product) = struct + type 'a t = 'a M.t + let return x = M.return x + let bind f m = M.bind f m + let flatten m = bind (fun x -> x) m + let map f m = bind (fun x -> return (f x)) m + let product l r = M.product l r + module Operators = struct + let ( >>- ) m f = bind f m + let ( let* ) m f = bind f m + let ( let+ ) m f = map f m + let ( >>-: ) m f = map f m + let ( and* ) l r = product l r + let ( and+ ) l r = product l r + end +end + +(** Extend a based on map monad with a product *) +module Make_based_on_map_with_product (M : Based_on_map_with_product) = struct + type 'a t = 'a M.t + let return x = M.return x + let map f m = M.map f m + let flatten m = M.flatten m + let bind f m = flatten (map f m) + let product l r = M.product l r + module Operators = struct + let ( >>- ) m f = bind f m + let ( let* ) m f = bind f m + let ( let+ ) m f = map f m + let ( >>-: ) m f = map f m + let ( and* ) l r = product l r + let ( and+ ) l r = product l r + end +end diff --git a/src/libraries/monads/monad.mli b/src/libraries/monads/monad.mli new file mode 100644 index 0000000000000000000000000000000000000000..9d1ed937b9f5a809dff646b842fd81573a816268 --- /dev/null +++ b/src/libraries/monads/monad.mli @@ -0,0 +1,299 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(** This module provides generic signatures for monads along with tools + to build them based on minimal definitions. Those tools are provided + for advanced users that would like to define their own monads. Any + user that only wants to use the monads provided by the kernel can + completly ignore them. + @since Frama-C+dev *) + + + +(** {2 Monad signature with let-bindings} + + This signature provides all the usual monadic operators along with + let-bindings definitions used to simplify codes relying on monads. + The provided operators are as follows: + - [return] embeds a value [x] in the monad. + - [bind] encodes the idea of "sequence" in the monadic world, i.e the + call [bind f m] comes down to performing the computation [m] before + applying the next computation step, represented by the function [f], + to the resulting value. + - [map] applies a function through the monad. One can examplify it by + making a parallel with the list [map] operator. + - [flatten] is used to handle nested applications of the monad. + + The provided let-bindings operators can be used to write simpler and + cleaner code. For example, one can write [let+ v = compute x in v + 1] + instead of [map (fun v -> v + 1) (compute x)]. The more monadic steps, + the simpler the code will get when written using those operators. + In this module, [>>-] and [let*] always correspond to the [bind] + operator while [>>-:] and [let+] always correspond to the [map]. + All those operators are provided in an [Operators] module to avoid + namespace conflicts. Indeed, one can use the expression + [let open MyMonad.Operators in] to use all the let-bindings without + risking conflicts by including the other definitions, which have + rather common names. This idiom also helps indicate which monad is + currently used in a code. + @since Frama-C+dev *) +module type S = sig + type 'a t + val return : 'a -> 'a t + val flatten : 'a t t -> 'a t + val map : ('a -> 'b ) -> 'a t -> 'b t + val bind : ('a -> 'b t) -> 'a t -> 'b t + module Operators : sig + val ( >>- ) : 'a t -> ('a -> 'b t) -> 'b t + val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t + val ( >>-: ) : 'a t -> ('a -> 'b) -> 'b t + val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t + end +end + + + +(** {2 Monad signature with product} + + In a computational point of view, a monad is an abstraction of a sequence + of operations. But sometimes, one may need to specify that two operations + can be performed in *any* order, for instance when dealing with concurrency + or generic errors handling using the [option] type. To do so, one needs + a *product* on monadic values, i.e a way to combine two monads into a new + one. Thus a second signature is provided, including a product operator + and the two let-bindings [and*] and [and+]. + @since Frama-C+dev *) +module type S_with_product = sig + type 'a t + val return : 'a -> 'a t + val flatten : 'a t t -> 'a t + val map : ('a -> 'b ) -> 'a t -> 'b t + val bind : ('a -> 'b t) -> 'a t -> 'b t + val product : 'a t -> 'b t -> ('a * 'b) t + module Operators : sig + val ( >>- ) : 'a t -> ('a -> 'b t) -> 'b t + val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t + val ( and* ) : 'a t -> 'b t -> ('a * 'b) t + val ( >>-: ) : 'a t -> ('a -> 'b) -> 'b t + val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t + val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t + end +end + + + +(** {2 Building monads from minimal signatures} + + Below are functors for creating monads (with or without product). Both + gives a strictly equivalent implementation but differ from the primitives + given as argument. For an example of monad implementation based on this + module, see {!Option}. *) + + +(** {3 Minimal signature based on bind} + + This is the usual minimal definition of a monadic type constructor, also + called a Kleisli triple. The [return] function embeds a value {m x} in + the monad. The [bind] function, also called the "combinator", corresponds + to the idea of "sequence" in the monadic world, i.e the call [bind f m] + comes down to performing the computation [m] before applying the next + computation step, represented by the function [f], to the resulting value. + + To fully qualify as a monad, these three parts must respect the three + following laws : + + 1. [return] is a left-identity for [bind] : + ∀f:('a -> 'b t), ∀x:'a, [bind f (return x) ≣ return (f x)] + + 2. [return] is a right-identity for [bind] : + ∀m:'a t, [bind return m ≣ m] + + 3. [bind] is associative : + ∀m:'a t, ∀f:('a -> 'b t), ∀g:('b -> 'c t), + [bind (fun x -> bind g (f x)) m ≣ bind g (bind f m)] + + As there is no way in the OCaml type system to enforce those properties, + users have to trust the implemented monad when using it, and developers + have to manually check that they are respected. + @since Frama-C+dev *) +module type Based_on_bind = sig + type 'a t + val return : 'a -> 'a t + val bind : ('a -> 'b t) -> 'a t -> 'b t +end + + +(** {3 Minimal signature based on bind with product} + + This signature simply extends the previous one with a product operator. + @since Frama-C+dev *) +module type Based_on_bind_with_product = sig + type 'a t + val return : 'a -> 'a t + val bind : ('a -> 'b t) -> 'a t -> 'b t + val product : 'a t -> 'b t -> ('a * 'b) t +end + + +(** {3 Minimal definition based on map} + + In a computer science context, this is a rarer definition of a monadic + type constructor. It is however equivalent to the Kleisli triple one, + and may be simpler to use for certain monads, such as the [list] one. + + This approach requires three monadic operations. The first one, as in + the Kleisli triple approach, is a [return] function, allowing to plundge + values into the monad. The second one is a [map], that can be understand + as a parallel of the [map] on list, i.e a way to apply a function through + the monad. Finally, the third one is a [flatten] function, which allows + to handle nested applications of the monad. + + As in the Kleisli triple approach, all those functions must satisfy some + laws to fully qualify as a monad : + + 1. ∀x:'a, ∀f:('a -> 'b), [return (f x) ≣ map f (return x)] + + 2. ∀m:('a t t t), [flatten (map flatten m) ≣ flatten (flatten m)] + + 3. ∀m:('a t), [flatten (map return m) ≣ flatten (return m) ≣ m] + + 4. ∀m:('a t t), ∀f: ('a -> 'b), [flatten (map (map f) m) ≣ map f (flatten m)] + + As there is no way in the OCaml type system to enforce those properties, + users have to trust the implemented monad when using it, and developers + have to manually check that they are respected. + + More explanations on this approach on monads and its deep roots in the + category theory can be found at the end of this file. + @since Frama-C+dev *) +module type Based_on_map = sig + type 'a t + val return : 'a -> 'a t + val map : ('a -> 'b) -> 'a t -> 'b t + val flatten : 'a t t -> 'a t +end + + +(** {3 Minimal signature based on map with product} + + This signature simply extends the previous one with a product operator. + @since Frama-C+dev *) +module type Based_on_map_with_product = sig + type 'a t + val return : 'a -> 'a t + val map : ('a -> 'b) -> 'a t -> 'b t + val flatten : 'a t t -> 'a t + val product : 'a t -> 'b t -> ('a * 'b) t +end + + +(** {3 Functors extending minimal signatures} + + Those functors provide a way to extend a minimal signature into a full + monad that satisfies the signatures defined above. This is possible + because one can define operations from one monadic definition + using the operations required by the others. Indeed : + + 1. ∀m:('a t), ∀f:('a -> 'b), [map f m ≣ bind (fun x -> return (f x)) m] + + 2. ∀m:('a t t), [flatten m ≣ bind identity m] + + 3. ∀m:('a t), ∀f:('a -> 'b t), [bind f m ≣ flatten (map f m)] + + All required laws expressed in both minimal signatures are respected + using those definitions. *) + +(** Extend a minimal monad based on bind. + @since Frama-C+dev *) +module Make_based_on_bind (M : Based_on_bind) : + S with type 'a t = 'a M.t + +(** Extend a minimal monad based on map. + @since Frama-C+dev *) +module Make_based_on_map (M : Based_on_map) : + S with type 'a t = 'a M.t + +(** Extend a minimal monad based on bind with product. + @since Frama-C+dev *) +module Make_based_on_bind_with_product (M : Based_on_bind_with_product) : + S_with_product with type 'a t = 'a M.t + +(** Extend a minimal monad based on map with product. + @since Frama-C+dev *) +module Make_based_on_map_with_product (M : Based_on_map_with_product) : + S_with_product with type 'a t = 'a M.t + + + +(** {3 Detailled explanations and category theory} + + To be pedantic, the map based approach defines a monad as a categoric + functor equipped with two natural transformations. This does sound + frightening but this breaks down to rather simple concepts. + + Let's start at the beginning. A category is just a collection of objets + and arrows (or morphisms) between those objets that satisfies two + properties: there exists a morphism from all objects to themselves, i.e + an identity, and if there is a morphism between objects [a] and [b] and + a morphism between objects [b] and [c], then there must be a morphism + between [a] and [c], i.e morphisms are associative. + + There is a strong parallel between categories and type systems. Indeed, + if one uses the collection of all types as objects, then for all types + ['a] and ['b], the function [f : 'a -> 'b] can be seen as a morphism + between the objets ['a] and ['b]. As functions are naturally associative + and, for any type ['a], one can trivially defines the identity function + ['a -> 'a], one can conclude that types along with all functions of + arity one forms a category. + + Next, there is the idea of functors. In the category theory, a functor + is a mapping between categories. That means that, given two categories + [A] and [B], a functor maps all objects of [A] to an object of [B] and + maps any morphism of [A] into a morphism of [B]. But, not all mappings + are functors. Indeed, to be a valid functor, one has to preserve the + identity morphisms and the composition of morphims. + + The idea of functors can also be seen in a type systems. At least, the + more restricted but enough here of an endofunctor, a functor from a + category to itself. Indeed, for any type [v] and for any parametric + type ['a t], the type [v t] can be seen as a mapping from values of + type [v] to values of type [v t]. Thus the type ['a t] encodes the first + part of what a functor is, a mapping from objects of the Type category + to objects of the Type category. For the second part, we need a way to + transform morphisms of the Type category, i.e functions of type ['a -> 'b], + in a way that preserves categoric properties. Expressed as a type, this + transformation can be seen as a higher-order function [map] with the + type [map : ('a -> 'b) -> ('a t -> 'b t)]. + + Finally, this definition of a monad requires two natural transformations. + Simply put, a natural transformation is a mapping between functors that + preserves the structures of all the underlying categories (mathematicians + and naming conventions...). That may be quite complicated to grasp, but in + this case, the two required transformations are quite simple and intuitive. + + The first one is the [return] function that embeds values into the monad, + which can be seen as a natural transformation from the identity functor + to the monad functor. The second one is the [flatten] function, which + is a transformation from two applications of the monad functor to the + monad functor. With this two transformations, any number of application + of the monad functor can be taken down to a unique application of the + monad. *) diff --git a/src/libraries/utils/option.ml b/src/libraries/monads/option.ml similarity index 86% rename from src/libraries/utils/option.ml rename to src/libraries/monads/option.ml index f6487051fd02f6ff6dd2e652bce92f302ec6238f..653bd86f92be7a7ddbdbc0d23112fa9851decfda 100644 --- a/src/libraries/utils/option.ml +++ b/src/libraries/monads/option.ml @@ -22,11 +22,12 @@ include Stdlib.Option -let zip l r = match l, r with Some l, Some r -> Some (l, r) | _ -> None - -module Operators = struct - let ( let* ) m f = bind m f - let ( let+ ) m f = map f m - let ( and* ) l r = zip l r - let ( and+ ) l r = zip l r +module Minimal = struct + type 'a t = 'a option + let return v = Some v + let bind f m = bind m f + let product l r = match l, r with Some l, Some r -> Some (l, r) | _ -> None end + +include Monad.Make_based_on_bind_with_product (Minimal) +let bind m f = bind f m diff --git a/src/libraries/utils/option.mli b/src/libraries/monads/option.mli similarity index 78% rename from src/libraries/utils/option.mli rename to src/libraries/monads/option.mli index 5e13d5e8245e779fa2f60a9873e14f6d3f9fe71d..eabf184d63c32fc9a6e7d1e7e4d9526df33c8bda 100644 --- a/src/libraries/utils/option.mli +++ b/src/libraries/monads/option.mli @@ -20,16 +20,11 @@ (* *) (**************************************************************************) -(* Adding let binding operators to the Option module. See - https://v2.ocaml.org/manual/bindingops.html for more information. *) +(** Extend the [option] type to a full fleshed monad. + @since Frama-C+dev *) include module type of Stdlib.Option +include Monad.S_with_product with type 'a t = 'a option -val zip : 'a option -> 'b option -> ('a * 'b) option - -module Operators : sig - val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option - val ( let+ ) : 'a option -> ('a -> 'b) -> 'b option - val ( and* ) : 'a option -> 'b option -> ('a * 'b) option - val ( and+ ) : 'a option -> 'b option -> ('a * 'b) option -end +(** The bind is reversed for retrocompatibility reasons. *) +val bind : 'a t -> ('a -> 'b t) -> 'b t diff --git a/src/libraries/utils/result.ml b/src/libraries/monads/result.ml similarity index 96% rename from src/libraries/utils/result.ml rename to src/libraries/monads/result.ml index 993f64f752e309c6dbf9a82cbb0c6b5bd03ce4b7..ae4257392a5433de02df382f4240450069f28915 100644 --- a/src/libraries/utils/result.ml +++ b/src/libraries/monads/result.ml @@ -29,7 +29,9 @@ let zip l r = | _, Error e -> Error e module Operators = struct + let ( >>- ) r f = bind r f let ( let* ) r f = bind r f + let ( >>-: ) r f = map f r let ( let+ ) r f = map f r let ( and* ) l r = zip l r let ( and+ ) l r = zip l r diff --git a/src/libraries/utils/result.mli b/src/libraries/monads/result.mli similarity index 84% rename from src/libraries/utils/result.mli rename to src/libraries/monads/result.mli index 51edb89cf62ae989d4651a247023d03a50fd8619..6afddc1e457471b73bcb72d873865fb8eb756355 100644 --- a/src/libraries/utils/result.mli +++ b/src/libraries/monads/result.mli @@ -20,9 +20,10 @@ (* *) (**************************************************************************) -(* Adding let binding operators to the Result module. See - https://v2.ocaml.org/manual/bindingops.html for more information. *) - +(** Adding let binding operators to the Result module. + @see <https://v2.ocaml.org/manual/bindingops.html> + This module does not use the generic monad interface (cf. {!Monad}) because + of the error type, which would require another layer of functors. *) include module type of Stdlib.Result (** [zip r1 r2] regroups values in a pair [Ok (v1, v2)] if both arguments are @@ -31,7 +32,9 @@ include module type of Stdlib.Result val zip : ('a, 'e) result -> ('b, 'e) result -> ('a * 'b, 'e) result module Operators : sig + val ( >>- ) : ('a, 'e) result -> ('a -> ('b, 'e) result) -> ('b, 'e) result val ( let* ) : ('a, 'e) result -> ('a -> ('b, 'e) result) -> ('b, 'e) result + val ( >>-: ) : ('a, 'e) result -> ('a -> 'b) -> ('b, 'e) result val ( let+ ) : ('a, 'e) result -> ('a -> 'b) -> ('b, 'e) result val ( and* ) : ('a, 'e) result -> ('b, 'e) result -> ('a * 'b, 'e) result val ( and+ ) : ('a, 'e) result -> ('b, 'e) result -> ('a * 'b, 'e) result diff --git a/src/libraries/monads/state_monad.ml b/src/libraries/monads/state_monad.ml new file mode 100644 index 0000000000000000000000000000000000000000..199b34d8bb2ad827bd2b1c38da6fd7c7737c3a43 --- /dev/null +++ b/src/libraries/monads/state_monad.ml @@ -0,0 +1,51 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +module Make (Env : Datatype.S_with_collections) = struct + + module Cache = Env.Hashtbl + type 'a cache = 'a Cache.t + type env = Env.t + + module Minimal = struct + + type 'a t = ('a * env) cache option * (env -> 'a * env) + let return (x : 'a) : 'a t = (None, fun env -> x, env) + + let compute ((cache, make) : 'a t) (env : env) : 'a * env = + match cache with + | None -> make env + | Some cache when Cache.mem cache env -> Cache.find cache env + | Some cache -> let r = make env in Cache.add cache env r ; r + + let bind (f : 'a -> 'b t) (m : 'a t) : 'b t = + let make env = let a, env = compute m env in compute (f a) env in + (Some (Cache.create 13), make) + + end + + include Monad.Make_based_on_bind (Minimal) + let get_environment : env t = None, fun env -> env, env + let set_environment (env : env) : unit t = None, fun _ -> (), env + let resolve (m : 'a t) (env : env) : 'a * env = Minimal.compute m env + +end diff --git a/src/libraries/monads/state_monad.mli b/src/libraries/monads/state_monad.mli new file mode 100644 index 0000000000000000000000000000000000000000..60d728ff45e4bb94175a17580dc02b2b91646180 --- /dev/null +++ b/src/libraries/monads/state_monad.mli @@ -0,0 +1,33 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2024 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(** The State monad represents computations relying on a global mutable + state but implemented in a functionnal way. + @since Frama-C+dev *) + +module Make (Env : Datatype.S_with_collections) : sig + include Monad.S + type env = Env.t + val get_environment : env t + val set_environment : env -> unit t + val resolve : 'a t -> env -> 'a * env +end diff --git a/src/plugins/eva/api/values_request.ml b/src/plugins/eva/api/values_request.ml index 3cbd20fe5f0e556d02c8d09bca4ad6f10a458067..83aec8185f76a68874230e92c8cbdd19f9b9b49e 100644 --- a/src/plugins/eva/api/values_request.ml +++ b/src/plugins/eva/api/values_request.ml @@ -417,7 +417,7 @@ module Proxy(A : Analysis.Engine) : EvaProxy = struct | Status truth -> Alarmset.Status.pretty fmt truth let get_pointed_bases = function - | Value v -> get_bases (Bottom.fold ~bottom:Cvalue.V.bottom get_cvalue v.v) + | Value v -> get_bases Bottom.(map get_cvalue v.v |> value ~bottom:Cvalue.V.bottom) | Offsetmap offsm -> get_pointed_bases offsm | Status _ -> Base.Hptset.empty @@ -445,7 +445,7 @@ module Proxy(A : Analysis.Engine) : EvaProxy = struct let pretty_eval = Bottom.pretty (pp_result typ) in let value = Pretty_utils.to_string pretty_eval result in let pointed_markers = get_pointed_markers eval_point in - let pointed_vars = Bottom.fold ~bottom:[] pointed_markers result in + let pointed_vars = Bottom.(map pointed_markers result |> value ~bottom:[]) in { value; alarms; pointed_vars } (* --- Evaluates an expression or lvalue into an evaluation [result]. ----- *) diff --git a/src/plugins/eva/contexts/context_product.ml b/src/plugins/eva/contexts/context_product.ml index a6db310f46c7d3d9312ab3af56533fb508530bda..2ff2aeb9a1d71b01e53a43e893e0880e7fdc7e68 100644 --- a/src/plugins/eva/contexts/context_product.ml +++ b/src/plugins/eva/contexts/context_product.ml @@ -24,5 +24,5 @@ module Make (L : Abstract_context.S) (R : Abstract_context.S) = struct type t = L.t * R.t let top = (L.top, R.top) let narrow (l, r) (l', r') = - Lattice_bounds.Bottom.zip (L.narrow l l') (R.narrow r r') + Lattice_bounds.Bottom.product (L.narrow l l') (R.narrow r r') end diff --git a/src/plugins/eva/domains/multidim/segmentation.ml b/src/plugins/eva/domains/multidim/segmentation.ml index bff6e16b6292ec970b956cacb1041704e333d1b3..26225f7a0730443604b05a0faf257b31c8b9f231 100644 --- a/src/plugins/eva/domains/multidim/segmentation.ml +++ b/src/plugins/eva/domains/multidim/segmentation.ml @@ -304,14 +304,14 @@ struct let lower_bound ~oracle b1 b2 = if b1 == b2 || eq b1 b2 then `Value b1 else - let+ i1,i2 = Top.zip + let+ i1,i2 = Top.product (lower_integer ~oracle:(oracle Left) b1) (lower_integer ~oracle:(oracle Right) b2) in Const (Integer.min i1 i2) let upper_bound ~oracle b1 b2 = if b1 == b2 || eq b1 b2 then `Value b1 else - let+ i1,i2 = Top.zip + let+ i1,i2 = Top.product (upper_integer ~oracle:(oracle Left) b1) (upper_integer ~oracle:(oracle Right) b2) in Const (Integer.max i1 i2) @@ -510,7 +510,7 @@ struct let hull ~oracle (m : t) : (bound * bound) or_top = let l = m.start and u = last_bound m.segments in - Top.zip (B.lower_const ~oracle l) (B.upper_const ~oracle u) + Top.product (B.lower_const ~oracle l) (B.upper_const ~oracle u) let is_empty_segment ~oracle l u = let open (val (B.operators oracle)) in diff --git a/src/plugins/eva/domains/octagons.ml b/src/plugins/eva/domains/octagons.ml index df124cd60bfa28d3db684dda973544f806d6e1b9..37469f52eb31c2b763583a1d8a7b796790b062c4 100644 --- a/src/plugins/eva/domains/octagons.ml +++ b/src/plugins/eva/domains/octagons.ml @@ -1277,8 +1277,9 @@ module State = struct let mk_variable_builder (eval : evaluator) (_: t) = let (let*) x f = Option.bind (Top.to_option x) f in (* Is the interval computed for a variable a singleton? *) - let is_singleton = - Top.fold ~top:false Cvalue.V.cardinal_zero_or_one + let is_singleton v = + Top.map Cvalue.V.cardinal_zero_or_one v + |> Top.value ~top:false in fun exp -> match exp.node with diff --git a/src/plugins/eva/engine/evaluation.ml b/src/plugins/eva/engine/evaluation.ml index a852fd484d95515b460de07548be7f1f3350ab60..c661e6c5a7719e90919634cc3fff9cd9f94eef7c 100644 --- a/src/plugins/eva/engine/evaluation.ml +++ b/src/plugins/eva/engine/evaluation.ml @@ -1723,26 +1723,24 @@ module Make match kfs with | `Top -> top_function_pointer funcexp | `Value kfs -> + let open Bottom.Operators in let args_types = Option.map (List.map (fun e -> e.typ)) args in - let kfs, alarm' = - Eval_typ.compatible_functions funcexp.typ ?args:args_types kfs - in + let compatible_funcs = Eval_typ.compatible_functions funcexp.typ in + let kfs, alarm' = compatible_funcs ?args:args_types kfs in let reduce = backward_function_pointer valuation state v in - let process acc kf = - let res = reduce kf >>-: fun valuation -> kf, valuation in - Bottom.add_to_list res acc - in - let list = List.fold_left process [] kfs in - let status = - if kfs = [] then Alarmset.False - else if alarm || alarm' then Alarmset.Unknown - else Alarmset.True + let reduce kf = let+ value = reduce kf in (kf, value) in + let process acc kf = Bottom.add_to_list (reduce kf) acc in + let res, status = + let res = `Value (List.fold_left process [] kfs) in + if kfs = [] then `Bottom, Alarmset.False + else if alarm || alarm' then res, Alarmset.Unknown + else res, Alarmset.True in let cil_v = Eva_ast.to_cil_exp v in let cil_args = Option.map (List.map Eva_ast.to_cil_exp) args in let alarm = Alarms.Function_pointer (cil_v, cil_args) in let alarms = Alarmset.singleton ~status alarm in - Bottom.bot_of_list list, alarms + res, alarms end | _ -> assert false end diff --git a/src/plugins/eva/engine/transfer_stmt.ml b/src/plugins/eva/engine/transfer_stmt.ml index 2d673a4556c5ece200ac5fe3792012e419b58a2b..8d4445d59d31546df2895cf99edc074e8a6bd21a 100644 --- a/src/plugins/eva/engine/transfer_stmt.ml +++ b/src/plugins/eva/engine/transfer_stmt.ml @@ -735,7 +735,7 @@ module Make (Engine: Engine_sig.S) = struct cacheable := NoCacheCallers; states in - Bottom.list_of_bot states + Bottom.value ~bottom:[] states in (* Process each possible function apart, and append the result list. *) let process acc (kf, valuation) = @@ -743,7 +743,7 @@ module Make (Engine: Engine_sig.S) = struct in List.fold_left process [] functions in - Bottom.list_of_bot eval, !cacheable + Bottom.value ~bottom:[] eval, !cacheable (* ------------------------------------------------------------------------ *) (* Unspecified Sequence *)