Thank you @jr0cket! That’s a great example (and much easier to understand when you type all the words out, so thank you for that too! 😁)
I read something yesterday about someone creating a Rails-like stack for Clojure, but that it would be unpopular - can anyone give me more context on what this is and why there might be pushback on it?
I believe it was this: http://coastonclojure.com/
@amelia The Clojure community tends to prefer combining small, focused libraries that do one thing well. This keeps the design quite simple and gives a lot of flexibility In general frameworks tend to encourage / force a particular design. So with a framework, many design decisions have been made for you. Not all those decisions may fit your needs. Frameworks can be great for getting something done quickly, once you have become familiar with them. However frameworks also grow and change and can leave you stuck or having to relearn a framework. The same think can happen with a library, but as libraries are smaller it's usually easier to swap them for another library.
By default, the Clojure community leans towards libraries rather than frameworks. The distinction between those two is fuzzy. The idea is that libraries are more single-purpose and programmers use them like an individual tool or building block, forming it into the developer's larger solution. Frameworks are more of a comprehensive solution that tries to solve all the problems you would expect to have, at the cost of being more rigid. This makes frameworks more of a pre-existing solution that the developer conforms to, rather than the other way around. Many folks feel that web development is well-suited to framework approaches, since the solution so often has the same basic shape.
Rails is the epitome of a framework approach. The Clojure community's default preference for libraries means that many Rails devs are lost when they try to architect a solution without a framework.
Rails' "convention over configuration" philosophy is a key differentiator
In Clojure there are a few good frameworks, eg. Pedestal, Arachne, re-frame. I tend to use frameworks as examples of how I could build something similar. There are also templates for Leiningen that give you a head start, by creating a project with libraries and example code (composure, luminous).
That's an excellent point—one of the ways many Clojure devs accomplish similar goals of frameworks is with lein templates like Luminus.
Thank you both! That makes it much clearer.
There does seem to be more Clojure frameworks coming out, which I think is a good thing for Clojure overall. There is also the concept of micro-frameworks which are just frameworks that try to be quite specific (and not get too bloated).
I like the idea of being able to make a web app quickly as a way to get started, though at some point I’d want to learn how each moving part works and how it all fits together.
a framework is essentially the inverse approach to solving a problem to a library. when solving a problem with a framework, you generally must think "how can i solve this problem in this particular framework" when solving a problem with libraries, you generally think "what library/ies can i pick in order to help me solve it"
I just realised I don’t know what a library is! I’ve been sort of nodding along, but I couldn’t define it if you asked me to. Is it like the clojure.core/clojure.string/clojure.set collections of functions?
Both approaches have merit. For me, the concern with frameworks is that one still has to learn a great deal about how the framework works, which is non-portable knowledge. Usually what one learns to get similar functionality using libraries is knowledge that is not specific to the library.
Ah, that makes sense… Like getting really good at Guitar Hero instead of learning guitar.
sort of; those functions are part of Clojure's 'standard library': i.e. a library of functions that comes with the language
or being good at doing your taxes using TurboTax
Let’s not talk about taxes… 😓
generally when people say 'library' they mean one external to the language, third-party or otherwise
so for example, a library for working with dates and times in Clojure is https://github.com/dm3/clojure.java-time
(I have to submit corporate taxes for the first time this year, and it seems very complicated and expensive.)
your actual problem might be, say, making a calendar app - that library just helps you with the date/time bits specifically
Ah, got it. Thank you!
no problem 🙂
I’ve tried googling, but I don’t quite understand what is meant by “lazy behaviour” - can anyone explain it in clearer terms?
The basic idea is “deferred execution”. For example, (range)
with no arguments will return a theoretically infinite series of integers, starting with 0. It should lock up your computer while it dumps an endless series of digits, but in practice you can do (take 10 (range))
because range
is lazy. It only returns values as you ask for them, and then waits for you to ask for more.
Of course, if you type (range)
at the REPL, you are asking for an infinite series of integers (and if you ever try it, you’ll remember next time not to do that, guaranteed!)
What would the alternative be? What’s the opposite of lazy?
Usually people use “eager” to refer to the opposite of lazy
map
is lazy: you can do (take 5 (map inc (range)))
and only those 5 calls to inc
will be done
mapv
however is eager since it returns a vector (so all the elements need to be present at once): if you do (mapv inc (range))
your program will indefinitely inc
the elements of the infinite sequence until you cancel it
boot.user=> (take 5 (map inc (range)))
(1 2 3 4 5)
boot.user=> (mapv inc (take 5 (range)))
[1 2 3 4 5]
boot.user=> (mapv inc (range))
(forever)And riffing on Sundar's first example, it is a lazy sequence that is only realized by virtue of it being printed by the REPL.
cljs.user=> (def xs (take 5 (map inc (range))))
#'cljs.user/xs
cljs.user=> (realized? xs)
false
cljs.user=> (first xs)
1
cljs.user=> (realized? xs)
true
cljs.user=> (realized? (rest xs))
false
What does xs mean in that context?
the plural of x
: a sequence of x's. it just means a generic sequence you haven't given a specific name
here's another example that makes it more visible what laziness does:
boot.user=> (type (map println [1 2 3]))
clojure.lang.LazySeq
boot.user=> (type (mapv println [1 2 3]))
1
2
3
clojure.lang.PersistentVector
Sorry, I still don’t quite understand. Maybe I should come back to this…
fair enough 🙂
perhaps thinking about it like functions will help.
(fn [] (+ 1 2))
creates a function that evaluates (+ 1 2)
only when the function is called (the result of the function is asked for). lazy sequences similar to that, except the deferral applies to multiple values instead of just one:
(map inc (range))
creates a lazy sequence that, when asked for a number of items, will call inc
on each of those items as it returns them
(map inc (range))
by itself will only create a lazy sequence, nothing else. it's only when the items inside it are asked for that any code is run (in this case (inc 0)
(inc 1)
etc)
if you run that in a repl, the repl will try to print it, and since it needs the items to do that, it will ask the lazy sequence for those items
thereby running the code
it's 'lazy' because it only does the work when you force it to do so by asking for the items
if you do not ask for any items, it will not do any work
I’d say lazy vs eager evaluation is a bit advanced for this channel, and for someone’s first steps into programming it could be safely ignored.
But I also can’t resist giving my own take on it 🙂 — when running a program, usually the computer will take expressions one by one and “run” them (or evaluate them). For example when encountering (+ 1 2)
the computer will run and replace that with 3
. This is the usual case and it’s called “eager evaluation”. By contrast, in “lazy evaluation”, the computer will instead “remember” (+ 1 2)
, but not actually run the computation immediately. It will instead wait until some other part of the program needs the value of (+ 1 2)
right now because it can’t progress without it. Only the computer will actually make the addition and calculate the result.
This is an oversimplified overview of lazy/eager. Some languages like Haskell are fully lazy. Clojure is not, but has lazy sequences.
In Clojure, a lot of things return a lazy sequence. This is a sequence that is “remembered” by the computer, but not actually produced yet.
For example, (range 100000000000000000)
— this should generate a gajillion of numbers and very quickly grind the computer to a halt. However (take 3 (range 100000000000000000))
executes immediately — what gives? The range is lazy, and since we need only the first 3 elements, the rest are never produced.
There is a catch like most people have said, that typing (range 100000000000000000)
in a REPL will usually result in the REPL trying to print all of those numbers, and of course taking up all your memory and CPU.