Mainstream languages, almost by definition, are more likely to be used
in mainstream commercial projects. So if I'm involved in such a
project (and I do get involved in lots of them), what benefits would
functional programming expertise give me, and how could I achieve
those benefits?
I'm working through SICP with Scheme. I've discovered that I'm going
through interesting stages: first discovering how to use Scheme to
solve certain example problems (using recursion, for ex.), then
finding that approach generalizing into my brain as just a way to
*see* the problem independent of Scheme (which, of course, is the
point of SICP), then discovering that I can transcribe that way of
seeing the problem into mainstream languages that I've been using for
years, even if those languages make it a little harder.
Even though I've used recursion occasionally for years, for example, I
never really had a "sense" of it until I used it for a while in a
language really designed for it. Now, I'm wondering how much else I
could learn from Scheme, Haskell, or Ocaml, what additional "senses" I
could develop, how much of it is applicable even in mainstream
languages, and how much of it simply isn't of practical use unless you
actually use an FP (or FP-oriented) language.
And when using mainstream languages, are there ways to get more out of
them -- such as special libraries with FP-style datatypes and
functions on them -- or if such things simply aren't realistic.
Thanks.
> I'm curious to what extent a solid understanding of functional
> programming would help me if I still had to do most real work in
> mainstream imperative languages.
>
> Mainstream languages, almost by definition, are more likely to be used
> in mainstream commercial projects. So if I'm involved in such a
> project (and I do get involved in lots of them), what benefits would
> functional programming expertise give me, and how could I achieve
> those benefits?
>
> I'm working through SICP with Scheme. I've discovered that I'm going
> through interesting stages: first discovering how to use Scheme to
> solve certain example problems (using recursion, for ex.), then
> finding that approach generalizing into my brain as just a way to
> *see* the problem independent of Scheme (which, of course, is the
> point of SICP), then discovering that I can transcribe that way of
> seeing the problem into mainstream languages that I've been using for
> years, even if those languages make it a little harder.
>
> Even though I've used recursion occasionally for years, for example, I
> never really had a "sense" of it until I used it for a while in a
> language really designed for it. Now, I'm wondering how much else I
> could learn from Scheme, Haskell, or Ocaml, what additional "senses" I
> could develop, how much of it is applicable even in mainstream
> languages, and how much of it simply isn't of practical use unless you
> actually use an FP (or FP-oriented) language.
Here's a page that describes a bunch of "patterns" inspired by
functional programming. This should give you an idea of the kinds of
techniques more prevalent in functional programming and how they can
benefit even imperative programs:
http://www.c2.com/cgi/wiki?FunctionalPatternSystemForObjectOrientedDesign
In general, functional programming encourages a more bottom-up style of
programming which can lead to entirely different ways of approaching
problems.
> And when using mainstream languages, are there ways to get more out of
> them -- such as special libraries with FP-style datatypes and
> functions on them -- or if such things simply aren't realistic.
There are libraries that attempt to minimize the pain of doing some
functional techniques in languages that don't really have the right
support, e.g. FC++.
> I'm curious to what extent a solid understanding of functional
> programming would help me if I still had to do most real work in
> mainstream imperative languages.
[snip]
> I'm working through SICP with Scheme. I've discovered that I'm going
> through interesting stages: first discovering how to use Scheme to
> solve certain example problems (using recursion, for ex.), then
> finding that approach generalizing into my brain as just a way to
> *see* the problem independent of Scheme (which, of course, is the
> point of SICP), then discovering that I can transcribe that way of
> seeing the problem into mainstream languages that I've been using for
> years, even if those languages make it a little harder.
I think you just answered your own question!
--
~jrm
> I'm working through SICP with Scheme. I've discovered that I'm going
> through interesting stages: first discovering how to use Scheme to
> solve certain example problems (using recursion, for ex.), then
> finding that approach generalizing into my brain as just a way to
> *see* the problem independent of Scheme (which, of course, is the
> point of SICP), then discovering that I can transcribe that way of
> seeing the problem into mainstream languages that I've been using for
> years, even if those languages make it a little harder.
Same for me. Just an anedoctical example: I recently wrote a state
machine in Scheme which (quite naturally) came out to be full of closures.
Then I translated it in Python and discovered (with my surprise) that I
could maintain the functional structure essentially unchanged. Nevertheless,
I would never have used that style if I had coded in Python from the
beginning (I would have used classes instead). So, certainly the
functional paradigm is independent from the language. Still, I do think
it is bad practice to code Python with a Scheme mindset, just as it is
bad practice to code Scheme with a Python mindset. I personally make a
personality split and I code with different styles and paradigm according
to the target language,unless for some reason I need to translate literally
from one to the other (as in that case). OTOH, it is by no means guaranteed
that a paradigm which is superior in language X is also superior in language
Y, so my advice is to use the paradigm the language support the best.
Michele Simionato
P.S. about my other postings on Python-like "for" loops in Scheme: I want
to make clear that I want to implement them just for educational purpose,
NOT to use them in real code. Actually, my Scheme code is full of named let,
my favorite looping construct and I will continue to use it over the
Python-like "for" loops, if not for other reasons, because I want my
code to be readable to others than myself. I am not interested in
making Scheme similar to Python or coding Python in Scheme, otherwise
the purpose of learning a new language would be completely lost. Still,
it is quite fun to see how much you can stretch a language and I enjoy
"stretchable" languages ;-)
--
(for-each (lambda (str) (display (string-append (make-string (- 40
(quotient (string-length str) 2)) #\space) str)) (newline))
'("" "Bruce Lewis" "MIT 1990" " http://brl.codesimply.net/
" ))
> P.S. about my other postings on Python-like "for" loops in Scheme: I want
> to make clear that I want to implement them just for educational purpose,
> NOT to use them in real code. Actually, my Scheme code is full of named let,
> my favorite looping construct and I will continue to use it over the
> Python-like "for" loops, if not for other reasons, because I want my
> code to be readable to others than myself. I am not interested in
> making Scheme similar to Python or coding Python in Scheme, otherwise
> the purpose of learning a new language would be completely lost. Still,
> it is quite fun to see how much you can stretch a language and I enjoy
> "stretchable" languages ;-)
You may want to start thinking about functional loops:
(module range mzscheme
(require (lib "etc.ss") (lib "contract.ss"))
(provide/contract
(range
(case-> (natural-number? . -> . (listof natural-number?))
(natural-number? natural-number? . -> . (listof natural-number?))
(natural-number? natural-number? natural-number? . -> .
(listof natural-number?))))
#| ----------------------------------------------------------------------------
This is Python's range function, which separates Algol's notion of stepping
in a for-loop from the actual looping construct.
(range n) = (list 0 ... n-1)
(range n m) = (list n ... m-1),
if n < m
(range n m k) = (list n n+1*k n+2*k ... n+l*k)
if n < m, k > 0 for l = ceiling[m-n/k]
|#
)
(define range
(case-lambda
[(n) (build-list n identity)]
[(n m)
(if (< n m)
(build-list (- m n) (lambda (i) (+ n i)))
(error 'range (format range1 n m)))]
[(n m k)
(if (and (< n m) (> k 0))
(build-list (ceiling (/ (- m n) k)) (lambda (i) (+ n (* i k))))
(error 'range (format range2 n m k)))]))
(define range1 "(range n m) for n, m in N, and n < m expected; given ~e ~e")
(define range2
"(range n m k) with n, m, k in N, and n < m, k 0 expected; given: ~e ~e ~e")
)
-- Matthias
> Still, I do think it is bad practice to code Python with a Scheme
> mindset, just as it is bad practice to code Scheme with a Python
> mindset.
I don't.
A friend of mine once remarked that even my C code looked like
Scheme. He meant it as a compliment and I took it as one.
Well, not really. I didn't ask whether it was ever possible to use FP
concepts to advantage in non-FP languages. The fact that I knew the
answer to that one led me to want to know more.
Specifically, when working in *non-FP languages*:
-- which FP concepts/techniques/advantages are still
available/usable/practical in real production work?
-- for each, to what extent? (on rare occasions, almost always, etc.)
-- which ones might I be tempted to use but should probably avoid (and
why)?
-- any other advice/tips on when and when not to use them?
-- any advice for *how* best to do so (good libraries, a style of
modularization, etc.)?
-- any other practical advice for getting the most benefit out of FP
study if most real production work will still be in mainstream
languages for the foreseeable future? (particular books or online docs
you recommend, language recommendations for this purpose, study
techniques such as "I always prototype X-type apps in FP language Y
before implementing in non-FP lang Z", things to be wary of, etc.)
Any advice or suggestions regarding any of the above items would be
appreciated.
Thanks.
tuan...@hotmail.com (Tuang) once said:
>Specifically, when working in *non-FP languages*:
>
>-- which FP concepts/techniques/advantages are still
>available/usable/practical in real production work?
>-- for each, to what extent? (on rare occasions, almost always, etc.)
I would cite
- higher order functions
- lazy evaluation
- parametric polymorphism
- monads
I think HOFs are the most useful (and are often applicable). If you
understand them well and have witnessed some cool combinator libraries,
you'll be provided with a new perspective on how to do design in
certain domains.
Lazy evaluation I've only found to be useful/essential on rare
occasions, but it's a useful tool to have in your toolbox for when
certain problems arise.
I'm not sure of "parametric polymorphism" counts as an FP concept (it's
becoming common in mainstream languages), but I think the FP community
has the most advanced experience with it and how to use it well.
I dunno if "monads" should actually be on the list (yet), but they've
shown me an interesting way to structure programs that modularizes
various concerns in a way akin to aspect-oriented programming (which
itself seems to be on a "mainstream/popularity" rise).
>-- which ones might I be tempted to use but should probably avoid (and
>why)?
>-- any other advice/tips on when and when not to use them?
>-- any advice for *how* best to do so (good libraries, a style of
>modularization, etc.)?
I would tie these all together and say that "using FP techniques in
mainstream languages" only works well when you have the necessary
language/library support to do it. What constitutes "sufficient
support" is probably open to opinion: there's a kind of continuuum with
points like "impossible", "possible but difficult", "straightforward
but klunky" and "easy/effortless".
>-- any other practical advice for getting the most benefit out of FP
>study if most real production work will still be in mainstream
>languages for the foreseeable future? (particular books or online docs
>you recommend, language recommendations for this purpose, study
The paper "FC++: Functional Tools for OO Tasks" (an old version is
available at the FC++ web site: http://www.cc.gatech.edu/~yannis/fc++/)
describes applying FP techniques (mainly HOFs and parametric
polymorphism) to the implementation of object-oriented design
patterns. The concrete examples there give a sense of some ways to
reap benefits from FP techniques.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
Python is another beast. It offers some (pretty limited but...) support
for fp constructs, but with *really* bad performances. As Python is
already a bit slow, slowing it down some more is not a good idea.
My 2 cents
> Joe Marshall <prunes...@comcast.net> wrote in message news:<7jyt6r...@comcast.net>...
>>
>> I think you just answered your own question!
>
> Well, not really. I didn't ask whether it was ever possible to use FP
> concepts to advantage in non-FP languages. The fact that I knew the
> answer to that one led me to want to know more.
>
> Specifically, when working in *non-FP languages*:
>
> -- which FP concepts/techniques/advantages are still
> available/usable/practical in real production work?
> -- for each, to what extent? (on rare occasions, almost always, etc.)
> -- which ones might I be tempted to use but should probably avoid (and
> why)?
> -- any other advice/tips on when and when not to use them?
> -- any advice for *how* best to do so (good libraries, a style of
> modularization, etc.)?
I have found that most mainstream languages force you to think real
hard about the `machinery' that underlies the implementation. This
disadvantage of the language can be turned to your advantage: if an FP
technique (such as lazy evaluation) is impractical in the language, it
will be *so* painful to use that it will be glaringly obvious. If the
technique is useful, however, it mostly just `looks funny' to people
who use the language `normally'.
This is just my experience, but it seems to hold.
-------------------
Here are a couple of very specific things:
1. You don't need to name *every* intermediate expression:
redirect_loop (new Set ().adjoin (new URL (server_uri)));
1. Use the `? :' conditional expression when you are using an
infix language. If you indent it right, it almost looks like a
Scheme or Lisp IF:
public int cardinality () {
return (my_element == null)
? 0
: my_more.cardinality() + 1;
}
You can make it look like a COND, too:
public Set remove (Object element) {
return
my_element == null ? this
: my_element.equals (element) ? my_more.remove (element)
: new Set (my_element, my_more.remove (element));
}
2. You call them exceptions, I call them continuations. You can
use the exception handling mechanism to mimic
continuation-passing-style. (Note that this is often quite
slow because language implementors generally don't think users
will be this perverse.)
try {
// serveResponse always throws one of four values
serveResponse (false);
}
catch (RedirectNoService redirect) {
// This server can't handle this request.
// Place it in the `bad servers' set.
bad_servers = bad_servers.adjoin (server_to_try);
servers_to_try =
servers_to_try.union
(redirect.alternateServers().difference
(lazy_servers.union (busy_servers.union (dead_servers.union (bad_servers)))));
}
catch (RedirectReadOnly redirect) {
// Server is read-write, but command is read-only.
// Place server in the `lazy' list.
lazy_servers = lazy_servers.adjoin (server_to_try);
// More alternates
servers_to_try =
servers_to_try.union
(redirect.alternateServers().difference
(lazy_servers.union (busy_servers.union (dead_servers.union (bad_servers)))));
}
catch (RedirectBusy redirect) {
// This server is too busy, maybe try others.
// Place it in the `busy' list.
busy_servers = busy_servers.adjoin (server_to_try);
servers_to_try =
// add the new alternates to the servers_to_try
servers_to_try.union
(redirect.alternateServers().difference
(lazy_servers.union (busy_servers.union (dead_servers.union (bad_servers)))));
}
catch (RedirectContinue redirect) {
busy_servers = busy_servers.adjoin (server_to_try);
priority_flag = false; // new command starts at low priority
command = redirect.newCommand();
servers_to_try =
servers_to_try.union
(lazy_servers.union
(busy_servers.union
(bad_servers.union (redirect.alternateServers()))))
.difference (dead_servers);
busy_servers = new Set ();
lazy_servers = new Set ();
bad_servers = new Set ();
}
-------
public void serveCliResponse (boolean debugTrace)
throws RedirectNoService, RedirectBusy, RedirectContinue, RedirectReadOnly
{
String redirect_type = st.nextToken ();
Set alternates = new Set ();
if (redirect_type.equals (":NO-SERVICE"))
throw new RedirectNoService (alternates);
if (redirect_type.equals (":BUSY"))
throw new RedirectBusy (alternates);
if (redirect_type.equals (":READ-ONLY"))
throw new RedirectReadOnly (alternates);
if (redirect_type.equals (":CONTINUE"))
throw new RedirectContinue (alternates, new_command);
}
--
~jrm
: Specifically, when working in *non-FP languages*:
:
: -- which FP concepts/techniques/advantages are still
: available/usable/practical in real production work?
: -- for each, to what extent? (on rare occasions, almost always, etc.)
: -- which ones might I be tempted to use but should probably avoid (and
: why)?
: -- any other advice/tips on when and when not to use them?
: -- any advice for *how* best to do so (good libraries, a style of
: modularization, etc.)?
: -- any other practical advice for getting the most benefit out of FP
: study if most real production work will still be in mainstream
: languages for the foreseeable future? (particular books or online docs
: you recommend, language recommendations for this purpose, study
: techniques such as "I always prototype X-type apps in FP language Y
: before implementing in non-FP lang Z", things to be wary of, etc.)
:
: Any advice or suggestions regarding any of the above items would be
: appreciated.
There have been circulating in the c++ community for several years certain
"rules of thumb" which mirror some functional approaches much more than they
mirror some common object techniques. In particular, in algorithmics, it is
often much better for reuse to not associate functions of general form with
a class, and it is argued quite convincingly that this actually increases
encapsulation. In other words, instead of uber class hierarchies with
classes containing many members, a better rule of thumb is to use shallow
hierarchies and detach any functions that can be defined in terms of the
public interfaces (cf. Scott Meyers' "How non-member functions increase
encapsulation" in the c/c++ user's journal). Interfaces should be minimal.
This gives the immediate benefit that generic algorithmics can be written in
O(M + N) "pieces", as opposed to O(MN) (for M functions on N types
overlapping in this functionality), but also it allows more functional
concepts of reuse, such as functional composition and adaption.
Additionally, the more imperative state one must manage, the more difficult
it is to provide robust resource management. Although there are idioms such
as RAII, striving for declarative semantics just makes the task much easier.
--
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
galathaea: prankster, fablist, magician, liar
If you're on the IA31 architecture, the psyco JIT compiler speeds
Python right up again. To the point where you don't have to write
'optimised' Python, but can express yourself more naturally. Check it
out: http://psyco.sourceforge.net
Stefan,
--
Stefan Axelsson (email at http://www.cs.chalmers.se/~sax)
I fail to understand what do you mean by functional loops and how they
are related to the range function. Do you mean Python loops are functional
loops? Or do you mean for-each is a functional loop since it is a function
and not a macro? Just confused about the terminology.
Michele Simionato
My point was not about performances: even if the performance of functional
programming in Python was excellent, still I would not use functional
techniques too much, since there are alternative (i.e. OOP programming)
which are better supported by the language and have advantages in terms
of readability (i.e. write in the paradigm people are used to, if you
want your code to be read by others), self-documentation (i.e. Python the
introspection features are very strong for classes and methods, very
poor for functions inside functions) and extensibility (i.e. it is
easier to add methods to a class than to add inner functions to a
nested function).
Michele Simionato
In FP, you want to compose functions to implement your specs. So, a loop in
functional programming is something that iterates an action over a recursive
data structure (incl N). Try to program like that instead of using Python's
imperative loops and you will have a small revelation.
Here is a teaser:
;; add up numbers in a vector
(define A (vector 0 1 2 3 4 5 6 7 8 9))
;; FP:
(apply + (vector->list A))
;; Python or your favorite imperative (mindset) language:
(let ([sum 0])
(for-each (lambda (i)
(set! sum (+ sum (vector-ref A i))))
(range (vector-length A)))
sum)
-- Matthias
> Here are a couple of very specific things:
>
> 1. You don't need to name *every* intermediate expression:
>
> redirect_loop (new Set ().adjoin (new URL (server_uri)));
But you should - at least in most languages. The "new" operators may
fail, and you have to do error handling.
Well, at least that's what happens in C++ (since it doesn't have garbage
collection, and you're supposed to clean up in case of failure).
Of course, this problem is just waiting for a Maybe monad.
Point being, you don't have Maybe in most non-functional languages. (I'm
not sure that it can be sensibly expressed in C++ anyway... it may be
difficult to cope with all these failure modes, while something wrapped
in Maybe may not have any side effects except returning Nothing).
Of course, things are a *lot* simpler in Java - garbage collection makes
this style useful. Just watch out for side effects - say, if
redirect_loop has side effects, it may be a bug if it's called after a
failure in "new URL".
Regards,
Jo
--
Currently looking for a new job.
Cheers,
Michael
And in Java you do have GC, so take advantage of it. It really really allows
for prettier, more succinct, cleaner code.
While I miss some of C++'s expressiveness, I simply can no longer live without
garbage collection. Gone are the days where 90% of the bugs were bad pointer
problems.
> Of course, things are a *lot* simpler in Java - garbage collection makes this
> style useful. Just watch out for side effects - say, if redirect_loop has side
> effects, it may be a bug if it's called after a failure in "new URL".
Exceptions force you to deal with failures, such that redirect_loop won't even
be called:
try
{
redirect_loop (new Set ().adjoin (new URL (server_uri)));
}
catch (MalformedURLException badUrl)
{
// ... report or rethrow ...
}
--
Cheers, The Rhythm is around me,
The Rhythm has control.
Ray Blaak The Rhythm is inside me,
rAYb...@STRIPCAPStelus.net The Rhythm has my soul.
I see. I should point out that I have already looked at SRFI-1 in the
last few weeks, and I am already a converted to fold, fold-right and
company. It is quite easy to think functional for me, probably due to
my mathematical background. Also macro programming fits my mind quite
easily, I was already in the right mind set. Continuations, OTOH,
are quite new and they still need some thinking on my part.
P.S. in Python 2.3 you would do sum(range(10)), in older versions
reduce(operator.add,range(10)). So, there is already some support
for FP. The main issues are the limited lambda and the unhappy scope
rules.
> Joe Marshall wrote:
>
>> Here are a couple of very specific things: 1. You don't need to
>> name *every* intermediate expression:
>> redirect_loop (new Set ().adjoin (new URL (server_uri)));
>
> But you should - at least in most languages. The "new" operators may
> fail, and you have to do error handling.
It *ought* to throw.
> Well, at least that's what happens in C++ (since it doesn't have
> garbage collection, and you're supposed to clean up in case of
> failure).
Can't you redefine it to do the right thing?
I snarfed that from Java code I wrote. No parameterized types there.
> Matthias Felleisen <matt...@ccs.neu.edu> wrote in message news:<c0ima6$iss$1...@camelot.ccs.neu.edu>...
>>Here is a teaser:
>>
>>;; add up numbers in a vector
>>
>>(define A (vector 0 1 2 3 4 5 6 7 8 9))
>>
>>;; FP:
>>(apply + (vector->list A))
>>
>>;; Python or your favorite imperative (mindset) language:
>>(let ([sum 0])
>> (for-each (lambda (i)
>> (set! sum (+ sum (vector-ref A i))))
>> (range (vector-length A)))
>> sum)
>>
>>-- Matthias
>
>
> P.S. in Python 2.3 you would do sum(range(10)), in older versions
> reduce(operator.add,range(10)).
Excuse me, I should have not used such a stupid sample vector:
(vector -1 9 0 3 2 55 -100)
> So, there is already some support
> for FP. The main issues are the limited lambda and the unhappy scope
> rules.
Sure but GvR disavows FP programming, and the real problem is that every
built-in method returns unit (is of void type). Even if you wanted to (we
tried), you can't maintain an FP style in this world.
-- Matthias :-)
> Joachim Durchholz <joachim....@web.de> writes:
>
>> Well, at least that's what happens in C++ (since it doesn't have
>> garbage collection, and you're supposed to clean up in case of
>> failure).
>
> Can't you redefine it to do the right thing?
Well, sort of - it's quite difficult and requires a lot of replicated
boilerplate code to make it watertight. Catching exceptions that a C++
constructor may throw is *messy*.
That's why "two-step construction" is one of the more common C++ idioms.
You write a simple construct that cannot throw exceptions (i.e. you dumb
it down as far as possible, to the point that it doesn't do anything
useful other than allocate the object), then you call an initialization
function and handle any exceptions that come out of that. Since the
dumbed-down constructor can't do anything but fail due to memory
allocation problems, you're guaranteed that there's no memory leak.
(There are still some caveats to worry about...)
The downside is that two-step initialization requires assignment of the
intermediary result to a name. (Of course, a function not based on a
class could do that as well. But that's non-OO *gg*)
Then sum([-1,9,0,3,2,55,-100]) ;-)
> > So, there is already some support
> > for FP. The main issues are the limited lambda and the unhappy scope
> > rules.
>
> Sure but GvR disavows FP programming,
True
> and the real problem is that every
> built-in method returns unit (is of void type). Even if you wanted to (we
> tried), you can't maintain an FP style in this world.
I think you are referring at the implementation level. At the user level
it is quite possible to do FP in Python, it is just ugly. For instance,
since there is no "set!", you must use tricks such as to wrap variables in
a list and modify the list. This is ugly, so people don't do that. It is
also true that the uglyness is on design since Guido wants to have "only
one obvious way to do it" (which is not the FP way).
Michele Simionato
Its about the same really as writing a C function that constructs a complex
object and gracefully handles failure. Its a bit of a pain in both cases,
and you can easily get to experience the same pain in garbage-colected
languages if you are handling things like database connections.
>
> Regards,
> Jo
> --
> Currently looking for a new job.
>
--
Sander
+++ Out of cheese error +++
Nope. You obviously haven't programmed in C++, or you're not aware of
the all the details.
C++ has exceptions, C doesn't. C++ has default constructors, C doesn't.
A C++ constructor will automatically run all the constructors of the
base classes, with no means of catching exception in the base class
constructors [*]; any uncaught exception here will leave the
object-in-creation being left lying around with no owner referring to
it, hence nobody being responsible for destroying it. Which means you
have a memory leak.
[*] That's a white lie: you *can* catch such exceptions. But the syntax
for that is incredibly ugly, and the exception handling code for that
case is often a verbatim copy of another exception handler within the
constructor code. It's one of the worst instances of language design I
have seen in my life, rivalled only by the ugliness of RPG IV.
> and you can easily get to experience the same pain in garbage-colected
> languages if you are handling things like database connections.
No, that's just cleaning up where you must.
Besides, there are enough idioms in FP that allow a separation of
concerns. C++ exception handling in constructors gives you no means of
doing that - you have to write the same boilerplate code over and over
again. (And check that it was done correctly. And test and debug it. Ack.)
Alternatively, you can adopt a "two-step construction" idiom. Which
means that every single object has a "constructor" and an "initializer,
and instead of writing
display (new url ("http://www.foo.com"))
you have to write
HttpConnection conn = new url ("http://www.foo.com")
conn.open()
display (conn)
which is bearable for trivial examples, but incredibly painful if you
handle just a modest amount of intermediate results within a routine. (I
suspect that a large part of the "higher expressivity" (i.e. less lines
of code per function point) comes from the fact that you don't have to
name and initialize every intermediate result. But that's just an
educated guess.)
> Sander Vesik wrote:
>> Its about the same really as writing a C function that constructs a
>> complex
> Joachim Durchholz <joachim....@web.de> writes:
> A C++ constructor will automatically run all the constructors
> of the base classes, with no means of catching exception in the base
> class constructors [*];
You guys are funny. Every C++ program I ever used handled constructor
exceptions the same way: dumping core.
--
~jrm
| Sander Vesik wrote:
| > In comp.lang.scheme Joachim Durchholz <joachim....@web.de> wrote:
| >
| >>Well, sort of - it's quite difficult and requires a lot of replicated
| >>boilerplate code to make it watertight. Catching exceptions that a C++
| >>constructor may throw is *messy*.
| > Its about the same really as writing a C function that constructs a
| > complex
| > object and gracefully handles failure. Its a bit of a pain in both
| > cases,
|
| Nope. You obviously haven't programmed in C++, or you're not aware of
| the all the details.
Ahem.
| C++ has exceptions, C doesn't. C++ has default constructors, C
| doesn't. A C++ constructor will automatically run all the constructors
| of the base classes, with no means of catching exception in the base
| class constructors [*]; any uncaught exception here will leave the
| object-in-creation being left lying around with no owner referring to
As part of stack unwinding, cleanups (i.e. destructors) are run for
automatic objects.
| it, hence nobody being responsible for destroying it. Which means you
| have a memory leak.
An object that is partially constructed or partially destroyed will
have destructors executed for all of its fully constructed
subobjects, that is, for subobjects for which the constructor has
completed execution and the destructor has not yet begun
execution. Should a constructor for an element of an automatic array
throw an exception, only the constructed elements of that array will
be destroyed. If the object or array was allocated in a newexpression
and the newexpression does not contain a newplacement, the deallocation
function (3.7.3.2, 12.5) is called to free the storage occupied by the
object; the deallocation function is chosen as specified in
5.3.4. If the object or array was allocated in a newexpression
and the newexpression contains a newplacement, the storage occupied
by the object is deallocated only if an appropriate placement
operator delete is found, as specified in 5.3.4.
The programming technique known as RAII (Resource Acquisition Is
Initialization is based on that).
| [*] That's a white lie:
At least, try to make it credible ;-/
-- Gaby
> You guys are funny. Every C++ program I ever used handled constructor
> exceptions the same way: dumping core.
one size fits all.
----
Garry Hodgson, Technology Consultant, AT&T Labs
Be happy for this moment.
This moment is your life.
Sounds interesting. Could you post an example / do you have links to
one? That'd be nice.
Regards,
Mario.
Uh... it's been a while, and I didn't retain any code from that time.
The first hit from googling for
"C++ constructor" exception
gave me
http://www.gotw.ca/gotw/066.htm
which is a pretty complete description of how to do that and all the
ramifications.
I just hope that your C++ compiler implements function try blocks
properly for constructors (and the function try block syntax is what I
referred to with "incredibly ugly" - you wrap the entire body of the
function into a try-catch block, which is quite heavyweight syntax for a
simply problem).
I have to take some of my words back - code duplication is probably not
necessary if you really, really stick with the practices recommended in
that article. (When I last used C++, I didn't have that article
available and finally got some very annoying code duplication, and it
seems now that I could have done better.)
It's an excellent example of "C++ gives you lots of options for shooting
yourself, a single way of doing it correctly, and making the syntax so
ugly that you don't ever want to code that way".
It seems that C++ is nearer to Cobol than I ever imagined (there are
other similarities, like "it's incredibly verbose", "everybody uses it
despite inferior design", "it's not going to go away for decades",
"maintaining it is painful", and other parallels that I'm too lazy to
remember right now...)
> Joachim Durchholz <joachim....@web.de> writes:
>
> | Sander Vesik wrote:
> | > In comp.lang.scheme Joachim Durchholz <joachim....@web.de> wrote:
> | >
> | >>Well, sort of - it's quite difficult and requires a lot of replicated
> | >>boilerplate code to make it watertight. Catching exceptions that a C++
> | >>constructor may throw is *messy*.
> | > Its about the same really as writing a C function that constructs a
> | > complex
> | > object and gracefully handles failure. Its a bit of a pain in both
> | > cases,
> |
> | Nope. You obviously haven't programmed in C++, or you're not aware of
> | the all the details.
>
> Ahem.
>
> | C++ has exceptions, C doesn't. C++ has default constructors, C
> | doesn't. A C++ constructor will automatically run all the constructors
> | of the base classes, with no means of catching exception in the base
> | class constructors [*]; any uncaught exception here will leave the
> | object-in-creation being left lying around with no owner referring to
>
> As part of stack unwinding, cleanups (i.e. destructors) are run for
> automatic objects.
I meant subobjects (i.e. objects inherited from parents).
C++ forces you to use smart pointers and other advanced stuff if you
wish to avoid memory leaks.
It can be done, but you have to carefully code for it.
> The programming technique known as RAII (Resource Acquisition Is
> Initialization is based on that).
It's just that RAII forces programmers to tackle two complicated issues
at the same time: C++ construction/destruction and resource
acquisition/release, complicated by advanced features like initializer
lists and function try-catch blocks.
I'm really wondering who's expecting anybody to learn C++ to an
accceptable, ready-for-production level in less than two years... I have
seen people catch up on Eiffel in less than six months, and I'd think
that learning a functional language takes considerably less. (All
figures excluding time for learning the paradigms - I'm assuming
programmers who already know the paradigm but learn the language.)
Anyway, the relevant point still stands: C++ is sufficiently removed
from C that handling failure is an entirely different issue in that
language. Most differences lie in the area of object
construction/destruction, with some additional considerations due to the
availability of exceptions in C++.
> Joe Marshall wrote:
>
> > You don't need to name *every* intermediate expression:
>
> But you should - at least in most languages. The "new" operators may
> fail, and you have to do error handling. Well, at least that's what
> happens in C++ (since it doesn't have garbage collection, and you're
> supposed to clean up in case of failure).
Languages without garbage collection must die. The ones with
*nothing* must die first, and the ones with only reference counting
must eventually die as well, or get real gc. The days are gone when
it was acceptable to spend dozens of programmer hours finding a memory
leak (or, worse, leave it in and allow apps to periodically crash for
no good reason) in order to save a few measley bytes or hertz. The
human's time is now worth more than the computer's resources in
virtually every circumstance.
--
$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}}
split//,"ten.thgirb\@badanoj$/ --";$\=$ ;-> ();print$/
> You guys are funny. Every C++ program I ever used handled constructor
> exceptions the same way: dumping core.
C++ is high on the list of Languages That Must Die, IMO.
> michele....@poste.it (Michele Simionato) writes:
>
> > Still, I do think it is bad practice to code Python with a Scheme
> > mindset, just as it is bad practice to code Scheme with a Python
> > mindset.
>
> I don't.
>
> A friend of mine once remarked that even my C code looked like
> Scheme. He meant it as a compliment and I took it as one.
Yes and no. It's fine to program in one language using some
constructs and approaches that come from the mindset of another
language; in fact, that's good. However, you don't want to be
ignorant of the mindset of the language you *are* programming in,
either. The best example I can think of for this is people who
previously know only procedural programming (especially C) and then
start learning Perl. At first there are certain whole classes of
mistakes that they make because of fundamental misunderstandings that
they have. The most common case is that they think of things as
statements that would be statements in C -- but Perl doesn't have
statements, or anything very much like them. This leads to code like
the following:
print (3 + 6) * 2;
The procedural programmer, thinking of that as a statement, assumes
that it will print 18, which of course is ludicrous, because print is
a list operator function. Therefore, the parentheses set off its
argument list, just like they would with any other function, and its
return value gets multiplied by 2 in void context (which will generate
a warning, but C programmers are typically so used to ignoring
warnings that they often don't notice this).
A little more familiarity with the language you're using will clear up
this sort of problem. It's certainly fine to use a procedural
paradigm in Perl, similar to that in C, but you have to be aware of
the fact that you're doing that and do it deliberately, rather than
just assuming the language is that way (because, it's not entirely).
The same sort of things would apply to writing Python code with a
Scheme mindset, I would think. As long as you know Python well enough
to carry over the Scheme paradigms effectively, all is well and good,
but you wouldn't want to just assume that Python works like Scheme.
> Just an anedoctical example: I recently wrote a state machine in
> Scheme which (quite naturally) came out to be full of closures.
> Then I translated it in Python and discovered (with my surprise)
> that I could maintain the functional structure essentially
> unchanged.
This is not surprising to me. I'm not by any means a Python
programmer, but I know just enough of the language to be pretty sure
it's a multiparadigmatic language (though not quite as expressly so as
Perl). The Python docs spend a lot of time talking about OO stuff,
but the language is clearly more flexible than that.
> Nevertheless, I would never have used that style if I had coded in
> Python from the beginning (I would have used classes instead).
Next question: would it (the Python implementation) have been better
coded using classes, or is the functional implementation just as good?
Id est, was your implementation warped undesirably by the bias of
translating from the other language, or OTOH is your Python coding
normally biased unfairly against the function paradigm, perhaps due to
the way the Python documentation emphasizes OO?
> So, certainly the functional paradigm is independent from the
> language.
Quite. I find programming in Perl that closures are an indispensible
tool to have at my disposal. I don't use them in every program, of
course, but I use them with some regularity. Certain classes of
problems are just best solved that way.
> Still, I do think it is bad practice to code Python with a Scheme
> mindset, just as it is bad practice to code Scheme with a Python
> mindset. I personally make a personality split and I code with
> different styles and paradigm according to the target language,
> unless for some reason I need to translate literally from one to the
> other (as in that case).
This is somewhat different from my approach. I freely mix whatever
paradigms are available to me in the language I'm using, based on what
best solves the problem at hand. This may be partly because my
primary language is Perl, which is an expressly multiparadigmatic
language. Functional, contextual, and procedural programming are all
equally straightforward in Perl, and OO is also possible (though a bit
klunky in Perl5; Perl6 is going to fix this, but it's not ready yet )-:
> OTOH, it is by no means guaranteed that a paradigm which is superior
> in language X is also superior in language Y, so my advice is to use
> the paradigm the language support the best.
Often a particular problem is best solved with a certain paradigm, or
at least better solved with some paradigms than with others.
The idea that functional programming is straightforward in Perl only lasts
until you try to write, say, a CPS parser in it. The point being that Perl
doesn't support tail recursion, and so at the very least it'll use a huge
amount of space when it doesn't need to; at worst, you'll run out of space
if your machine survives the humongous memory allocation that it does with
recursive programs.
To see the problem, try monitoring memory usage with the following program:
sub foo {
my $x = shift;
print $x if $x%100000 == 0;
foo ($x+1);
}
foo(0);
This will use up hundreds of megabytes of memory, startlingly quickly. The
equivalent Scheme, ML etc. will run without any impact on memory usage,
until numeric overflow occurs:
(define (foo x) (if (zero? (remainder x 100000)) (display x)) (foo (+ x
1)))
(foo 0)
Although it's true that experienced functional programmers can use languages
like Perl & Python in various functional ways, often by relying on
workarounds, there's a problem with claiming that these languages support
functional programming: it encourages Perl/Python/Ruby programmers to
believe that they understand FP and have access to it in their language, so
they miss out on the opportunity to learn more about one of the more
important and enlightening programming paradigms.
It's particularly unfortunate that the generally poor support for FP in
these languages often leads people to conclude that the functional paradigm
is flawed, when the fault actually lies with the non-FP language they're
using.
Anton
> "Jonadab the Unsightly One" wrote:
>> This may be partly because my primary language is Perl, which
>> is an expressly multiparadigmatic language. Functional, contextual,
>> and procedural programming are all equally straightforward in Perl,
>
> The idea that functional programming is straightforward in Perl only lasts
> until you try to write, say, a CPS parser in it. The point being that Perl
> doesn't support tail recursion, and so at the very least it'll use a huge
> amount of space when it doesn't need to; at worst, you'll run out of space
> if your machine survives the humongous memory allocation that it does with
> recursive programs.
Languages that are not `safe for space' are fundamentally broken.
--
~jrm
I don't think Python is less flexible than Perl, at least in the stuff
that matters (it is much less flexible in the syntax, of course).
However, doing things in different ways (i.e. not Guido's way) is
discouraged. The Zen of Python explicitely says:
"There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch."
So, in principle, there should be only one "obviously Pythonic" style.
Pythonistas think of this kind of uniformity as of a worthwhile goal,
Perl programmers probably see this as a disgusting perspective ;)
> Next question: would it (the Python implementation) have been better
> coded using classes, or is the functional implementation just as good?
What does it mean better? Faster, more efficient? Or simpler to write,
simpler to maintain, simpler to read for other Pythonistas?
I haven't never benchmarked applications using a functional style
vs. applications using an object oriented style, so I can't say,
but I don't expect to see big differences. OTOH, writing the
application in the obvious OO style has big advantages if the
application has to be maintained by another Python programmer not
very familiar with functional techniques.
In my experience the difference between OO style and functional style
(in Python) is more a matter of personal taste than a matter of substance.
In Scheme the situation is different, since OO is not in the standard
and you are naturally encouraged to think functionally.
> Id est, was your implementation warped undesirably by the bias of
> translating from the other language,
Yes, using all those closures would look bizarre, "unpythonic" to
another Python programmer looking at that code.
> or OTOH is your Python coding
> normally biased unfairly against the function paradigm, perhaps due to
> the way the Python documentation emphasizes OO?
I would not say "unfairly", but it is true that I would use FP techniques
much more if I had better support in the language. By design the language
itself (i.e. it is not a matter of documentation only) has good
support for OOP, but only minimal support for functional programming.
For instance, my first post on c.l.p. was originated by my confusion
about the scope rules in closures (*):
def make_adders(n):
return [(lambda x: x+i) for i in range(n)]
add0,add1=make_adders(2)
print add0(0) #=> 1 !! this is surprising !!
print add1(0) #=> 1
(I guess Perl is less surprising in this respect).
The way to get what I wanted was this:
def make_adders(n):
return [(lambda x,i=i: x+i) for i in range(n)]
add0,add1=make_adders(2)
print add0(0) #=> 0
print add1(0) #=> 1
You see a typical example of "Pythonicity" here: this way is not obvious,
so it is NOT the right way to go. The scope rules are bizarre on purpose,
since people are not expected to use closures, they should use objects:
class adder(object):
"Returns callable objects acting as adders."
def __init__(self,i):
self.i=i
def __call__(self,x):
return x+self.i
def make_adders(n):
return [adder(i) for i in range(n)]
add0,add1=make_adders(2)
print add0(0) #=> 0
print add1(0) #=> 1
This is the "obvious" way for a Python programmer (I am sure Scheme or
Perl programmers here will think the functional version is clearer, but
let it be) and has various advantages over the functional version:
- the adder logic is encapsulated in an object with a self-documenting name;
- I can add docstrings to the adder class and not to the lambda;
- I can later extends the class whereas I cannot derive from the lambda;
- I dont'use the esoteric name "lambda" which may scare my readers ;)
OTOH, if I had good scope rules in the language, I would code the
adder with a lambda, and my code would be less Pythonic, i.e. less
readable to others. So, Guido doesn't give me better scope rules and
"ipso facto" guarantees that the only *obvious* way to do it is the OO way.
Uniformity of style is a very valuable thing in a corporate environment,
less so if you code for yourself.
>
> > Still, I do think it is bad practice to code Python with a Scheme
> > mindset, just as it is bad practice to code Scheme with a Python
> > mindset. I personally make a personality split and I code with
> > different styles and paradigm according to the target language,
> > unless for some reason I need to translate literally from one to the
> > other (as in that case).
>
> This is somewhat different from my approach. I freely mix whatever
> paradigms are available to me in the language I'm using, based on what
> best solves the problem at hand. This may be partly because my
> primary language is Perl, which is an expressly multiparadigmatic
> language. Functional, contextual, and procedural programming are all
> equally straightforward in Perl, and OO is also possible (though a bit
> klunky in Perl5; Perl6 is going to fix this, but it's not ready yet )-:
The choice of the paradigm is somewhat restricted by the language,
not only by the problem. For instance, I am aware of the possible warts
of using closures in Python, so I have two choices: 1) I am very careful, 2)
I switch to the OOP paradigm. More often than not 2) is the simplest
choice. I am sure that in Perl you would tend to prefer the functional
way over the object oriented way, instead.
> > OTOH, it is by no means guaranteed that a paradigm which is superior
> > in language X is also superior in language Y, so my advice is to use
> > the paradigm the language support the best.
>
> Often a particular problem is best solved with a certain paradigm, or
> at least better solved with some paradigms than with others.
That's true, but still the language may encourage you to go with a
certain paradigm.
Coming back OT, i.e. to Scheme, I would say in Scheme (or Lisp) these
issues are less relevant, since in these languages you have the
opportunity to modify the language itself to follows the paradigm
you have in mind. Still, even Scheme encourage some paradigm over others:
for instance you are encouraged to solve a problem recursively and not
iteratively, to think in terms of higher order functions, etc.
Summarizing, I think that if you want to use a paradigm not supported by
language X, you have two choices: 1) change your mind: 2) switch to language
Y. In the case of X = Scheme, Y can be a customization of Scheme written
by yourself; still you have to to write the language yourself, which
is a bigger effort than using the native constructs. I.e. I may
implement Python "for" loops in Scheme, but more often than not I
am better off by using the native looping constructs.
(*) notice that I was naturally inclined to think in a FP way even
before studying Python and before studying Scheme: I was introduced
to map and friends by Mathematica and Maple. I had no problems at
all in learning the FP approach, whereas I needed a more substantial
effort to grasp OOP.
> However, doing things in different ways (i.e. not Guido's way) is
> discouraged. The Zen of Python explicitely says:
>
> "There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch."
I wonder how Guido puts his pants on in the morning.
> Joachim Durchholz <joachim....@web.de> writes:
> But you should - at least in most languages.
So much for referential transparency. You shouldn't have to.
> Joe Marshall <j...@ccs.neu.edu> writes:
>
>> A friend of mine once remarked that even my C code looked like
>> Scheme. He meant it as a compliment and I took it as one.
>
> Yes and no. It's fine to program in one language using some
> constructs and approaches that come from the mindset of another
> language; in fact, that's good.
I think that the proper mindset transcends the language.
> However, you don't want to be ignorant of the mindset of the
> language you *are* programming in, either.
Yes, you should know what you are up against.
For instance, function pointers are first-class objects in C, but they
are not closures. You can write higher-order function in C (the
syntax is quite painful, but the results are nice), but you must
always remember that the `environment' is not part of the `function'
and must be passed separately.
Definitely!
One language I worked with didn't have a "new" operator, forcing me to
assign all newly-created objects to names. This most definitely sucked.
Of course, doing without those "new" operators sprinkled everywhere is
even better :-)
In parallel assignment, order is an implementation issue:
left_leg, right_leg = pants()
;)
-M
--
Michael J. Fromberger | Lecturer, Dept. of Computer Science
http://www.dartmouth.edu/~sting/ | Dartmouth College, Hanover, NH, USA
>
> Languages that are not `safe for space' are fundamentally broken.
>
Is Common Lisp fundamentally broken? Or are implementations space safe,
notwithstanding the lack of mandate in the standard?
--
Grzegorz Chrupała | http://pithekos.net | grze...@jabber.org
gosh -bE "begin (print \
(map (cut number->string <> 36) '(18 806564 1714020422))) (exit)"
> Joe Marshall wrote:
>
>>
>> Languages that are not `safe for space' are fundamentally broken.
>>
>
> Is Common Lisp fundamentally broken?
I was going for hyperbole, but I would consider a Common Lisp
implementation that could not be made tail recursive through the
appropriate magic to be seriously flawed.
> Or are implementations space safe, notwithstanding the lack of
> mandate in the standard?
Some are. Franz Allegro and Lispworks can be made properly tail
recursive with the correct compiler switches.
> The point being that Perl doesn't support tail recursion,
Not true, though you have to know the magic words.
> To see the problem, try monitoring memory usage with the following program:
>
> sub foo {
> my $x = shift;
> print $x if $x%100000 == 0;
> foo ($x+1);
> }
> foo(0);
sub foo {
my $x = shift;
print $x unless $x%100000;
@_=($x-1); # <-- pass arguments
goto &foo; # <-- tail recursive call
}
foo(0);
/s
Unless you're talking about something new in a bleeding-edge version of
Perl, this doesn't seem to work - it eats memory just as fast. I tried it
in Perl v5.8.2, as well as a couple of earlier versions, under Debian, Red
Hat, and Windows.
If there is a newer version of Perl which has added some sort of tail
recursion optimization, then based on the above example, it would still not
be very practical, if used widely in real programs. It would put recursive
programs firmly into the same category as OOP in Perl 5: you would be able
to do it, but it would be messy, bug-prone, and far from natural. It would
still be extremely misleading to claim that such a language "supports
functional programming", and it would be a disservice to Perl programmers
interested in learning about techniques beyond the confines of Perl.
Anton
Nice post, Michele.
You reminded me of an epigram I came up last year:
When a language makes something hard to say, there are three options: change
the language, abuse the language, or say something different.
--
.:[ dave benjamin: ramen/[sp00] -:- spoomusic.com -:- ramenfest.com ]:.
: please talk to your son or daughter about parametric polymorphism. :
This must have changed somewhere between 5.6.0 and 5.8.3, the two
versions I have available. The version I posted still grows over
time, but much more slowly than your original. My guess is that this
has to do with the lexical "my $x" not being freed; this seems like a
bug in the implementation. The following version avoids creating a
lexical, and does not grow:
sub foo {
print $_[0] unless $_[0]%100000;
$_[0]--;
goto &foo
}
$x=1;
foo($x);
For an explanation "perldoc perlfunc", and search for "goto &NAME".
> [re recursive programming]
>
> you would be able to do it, but it would be messy,
That's just syntax ;).
> bug-prone,
Explain. Are you referring to the extra step in a function call?
> and far from natural.
I agree it's unnatural, though I don't know about the "far from".
> It would still be extremely misleading to claim that such a language
> "supports functional programming", and it would be a disservice to
> Perl programmers interested in learning about techniques beyond the
> confines of Perl.
Why is this misleading? Is the programmer misled about what tail call
elimination does? To cite another poster, does Lisp's lack of
guaranteed tail call elimination make it "extremely misleading to
claim that such a language 'supports functional programming'"?
And why is it a disservice? Is the programmer, after using tail calls
in Perl, unable to recognize or use them in other languages? At worst
the Perl programmer, wanting to use "goto &func" in his Scheme
program, will be surprised that he can use the same syntax as for a
normal function call. At best, he will have a decent understanding
of what the techinque actually does.
/s
> When a language makes something hard to say, there are three options: change
> the language, abuse the language, or say something different.
I generally say something abusive about the language.
--
~jrm
I tried with 5.6.0, 5.8.2, and some in between, and growth was unreasonably
fast in all cases. Are you saying 5.8.3 is better at this? I'm not sure I
believe that, based on what I've seen so far.
> My guess is that this has to do with the lexical "my $x" not
> being freed; this seems like a bug in the implementation.
"Bug" or "broken", whatever you call it, it prevents you from using the
language to do functional programming. It's quite likely that it's not a
simple thing to fix.
> The following version avoids creating a
> lexical, and does not grow:
>
> sub foo {
> print $_[0] unless $_[0]%100000;
> $_[0]--;
> goto &foo
> }
> $x=1;
> foo($x);
This version still grows without apparent limit under 5.6.0 and 5.8.2 - just
a bit more slowly than the previous examples. None of this seems to be
solving the problem, but it's certainly doing a good job of proving that
Perl doesn't *support* functional programming, no matter how many tricks are
used. It might be possible to simulate FP poorly, but that's not "support".
> > bug-prone,
>
> Explain. Are you referring to the extra step in a function call?
It requires the user to manually choose when to use which function call
mechanism, opening the possibility that the wrong one is used, leading to
space leaks at least, if not semantic errors. The point is, Perl does not
*support* recursive programming, or functional programming, and it's
misleading to claim otherwise.
> > and far from natural.
>
> I agree it's unnatural, though I don't know about the "far from".
No offense, but looking at the latest code sample above - in which a trivial
example is mangled beyond any hope of usability, yet still appears to suffer
from a serious space leak - this exchange is reminding me of the Monty
Python "Black Knight" skit, where the Black Knight continues to taunt his
opponent even after all of his limbs have been chopped off. ;)
> > It would still be extremely misleading to claim that such a language
> > "supports functional programming", and it would be a disservice to
> > Perl programmers interested in learning about techniques beyond the
> > confines of Perl.
>
> Why is this misleading? Is the programmer misled about what tail call
> elimination does?
It's misleading because it leads people to believe that what they can do (or
have to do) in Perl is in some way representative of functional programming.
It isn't - not even close.
We've just been focusing on one feature here: tail recursion. We haven't
even started on tail calls in general, and since the workarounds given so
far don't even fix pure self-recursion, it seems pointless to speculate on
what might happen with more complex, mutually recursive programs, or
programs written in CPS. And then there's the data structures: Perl doesn't
have recursive data structures, and if you try to use its existing types to
build recursive structures, you run into the same problems that make OOP in
Perl so clumsy.
> To cite another poster, does Lisp's lack of guaranteed tail
> call elimination make it "extremely misleading to claim that
> such a language 'supports functional programming'"?
The question of whether it's the specification or the implementation that
supports such a feature isn't particularly relevant when *neither* the
specification nor implementation supports it, as is the case in Perl. In
practice, Lisp implementations handle recursion far better than Perl.
> And why is it a disservice? Is the programmer, after using tail calls
> in Perl, unable to recognize or use them in other languages?
It's a disservice because it misleads programmers into believing that
they've gained an understanding of functional programming from within Perl
(or Python), when that's not the case at all. This seems to make many
people feel they don't need to investigate further - the myth that Perl
supports functional programming encourages a sort of "oh, is that all there
is to it?" reaction, when they in fact haven't been exposed to even a
fraction of what functional programming is about. People are good at making
up excuses not to learn things - giving them another such excuse by
pretending that Perl supports a paradigm that it doesn't, is a disservice.
> At worst
> the Perl programmer, wanting to use "goto &func" in his Scheme
> program, will be surprised that he can use the same syntax as for a
> normal function call. At best, he will have a decent understanding
> of what the techinque actually does.
That's not my experience in practice. Can you point to any significant
programs written in the style you've described? I'd be surprised to find
they exist, other than in small proof-of-concept examples. If people aren't
writing recursive programs as a matter of course, then they're not doing
functional programming.
Anton
What's functional programming? Could one say that Scheme doesn't
support functional programming because it has set!? Or that ML and
Haskell don't support functional programming because you can't define
anti?
[from a current c.l.s thread where
(define (anti f)
(lambda args (not (apply f args))))
]
--
Alex
There are flavors of functional programming - statically typed, dynamically
typed, pure, impure. Scheme is impure, but has a pure subset, so it has
both of those bases covered.
> Or that ML and Haskell don't support functional programming
> because you can't define anti?
That's because they're of the statically-typed flavor.
Although I don't think any of the above examples really demonstrate it,
there is a spectrum of support for functional programming. However, I'm
saying that the languages on the impoverished end of that spectrum don't do
an acceptable job of allowing someone to learn the principles of functional
programming, and they actively harm one's prospects of learning those
principles, as long as one restricts oneself to those languages.
If you want a litmus test for functional programming, it would relate to the
kinds of programs you can reasonably write in the language. Anything
written in CPS is a good test - almost any functional language can support
CPS, whether or not you would really choose to write programs that way in
the language.
By that test, two of the more important characteristics for functional
programming would be *correct* support for first-class lexical closures
(Perl has, Python doesn't), as well as space-safety in the presence of tail
calls (Perl doesn't have this, Stackless Python does). But there are other
requirements also, as I mentioned in my previous post, such as how well a
language supports recursive data structures.
I think it's encouraging that these languages are evolving in more
sophisticated directions, but it should be recognized that they're not there
yet, and anyone who wants to learn about functional programming should
explore at least one real functional language.
Anton
I was incorrect, although this example does grow under 5.6.0, it does not
grow under 5.8.2. (In my prior test, the Linux instance running 5.8.2 had
hung during a previous run in which all memory & swap was consumed, and I
ended up inadvertently only testing earlier versions.)
Anyway, I'll eagerly await the "bugfix" which allows one to safely use local
variables in procedures which perform tail calls. After that, a version
which can safely perform tail calls without explicit stack manipulation
would be nice...
Anton
> If you want a litmus test for functional programming, it would relate to the
> kinds of programs you can reasonably write in the language. Anything
> written in CPS is a good test - almost any functional language can support
> CPS, whether or not you would really choose to write programs that way in
> the language.
>
> Anton
>
Exactly! If you cannot program in CPS, the language is broken.
(I was just saying this yesterday.)
This naturally implies proper tail recursion and first-class lexical
closures.
--
~jrm
Either you admit that there is ALWAYS a way of making tail recursion or
other Glorious Constructs in *any* language, so none are *really* broken,
Or you say that all assemblers are broken.
This, in my eyes, spoils the word "broken" of any sense but your personal
appreciation.
Jerzy Karczmarczuk
(PS. What do you recommend to do with broken languages?
And with people (as myself) who profess broken English?)
Jerzy Karczmarczuk <kar...@info.unicaen.fr> writes:
>
> Either you admit that there is ALWAYS a way of making tail recursion or
> other Glorious Constructs in *any* language, so none are *really* broken,
I refuse to admit it!
Ok, one can obviously implement a Scheme interpreter in any
Turing-complete language, but that's not what I mean. If we consider
the `macro-expressibility' as suggested by Felleisen in `On the
Expressive Power of Programming Languages', then we can make the
distinction between tail-recursion being *provided* by the language
vs. tail-recursion simply being *implementable* in the language.
>
> Or you say that all assemblers are broken.
Actually, I think assembly code is second to Scheme in this regard.
In general (like x86 or PDP-11 sort of assembly code), assembly code
has poor support for closures, but full tail-recursion is trivial. In
addition, you can easily program in continuation-passing-style by
pushing multiple return addresses and allowing the callee to return to
the one of its choosing.
> This, in my eyes, spoils the word "broken" of any sense but your personal
> appreciation.
You say that as if my personal appreciation isn't the highest measure
of objective utility.
> Jerzy Karczmarczuk
>
> (PS. What do you recommend to do with broken languages?
> And with people (as myself) who profess broken English?)
It depends on how strongly you profess broken English. I imagine that
broken English is better than none at all for communicating to an
English speaker, but probably not as effective as fluent English.
Your broken English is certainly better than my broken French.
This example is beside the point, because it is overly trivial. For
functional programming you need tail-recursion for functions that
actually return something. How can a goto help in this situation?
- Andreas
--
Andreas Rossberg, ross...@ps.uni-sb.de
Let's get rid of those possible thingies! -- TB
Magic ;)
sub foo {
return $_[0] if $_[0] == $y;
$_[0]++; goto &foo
}
for (4..31) {
$y = 2**$_;
print "$y: ", foo($x=1);
}
/s
Check in the next "version" of "perl" to see if this "issue" has been
"addressed".
> After that, a version which can safely perform tail calls without
> explicit stack manipulation would be nice...
It's been on the to-do list for quite some time. As they say in the
Perl world, "patches welcome" ;).
/s
This sort of thing only drives home my original point. If you show a Perl
programmer how to program in a functional style using techniques like the
above, the typical reaction will be "why on earth would I ever want to
program like that?"
You can respond to this by saying that there are languages in which this
style is natural and elegant, and is even used to implement basic constructs
like loops. At that point, said Perl programmer is looking at you with a
funny expression, shaking his head, and backing away slowly. That
programmer has lost the opportunity to learn something important. That's
why it's misleading, and a disservice to Perl programmers, to claim that
Perl supports functional programming.
Anton
I have a patch that fixes this and a number of other problems, too. It's
called "Scheme" (substitute Lisp, ML, Haskell etc. to taste.)
Anton
Well, let me know when you've ported CPAN to Scheme, and I'll be
there. (Yes, I know about Slib. No, it's not the same.) I'll let
you know when I've added cons cells and tail call elimination to Perl.
/s
>>I have a patch that fixes this and a number of other problems, too.
>>It's called "Scheme" (substitute Lisp, ML, Haskell etc. to taste.)
> Well, let me know when you've ported CPAN to Scheme, and I'll be
> there.
The Schemers are embracing Python first. Then ...
<http://155.33.117.141:20080/servlets/spy-web/python-web.scm?page=home>
--
Jens Axel Søgaard
> You can respond to this by saying that there are languages in which this
> style is natural and elegant, and is even used to implement basic constructs
> like loops. At that point, said Perl programmer is looking at you with a
> funny expression, shaking his head, and backing away slowly. That
> programmer has lost the opportunity to learn something important. That's
> why it's misleading, and a disservice to Perl programmers, to claim that
> Perl supports functional programming.
perl supports FP only in that it allows you to simulate it, much like
it supports OO.
----
Garry Hodgson, Technology Consultant, AT&T Labs
Be happy for this moment.
This moment is your life.
Simulation is not support, in this context. Besides, Perl's ability to
simulate FP is poor and incomplete, as I've been pointing out.
Anton
> Garry Hodgson wrote:
> > perl supports FP only in that it allows you to simulate it, much like
> > it supports OO.
>
> Simulation is not support, in this context. Besides, Perl's ability to
> simulate FP is poor and incomplete, as I've been pointing out.
i believe we're in violent agreement here.
Joe Marshall <j...@ccs.neu.edu> wrote:
> Actually, I think assembly code is second to Scheme in this regard.
> In general (like x86 or PDP-11 sort of assembly code), assembly code
> has poor support for closures, but full tail-recursion is trivial.
Indeed, Steele's paper on tail calls ("Procedure Calls Considered
Harmful") focused on how easy it is to implement procedure calls as tail
calls in assembly. (In practice, I've found that it's harder than that
paper makes it seem, mostly because of procedure arguments.)
--
Bradd W. Szonye
http://www.szonye.com/bradd
I saw this awhile ago, and thought it was cool until I saw mention of
the 2-3 order of magnitude slowdown vs. CPython. I don't know if this
has improved, but hopefully this project will stay alive long enough
to produce a competitive (i.e. within 1 order of magnitude)
implementation, rather than having everyone just take their toys and
go home again. Have any of the regular CPython people bought into PLT
Spy?
/s
Any language benefits from having several implementations. You can use
Scheme for almost anything, due to the huge number of implementations.
If one implementor comes up with an idea worth copying, the other
implementors follow.
It seems as though you judge the implementation on speed alone?
That would be a mistake.
In the Scheme world it is not uncommon to use one Scheme implementation
for development and another for deployment. An example: The Stalin compiler
makes *very* fast code, due to it's many optimizations, but since it is a
whole program compiler, the compile time are not fit for development.
A good language implementation provides the programmer with good tools.
In case of DrScheme you get among other things an GUI editor, control
flow analysis, a profiler, a tool for test suites and syntactical analysis.
The translation from Python to Scheme allows the Python programmer
to use the *same* tools DrScheme provides for the Scheme programmer.
The implementors of DrScheme have namely implmented the tools in a
language independent way.
As an example consider the arrows in figure 2 in
<http://155.33.117.141:20080/spy-web/scheme-workshop-2003-web/scheme-workshop-2003.html>
They are drawn at the basis of the semantics of the underlying language -
it's not just a simple text analysis.
Note that from a Scheme programmers point of view, the scoop is that we can
use Python libraries from Scheme.
Oh - apropos the Perl discussion, did you notice that this implementation
supports tail call optimization?
(Poor Guido - now there is more than one way to make loops)
--
Jens Axel Søgaard
Hi, I am Spy's currently active developer. As Philippe said, I'm in the
process of integrating half of the CPython core C modules (String,
Integer, List, etc). It is my belief that we will gain a good amount of
speed by doing that, since they've been tweaking their C code for a while
now.
After that, I will go back to optimizing code generation. Tail call
optimization was easy and I've removed a lot of the other unnecessary
escape continuations as well. Locking down built-in methods and
collapsing constant expressions should give us another boost.
Then we can start using Python for FP...
- Daniel
I see your point. When all the implementations are of the exact same
standard, efficiency is pretty much all that matters to me. This
"efficiency" can be in speed, space, or edit-compile-debug time,
meaning that different implementations may be most efficient in
different circumstances.
What I didn't consider is that "Schemes" implement a number of
subtlely different languages (construed as semi-necessary extensions
beyond R5RS). This diversity is certainly a boon for language
implementors, but not so great for language users trying to try out
multiple implementations. For example, I've used both Bigloo and PLT,
and been bitten dealing with their different module systems.
> A good language implementation provides the programmer with good
> tools.
>
> In case of DrScheme you get among other things an GUI editor,
> control flow analysis, a profiler, a tool for test suites and
> syntactical analysis.
A better language implementation allows other programmers to write
their own tools, and to plug into existing ones. One reason I prefer
Bigloo to PLT is that it gives me an IDE to use within Emacs, and can
therefore immediately take advantage of its many charms. YMMV, of
course.
> As an example consider the arrows in figure 2 in
>
> <http://155.33.117.141:20080/spy-web/scheme-workshop-2003-web/scheme-workshop-2003.html>
>
> They are drawn at the basis of the semantics of the underlying language -
> it's not just a simple text analysis.
I think this kind of semantic analysis would be really cool if it can
be applied to languages more complicated than Scheme (e.g. Python). I
haven't used the PLT tools in quite awhile, but I enjoy having a
language-aware IDE such as those for Lisp and Java.
> Note that from a Scheme programmers point of view, the scoop is that
> we can use Python libraries from Scheme.
And you guys will rule the world if you can pull this off. Well, at
least that lesser corner of the world that isn't ruled by Perl ;).
> Oh - apropos the Perl discussion, did you notice that this
> implementation supports tail call optimization?
>
> (Poor Guido - now there is more than one way to make loops)
You realize, of course, that you've doomed PLT Spy as an "approved"
Python implementation, at least until you come up with an "unimport
MTOWTDI" statement. (Aside: it would be interesting (but hard) for
the compiler to detect common "synonymous" code constructs and warn
about the inconsistency).
/s
Gack! How much would you have had to change the semantics of Python
to just use native Scheme data types? While the operators
(e.g. division) have different semantics, the basic datatypes like
bignums, complexes, and strings, seem very similar. It seems like it
would avoid a world of hurt if you could avoid converting
primitives. I'm sure you looked at doing this, but I'm just curious
where the similarity breaks down, and how badly.
>>>Have any of the regular CPython people bought into PLT Spy?
>
> No yet but we are going to Pycon at the end of next month to recruit
> people! http://www.pycon.org/dc2004/talks/index_html#plt-scheme
So your pitch will be centered on the semantic information in the IDE?
Tragically, I'm more of a Perl aficionado, and I doubt you want to try
porting that to PLT (though there's work at Fontago porting it to
Parrot). Anyways...
/s
> (Aside: it would be interesting (but hard) for
> the compiler to detect common "synonymous" code constructs and warn
> about the inconsistency).
Do you mean an analysis that warns a programmer that they used a
tail-calling pattern in one place and a FOR loop in another?
I wonder why this would even be interesting, given that each has its
place. It would seem that the interesting analysis is instead one
that responds with
"Hey, you used this complicated tail-calling pattern, but a simple
FOR loop would have done just as well (unless you mean to extend
this in ways that would be more difficult with a loop)" (followed by
"If you hold on a moment, I'll generate the FOR version for you")
and
"Hey, you have this convoluted FOR with additional iteration
variables; did you consider using a simple tail-calling loop
instead?" (followed by ...)
At any rate, it's very important that the analysis and refactoring
tool prefix all its output with "Hey".
Shriram
* It's interesting from the Pythonic perspective that always doing
things one way makes it easier to understand code. If your code is
internally inconsistent (e.g. reflecting learning between having
written different parts), this will show you which parts could use
re-writing. If you're working with a number of people, this could
help enforce coding style standards.
* It's the same analysis as suggested simplifications, except with the
latter, you're either seeding the code with some exemplars, and/or
putting in some metric of code clarity (e.g. branches per
function).
> At any rate, it's very important that the analysis and refactoring
> tool prefix all its output with "Hey".
Hey, I think you're onto something here.
/s
We didn't use primitive Scheme types for primitive Python times because we
needed them to behave like all other Python objects, and a big cond at
every runtime operation would be hell. Luckily, we're making progress
with the C modules.
>>>>Have any of the regular CPython people bought into PLT Spy?
>>
>> No yet but we are going to Pycon at the end of next month to recruit
>> people! http://www.pycon.org/dc2004/talks/index_html#plt-scheme
>
> So your pitch will be centered on the semantic information in the IDE?
> Tragically, I'm more of a Perl aficionado, and I doubt you want to try
> porting that to PLT (though there's work at Fontago porting it to
> Parrot). Anyways...
You should give it a shot. All you need is the Perl BNF and what it means.
Daniel
>>Gack! How much would you have had to change the semantics of Python
>>to just use native Scheme data types? While the operators
>>(e.g. division) have different semantics, the basic datatypes like
>>bignums, complexes, and strings, seem very similar. It seems like it
>>would avoid a world of hurt if you could avoid converting
>>primitives. I'm sure you looked at doing this, but I'm just curious
>>where the similarity breaks down, and how badly.
>>Tragically, I'm more of a Perl aficionado, and I doubt you want to try
>>porting that to PLT (though there's work at Fontago porting it to
>>Parrot).
Suppose somone offered you PerlPlus, a language with the exact syntax of your
favorite programming language but a language whose programs always behave a
little bit differently than what you expect. Would you look at this PerlPlus twice?
-- Matthias
> You should give it a shot. All you need is the Perl BNF and what it means.
"Perl's grammar can not be reduced to BNF. The work of parsing Perl
is distributed between yacc, the lexer, smoke and mirrors."
--- Chaim Frenkel as quoted in the Perl FAQ
Does Perl even *have* semantics?
--
~jrm
Should be straightforward to port the Perl parser using the same DIY
methods, no?
- Daniel
> On Sat, 21 Feb 2004 22:35:43 +0000, Joe Marshall wrote:
>
>> "Daniel P. M. Silva" <dsi...@ccs.neu.edu> writes:
>>
>>> You should give it a shot. All you need is the Perl BNF and what it means.
>>
>> "Perl's grammar can not be reduced to BNF. The work of parsing Perl
>> is distributed between yacc, the lexer, smoke and mirrors."
>> --- Chaim Frenkel as quoted in the Perl FAQ
>>
>> Does Perl even *have* semantics?
>
> Should be straightforward to port the Perl parser using the same DIY
> methods, no?
You'd be surprised. The parse is context-sensitive which means in
this case that the meaning of the next character will depend on the
runtime state.
To steal some examples from http://perlmonks.thepen.com/44722.html
The '/' character either means divide or that the following text is a
regular expression. So this parse:
BEGIN {
eval (time % 2 ? 'sub zany ();' : 'sub zany (@);');
}
zany / ...
will depend on whether you attempted to do it on an even or odd
second.
sin / 25 ; # / ; die "this doesn't die";
time / 25 ; # / ; die "this dies!";
The first one is computing the sin of the true/false value gotten by
matching " 25 ; # " against $_. Then it dies. The second one is
computing the time of day divided by 25, then ignoring the comment.
--
~jrm
Nasty. Never mind DrPerl then.
Oh well, too bad. I doubt the Perl and Scheme communities care about each
other anyway.
- Daniel
I think that would be spelled "Perl#". By "what I'd expect", I assume
you mean "what I'd expect if they were Perl programs. As long as it
behaved slightly differently in a consistent way, and as long as the
CPAN's modules worked with it, sure.
I'm not a monoglot -- I even enjoy learning new languages, provided I
see them giving me some advantage. If you were to dirty yourself and
your grad students writing a PerlLT that didn't take away CPAN, and
had tail call elimination and/or macros (or some means to give me
access to the AST), I would leap to use, improve, and evangelize it.
I would even do so if it behaved slightly differently. On the other
hand, if it were just an 80% throwaway implementation that translated
into PLT scheme and ran 100x slower, I would be more reluctant. And
unfortunately, I'll bet that there aren't theses and papers to be had
writing a 99.44%, industrial-strength PerlLT...
/s
False! See Dan Sugalski's presence on the LL1 list. I think Perl
people are generally receptive to new ideas, if they can see examples
and applications. The belief that "TMTOWTDI" implies a willingness to
learn new ways to do things.
However, the two communities _do_ seem to have trouble avoiding
flamefests. The conversation has trouble walking that knife-edge
between "You haven't read so-and-so's paper on control/reset? You
heathen!" and "Get back to me when you've written some *real* apps."
If we could avoid the flames and mutual disdain, I think both sides
could learn. As I read somewhere recently: "For just one day imagine,
whenever someone disagrees with you, that they are smarter and more
knowledgeable than you are, and ask yourself why they have beliefs
apparently opposed to yours."
/s
As other posters have said, there is no BNF for Perl. I'm not a Perl
5 hacker, so I don't know the full extent of the pain. In addition to
the business another poster cited about function prototypes changing
the parse (IIRC C/C++ typedefs have a similar effect), I recently
found that "9-y/aeiou//" (i.e. 9 minus the number of translations of
characters "aieou") parses correctly in 5.8.x, but requires a space
after the "-" in 5.6.x. For an even more extreme example, check out
the mailing list posts about distinguishing between the "defined-or"
operator "//" and the empty regexp "//".
On the one hand, I find this somewhat distasteful -- my CS-major's
stomach churns -- so you probably find it absolutely appalling. On
the other hand, I am continually surprised at the Perl community's
ability to gradually massage these sorts of perversions into a state
where they do "what you mean", where "you" includes nearly the entire
vocal Perl community.
/s
> As other posters have said, there is no BNF for Perl. I'm not a Perl
> 5 hacker, so I don't know the full extent of the pain.
Bad enough that Perl6 will be a complete rewrite, with a grammar. A
grammar that it will be possible to change at runtime, mind you, but
the Lisp people should be familiar with that sort of thing, I think.
Even today it's possible to write parts of your Perl programs in other
languages, by way of the Inline:: set of modules. The only functional
one I can see offhand is Scheme (GNU Guile, specifically), but it
should be fairly easy to add more. An Erlang one would be pretty
interesting, to get at the distributedness.
> For an even more extreme example, check out the mailing list posts
> about distinguishing between the "defined-or" operator "//" and the
> empty regexp "//".
As far as I've understood things on the P6 mailing lists, Larry and
Damian are trying to remove (or at least minimize) this sort of thing
in Perl6.
> On the one hand, I find this somewhat distasteful -- my CS-major's
> stomach churns -- so you probably find it absolutely appalling.
This, however, is unlikely to change. Perl will, to borrow an
expression from Sean Burke, continue to be the Borg of programming
languages: it will actively hunt down other languages and steal their
nifty features (prime targets for Perl6 at the moment seem to be Ruby
and Smalltalk). While this makes for a language where you can almost
always choose a way to express a problem that makes it easy to solve,
it *really* does not make for the sort of cleanliness and
orthogonality that CS people tend to like.
--
Calle Dybedahl <ca...@lysator.liu.se>
"Being stomped on by some sexy commie chick and her perfectly vegan Doc
Martens is not as hot as it sounds." -- babycola
> The '/' character either means divide or that the following text is a
> regular expression.
But this is at least possible to specify, it depends only on how the
function before '/' was prototyped (if at all). There are worse cases,
my favorite is this:
/* S_intuit_more
* Returns TRUE if there's more to the expression (e.g., a subscript),
* FALSE otherwise.
*
* It deals with "$foo[3]" and /$foo[3]/ and /$foo[0123456789$]+/
*
* ->[ and ->{ return TRUE
* { and [ outside a pattern are always subscripts, so return TRUE
* if we're outside a pattern and it's not { or [, then return FALSE
* if we're in a pattern and the first char is a {
* {4,5} (any digits around the comma) returns FALSE
* if we're in a pattern and the first char is a [
* [] returns FALSE
* [SOMETHING] has a funky algorithm to decide whether it's a
* character class or not. It has to deal with things like
* /$foo[-3]/ and /$foo[$bar]/ as well as /$foo[$\d]+/
* anything else returns TRUE
*/
/* This is the one truly awful dwimmer necessary to conflate C and sed. */
STATIC int
S_intuit_more(pTHX_ register char *s)
{
if (PL_lex_brackets)
return TRUE;
if (*s == '-' && s[1] == '>' && (s[2] == '[' || s[2] == '{'))
return TRUE;
if (*s != '{' && *s != '[')
return FALSE;
if (!PL_lex_inpat)
return TRUE;
/* In a pattern, so maybe we have {n,m}. */
if (*s == '{') {
s++;
if (!isDIGIT(*s))
return TRUE;
while (isDIGIT(*s))
s++;
if (*s == ',')
s++;
while (isDIGIT(*s))
s++;
if (*s == '}')
return FALSE;
return TRUE;
}
/* On the other hand, maybe we have a character class */
s++;
if (*s == ']' || *s == '^')
return FALSE;
else {
/* this is terrifying, and it works */
int weight = 2; /* let's weigh the evidence */
char seen[256];
unsigned char un_char = 255, last_un_char;
char *send = strchr(s,']');
char tmpbuf[sizeof PL_tokenbuf * 4];
if (!send) /* has to be an expression */
return TRUE;
Zero(seen,256,char);
if (*s == '$')
weight -= 3;
else if (isDIGIT(*s)) {
if (s[1] != ']') {
if (isDIGIT(s[1]) && s[2] == ']')
weight -= 10;
}
else
weight -= 100;
}
for (; s < send; s++) {
last_un_char = un_char;
un_char = (unsigned char)*s;
switch (*s) {
case '@':
case '&':
case '$':
weight -= seen[un_char] * 10;
if (isALNUM_lazy_if(s+1,UTF)) {
scan_ident(s, send, tmpbuf, sizeof tmpbuf, FALSE);
if ((int)strlen(tmpbuf) > 1 && gv_fetchpv(tmpbuf,FALSE, SVt_PV))
weight -= 100;
else
weight -= 10;
}
else if (*s == '$' && s[1] &&
strchr("[#!%*<>()-=",s[1])) {
if (/*{*/ strchr("])} =",s[2]))
weight -= 10;
else
weight -= 1;
}
break;
case '\\':
un_char = 254;
if (s[1]) {
if (strchr("wds]",s[1]))
weight += 100;
else if (seen['\''] || seen['"'])
weight += 1;
else if (strchr("rnftbxcav",s[1]))
weight += 40;
else if (isDIGIT(s[1])) {
weight += 40;
while (s[1] && isDIGIT(s[1]))
s++;
}
}
else
weight += 100;
break;
case '-':
if (s[1] == '\\')
weight += 50;
if (strchr("aA01! ",last_un_char))
weight += 30;
if (strchr("zZ79~",s[1]))
weight += 30;
if (last_un_char == 255 && (isDIGIT(s[1]) || s[1] == '$'))
weight -= 5; /* cope with negative subscript */
break;
default:
if (!isALNUM(last_un_char) && !strchr("$@&",last_un_char) &&
isALPHA(*s) && s[1] && isALPHA(s[1])) {
char *d = tmpbuf;
while (isALPHA(*s))
*d++ = *s++;
*d = '\0';
if (keyword(tmpbuf, d - tmpbuf))
weight -= 150;
}
if (un_char == last_un_char + 1)
weight += 5;
weight -= seen[un_char];
break;
}
seen[un_char]++;
}
if (weight >= 0) /* probably a character class */
return FALSE;
}
return TRUE;
}
--
__("< Marcin Kowalczyk
\__/ qrc...@knm.org.pl
^^ http://qrnik.knm.org.pl/~qrczak/
I don't understand this. Because PLT Scheme includes functional
libraries and allows for FP without worries about special calling
conventions, does it make the language suitable only for FP?
Imagine it allowed you to write:
;; A and B are sequences...
(for i := 0 step 1 until m do
(when (= x (aref A i))
(set-aref! B i (+ 1 (aref B i)))
(break)))
Would the language now push for an imperative style?
Imagine PLT Scheme had a class system included with the regular
distribution. It would then be an OOP language as well. Which style would
be appropriate to use with the language at that point?
Then imagine it included a reactive programming collection called FrTime...
Daniel
> "Jonadab the Unsightly One" wrote:
> > This may be partly because my primary language is Perl, which
> > is an expressly multiparadigmatic language. Functional, contextual,
> > and procedural programming are all equally straightforward in Perl,
>
> The idea that functional programming is straightforward in Perl only lasts
> until you try to write, say, a CPS parser in it.
Some functional programming is easy in Perl. Closures are like eating
cake. Continuations are another matter; Perl5 doesn't really have
them. I've heard that Perl6 is probably getting them, though.
> The point being that Perl doesn't support tail recursion,
It does support it at the language level; it just doesn't make the
guarantee that Scheme makes that the interpreter/compiler/whatever
will do a certain optimization.
> and so at the very least it'll use a huge amount of space when it
> doesn't need to; at worst, you'll run out of space if your machine
> survives the humongous memory allocation that it does with recursive
> programs.
Yes, this is true, at least somewhat. Tail recursion really ought to
be optimized; that would be a quite worthwhile improvement. I suspect
it will be made eventually.
> Although it's true that experienced functional programmers can use
> languages like Perl & Python in various functional ways, often by
> relying on workarounds, there's a problem with claiming that these
> languages support functional programming: it encourages
> Perl/Python/Ruby programmers to believe that they understand FP and
> have access to it in their language, so they miss out on the
> opportunity to learn more about one of the more important and
> enlightening programming paradigms.
Actually, it is Perl's functional stuff that has lead me to want to
learn Scheme. I previously knew elisp, but never messed with lambda
expressions or closures in that language. Just one data point, but I
think it's significant that I never understood closures until I
learned to use them in Perl. Now Perl6 is getting continuations, or
so I've heard, and to understand them ahead of time I'm learning
Scheme.
> It's particularly unfortunate that the generally poor support for FP
> in these languages often leads people to conclude that the
> functional paradigm is flawed
I've not seen this to be the case. (I'm not saying it doesn't happen,
just that it's not what I've seen.) The elegance of things like list
transformations in Perl (especially map, and extra-especially
constructions like the Schwartzian Transform[1] and the Orcish
Manoeuver) leads people to learn about FP who formerly, due to
indoctrination in C and C++ and their ilk, considered FP to be
impractical. Closures also are extremely useful in Perl. So while
it's true that not all elements of functional programming are
supported in Perl5, the ones that are supported prove to be undeniably
useful and practical, in a world where "useful" and "practical" are
not terms a lot of people are used to applying to functional
programming.
The Schwartzian Transform is a particularly potent argument against
the notion that FP is impractical, because the hard cold data show it
to run circles around the more "traditional" (i.e., procedural) ways
of doing the same thing, to the extent that sometimes it makes the
difference between usable and unusable.
Okay, so experienced functional programmers (the sort of people who
are accustomed to implementing custom problem-domain-specific
languages using CPS just for a particular problem) probably don't
consider the Schwartzian Transform to be "real" functional
programming, but it's a whole lot more functionally-oriented than
traditional procedural or OO programming.
(And yes, I know that FP has just as much tradition as the other
paradigms, but it's been (unfortunately) shoved into niches for so
long that the other paradigms are often considered more "traditional",
probably mostly due to the influence of C and C++. It may also be
noted that the "OO" in C++ is a long way from being real OO and has
unfortunately seriously warped a lot of people's perception of OO, but
talking much about that will take us off-topic.)
[1] An example, just in case anyone's not familiar with it:
dosomething for map { $$_[0] } sort { $$a[1] <=> $$b[1]
} map { [$_, some_expensive_function($_)] } somelist;
If my Scheme were a lot better, I'd translate this, but basically the
first map (the one on the second line) performs some calculation or
database lookup or whatever once for each element of the list so that
the sort doesn't have to do it multiple times, and the second map (on
the first line) returns the elements of the list to their original
state, keeping the sorted order. The same thing could be done more in
a more procedural fashion, but it would be a lot more verbose and
inelegant and involve superfluous list-copying:
{
my @transformed;
for (somelist) {
push @transformed, [$_, some_expensive_function($_)];
}
my @sorted = sort { $$a[1] <=> $$b[1] } @transformed;
my @restored;
for (@sorted) {
push @restored $$_[0];
}
for (@restored) {
dosomething;
}
}
--
$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}}
split//,"ten.thgirb\@badanoj$/ --";$\=$ ;-> ();print$/
Oh lord jesus have mercy on their souls. They are going down down to hell.
--
Cheers, The Rhythm is around me,
The Rhythm has control.
Ray Blaak The Rhythm is inside me,
rAYb...@STRIPCAPStelus.net The Rhythm has my soul.
> Joe Marshall <prunes...@comcast.net> writes:
> > sin / 25 ; # / ; die "this doesn't die";
> > time / 25 ; # / ; die "this dies!";
> >
> > The first one is computing the sin of the true/false value gotten by
> > matching " 25 ; # " against $_. Then it dies. The second one is
> > computing the time of day divided by 25, then ignoring the comment.
>
> Oh lord jesus have mercy on their souls. They are going down down to hell.
i'd say they were already there.
Suppose you have an ideal multi-paradigm language where every programming
paradigm is supported equally well. Then you would chose the paradigm
according to the problem. End of the story, fine.
However, in real life languages do not support all paradigms equally well
and you have to take in account this fact.
For instance PLT Scheme object system is not that good (IMHO of course);
suppose also that for some reason I cannot/don't want to install Swindle,
which is quite good instead. Then, I would probably chose a non-OO
programming paradigm if possible, whereas my choice would probably be
different had I the option of using Swindle.
The choice of the paradigm is always a matter of costs vs. benefits, and
the language facilities enter in the game, isn't it?
Michele Simionato
Oz is a multiparadigm language and at least *claims* to support them
equally well. I suspect though that often people just choose the
subset of paradigms they are most comfortable with.
k
Having worked for quite a while in a large project, I'd like
to stress the importance of consistency in _large_ software
systems. I'd much rather stick to one paradigm that supports
90% of my problems _well enough_, and most of the really
critical problems for my domain exceedingly well. I could
live with a language that does this, and proves completely
unsuitable for some percentage of my problems.
I'd be wary of a language that supports all paradigms and
doesn't offer a clear preference regarding style. For the same
reason, I'm wary of programming languages with endless macro
capabilities (e.g. LISP) and languages with a motto like
"there's more than one way to do it" (perl).
IMHO, a programming language for large systems should _encourage_
good design by offering intuitive and consistent abstractions,
subtly leading the programmer towards a reasonably clean and
stable design. This may not preclude the notion of supporting
multiple paradigms, but offering too many modeling options to
the programmer and letting him/her choose may well be counter-
productive on a sufficiently large scale.
Arguably, it is sometimes handy to have a gun in your house,
but if you also have kids, you'd better make sure to store
that gun where the kids can't get to it.
Programmers in large systems are, almost by definition, of
average skill and constantly under pressure to deliver.
Furthermore, 80% of the life cycle cost lies in maintenance,
and the people maintaining the code are almost never the
same people as those who wrote it. This makes uniformity
more important in the long run than flexibility.
(BTW, this is an area where FPLs should be able to shine.)
/Uffe
--
Ulf Wiger, Senior Specialist,
/ / / Architecture & Design of Carrier-Class Software
/ / / Strategic Product & System Management
/ / / Ericsson AB, Connectivity and Control Nodes
>michele....@poste.it (Michele Simionato) writes:
>>
>> Suppose you have an ideal multi-paradigm language where every programming
>> paradigm is supported equally well. Then you would chose the paradigm
>> according to the problem. End of the story, fine.
>> However, in real life languages do not support all paradigms equally well
>> and you have to take in account this fact.
>
>Oz is a multiparadigm language and at least *claims* to support them
>equally well.
Oz may be a multiparadigm language, but I don't think it supports
the template meta-programming paradigm very well.
Supporting a few or even a large number of particular paradigms is not
the same as supporting *every* paradigm.
Furthermore, sometimes support for one paradigm will encourage
design decisions that may be less than optimal for another paradigm.
For example, in Oz, higher-order terms have identity, which means that
they do not obey all the same laws that higher-order terms obey in a
pure functional and/or pure logic language.
--
Fergus Henderson <f...@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
> I don't think Python is less flexible than Perl, at least in the
> stuff that matters (it is much less flexible in the syntax, of
> course).
Syntax is only syntax. (Okay, significant whitespace is arguably an
evil syntax, but still... the semantics are of much more critical
importance than the syntax.)
> However, doing things in different ways (i.e. not Guido's way) is
> discouraged. The Zen of Python explicitely says:
>
> "There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch."
Interesting. When I played around with Python I found that I didn't
like it. I now wonder whether this had something to do with that. At
the time, I didn't think so. I knew the docs stressed OO especially,
but I figured that was just the docs trying to capitalize on the
buzzword value of OO. Additionally, I wouldn't expect an emphasis on
OO to drive me away from a language; Inform is very OO (though it's
also possible to program procedurally, but the OO is much stronger),
and I love Inform. (The quality of the Designer's Manual had
something to do with that...) No, I think it was other things about
Python that I personally didn't like, things that are not relevant to
this discussion or this newsgroup, such as the typing system.
> So, in principle, there should be only one "obviously Pythonic" style.
> Pythonistas think of this kind of uniformity as of a worthwhile goal,
> Perl programmers probably see this as a disgusting perspective ;)
Fortunately, Python as a language does not enforce this, at least not
entirely. Still, what you point out below is disturbing.
> > Next question: would it (the Python implementation) have been better
> > coded using classes, or is the functional implementation just as good?
>
> What does it mean better? Faster, more efficient? Or simpler to write,
> simpler to maintain, simpler to read for other Pythonistas?
Simpler to write and simpler to maintain for the author, is what I was
thinking. Authors and maintainers, if there are multiple such.
There's also an implication of _reasonable_ efficiency; An O(n!)
algorithm would not be "better" than an O(n) algorithm, even if it's
much easier to write and maintain. I'm not talking about minor
differences in efficiency, though, like one order of magnitude; those
get lost in the underflow on modern systems.
> I haven't never benchmarked applications using a functional style
> vs. applications using an object oriented style, so I can't say,
If it's even remotely close, it doesn't matter which is more efficient
with the computer's resources. Unless you're trying to sqeeze a
couple more years of life out of that old 386, or something.
I do try to avoid any algorithm worse than O(n^2) though. But that
isn't what I was talking about. Perl gets accused (by C programmers
mostly) of poor performance, but the only time it's not instant in my
experience is when it's retrieving data from a slow external source (a
database, the internet, ...) or when the algorithm is extremely bad
for performance (say, O(n!) or something like that). I guess I'm just
not very interested in benchmarking to save a few microseconds. Spend
the extra nanowatt and make my life easier.
> but I don't expect to see big differences. OTOH, writing the
> application in the obvious OO style has big advantages if the
> application has to be maintained by another Python programmer not
> very familiar with functional techniques.
What if the application is to be maintained by some future programmer
yet to be determined who may or may not have significant pre-existing
familiarity with OO, FP, or Python?
I would think it would be a mistake to write a program under the
assumption that an unknown future maintainer will necessarily be bound
by a certain suboptimal mindset.
> Yes, using all those closures would look bizarre, "unpythonic" to
> another Python programmer looking at that code.
Okay, but what about a competent programmer who knows several
languages (as all really good programmers do) but happen at the moment
to be using Python, because the code is written in Python?
> You see a typical example of "Pythonicity" here: this way is not
> obvious, so it is NOT the right way to go. The scope rules are
> bizarre on purpose, since people are not expected to use closures,
> they should use objects:
Objects for *that*? Okay, I'm going way off topic here, but I'd sure
have used closures rather than objects for something like that in Perl:
sub make_adders {
return map { my $i=$_; sub { $i+shift } } 1 .. shift if wantarray;
my $i = shift; return sub { $i + shift }
}
my $addfive = make_adders(5);
my ($addone, $addtwo) = make_adders(2);
print $addfive->($addone->($addtwo->(3))) . "\n"; # 11
(Except, I wouldn't really create an object or closure for such a
simple thing as adding 5 to a number. But that's beside the point.)
Python aims to be a general-purpose VHLL; it's going to eventually
have to have the scoping needed to provide decent support for FP, just
as Perl discovered that good OO was necessary. (The OO in Perl6 is
being completely overhauled. It will rock, when it's finally done,
which I currently estimate as sometime between entirely too long from
now and never.)
> [OO] is the "obvious" way for a Python programmer (I am sure Scheme
> or Perl programmers here will think the functional version is
> clearer, but let it be) and has various advantages over the
> functional version:
>
> - the adder logic is encapsulated in an object with a
> self-documenting name;
> - I can add docstrings to the adder class and not to the lambda;
> - I can later extends the class whereas I cannot derive from the
> lambda;
> - I dont'use the esoteric name "lambda" which may scare my readers
> ;)
Wait, you can't derive from a closure in Python? Does that mean you
can't have a closure that holds other closures and uses them for
stuff? That seems like a pretty serious limitation. I would have
thought Python, being mostly a fairly flexible language, could handle
that, even if it's not The Van Rossam Way. Or did you only mean that
there's no built-in support for altering or cloning a closure?
> OTOH, if I had good scope rules in the language, I would code the
> adder with a lambda, and my code would be less Pythonic, i.e. less
> readable to others. So, Guido doesn't give me better scope rules and
> "ipso facto" guarantees that the only *obvious* way to do it is the
> OO way. Uniformity of style is a very valuable thing in a corporate
> environment, less so if you code for yourself.
This kind of uniformity isn't the mere normal sort of style -- it's
bondage and discipline. It reminds me of the Pascal teacher who
wanted us to have each subroutine only be called from one place, to
keep control flow straightforward. Even Pascal wouldn't enforce that
kind of severe limitation.
> > This is somewhat different from my approach. I freely mix
> > whatever paradigms are available to me in the language I'm using,
>
> The choice of the paradigm is somewhat restricted by the language,
> not only by the problem. For instance, I am aware of the possible
> warts of using closures in Python, so I have two choices: 1) I am
> very careful, 2) I switch to the OOP paradigm. More often than not
> 2) is the simplest choice. I am sure that in Perl you would tend to
> prefer the functional way over the object oriented way, instead.
I intermix them freely. For example, my closures might hold private
objects, which they might use for various things. Any moderately
competent Perl programmer understands both, so there's no problem.
Whatever gets the job done and makes the code maintainable...
my @foo = map {
my %rec = %{GetRecord('sometable',$_)};
my $dt = DateTimeFromDB($rec{somefield});
return { # A hashful of closures:
isfinished => sub { DateTime::compare($dt, DateTime->now()) >= 0 }
othermethod => sub { dostuff($rec{foo}), $dt->year }
anothersub => sub { whee(@rec{somefield,anotherfield}) }
}
} grep {
somecriterion($_)
} @whatever;
$foo[$someindex]{othermethod}->();
The arrow syntax is annoying, but it's only syntax, and it's getting
fixed in Perl6.
> Coming back OT, i.e. to Scheme, I would say in Scheme (or Lisp)
> these issues are less relevant, since in these languages you have
> the opportunity to modify the language itself to follows the
> paradigm you have in mind.
That's something I'd really like to explore; sounds incredibly useful.
However, I think I'd better get a more solid handle on continuations
first.
> Still, even Scheme encourage some paradigm over others: for
> instance you are encouraged to solve a problem recursively and not
> iteratively, to think in terms of higher order functions, etc.
Recursion (well, general recursion) and iteration are isomorphic. If
you fully understand them both, thinking in terms of the one is
exactly the same as thinking in terms of the other.
sub iterative_wondrousp {
my $n = int shift;
while ($n > 1) {
if ($n % 2) {
($n*=3)++;
} else {
$n /= 2;
}
}
return "Wondrous!";
}
sub recursive_wondrousp {
my $n = int shift;
return "Wondrous!" unless $n > 1;
if ($n % 2) {
return resursive_wondrousp($n*3+1);
} else {
return recursive_wondrousp($n/2);
}
}
print map {"$_\n"} (iterative_wondrousp(27), recursive_wondrousp(27));
Is that even different? I guess I moved the return associated with
the base case to a different physical spot in the code, but the
control flow hasn't changed at all. If you desk-check it (i.e., walk
through the interpretive flow by hand), you'll quickly see that the
two are identical in every significant way.
It's possible to think in terms of iteration or in terms of recursion,
but it's also possible to just think about them generally,
collectively, and to think about solving a particular problem using
either of them, without worrying about which. Which one you use is an
unimportant detail, in most of the common cases.
Sure, there are some problems that are difficult to think about
iteratively, because of their complexity. Still, ultimately, the
recursion boils down to iteration at the machine code level. Or the
iteration boils down to recursion, depending on how you look at it.
Really, they're the same.
> Summarizing, I think that if you want to use a paradigm not
> supported by language X, you have two choices: 1) change your mind:
> 2) switch to language Y. In the case of X = Scheme, Y can be a
> customization of Scheme written by yourself; still you have to to
> write the language yourself, which is a bigger effort than using the
> native constructs. I.e. I may implement Python "for" loops in
> Scheme, but more often than not I am better off by using the native
> looping constructs.
> (*) notice that I was naturally inclined to think in a FP way even
> before studying Python and before studying Scheme: I was introduced
> to map and friends by Mathematica and Maple. I had no problems at
> all in learning the FP approach, whereas I needed a more substantial
> effort to grasp OOP.
I understood OO before FP, largely due to the influence of Inform
(which has really good OO, incidentally, much better than Perl5), but
there are some problems that are inherently easier to solve
functionally than objectually. Languages that don't provide at least
some of both paradigms (or adequate flexibility so that you can easily
construct them) are missing important parts. I don't know Python well
enough to be certain of which category it falls into; my feeling
before this discussion was that the language itself was flexible
enough for multiple paradigms, but that the docs just tended to
emphasize OO more than the others. But I never got all the way
through the Python book before other things distracted me, so it could
have some stiffer limitations than I realised.
Of coruse, it doesn't really matter; I didn't come to comp.lang.scheme
to discuss Python -- or, for that matter, Perl or elisp. I want to
learn about continuations in Scheme. This is just a side-discussion,
albeit an interesting one.
> Languages that are not `safe for space' are fundamentally broken.
Yeah, I'm looking forward to that's being changed in Perl6, too.