Re: Stream programming

2012-03-26 Thread Jean-Michel Pichavant

Kiuhnm wrote:

[snip]

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
- ['same', 'same'] - streams(cat) - 'same'

It reads as

take a list of numbers - save it - compute the average and named it 
'med' - restore the flow - create two streams which have, respect., 
the numbers less than 'med' and those greater or equal to 'med' - do 
the /entire/ 'same' process on each one of the two streams - concat 
the resulting streams - name all this /entire/ process 'same'.

Not readable enough? Replace 'same' with 'qsort'.

Is that readable or am I going crazy? [note: that's a rhetorical 
question whose answer is That's very readable!]


Kiuhnm


Here's a rhetorical answer to your question : whatever you're taking, I 
want some !


JM
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-26 Thread Kiuhnm

On 3/26/2012 11:27, Jean-Michel Pichavant wrote:

Kiuhnm wrote:

[snip]

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
- ['same', 'same'] - streams(cat) - 'same'

It reads as

take a list of numbers - save it - compute the average and named it
'med' - restore the flow - create two streams which have, respect.,
the numbers less than 'med' and those greater or equal to 'med' - do
the /entire/ 'same' process on each one of the two streams - concat
the resulting streams - name all this /entire/ process 'same'.
Not readable enough? Replace 'same' with 'qsort'.

Is that readable or am I going crazy? [note: that's a rhetorical
question whose answer is That's very readable!]

Kiuhnm


Here's a rhetorical answer to your question : whatever you're taking, I
want some !


LOL. I should have titled my post A new obfuscation technique then :)

Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-24 Thread Kiuhnm

On 3/24/2012 4:23, Steven D'Aprano wrote:

On Fri, 23 Mar 2012 17:00:23 +0100, Kiuhnm wrote:


I've been writing a little library for handling streams as an excuse for
doing a little OOP with Python.

I don't share some of the views on readability expressed on this ng.
Indeed, I believe that a piece of code may very well start as complete
gibberish and become a pleasure to read after some additional
information is provided.

[...]

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'

Ok, we're at the complete gibberish phase.

Time to give you the additional information.


There are multiple problems with your DSL. Having read your explanation,
and subsequent posts, I think I understand the data model, but the syntax
itself is not very good and far from readable. It is just too hard to
reason about the code.

Your syntax conflicts with established, far more common, use of the same
syntax: you use - to mean call a function and | to join two or more
streams into a flow.

You also use () for calling functions, and the difference between - and
() isn't clear. So a mystery there -- your DSL seems to have different
function syntax, depending on... what?

The semantics are unclear even after your examples. To understand your
syntax, you give examples, but to understand the examples, the reader
needs to understand the syntax. That suggests that the semantics are
unclear even in your own mind, or at least too difficult to explain in
simple examples.

Take this example:


Flows can be saved (push) and restored (pop) :
[1,2,3,4] - push - by(2) - 'double' - pop | val('double')
=  [1,2,3,4] | [2,4,6,8]


What the hell does that mean? The reader initially doesn't know what
*any* of push, by(2), pop or val('double') means. All they see is an
obfuscated series of calls that starts with a stream as input, makes a
copy of it, and doubles the entries in the copy: you make FIVE function
calls to perform TWO conceptual operations. So the reader can't even map
a function call to a result.

With careful thought and further explanations from you, the reader (me)
eventually gets a mental model here. Your DSL has a single input which is
pipelined through a series of function calls by the - operator, plus a
separate stack. (I initially thought that, like Forth, your DSL was stack
based. But it isn't, is it?)

It seems to me that the - operator is only needed as syntactic sugar to
avoid using reverse Polish notation and an implicit stack. Instead of the
Forth-like:

[1,2,3,4] dup 2 *

your DSL has an explicit stack, and an explicit - operator to call a
function. Presumably [1,2] push would be a syntax error.

I think this is a good example of an inferior syntax. Contrast your:

[1,2,3,4] - push - by(2) - 'double' - pop | val('double')

with the equivalent RPL:

[1,2,3,4] dup 2 *


I was just explaining how push and pop work.
I also said that
  [1,2,3,4] - [id,by(2)]
would be the recommended way to do it.


Now *that* is a pleasure to read, once you wrap your head around reverse
Polish notation and the concept of a stack. Which you need in your DSL
anyway, to understand push and pop.


I don't see why. Push and pop are not needed. They're just handful 
mainly to modify a flow, collect a result, and go back to how the flow 
was before the push.

It has nothing to do with RPN (which RPL is based on).


You say that this is an easier way to get the same result:

[1,2,3,4] - [id, by(2)]

but it isn't, is it? The more complex example above ends up with two
streams joined in a single flow:

[1,2,3,4]|[2,4,6,8]

whereas the shorter version using the magic id gives you a single
stream containing nested streams:

[[1,2,3,4], [2,4,6,8]]


Says who?

Here are the rules again:
A flow can be transformed:
  [1,2] - f = [f(1),f(2)]
  ([1,2] | [3,4]) - f = [f(1,3),f(2,4)]
  ([1,2] | [3,4]) - [f] = [f(1),f(2)] | [f(3),f(4)]
  ([1,2] | [3,4]) - [f,g] = [f(1),f(2)] | [g(3),g(4)]
  [1,2] - [f,g] = [f(1),f(2)] | [g(1),g(2)]

Read the last line.
What's very interesting, is that [f,g] is an iterable as well, so your 
functions can be generated as needed.



So, how could you make this more readable?

* Don't fight the reader's expectations. If they've programmed in Unix
shells, they expect | as the pipelining operator. If they haven't, they
probably will find  easy to read as a dataflow operator. Either way,
they're probably used to seeing a|b as meaning or (as in this stream,
or this stream) rather than the way you seem to be using it (this
stream, and this stream).

Here's my first attempt at improved syntax that doesn't fight the user:

[1,2,3,4]  push  by(2)  'double'  pop  val('double')


There are problems with your syntax.
Mine:
[...]+[...] - f + [...] - g - h + [...] - i + [...]
Yours:
((([...]+[...]  f) + [...]  g  h) + [...]  i) + [...]
I first tried to use '' and '' but '+' and '-' are much better.


push and pop are poor choices of words. Push does not actually push

Stream programming

2012-03-23 Thread Kiuhnm
I've been writing a little library for handling streams as an excuse for 
doing a little OOP with Python.


I don't share some of the views on readability expressed on this ng. 
Indeed, I believe that a piece of code may very well start as complete 
gibberish and become a pleasure to read after some additional 
information is provided.


I must say that imposed indentation is a pain when one is trying to 
define some sort of DSL (domain specific language). Also, Python's 
operator overloading is a bit limited, but that makes for a more 
rewarding experience in my case.


Here's an example of what you can write:

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
- ['same', 'same'] - streams(cat) - 'same'

Ok, we're at the complete gibberish phase.

Time to give you the additional information.

I will use = to mean is equivalent to. That's not part of the DSL.
A flow has one or more streams:
  1 stream:
[1,2,3]
  2 streams:
[1,3,5] | [2,4,6]
Two flows can be concatenated:
  [1,2,3] + [4,5,6] = [1,2,3,4,5,6]
  [0] + ([1,2] | [3,4]) + [10] = [0,1,2,10] | [0,3,4,10]
  ([1,2] | [10,20]) + ([3,4] | [30,40]) = [1,2,3,4] | [10,20,30,40]
A flow can be transformed:
  [1,2] - f = [f(1),f(2)]
  ([1,2] | [3,4]) - f = [f(1,3),f(2,4)]
  ([1,2] | [3,4]) - [f] = [f(1),f(2)] | [f(3),f(4)]
  ([1,2] | [3,4]) - [f,g] = [f(1),f(2)] | [g(3),g(4)]
  [1,2] - [f,g] = [f(1),f(2)] | [g(1),g(2)]
Some functions are special and almost any function can be made special:
  [1,2,3,4,5] - filter(isprime) = [2,3,5]
  [[],(1,2),[3,4,5]] - flatten = [1,2,3,4,5]
Note that 'filter' is not really necessary, thanks to 'flatten'.
Flows can be named, remembered and used
  as a value:
[1,2,3,4,5] - 'flow' + val('flow') = [1,2,3,4,5]*2
  as a transformation chain:
[1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
  = [2,3] | [5,6]
  Recursion is also possible and stops when a function is applied to an 
empty sequence.

Flows can be saved (push) and restored (pop) :
  [1,2,3,4] - push - by(2) - 'double' - pop | val('double')
  = [1,2,3,4] | [2,4,6,8]
There are easier ways to achieve the same result, of course:
  [1,2,3,4] - [id, by(2)]

Let's go back to our example. I didn't tell you anything but you should 
be able to understand it anyway.


numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
- ['same', 'same'] - streams(cat) - 'same'

It reads as

take a list of numbers - save it - compute the average and named it 
'med' - restore the flow - create two streams which have, respect., the 
numbers less than 'med' and those greater or equal to 'med' - do the 
/entire/ 'same' process on each one of the two streams - concat the 
resulting streams - name all this /entire/ process 'same'.

Not readable enough? Replace 'same' with 'qsort'.

Is that readable or am I going crazy? [note: that's a rhetorical 
question whose answer is That's very readable!]


Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/23/2012 17:00, Kiuhnm wrote:

I've been writing a little library for handling streams as an excuse for
doing a little OOP with Python.

I don't share some of the views on readability expressed on this ng.
Indeed, I believe that a piece of code may very well start as complete
gibberish and become a pleasure to read after some additional
information is provided.

I must say that imposed indentation is a pain when one is trying to
define some sort of DSL (domain specific language). Also, Python's
operator overloading is a bit limited, but that makes for a more
rewarding experience in my case.

Here's an example of what you can write:

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
- ['same', 'same'] - streams(cat) - 'same'

Ok, we're at the complete gibberish phase.

Time to give you the additional information.

I will use = to mean is equivalent to. That's not part of the DSL.
A flow has one or more streams:
1 stream:
[1,2,3]
2 streams:
[1,3,5] | [2,4,6]
Two flows can be concatenated:
[1,2,3] + [4,5,6] = [1,2,3,4,5,6]
[0] + ([1,2] | [3,4]) + [10] = [0,1,2,10] | [0,3,4,10]
([1,2] | [10,20]) + ([3,4] | [30,40]) = [1,2,3,4] | [10,20,30,40]
A flow can be transformed:
[1,2] - f = [f(1),f(2)]
([1,2] | [3,4]) - f = [f(1,3),f(2,4)]
([1,2] | [3,4]) - [f] = [f(1),f(2)] | [f(3),f(4)]
([1,2] | [3,4]) - [f,g] = [f(1),f(2)] | [g(3),g(4)]
[1,2] - [f,g] = [f(1),f(2)] | [g(1),g(2)]
Some functions are special and almost any function can be made special:
[1,2,3,4,5] - filter(isprime) = [2,3,5]
[[],(1,2),[3,4,5]] - flatten = [1,2,3,4,5]
Note that 'filter' is not really necessary, thanks to 'flatten'.
Flows can be named, remembered and used
as a value:
[1,2,3,4,5] - 'flow' + val('flow') = [1,2,3,4,5]*2
as a transformation chain:
[1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
= [2,3] | [5,6]
Recursion is also possible and stops when a function is applied to an
empty sequence.
Flows can be saved (push) and restored (pop) :
[1,2,3,4] - push - by(2) - 'double' - pop | val('double')
= [1,2,3,4] | [2,4,6,8]
There are easier ways to achieve the same result, of course:
[1,2,3,4] - [id, by(2)]

Let's go back to our example. I didn't tell you anything but you should


Ops... *everything*.

Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Nathan Rice
 I will use = to mean is equivalent to. That's not part of the DSL.
 A flow has one or more streams:
  1 stream:
    [1,2,3]
  2 streams:
    [1,3,5] | [2,4,6]
 Two flows can be concatenated:
  [1,2,3] + [4,5,6] = [1,2,3,4,5,6]
  [0] + ([1,2] | [3,4]) + [10] = [0,1,2,10] | [0,3,4,10]
  ([1,2] | [10,20]) + ([3,4] | [30,40]) = [1,2,3,4] | [10,20,30,40]

Algebraically, your concatenation rules don't really make sense - your
flows are both distributive and non distributive.  You also make the
implicit assumption of an order over streams in a flow, but disregard
the implications of that assumption in some cases.  I understand what
you're trying to communicate, so I think you need to be a little more
strict and explicit in your definitions.

 A flow can be transformed:
  [1,2] - f = [f(1),f(2)]
  ([1,2] | [3,4]) - f = [f(1,3),f(2,4)]
  ([1,2] | [3,4]) - [f] = [f(1),f(2)] | [f(3),f(4)]
  ([1,2] | [3,4]) - [f,g] = [f(1),f(2)] | [g(3),g(4)]
  [1,2] - [f,g] = [f(1),f(2)] | [g(1),g(2)]

Given the examples you pose here, it is clear that you are assuming
that the streams are synchronized in discrete time.  Since you do not
provide any mechanism for temporal alignment of streams you are also
assuming every stream will have an element at every time point, the
streams start at the same time and are of the same length.  Is this
what you want?  These seem like pretty rough simplifying assumptions.

 Some functions are special and almost any function can be made special:
  [1,2,3,4,5] - filter(isprime) = [2,3,5]
  [[],(1,2),[3,4,5]] - flatten = [1,2,3,4,5]
 Note that 'filter' is not really necessary, thanks to 'flatten'.

This implies that your transformations again produce flows.  You
should explicitly state this.

 Flows can be named, remembered and used
  as a value:
    [1,2,3,4,5] - 'flow' + val('flow') = [1,2,3,4,5]*2

Is this a flow with two identical streams, or a flow with one long
stream formed by concatenation?

  as a transformation chain:
    [1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
      = [2,3] | [5,6]
  Recursion is also possible and stops when a function is applied to an empty
 sequence.
 Flows can be saved (push) and restored (pop) :
  [1,2,3,4] - push - by(2) - 'double' - pop | val('double')
      = [1,2,3,4] | [2,4,6,8]
 There are easier ways to achieve the same result, of course:
  [1,2,3,4] - [id, by(2)]

You are grasping at an algebra here, a sort of calculus of temporal
observations.  You need to step back and make it rigorous before you
worry about issues such as a readable syntax.

Nathan
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread MRAB

On 23/03/2012 16:33, Nathan Rice wrote:

 I will use = to mean is equivalent to. That's not part of the DSL.
 A flow has one or more streams:
   1 stream:
 [1,2,3]
   2 streams:
 [1,3,5] | [2,4,6]
 Two flows can be concatenated:
   [1,2,3] + [4,5,6]=  [1,2,3,4,5,6]
   [0] + ([1,2] | [3,4]) + [10]=  [0,1,2,10] | [0,3,4,10]
   ([1,2] | [10,20]) + ([3,4] | [30,40])=  [1,2,3,4] | [10,20,30,40]


Algebraically, your concatenation rules don't really make sense - your
flows are both distributive and non distributive.  You also make the
implicit assumption of an order over streams in a flow, but disregard
the implications of that assumption in some cases.  I understand what
you're trying to communicate, so I think you need to be a little more
strict and explicit in your definitions.


When concatenating, either there are the same number of streams, or one
of them is a single stream which is duplicated.

Therefore, in this example:

[0] + ([1, 2] | [3, 4])

you're concatenating a single stream with a pair, so the single stream
is duplicated:

([0] | [0]) + ([1, 2] | [3, 4])

and then they can be concatenated:

([0, 1, 2] | [0, 3, 4])

However, this:

([0, 1] | [2, 3]) + ([4, 5] | [6, 7] | [8, 9])

won't work.
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Nathan Rice
  I will use = to mean is equivalent to. That's not part of the DSL.
  A flow has one or more streams:
   1 stream:
     [1,2,3]
   2 streams:
     [1,3,5] | [2,4,6]
  Two flows can be concatenated:
   [1,2,3] + [4,5,6]=  [1,2,3,4,5,6]
   [0] + ([1,2] | [3,4]) + [10]=  [0,1,2,10] | [0,3,4,10]
   ([1,2] | [10,20]) + ([3,4] | [30,40])=  [1,2,3,4] | [10,20,30,40]


 Algebraically, your concatenation rules don't really make sense - your
 flows are both distributive and non distributive.  You also make the
 implicit assumption of an order over streams in a flow, but disregard
 the implications of that assumption in some cases.  I understand what
 you're trying to communicate, so I think you need to be a little more
 strict and explicit in your definitions.

 When concatenating, either there are the same number of streams, or one
 of them is a single stream which is duplicated.

 Therefore, in this example:

    [0] + ([1, 2] | [3, 4])

 you're concatenating a single stream with a pair, so the single stream
 is duplicated:

    ([0] | [0]) + ([1, 2] | [3, 4])

 and then they can be concatenated:

    ([0, 1, 2] | [0, 3, 4])

 However, this:

    ([0, 1] | [2, 3]) + ([4, 5] | [6, 7] | [8, 9])

 won't work.

I understand how he wants the system to work in this case; my point
was that it isn't consistent.

He stated flows can be concatenated, so [0] is just a flow of a single
stream.  Because he clearly intends that streams in a flow are
ordered, that indicates that he wants some sort of algebraic module.
He could get close if he can extend a stream to meet the requirements
of a ring, then a flow would be a module over the ring of streams.
The problem he is going to run into is that operations on streams as
he defines them are not commutative.  Identity elements for the two
binary operations are also not immediately obvious to me.

He could just be smart and use the pi calculus, it is rigorously
developed and can model his desired behavior, if he reformulates it
slightly.

Another option is just to give up on being rigorous.  Given the
abstract treatment he attempted I would be disappointed, but if his
only goal is to get practice writing code and he isn't interested in
exploring the conceptual space of the problem domain it would be the
right decision.
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/23/2012 17:33, Nathan Rice wrote:

I will use = to mean is equivalent to. That's not part of the DSL.
A flow has one or more streams:
  1 stream:
[1,2,3]
  2 streams:
[1,3,5] | [2,4,6]
Two flows can be concatenated:
  [1,2,3] + [4,5,6]=  [1,2,3,4,5,6]
  [0] + ([1,2] | [3,4]) + [10]=  [0,1,2,10] | [0,3,4,10]
  ([1,2] | [10,20]) + ([3,4] | [30,40])=  [1,2,3,4] | [10,20,30,40]


Algebraically, your concatenation rules don't really make sense - your
flows are both distributive and non distributive.


?


You also make the
implicit assumption of an order over streams in a flow, but disregard
the implications of that assumption in some cases.


?


 I understand what
you're trying to communicate, so I think you need to be a little more
strict and explicit in your definitions.


No, I don't think you understand what I meant.


A flow can be transformed:
  [1,2] - f=  [f(1),f(2)]
  ([1,2] | [3,4]) - f=  [f(1,3),f(2,4)]
  ([1,2] | [3,4]) - [f]=  [f(1),f(2)] | [f(3),f(4)]
  ([1,2] | [3,4]) - [f,g]=  [f(1),f(2)] | [g(3),g(4)]
  [1,2] - [f,g]=  [f(1),f(2)] | [g(1),g(2)]


Given the examples you pose here, it is clear that you are assuming
that the streams are synchronized in discrete time.  Since you do not
provide any mechanism for temporal alignment of streams you are also
assuming every stream will have an element at every time point, the
streams start at the same time and are of the same length.  Is this
what you want?


Yes. I thought that streams as an alternative to functional programming 
were widely known.



Some functions are special and almost any function can be made special:
  [1,2,3,4,5] - filter(isprime)=  [2,3,5]
  [[],(1,2),[3,4,5]] - flatten=  [1,2,3,4,5]
Note that 'filter' is not really necessary, thanks to 'flatten'.


This implies that your transformations again produce flows.  You
should explicitly state this.


Isn't that obvious? BTW, those are not rigorous definitions. I thought I 
was talking to people who regularly works with streams.



Flows can be named, remembered and used
  as a value:
[1,2,3,4,5] - 'flow' + val('flow')=  [1,2,3,4,5]*2


Is this a flow with two identical streams, or a flow with one long
stream formed by concatenation?


What does
  [1,2,3,4,5]*2
mean in Python?

Those are Python's lists/arrays.


  as a transformation chain:
[1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
  =  [2,3] | [5,6]
  Recursion is also possible and stops when a function is applied to an empty
sequence.
Flows can be saved (push) and restored (pop) :
  [1,2,3,4] - push - by(2) - 'double' - pop | val('double')
  =  [1,2,3,4] | [2,4,6,8]
There are easier ways to achieve the same result, of course:
  [1,2,3,4] - [id, by(2)]


You are grasping at an algebra here, a sort of calculus of temporal
observations.  You need to step back and make it rigorous before you
worry about issues such as a readable syntax.

Nathan


I don't agree. Sorry.

Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/23/2012 20:23, Nathan Rice wrote:

  I will use = to mean is equivalent to. That's not part of the DSL.
  A flow has one or more streams:
   1 stream:
 [1,2,3]
   2 streams:
 [1,3,5] | [2,4,6]
  Two flows can be concatenated:
   [1,2,3] + [4,5,6]=[1,2,3,4,5,6]
   [0] + ([1,2] | [3,4]) + [10]=[0,1,2,10] | [0,3,4,10]
   ([1,2] | [10,20]) + ([3,4] | [30,40])=[1,2,3,4] | [10,20,30,40]



Algebraically, your concatenation rules don't really make sense - your
flows are both distributive and non distributive.  You also make the
implicit assumption of an order over streams in a flow, but disregard
the implications of that assumption in some cases.  I understand what
you're trying to communicate, so I think you need to be a little more
strict and explicit in your definitions.


When concatenating, either there are the same number of streams, or one
of them is a single stream which is duplicated.

Therefore, in this example:

[0] + ([1, 2] | [3, 4])

you're concatenating a single stream with a pair, so the single stream
is duplicated:

([0] | [0]) + ([1, 2] | [3, 4])

and then they can be concatenated:

([0, 1, 2] | [0, 3, 4])

However, this:

([0, 1] | [2, 3]) + ([4, 5] | [6, 7] | [8, 9])

won't work.


I understand how he wants the system to work in this case; my point
was that it isn't consistent.

He stated flows can be concatenated, so [0] is just a flow of a single
stream.  Because he clearly intends that streams in a flow are
ordered, that indicates that he wants some sort of algebraic module.
He could get close if he can extend a stream to meet the requirements
of a ring, then a flow would be a module over the ring of streams.
The problem he is going to run into is that operations on streams as
he defines them are not commutative.  Identity elements for the two
binary operations are also not immediately obvious to me.


Instead of talking of what I wasn't trying to do and, indeed, I didn't 
do, you should try to understand what I wanted to do and, in fact, I did.
I'm afraid your cup is too full to understand simple things as the one I 
wrote in my OP.


Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Nathan Rice
  I understand what
 you're trying to communicate, so I think you need to be a little more
 strict and explicit in your definitions.


 No, I don't think you understand what I meant.

I don't agree. Sorry.

 Yes. I thought that streams as an alternative to functional programming were
 widely known.

Streams aren't really a paradigm of computation.  They're a semantic
element of a computational system which cuts across paradigms.  If you
want to retract that and say you were talking about dataflow
programming (which is much larger than streams, and actually has a
cohesive definition), I won't hold it against you.  Ultimately though,
functional programming and dataflow programming are closely linked,
the main difference being the use of queues based rather than stacks.

 Isn't that obvious? BTW, those are not rigorous definitions. I thought I was 
 talking to people who regularly works with streams.

That is the GPU mailing list, down the hall on the left.

 Instead of talking of what I wasn't trying to do and, indeed, I didn't do,
 you should try to understand what I wanted to do and, in fact, I did.
 I'm afraid your cup is too full to understand simple things as the one I
 wrote in my OP.

Clearly, because I didn't explicitly include the possibility that you
are just writing throwaway code with no attempt at development of
ideas for the purpose of practicing writing code in the paragraph
after the one you quoted.

If your goal is to learn to code, instead of posting a message stating
that you have a superior way to compose code, you might want to try to
solve a problem and ask others their opinion of the structure and
techniques in your code.
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Ethan Furman

Kiuhnm wrote:

On 3/23/2012 17:33, Nathan Rice wrote:

Given the examples you pose here, it is clear that you are assuming
that the streams are synchronized in discrete time.  Since you do not
provide any mechanism for temporal alignment of streams you are also
assuming every stream will have an element at every time point, the
streams start at the same time and are of the same length.  Is this
what you want?


Yes. I thought that streams as an alternative to functional programming 
were widely known.


Don't know about widely, but I can say I am unfamiliar with the way you 
are using them.




This implies that your transformations again produce flows.  You
should explicitly state this.


Isn't that obvious? BTW, those are not rigorous definitions. I thought I 
was talking to people who regularly works with streams.


Why would you think that?  This list is composed of all types that use 
Python.  I've seen occasional discussion of functional programming, but 
I've only seen anything this confusing maybe twice before... granted, I 
don't read *everything*, but I do read quite a bit -- especially the 
stuff that looks like it might be interesting... like stream 
programming, for example.  ;)



After the discussion I've seen so far, I still have no idea how I would 
use your code or what it's good for.


~Ethan~
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Ray Song
On Fri, Mar 23, 2012 at 05:00:23PM +0100, Kiuhnm wrote:
 I've been writing a little library for handling streams as an excuse for
 doing a little OOP with Python.

 I don't share some of the views on readability expressed on this ng.
 Indeed, I believe that a piece of code may very well start as complete
 gibberish and become a pleasure to read after some additional
 information is provided.

 I must say that imposed indentation is a pain when one is trying to
 define some sort of DSL (domain specific language). Also, Python's
 operator overloading is a bit limited, but that makes for a more
 rewarding experience in my case.

 Here's an example of what you can write:

 numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'

 Ok, we're at the complete gibberish phase.

 Time to give you the additional information.

 I will use = to mean is equivalent to. That's not part of the DSL.
 A flow has one or more streams:
1 stream:
  [1,2,3]
2 streams:
  [1,3,5] | [2,4,6]
 Two flows can be concatenated:
[1,2,3] + [4,5,6] = [1,2,3,4,5,6]
[0] + ([1,2] | [3,4]) + [10] = [0,1,2,10] | [0,3,4,10]
([1,2] | [10,20]) + ([3,4] | [30,40]) = [1,2,3,4] | [10,20,30,40]
 A flow can be transformed:
[1,2] - f = [f(1),f(2)]
([1,2] | [3,4]) - f = [f(1,3),f(2,4)]
([1,2] | [3,4]) - [f] = [f(1),f(2)] | [f(3),f(4)]
([1,2] | [3,4]) - [f,g] = [f(1),f(2)] | [g(3),g(4)]
[1,2] - [f,g] = [f(1),f(2)] | [g(1),g(2)]
 Some functions are special and almost any function can be made special:
[1,2,3,4,5] - filter(isprime) = [2,3,5]
[[],(1,2),[3,4,5]] - flatten = [1,2,3,4,5]
 Note that 'filter' is not really necessary, thanks to 'flatten'.
 Flows can be named, remembered and used
as a value:
  [1,2,3,4,5] - 'flow' + val('flow') = [1,2,3,4,5]*2
as a transformation chain:
  [1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
= [2,3] | [5,6]
Recursion is also possible and stops when a function is applied to an
 empty sequence.
 Flows can be saved (push) and restored (pop) :
[1,2,3,4] - push - by(2) - 'double' - pop | val('double')
= [1,2,3,4] | [2,4,6,8]
 There are easier ways to achieve the same result, of course:
[1,2,3,4] - [id, by(2)]

 Let's go back to our example. I didn't tell you anything but you should
 be able to understand it anyway.

 numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'

 It reads as

 take a list of numbers - save it - compute the average and named it
 'med' - restore the flow - create two streams which have, respect., the
 numbers less than 'med' and those greater or equal to 'med' - do the
 /entire/ 'same' process on each one of the two streams - concat the
 resulting streams - name all this /entire/ process 'same'.
 Not readable enough? Replace 'same' with 'qsort'.

 Is that readable or am I going crazy? [note: that's a rhetorical
 question whose answer is That's very readable!]


This sounds like a concatenative programming and i've found many cool
examples from Haskell and Ruby. I was pleased with Python's rigid
off-side rule at first but frustrated when I realized that this is a
two-edged blade as the rule makes a DSL difficult.

Sorry for former mail to you (my damn Android gmail client doesn't
understand mailing lists well).

--
Ray
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/23/2012 22:12, Ethan Furman wrote:

Kiuhnm wrote:

On 3/23/2012 17:33, Nathan Rice wrote:

Given the examples you pose here, it is clear that you are assuming
that the streams are synchronized in discrete time. Since you do not
provide any mechanism for temporal alignment of streams you are also
assuming every stream will have an element at every time point, the
streams start at the same time and are of the same length. Is this
what you want?


Yes. I thought that streams as an alternative to functional
programming were widely known.


Don't know about widely, but I can say I am unfamiliar with the way you
are using them.


That's good! It means I'm saying something new (and, hopefully, 
interesting).



This implies that your transformations again produce flows. You
should explicitly state this.


Isn't that obvious? BTW, those are not rigorous definitions. I thought
I was talking to people who regularly works with streams.


Why would you think that? This list is composed of all types that use
Python. I've seen occasional discussion of functional programming, but
I've only seen anything this confusing maybe twice before... granted, I
don't read *everything*, but I do read quite a bit -- especially the
stuff that looks like it might be interesting... like stream
programming, for example. ;)


After the discussion I've seen so far, I still have no idea how I would
use your code or what it's good for.


The idea is simple. Flows or streams let you be more declarative without 
being too functional :)

In imperative progamming you write statements or commands.
In functional programming (FP) you write expressions.
In streaming programming (SP) you create flows.
Basically, if in FP you write
  h9(h8(h7(.(h1))))
in SP you write
  h1-h2-h3-...-h9
which I greatly prefer because that's the way I think.
I think that SP could be an interesting alternative to FP (in ten years, 
maybe). Mine was just a little experiment.


Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/23/2012 22:18, Nathan Rice wrote:

  I understand what
you're trying to communicate, so I think you need to be a little more
strict and explicit in your definitions.



No, I don't think you understand what I meant.


I don't agree. Sorry.


You could just point out those inconsistencies that you found.


Yes. I thought that streams as an alternative to functional programming were
widely known.


Streams aren't really a paradigm of computation.  They're a semantic
element of a computational system which cuts across paradigms.  If you
want to retract that and say you were talking about dataflow
programming (which is much larger than streams, and actually has a
cohesive definition), I won't hold it against you.


I wasn't talking of dataflow programming. Flow programming is much 
closer to functional programming than dataflow programming is.



Instead of talking of what I wasn't trying to do and, indeed, I didn't do,
you should try to understand what I wanted to do and, in fact, I did.
I'm afraid your cup is too full to understand simple things as the one I
wrote in my OP.


Clearly, because I didn't explicitly include the possibility that you
are just writing throwaway code with no attempt at development of
ideas for the purpose of practicing writing code in the paragraph
after the one you quoted.


x != 'a' doesn't imply that x == 'b'.


If your goal is to learn to code, instead of posting a message stating
that you have a superior way to compose code, you might want to try to
solve a problem and ask others their opinion of the structure and
techniques in your code.


Never said it was superior.
We've been talking about readability a lot, haven't we? I was just 
proposing something different.


Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Kiuhnm

On 3/24/2012 0:32, Ray Song wrote:

On Fri, Mar 23, 2012 at 05:00:23PM +0100, Kiuhnm wrote:

I've been writing a little library for handling streams as an excuse for
doing a little OOP with Python.

I don't share some of the views on readability expressed on this ng.
Indeed, I believe that a piece of code may very well start as complete
gibberish and become a pleasure to read after some additional
information is provided.

I must say that imposed indentation is a pain when one is trying to
define some sort of DSL (domain specific language). Also, Python's
operator overloading is a bit limited, but that makes for a more
rewarding experience in my case.

Here's an example of what you can write:

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'

Ok, we're at the complete gibberish phase.

Time to give you the additional information.

I will use = to mean is equivalent to. That's not part of the DSL.
A flow has one or more streams:
1 stream:
  [1,2,3]
2 streams:
  [1,3,5] | [2,4,6]
Two flows can be concatenated:
[1,2,3] + [4,5,6]=  [1,2,3,4,5,6]
[0] + ([1,2] | [3,4]) + [10]=  [0,1,2,10] | [0,3,4,10]
([1,2] | [10,20]) + ([3,4] | [30,40])=  [1,2,3,4] | [10,20,30,40]
A flow can be transformed:
[1,2] - f=  [f(1),f(2)]
([1,2] | [3,4]) - f=  [f(1,3),f(2,4)]
([1,2] | [3,4]) - [f]=  [f(1),f(2)] | [f(3),f(4)]
([1,2] | [3,4]) - [f,g]=  [f(1),f(2)] | [g(3),g(4)]
[1,2] - [f,g]=  [f(1),f(2)] | [g(1),g(2)]
Some functions are special and almost any function can be made special:
[1,2,3,4,5] - filter(isprime)=  [2,3,5]
[[],(1,2),[3,4,5]] - flatten=  [1,2,3,4,5]
Note that 'filter' is not really necessary, thanks to 'flatten'.
Flows can be named, remembered and used
as a value:
  [1,2,3,4,5] - 'flow' + val('flow')=  [1,2,3,4,5]*2
as a transformation chain:
  [1,2,3] - skipfirst - 'again' | [4,5,6] - func('again')
=  [2,3] | [5,6]
Recursion is also possible and stops when a function is applied to an
empty sequence.
Flows can be saved (push) and restored (pop) :
[1,2,3,4] - push - by(2) - 'double' - pop | val('double')
=  [1,2,3,4] | [2,4,6,8]
There are easier ways to achieve the same result, of course:
[1,2,3,4] - [id, by(2)]

Let's go back to our example. I didn't tell you anything but you should
be able to understand it anyway.

numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'

It reads as

take a list of numbers - save it - compute the average and named it
'med' - restore the flow - create two streams which have, respect., the
numbers less than 'med' and those greater or equal to 'med' - do the
/entire/ 'same' process on each one of the two streams - concat the
resulting streams - name all this /entire/ process 'same'.
Not readable enough? Replace 'same' with 'qsort'.

Is that readable or am I going crazy? [note: that's a rhetorical
question whose answer is That's very readable!]



This sounds like a concatenative programming and i've found many cool
examples from Haskell and Ruby. I was pleased with Python's rigid
off-side rule at first but frustrated when I realized that this is a
two-edged blade as the rule makes a DSL difficult.


Yes, there is that. I also noticed that I couldn't use the comparison 
operators the way I liked.
Precedence is also an issue. I had to use '+' and '-' instead of '+' and 
'' or '' because of that. I didn't want to introduce extra parentheses.



Sorry for former mail to you (my damn Android gmail client doesn't
understand mailing lists well).


Don't worry, my email address is invalid!

Kiuhnm
--
http://mail.python.org/mailman/listinfo/python-list


Re: Stream programming

2012-03-23 Thread Steven D'Aprano
On Fri, 23 Mar 2012 17:00:23 +0100, Kiuhnm wrote:

 I've been writing a little library for handling streams as an excuse for
 doing a little OOP with Python.
 
 I don't share some of the views on readability expressed on this ng.
 Indeed, I believe that a piece of code may very well start as complete
 gibberish and become a pleasure to read after some additional
 information is provided.
[...]
 numbers - push - avrg - 'med' - pop - filter(lt('med'), ge('med'))\
  - ['same', 'same'] - streams(cat) - 'same'
 
 Ok, we're at the complete gibberish phase.
 
 Time to give you the additional information.

There are multiple problems with your DSL. Having read your explanation, 
and subsequent posts, I think I understand the data model, but the syntax 
itself is not very good and far from readable. It is just too hard to 
reason about the code.

Your syntax conflicts with established, far more common, use of the same 
syntax: you use - to mean call a function and | to join two or more 
streams into a flow.

You also use () for calling functions, and the difference between - and 
() isn't clear. So a mystery there -- your DSL seems to have different 
function syntax, depending on... what?

The semantics are unclear even after your examples. To understand your 
syntax, you give examples, but to understand the examples, the reader 
needs to understand the syntax. That suggests that the semantics are 
unclear even in your own mind, or at least too difficult to explain in 
simple examples.

Take this example:

 Flows can be saved (push) and restored (pop) :
[1,2,3,4] - push - by(2) - 'double' - pop | val('double')
= [1,2,3,4] | [2,4,6,8]

What the hell does that mean? The reader initially doesn't know what 
*any* of push, by(2), pop or val('double') means. All they see is an 
obfuscated series of calls that starts with a stream as input, makes a 
copy of it, and doubles the entries in the copy: you make FIVE function 
calls to perform TWO conceptual operations. So the reader can't even map 
a function call to a result.

With careful thought and further explanations from you, the reader (me) 
eventually gets a mental model here. Your DSL has a single input which is 
pipelined through a series of function calls by the - operator, plus a 
separate stack. (I initially thought that, like Forth, your DSL was stack 
based. But it isn't, is it?)

It seems to me that the - operator is only needed as syntactic sugar to 
avoid using reverse Polish notation and an implicit stack. Instead of the 
Forth-like:

[1,2,3,4] dup 2 *

your DSL has an explicit stack, and an explicit - operator to call a 
function. Presumably [1,2] push would be a syntax error.

I think this is a good example of an inferior syntax. Contrast your:

[1,2,3,4] - push - by(2) - 'double' - pop | val('double')

with the equivalent RPL:

[1,2,3,4] dup 2 *


Now *that* is a pleasure to read, once you wrap your head around reverse 
Polish notation and the concept of a stack. Which you need in your DSL 
anyway, to understand push and pop.

You say that this is an easier way to get the same result:

[1,2,3,4] - [id, by(2)]

but it isn't, is it? The more complex example above ends up with two 
streams joined in a single flow:

[1,2,3,4]|[2,4,6,8]

whereas the shorter version using the magic id gives you a single 
stream containing nested streams:

[[1,2,3,4], [2,4,6,8]]


So, how could you make this more readable?

* Don't fight the reader's expectations. If they've programmed in Unix 
shells, they expect | as the pipelining operator. If they haven't, they 
probably will find  easy to read as a dataflow operator. Either way, 
they're probably used to seeing a|b as meaning or (as in this stream, 
or this stream) rather than the way you seem to be using it (this 
stream, and this stream).

Here's my first attempt at improved syntax that doesn't fight the user:

[1,2,3,4]  push  by(2)  'double'  pop  val('double')

push and pop are poor choices of words. Push does not actually push 
its input onto the stack, which would leave the input stream empty. It 
makes a copy. You explain what they do:

Flows can be saved (push) and restored (pop)

so why not just use SAVE and RESTORE as your functions? Or if they're too 
verbose, STO and RCL, or my preference, store and recall.

[1,2,3,4]  store  by(2)  'double'  recall  val('double')

I'm still not happy with  for the join operator. I think that the use of 
+ for concatenate and  for join is just one of those arbitrary choices 
that the user will have to learn. Although I'm tempted to try using a 
colon instead.

[1,2,3]:[4,5,6]

would be a flow with two streams.

I don't like the syntax for defining and using names. Here's a random 
thought:

[1,2,3,4]  store  by(2)  @double  recall  double 

Use @name to store to a name, and the name alone to retrieve from it. But 
I haven't given this too much thought, so it too might suck.

Some other problems with your DSL:

 A flow can be transformed:
[1,2] - f =