Hacker Newsnew | comments | show | ask | jobs | submit | login

I've read through the article twice, and I still have no idea what the author is getting at.

The author suggests that "the obvious way to implement a DSL as a macro, as we saw with if-match, hard-codes the form of the new syntax class". I disagree. That's not what I'd consider the obvious way at all.

I'd consider the most obvious approach would be to pass the macro onto a polymorphic function of some description:

    (defmulti if-match*
      (fn [pat _ _ _] (if (list? pat) (first pat) (type pat)))

    (defmacro if-match [pat expr then else]
      (if-match* pat expr then else))
Macros have all the same capabilities for extensibility as regular functions. In Clojure at least, macros are just functions with some metadata attached.

reply


That's a very clever use of defmulti that I hadn't considered --- consider that you may know more about writing extensible macros than the average lisper :P. My article was also aimed at being language-agnostic, so a Clojure-specific feature like defmulti wouldn't have been appropriate to introduce. (Although of course CLOS does have multimethods as well, but that's an even more complicated subject!)

However:

1. The code you give still isn't smart enough. It dispatches on the symbol at the head of the list, but that doesn't account for namespacing. So your pattern-macros will all end up in one giant namespace. You could probably invent something clever to account for this but...

2. My overall point[1] was that writing a macro-extensible macro shouldn't require cleverness or new code - it should be in the standard library! Indeed, ideally defining a "pattern-macro" should be accomplished via the same mechanism as defining an "expression-macro"; you shouldn't need separate, custom macro-defining-macros for each syntax class. I'd settle for it just being easy to define an extensible syntax class along with a macro-defining-macro for it, though.

[1] Admittedly, this point could have been far clearer.

reply


Regarding 1., I don't think it follows that the pattern-macro will end up in one giant namespace. I'd love to understand why you think so.

And for 2, even though what you say seems desirable on the surface, you still approaches the problem in a way that is too fuzzy, or abstract. Just as saying "we should write more secure code" and then failing to attack the problem directly.

No offense, but even though you may have a nice idea, your explanation is a little too handwavy.

reply


Regarding 2: The article punts on the question of how to implement such a system, because I think it's a hard question. I could have been clearer about that, and maybe in future I'll write an article speculating on some ways one could approach the problem. But I think it's an underappreciated problem, and I hope this article will spur other people to consider it.

Re 1:

In a.clj:

    (ns a (:refer-clojure))

    (defmulti if-match*
      (fn [pat _ _ _] (if (list? pat) (first pat) (type pat))))

    (defmacro if-match [pat expr then else]
      (if-match* pat expr then else))

    (defmethod if-match* clojure.lang.Symbol
      [pat expr then else]
      `(let [~pat ~expr] ~then))
In b.clj:

    (ns b (:refer-clojure) (:require a))

    ;; a 'foo pattern-macro that does negation
    (defmethod a/if-match* 'foo [pat expr then else]
      `(a/if-match ~(second pat) ~expr ~else ~then))
In c.clj:

    (ns c (:refer-clojure) (:require a))

    ;; a 'foo pattern-macro that is the identity pattern
    (defmethod a/if-match* 'foo [pat expr then else]
      `(a/if-match ~(second pat) ~expr ~then ~else))
Now, at the repl:

     user=> (use 'a)
     nil
     user=> (require 'b)
     nil
     user=> (if-match (foo x) 'yes x 'no)
     no
     user=> (require 'c)
     nil
     user=> (if-match (foo x) 'yes x 'no)
     yes
Note that 'require doesn't actually import the symbols defined by b.clj or c.clj, and despite that we're able to use the pattern-macro 'foo they define, because they're modifying if-match's dispatch-table. So our pattern-macros aren't being scoped the same way our regular macros are. I think this is wrong. Moreover, our pattern-macros have a single namespace, so we get collisions between what b.clj defines 'foo to mean and what c.clj defines it to mean, which is why (if-match (foo x) 'yes x 'no) changes behavior after the (require 'c).

reply


It's worth clarifying that macros differ from functions in clojure in some pretty important ways, most obviously that you can't take their value - you can't (map macro coll) etc, which to me at least is a regular frustration.

That aside, clojure.core.match is implemented pretty much as you describe:

https://github.com/clojure/core.match/wiki/Extending-match-f...

I too was a bit confused about what the article was getting at.

reply


It's definitely a database.

reply


It's definitely a very slow database. You have to be extremely fortunate to have a problem that fits into its niche neatly. I'd sooner figure out a historical insert-only schema for PostgreSQL in future. They're not great about fixing problems with Datomic either, it feels like an afterthought. Means of overflowing labor not currently allocated to a Cognitect contract gig, not a priority in its own right.

-- sad production ex-user of Datomic

reply


I think they've improved a lot WRT fixing problems--we had a chat with them after some issues with Datomic in production, and since then (6 months ago) we've had every problem we've discovered get fixed very promptly, and Datomic's continued to scale for us.

reply


coolsunglasses - Why did Datomic seem slow to you? Can you describe the problems you had in detail? I'm not from Cognitect, just someone who is developing some prouducts that currently use Datomic among a few other databases.

Would love to hear some honest feedback. Maybe your struggles were because of the tech, earlier versions, bad hardware config, or mis-applied use case?

reply


European countries have gone to war over Poland before. The idea that the EU would sit back and allow another country to invade its member states is just crazy. If nothing else, it would cause huge financial disruption to all the other countries in the union.

Britain, France and Germany alone have a combined military budget greater than any other nation save the United States. The UK went to the other side of the planet last time its dependant territories were threatened. The idea that the EU wouldn't defend itself, when it has the capability, the motive, and a long and bloody history of warfare, is so bizarre I can't even begin to understand your reasoning.

reply


China's military budget is likely beyond France + Britain + Germany. It's admitted to be $141 billion now, and most analysts think that's understated by upwards of 50%.

Europe is allowing Ukraine (of course not a EU member) to be destroyed right now. and Russia isn't done yet, they're going to take more territory. Europe at best has been half-limp in its response. Which makes sense given the energy ransom Russia holds over most of Europe's collective head.

Like Europe previously looked the other way while Georgia was sliced to pieces. What does that have to do with the EU? We're seeing that right now, Putin is being encouraged in Ukraine, he is seeing that there are no military consequences from the EU to taking non-EU European territory.

By the time Russia gets to Odessa, Moldova is going to look like a free acquisition. Belarus is another easy target for Russia, and Putin has already said 'unifying' with Belarus was desirable and possible. Is the EU going to war with Russia over Belarus? No chance, and that's as much in their backyard as you can get.

reply


France, Britain and Germany have a combined budget of $167 billion, and even if China's military budget turns out to exceed that figure, that's only 3 of the EU's member states.

China's military is also severely lacking in combat experience. The US is one of the most battle-tested military forces in the world, has relatively little corruption compared to China, and yet it still suffers from projects like the F-35. It's hard to estimate how effectively that Chinese budget is being employed.

The EU isn't sending a military force into Ukraine, but then neither is the US. The EU does have problematic energy ties to Russia, but that works both ways; just as Europe has an unhealthy dependency on Russian gas, Russia has an even greater dependency on Europe.

Putin's playing a dangerous game with the EU. On the one hand, the EU relies on Russia for about a third of its gas and petroleum. On the other hand, Russia depends on the EU for the majority its energy exports, and virtually all its gas exports. The EU doesn't need to go to war with Russia to cripple it; Russia's economy currently depends on the EU accepting its imports.

Ukraine is a difficult problem to solve. Sure, the EU could march in and reclaim Ukrainian territory without much resistance. The Russian army may have a lot of equipment, but most of it is outdated, and the first Iraq war proved how overwhelming an advantage a technology gap can be. But the EU has also seen what's happened to Iraq and Afghanistan, and knows that the east of Ukraine has strong Russian ties. It doesn't want to find itself in embroiled in a decades-long guerrilla war, which is what would happen if it used force.

Putin knows this, but he also knows that his country's economy is dependent on the EU. He's trying to edge Russia into a better strategic position without provoking the EU into action.

reply


> If you extrapolate from the amount of progress we have made toward AGI in the last 50 years (ie, none)

That's an odd way of defining progress.

> There are intellectual problems that humans aren't capable of solving; it wouldn't make any sense to talk about "superhuman intelligence" if that wasn't the case.

A superhuman intelligence doesn't necessarily have to come up with solutions humans would never think of, it just needs to come up with a solution in less time, or with less available data, or with fewer attempts.

reply


Java doesn't have particularly good compile-time type checking. It's mostly there for performance rather than safety guarantees.

While Clojure is a dynamic language, it actually does have optional type checking, courtesy of the core.typed library. This isn't perfect, but it is considerably more sophisticated than Java's inbuilt type system.

Even without static type checking, I'd argue that Clojure is the safer language by default, since it mostly avoids mutability.

-----


This. The immutable nature of Clojure is what, IMHO, makes it such a better choice than Java for me. I frequently see discussions/debates around type safety on HN, but in my experience, it has been the mutable nature of Java systems that has created more bugs than anything else. Clojure being immutable by default has drastically reduced the pain caused by these bugs. Clojure's collections, FP style, and meta-programming (via macros) all make it a great choice for me, but I think being immutable by default is the biggest thing.

-----


Protocols and records exist purely for polymorphism. If you want type safety, then perhaps try core.typed:

    (defalias Point
      (HMap :mandatory {:x Num, :y Num}
            :complete? true))

    (ann add-vectors [Point Point -> Point])
    (defn add-vectors [a b]
      {:x (+ (:x a) (:x b))
       :y (+ (:y a) (:y b))})

-----


> The advantage of OO has always been its accessible constructs and abstractions that mirror how we talk about things linguistically.

I don't think there's much of a connection to natural language. OO is about commanding objects through messages; if we must make a connection to language, it would be equivalent to the imperative tense.

> Heck, even Clojure has its own object systems; they just often call them entities rather than objects

Which object systems would these be?

-----


> I don't think there's much of a connection to natural language. OO is about commanding objects through messages; if we must make a connection to language, it would be equivalent to the imperative tense.

I've designed non-imperative OO languages before; e.g. http://research.microsoft.com/apps/pubs/default.aspx?id=1793...

The community didn't scream out and say "but that language doesn't have messages, it can't be object oriented!" Of course, Alan Kay wasn't at ECOOP that year, but I did get an argument from Ralph Johnson that what I did was just Smalltalk :p.

> Which object systems would these be?

Clojure's (Rich Hickey's?) ideas about OO are surprisingly close to my own:

http://clojure.org/state

> OO is, among other things, an attempt to provide tools for modeling identity and state in programs (as well as associating behavior with state, and hierarchical classification, both ignored here). OO typically unifies identity and state, i.e. an object (identity) is a pointer to the memory that contains the value of its state.

The important thing about objects is their identity; they have names like Fred or Bob; they aren't anonymous values that can only be identified by their structure 42 or (2, 3).

> There is no way to observe a stable state (even to copy it) without blocking others from changing it.

He is not against objects, just how they are realized in imperative languages.

> OO doesn't have to be this way, but, usually, it is (Java/C++/Python/Ruby etc).

Yep. So he solves the problem in a smart way:

> In coming to Clojure from an OO language, you can use one of its persistent collections, e.g. maps, instead of objects. Use values as much as possible. And for those cases where your objects are truly modeling identities (far fewer cases than you might realize until you start thinking about it this way), you can use a Ref or Agent...

I will disagree, as soon as you have a collection with a key that is a GUID (or a name like Fred or Bob), you've just invented an object, whether its properties are embedded in the object or not.

Clojure just has its own ways of doing OO programming. If you hate OO, then you might simply claim "it is not OO", but this definitely is not pure functional programming that lacks identity at all (and pure Haskell solutions really do avoid all of this, Haskell is very non-OO in ways that pragmatic LISPs are not).

Note that there are other ways of fixing this problem, one can manage time so that object properties are always observed safely; this is the approach I'm currently taking with my own research:

http://research.microsoft.com/pubs/211297/onward14.pdf

-----


> as soon as you have a collection with a key that is a GUID (or a name like Fred or Bob), you've just invented an object

That's an unusually broad definition of an object. Is that the only criteria you have? Does the key need to be unique?

-----


If you are contrasting objects to pure values, that is really the main distinction: objects have names at design time, compile time, and run-time, values do not. I don't think it is unusually broad (at least it is not considered a radical position in the academic OOP community). Everything else: encapsulation, methods attached to data, subsumption, nominal subtyping, require that an object has its own identity and further support its "objectness."

Even if you didn't have these features in your language, they are fairly straightforward to construct in an ad hoc way as long as you have identity (that includes aliasing, obviously). Some kind of object system is often invented while building C programs of non-trivial size.

If the key isn't unique, then multiple objects could share the same state...they would be the same object!

-----


I'm not sure what you mean by a "name". Do you mean the same thing as Hickey's definition of "identity", i.e. "a stable logical entity associated with a series of different values over time"?

-----


Yes. To identify something whose fields change over time, it needs a name (even if that name is just a GUID or address). Well without fields, you don't need names, but this isn't a very interesting case (it is hard to scale up programs with just what are essentially global variables!).

Values are anonymous in contrast: you can't really tell the difference between this 42 and that 42.

-----


So would you consider a pointer to a memory location to be an object?

-----


Use the wiki [1]:

> In computer science, an object is a location in memory having a value and possibly referenced by an identifier.

Not useful huh?

I would say, if the memory location is heap allocated and has mutable fields, then it is probably acting like an object (it could also be a value, it depends if its identity is important or not).

[1] http://en.wikipedia.org/wiki/Object_(computer_science)

-----


Wikipedia appears to distinguish between "object-based" languages and "object-orientated" languages. On the subject of object orientated languages, it says:

> An object has state (data) and behavior (code).

Which is the definition I'm most familiar with. I wouldn't class a Clojure reference as an "object", as it has state but no inherent behavior.

> I would say, if the memory location is heap allocated and has mutable fields, then it is probably acting like an object

So a reference to an immutable map wouldn't count as an object for you, because there are no mutable fields?

-----


The reference to the immutable map doesn't count (a immutable map is just a value), but keys into the map can form objects (in that they represent "object" properties). Actually, that is how you get objects without first class mutability (identities must still be generated, of course, which is the same thing).

Ya, the wiki article makes a distinction between programming and designing with objects, and object-oriented programming. It really. All my arguments are about designing and programming with objects vs. designing and programming with values, call it "programming with objects" if you must.

-----


> I expect Clojure to be the same in a year or so.

I'd say it was there years ago. On Heroku you can deploy any Clojure project that specifies a ":main" namespace. Deploying Clojure to a VPN is no different to any other language that runs behind an nginx proxy.

-----


No it doesn't. I don't know why people think this.

-----


Because it interoperates with Maven. It can fetch and install code from Maven repositories, can read and write a pom.xml file, and uses the same groupId/artifactId/version format for specifying dependencies. And it also depends on a few org.apache.maven packages. So without looking at the source it would be easy to mistake lein for a Clojure wrapper around mvn. But it's more like a Clojure implementation of a superset of Maven's functionality, I guess.

-----


You and I are definitely both correct about it being a dependency: it caused me some installation headaches. I may have misunderstood the docs about how deep of a dependency it is; I haven't read the source.

Returning to the OP's point, given that it is a dependency, it's still possible there is some resulting startup overhead, but I couldn't say for sure without profiling it.

-----


My biggest headache with it was just figuring out how to tell it to use a local jar. It really wanted to fetch it from clojars or maven central but sometimes you just need to use a jar that's sitting on your hard disk. IIRC there's no standard way to do this and I had to create a local maven repo for lein to pull it from.

-----


It used to be true; the 1.x series used actual Maven code. But that was years ago.

-----


Macros can be useful as syntax sugar, but a lot of the time they obscure the meaning of the code. In general, data is better than functions, and functions are better than macros.

For instance, in your parser example, you could get exactly the same syntax just using higher-level functions. There's no need for a "defparser" macro at all.

Clojure treats macros as a last resort, something to reach for only when you've exhausted all other options. Without them, we wouldn't be able to have language extensions like core.async, core.typed, and core.logic packaged up as optional libraries, but in most cases they should be used sparingly.

-----

More

Applications are open for YC Summer 2015

Guidelines | FAQ | Support | API | Lists | Bookmarklet | DMCA | Y Combinator | Apply | Contact

Search: