Difference between revisions of "Partible laws"

From HaskellWiki
Jump to navigation Jump to search
m (Preconditions for law "Substitutive" simplified)
m (Changes to selected formatting metadata)
 
(4 intermediate revisions by the same user not shown)
Line 2: Line 2:
 
== Preliminaries ==
 
== Preliminaries ==
   
* ''p''''q'' simply means that you can replace ''p'' with ''q'' and vice-versa, and the behaviour of your program will not change: ''p'' and ''q'' are equivalent;
+
* <i>p</i><i>q</i> simply means that you can replace <i>p</i> with <i>q</i> and vice-versa, and the behaviour of your program will not change: <i>p</i> and <i>q</i> are equivalent;
   
* <code>T</code> is a ''partible type'', with an instance for <code>Partible</code>;
+
* <code>T</code> is a <i>partible type</i>, with an instance for <code>Partible</code>;
   
* ''S''(<code>T</code>) is the set of ''basic definitions'' for <code>T</code>-values (analogous to the set of ''primitive definitions'' for a builtin type);
+
* <i>S</i>(<code>T</code>) is the set of <i>basic definitions</i> for <code>T</code>-values (analogous to the set of <i>primitive definitions</i> for a builtin type);
   
* <i>new</i>(<code>u :: T</code>) means <code>u</code> has never been used by any member of ''S''(<code>T</code>);
+
* <i>new</i>(<code>u :: T</code>) means <code>u</code> isn't used elsewhere by a member of <i>S</i>(<code>T</code>);
   
* <i>old</i>(<code>u :: T</code>) means <code>u</code> was previously used by a member of ''S''(<code>T</code>);
+
* <i>old</i>(<code>u :: T</code>) means <code>u</code> is used elsewhere by a member of <i>S</i>(<code>T</code>);
   
* Each member of ''S''(<code>T</code>) uses one or more <code>T</code>-values ''strictly and exclusively'':
+
* Each member of <i>S</i>(<code>T</code>) uses one or more <code>T</code>-values <i>strictly</i> and <i>exclusively</i>:
::if <code>x</code> ⊥ or ''old''(<code>x</code>) causes <code>f</code> <b>…</b> (<code>x :: !T</code>) <b>…</b> ≡ ⊥, then <code>f</code> ∈ ''S''(<code>T</code>);
+
::if <code>f</code> <i>S</i>(<code>T</code>), then <code>f</code> <b>…</b> (<code>x :: !T</code>) <b>…</b> ≡ ⊥:
  +
::* if <code>x</code> ≡ ⊥
  +
::* or <i>old</i>(<code>x</code>);
   
* The <code>Partible</code> instance for <code>T</code> is ''well-defined'':
+
* The <code>Partible</code> instance for <code>T</code> is <i>well-defined</i>:
::if <i>new</i>(<code>u</code>) and <code>part</code> ∈ ''S''(<code>T</code>), then <code>part u</code> ≡ <code>(u1, u2)</code>, where <i>new</i>(<code>u1</code>) and <i>new</i>(<code>u2</code>); <code>u</code>, <code>u1</code> and <code>u2</code> are distinct and disjoint;
+
::if <i>new</i>(<code>u</code>) and <code>part</code> ∈ <i>S</i>(<code>T</code>), then <code>part u</code> ≡ <code>(u1, u2)</code>, where:
  +
::* <i>new</i>(<code>u1</code>) and <i>new</i>(<code>u2</code>)
  +
::* <code>u</code>, <code>u1</code> and <code>u2</code> are distinct
  +
::* <code>u1</code> and <code>u2</code> are disjoint;
   
 
* <code>g</code> is a strict function: <code>g</code> ⊥ ≡ ⊥
 
* <code>g</code> is a strict function: <code>g</code> ⊥ ≡ ⊥
   
* <code>g</code>'s parameter is meant to be used strictly and exclusively by a member of ''S''(<code>T</code>):
+
* <code>g</code>'s parameter is meant to be used strictly and exclusively by a member of <i>S</i>(<code>T</code>):
::<code>g x</code> = <b>…</b> (<code>f</code> <b>…</b> (<code>x :: !T</code>) <b>…</b>) <b>…</b>, where <code>f</code> ∈ ''S''(<code>T</code>)
+
::<code>g u</code> = <b>…</b> (<code>f</code> <b>…</b> (<code>u :: !T</code>) <b>…</b>) <b>…</b>, where <code>f</code> ∈ <i>S</i>(<code>T</code>)
 
::<code>g u</code> ≡ ⊥, <i>old</i>(<code>u</code>)
 
::<code>g u</code> ≡ ⊥, <i>old</i>(<code>u</code>)
   
Line 48: Line 53:
 
== Why the partible laws should be satisfied ==
 
== Why the partible laws should be satisfied ==
   
* ''In theory'' - they add to the set of valid transformations available for reasoning about the definitions of programs e.g. verifying Kleisli-composition:
+
* <i>In theory</i> - they add to the set of valid transformations available for reasoning about the definitions of programs e.g. verifying Kleisli-composition:
   
 
:Using:
 
:Using:
Line 94: Line 99:
 
|}
 
|}
   
* ''In practice'' - most optimising Haskell implementations use transformations like these to improve a program's performance, so a not-quite-partible type would lead to confusing runtime errors. But even if your Haskell implementation doesn't use them, '''you''' still want the laws for your own sake, just so you can avoid pulling your hair out over counter-intuitive program behaviour resulting from seemingly-innocuous changes such as rearranging calls to <code>part</code> or how values of some supposedly-partible type are used...
+
* <i>In practice</i> - most optimising Haskell implementations use transformations like these to improve a program's performance, so a not-quite-partible type would lead to confusing runtime errors. But even if your Haskell implementation doesn't use them, '<i>you</i>' still want the laws for your own sake, just so you can avoid pulling your hair out over counter-intuitive program behaviour resulting from seemingly-innocuous changes such as rearranging calls to <code>part</code> or how values of some supposedly-partible type are used...
   
 
== Examples ==
 
== Examples ==
   
=== Proving the ''part-easing'' rule ===
+
=== Proving the <i>part-easing</i> rule ===
   
 
:* LSE = <code>(\(!_) -> r) (part u)</code>
 
:* LSE = <code>(\(!_) -> r) (part u)</code>
 
:* RSE = <code>(\(!_) -> r) u</code>
 
:* RSE = <code>(\(!_) -> r) u</code>
:* conditions: <code>r</code> doesn't require <code>part u</code>, <code>u</code> is obtained from <code>part u0</code>, <i>new</i>(<code>u0</code>)
+
:* conditions: <code>r</code> doesn't require <code>part u</code>, <i>new</i>(<code>u</code>)
 
<sub> </sub>
 
<sub> </sub>
 
:{|
 
:{|
Line 119: Line 124:
 
| =
 
| =
 
| <haskell>(\(!_) -> r) u</haskell>
 
| <haskell>(\(!_) -> r) u</haskell>
| [law: ''Left-easing'']
+
| [law: <i>Left-easing</i>]
 
|-
 
|-
 
| =
 
| =
Line 126: Line 131:
 
|}
 
|}
 
----
 
----
=== Proving the ''monad laws'' ===
+
=== Proving the <i>monad laws</i> ===
   
...''Kleisli-composition'' style:
+
...<i>Kleisli-composition</i> style:
   
 
{|
 
{|
 
|-
 
|-
| ''Left identity:''
+
| <i>Left identity:''
 
| <haskell>return >=> h</haskell>
 
| <haskell>return >=> h</haskell>
 
| ≡
 
| ≡
 
| <haskell>h</haskell>
 
| <haskell>h</haskell>
 
|-
 
|-
| ''Right identity:''
+
| <i>Right identity:''
 
| <haskell>f >=> return</haskell>
 
| <haskell>f >=> return</haskell>
 
| ≡
 
| ≡
 
| <haskell>f</haskell>
 
| <haskell>f</haskell>
 
|-
 
|-
| ''Associativity:''
+
| <i>Associativity:''
 
| <haskell>(f >=> g) >=> h</haskell>
 
| <haskell>(f >=> g) >=> h</haskell>
 
| ≡
 
| ≡
Line 196: Line 201:
 
!y -> h y u2
 
!y -> h y u2
 
</haskell>
 
</haskell>
| [rule: ''trivial case'']
+
| [rule: <i>trivial case</i>]
 
|-
 
|-
 
| =
 
| =
Line 204: Line 209:
 
!y -> h y u2
 
!y -> h y u2
 
</haskell>
 
</haskell>
| [rule: ''part-easing'']
+
| [rule: <i>part-easing</i>]
 
|-
 
|-
 
| =
 
| =
Line 227: Line 232:
 
(u1, u2) -> h x u2
 
(u1, u2) -> h x u2
 
</haskell>
 
</haskell>
| [rule: ''trivial case''; '''assuming''' <code>x</code> ≠ ⊥]
+
| [rule: <i>trivial case</i>; <b>assuming</b> <code>x</code> ≠ ⊥]
 
|-
 
|-
 
| =
 
| =
Line 248: Line 253:
 
u2 -> h x u2
 
u2 -> h x u2
 
</haskell>
 
</haskell>
| [law: ''Right-easing'']
+
| [law: <i>Right-easing</i>]
 
|-
 
|-
 
| =
 
| =
Line 254: Line 259:
 
\u -> h x u
 
\u -> h x u
 
</haskell>
 
</haskell>
| [rule: ''trivial case'']
+
| [rule: <i>trivial case</i>]
 
|-
 
|-
 
| =
 
| =
Line 262: Line 267:
 
| =
 
| =
 
| RSE
 
| RSE
| ['''assuming''' <code>x</code> ≠ ⊥]
+
| [<b>assuming</b> <code>x</code> ≠ ⊥]
 
|}
 
|}
   
Line 304: Line 309:
 
!y -> (\(!_) -> y) (part u2)
 
!y -> (\(!_) -> y) (part u2)
 
</haskell>
 
</haskell>
| [rule: ''trivial case'']
+
| [rule: <i>trivial case</i>]
 
|-
 
|-
 
| =
 
| =
Line 312: Line 317:
 
!y -> (\(!_) -> y) u2
 
!y -> (\(!_) -> y) u2
 
</haskell>
 
</haskell>
| [rule: ''part-easing'']
+
| [rule: <i>part-easing</i>]
 
|-
 
|-
 
| =
 
| =
Line 335: Line 340:
 
(u1, u2) -> f x u1
 
(u1, u2) -> f x u1
 
</haskell>
 
</haskell>
| [rule: ''trivial case''; '''assuming''' <code>f x …</code> ≠ ⊥]
+
| [rule: <i>trivial case</i>; <b>assuming</b> <code>f x …</code> ≠ ⊥]
 
|-
 
|-
 
| =
 
| =
Line 356: Line 361:
 
u1 -> f x u1
 
u1 -> f x u1
 
</haskell>
 
</haskell>
| [law: ''Left-easing'']
+
| [law: <i>Left-easing</i>]
 
|-
 
|-
 
| =
 
| =
 
| <haskell> \u -> f x u </haskell>
 
| <haskell> \u -> f x u </haskell>
| [rule: ''trivial case'']
+
| [rule: <i>trivial case</i>]
 
|-
 
|-
 
| =
 
| =
Line 368: Line 373:
 
| =
 
| =
 
| RSE
 
| RSE
| ['''assuming''' <code>f x …</code> ≠ ⊥]
+
| [<b>assuming</b> <code>f x …</code> ≠ ⊥]
 
|}
 
|}
   
Line 406: Line 411:
 
!y -> h y u2
 
!y -> h y u2
 
</haskell>
 
</haskell>
| [rule: ''case of case'']
+
| [rule: <i>case of case</i>]
 
|-
 
|-
 
| =
 
| =
Line 416: Line 421:
 
!y -> h y u2
 
!y -> h y u2
 
</haskell>
 
</haskell>
| [rule: ''case of case'']
+
| [rule: <i>case of case</i>]
 
|-
 
|-
 
| =
 
| =
Line 426: Line 431:
 
!y -> h y u1
 
!y -> h y u1
 
</haskell>
 
</haskell>
| [law: ''Substitutive'']
+
| [law: <i>Substitutive</i>]
 
|-
 
|-
 
| =
 
| =
Line 436: Line 441:
 
of !y -> h y u4
 
of !y -> h y u4
 
</haskell>
 
</haskell>
| [law: ''Substitutive'']
+
| [law: <i>Substitutive</i>]
 
|-
 
|-
 
| =
 
| =
Line 446: Line 451:
 
!y -> h y u5
 
!y -> h y u5
 
</haskell>
 
</haskell>
| [law: ''Substitutive'']
+
| [law: <i>Substitutive</i>]
 
|-
 
|-
 
| =
 
| =
Line 468: Line 473:
 
(f x u1)
 
(f x u1)
 
</haskell>
 
</haskell>
| [rule: ''function-case'']
+
| [rule: <i>function-case</i>]
 
|-
 
|-
 
| =
 
| =
Line 508: Line 513:
 
|}
 
|}
 
<sub> </sub>
 
<sub> </sub>
(Note: those residual assumptions from the proofs for the identity laws are to be expected considering how <code>return</code> and <code>(>=>)</code> are defined - for more details, see section 3.4 of Philip Wadler's [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf How to Declare an Imperative].)
+
(Note: those residual condtions from the proofs for the identity laws arise from how <code>return</code> and <code>(>=>)</code> are defined - for more details, see section 3.4 of Philip Wadler's [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf How to Declare an Imperative].)
   
 
== References ==
 
== References ==

Latest revision as of 12:52, 1 January 2024

Preliminaries

  • pq simply means that you can replace p with q and vice-versa, and the behaviour of your program will not change: p and q are equivalent;
  • T is a partible type, with an instance for Partible;
  • S(T) is the set of basic definitions for T-values (analogous to the set of primitive definitions for a builtin type);
  • new(u :: T) means u isn't used elsewhere by a member of S(T);
  • old(u :: T) means u is used elsewhere by a member of S(T);
  • Each member of S(T) uses one or more T-values strictly and exclusively:
if fS(T), then f (x :: !T) ≡ ⊥:
  • if x ≡ ⊥
  • or old(x);
  • The Partible instance for T is well-defined:
if new(u) and partS(T), then part u(u1, u2), where:
  • new(u1) and new(u2)
  • u, u1 and u2 are distinct
  • u1 and u2 are disjoint;
  • g is a strict function: g ⊥ ≡ ⊥
  • g's parameter is meant to be used strictly and exclusively by a member of S(T):
g u = (f (u :: !T) ) , where fS(T)
g u ≡ ⊥, old(u)

The laws

Substitutive: g u1 g u2, new(u1) and new(u2); u1 and u2 are distinct and disjoint;
Left-easing: g (fst (part u)) g u, new(u)
Right-easing: g (snd (part u)) g u, new(u)

Why the partible laws should be satisfied

  • In theory - they add to the set of valid transformations available for reasoning about the definitions of programs e.g. verifying Kleisli-composition:
Using:
(g >=> h) x  = \u -> case part u of
                       (u1, u2) -> case g x u1 of
                                     !y -> h y u2  

m >>= k      = \u -> case part u of
                       (u1, u2) -> case m u1 of
                                     !x -> k x u2
  • LSE = (g >=> h) x
  • RSE = g x >>= h

  g x >>= h
[RSE]
=
  \u -> case part u of
          (u1, u2) -> case g x u1 of 
                        !x -> h x u2
[application of (>>=)]
=
  \u -> case part u of
          (u1, u2) -> case g x u1 of 
                        !y -> h y u2
[bound variable renamed]
=
  (g >=> h) x
[definition of (>=>)]
= LSE
  • In practice - most optimising Haskell implementations use transformations like these to improve a program's performance, so a not-quite-partible type would lead to confusing runtime errors. But even if your Haskell implementation doesn't use them, 'you' still want the laws for your own sake, just so you can avoid pulling your hair out over counter-intuitive program behaviour resulting from seemingly-innocuous changes such as rearranging calls to part or how values of some supposedly-partible type are used...

Examples

Proving the part-easing rule

  • LSE = (\(!_) -> r) (part u)
  • RSE = (\(!_) -> r) u
  • conditions: r doesn't require part u, new(u)

(\(!_) -> r) (part u)
[LSE]
=
(\(!_) -> r) (case part u of (u1, _) -> u1)
[scrutinee evaluated but not used in r]
=
(\(!_) -> r) (fst (part u))
[definition of fst]
=
(\(!_) -> r) u
[law: Left-easing]
= RSE

Proving the monad laws

...Kleisli-composition style:

Left identity:
return >=> h
h
Right identity:
f >=> return
f
Associativity:
(f >=> g) >=> h
f >=> (g >=> h)

Using:

return x     = \u -> case part u of !_ -> x

(g >=> h) x  = \u -> case part u of
                       (u1, u2) -> case g x u1 of
                                     !y -> h y u2

Left identity

  • LSE = (return >=> h) x
  • RSE = h x

  (return >=> h) x
[LSE]
=
  \u -> case part u of
          (u1, u2) -> case return x u1 of 
                        !y -> h y u2
[application of (>=>)]
=
  \u -> case part u of
          (u1, u2) -> case (case part u1 of !_ -> x) of 
                        !y -> h y u2
[application of return]
=
  \u -> case part u of
          (u1, u2) -> case (\t -> case t of !_ -> x) (part u1) of 
                        !y -> h y u2
[function abstraction]
=
  \u -> case part u of
          (u1, u2) -> case (\(!_) -> x) (part u1) of 
                        !y -> h y u2
[rule: trivial case]
=
  \u -> case part u of
          (u1, u2) -> case (\(!_) -> x) u1 of 
                        !y -> h y u2
[rule: part-easing]
=
  \u -> case part u of
          (u1, u2) -> case (\_ -> x) u1 of 
                        !y -> h y u2
[u1 not used anywhere else]
=
  \u -> case part u of
          (u1, u2) -> case x of
                        !y -> h y u2
[function application]
=
  \u -> case part u of
          (u1, u2) -> h x u2
[rule: trivial case; assuming x ≠ ⊥]
=
  \u -> case (\(u1, u2) -> u2) (part u) of 
          u2 -> h x u2
[definition of snd]
=
  \u -> case snd (part u) of 
          u2 -> h x u2
[definition of snd]
=
  \u -> case u of
          u2 -> h x u2
[law: Right-easing]
=
  \u -> h x u
[rule: trivial case]
=
  h x
[eta-substitution]
= RSE [assuming x ≠ ⊥]

Right identity

  • LSE = (f >=> return) x
  • RSE = f x

  (f >=> return) x
[LSE]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> return y u2
[application of (>=>)]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> case part u2 of !_ -> y
[application of return]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> (\t -> case t of !_ -> y) (part u2)
[function abstraction]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> (\(!_) -> y) (part u2)
[rule: trivial case]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> (\(!_) -> y) u2
[rule: part-easing]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !y -> (\_ -> y) u2
[u2 not used anywhere else]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of 
                        !y -> y
[function application]
=
  \u -> case part u of
          (u1, u2) -> f x u1
[rule: trivial case; assuming f x … ≠ ⊥]
=
  \u -> case (\(u1, u2) -> u1) (part u) of 
          u1 -> f x u1
[function abstraction]
=
  \u -> case fst (part u) of 
          u1 -> f x u1
[definition of fst]
=
  \u -> case u of
          u1 -> f x u1
[law: Left-easing]
=
  \u -> f x u
[rule: trivial case]
=
  f x
[eta-substitution]
= RSE [assuming f x … ≠ ⊥]

Associativity

  • LSE = ((f >=> g) >=> h) x
  • RSE = (f >=> (g >=> h)) x

  ((f >=> g) >=> h) x
[LSE]
=
  \u -> case part u of
          (u1, u2) -> case (f >=> g) x u1 
                        of !y -> h y u2
[application of (>=>)]
=
  \u -> case part u of
          (u1, u2) -> case (case part u1 of
                              (u4, u5) -> case f x u4
                                            of !d -> g d u5) of 
                        !y -> h y u2
[application of (>=>)]
=
  \u -> case part u of
          (u1, u2) -> case part u1 of
                        (u4, u5) -> case (case f x u4 of
                                            !d -> g d u5) of 
                                      !y -> h y u2
[rule: case of case]
=
  \u -> case part u of
          (u1, u2) -> case part u1 of
                        (u4, u5) -> case f x u4 of
                                      !d -> case g d u5 of
                                              !y -> h y u2
[rule: case of case]
=
  \u -> case part u of
          (u1, u2) -> case part u2 of
                        (u4, u5) -> case f x u4 of
                                      !d -> case g d u5 of
                                              !y -> h y u1
[law: Substitutive]
=
  \u -> case part u of
          (u1, u2) -> case part u2 of
                        (u4, u5) -> case f x u1 of
                                      !d -> case g d u5
                                              of !y -> h y u4
[law: Substitutive]
=
  \u -> case part u of
          (u1, u2) -> case part u2 of
                        (u4, u5) -> case f x u1 of
                                      !d -> case g d u4 of
                                              !y -> h y u5
[law: Substitutive]
=
  \u -> case part u of
          (u1, u2) -> case part u2 of
                        (u4, u5) -> (\t -> case t of
                                             !d -> case g d u4 of
                                                     !y -> h y u5) 
                                      (f x u1)
[function abstraction]
=
  \u -> case part u of
          (u1, u2) -> (\t -> case t of
                               !d -> case part u2 of
                                       (u4, u5) -> case g d u4 of
                                                     !y -> h y u5) 
                        (f x u1)
[rule: function-case]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !d -> case part u2 of
                                (u4, u5) -> case g d u4 of
                                              !y -> h y u5
[function application]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !d -> (\b c u3 -> case part u3 of
                                            (u4, u5) -> case b d u4 of
                                                          !y -> c y u5) 
                                g h u2
[function abstraction]
=
  \u -> case part u of
          (u1, u2) -> case f x u1 of
                        !d -> (g >=> h) d u2
[definition of (>=>)]
=
  (f >=> (g >=> h)) x
[definition of (>=>)]
= RSE

(Note: those residual condtions from the proofs for the identity laws arise from how return and (>=>) are defined - for more details, see section 3.4 of Philip Wadler's How to Declare an Imperative.)

References