
Metabase: Why we picked Clojure - tlrobinson
https://medium.com/@metabase/why-we-picked-clojure-448bf759dc83#.w1uqkz6sp
======
kisstheblade
" However, we had a strong aversion to using Java itself, so the real short
list was Scala or Clojure."

Sadly they couldn't articulate any actual problems with Java...

I find it weird that so many developers focus on the "writability" of the
language. I mean stuff that java gets criticized for, like verbosity etc.
Which I don't even find so bad especially since the newer jdk versions.

But in my opinion the _readability_ of the result is the most important
factor. I rather read copy/pasted code than some smart clojure onliner which
nobody dares to touch or really understands.

You only write the program once, but you have to read it over and over again
when maintaining it and developing new features, and java's maybe a little
verbose syntax really helps here, and also the fact that there really is only
one way to do a thing (unlike eg. perl).

The tooling, documentation, frameworks, libraries and runtimes are all top
notch with java. All other languages seem to have problems in one (or more) of
these areas.

~~~
j-pb
Clojure allows me to express thoughts that I wouldn't know how to express in
for, while, if, variables and objects.

And on readability, what do you prefer?

    
    
      List<Integer> even = new List()
      for(int i = 0; i < 100; i++){
        if((i%2)==0){
          even.add(i);
        }
      }
      return even;
    

vs.

    
    
      (filter even? (range 0 100))

~~~
yeukhon
But what is even?

I am sure functional programming has its natural appealing in areas like
recursion, but this example doesn't seem too convincing for winning
readability, I can always write this in python

filter(even, range(0, 101)))

where even is a function I wrote.

Please give an example for "Clojure allows me to express thoughts that I
wouldn't know how to express in for, while, if, variables and objects." if you
can.

~~~
j-pb
We were talking about java here. ^^ So python is offering a lot more already
with it somewhat first class functions. What would you guess even? does, btw?

But sure, let's make things a bit harder. As below, let's produce a sequence
of the form [true false false true true true false false false false ...] of
exact length 1000 without computing any more than exactly 1000 elements.

    
    
      (take 1000 (mapcat (fn [i] (repeat i (odd? i))) (range)))
    

Of course the really interesting stuff is hard to do in one liners. But I
think one of the more mind bending things is probably core.async. Go style
coroutines and channels implemented as a library on top of the language. Which
allows for concurrent programming inside clojurescript which is compiled to JS
and thus inherently single-threaded.

[http://swannodette.github.io/2013/08/02/100000-processes/](http://swannodette.github.io/2013/08/02/100000-processes/)

~~~
yeukhon
My point was "even" and "filter" can be built by someone, just to be fair
about the whole readability. If "even" is already built into the language,
then that's a given.

Readability and language understanding are not separable. I for one have a
hard time actually understand what the code does above (although I can guess
it). But I am not sure how ^ translate into [true false false true true true
false false false false ...] sequence because, I thought we were calculating
oddity of 1000 numbers.

[1 2 3 4 5 6 7 8 9 10 ...] ==> [T F T T F T F T T T ]

Perhaps the lack of understanding Clojure syntax to comprehend what the code
is doing.

~~~
huahaiy
Exactly the point. In many other languages, every programmer can write their
own "even" and "filter" with their own idiosyncrasy. However, in Clojure,
every one uses the same set of high level functions in the standard library,
because they are sufficiently rich to express most of the programming
concepts, without having to roll your own.

In Clojure, readability is obtained through standardization. Every Clojure
programmer instantly knows what the functions in the standard library do.
Because there's not much syntax in a Lisp, code is very easy to read once one
knows the functions.

~~~
dragonwriter
> In many other languages, every programmer can write their own "even" and
> "filter" with their own idiosyncrasy.

"filter" is incredibly useful, and most modern languages (including Java 8)
have it built in.

"even?" is sometimes useful, and languages are kind of hit-and-miss about it,
but its usually small enough to inline, and the biggest cost of not having it
built-in is probably things like people using modulus rather than first-bit
testing in Java which, IIRC, produces somewhat slower code for that check.

------
iagooar
It's great to see such an "exotic" programming language like Clojure getting
some serious traction. And by exotic I mean a language that stands out from
the imperative or object-oriented crowd.

I like how Clojure challenges established paradigms with a surprisingly mature
ecosystem.

Being a hosted language on the JVM has its pros and cons, but the cleverness
and pragmatism of Rich Hickey (creator of Clojure) really stands out. He
decided to go with an industry strong, battle-tested environment, effectively
standing on the shoulders of giants, while providing a lot of fresh ideas.

Also, the fact of being a Lisp can be seen from different angles: for some
it's lots of weirdly nested parentheses, for others it has a beautifully
simple, yet extremely powerful and expressive syntax.

But don't let those aspects fool you. Once you scratch a little bit the
surface, you will see the extraordinary power of its persistent data
structures. For those who don't know what persistent data structures are: in
Clojure, oversimplifying, there is no mutation to data structures. Every time
you "add" or "remove" a key from a hash map, you are actually creating a copy
of it. And in order to make this efficient, you need special data structures
with "superpowers", that are not actually creating a whole copy of themselves,
but rather modify atomically only a part (hint: trees).

And having those data structures leads to the next big feature of Clojure:
concurrency. Not only does Clojure provide a mechanism to dispatch thread-like
constructs (core.async), but the whole language is designed around supporting
concurrency.

I also want to add that what really surprised me about Clojure is its thriving
community and smart people using it. I am pretty sure that if you want to hire
great, highly motivated talent, going Clojure will help you with that.

~~~
troym
> Once you scratch a little bit the surface, you will see the extraordinary
> power of its persistent data structures.

I've heard this repeatedly, yet in the examples I've read (for web servers and
for web ui work with cljs), they're using atoms with `swap!` to provide
"current state".

Could you provide an example of using persistent data structures in a web app?
Or am I mis-understanding?

~~~
danneu
You're compare-and-swapping out persistent values with an atom (it holds a
succession of immutable values over time). It's not an example of people
shirking persistent data structures for mutation.

~~~
troym
Yes, I understand that. I guess my original question would have been better
put: can the notion of "current state" be avoided with persistent data
structures, and if so, how?

~~~
erikpukinskis
I'm a hardcore imperativist right now, so don't take this as authoritative,
but here's my understanding of how that works:

Think of your data as one reality in the set of all possible realities. None
of them exist or don't exist, they'll just receive human attention or they
won't. Instead of an inbox with an ID and a current state, you just store
events that happened and a user might say show me the end state of the
timeline that started at event d79se5k. They might also ask you for the
timeline starting at a different point.

There's no "current state". You're just ready to answer questions about
hypothetical realities. And while some realities are more likely to be
computed (and cached) that's only because they correspond to some key facts
that a human is going to look up.

Put another way, imagine a soup of unrelated facts. Your code is a way to
project those facts into different mathematic spaces. You never just say
"what's the current state" you have to actually write out specifically what
you mean as a query.

Sorry for the vagueness. I'm just putting out there my fuzzy understanding
until someone else can write something better. :)

I'm really interested this approach, and I think a lot of data should be
immutable. Sadly, a lot of people are treating immutability as a religion
these days, which causes problems when computation is better suited to
imperative structures.

------
mark_l_watson
I read this article yesterday because I am having a language choice issue,
between Ruby and Clojure.

I am in my (almost) mid 60s, and except for being on retainer for advice I
don't really accept consulting work anymore. I have a __long __list of
projects that I want to do, mostly open source, and settling on one language
for a few years would make things easier.

Clojure and Ruby both support DSL development very well which is important to
me and both play very well with Java (via JRuby). I am starting to ask myself
though if I wouldn't just be better off with Java 8, even without good support
for writing DSLs. I have mostly stopped using Haskell and Scala.

When I was much younger I totally enjoyed learning and using many programming
languages but as I get older I feel more like just getting stuff done,
especially since I dedicate much less time now to technical pursuits. So in a
sense I am facing roughly the same type of decision as the Metabase people.

~~~
daxfohl
I follow your blog. I'm a month from 40 and agree. After several years working
mostly in Clojure, Scala, and F#, I am finding myself most productive in
traditional languages even though they're less interesting. And am finding
_projects_ more interesting than _implementations_ , which is probably the way
it should be.

I'm finding now that OOP is actually a pretty good model for most things.
Loops and mutation reflect reality: you do a task N times and write down each
result; you don't take the first N results from an infinite seq of tasks. IoC
is useful. In addition having a much larger set of things to copy/paste from
github/stackoverflow is nice.

That said, most (all?) of my paid work is pretty banal, nothing that's really
begging for functional style. And for whatever reason I've never had much
success with REPLs. Getting them updated correctly with the code and
dependency changes, typing in what I want to test, then running it always
seems to take me longer than just executing the app and testing changes
directly.

------
RyanZAG
_> .. However, we had a strong aversion to using Java itself .. _

Honestly, why? Seems odd given Java's great track record for exactly this
problem and huge library and developer share. The code itself is forced to be
static and explicit which makes for easy onboarding for new developers.

It's a bit strange to just dismiss Java in the context of the rest of the
article. You're kind of staking your company on decisions like these, and
making them because of some unquantifiable 'strong aversion' seems like a poor
choice.

That said, Clojure or other lisp base definitely seems like the correct choice
for a query language / dsl. That's the shining point of lisps after all.

~~~
zem
personally, i find java pretty painful to use; it's definitely capable of
doing whatever job you have in mind, but when i use it i have the constant
feeling i'm using a suboptimal tool. and then it leads to reams of boilerplate
all of which has to be maintained, so it's not just a development-time cost;
there's the depressing feeling of having to deal with it every time you want
to read the code. clojure would be worth it just for the joy of using a tool
that fits well into your hand.

~~~
RyanZAG
It's kind of like building a model with clay or with sticks. The clay model is
going to look nicer with rounded curves and no wasted points. The stick model
will have bits sticking out.

However when you get 30 people working together to make a big model, the clay
model is going to start looking very strange indeed. Different people will
have different finger pressure and shape the clay differently. Some people
will make more right angled parts with the clay which will clash with the
parts of the clay that are carefully smoothed out. Someone might spend a week
fixing a perfect parabola for a curve at the top of the model just for someone
else to bend it off to the side with an ugly twist to fit with the other side
of the model.

30 people on the stick model? Not really a problem. All the sticks look the
same. Anybody who needs to attach one side of the stick model to something
else just needs to grab a stick and tie it on. You won't see a perfect stick
parabola, but then you also won't be stuck with a strangely twisted piece of
clay.

You can always create a style guide for using the clay - always keep between a
30 and 60 degree angle on joins. Apply one finger of pressure when doing
joins. And so on. Sometimes this works with very diligent reviews that keep
everything in place, but as soon as a crisis comes up or the model needs to
pivot urgently? Everyone rushes to touch up the model in the quickest way
possible and your model starts to get a bit thinner on one side than the
other, etc.

So there's nothing wrong with choosing a great clay model that fits well into
your hand and you can shape effortlessly - but you need to remember that
sometimes the 'painfulness' of using a model made out of sticks can be a big
blessing when you're trying to massage a bit of clay that someone else pressed
a bit too firmly on.

EDIT:

Also, when you've been making and using models for awhile, the stick models
get an added benefit: you can browse through the stick models really fast
based on only their outline and be able to understand very quickly how they
work. Pretty clear why: the rigid sticks form a very distinct outline (think
interfaces and forced directory structure in Java). Being a stick model expert
therefore has very good job mobility and high demand.

Each clay model is generally more unique and requires some study to understand
even if it does look nicer.

~~~
a-saleh
I will try to continue your clay/sticks parable, but in my limited clojure
experience, you seldom create a clay model, it resembles more a creation of a
custom clay brick-set.

Most of the time, all of the team is either building bricks or using those
bricks.

The biggest plus of using clay instead of sticks is that when you find
yourself in a corner, it is easy to invent a new brick shape that fits.

The drawback is, that without the style-guide, you can easily end up with
brick-shape per developer. But I see a difference between "Hey, why did you
invent a new brick type again?" and "Apply one finger of pressure when doing
joins."

When crisis comes and model needs to pivot urgently, there are two
possibilities. Either you just need to rearrange your bricks. Or you realize
there is something fundamentally wrong with your bricks, and then you create a
new brick-type and carefully replace old bricks with new bricks.

Ok, programming parables are weird. I might write a more concrete example, if
I have time.

~~~
RyanZAG
Nice extension! Feels right too, clojure does feel a lot like passing around
similar clay bricks. When all the bricks line up, it's kind of like Lego?

The analogy also opens up another difference. In clojure, you often end up
passing around a lot of maps with string keys. What are those keys exactly?
You have to crack open the clay brick to take a look at how it works. With the
stick model, you can often see how the sticks work if the bundle of sticks has
been color coded nicely - eg, well constructed generics. Luckily cracking open
a Clojure brick is as simple as ctrl-clicking, so it's not nearly as bad as it
sounds!

Programming parables are definitely weird though - programming does not have
true analogies with the real world. It's more of a superset of the real world
and the use of programming languages is more akin to defining physical laws
than building bridges with clay or sticks. I don't think we as humans have
really come to understand exactly what we are unlocking with the development
of programming and how vast it really is.

------
dustingetz
"So, in summary, we chose Clojure for JVM threads + Database drivers, the
ability to ship an uberjar and the _ease of expressing tree manipulations_
which were the core piece of complexity in our backend."

for people who may not know clojure here is an example of tree manipulations,
it's really powerful especially in UI programming which is all about
manipulating recursive structures:
[https://gist.github.com/dustingetz/c11ae4edb9e0d14787d2](https://gist.github.com/dustingetz/c11ae4edb9e0d14787d2)

Scala has answers to this too (I would love for someone to reply with the best
scala way) but for whatever subjective reasons the clojure way makes me
happier for ui work.

As to scala pushing towards typed object mappers, here is one example of
generating untyped queries with Anorm [1], I'd be surprised if this couldn't
be made to work for their query language, but that said the scala ecosystem
has a lot of, shall we say "diversity of opinion" about the Proper Way To Do
Things, so I am not surprised that newcomers to the language had a bad time.

[1] (2013)
[http://www.dustingetz.com/2013/03/26/orm.html](http://www.dustingetz.com/2013/03/26/orm.html)

------
staticelf
This is probably going to be a very unpopular opinion here, but I think
Clojure is a horrible language to work with.

I don't have the experience with non imperitive languages which makes learning
clojure a really steep curve. Also the error you can recieve with clojure are
many times a big mystery, I've never seen as bad error reporting as I've seen
with Clojure. Other issues I have with it is that Clojure-fans tends to want
to write EVERYTHING in clojure or lisp style code. That includes stuff like
Javascript, HTML and similar stuff. Things that just makes it even more
complicated.

I have really tried to learn it several times but got so frustrated I quit. We
have a small thing at work which is written in Clojure and no one knows how to
fix issues with it since no one has any experience with Clojure.

Clojure does not fit my brain.

~~~
chrislloyd
I think your criticisms are definitely valid.

The learning curve is steep (intentionally so) but definitely worth it. Your
programs will become more reliable and you'll get more done as a consequence.
Please push through!

The error messages are terrible, and there are a lot of ongoing conversations
about them at the moment in the Clojure community[1]. The Clojure[script]
toolchain does tend to be all-in but there's work happening now to break that
up — stay tuned. StackOverflow and the Clojure Slack channel also full of
people who know how to fix issues :)

Stick with it, when you'll have an "ah-ha!" moment soon enough!

[1]: This talk is a good backstory as to _why_ the errors are so bad, and
useful no matter what language you use
[https://www.youtube.com/watch?v=FihU5JxmnBg](https://www.youtube.com/watch?v=FihU5JxmnBg)

------
mpdehaan2
Kind of shocked that building database libraries for python (homebrew install
gcc? pip install foo) was "hard" for them.

I find the Python ORM options (sqlalchemy/Django/etc) lot better evolved than
what is available for clojure and couldn't image that being a roadblock. Korma
is basically a query builder and lacks a lot of things I was used to, and I
had to engineer a declarative layer for defining models in the ways I wanted.

Pain in setting up developer machines could be simplified with an ansible
playbook pretty easily.

OTOH, one of the worst things I hate is trying to figure out how to
automatically get around Sun's paywalls and fiddle with Java classpaths :)

I do feel some level of want to avoid vagrant in the development workflow, in
which cases I'm usually fond of just logging into a real VM. I do think it's
potentially good for tests.

IDK, I always want to solve the idea of "fragile deploys" with immutable
systems in prod. Uberjar was cool, I'll give it that -- but the startup time
of clojure was often not worth the wait.

My biggest complaints with clojure is documentation quality, inconsistency in
return types (or just willingness to return nil in error scenarios), and
tracebacks that are very difficult to sort through.

Clojure also presents some problems with the idiomatic nature of trying to
roll up everything into one statement, whereas adding debug and debugging
require unrolling it, than packing statements up again to be socially
acceptable idiomatic clojure.

Testing in clojure also strikes me as painful, as it tends to punish you with
"let" causing an indentation every time you need to assign a variable,
something that is useful in tests that want to check results a lot along the
way.

My gut feeling is clojure was good for a little light scripting on top of a
big Java codebase (kind of like you might use Lua in a game engine) but would
not be something I'd pick for a primary language. Primarily using Java libs
would also solve some of the ecosystem problems.

~~~
salsakran
It's not hat "homebrew install gcc? pip install foo" is hard for us, it's that
we didn't want to support 1000s of people trying to set it up on their
machines.

Every possible source of error we eliminated meant we'd need to spend less
time on twitter debugging installation problems. It's useful to remember that
our target install base isn't python developers who might want to hack away on
a python project, but rather analysts, end users, DBAs and all kinds of people
who've never heard of homebrew and who might not even be comfortable with
using a terminal in Mac OS X let alone compiling things.

~~~
yeukhon
So why can't you release an installer? You are not compiling .NET. If many
desktop applications are either written in Python, Java, or C++, so what
prevents you from releasing an installer? Heck, even Go programmers have to
think about compile cross-platform.

~~~
andrioni
> Heck, even Go programmers have to think about compile cross-platform.

Not that much in Go 1.5 :) On the last project I worked, just setting GOARCH
and GOOS worked out of the box.

------
vosper
> Python’s Mysql and Postgres database drivers required compilation. This made
> both developer machine setup as well as deployment more complicated than it
> really needed to be.

Am I missing something - this was really a such a problem that it was a factor
in switching languages?

~~~
eterm
I've started interpreting a lot of these "Why we use X" posts as less
'bragging' and more these companies trying to justify their own choices. In
part because it perhaps makes them feel less isolated in their choices, and in
part also because by hoping that others follow them they'll find it easier to
recruit, something that is easily underestimated especially outside of major
hubs such as NY, SF, or London.

Perhaps I am being uncharitable. I note with interest they say, "However, we
had a strong aversion to using Java itself", but don't explain why they had
that aversion, or whether it was based on anything but the fact that Java
isn't trendy.

~~~
salsakran
Off the cuff, I'd estimate that between the team members at the time, we'd
shipped perhaps 100k+ LOC of java into production systems at past jobs.

It didn't seem interesting to rehash why we didn't want Java's verbosity in a
small team.

------
giancarlostoro
One of the things that I love about Clojure is Leiningen. It just takes away
the headaches of setting up projects in Clojure. I think the only other tool
I'm aware of for another language that is quite as useful is probably NuGet.
I've never had issues using lein, just drop it into my ~/bin/ folder (I'm
using openSUSE so that folder is in the system path by default), open up a
terminal and type in lein and it works. I don't even need clojure installed,
just the JVM, it will download clojure for you as needed!

Props to those working on lein.

~~~
chii
And yet maven does the same thing but gets no love from anyone (including me!)

~~~
giancarlostoro
Not sure if Maven can be compared to lein, never personally used it, not
knowingly at least. I've heard great things about Gradle though.

~~~
vorg
If Gradle's so great, then why does it use a JVM language that wasn't
considered by Metabase.

> The shortlist really came down to “something on the JVM” [...] However, we
> had a strong aversion to using Java itself, so the real short list was Scala
> or Clojure

Gradle requires the configuration files to be written in Groovy, and is itself
written in Java and lots of Groovy.

~~~
giancarlostoro
I do know Android Studio likes to rock Gradle. Clojure has Lein though, why
would you use Maven then? :)

------
tunesmith
Can someone expand on what they mean by lightweight threads for Clojure? My
understanding is that lightweight threads (green threads) is what java/jvm
explicitly moved away from way back when; it's user-mode threads bound to one
processor, so if the processor gets busy, then all of the lightweight threads
for that processor get blocked. Meanwhile, Java/jvm uses kernel threads and
context-switching so you can do real multi-threading.

I'm aware of how Quasar purports to be the best of both worlds, and it looks
like Pulsar is a Clojure API for Quasar, maybe that's what they're using.

It doesn't appear Scala has something comparative, but I have a hazy sense
that Scala people believe that Akka and monads are better for easy
asynchronous programming.

~~~
patrickthebold
Clojure's core.async has "go routines", (this is more or less the same as in
go), basically you can put and take messages on a channel. These operations
will block the green thread, so you end up writing synchronous code. The idea
is that synchronous, blocking code is easier to understand then async code.
Since these are lightweight threads it will scale better than using real
threads. I'm pretty sure it's fine to have hundreds of thousands of go
routines.

This abstraction sits on top of java's real threads, so you can have several
real threads doing the work and they can switch between the go routines once
the go routine hits one of the blocking operations.

Or something like that... read this:
[http://clojure.com/blog/2013/06/28/clojure-core-async-
channe...](http://clojure.com/blog/2013/06/28/clojure-core-async-
channels.html)

------
stcredzero
_Now, choosing a programming language for a project is usually decided on a
mix of what a team is most comfortable with, beer-fueled debates on dynamic vs
static typing, benchmarks of marginal relevance and that elusive quest for
Dev-Hipster points._

How about this for an interview strategy: Have these discussions. Hire the
people that realize most of the above mentioned things are meaningless.

(Clarification: Only the "comfortable" part isn't meaningless. But there too,
it's only as meaningful as the placebo effect is significant.)

~~~
ionforce
How is "dynamic vs static typing" meaningless?

~~~
stcredzero
For most applications, either can work. They both have to be managed
correctly.

~~~
ionforce
What are "most applications"?

How do they have to be managed?

~~~
stcredzero
[http://bfy.tw/36aT](http://bfy.tw/36aT)

------
biokoda
Considering their criteria Erlang/Elixir seems like a huge omission from the
contender list.

~~~
devinus
> Wide variety of mature database drivers

That requirement was probably not met when they started.

~~~
learc83
Does Erlang lack a variety of mature database drivers?

~~~
dsp1234
I'm not an Erlang dev, but a quick look at the Erlang docs shows:

"If you need to access a relational database such as sqlserver, mysql,
postgres, oracle, cybase etc. from your erlang application using the Erlang
ODBC interface is a good way to go about it."[0]

"It is built on top of Microsofts ODBC interface and therefore requires that
you have an ODBC driver to the database that you want to connect to."[0]

"But currently it is only regularly tested for sqlserver and postgres."[1]

"But currently it is only tested for Solaris, Windows 2000, Windows XP and
NT"[2]

[0] -
[http://www.erlang.org/doc/apps/odbc/databases.html](http://www.erlang.org/doc/apps/odbc/databases.html)

[1] -
[http://www.erlang.org/doc/man/odbc.html](http://www.erlang.org/doc/man/odbc.html)

[2] -
[http://www.erlang.org/doc/apps/odbc/getting_started.html](http://www.erlang.org/doc/apps/odbc/getting_started.html)

------
melipone
Lisp has been around a while, Java has been around a while. Clojure is the
perfect combination and I predict it will be around a while.

------
melipone
Lisp has been around a while. Java has been around a while. Clojure is the
perfect combination and I predict it will be around a while. It's exhilarating
to program in Clojure. That's the only word I can find to describe what I feel
to program in that language.

------
moomin
I had this conversation on twitter already: typed object mappers seem like a
questionable choice for the problem space. Since it seems like all of their
work in Scala used them, I'm not surprised they hated it.

(Disclaimer: I have no particular liking for Scala.)

------
daxfohl
I'd be interested to understand how Korma solves their problem. It's macro-
based so I wouldn't think it would be particularly good at dynamically
restructuring queries based on runtime variables.

~~~
thom
Yeah, clojureql was much better for this sort of thing but is to all intents
and purposes abandoned.

------
lectrick
Why wasn't Erlang/Elixir even considered? It checks off all their initial set
of requirements

------
programminggeek
You know what else is easy to work with and deploy? PHP.

~~~
lectrick
I wouldn't call a language that encourages writing spaghetti code and creating
dependency hell to be "easy to work with", much less one which _doesn 't even
pass its own test suite_, which implies that even if you do everything 100%
correctly, the spec itself is faulty, so you WILL have non-deterministic "3AM
phone call, up till 3PM fixing" type bugs... No thanks

------
johansch
Has anyone here ever heard of Metabase before? Their github page indicates
there are 3 (three) contributors. Wouldn't it be safe to assume this post is
primarily a marketing venture?

~~~
escherize
I've used Metabase before, it's a super easy way for a non-technical to do sql
queries.

