This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-13
Channels
- # adventofcode (37)
- # announcements (11)
- # babashka (46)
- # beginners (35)
- # biff (1)
- # clojure (44)
- # clojure-austin (1)
- # clojure-europe (23)
- # clojure-nl (2)
- # clojure-norway (8)
- # clojure-uk (5)
- # conjure (3)
- # cursive (22)
- # data-science (13)
- # docker (11)
- # events (8)
- # hyperfiddle (7)
- # joyride (1)
- # juxt (9)
- # malli (7)
- # matrix (4)
- # pedestal (3)
- # podcasts-discuss (1)
- # portal (1)
- # re-frame (62)
- # reitit (2)
- # releases (1)
- # schema (3)
- # sql (14)
- # squint (3)
- # xtdb (6)
- # yamlscript (4)
I just got YAMLScript let
and lambda
(assuming #(...)
is called lambda in Clojure) syntax implemented.
I think looks really nice.
I've been writing my https://yamlscript.org/posts/advent-2023/index/ and I was going to do tomorrow's post on Let-Over-Lambda in YS.
I came up with this example:
$ cat sample/lol.ys
!yamlscript/v0
defn lol(x):
y =: 2 * x
=>: \(% + y)
say:
lol(10): 4
$ ys --compile sample/lol.ys
(defn lol [x] (let [y (_* 2 x)] (fn [_1] (+ _1 y))))
(say ((lol 10) 4))
$ ys sample/lol.ys
24
but my friend said that wasn't let-over-lambda. (I still think it is).
So my question is, what do people consider to be let-over-lambda in Clojure?In case you were like wtf is yamlscript,
it's a yaml based programming language (written in clojure) that compiles to clojure code and is eval'd using sci. The ys
cli is created by having the yamlscript compiler and sci and libraries compiled with graalvm native-image. It also builds a shared library for ffi binding to other langs like say python where it can be used as a normal or highly-dynamic-as-clojure yaml loader.
My question here is about the clojure code genned:
$ ys --compile sample/lol.ys
(defn lol [x] (let [y (_* 2 x)] (fn [_1] (+ _1 y))))
(say ((lol 10) 4))
and whether people would consider that an example of let-over-lambda in clojure or not; and if not, why not...“let over lambda” refers to a specific design pattern for holding state, so your friend is right
well what does that look in clojure
he said it would look like (let [x 1] (defn a [] x))
which doesn't make sense. at least not without context
it helps to use a lisp that supports mutation, but in clojure you can emulate it with atoms like so
(defn make-account [initial-balance]
(let [balance (atom initial-balance)]
(fn withdraw [amount]
(if (< @balance amount)
:insufficient-funds
(reset! balance (- @balance amount))))))
(def withdraw (make-account 100))
(withdraw 5) ;=> 95
(withdraw 5) ;=> 90
(withdraw 91) ;=> :insufficient-funds
(withdraw 5) ;=> 85
also this works in ys:
$ cat sample/lol2.ys
!yamlscript/v0
defn lol(x):
y =: 2 * x
defn f(z): z + y
say:
lol(10): 4
$ ys --compile sample/lol2.ys
(defn lol [x] (let [y (_* 2 x)] (defn f [z] (_+ z y))))
(say ((lol 10) 4))
$ ys sample/lol2.ys
24
so is that let over lambda“let over lambda” is more of a design pattern if anything — its a way to create functions that behave like objects, in the sense that the functions hold state
my understanding was that lol is let returning a closure around one or more let vars
that is how they achieve holding state, but again, the point is doing this to hold state
i.e. what makes this design pattern interesting is the fact you can use it to create stateful objects
for more inspiration you can look at chapter 3 of SICP, which covers the exact same thing as “let-over-lambda”, but doesnt use a cool sounding name like that
we also use this pattern in clojurescript libraries like reagent, where UI components are functions returning HTML as data, and state is introduced by using this exact same pattern:
(defn my-component []
(let [count (r/atom 0)]
(fn []
[:div
[:p "You clicked " @count " times"]
[:button {:on-click #(swap! count inc)} "Click me"]]))
"State" here implies mutable state that changes from one execution of the function to another, not an effective constant that gets lexically closed over.
Yeah I'm starting to see that. I was reading part of https://letoverlambda.com/ and it said > Let Over Lambda > Let over lambda is a nickname given to a lexical closure. Let over lambda more closely mirrors the lisp code used to create closures than does most terminology. In a let over lambda scenario, the last form returned by a let statement is a lambda expression. It literally looks like let is sitting on top of lambda: >
* (let ((x 0))
> (lambda () x))
So I was like, oh it's just a closure and let makes the new lexical to close over.
But them I saw a bit later:
> (let ((counter 0))
> (lambda () (incf counter)))
> This closure will return 1 the first time it is called, 2 the subsequent time, and so on. One way of thinking about closures is that they are functions with state. These functions are not mathematical functions, but rather procedures, each with a little memory of its own. Sometimes data structures that bundle together code and data are called objects. An object is a collection of procedures and some associated state. Since objects are so closely related to closures, they can often be thought of as one and the same.
Which jives more with what you (@U06PNK4HG) and @U0479UCF48H are saying.my original example was gonna be in common lisp precisely because its easier to mutate there compared to clojure 🙂 ended up using clojure anyway since you asked how let-over-lambda would look in practice in clojure
I think I got LoL right in YS now:
$ cat sample/lol.ys
!yamlscript/v0
do:
count =: atom(0)
counter =: \(swap! count inc)
say: counter()
say: counter()
say: counter()
$ ys sample/lol.ys -c
(do
(let
[count (atom 0) counter (fn [] (swap! count inc))]
(say (counter))
(say (counter))
(say (counter))))
$ ys sample/lol.ys
1
2
3
agree or disagree?(the top level do
was necessary here because a =: b
gens (def a b)
at the top level and (let [a b] ...)
when not at top; and LoL examples really should have a let
)
Awesome!
Is anyone familiar with maven and/or clojure-maven-plugin? I want to add source outside of the $basedir but that doesn't appear to be possible with this plugin. I have a dev project that references sources in sister project folders.
I'm already doing this in java using the build-helper-maven-plugin which allows for relative paths, but now I want to add clojure source paths.
With lein codox
all namespaces are listed together on the html page. Is there a way to hide namespace packages and expand only as clicked?
Maybe you could do that via HTML transformations? https://github.com/weavejester/codox#html-transformations
does anyone have experience using the Athena JDBC driver in a clojure project? I'm having a hard time even just including it in my project, it appears the drivers aren't published in maven?
oh my god, you are a life saver. thank you!
this project isn't using deps.edn which is a whole other thing
@U0ETXRFEW magically turned my lein project into a lein project with dependencies managed by deps.edn, but otherwise configured by lein.
Hi again. I'd like to have CLI for my library. Coming from a NodeJS background, I usually run npx command
or install packages with -g and have them in my path. For the Clojure world, building a lein plugin would be the best way to go?
Depends on what tool you want to target.
For Lein - yes, probably a plugin.
For tools.deps
it would be a tool, similar to what https://github.com/seancorfield/deps-new does.
Another option is to create a stand-alone executable, similar to https://github.com/babashka/neil. You'd have infinite flexibility but you'd have to manage your own installation channels.
There's also this kind of hackery that will let you write a script that runs via the shebang line: https://gist.github.com/ericnormand/6bb4562c4bc578ef223182e3bb1e72c5
If you want to run code that is in the current project, then clojure -M -m some.namespace
runs some.namespace/-main
-- and you can have -main
in as many namespaces as you want; and clojure -X some.namespace/my-fn
will run that function, passing a single argument: a hash map of key/value pairs read from the command-line.
Aliases in deps.edn
would let you shrink those to something like clojure -M:my-cli
or clojure -X:my-exec
depending on whether you used :main-opts
or :exec-fn
.
If you want to run code from a library, you don't need to "install" it -- you can either specify its "coordinates" on the command-line or hide it behind an alias in your deps.edn
file: clojure -Sdeps '{:deps {my-new/library {:mvn/version "RELEASE"}}}' -M -m that.library
(or -X that.library/function
).
But putting aliases into your user deps.edn
is a pretty common what to deal with this. This is my user deps.edn
file: https://github.com/seancorfield/dot-clojure/blob/develop/deps.edn