Difference between revisions of "Denotative"

From HaskellWiki
Jump to navigation Jump to search
(Initial content)
 
(New and rewritten sections, various other changes)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
On page 8 of his paper [https://www.cs.cmu.edu/~crary/819-f09/Landin66.pdf The Next 700 Programming Languages], Peter Landin introduces the word ''"denotative"'' as the direct opposite of the term ''"imperative"''. To be considered denotative, Landin stipulates the following conditions for the expressions of a programming language:
+
On page 8 of [https://www.cs.cmu.edu/~crary/819-f09/Landin66.pdf The Next 700 Programming Languages], Peter Landin introduces the word <q>denotative</q> as the direct opposite of the term <q>imperative</q>. To be considered denotative, Landin stipulates the following conditions for the expressions of a programming language:
   
  +
<blockquote>
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
 
(a) each expression has a nesting subexpression structure,<br>
 
(a) each expression has a nesting subexpression structure,<br>
 
(b) each subexpression denotes something (usually a number, truth value or numerical function),<br>
 
(b) each subexpression denotes something (usually a number, truth value or numerical function),<br>
(c) the thing an expression denotes, i.e., its "value", depends only on the ''values'' of its subexpressions, not on other properties of them.
+
(c) the thing an expression denotes, i.e., its "value", depends only on the <i>values</i> of its subexpressions, not on other properties of them.
</div>
+
</blockquote>
   
 
A programming language is trivially denotative if it only permits programs to be defined in terms of denotative expressions.
 
A programming language is trivially denotative if it only permits programs to be defined in terms of denotative expressions.
  +
  +
== Being privately imperative ==
  +
  +
[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.144.2237&rep=rep1&type=pdf In 1994] researchers discovered a way to denotatively accommodate the imperative style by excluding the use of <i>externally visible</i> side-effects. For example:
  +
  +
<blockquote>
  +
<haskell>
  +
type Graph = Array Vertex [Vertex]
  +
data Tree a = Node a [Tree a]
  +
  +
dfs :: Graph -> [Vertex] -> [Tree Vertex]
  +
dfs g vs = runST (
  +
newSTArray (bounds g) False >>= \ marks ->
  +
search marks vs
  +
)
  +
where search :: STArray s Vertex Bool -> [Vertex]
  +
-> ST s [Tree Vertex]
  +
search marks [] = return []
  +
search marks (v:vs) = readSTArray marks v >>= \ visited ->
  +
if visited then
  +
search marks vs
  +
else
  +
writeSTArray marks v True >>
  +
search marks (g!v) >>= \ ts ->
  +
search marks vs >>= \ us ->
  +
return ((Node v ts): us)
  +
</haskell>
  +
 
:<small>from Figure 2 (page 17 of 51) of [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.52.3656&rep=rep1&type=pdf State in Haskell]</small>
  +
</blockquote>
  +
  +
Even though <code>dfs</code>'s local definition:
  +
  +
<haskell>
  +
search :: STArray s Vertex Bool -> [Vertex] -> ST s [Tree Vertex]
  +
</haskell>
  +
  +
uses mutability and sequencing, the use of <code>runST</code> keeps those imperative details confined to <code>dfs</code>, hence its denotative type:
  +
  +
<haskell>
  +
dfs :: Graph -> [Vertex] -> [Tree Vertex]
  +
</haskell>
  +
  +
Therefore to be denotative, a programming language only needs to be free of externally visible side-effects: namely those of I/O.
   
 
== Practical considerations ==
 
== Practical considerations ==
   
  +
But most users <i>want</i> to interact with their programs, including those written in denotative languages! That requires I/O to be managed by the denotative language's implementation, with parts of it written in a non-denotative (I/O-capable) programming language.
Of the three conditions, (c) is the most interesting from the viewpoint of an implementation: the absolute
 
  +
requirement for an expression to depend solely on the values of its subexpressions - not its ordering relative to other expressions, or any observable use of effects (e.g outside interactions) - requires an implementation to accomodate all the imperative mechanisms needed to make the use of denotative programs commonplace, in contrast to the current trend of moving the subsequent complexity out of the implementation into the language:
 
  +
This need for a non-denotative language though presents a dilemma:
  +
  +
* if it's too different from the denotative language, it's likely that users will [https://discourse.dhall-lang.org/t/proposal-do-notation-syntax/99 request] or [https://discourse.dhall-lang.org/t/a-failed-attempt-at-doing-io-with-dhall/565 attempt to add] an I/O mechanism to the denotative language (to avoid having to extend the implementation);
  +
  +
* if it's quite similar to the denotative language, users could do all their programming in it and simply ignore the denotative language (as writing a program in multiple languages is often tedious).
   
 
It remains to be seen whether these two opposing requirements can be reconciled.
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
For example, we want to have lazy file reading. If it cannot be implemented in Haskell then it will have to be implemented "underground" as a primitive operation written in C or machine code. The same goes for unique-supply trees and lazy arrays.<br>
 
''Implementing such operations in C does not make them more hygienic or easy to reason about.'' On the contrary, it is much easier to understand, modify and construct variants of them if they are implemented in a Haskell library module than if they are embedded in the runtime system.
 
   
<tt>[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.52.3656&rep=rep1&type=pdf State in Haskell], John Launchbury and Simon Peyton Jones (page 45).</tt>
 
</div>
 
   
  +
[[Category:Glossary]]
It remains to be seen whether the two seemingly-opposite approaches can be reconciled.
 

Latest revision as of 04:24, 5 April 2024

On page 8 of The Next 700 Programming Languages, Peter Landin introduces the word denotative as the direct opposite of the term imperative. To be considered denotative, Landin stipulates the following conditions for the expressions of a programming language:

(a) each expression has a nesting subexpression structure,
(b) each subexpression denotes something (usually a number, truth value or numerical function),
(c) the thing an expression denotes, i.e., its "value", depends only on the values of its subexpressions, not on other properties of them.

A programming language is trivially denotative if it only permits programs to be defined in terms of denotative expressions.

Being privately imperative

In 1994 researchers discovered a way to denotatively accommodate the imperative style by excluding the use of externally visible side-effects. For example:

type Graph = Array Vertex [Vertex]
data Tree a = Node a [Tree a]

dfs :: Graph -> [Vertex] -> [Tree Vertex]
dfs g vs = runST (
              newSTArray (bounds g) False >>= \ marks ->
              search marks vs
           )
  where search :: STArray s Vertex Bool -> [Vertex]
                  -> ST s [Tree Vertex]
        search marks   []   = return []
        search marks (v:vs) = readSTArray marks v >>= \ visited ->
                              if visited then
                                 search marks vs
                              else
                                writeSTArray marks v True >>
                                search marks (g!v)    >>= \ ts ->
                                search marks vs       >>= \ us ->
                                return ((Node v ts): us)
from Figure 2 (page 17 of 51) of State in Haskell

Even though dfs's local definition:

        search :: STArray s Vertex Bool -> [Vertex] -> ST s [Tree Vertex]

uses mutability and sequencing, the use of runST keeps those imperative details confined to dfs, hence its denotative type:

dfs :: Graph -> [Vertex] -> [Tree Vertex]

Therefore to be denotative, a programming language only needs to be free of externally visible side-effects: namely those of I/O.

Practical considerations

But most users want to interact with their programs, including those written in denotative languages! That requires I/O to be managed by the denotative language's implementation, with parts of it written in a non-denotative (I/O-capable) programming language.

This need for a non-denotative language though presents a dilemma:

  • if it's too different from the denotative language, it's likely that users will request or attempt to add an I/O mechanism to the denotative language (to avoid having to extend the implementation);
  • if it's quite similar to the denotative language, users could do all their programming in it and simply ignore the denotative language (as writing a program in multiple languages is often tedious).

It remains to be seen whether these two opposing requirements can be reconciled.