473,785 Members | 2,553 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

A critic of Guido's blog on Python's lambda

Python, Lambda, and Guido van Rossum

Xah Lee, 2006-05-05

In this post, i'd like to deconstruct one of Guido's recent blog about
lambda in Python.

In Guido's blog written in 2006-02-10 at
http://www.artima.com/weblogs/viewpo...?thread=147358

is first of all, the title “Language Design Is Not Just Solving
Puzzles”. In the outset, and in between the lines, we are told that
“I'm the supreme intellect, and I created Python”.

This seems impressive, except that the tech geekers due to their
ignorance of sociology as well as lack of analytic abilities of the
mathematician, do not know that creating a language is a act that
requires little qualifications. However, creating a language that is
used by a lot people takes considerable skill, and a big part of that
skill is salesmanship. Guido seems to have done it well and seems to
continue selling it well, where, he can put up a title of belittlement
and get away with it too.

Gaudy title aside, let's look at the content of his say. If you peruse
the 700 words, you'll find that it amounts to that Guido does not like
the suggested lambda fix due to its multi-line nature, and says that he
don't think there could possibly be any proposal he'll like. The
reason? Not much! Zen is bantered about, mathematician's impractical
ways is waved, undefinable qualities are given, human's right brain is
mentioned for support (neuroscience!) , Rube Goldberg contrivance
phraseology is thrown, and coolness of Google Inc is reminded for the
tech geekers (in juxtaposition of a big notice that Guido works
there.).

If you are serious, doesn't this writing sounds bigger than its
content? Look at the gorgeous ending: “This is also the reason why
Python will never have continuations, and even why I'm uninterested in
optimizing tail recursion. But that's for another installment.” . This
benevolent geeker is gonna give us another INSTALLMENT!

There is a computer language leader by the name of Larry Wall, who said
that “The three chief virtues of a programmer are: Laziness,
Impatience and Hubris” among quite a lot of other ingenious
outpourings. It seems to me, the more i learn about Python and its
leader, the more similarities i see.

So Guido, i understand that selling oneself is a inherent and necessary
part of being a human animal. But i think the lesser beings should be
educated enough to know that fact. So that when minions follow a
leader, they have a clear understanding of why and what.

----

Regarding the lambda in Python situation... conceivably you are right
that Python lambda is perhaps at best left as it is crippled, or even
eliminated. However, this is what i want: I want Python literatures,
and also in Wikipedia, to cease and desist stating that Python supports
functional programing. (this is not necessarily a bad publicity) And, I
want the Perl literatures to cease and desist saying they support OOP.
But that's for another installment.

----
This post is archived at:
http://xahlee.org/UnixResource_dir/w...bda_guido.html

* * Xah
* * xa*@xahlee.org
http://xahlee.org/

May 6 '06
267 10845
Ken Tilton <ke*******@gmai l.com> writes:

Set Kelvin, and make Celsius and Fahrneheit functions of that.


Or Rankine:-)

--
Robert Uhl <http://public.xdi.org/=ruhl>
Brought to you by 'Ouchies', the sharp, prickly toy you bathe with...
May 10 '06 #211
Cameron Laird wrote:
In article <1h************ *************** **@yahoo.com>,
Alex Martelli <al*****@yahoo. com> wrote:

.... .
Of course, the choice of Python does mean that, when we really truly
need a "domain specific little language", we have to implement it as a
language in its own right, rather than piggybacking it on top of a
general-purpose language as Lisp would no doubt afford; see
<http://labs.google.com/papers/sawzall.html> for such a DSLL developed
at Google. However, I think this tradeoff is worthwhile, and, in
particular, does not impede scaling....


....I'm confused, Alex: I sure
think *I* have been writing DSLs as specializations of Python,
and NOT as "a language in its own right"....


I think Alex is suggesting that if they used, for example, a version of
scheme
with a good optimizing compiler they could implement sawzall like
convenience
with almost the same performance, including startup, etc. whereas even
a
highly optimized python based approach would at least have a
comparatively
large startup penalty. For an environment like Google where they
scrape
thru their logs of various sorts doing lots of trivial scans you can
probably save
a lot of money and time on lots of machines by optimizing such scrapes
(but keep your bactine handy). And as the sawzall paper pointed out,
even static
type checks can prevent a lot of wasted machine bandwidth by avoiding
dumb
errors.

But the real question for someone like Rob Pike is why use scheme when
you
can invent another little language instead, I suspect :).

-- Aaron Watters

===

Stop procrastinating soon.

May 10 '06 #212
Kenny replied to me saying:
Yep. But with Cells the dependency graph is just a shifting record of
who asked who, shifting because all of a sudden some outlier data will
enter the system and a rule will branch to code for the first time,
and suddenly "depend on" on some new other cell (new as in never
before used by this cell). This is not subject to static analysis
because, in fact, lexically everyone can get to everything else, what
with closures, first-class functions, runtime branching we cannot
predict... fuggedaboutit.

So we cannot say, OK, here is "the graph" of our application
model. All we can do is let her rip and cross our fingers. :)


Yes, if you have Turing completeness in your dependency graph, the
problem is unsolvable. However, it's like the static v. dynamic
typing debate, you can pick how much you want to allow your graph to
be dynamic versus how much "safety" you want. In particular, I
suspect that in many applications, one can compute the set of
potentially problematic dependencies (and that set will be empty).
It's just a matter of structuring and annotating them correctly. Just
like one can create type systems that work for ML and Haskell. Of
course, if you treat your cell references like C pointers, then you
get what you deserve.

Note that you can even run the analysis dynamically, recomputing
whether the graph is cycle free as each dependency changes. Most
updates have local effect. Moreover, if you have used topological
sort to compute an ordering as well as proving cycle-free-ness, the
edge is only pontentially problemantic when it goes from a later
vertex in the order to an earlier one. I wouldn't be surprised to
find efficient algorithms for calculating and updating a topological
sort already in the literature.

It is worth noting that in typical chip circuitry there are
constructions, generally called "busses" where the flow of information
is sometimes "in" via an edge and sometimes "out" via the same edge
and we can model them in a cycle-free manner.

If you want to throw up your hands and say the problem is intractable
in general, you can. However, in my opinion one doesn't have to give
up quite that easily.

-Chris
May 10 '06 #213
Ken Tilton <ke*******@gmai l.com> writes:
sross wrote:
I do wonder what would happen to Cells if I ever want to support
multiple threads. Or in a parallel processing environment. AFAIK It should be fine.
In LW, SBCL and ACL all bindings of dynamic variables are thread-local.


Ah, I was guilty of making an unspoken segue: the problem is not with
the *dependent* special variable, but with the sequentially growing
numeric *datapulse-id* ("the ID") that tells a cell if it needs to
recompute its value. The ID is not dynamically bound. If threads T1
and T2 each execute a toplevel, imperative assignment, two threads
will start propagating change up the same dependency
graph... <shudder>

Might need to specify a "main" thread that gets to play with Cells and
restrict other threads to intense computations but no Cells?


Hmmm. I am wondering if a Cells Manager class could be the home for
all Cells. Each thread could the have its own Cells Manager...

Actually, I got along quite a while without an ID, I just propagated
to dependents and ran rules. This led sometimes to a rule running
twice for one change and transiently taking on a garbage value, when
the dependency graph of a Cell had two paths back to some changed
Cell.

Well, Cells have always been reengineered in the face of actual use
cases, because I am not really smart enough to work these things out
in the abstract. Or too lazy or something. Probably all three.


Nah. It's me asking again and again those silly questions about
real Cells usage in some real life apps ;-)

Frank
May 10 '06 #214
Alex Martelli wrote:
Tomasz Zielonka <to************ *@gmail.com> wrote:
...
higher level languages. There are useful programming techniques, like
monadic programming, that are infeasible without anonymous functions.
Anonymous functions really add some power to the language.
Can you give me one example that would be feasible with anonymous
functions, but is made infeasible by the need to give names to
functions?


Perhaps you were speaking more about Python, and I was speaking more
generally. There are useful programming techniques that require using
many functions, preferably anonymous ones, but these techniques probably
won't fit Python very well anyway.

In Haskell when I write IO intensive programs, when I use monadic
parsing libraries, etc. I use many lambdas, sometimes disguised as a
do-notation bind syntax (do x <- a; ...). I wouldn't want to name
all those functions.

Here is the random page with Haskell code I found on haskell.org
http://haskell.org/haskellwiki/Sudoku
See how many are there uses of lambdas:
\... ->
and do-bindings, which are basically a syntactic sugar for lambdas
and monadic bind operations:
do
x <- something ...
Also, there are many anonymous functions created by using higher
order functions or by partial application.

I want to make it clear that monadic programming in Haskell is not only
something that we *have* to do in order to perform IO in a purely
functional program, but it's often something we *can* and *want* to do,
because it's powerful, convenient, etc.

Haskell programs without many monadic programming also contain many
anonymous functions.

Of course I wouldn't use monadic programming too often in Python, mostly
because I wouldn't have to (eg. for IO), but also because it would be
inconvenient and difficult in Python.
In Python, specifically, extended with whatever fake syntax
you favour for producing unnamed functions?

I cannot conceive of one. Wherever within a statement I could write the
expression
lambda <args>: body
I can *ALWAYS* obtain the identical effect by picking an otherwise
locally unused identifier X, writing the statement
def X(<args>): body
and using, as the expression, identifier X instead of the lambda.


I know that. But the more such functions you use, the more cumbersome it
gets. Note also that I could use your reasoning to show that support
for expressions in the language is not neccessary.
On the other hand, what do you get by allowing ( as an indentifier?


Nothing useful -- the parallel is exact.


What we are discussing here is language expressivity and power,
something very subtle and hard to measure. I think your logical
reasoning proves nothing here. After all, what is really needed
in a programming language? Certainly not indentation sensitivity :-)
0s and 1s should be enough.
Significant whitespace is a good thing, but the way it is designed in
Python it has some costs. Can't you simply acknowledge that?


I would have no problem "acknowledg ing" problems if I agreed that any
exist, but I do not agree that any exist. Please put your coding where
your mouth is, and show me ONE example that would be feasible in a
Python enriched by unlimited unnamed functions but is not feasible just
because Python requires naming such "unlimited" functions.


You got me, monadic programming is infeasible in Python even with
lambdas ;-)

Best regards
Tomasz
May 10 '06 #215
Tomasz Zielonka wrote:
(x * 2) + (y * 3)

Here (x * 2), (y * 3) and (x * 2) + 3 are anonymous numbers ;-)

^^^^^^^^^^^

Of course it should be (x * 2) + (y * 3).

Best regards
Tomasz
May 10 '06 #216
Alex Martelli wrote:
Tomasz Zielonka <to************ *@gmail.com> wrote:
...
higher level languages. There are useful programming techniques, like
monadic programming, that are infeasible without anonymous functions.
Anonymous functions really add some power to the language.
Can you give me one example that would be feasible with anonymous
functions, but is made infeasible by the need to give names to
functions?


Perhaps you were speaking more about Python, and I was speaking more
generally. There are useful programming techniques that require using
many functions, preferably anonymous ones, but these techniques probably
won't fit Python very well anyway.

In Haskell when I write IO intensive programs, when I use monadic
parsing libraries, etc. I use many lambdas, sometimes disguised as a
do-notation bind syntax (do x <- a; ...). I wouldn't want to name
all those functions.

Here is the random page with Haskell code I found on haskell.org
http://haskell.org/haskellwiki/Sudoku
See how many are there uses of lambdas:
\... ->
and do-bindings, which are basically a syntactic sugar for lambdas
and monadic bind operations:
do
x <- something ...
Also, there are many anonymous functions created by using higher
order functions or by partial application.

I want to make it clear that monadic programming in Haskell is not only
something that we *have* to do in order to perform IO in a purely
functional program, but it's often something we *can* and *want* to do,
because it's powerful, convenient, etc.

Haskell programs without many monadic programming also contain many
anonymous functions.

Of course I wouldn't use monadic programming too often in Python, mostly
because I wouldn't have to (eg. for IO), but also because it would be
inconvenient and difficult in Python.
In Python, specifically, extended with whatever fake syntax
you favour for producing unnamed functions?

I cannot conceive of one. Wherever within a statement I could write the
expression
lambda <args>: body
I can *ALWAYS* obtain the identical effect by picking an otherwise
locally unused identifier X, writing the statement
def X(<args>): body
and using, as the expression, identifier X instead of the lambda.


I know that. But the more such functions you use, the more cumbersome it
gets. Note also that I could use your reasoning to show that support
for expressions in the language is not neccessary.
On the other hand, what do you get by allowing ( as an indentifier?


Nothing useful -- the parallel is exact.


What we are discussing here is language expressivity and power,
something very subtle and hard to measure. I think your logical
reasoning proves nothing here. After all, what is really needed
in a programming language? Certainly not indentation sensitivity :-)
0s and 1s should be enough.
Significant whitespace is a good thing, but the way it is designed in
Python it has some costs. Can't you simply acknowledge that?


I would have no problem "acknowledg ing" problems if I agreed that any
exist, but I do not agree that any exist. Please put your coding where
your mouth is, and show me ONE example that would be feasible in a
Python enriched by unlimited unnamed functions but is not feasible just
because Python requires naming such "unlimited" functions.


You got me, monadic programming is infeasible in Python even with
lambdas ;-)

Best regards
Tomasz
May 10 '06 #217
Joe Marshall <ev********@gma il.com> wrote:
Alex Martelli wrote:
Joe Marshall <ev********@gma il.com> wrote:
...
The problem is that a `name' is a mapping from a symbolic identifier to
an object and that this mapping must either be global (with the
attendant name collision issues) or within a context (with the
attendant question of `in which context').
Why is that a problem? Even for so-called "global" names, Python
supports a structured, hierarchical namespace, so there can never be any
collision betwen the "globals" of distinct modules (including modules
which happen to have the same name but live in distinct packages or
subpackages) -- I did mention that names could usefully be displayed in
some strcutured form such as apackage.somemo dule.thefunctio n but perhaps
I was too tangential about it;-).


Can you refer to inner functions from the global context? Suppose I
have this Python code:

def make_adder(x):
def adder_func(y):
sum = x + y
return sum
return adder_func

Can I refer to the inner adder_func in any meaningful way?


You can refer to one instance/closure (which make_adder returns), of
course -- you can't refer to the def statement itself (but that's a
statement, ready to create a function/closure each time it executes, not
a function, thus, not an object) except through introspection. Maybe I
don't understand what you mean by this question...

If I used continuations (I assume you mean in the call/cc sense rather
than some in which I'm not familiar?) I might feel the same way, or not,
but I don't (alas), so I can't really argue the point either way for
lack of real-world experience.


I meant continuations as in the receiver function in
continuation-passing-style. If you have a function that has to act
differently in response to certain conditions, and you want to
parameterize the behavior, then one possibility is to pass one or more
thunks to the function in addition to the normal arguments. The


Ah, OK, I would refer to this as "callbacks" , since no
call-with-continuation is involved, just ordinary function calls; your
use case, while pretty alien to Python's typical style, isn't all that
different from other uses of callbacks which _are_ very popular in
Python (cfr the key= argument to the sort methods of list for a typical
example). I would guess that callbacks of all kinds (with absolutely
trivial functions) is the one use case which swayed Guido to keep lambda
(strictly limited to just one expression -- anything more is presumably
worth naming), as well as to add an if/else ternary-operator. I still
disagree deeply, as you guessed I would -- if I had to work with a
framework using callbacks in your style, I'd name my callbacks, and I
wish Python's functools module provided for the elementary cases, such
as:

def constant(k):
def ignore_args(*a) : return k
return ignore_args

def identity(v): return v

and so on -- I find, for example, that to translate your
(define (option3 key table default-value)
(lookup key table
(lambda (value) value)
(lambda () default-value)))


I prefer to use:

def option3(key, table, default_value):
return lookup(key, table, identity, constant(defaul t_value))

as being more readable than:

def option3(key, table, default_value):
return lookup(key, table, lambda v: v, lambda: default_value)

After all, if I have in >1 place in my code the construct "lambda v: v"
(and if I'm using a framework that requires a lot of function passing
I'm likely to be!), the "Don't Repeat Yourself" (DRY) principle suggests
expressing the construct *ONCE*, naming it, and using the name.

By providing unnamed functions, the language aids and abets violations
of DRY, while having the library provide named elementary functions (in
the already-existing appropriate module) DRY is reinforced and strongly
supported, which, IMHO, is a very good thing.
Alex
May 11 '06 #218


Alex Martelli wrote:
Stefan Nobis <sn****@gmx.d e> wrote:

al***@mac.c om (Alex Martelli) writes:

if anonymous functions are available, they're used in even more
cases where naming would help


Yes, you're right. But don't stop here. What about expressions? Many
people write very complex expression, that are hard to understand. A
good language should forbid these abuse and don't allow expressions
with more than 2 or maybe 3 operators!

That would _complicate_ the language (by adding a rule). I repeat what
I've already stated repeatedly: a good criterion for deciding which good
practices a language should enforce and which ones it should just
facilitate is _language simplicity_. If the enforcement is done by
adding rules or constructs it's probably not worth it; if the
"enforcemen ts" is done by NOT adding extra constructs it's a double win
(keep the language simpler AND push good practices).


Gosh, that looks like fancy footwork. But maybe I misunderstand, so I
will just ask you to clarify.

In the case of (all syntax imaginary and not meant ot be Python):

if whatever = 42
dothis
do that
do something else
else
go ahead
make my day

You do not have a problem with unnamed series of statements. But in the
case of:

treeTravers( myTree, lambda (node):
if xxx(node)
print "wow"
return 1
else
print "yawn"
return 0)

....no, no good, you want a named yawnOrWow function? And though they
look similar, the justification above was that IF-ELSE was lucky enough
to get multiline branches In the Beginning, so banning it now would be
"adding a rule", whereas lambda did not get multiline In the Beginning,
so allowing it would mean "adding a construct". So by positing "adding a
rule or construct" as always bad (even if they enforce a good practice
such as naming an IF branch they are bad since one is /adding/ to the
language), the inconsistency becomes a consistency in that keeping IF
powerful and denying lambda the same power each avoids a change?

In other words, we are no longer discussing whether unnamed multi-line
statements are a problem. The question is, would adding them to lambda
mean a change?

Oh, yeah, it would. :)

hth, kenny
--
Cells: http://common-lisp.net/project/cells/

"Have you ever been in a relationship?"
Attorney for Mary Winkler, confessed killer of her
minister husband, when asked if the couple had
marital problems.
May 11 '06 #219
Alex Martelli wrote:
M Jared Finder <ja***@hpalace. com> wrote:
...
Your reasoning, taken to the extreme, implies that an assembly language,
by virtue of having the fewest constructs, is the best designed language


Except that the major premise is faulty! Try e.g.
<http://docs.sun.com/app/docs/doc/817-5477/6mkuavhrf#hic> and count the
number of distinct instructions -- general purpose, floating point,
SIMD, MMX, SSE, SSE2, OS support... there's *hundreds*, each with its
own rules as to what operand(s) are allowed plus variants such as (e.g.)
cmovbe{w,l,q} for "conditiona l move if below or equal" for word, long,
quadword (no byte variant) -- but e.g cmpxchg{b,w,l,q } DOES have a byte
variant too, while setbe for "set if below or equal" ONLY has a byte
variant, etc, etc -- endless memorization;-).

When you set up your strawman arguments, try to have at least ONE of the
premises appear sensible, will you?-)

I never argued against keeping languages at a high level, of course
(that's why your so utterly unfounded argument would be a "strawman"
even if it WAS better founded;-).
prone, code. I think the advantages of anonymous functions:

...
e) making the language simpler to implement


Adding one construct (e.g., in Python, having both def and lambda with
vast semantic overlap, rather than just one) cannot "make the language
simpler to implement" -- no doubt this kind of "reasoning" (?) is what
ended up making the instruction-set architecture of the dominant
families of CPUs so bizarre, intricate, and abstruse!-)


It sure can. First, let's cover the cost. I'll be measuring everything
in terms of lines of code, with the assumption that the code has been
kept readable.

Here's an implementation of lambda (anonymous functions) in Lisp based
on flet (lexically scoped functions):

(defmacro lambda (args &rest body)
(let ((name (gensym)))
`(flet ((,name ,args ,@body)) (function ,name))))

That's three lines of code to implement. An almost trivial amount.

Now by using anonymous functions, you can implement many other language
level features simpler. Looping can be made into a regular function
call. Branching can be made into a regular function call. Defining
virtual functions can be made into a regular function call. Anything
that deals with code blocks can be made into a regular function call.

By removing the special syntax and semantics from these language level
features and making them just pain old function calls, you can reuse the
same evaluator, optimizer, code parser, introspector, and other code
analyzing parts of your language for these (no longer) special
constructs. That's a HUGE savings, well over 100 lines of code.

Net simplification, at least 97 lines of code. For a concrete example
of this in action, see Smalltalk.

-- MJF
May 11 '06 #220

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

181
8914
by: Tom Anderson | last post by:
Comrades, During our current discussion of the fate of functional constructs in python, someone brought up Guido's bull on the matter: http://www.artima.com/weblogs/viewpost.jsp?thread=98196 He says he's going to dispose of map, filter, reduce and lambda. He's going to give us product, any and all, though, which is nice of him.
30
2179
by: Mike Meyer | last post by:
I know, lambda bashing (and defending) in the group is one of the most popular ways to avoid writing code. However, while staring at some Oz code, I noticed a feature that would seem to make both groups happy - if we can figure out how to avoid the ugly syntax. This proposal does away with the well-known/obscure "lambda" keyword. It gives those who want a more functional lambda what they want. It doesn't add any new keywords. It doesn't...
0
10147
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
1
10090
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
9949
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
8971
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development projectplanning, coding, testing, and deploymentwithout human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
7499
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupr who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
6739
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
5380
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
5511
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
3
2879
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.