tag:blogger.com,1999:blog-6031647961506005424.post3175749400227790189..comments2023-10-09T20:19:28.997+03:00Comments on Lisp, the Universe and Everything: Clojure & ComplexityVsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.comBlogger68125tag:blogger.com,1999:blog-6031647961506005424.post-38048774347081820562012-02-21T22:11:23.173+02:002012-02-21T22:11:23.173+02:00> Well, if you tried using Clojure in a big pro...> Well, if you tried using Clojure in a big project and that didn't work out, then it's just not for you...<br /><br />I wouldn't have written this article, if not for the fact, that Clojure is marketed as "a modern Lisp". If Clojure was more properly called as a new language, borrowing CL syntax and a couple features, I would've being better informed, and might not have spent my time on it. This article is the result of my experience, so that other lispers. approaching the language, were able to make a more informed choice.<br /><br />I'm sure, that many people will find Clojure useful for them, including me, possibly. But I don't like all the prejudices around Lisp and don't want new misconceptions to appear, because people will think, tha Clojure is a Lisp. As it is not.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-32655272032118998072012-02-21T22:11:11.101+02:002012-02-21T22:11:11.101+02:00@Alex, the presence of similar opinions and delusi...@Alex, the presence of similar opinions and delusions, that you'd expressed, prompted me to write this post. Thank you for sharing them - below are my comments.<br /><br />> Yet I find most of arguments prejudiced towards CL...<br /><br />Surely, they are grounded in my CL experience. Yet I wouldn't call them prejudiced, as there's no one true opinion on this topics. I just express an alternative opinion.<br /><br />> The argument that has struck me the most ... Clojure is a functional language!<br /><br />This is confusion number 1. A functional language isn't defined as the language, disallowing assignment. You confuse it with the term "purely functional language" (of which the only widespread example is Haskell). Most functional languages: Scheme, OCaml and, also, Clojure, support some form of assigment, and thus allow to violate referential transparency. So what defines a functional language, as for me, is treating the program as consisting of expressions and not statements, as it is in imperative languages.<br /><br />> I don't see any space for further discussion here, Alonzo Church left us without it long time before.<br /><br />Alonzo Church doesn't have anything to do with this discussion, as we aren't talking about lambda calculus theory, but about practice of programming. None of the languages at question implement Church's lambda calculus, although it is possible to reason about some of them in such terms. <br /><br />> If the idea you are trying to bring forward is that a language should be imperative/multiparadigm to be called a Lisp, then this point of yours is just irrelevant. There is no definition of "Lisp" anyway.<br /><br />This is confusion number 2. First of all, there is a definition of Lisp, which is even an ANSI standard. And even if you disregard the standard, I was basing my judgement in the history of the language and the views of its community, expressed in numerous books and other materials. Following your train of thought, you could tell just the same about any language. Is there a definition of Javascript? What if someone says, that Coffeescript is the modern JavaScript or, say, Dart is. And when you'd say, "No, Dart doesn't support prototype inheritance" your opponent would answer: "Is it essential to JavaScript? No, for me the essential features of JavaScript are that it runs in the browser and has automatic memory management". This way you can call anything by any name...<br /><br />> Your attack on the concurrency model in Clojure is purely superficial...<br /><br />The argument was, that these features are advertised as very important and essential to the language. Moreover, they are built-in and influence the design of the whole language. Yet I couldn't find a significant benefit in them. Although I admit, that there may be many specific use-cases, that this model will suit very well. But they don't seem general enough for me to justify such core dependency. This is in contrast with Erlang, which implements really fundamentally different model, from the currently existing one. Yet, surely I don't object against having Clojure concurrency primitives as a library and using it.<br /><br />> The only major Clojure drawback I agree with you on is that it allows no custom reader macros. Though this drawback is thought-out and has its reasons behind.<br /><br />Surely. But it goes against the established Lisp principles.<br /><br />> Lisp code is not extremely easy to read and custom reader macros only added to this fact. Leaving users only with a set of predefined macros to learn eliminate the abuse of alien custom syntax which appeared in every other CL project.<br /><br />I don't know about your experience with CL, but in my almost 5-year experience I always found special reader-syntaxes beneficial for the project. I also don't agree with the notion of Lisp code being hard to read, but it's almost impossible to argue here, because it depends on very basic human perception qualities, so is very subjective.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-77548877786520081362012-02-21T20:20:13.063+02:002012-02-21T20:20:13.063+02:00This comment has been removed by the author.Alex Yakushevhttps://www.blogger.com/profile/15701457317370189536noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-81211075675484940282012-02-21T20:19:50.642+02:002012-02-21T20:19:50.642+02:00This comment has been removed by the author.Alex Yakushevhttps://www.blogger.com/profile/15701457317370189536noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-52239967009291903602012-02-21T20:19:03.207+02:002012-02-21T20:19:03.207+02:00I want to thank you, Vsevolod, for sharing this pi...I want to thank you, Vsevolod, for sharing this piece since there is not much detailed analysis (with disadvantages for the rest) on Clojure around the web. Yet I find most of arguments prejudiced towards CL for just the fact that you are more used to the latter.<br />The argument that has struck me the most is one about the lack of assignment and therefore inability to write proper imperative code. That is no wonder to me because Clojure is - wait for it - a functional language! I don't see any space for further discussion here, Alonzo Church left us without it long time before. If the idea you are trying to bring forward is that a language should be imperative/multiparadigm to be called a Lisp, then this point of yours is just irrelevant. There is no definition of "Lisp" anyway.<br />Your attack on the concurrency model in Clojure is purely superficial. You complain about the features STM lacks/doesn't match with your expectations? Well, CL hasn't any at all. Well, you can write them in CL rather quickly, so do you in Clojure. I don't see this point being a candidate for an argument either.<br />The only major Clojure drawback I agree with you on is that it allows no custom reader macros. Though this drawback is thought-out and has its reasons behind. Lisp code is not extremely easy to read and custom reader macros only added to this fact. Leaving users only with a set of predefined macros to learn eliminate the abuse of alien custom syntax which appeared in every other CL project.<br />Well, if you tried using Clojure in a big project and that didn't work out, then it's just not for you. Anyway it is good to know that someone makes efforts to introduce Clojure as an industry language. I'm sure your experience will be taken into consideration the next time someone thinks about pros and cons they can get when choosing the language for the next project.Alex Yakushevhttps://www.blogger.com/profile/15701457317370189536noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-45371484982925433792011-12-06T21:22:39.329+02:002011-12-06T21:22:39.329+02:00Sorry for so many comments, last one I promise :-)...Sorry for so many comments, last one I promise :-). Atoms are quite fast in single-threaded code, since they only have to try once to swap. I think a combination of atoms and transients might be the best we can do to imitate mutability until pods come along. About 100x slower than doing nothing and only ~20x slower than a simple increment.<br /><br />user=> (time (let [_count (atom 0)]<br />(dotimes [a 10000000] (swap! _count inc))))<br />"Elapsed time: 1292.590795 msecs"<br />nil<br /><br />user=> (time (let []<br />(dotimes [a 10000000] (inc a))))<br />"Elapsed time: 81.46009 msecs"<br />nil<br /><br />user=> (time (let []<br />(dotimes [a 10000000] nil)))<br />"Elapsed time: 20.368163 msecs"gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-67947779825116412282011-12-06T20:54:04.078+02:002011-12-06T20:54:04.078+02:00a source: http://osdir.com/ml/clojure/2010-11/msg0...a source: http://osdir.com/ml/clojure/2010-11/msg00399.htmlgtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-63373658745194353952011-12-06T20:52:04.794+02:002011-12-06T20:52:04.794+02:00I think you should be careful there, it's note...I think you should be careful there, it's noted that transients aren't designed for in-place bashing, you're still expected to use the result of the function in the next call, not the initial reference. In my minimal testing, I haven't found an issue but some coworkers have had problems trying to use it that way. It's definitely not guaranteed to work, though.gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-59616709353563206612011-12-06T18:59:32.574+02:002011-12-06T18:59:32.574+02:00@skuro Thank's a lot, that was what I was look...@skuro Thank's a lot, that was what I was looking for. Strange, that I couldn't accomplish the same with transients myself - as it's quite simple... This makes Clojure a little bit more usable, than before.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-26254809808972157172011-12-06T10:43:50.246+02:002011-12-06T10:43:50.246+02:00@Vsevolod Dyomkin your CL code wasn't translat...@Vsevolod Dyomkin your CL code wasn't translatable into Clojure + transients for the following reasons:<br /><br />- transients in Clojure are indeed limited (e.g.: there's no transient for lists)<br />- vectors append to tail (and I assumed order matter)<br /><br />Maps are different beasts, thus:<br /><br />user=> (let [a (transient {})]<br /> (doseq [b {:a 1 :b 2 :c 3}]<br /> (apply assoc! a b))<br /> (persistent! a))<br />{:a 1, :b 2, :c 3}<br /><br />Or would your point be really about order and reversal even in case of maps?skurohttps://www.blogger.com/profile/00810790510735051811noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-68459515503222289402011-12-06T03:27:11.487+02:002011-12-06T03:27:11.487+02:00Thanks for the post! I am on still on the fence de...Thanks for the post! I am on still on the fence deciding whether using Clojure has made my programming life easier or not (and whether to use it for my next project). So your perspective as a Lisper was very valuable!Felix Breuerhttps://www.blogger.com/profile/07562782118792528889noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-83938769079835908272011-12-05T20:08:48.470+02:002011-12-05T20:08:48.470+02:00@gtark yes, even if you take vectors, you won'...@gtark yes, even if you take vectors, you won't be able to do the same with them. Your only option is loop/recur (and that was what I mentioned in the article). And there are case, when it's far from the best pattern: like, when you have to accumulate data in maps, for example. Actually, it's pretty hard to show it on toy examples, but when you encounter real-world problems, you can have massive loop/recur loops, that can span more than a page of code (actually, a couple of nested loops). This is when it gets really nasty...Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-70845427208716123932011-12-05T19:53:26.403+02:002011-12-05T19:53:26.403+02:00Ah, I just tried to do it with transients, turns o...Ah, I just tried to do it with transients, turns out they're not implemented for lists, which makes sense, since they're just cons cells and there wouldn't be a performance benefit. <br /><br />From the clojure site: "Note that not all Clojure data structures can support this feature, but most will. Lists will not, as there is no benefit to be had."<br /><br />Conj'ing onto a vector appends to the tail, since that's the end that grows.<br /><br />The closest we can get for this example is loop/recur, which really isn't that bad.<br /><br /> (loop [in '(1 2 3) out '()]<br /> (if (empty? in) out<br /> (recur (rest in) (conj out (first in)))))<br /><br />But this example in general goes against what clojure's optimized to do, lazy sequences/streams and the relevant functions.gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-15775374372364784162011-12-05T19:07:56.808+02:002011-12-05T19:07:56.808+02:00@gtrak ok. Actually, there can't be neither an...@gtrak ok. Actually, there can't be neither an implementation of this exact pattern with transients, nor with anything else in Clojure.<br /><br />> But, I personally think the use-case for mutability is very rare. I'd like to see some examples where a mutable, imperative example cannot be made more concise and simpler with a functional approach, or cannot be handled by clojure's iteration functions (doseq, for)<br /><br />This is purely hypothetical. If you engage in some projects, that require implementation of complex algorithms, you'll soon see the need for it...Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-27718105253899908722011-12-05T18:44:36.648+02:002011-12-05T18:44:36.648+02:00I do enjoy trolling, but in this case I just didn&...I do enjoy trolling, but in this case I just didn't see you asking for an implementation with transients until I had submitted already. My goal is to learn new, better approaches, if if the conversation's not going anywhere I'll take my efforts elsewhere, too.gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-72352535743002255382011-12-05T18:37:23.357+02:002011-12-05T18:37:23.357+02:00There is stuff in the works to support mutability ...There is stuff in the works to support mutability more flexibly and safely. Rich Hickey spent some time talking about "Pods" at the last clojure-conj, they basically let you safely take advantage of the performance gains of transients from multiple threads.<br /><br />http://kotka.de/blog/2010/12/What_are_Pods.html<br /><br />But, I personally think the use-case for mutability is very rare. I'd like to see some examples where a mutable, imperative example cannot be made more concise and simpler with a functional approach, or cannot be handled by clojure's iteration functions (doseq, for)gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-44104553627318046112011-12-05T18:35:49.000+02:002011-12-05T18:35:49.000+02:00@gtrak Oh I see, you like trolling. Show me some o...@gtrak Oh I see, you like trolling. Show me some of your public code (have you heard of github?), or I won't waste any more time answering you.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-24712122264035551842011-12-05T18:15:01.723+02:002011-12-05T18:15:01.723+02:00@Vsevelod Dyomkin
per your example:
"CL-USER...@Vsevelod Dyomkin<br /><br />per your example:<br />"CL-USER> (let ((a '()))<br />(dolist (b '(1 2 3))<br />(setf a (cons b a)))<br />a)<br />(3 2 1)<br />"<br /><br /><br />Here's how you do it in clojure:<br />=> (reduce conj '() '(1 2 3))<br />(3 2 1)<br /><br />Show me another :-)gtrakhttps://www.blogger.com/profile/01357233765157459794noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-29275231251560329572011-12-04T15:16:59.522+02:002011-12-04T15:16:59.522+02:00@Sam I have clearly stated the principles, that a ...@Sam I have clearly stated the principles, that a Lisp variant should follow: dynamicity, simplicity and putting control in developer's hands (flexibility and extensibility). And tried to show with concrete examples, how they aren't followed by Clojure in critical parts of the language. And, as it turned out, I've examined mostly the same areas, that are covered in Clojure rationale (http://clojure.org/rationale), so, indeed, those are critical parts of the language.<br /><br />Surely, the whole article is opinionated and grounded in my experience. And, I think, we should admit, that this is how every argument is in software development. For me "purely language standpoint" is just a spherical cow, because, probably, any feature can have merit from some specific use-case or point-of-view, so if you don't declare your principles first, everything else is just word jiggery.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-7067945936845301002011-12-04T13:36:55.129+02:002011-12-04T13:36:55.129+02:00"...actually, [Clojure is] not a Lisp at all&..."...actually, [Clojure is] not a Lisp at all"<br /><br />Yet another article that declares Clojure to not be a "Lisp" without providing a strong argument as to why. In this case the argument starts:<br /><br />"But, in my opinion as a Lisp programmer..."<br /><br />So it seems that not only is this argument clearly driven by opinion, it's self-referential - you have to already be a Lisp programmer to be able to differentiate Lisps from non-Lisps.<br /><br />I'd have much preferred to read an argument about which features of Clojure have merit and where the author believes Clojure to be lacking - purely from a language standpoint without having to resort to "Lisp variant A is not a True Lisp therefore not as good as Lisp" nonsense.Sam Aaronhttps://www.blogger.com/profile/07718211095858116397noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-8024958651186411652011-12-03T16:17:59.464+02:002011-12-03T16:17:59.464+02:00Its as always with hypes - its not the fault of th...Its as always with hypes - its not the fault of the hyped thing. Clojure is a cool project and a versatile tool. The "Next Lisp" it ain't be. Like the hypers have seen it. It will perhaps be just yet another Lispy JVM language; a good one even if it is a relatively unlispy one.<br /><br />Clojure has a difficult stand:<br />Only time will show if Non-Lispers really go beyond its still Lispy Syntax and its own self grown oddities, or if another language has to come which delivers the Clojure cool-aid in a more traditional package.<br /><br />Lispers, particularily Common Lispers and Schemers will not find a complete replacement in Clojure. It misses important bits of those languages.<br /><br />My gut tells me, that Clojure is here to stay, but its influence will be more used in other languages than within itself. But hey! Thats just my guts talking.Jochen H. Schmidthttps://www.blogger.com/profile/10098367161841101950noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-66308881725177282832011-12-02T22:12:23.549+02:002011-12-02T22:12:23.549+02:00And, once again, to all, who call transients mutab...And, once again, to all, who call transients mutable local state, please translate into Clojure code, using transients, the following simplest example from CL:<br /><br />CL-USER> (let ((a '()))<br /> (dolist (b '(1 2 3))<br /> (setf a (cons b a)))<br /> a)<br />(3 2 1)Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-1535524775352217992011-12-02T22:06:16.908+02:002011-12-02T22:06:16.908+02:00> About killer apps: CL did not deliver too man...> About killer apps: CL did not deliver too many of those, although it had decades of time, while Clojure showed very nice developments in only three years. The trend is obvious.<br /><br />It's a pity, that you view this post as antagonistic between Lisp and Clojure. Lisp may have or not it's killer apps (I would say, Emacs is one of such: and you should know, that elisp is CL's mostly incomplete copy), but that doesn't effect Clojure. What effects Clojure's adoption is that people, who are looking for solutions to concurrency problems, will see RabbitMQ as an example of effective solution in Erlang, but won't find such examples in Clojure...<br /><br />> The STM: it is not so deeply integrated that you couldn't use some few other STMs when you do Clojure development. Just provide your own version, put them online at Bitbucket and others can use it. It is just one STM that ships directly with Clojure. CL does not even ship one.<br /><br />The point was, that you can't control Clojure STM. This is unlispy, in my view. That's all.<br /><br />> For distributed problems there already are loads of helpful libs, you listed some. Those are lib addons, and don't belong into the language itself. The CL world lacks things such as Terracotta & Co.<br /><br />Surely. Was I saying, that CL is positioned as the best language to solve hard concurrency problems? The question was about Clojure.<br /><br />> You critizized Leiningen and the build tools, while Clojure is way ahead of all what CL can offer. Quicklisp is nice, but it still lacks versions, without which one can nearly not develop professionally. You will have to use subrepos (in hg for example) and do the work yourself.<br /><br />Actually, versions are not an issue of quicklisp, but of ASDF. And it supports versions, although not to the desired extent (only equal comparisons of versions, not greater/lesser comparisons). But that is quite enough to develop professionally, because, as you know, two versions of the same system can't be loaded into one image anyway. Neither in CL, nor in Java (and Clojure consequently). So if you specify exact version dependencies in ASD files, quicklisp will do the right thing for you.<br /><br />What I didn't like about leiningen is that it works like a black box. And you can't trust it: a couple of times it wiped the contents of my lib/ folder, which had some java libraries in it, that appeared there <b>before</b> leiningen was even installed. The other issue was with the ability to specify multiple java source paths, which was fixed some time ago, but too late for me to find out about that (and there's still no mention of that in the docs).<br /><br />> So, for professional development I would always go with Clojure. And in private too, because it feels much nicer and is decades more modern than CL.<br /><br />I've expressed my view here. You have your own, and you are free to boast about it in your blog. To make an informed decision people need to consider different views.<br /><br />In the project, I've mentioned (which was not heavy on concurrency, but heavy on algorithms) Clojure didn't work for me. I had to complete it in Java, because Clojure gave no outstanding benefits, while was very hard to work with for other members of the team, who didn't have any Lisp background, like myself, and the desire to overcome various hurdles, posed by the language.Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-69565541776181398462011-12-02T22:05:23.521+02:002011-12-02T22:05:23.521+02:00@kury
> You brought up some correct points, ho...@kury<br /><br />> You brought up some correct points, however, I mostly have to disagree. Lots of things that you mention are of theoretical nature, which have no practical limitations.<br /><br />But I encountered them in my daily practice of using Clojure. I've honestly tried to become friends with it and help my co-workers start using it.<br /><br />> In these three years I never missed tail call optimization.<br /><br />My problem isn't with lack of TCO. Actually, loop/recur pattern is quite good in itself (although, sometimes you'd prefer to have a separate function, which isn't possible without TCO). What I very much miss are imperative loops, which amount to 30-50 percent of my loops (and loops are essential). I mean imperative loops, that manipulate some local state, that isn't possible in Clojure.<br /><br />> Your )))))))])))))])]) example is another such fabrication. How often per year do you stumble upon such a construct in the real world?<br /><br />Actually, it's not my example - it's from the Python world. I'd say every week at least I had to deal with it. If you do some involved binding in let using mapping and so on, you would encounter that. But I agree, that it's a very minor issue. But the whole point was, actually, a counter-point to Rich Hickeys mention in his talk of '() and () being interleaving (which makes makes not more sense).<br /><br />> Lisp-1 is more readable than Lisp-2, and even heavy use of functional programming is not required.<br /><br />This is purely subjective. For me - the opposite. I'm sometimes confused, when I see in function position something, that resembles a (data) variable. I'm never confused in CL with this. <br /><br />> Your (Map Key) vs. (gethash Map Key) example is flawed, because it unfortunately is (gethash Key Map). And there are tons of other inconsistencies in CL that Clojure finally threw away, which cleaned up the language and makes code more readable.<br /><br />Yes, Clojure clean-up some bits in CL, but also introduced a lot of its own inconsistencies. And it just omitted tons of useful functionality from CL.<br /><br />> I agree with you that it would be nicer to be able to have more control over pmap. But good that there is such a construct, while in CL there isn't. And lots of things in CL can also not be controlled so nicely in general.<br /><br />So how long does it take to create pmap in CL? (Hint: here you can find one of the variants - http://marijnhaverbeke.nl/pcall/). It's totally a library function. And being a library function you get the benefit, that when you don't like an implementation, you can use any other. In Clojure you also can, but it's not very good to redefine the language core, don't you agree?Vsevolod Dyomkinhttps://www.blogger.com/profile/07729454371491530027noreply@blogger.comtag:blogger.com,1999:blog-6031647961506005424.post-71982009424018357752011-12-02T21:02:38.266+02:002011-12-02T21:02:38.266+02:00@kury I know, that's why I called them *local*...@kury I know, that's why I called them *local* mutable state. And it's quite nice that they remain local, as all the pitfalls of concurrent access are avoided at once. I'm happy for the OP that he's not struggling with it, but I personally do care for multithreaded concurrency issues.<br /><br />I brought transients into the discussion to counter the statement that mutability was completely banned from Clojure, which I find simply untrue because of transient, interop and STM.skurohttps://www.blogger.com/profile/00810790510735051811noreply@blogger.com