Quantcast

On the purity of Haskell

classic Classic list List threaded Threaded
129 messages Options
1234 ... 7
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

On the purity of Haskell

Steve Horne
This is just my view on whether Haskell is pure, being offered up for criticism. I haven't seen this view explicitly articulated anywhere before, but it does seem to be implicit in a lot of explanations - in particular the description of Monads in SBCs "Tackling the Awkward Squad". I'm entirely focused on the IO monad here, but aware that it's just one concrete case of an abstraction.

Warning - it may look like trolling at various points. Please keep going to the end before making a judgement.

To make the context explicit, there are two apparently conflicting viewpoints on Haskell...
  1. The whole point of the IO monad is to support programming with side-effecting actions - ie impurity.
  2. The IO monad is just a monad - a generic type (IO actions), a couple of operators (primarily return and bind) and some rules - within a pure functional language. You can't create impurity by taking a subset of a pure language.
My view is that both of these are correct, each from a particular point of view. Furthermore, by essentially the same arguments, C is also both an impure language and a pure one.

See what I mean about the trolling thing? I'm actually quite serious about this, though - and by the end I think Haskell advocates will generally approve.

First assertion... Haskell is a pure functional language, but only from the compile-time point of view. The compiler manipulates and composes IO actions (among other things). The final resulting IO actions are finally swallowed by unsafePerformIO or returned from main. However, Haskell is an impure side-effecting language from the run-time point of view - when the composed actions are executed. Impurity doesn't magically spring from the ether - it results from the translation by the compiler of IO actions to executable code and the execution of that code.

In this sense, IO actions are directly equivalent to the AST nodes in a C compiler. A C compiler can be written in a purely functional way - in principle it's just a pure function that accepts a string (source code) and returns another string (executable code). I'm fudging issues like separate compilation and #include, but all of these can be resolved in principle in a pure functional way. Everything a C compiler does at compile time is therefore, in principle, purely functional.

In fact, in the implementation of Haskell compilers, IO actions almost certainly *are* ASTs. Obviously there's some interesting aspects to that such as all the partially evaluated and unevaluated functions. But even a partially evaluated function has a representation within a compiler that can be considered an AST node, and even AST nodes within a C compiler may represent partially evaluated functions.

Even the return and bind operators are there within the C compiler in a sense, similar to the do notation in Haskell. Values are converted into actions. Actions are sequenced. Though the more primitive form isn't directly available to the programmer, it could easily be explicitly present within the compiler.

What about variables? What about referential transparency?

Well, to a compiler writer (and equally for this argument) an identifier is not the same thing as the variable it references.

One way to model the situation is that for every function in a C program, all explicit parameters are implicitly within the IO monad. There is one implicit parameter too - a kind of IORef to the whole system memory. Identifiers have values which identify where the variable is within the big implicit IORef. So all the manipulation of identifiers and their reference-like values is purely functional. Actual handling of variables stored within the big implicit IORef is deferred until run-time.

So once you accept that there's an implicit big IORef parameter to every function, by the usual definition of referential transparency, C is as transparent as Haskell. The compile-time result of each function is completely determined by its (implicit and explicit) parameters - it's just that that result is typically a way to look up the run-time result within the big IORef later.

What's different about Haskell relative to C therefore...
  1. The style of the "AST" is different. It still amounts to the same thing in this argument, but the fact that most AST nodes are simply partially-evaluated functions has significant practical consequences, especially with laziness mixed in too. There's a deep connection between the compile-time and run-time models (contrast C++ templates).
  2. The IO monad is explicit in Haskell - side-effects are only permitted (even at run-time) where the programmer has explicitly opted to allow them.
  3. IORefs are explicit in Haskell - instead of always having one you can have none, one or many. This is relevant to an alternative definition of referential transparency. Politicians aren't considered transparent when they bury the relevant in a mass of the irrelevant, and even pure functions can be considered to lack transparency in that sense. Haskell allows (and encourages) you to focus in on the relevant - to reference an IORef Bool or an IORef Int rather than dealing with an IORef Everything.
That last sentence of the third point is my most recent eureka - not so long ago I posted a "Haskell is just using misleading definitions - it's no more transparent than C" rant, possibly on Stack Overflow. Wrong again :-(

So - what do you think?


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

AUGER Cédric
Le Wed, 28 Dec 2011 17:39:52 +0000,
Steve Horne <[hidden email]> a écrit :

> This is just my view on whether Haskell is pure, being offered up for
> criticism. I haven't seen this view explicitly articulated anywhere
> before, but it does seem to be implicit in a lot of explanations - in
> particular the description of Monads in SBCs "Tackling the Awkward
> Squad". I'm entirely focused on the IO monad here, but aware that
> it's just one concrete case of an abstraction.
>
> Warning - it may look like trolling at various points. Please keep
> going to the end before making a judgement.

It is not yet a troll for me as I am a newbie ^^

> To make the context explicit, there are two apparently conflicting
> viewpoints on Haskell...
>
>  1. The whole point of the IO monad is to support programming with
>     side-effecting actions - ie impurity.
>  2. The IO monad is just a monad - a generic type (IO actions), a
> couple of operators (primarily return and bind) and some rules -
> within a pure functional language. You can't create impurity by
> taking a subset of a pure language.
>
> My view is that both of these are correct, each from a particular
> point of view. Furthermore, by essentially the same arguments, C is
> also both an impure language and a pure one.
>
> See what I mean about the trolling thing? I'm actually quite serious
> about this, though - and by the end I think Haskell advocates will
> generally approve.
>
> First assertion... Haskell is a pure functional language, but only
> from the compile-time point of view. The compiler manipulates and
> composes IO actions (among other things). The final resulting IO
> actions are finally swallowed by unsafePerformIO or returned from
> main. However, Haskell is an impure side-effecting language from the
> run-time point of view - when the composed actions are executed.
> Impurity doesn't magically spring from the ether - it results from
> the translation by the compiler of IO actions to executable code and
> the execution of that code.
>
> In this sense, IO actions are directly equivalent to the AST nodes in
> a C compiler. A C compiler can be written in a purely functional way
> - in principle it's just a pure function that accepts a string
> (source code) and returns another string (executable code). I'm
> fudging issues like separate compilation and #include, but all of
> these can be resolved in principle in a pure functional way.
> Everything a C compiler does at compile time is therefore, in
> principle, purely functional.
>
> In fact, in the implementation of Haskell compilers, IO actions
> almost certainly *are* ASTs. Obviously there's some interesting
> aspects to that such as all the partially evaluated and unevaluated
> functions. But even a partially evaluated function has a
> representation within a compiler that can be considered an AST node,
> and even AST nodes within a C compiler may represent partially
> evaluated functions.
>
> Even the return and bind operators are there within the C compiler in
> a sense, similar to the do notation in Haskell. Values are converted
> into actions. Actions are sequenced. Though the more primitive form
> isn't directly available to the programmer, it could easily be
> explicitly present within the compiler.
>
> What about variables? What about referential transparency?
>
> Well, to a compiler writer (and equally for this argument) an
> identifier is not the same thing as the variable it references.
>
> One way to model the situation is that for every function in a C
> program, all explicit parameters are implicitly within the IO monad.
> There is one implicit parameter too - a kind of IORef to the whole
> system memory. Identifiers have values which identify where the
> variable is within the big implicit IORef. So all the manipulation of
> identifiers and their reference-like values is purely functional.
> Actual handling of variables stored within the big implicit IORef is
> deferred until run-time.
>
> So once you accept that there's an implicit big IORef parameter to
> every function, by the usual definition of referential transparency,
> C is as transparent as Haskell. The compile-time result of each
> function is completely determined by its (implicit and explicit)
> parameters - it's just that that result is typically a way to look up
> the run-time result within the big IORef later.

Now as you ask here is my point of view:

IO monad doesn't make the language impure for me, since you can give
another implementation which is perfectly pure and which has the same
behaviour (although completely unrealistic):

-- An alternative to IO monad

data IO_ = IO_
  { systemFile :: String -> String -- ^ get the contents of the file
  , handlers :: Handler -> (Int, String)
    -- ^ get the offset of the handler
  }

type IO a = IO { trans :: IO_ -> (a, IO_) }

instance Monad IO where
  bind m f = IO { trans = \io1 -> let (a, io2) = trans m io1 in
                                  trans (f a) io2
                }
  return a = IO { trans = \io_ -> (a, io_) }

-- An example: hGetChar

hGetChar ::  Handler -> IO Char
hGetChar h =
  IO { trans = \io_ -> let (pos, file) = handlers io_ h
                           c = (systemFile file) !! pos
                           newHandlers = \k -> if h==k
                                               then (1+pos, file)
                                               else handlers io_ k
                       in (c, IO_ {systemFile = systemFile io_
                                  ,handlers = newHandlers})
     }

Now how would this work?
In a first time, you load all your system file before running the
program (a "side-effect" which does not modify already used structures;
it is just initialization), then you run the program in a perfectly
pure way, and at the end you commit all to the system file (so you
modify structures the running program won't access as it has
terminated).

I am too lazy (like many Haskellers) to show how to implement the other
IO functions; but if you believe me you can do it, you can see that I
haven't done any side effect.
Now the "main" function has type: IO (), that is it is an encapsulated
function of type : IO_ -> ((), IO_); in other words, simply an
environment transformation. main really is a pure function.

It is quite different from C, as in Haskell you need to provide the
environment in which it is executed (stored in the type IO_); in C, it
is not the case
"read(5/*file descriptor, that is an int which cannot contain an
         environment*/,
      buff/*buffer (int*), cannot contain an environment*/,
      len/*size (int) to be read, cannot contain an environment*/);
 /*return type: void, cannot contain an environment*/"
but this expression modifies an environment, which is the side effect.
To make C pure, we need to say that all function calls are implicitly
given an environment, but in C we have no way to expose this
environment, and the typing system doesn't tell us if the call of the
function only access or also modifies the environment.

In haskell, the environment is explicitely passed to each function
(although the 'do' notations tends to hide it, but it is only syntactic
sugar), so by reading the signature and unfolding all
"data/type/newtype" definitions, we can tell if there is or not side
effect; for C, we must unfold 'terms' to understand that.

In Haskell,
'hGetChar h >>= \c -> hPutChar i' always has the same value, but
'trans (hGetChar h >>= \c -> hPutChar i) (IO_ A)'
'trans (hGetChar h >>= \c -> hPutChar i) (IO_ B)'
may have different values according to A and B.

In C, you cannot express this distinction, since you only have:
'read(h, &c, 1); write(i, &c, 1);' and cannot pass explicitely the
environment.

As you can see my point of view is really different, as I do not think
in the "AST way" (even if I have always heard of it, and think it is
another good way to think of the monads; in fact the 2 point of view
are the same for me, but express differently).

> What's different about Haskell relative to C therefore...
>
>  1. The style of the "AST" is different. It still amounts to the same
>     thing in this argument, but the fact that most AST nodes are
> simply partially-evaluated functions has significant practical
>     consequences, especially with laziness mixed in too. There's a
> deep connection between the compile-time and run-time models (contrast
>     C++ templates).
>  2. The IO monad is explicit in Haskell - side-effects are only
>     permitted (even at run-time) where the programmer has explicitly
>     opted to allow them.
>  3. IORefs are explicit in Haskell - instead of always having one you
>     can have none, one or many. This is relevant to an alternative
>     definition of referential transparency. Politicians aren't
>     considered transparent when they bury the relevant in a mass of
> the irrelevant, and even pure functions can be considered to lack
>     transparency in that sense. Haskell allows (and encourages) you to
>     focus in on the relevant - to reference an IORef Bool or an IORef
>     Int rather than dealing with an IORef Everything.
>
> That last sentence of the third point is my most recent eureka - not
> so long ago I posted a "Haskell is just using misleading definitions
> - it's no more transparent than C" rant, possibly on Stack Overflow.
> Wrong again :-(
>
> So - what do you think?
>

my 2 cents

NB. I encountered a situation where my vision of things was broken:

hGetContents :: Handle -> IO ByteString

in Data.ByteString.Lazy as BS

The following two programs

BS.hGetContents h >>= \b -> close h >> let x = BS.length b in print x
-- ^ Dangerous, never do it!

BS.hGetContents h >>= \b -> let x = BS.length b in close h >> print x
-- ^ The right way to do

is a problem for me, as BS.length doesn't receive an environment as a
parameter, so as they are given the SAME b, they should give the same
result. (For me all BS.ByteString operations should be done inside a
Monad; the non-lazy ByteString doesn't have this problem of course)

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Heinrich Apfelmus
In reply to this post by Steve Horne
Steve Horne wrote:

> This is just my view on whether Haskell is pure, being offered up for
> criticism. I haven't seen this view explicitly articulated anywhere
> before, but it does seem to be implicit in a lot of explanations - in
> particular the description of Monads in SBCs "Tackling the Awkward
> Squad". I'm entirely focused on the IO monad here, but aware that it's
> just one concrete case of an abstraction.
>
> Warning - it may look like trolling at various points. Please keep going
> to the end before making a judgement.
>
> To make the context explicit, there are two apparently conflicting
> viewpoints on Haskell...
>
> 1. The whole point of the IO monad is to support programming with
>    side-effecting actions - ie impurity.
> 2. The IO monad is just a monad - a generic type (IO actions), a couple
>    of operators (primarily return and bind) and some rules - within a
>    pure functional language. You can't create impurity by taking a
>    subset of a pure language.
>
> My view is that both of these are correct, each from a particular point
> of view. Furthermore, by essentially the same arguments, C is also both
> an impure language and a pure one. [...]

Purity has nothing to do with the question of whether you can express IO
in Haskell or not.

The word "purity" refers to the fact that applying a value

    foo :: Int -> Int

(a "function") to another value *always* evaluates to the same result.
This is true in Haskell and false in C.

The beauty of the IO monad is that it doesn't change anything about
purity. Applying the function

    bar :: Int -> IO Int

to the value 2 will always give the same result:

    bar 2 = bar (1+1) = bar (5-3)

Of course, the point is that this result is an *IO action* of type  IO
Int , it's not the  Int  you would get "when executing this action".


Best regards,
Heinrich Apfelmus

--
http://apfelmus.nfshost.com


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
On 28/12/2011 20:44, Heinrich Apfelmus wrote:

> Steve Horne wrote:
>> This is just my view on whether Haskell is pure, being offered up for
>> criticism. I haven't seen this view explicitly articulated anywhere
>> before, but it does seem to be implicit in a lot of explanations - in
>> particular the description of Monads in SBCs "Tackling the Awkward
>> Squad". I'm entirely focused on the IO monad here, but aware that
>> it's just one concrete case of an abstraction.
>>
>> Warning - it may look like trolling at various points. Please keep
>> going to the end before making a judgement.
>>
>> To make the context explicit, there are two apparently conflicting
>> viewpoints on Haskell...
>>
>> 1. The whole point of the IO monad is to support programming with
>>    side-effecting actions - ie impurity.
>> 2. The IO monad is just a monad - a generic type (IO actions), a couple
>>    of operators (primarily return and bind) and some rules - within a
>>    pure functional language. You can't create impurity by taking a
>>    subset of a pure language.
>>
>> My view is that both of these are correct, each from a particular
>> point of view. Furthermore, by essentially the same arguments, C is
>> also both an impure language and a pure one. [...]
>
> Purity has nothing to do with the question of whether you can express
> IO in Haskell or not.
>
...

> The beauty of the IO monad is that it doesn't change anything about
> purity. Applying the function
>
>    bar :: Int -> IO Int
>
> to the value 2 will always give the same result:
>
Yes - AT COMPILE TIME by the principle of referential transparency it
always returns the same action. However, the whole point of that action
is that it might potentially be executed (with potentially
side-effecting results) at run-time. Pure at compile-time, impure at
run-time. What is only modeled at compile-time is realized at run-time,
side-effects included.

Consider the following...

#include <stdio.h>

int main (int argc, char*argv)
{
   char c;
   c = getchar ();
   putchar (c);
   return 0;
}

The identifier c is immutable. We call it a variable, but the
compile-time value of c is really just some means to find the actual
value in the "big implicit IORef" at runtime - an offset based on the
stack pointer or whatever. Nothing mutates until compile-time, and when
that happens, the thing that mutates (within that "big implicit IORef")
is separate from that compile-time value of c.

In C and in Haskell - the side-effects are real, and occur at run-time.

That doesn't mean Haskell is as bad as C - I get to the advantages of
Haskell at the end of my earlier post. Mostly unoriginal, but I think
the bit about explicit vs. implicit IORefs WRT an alternate view of
transparency is worthwhile.

I hope If convinced you I'm not making one of the standard newbie
mistakes. I've done all that elsewhere before, but not today, honest.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Jerzy Karczmarczuk
Le 28/12/2011 22:45, Steve Horne a écrit :
Yes - AT COMPILE TIME by the principle of referential transparency it always returns the same action. However, the whole point of that action is that it might potentially be executed (with potentially side-effecting results) at run-time. Pure at compile-time, impure at run-time. What is only modeled at compile-time is realized at run-time, side-effects included.
(...)

I hope If convinced you I'm not making one of the standard newbie mistakes. I've done all that elsewhere before, but not today, honest.
Sorry, perhaps this is not a standard newbie mistake, but you - apparently - believe that an execution of an action on the "real world" is a side effect.

I don't think it is.
Even if a Haskell programme fires an atomic bomb, a very impure one, there are no side effects within the programme itself.
If you disagree, show them.

I don't think that speaking about "compile-time purity" is correct.

Jerzy Karczmarczuk



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
On 28/12/2011 22:01, Jerzy Karczmarczuk wrote:
Le 28/12/2011 22:45, Steve Horne a écrit :
Yes - AT COMPILE TIME by the principle of referential transparency it always returns the same action. However, the whole point of that action is that it might potentially be executed (with potentially side-effecting results) at run-time. Pure at compile-time, impure at run-time. What is only modeled at compile-time is realized at run-time, side-effects included.
(...)

I hope If convinced you I'm not making one of the standard newbie mistakes. I've done all that elsewhere before, but not today, honest.
Sorry, perhaps this is not a standard newbie mistake, but you - apparently - believe that an execution of an action on the "real world" is a side effect.

I don't think it is.
Even if a Haskell programme fires an atomic bomb, a very impure one, there are no side effects within the programme itself.
True. But side-effects within the program itself are not the only relevant side-effects.

As Simon Baron-Cohen says in "Tackling the Awkward Squad"...
Yet the ultimate purpose of running a program is invariably to cause some side effect: a changed file, some new pixels on the screen, a message sent, or whatever. Indeed it’s a bit cheeky to call input/output “awkward” at all. I/O is the raison d’ˆetre of every program. — a program that had no observable effect whatsoever (no input, no output) would not be very useful.
Of course he then says...
Well, if the side effect can’t be in the functional program, it will have to be outside it.
Well, to me, that's a bit cheeky too - at least if taken overliterally. Even if you consider a mutation of an IORef to occur outside the program, it affects the later run-time behaviour of the program. The same with messages sent to stdout - in this case, the user is a part of the feedback loop, but the supposedly outside-the-program side-effect still potentially affects the future behaviour of the program when it later looks at stdin.

A key point of functional programming (including its definitions of side-effects and referential transparency) is about preventing bugs by making code easier to reason about.

Saying that the side-effects are outside the program is fine from a compile-time compositing-IO-actions point of view. But as far as understanding the run-time behaviour of the program is concerned, that claim really doesn't change anything. The side-effects still occur, and they still affect the later behaviour of the program. Declaring that they're outside the program doesn't make the behaviour of that program any easier to reason about, and doesn't prevent bugs.

A final SBC quote, still from "Tackling the Awkward Squad"...
There is a clear distinction, enforced by the type system, between actions which may have
side effects, and functions which may not.
SBC may consider the side-effects to be outside the program, but he still refers to "actions which may have side-effects". The side-effects are still there, whether you consider them inside or outside the program, and as a programmer you still have to reason about them.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Bernie Pope-2
On 29 December 2011 10:51, Steve Horne <[hidden email]> wrote:

> As Simon Baron-Cohen says in "Tackling the Awkward Squad"...

I think you've mixed up your Simons; that should be Simon Peyton Jones.

Cheers,
Bernie.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
On 28/12/2011 23:56, Bernie Pope wrote:
> On 29 December 2011 10:51, Steve Horne<[hidden email]>  wrote:
>
>> As Simon Baron-Cohen says in "Tackling the Awkward Squad"...
> I think you've mixed up your Simons; that should be Simon Peyton Jones.
>
Oops - sorry about that.

FWIW - I'm diagnosed Aspergers. SBC diagnosed me back in 2001, shortly
after 9/1/1.

Yes, I *am* pedantic - which doesn't always mean right, of course.

Not relevant, but what the hell.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Thiago Negri

We can do functional programming on Java. We use all the design patterns for that.

At the very end, everything is just some noisy, hairy, side-effectfull, gotofull machinery code.

The beauty of Haskell is that it allows you to limit the things you need to reason about. If I see a function with the type "(a, b) -> a" I don't need to read a man page to see where I should use it or not. I know what it can do by its type. In C I can not do this. What can I say about a function "int foo(char* bar)"? Does it allocate memory? Does it asks a number for the user on stdin? Or does it returns the length of a zero-ending char sequence? In fact it can do anything, and I can't forbid that. I can't guarantee that my function has good behaviour. You need to trust the man page.

Em 28/12/2011 22:24, "Steve Horne" <[hidden email]> escreveu:
On 28/12/2011 23:56, Bernie Pope wrote:
On 29 December 2011 10:51, Steve Horne<[hidden email]>  wrote:

As Simon Baron-Cohen says in "Tackling the Awkward Squad"...
I think you've mixed up your Simons; that should be Simon Peyton Jones.

Oops - sorry about that.

FWIW - I'm diagnosed Aspergers. SBC diagnosed me back in 2001, shortly after 9/1/1.

Yes, I *am* pedantic - which doesn't always mean right, of course.

Not relevant, but what the hell.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
In reply to this post by AUGER Cédric
Sorry for the delay. I've written a couple of long replies already, and
both times when I'd finished deleting all the stupid stuff there was
nothing left - it seems I'm so focussed on my own view, I'm struggling
with anything else today. Maybe a third try...

On 28/12/2011 19:38, AUGER Cédric wrote:
> Le Wed, 28 Dec 2011 17:39:52 +0000,
> Steve Horne<[hidden email]>  a écrit :
>
>> This is just my view on whether Haskell is pure, being offered up for
>> criticism. I haven't seen this view explicitly articulated anywhere
>> before, but it does seem to be implicit in a lot of explanations - in
>> particular the description of Monads in SBCs "Tackling the Awkward
>> Squad". I'm entirely focused on the IO monad here, but aware that
>> it's just one concrete case of an abstraction.

> IO monad doesn't make the language impure for me, since you can give
> another implementation which is perfectly pure and which has the same
> behaviour (although completely unrealistic):

> Now how would this work?
> In a first time, you load all your system file before running the
> program (a "side-effect" which does not modify already used structures;
> it is just initialization), then you run the program in a perfectly
> pure way, and at the end you commit all to the system file (so you
> modify structures the running program won't access as it has
> terminated).
I don't see how interactivity fits that model. If a user provides input
in response to an on-screen prompt, you can't do all the input at the
start (before the prompt is delayed) and you can't do all the output at
the end.

Other than that, I'm OK with that. In fact if you're writing a compiler
that way, it seems fine - you can certainly delay output of the
generated object code until the end of the compilation, and the input
done at the start of the compilation (source files) is separate from the
run-time prompt-and-user-input thing.

See - I told you I'm having trouble seeing things in terms of someone
elses model - I'm back to my current obsession again here.
> In Haskell,
> 'hGetChar h>>= \c ->  hPutChar i' always has the same value, but
> 'trans (hGetChar h>>= \c ->  hPutChar i) (IO_ A)'
> 'trans (hGetChar h>>= \c ->  hPutChar i) (IO_ B)'
> may have different values according to A and B.
>
> In C, you cannot express this distinction, since you only have:
> 'read(h,&c, 1); write(i,&c, 1);' and cannot pass explicitely the
> environment.
Agreed. Haskell is definitely more powerful in that sense.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Antoine Latter-2
In reply to this post by Steve Horne
On Wed, Dec 28, 2011 at 3:45 PM, Steve Horne
<[hidden email]> wrote:

> On 28/12/2011 20:44, Heinrich Apfelmus wrote:
>>
>> Steve Horne wrote:
>>>
>>> This is just my view on whether Haskell is pure, being offered up for
>>> criticism. I haven't seen this view explicitly articulated anywhere before,
>>> but it does seem to be implicit in a lot of explanations - in particular the
>>> description of Monads in SBCs "Tackling the Awkward Squad". I'm entirely
>>> focused on the IO monad here, but aware that it's just one concrete case of
>>> an abstraction.
>>>
>>> Warning - it may look like trolling at various points. Please keep going
>>> to the end before making a judgement.
>>>
>>> To make the context explicit, there are two apparently conflicting
>>> viewpoints on Haskell...
>>>
>>> 1. The whole point of the IO monad is to support programming with
>>>   side-effecting actions - ie impurity.
>>> 2. The IO monad is just a monad - a generic type (IO actions), a couple
>>>   of operators (primarily return and bind) and some rules - within a
>>>   pure functional language. You can't create impurity by taking a
>>>   subset of a pure language.
>>>
>>> My view is that both of these are correct, each from a particular point
>>> of view. Furthermore, by essentially the same arguments, C is also both an
>>> impure language and a pure one. [...]
>>
>>
>> Purity has nothing to do with the question of whether you can express IO
>> in Haskell or not.
>>
> ...
>
>
>> The beauty of the IO monad is that it doesn't change anything about
>> purity. Applying the function
>>
>>   bar :: Int -> IO Int
>>
>> to the value 2 will always give the same result:
>>
> Yes - AT COMPILE TIME by the principle of referential transparency it always
> returns the same action. However, the whole point of that action is that it
> might potentially be executed (with potentially side-effecting results) at
> run-time. Pure at compile-time, impure at run-time. What is only modeled at
> compile-time is realized at run-time, side-effects included.
>

I don't think I would put it that way - the value 'bar 2' is a regular
Haskell value. I can put it in a list, return it from a function and
all other things:

myIOActions :: [IO Int]
myIOActions = [bar 2, bar (1+1), bar (5-3)]

And I can pick any of the elements of the list to execute in my main
function, and I get the same main function either way.

> Consider the following...
>
> #include <stdio.h>
>
> int main (int argc, char*argv)
> {
>  char c;
>  c = getchar ();
>  putchar (c);
>  return 0;
> }
>
> The identifier c is immutable. We call it a variable, but the compile-time
> value of c is really just some means to find the actual value in the "big
> implicit IORef" at runtime - an offset based on the stack pointer or
> whatever. Nothing mutates until compile-time, and when that happens, the
> thing that mutates (within that "big implicit IORef") is separate from that
> compile-time value of c.
>
> In C and in Haskell - the side-effects are real, and occur at run-time.
>
> That doesn't mean Haskell is as bad as C - I get to the advantages of
> Haskell at the end of my earlier post. Mostly unoriginal, but I think the
> bit about explicit vs. implicit IORefs WRT an alternate view of transparency
> is worthwhile.
>
> I hope If convinced you I'm not making one of the standard newbie mistakes.
> I've done all that elsewhere before, but not today, honest.
>
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/haskell-cafe

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
In reply to this post by Thiago Negri
On 29/12/2011 00:57, Thiago Negri wrote:

>
> We can do functional programming on Java. We use all the design
> patterns for that.
>
> At the very end, everything is just some noisy, hairy,
> side-effectfull, gotofull machinery code.
>
> The beauty of Haskell is that it allows you to limit the things you
> need to reason about. If I see a function with the type "(a, b) -> a"
> I don't need to read a man page to see where I should use it or not. I
> know what it can do by its type. In C I can not do this. What can I
> say about a function "int foo(char* bar)"? Does it allocate memory?
> Does it asks a number for the user on stdin? Or does it returns the
> length of a zero-ending char sequence? In fact it can do anything, and
> I can't forbid that. I can't guarantee that my function has good
> behaviour. You need to trust the man page.
>
Well, I did say (an unoriginal point) that "The IO monad is explicit in
Haskell - side-effects are only permitted (even at run-time) where the
programmer has explicitly opted to allow them.". So yes.

The "it could do anything!!!" claims are over the top and IMO
counterproductive, though. The type system doesn't help the way it does
in Haskell, but nevertheless, plenty of people reason about the
side-effects in C mostly-successfully.

Mostly /= always, but bugs can occur in any language.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Steve Horne
In reply to this post by Antoine Latter-2
On 29/12/2011 01:53, Antoine Latter wrote:

> The beauty of the IO monad is that it doesn't change anything about
> purity. Applying the function
>
>    bar :: Int ->  IO Int
>
> to the value 2 will always give the same result:
>
>> Yes - AT COMPILE TIME by the principle of referential transparency it always
>> returns the same action. However, the whole point of that action is that it
>> might potentially be executed (with potentially side-effecting results) at
>> run-time. Pure at compile-time, impure at run-time. What is only modeled at
>> compile-time is realized at run-time, side-effects included.
>>
> I don't think I would put it that way - the value 'bar 2' is a regular
> Haskell value. I can put it in a list, return it from a function and
> all other things:
>
> myIOActions :: [IO Int]
> myIOActions = [bar 2, bar (1+1), bar (5-3)]
>
> And I can pick any of the elements of the list to execute in my main
> function, and I get the same main function either way.
Yes - IO actions are first class values in Haskell. They can be derived
using all the functional tools of the language. But if this points out a
flaw in my logic, it's only a minor issue in my distinction between
compile-time and run-time.

Basically, there is a phase when a model has been constructed
representing the source code. This model is similar in principle to an
AST, though primarily (maybe entirely?) composed of unevaluated
functions rather than node-that-represents-whatever structs. This phase
*must* be completed during compilation. Of course evaluation of some
parts of the model can start before even parsing is complete, but that's
just implementation detail.

Some reductions (if that's the right term for a single evaluation step)
of that model cannot be applied until run-time because of the dependence
on run-time inputs. Either the reduction implies the execution of an IO
action, or an argument has a data dependency on an IO action.

Many reductions can occur either at compile-time or run-time.

In your list-of-actions example, the list is not an action itself, but
it's presumably a part of the expression defining main which returns an
IO action. The evaluation of the expression to select an action may have
to be delayed until run-time with the decision being based on run-time
input. The function that does the selection is still pure. Even so, this
evaluation is part of the potentially side-effecting evaluation and
execution of the main IO action. Overall, the run-time execution is
impure - a single side-effect is enough.

So... compile-time pure, run-time impure.


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Heinrich Apfelmus
In reply to this post by Steve Horne
Steve Horne wrote:

> Heinrich Apfelmus wrote:
>>
>> Purity has nothing to do with the question of whether you can express
>> IO in Haskell or not.
>>
> ....
>
>> The beauty of the IO monad is that it doesn't change anything about
>> purity. Applying the function
>>
>>    bar :: Int -> IO Int
>>
>> to the value 2 will always give the same result:
>>
> Yes - AT COMPILE TIME by the principle of referential transparency it
> always returns the same action. However, the whole point of that action
> is that it might potentially be executed (with potentially
> side-effecting results) at run-time. Pure at compile-time, impure at
> run-time. What is only modeled at compile-time is realized at run-time,
> side-effects included.

Well, it's a matter of terminology: "impure" /= "has side effects". The
ability of a language to describe side effects is not tied to its
(im)purity.

Again, purity refers to the semantics of functions (at run-time): given
the same argument, will a function always return the same result? The
answer to this question solely decides whether the language is pure or
impure. Note that this depends on the meaning of "function" within that
language. In C, side-effects are part of the semantics of functions, so
it's an impure language. In Haskell, on the other hand, functions will
always return the same result, so the language is pure. You could say
that side effects have been moved from functions to some other type
(namely IO) in Haskell.


Best regards,
Heinrich Apfelmus

--
http://apfelmus.nfshost.com


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Jerzy Karczmarczuk
Sorry, a long and pseudo-philosophical treatise. Trash it before reading.

Heinrich Apfelmus:
> You could say that side effects have been moved from functions to some
> other type (namely IO) in Haskell.
I have no reason to be categorical, but I believe that calling the
interaction of a Haskell programme with the World - a "side effect" is
sinful, and it is a source of semantical trouble.

People do it, SPJ (cited by S. Horne) did it as well, and this is too bad.
People, when you eat a sandwich: are you doing "side effects"??  If you
break a tooth on it, this IS a side effect, but neither the eating nor
digesting it, seems to be one.

This term should be used in a way compatible with its original meaning,
that something happens implicitly, "behind the curtain", specified most
often in an informal way (not always deserving to be called
"operational"). If you call all the assignments "side effects", why not
call -  let x = whatever in Something - also a "local side-effect"?
Oh, that you can often transform let in the application of lambda, thus
purely functional?

Doesn't matter, Steve Horne will explain you that (sorry for the irony):
"let is a compile-time pure construct ; at execution this is impure,
because x got a value".

S.H. admits that he reasons within his model, and has problems with
others. Everybody has such problems, but I see here something the (true)
Frenchies call "un dialogue de sourds". For me a Haskell programme is
ABSOLUTELY pure, including the IO. The issue is that `bind` within the
IO monad has an implicit parameter, the World. In fact, a stream of
Worlds, every putWhatever, getLine, etc. passes to a new instance.

We do not control this World, we call it "imperative" (whatever this
means, concerning eating a sandwich, or exploding an impure neutron
bomb), so we abuse the term "side effect" as hell!
The "Haskell sector" of the global world, the programme itself is just a
function. Pure as the robe of an angel. Simply, you are not allowed by
the Holy Scripts to look under this robe.

==

The rest is a (pure of course) délire.
Well, you might not believe me, but philosophically you don't need to
imagine the World as imperative. Personally I am a believer in the
Quantum Religion. If you accept all them Holy Dogmas of Unitarity, of
Linearity, etc., if you believe in the True Quantum Nature of the real
world, - then it becomes ... functional. Pure.
  Without a single trace of any "side effects".

The problem is that residing inside this world precludes the possibility
of considering *observed things* as pure, they are conceptually detached
from the stream of the Universe Vectors. They "change", so you say:
HaHa!! A particle got ASSIGNED a new position! This is an imperative
side-effect! -
- while from the point of view of an external observer, a common
evolution operator transformed both of you, YOU and the particle into a
new instance of this sector.

OK, I stop here, otherwise the digestion of your sandwiches may produce
some side effects.

Jerzy Karczmarczuk
Caen, France.
(William the Conqueror was here. Produced one nice side-effect.)




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Gregg Reynolds-4
In reply to this post by Heinrich Apfelmus
On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus
<[hidden email]> wrote:
>
> The beauty of the IO monad is that it doesn't change anything about purity.
> Applying the function
>
>   bar :: Int -> IO Int
>
> to the value 2 will always give the same result:
>
>   bar 2 = bar (1+1) = bar (5-3)

Strictly speaking, that doesn't sound right.  The "result" of an IO
operation is outside of the control (and semantics) of the Haskell
program, so Haskell has no idea what it will be.  Within the program,
there is no result.  So Int -> IO Int is not really a function - it
does not map a determinate input to a determinate output.  The IO
monad just makes it look and act like a function, sort of, but what it
really does is provide reliable ordering of non-functional operations
- invariant order, not invariant results.

To respond to original post: no language that supports IO can be
purely functional in fact, but with clever design it can mimic a
purely functional language.

Cheers

Gregg

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Iustin Pop-2
On Thu, Dec 29, 2011 at 07:19:17AM -0600, Gregg Reynolds wrote:

> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus
> <[hidden email]> wrote:
> >
> > The beauty of the IO monad is that it doesn't change anything about purity.
> > Applying the function
> >
> >   bar :: Int -> IO Int
> >
> > to the value 2 will always give the same result:
> >
> >   bar 2 = bar (1+1) = bar (5-3)
>
> Strictly speaking, that doesn't sound right.  The "result" of an IO
> operation is outside of the control (and semantics) of the Haskell
> program, so Haskell has no idea what it will be.  Within the program,
> there is no result.  So Int -> IO Int is not really a function - it
> does not map a determinate input to a determinate output.  The IO
> monad just makes it look and act like a function, sort of, but what it
> really does is provide reliable ordering of non-functional operations
> - invariant order, not invariant results.

Not only strictly speaking. In practice too:

bar _ = do
   s <- readFile "/tmp/x.txt"
   return (read s)

Once you're in a monad that has 'state', the return value doesn't
strictly depend anymore on the function arguments.

At least that's my understanding.

regards,
iustin

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Edward Z. Yang
In reply to this post by Steve Horne
Here's an alternative perspective to consider: consider some
data structure, such as a queue.  There are two ways you can
implement this, one the imperative way, with mutators, and the
other the purely functional way, with no destructive updates.

The question then, I ask, is how easy does a programming language
make it to write the data structure in the latter fashion?  How
easy is it for you to cheat?

Edward

Excerpts from Steve Horne's message of Wed Dec 28 12:39:52 -0500 2011:

> This is just my view on whether Haskell is pure, being offered up for
> criticism. I haven't seen this view explicitly articulated anywhere
> before, but it does seem to be implicit in a lot of explanations - in
> particular the description of Monads in SBCs "Tackling the Awkward
> Squad". I'm entirely focused on the IO monad here, but aware that it's
> just one concrete case of an abstraction.
>
> Warning - it may look like trolling at various points. Please keep going
> to the end before making a judgement.
>
> To make the context explicit, there are two apparently conflicting
> viewpoints on Haskell...
>
>  1. The whole point of the IO monad is to support programming with
>     side-effecting actions - ie impurity.
>  2. The IO monad is just a monad - a generic type (IO actions), a couple
>     of operators (primarily return and bind) and some rules - within a
>     pure functional language. You can't create impurity by taking a
>     subset of a pure language.
>
> My view is that both of these are correct, each from a particular point
> of view. Furthermore, by essentially the same arguments, C is also both
> an impure language and a pure one.
>
> See what I mean about the trolling thing? I'm actually quite serious
> about this, though - and by the end I think Haskell advocates will
> generally approve.
>
> First assertion... Haskell is a pure functional language, but only from
> the compile-time point of view. The compiler manipulates and composes IO
> actions (among other things). The final resulting IO actions are finally
> swallowed by unsafePerformIO or returned from main. However, Haskell is
> an impure side-effecting language from the run-time point of view - when
> the composed actions are executed. Impurity doesn't magically spring
> from the ether - it results from the translation by the compiler of IO
> actions to executable code and the execution of that code.
>
> In this sense, IO actions are directly equivalent to the AST nodes in a
> C compiler. A C compiler can be written in a purely functional way - in
> principle it's just a pure function that accepts a string (source code)
> and returns another string (executable code). I'm fudging issues like
> separate compilation and #include, but all of these can be resolved in
> principle in a pure functional way. Everything a C compiler does at
> compile time is therefore, in principle, purely functional.
>
> In fact, in the implementation of Haskell compilers, IO actions almost
> certainly *are* ASTs. Obviously there's some interesting aspects to that
> such as all the partially evaluated and unevaluated functions. But even
> a partially evaluated function has a representation within a compiler
> that can be considered an AST node, and even AST nodes within a C
> compiler may represent partially evaluated functions.
>
> Even the return and bind operators are there within the C compiler in a
> sense, similar to the do notation in Haskell. Values are converted into
> actions. Actions are sequenced. Though the more primitive form isn't
> directly available to the programmer, it could easily be explicitly
> present within the compiler.
>
> What about variables? What about referential transparency?
>
> Well, to a compiler writer (and equally for this argument) an identifier
> is not the same thing as the variable it references.
>
> One way to model the situation is that for every function in a C
> program, all explicit parameters are implicitly within the IO monad.
> There is one implicit parameter too - a kind of IORef to the whole
> system memory. Identifiers have values which identify where the variable
> is within the big implicit IORef. So all the manipulation of identifiers
> and their reference-like values is purely functional. Actual handling of
> variables stored within the big implicit IORef is deferred until run-time.
>
> So once you accept that there's an implicit big IORef parameter to every
> function, by the usual definition of referential transparency, C is as
> transparent as Haskell. The compile-time result of each function is
> completely determined by its (implicit and explicit) parameters - it's
> just that that result is typically a way to look up the run-time result
> within the big IORef later.
>
> What's different about Haskell relative to C therefore...
>
>  1. The style of the "AST" is different. It still amounts to the same
>     thing in this argument, but the fact that most AST nodes are simply
>     partially-evaluated functions has significant practical
>     consequences, especially with laziness mixed in too. There's a deep
>     connection between the compile-time and run-time models (contrast
>     C++ templates).
>  2. The IO monad is explicit in Haskell - side-effects are only
>     permitted (even at run-time) where the programmer has explicitly
>     opted to allow them.
>  3. IORefs are explicit in Haskell - instead of always having one you
>     can have none, one or many. This is relevant to an alternative
>     definition of referential transparency. Politicians aren't
>     considered transparent when they bury the relevant in a mass of the
>     irrelevant, and even pure functions can be considered to lack
>     transparency in that sense. Haskell allows (and encourages) you to
>     focus in on the relevant - to reference an IORef Bool or an IORef
>     Int rather than dealing with an IORef Everything.
>
> That last sentence of the third point is my most recent eureka - not so
> long ago I posted a "Haskell is just using misleading definitions - it's
> no more transparent than C" rant, possibly on Stack Overflow. Wrong
> again :-(
>
> So - what do you think?

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Donn Cave-4
In reply to this post by Gregg Reynolds-4
Quoth Gregg Reynolds <[hidden email]>,

> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus
> <[hidden email]> wrote:
>>
>> The beauty of the IO monad is that it doesn't change anything about purity.
>> Applying the function
>>
>>   bar :: Int -> IO Int
>>
>> to the value 2 will always give the same result:
>>
>>   bar 2 = bar (1+1) = bar (5-3)
>
> Strictly speaking, that doesn't sound right.

Look again at the sentence you trimmed off the end:

>> Of course, the point is that this result is an *IO action* of
>> type IO Int, it's not the Int you would get "when executing this
>> action".

I believe that more or less points to the key to this discussion.
If it didn't make sense to you, or didn't seem relevant to the
question of pure functions, then it would be worth while to think
more about it.

        Donn

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: On the purity of Haskell

Gregg Reynolds-4

On Dec 29, 2011, at 9:16 AM, Donn Cave wrote:

> Quoth Gregg Reynolds <[hidden email]>,
>> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus
>> <[hidden email]> wrote:
>>>
>>> The beauty of the IO monad is that it doesn't change anything about purity.
>>> Applying the function
>>>
>>>  bar :: Int -> IO Int
>>>
>>> to the value 2 will always give the same result:
>>>
>>>  bar 2 = bar (1+1) = bar (5-3)
>>
>> Strictly speaking, that doesn't sound right.
>
> Look again at the sentence you trimmed off the end:
>
>>> Of course, the point is that this result is an *IO action* of
>>> type IO Int, it's not the Int you would get "when executing this
>>> action".
>
> I believe that more or less points to the key to this discussion.
> If it didn't make sense to you, or didn't seem relevant to the
> question of pure functions, then it would be worth while to think
> more about it.

Ok, let's parse it out.  "…it's not the int you would get 'when executing this action".  Close, but no cooky: it's not any kind of int at all (try doing arithmetic with it).  "IO Int" is a piece of rhetoric for the mental convenience of the user; Haskell does not and cannot know what the result of an IO action is, because it's outside the scope of the language (and computation).  (The "Int" part of "IO Int" refers to the input, not the output; it's just a sort of type annotation.)  It's not even a computation, unless you want to take a broad view and include oracles, interaction, etc. in your definition of computation.

-Gregg
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
1234 ... 7
Loading...