From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Original-To: caml-list@sympa.inria.fr Delivered-To: caml-list@sympa.inria.fr Received: from mail2-relais-roc.national.inria.fr (mail2-relais-roc.national.inria.fr [192.134.164.83]) by sympa.inria.fr (Postfix) with ESMTPS id C46367FE44 for ; Wed, 6 Jul 2016 11:54:35 +0200 (CEST) IronPort-PHdr: 9a23:UxSrQROcXI6TLmiNjugl6mtUPXoX/o7sNwtQ0KIMzox0KPT8rarrMEGX3/hxlliBBdydsKMczbCI+Pi+EUU7or+5+EgYd5JNUxJXwe43pCcHRPC/NEvgMfTxZDY7FskRHHVs/nW8LFQHUJ2mPw6anHS+4HYoFwnlMkItf6KuS9aU1Zr8j7760qaQSj0AvCC6b7J2IUf+hiTqne5Sv7FfLL0swADCuHpCdrce72ppIVWOg0S0vZ/or9ZLuh5dsPM59sNGTb6yP+FhFeQZX3waNDUX48bmsQjFBTAO/HwAGjE0lRFFBxnDqir3RJDtmjb8t/Q40jGROcDsSLcyRXKs9fE4ZgXvjXIoOiQ1uFrLjchoiatdplr1phpxxKbbbZuZceFieafFeNocQyxNU5ACBGR6HoqgYt5XXKI6NuFCoty4/gNWoA== Authentication-Results: mail2-smtp-roc.national.inria.fr; spf=None smtp.pra=info@gerd-stolpmann.de; spf=None smtp.mailfrom=info@gerd-stolpmann.de; spf=None smtp.helo=postmaster@mout.kundenserver.de Received-SPF: None (mail2-smtp-roc.national.inria.fr: no sender authenticity information available from domain of info@gerd-stolpmann.de) identity=pra; client-ip=212.227.126.187; receiver=mail2-smtp-roc.national.inria.fr; envelope-from="info@gerd-stolpmann.de"; x-sender="info@gerd-stolpmann.de"; x-conformance=sidf_compatible Received-SPF: None (mail2-smtp-roc.national.inria.fr: no sender authenticity information available from domain of info@gerd-stolpmann.de) identity=mailfrom; client-ip=212.227.126.187; receiver=mail2-smtp-roc.national.inria.fr; envelope-from="info@gerd-stolpmann.de"; x-sender="info@gerd-stolpmann.de"; x-conformance=sidf_compatible Received-SPF: None (mail2-smtp-roc.national.inria.fr: no sender authenticity information available from domain of postmaster@mout.kundenserver.de) identity=helo; client-ip=212.227.126.187; receiver=mail2-smtp-roc.national.inria.fr; envelope-from="info@gerd-stolpmann.de"; x-sender="postmaster@mout.kundenserver.de"; x-conformance=sidf_compatible X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: A0DeAABb1HxXhrt+49RdhBR8sVmCZYZ9IoV2AoErPBABAQEBAQEBAREBAQEICwsJIS+CMoIaAQEEASMELiQQCQIYKgICVwYTCYgfCAQBCY5OnR2PeQEBAQEBAQQBAQEBAQETDoUvhUWETIJ1gloFiBqFaosPgTcChFCBMYcNgjiHDwSFX4gZh3E1gicRC4FObAGIcQEBAQ X-IPAS-Result: A0DeAABb1HxXhrt+49RdhBR8sVmCZYZ9IoV2AoErPBABAQEBAQEBAREBAQEICwsJIS+CMoIaAQEEASMELiQQCQIYKgICVwYTCYgfCAQBCY5OnR2PeQEBAQEBAQQBAQEBAQETDoUvhUWETIJ1gloFiBqFaosPgTcChFCBMYcNgjiHDwSFX4gZh3E1gicRC4FObAGIcQEBAQ X-IronPort-AV: E=Sophos;i="5.28,318,1464645600"; d="asc'?scan'208";a="225775028" Received: from mout.kundenserver.de ([212.227.126.187]) by mail2-smtp-roc.national.inria.fr with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 06 Jul 2016 11:54:34 +0200 Received: from office1.lan.sumadev.de ([146.60.54.77]) by mrelayeu.kundenserver.de (mreue003) with ESMTPSA (Nemesis) id 0MbtcA-1b2Vtn1Zyw-00JMua; Wed, 06 Jul 2016 11:54:33 +0200 Received: from [192.168.65.10] (unknown [192.168.65.10]) by office1.lan.sumadev.de (Postfix) with ESMTPSA id C8455DC05D; Wed, 6 Jul 2016 11:54:32 +0200 (CEST) Message-ID: <1467798868.25014.8.camel@e130.lan.sumadev.de> From: Gerd Stolpmann To: Jocelyn =?ISO-8859-1?Q?S=E9rot?= Cc: OCaml Mailing List Date: Wed, 06 Jul 2016 11:54:28 +0200 In-Reply-To: <44623127-96C5-43FB-828D-0F42DCCBA36B@univ-bpclermont.fr> References: <64440D5D-1FC8-4C53-9520-D9D3D21F8FE5@univ-bpclermont.fr> <44623127-96C5-43FB-828D-0F42DCCBA36B@univ-bpclermont.fr> Content-Type: multipart/signed; micalg="pgp-sha1"; protocol="application/pgp-signature"; boundary="=-SgUhdrbkJRL4Pz/FXxr8" X-Mailer: Evolution 3.10.4-0ubuntu2 Mime-Version: 1.0 X-Provags-ID: V03:K0:188TIquCgN3eJE9clBsYSWXq2S4AgAu+eyp+i/vEZ46zbDg8XQz 4LTeOmYFyXJlAzD/FMsgwuW4n3nr+94SmlTdSDuPxbnxDO97RI6ubz748vfQVM8N6OvJeLN 0HwVDod/EA1mEI4LE5FzDc+imagrUxMsxph6v/VDbXUQ9NM65CscFQcBYERq3lmNVN1c8YP Ak4AJQGucpeOeMGSr9qFQ== X-UI-Out-Filterresults: notjunk:1;V01:K0:weyiBdqNRHk=:i4pOJFjGdF45/R65oLI+28 yYAjno5PJOMG0iAXf9+taiORiHpazvk+BUW2khbOkMAts9FyvGM2R0s8qPqR7tByNl/W4dEFK yV0DC69dBCfooyQv3uPpEL9wJvcg/TdkpzUbW8zWpAh0crBd/SG6MyecgwnEOpEqxLmvyEfRt oBtRbdVPMtd59x9BpD+m1ZuOB98IXqgu7LgYEmbQfF3BhAocWC5jh51inJ4CqztOBdM4XfTRs wjvD/POXTaswMuVBRfi7urhhDGrQpKyeLyi6h/iA+sGzbwQGub34bBziMNdVAfmwYsheZGVm1 dRxDNb4o4BBKPGS0kfjvmHCjHiC6irZPh1mH9uoAJsp058Rld6YqMDtTxblzShZ5alqlFmXUp wzVOtmHr0s3ZoMpaTZHU0aGrB9yro8hsWujEc/UT5aLOm/Av3X1fm/VgG+e33OF6TXKUfk8pa cobDb6jfewEI1faBAhQ1GAcEPdkOazHxx5LyigTuxoMDWS+4UXIhVU/1n1VXA+Wz056vJ9HR4 HNAzsCK0WLrGWr7HTPFr3vYQsvyZYCVzBzZXVD3hGA4S0XihOEO7V2c3kIvYBzlackJTDcf7p VeZdcxOrbBthbY8unsZO3SMhgF/5xmob9XR3xOqbb19QBSGgBkNium9YvMvTPZ2gBonVFBOWh tuJZwGdvCQlHFGrGzGr+NYSChWLrC56vCWLxEcUJoFsYlqz92+DvtZVBq3SpdH0XC9VY= Subject: Re: [Caml-list] Q: functors and "has a" inheritance --=-SgUhdrbkJRL4Pz/FXxr8 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Am Mittwoch, den 06.07.2016, 10:44 +0200 schrieb Jocelyn S=C3=A9rot: > Hi Nicolas, >=20 >=20 > Thanks fro your answer. > If i understand correctly, you mean that if i write, say : >=20 >=20 > module type S =3D sig type t val zero: t end > module type T =3D sig type t val zero: t end > module Make (X : S) =3D (struct type t =3D X.t * X.t let zero =3D X.zero, > X.zero end : T) > module M1 =3D Make (struct type t =3D int let zero =3D 0 end) > module M2 =3D Make (struct type t =3D int let zero =3D 0 end) >=20 >=20 > then the compiler will never be able to deduce that M1.t and M2.t are > indeed compatible. Am i right ? Exactly. The feature is called "applicative functors", if you want to Google it. The results of two functor applications are considered compatible when the arguments of the functors are module paths that are identical or aliases of each other. So, e.g. for module Arg1 =3D struct type t =3D int let zero=3D0 end module Arg2 =3D struct type t =3D int let zero=3D0 end module Arg3 =3D struct type t =3D Arg1.t let zero=3D0 end module M1 =3D Make(Arg1) module M2 =3D Make(Arg1) module M3 =3D Make(Arg2) module M4 =3D Make(Arg3) module M5 =3D Make(struct type t =3D int let zero=3D0 end) only M1 and M2 are compatible. The compiler doesn't even look at the types of the argument module, it's only the module name/path that counts. Your design might work when you change Product: module Product (S1: T) (S2: T) (P : T with type elt =3D S1.elt * S2.elt and type attr =3D S1.attr * S2.attr) sig val product: S1.t -> S2.t -> P.t end i.e. the "real" product module is the argument P, and this functor only defines the product function. This way you can instantiate it for any P. Gerd >=20 >=20 > I guess it is because re-use the [Myseta.Product] functor only views > the abstract types exposed by the [Myset.Make] and [Myset.Product] > output signatures. >=20 >=20 > Seems therefore i am really stuck :( >=20 >=20 > Jocelyn >=20 >=20 > Le 6 juil. 2016 =C3=A0 09:49, Nicolas Ojeda Bar > a =C3=A9crit : >=20 > > Hi Jocelyn > >=20 > >=20 > > One issue is that you have two modules, P and R.S, of the form > > Set.Make(X), Set.Make (X') for modules X and X' which are > > structurally equal. Unfortunately this is not enough for the OCaml > > module system to deduce that P.t and R.S.t are compatible. In > > general if F is a functor with output signature S and t is abstract > > type in S, then F(X).t and F(X').t will be compatible exactly when X > > and X' are literally the same module. I don't think you will be > > able to fix this by adding type sharing constrains. > >=20 > >=20 > > Cheers > > Nicolas > >=20 > >=20 > >=20 > > On Tue, Jul 5, 2016 at 5:25 PM, Jocelyn S=C3=A9rot > > wrote: > > Dear all,=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > I=E2=80=99m stuck with a problem related with the use of functo= rs > > for implementing a library. > > The library concerns Labeled Transition Systems but i=E2=80=99ll > > present it in a simplified version using sets. > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Suppose i have a (very simplified !) Set module, which i > > will call Myset to distinguish from that of the standard > > library : > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.mli > > module type T =3D sig > > type elt=20 > > type t=20 > > val empty: t > > val add: elt -> t -> t > > val elems: t -> elt list > > val fold: (elt -> 'a -> 'a) -> t -> 'a -> 'a > > end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module Make (E : Set.OrderedType) : T with type elt =3D E.t > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.ml > > module type T =3D sig =E2=80=A6 (* idem myset.mli *) end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module Make (E : Set.OrderedType) =3D struct > > module Elt =3D E > > type elt =3D E.t > > type t =3D { elems: elt list; }=20=20 > > let empty =3D { elems =3D [] } > > let add q s =3D { elems =3D q :: s.elems } (* obviously > > wrong, but does not matter here ! *) > > let elems s =3D s.elems > > let fold f s z =3D List.fold_left (fun z e -> f e z) z > > s.elems > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > First, i add a functor for computing the product of two > > sets : > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.mli (cont=E2=80=99d) > > module Product (S1: T) (S2: T) : > > sig > > include T with type elt =3D S1.elt * S2.elt > > val product: S1.t -> S2.t -> t > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.ml (cont=E2=80=99d) > > module Product > > (S1: T) > > (S2: T) =3D > > struct > > module R =3D > > Make (struct type t =3D S1.elt * S2.elt let compare =3D > > compare end) > > include R > > let product s1 s2 =3D > > S1.fold > > (fun q1 z -> > > S2.fold > > (fun q2 z -> R.add (q1,q2) z) > > s2 > > z) > > s1 > > R.empty > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Here=E2=80=99s a typical usage of the Myset module :=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94 ex1.ml=20 > > module IntSet =3D Myset.Make (struct type t =3D int let compare > > =3D compare end) > > module StringSet =3D Myset.Make (struct type t =3D string let > > compare =3D compare end) > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > let s1 =3D IntSet.add 1 (IntSet.add 2 IntSet.empty) > > let s2 =3D StringSet.add "a" (StringSet.add "b" > > StringSet.empty) > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module IntStringSet =3D Myset.Product (IntSet) (StringSet) > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > let s3 =3D IntStringSet.product s1 s2 > > =E2=80=94=E2=80=94 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > So far, so good.=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Now suppose i want to =C2=AB augment =C2=BB the Myset module so= that > > some kind of attribute is attached to each set element. I > > could of course just modify the definition of type [t] and > > the related functions in the files [myset.ml] and > > [myset.mli]. But suppose i want to reuse as much as possible > > the code already written. My idea is define a new module - > > let=E2=80=99s call it [myseta] (=C2=AB a =C2=BB for attributes)= - in which the > > type [t] will include a type [Myset.t] and the definitions > > of this module will make use, as much as possible, of those > > defined in [Myset]. > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Here=E2=80=99s a first proposal (excluding the Product functor = for > > the moment) :=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myseta.mli > > module type Attr =3D sig type t end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module type T =3D sig > > type elt=20 > > type attr > > type t=20 > > module S: Myset.T > > val empty: t > > val add: elt * attr -> t -> t > > val elems: t -> elt list > > val attrs: t -> (elt * attr) list > > val set_of: t -> S.t > > val fold: (elt * attr -> 'a -> 'a) -> t -> 'a -> 'a > > end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module Make (E : Set.OrderedType) (A: Attr) : T with type > > elt =3D E.t and type attr =3D A.t > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myseta.ml > > module type Attr =3D sig type t end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module type T =3D sig (* idem myseta.mli *) end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module Make (E : Set.OrderedType) (A : Attr) =3D struct > > module Elt =3D E > > type elt =3D E.t > > type attr =3D A.t > > module S =3D Myset.Make(E) > > type t =3D { elems: S.t; attrs: (elt * attr) list } > > let empty =3D { elems =3D S.empty; attrs =3D [] } > > let add (e,a) s =3D { elems =3D S.add e s.elems; attrs =3D > > (e,a) :: s.attrs } > > let elems s =3D S.elems s.elems > > let attrs s =3D s.attrs > > let set_of s =3D s.elems > > let fold f s z =3D List.fold_left (fun z e -> f e z) z > > s.attrs > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > In practice, of course the [Attr] signature will include > > other specifications. > > In a sense, this is a =C2=AB has a =C2=BB inheritance : wheneve= r i > > build a [Myseta] module, i actually build a [Myset] > > sub-module and this module is used to implement all the > > set-related operations.=20 > > Again, so far, so good. > > The problem shows when i try to define the [Product] functor > > for the [Myseta] module : > > It=E2=80=99s signature is similar to that of the [Myset.Product] > > functor, with an added sharing constraint for attributes (in > > fact, we could imagine a more sophisticated scheme for > > merging attributes but cartesian product is here) : > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.mli (cont=E2=80=99d) > > module Product (S1: T) (S2: T) : > > sig > > include T with type elt =3D S1.elt * S2.elt > > and type attr =3D S1.attr * S2.attr > > val product: S1.t -> S2.t -> t > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Now, here=E2=80=99s my current implementation > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 myset.ml (cont=E2=80=99d) > > module Product > > (S1: T) > > (S2: T) =3D > > struct > > module R =3D > > Make > > (struct type t =3D S1.elt * S2.elt let compare =3D compare > > end) > > (struct type t =3D S1.attr * S2.attr let compare =3D > > compare end) > > include R > > module P =3D Myset.Product(S1.S)(S2.S) > > let product s1 s2 =3D > > { elems =3D P.product (S1.set_of s1) (S2.set_of s2); > > attrs =3D > > List.fold_left > > (fun acc (e1,a1) -> > > List.fold_left (fun acc (e2,a2) -> > > ((e1,e2),(a1,a2))::acc) acc (S2.attrs s2)) > > [] > > (S1.attrs s1) } > > end > > =E2=80=94=E2=80=94=E2=80=94=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > I use the [Myseta.Make] functor for building the resulting > > module [named R here]. For defining the [product] function, > > i first use the [Myset.Product] functor applied on the two > > related sub-modules [S1] and [S2] to build the product > > module (named P here) and re-use the [product] function of > > this module to compute the [elems] component of the result. > > The other component is computed directly.=20 > > The problem is that when i try to compile this i get this > > message :=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > File "myseta.ml", line 44, characters 14-53: > > Error: This expression has type P.t =3D > > Myset.Product(S1.S)(S2.S).t > > but an expression was expected of type S.t =3D R.S.t > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > My intuition is that a sharing constraint is missing > > somewhere but i just cannot figure out where to add it.=20 > > I tried to rewrite the signature of the [Myseta.Product] > > functor (in [myseta.mli]) as : > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > module Product (S1: T) (S2: T) : > > sig > > include T with type elt =3D S1.elt * S2.elt > > and type attr =3D S1.attr * S2.attr > > and type S.t =3D Myset.Product(S1.S)(S2.S).t (* > > added constraint *) > > val product: S1.t -> S2.t -> t > > end > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > but it did not change anything.. > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > So my question is : is my diagnostic correct and, if yes, > > which constraint(s) are missing and where; or, conversely, > > am i completely =C2=AB misusing =C2=BB the functor mechanisms f= or > > implementing this kind of =C2=AB reuse by inclusion =C2=BB ?=20 > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Any help will be grealy appreciated : i=E2=80=99ve been reading= and > > re-reading about functors for the last two days but have the > > impression that at this step, things get more and more > > opaque.. :-S > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > In anycase, the source code is > > here : http://filez.univ-bpclermont.fr/lamuemlqpm > >=20=20=20=20=20=20=20=20=20 > >=20=20=20=20=20=20=20=20=20 > > Jocelyn > >=20 > >=20 >=20 --=20 ------------------------------------------------------------ Gerd Stolpmann, Darmstadt, Germany gerd@gerd-stolpmann.de My OCaml site: http://www.camlcity.org Contact details: http://www.camlcity.org/contact.html Company homepage: http://www.gerd-stolpmann.de ------------------------------------------------------------ --=-SgUhdrbkJRL4Pz/FXxr8 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJXfNVUAAoJEAaM4b9ZLB5T4tIIAIm2zo26Leieks+lpAox085V hQiRAodLuypwTwDEXqREe/QT6PrKopxCcmdbqs08zgQt8cR9FcAcp9HZpdH6u9pD ROzA3gxzbxGvyWPVhOWWz3BHO6uEu+kLa/0KTtujJLam4hkS++FGIvEIHTiSQnJc 2aRdx969MkTeeedNW/bfwZKSsn4Fgn2I5xlHgH0m+aAA8YRwTIhJlFN2NmwsfxFG b+fEezSfMT5xBCZGESaJ7MpN5kaZ+zyNugvfj35ujHPemBRStwL6o6K+6+G7g/g7 VTiC+FnMfOhmAQLdza2/Jr+f+e+07f7QAk16fZy5NzKqJ97Ml1AabNxFiSFLaK4= =lcE7 -----END PGP SIGNATURE----- --=-SgUhdrbkJRL4Pz/FXxr8--