This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-31
Channels
- # announcements (6)
- # babashka (32)
- # beginners (78)
- # biff (11)
- # calva (7)
- # clj-kondo (20)
- # clojure (35)
- # clojure-europe (10)
- # clojure-nl (4)
- # clojure-norway (8)
- # clojure-uk (2)
- # clojurescript (41)
- # conjure (14)
- # core-async (10)
- # cursive (7)
- # datomic (12)
- # deps-new (4)
- # emacs (12)
- # fulcro (48)
- # gratitude (11)
- # hugsql (1)
- # hyperfiddle (3)
- # introduce-yourself (3)
- # jobs (3)
- # klipse (2)
- # off-topic (7)
- # polylith (30)
- # reitit (1)
- # remote-jobs (1)
- # reveal (8)
- # scittle (4)
- # shadow-cljs (40)
- # squint (13)
- # tools-deps (7)
- # xtdb (7)
Monads. Is it worth to learn how to use monads in Clojure? Is it worth to invest time into learning these. What are the most beneficial use cases to apply them in Clojure? https://github.com/clojure/algo.monads
Personally, I didn’t know of the algo.monads package; I suspect the same rings true for quite a few practicing Clojure programmers. Nor are monads very often mentioned in Clojure codebases.
From the point of view of broadening one's knowledge of computer science, it certainly doesn't harm to have at least a passing familiarity with monads.
My search for a solution to handle failures in multi-step processes veered towards https://fsharpforfunandprofit.com/rop/ and the related Either monad. In the discussions I read most senior Clojurians seem to snub monads in Clojure as non-idiomatic. @U1WAUKQ3E convincingly argues the case against monads in Clojure head-on: https://grishaev.me/en/no-monads/ Most agree with @UCYS6T599: > From the point of view of broadening one's knowledge of computer science, it certainly doesn't harm to have at least a passing familiarity with monads. I.e. monadic ideas may be useful, but keep it simple and data-oriented.
It's worth learning, but i suspect it's as easy to invent as needed, as it is to learn and then recognize when it would be useful.
My feeling is that monads are a lot less useful in a language without a static type system -- but monadic concepts still surface in various contexts and Clojure's nil-punning makes the concepts work without the formalism in many cases.
I think monads are somehow organizing structure in a way that works against lisp. Inverting it somehow. Like here is a talk about https://www.youtube.com/watch?v=xmCrMUhhg9c Here is the code they use to display the concept. I tried to walk through it with steps 1,2, etc.. and my brain exploded.
(ns monads
(:require [clojure.string :as str]))
;; monad version
(defn return [v] ;; 6. v1= "me and you" | v2 = me
(fn [s] [v ;; 7. v1 = "me and you" | v2 = me
s ;;
]))
(defn bind [mv f] ;;11 mv= (return "me")
(fn [s]
(let [[v sn] (mv s)]
((f v) sn))))
(defn m-tea [mv name] ;; name = "you"
(bind mv ;; 9. mv = (take-sugar (return "me"))
(fn [v] ;; 8. v= you
(return ;;5. which is taken by return
(str v " and " name) ;;4. v= me and name=you
))))
(defn take-sugar [mv] ;; 10. mv = (return "me")
(bind mv ;; 11.
(fn [vv] ;; 2. vv="me and you"
(fn [ss] [vv (dec ss)]) ;; 1. take ss=9 and return [vv (dec 9)]
)))
((-> (return "me")
(take-sugar)
(take-sugar)
(m-tea "you")
(take-sugar)
(m-tea "them"))
9)
;; => ["me and you and them" 6]
Here is the same output in clojure two other ways that I think thas the same state and perf implications:
(-> {:tea-drinkers []
:sugar-cubes 9}
(update :tea-drinkers conj "me")
(update :sugar-cubes dec)
(update :sugar-cubes dec)
(update :tea-drinkers conj "you")
(update :sugar-cubes dec)
(update :tea-drinkers conj "them")
(update :tea-drinkers #(str/join " and " %)))
;; => {:tea-drinkers "me and you and them", :sugar-cubes 6}
(reduce
(fn [[drinkers n-sugar] [fn & args]]
(case fn
:sugar [drinkers (dec n-sugar)]
:drink [(concat drinkers args) n-sugar]))
[[] 9]
[[:drink "me"]
[:sugar]
[:sugar]
[:drink "you"]
[:sugar]
[:drink "them"]])
;; => [("me" "you" "them") 6]
but the ideas used in monads are probably the building blocks for things like reduce and transeduce.
Counter point by question, how would you chain together a series of functions which take data, return a channel which might contain a result or an anomaly, other than bind?
@UK0810AQ2 I would do it the same way i passed the sugar counter above, only it would this "anomaly". {:anomaly ... :state ...} etc...I think it's very contextual if that's worth doing. I think it would time would be better spent on clarifying documentation or code so people can do the right thing. The reason i'm not particularly excited about most of the category theory/haskell nomenclature is mostly because i don't work with enough people that know it. (Myself included!!). And the investment to learning it is too high by itself. If i could magically memorize it, that would be the bees knees.
@U02F0C62TC1 exactly like some-> but for anomaly
And I take a channel and invoke on the result a function of value -> channel
This is internal to a library, but putting it behind a bind
abstraction in a impl
namespace made too much sense
Sometimes, you really do need a monad https://github.com/clojure/core.async/blob/master/src/main/clojure/clojure/core/async/impl/ioc_macros.clj#L58
Or let's say I have some ETL which might generate errors at one of many stages and they need to be propagated correctly, how would you compose that error handling over transducers and pipelines?
It unfortunately becomes a problem the more contexts you have to juggle. a sequence, an error, a channel, sometimes, all at once 🙂
> I will never let monads be in a Clojure project
>
Statements like this just show a basic lack of understanding of what a monad is. Sequence
in Closure is a pretty stellar example of a Monad, where map
is equivalent to Haskell's fmap
, concat
equivalent to join
, mapcat
equivalent to bind
, and list
(or maybe more precisely (sequence (list ... ))
) equivalent to return
.
I sure would like to see the code this author produces after prohibiting the use of sequences in all his projects.
You don't need a strict type system with the compiler enforcing monad laws, nor a library trying to replicate Haskell's API, for monads to be relevant.
You can use a macro in place of a Monad often, which is why Monads are not employed as often in Lisps languages I believe.
Sequences I'm not sure make sense as Monads either, I don't think it's fair to start including Functors as Monads? But I guess in general Category Theory helps learn some formalism about stuff you might be doing subconsciously in Clojure.
I think my favorite showing is https://github.com/uncomplicate/fluokitten where various category theoritic constructs are used often to perform operations on various types in optimal way, where-as sequences convert everything to a sequence
This blog post is cool: https://dragan.rocks/articles/18/Fluokitten-070-map-reduce-primitive-arrays-without-macros and I think it can show a bit the power of category theory abstractions like Functors and friends.
> I don't think it's fair to start including Functors as Monads Monad implies functor. (The reverse does not hold; functor does not imply monad.)
In the case of a Sequence, the combination of map
with concat
and/or mapcat
gives you the basic ingredients needed for monadic morphism.
I thought you meant that sequences are like Functors, which kinda, though they don't preserve the type, but they do preserve the structure
And I felt they're more like Functors than Monads, which is why I meant maybe it be better to relate them to Functors
Oh, also this one is pretty good intro: https://fluokitten.uncomplicate.org/articles/functors_applicatives_monads_in_pictures.html
> they don't preserve the type, but they do preserve the structure The structure is the defining feature. > And I felt they're more like Functors than Monads All monads are functors. That statement is like saying "I feel like mice are more like mammals than like rodents."
I mean I feel they are more like Functors in that they are not Monads. Like map is close to fmap, which is a functor not a Monad
But I guess if you mean that sequences support all those methods and together it makes like a seq a container that support up to the Monad operation, ya maybe.
But you don't only have map
. You also have concat
, which lets you onroll nested sequences.
Ya, I get what you meant now, kinda like:
(defn half [x]
(if (even? x)
(list (quot x 2))
nil))
(mapcat half (list 10))
Monads are invaluable as a concept. They teach us how to think about the systems we are building generically and algebraically. However, due to its dynamic typing, Clojure has less need for mathematical formality. Many patterns in Clojure follow category theory principles without formally verifying the types, as Haskell or Scala would. At the least, consider getting acquainted with the logic behind these two libraries. They are rarely found in practice but can improve understanding of Clojure abstractions: • https://fluokitten.uncomplicate.org/articles/guides.html • https://funcool.github.io/cats/latest/
I need help, Im new and i try to make one tutorial but there is:
clj -X:new :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'
and it gives me:
"No function found on command line or in :exec-fn"
Can anyone explain me what is goin on? 🙂
I tried to look on the internet but i failed 😕would this work ?
clojure -Tclj-new create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'
or maybe clj -X:new clj-new/create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'
This clj -X:new clj-new/create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]' have anoother error, but i think it is good 🙂 error Namespace could not be loaded: clj-new i try to make another name for namespace
clj -Tclj-new create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'
now something like this Error building classpath. Unknown tool: clj-new 😞
well either install it as tool (and use -Tclj-new), or have it in your deps.edn (and use -X:new)
im installing and checking
ok now i have version bug 🙂 Library com.github.seancorfield/clj-new has invalid tag: v1.2.399 its hard to start with this 😄
Clojure CLI version 1.11.1.1113
yeah the onboarding story is not great, hopefully it's more stable now. It's still a recent tool
but its great there are People to help like You Rolt 🙂
i will try now 🙂
Circling back on this -- Damian is using Powershell on Windows so quoting rules are different. I pointed him at the CLI docs for quoting which explains the Powershell differences.
@U04V70XH6 Yes, Thank You again 🙂
When using ->>
, how do I pass in the result?
For example:
(->> (split s #" ")
first
(split #"-")
)
This returns an error because the second split doesn't have a character sequwence argument.
s
is my original string, how do I push the result across the line?that's it, clojure functions are designed to take sequence as last argument to help with this
Use ->
:
(-> (split s #" ")
first
(split #"-"))
You use ->>
for lists, vectors and sequences, ->
for hash-maps and strings and https://clojuredocs.org/clojure.core/as-|as->> when the position of the result is changing.(mapv parse-long ["1" "3"])
i don't think you understand -> and ->> try
(macroexpand-1
'(-> (split s #" ")
first
(split #"-")
))
to get an idea of what it's doingResult of second split
is a vector, so you have to change ->
to ->>
(or use as->
):
(->> (-> (s/split "1-3" #" ")
first
(s/split #"-"))
(mapv parse-long))
(as-> "1-3" $
(s/split $ #" ")
(first $)
(s/split $ #"-")
(mapv parse-long $))
if i were to solve this problem:
(->> (re-find #"^(\d+)-(\d+)" "1-3")
(rest)
(map parse-long))
otherwise just a let binding:
(let [tokens (-> (split s #" ")
first
(split #"-")
)]
(map parse-long tokens))
i avoid mixing -> and ->>, makes it hard to read for meThere's a really good article that covers threading https://stuartsierra.com/2018/07/06/threading-with-style
Responding to an earlier question:
> hmm... why a long though? and why mapv instead of threading?
Clojure’s native integer type is the long
. You will only get an int
if you explicitly try to create one, or you interact with Java code through interop.
As for the second question… you may have figured it out already, but threading is just a way to pass the results of one function call into the next function call. Meanwhile, mapv
is a function that accepts a mapping function and a seq, and then returns a new vector built from running the mapping function on every element of the seq. In other words, it’s just a function that can be referenced in a threading macro, like all the other functions that go in there.
-> functions are macros/syntactic sugar. you don't need them to do anything in clojure. it may be better to learn to do things without them, and then refactor some code to use them, and decide when/if you want that syntax sugar or not.
hello, newbie here, I tried to use npx *create-cljs-project my-app*
and revived error when after *`npm start`* I have tried few java ask version
tomaszsikora@DeadBook-Void my-app % java --version
openjdk 18.0.2.1 2022-08-18
OpenJDK Runtime Environment Homebrew (build 18.0.2.1+0)
OpenJDK 64-Bit Server VM Homebrew (build 18.0.2.1+0, mixed mode, sharing)
but I get same error anyway:
tomaszsikora@DeadBook-Void my-app % npm start
> [email protected] start
> shadow-cljs watch app
shadow-cljs - config: /Users/tomaszsikora/dev/my-app/shadow-cljs.edn
Execution error (UnsatisfiedLinkError) at java.lang.ClassLoader/loadLibrary (ClassLoader.java:2398).
Can't load library: /var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/jna14133068692257536681.tmp
Full report at:
/var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/clojure-16147663004523767992.edn
{:clojure.main/message
"Execution error (UnsatisfiedLinkError) at java.lang.ClassLoader/loadLibrary (ClassLoader.java:2398).\nCan't load library: /var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/jna14133068692257536681.tmp\n",
:clojure.main/triage
{:clojure.error/class java.lang.UnsatisfiedLinkError,
:clojure.error/line 2398,
:clojure.error/cause
"Can't load library: /var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/jna14133068692257536681.tmp",
:clojure.error/symbol java.lang.ClassLoader/loadLibrary,
:clojure.error/source "ClassLoader.java",
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type java.lang.UnsatisfiedLinkError,
:message
"Can't load library: /var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/jna14133068692257536681.tmp",
:at [java.lang.ClassLoader loadLibrary "ClassLoader.java" 2398]}],
:trace
[[java.lang.ClassLoader loadLibrary "ClassLoader.java" 2398]
[java.lang.Runtime load0 "Runtime.java" 785]
[java.lang.System load "System.java" 1979]
[com.sun.jna.Native loadNativeLibraryFromJar "Native.java" 744]
[com.sun.jna.Native loadNativeLibrary "Native.java" 678]
[com.sun.jna.Native <clinit> "Native.java" 106]
[com.barbarysoftware.jna.CarbonAPI <clinit> "CarbonAPI.java" 6]
[com.barbarysoftware.jna.CFStringRef
toCFString
"CFStringRef.java"
10]
[com.barbarysoftware.watchservice.MacOSXListeningWatchService
register
"MacOSXListeningWatchService.java"
30]
[com.barbarysoftware.watchservice.WatchableFile
register
"WatchableFile.java"
30]
[com.barbarysoftware.watchservice.WatchableFile
register
"WatchableFile.java"
39]
[hawk.watcher$fn__133 invokeStatic "watcher.clj" 102]
[hawk.watcher$fn__133 invoke "watcher.clj" 99]
[hawk.watcher$fn__38$G__29__47 invoke "watcher.clj" 24]
[hawk.core$watch_BANG_ invokeStatic "core.clj" 83]
[hawk.core$watch_BANG_ doInvoke "core.clj" 59]
[clojure.lang.RestFn invoke "RestFn.java" 423]
[shadow.cljs.devtools.server.fs_watch_hawk$start_STAR_
invokeStatic
"fs_watch_hawk.clj"
42]
[shadow.cljs.devtools.server.fs_watch_hawk$start_STAR_
invoke
"fs_watch_hawk.clj"
30]
[shadow.cljs.devtools.server.fs_watch_hawk$start
invokeStatic
"fs_watch_hawk.clj"
103]
[shadow.cljs.devtools.server.fs_watch_hawk$start
invoke
"fs_watch_hawk.clj"
101]
[clojure.lang.Var invoke "Var.java" 399]
[shadow.cljs.devtools.server.fs_watch$start
invokeStatic
"fs_watch.clj"
26]
[shadow.cljs.devtools.server.fs_watch$start
invoke
"fs_watch.clj"
11]
[shadow.cljs.devtools.server$start_BANG_$fn__18606
invoke
"server.clj"
410]
[clojure.lang.AFn applyToHelper "AFn.java" 160]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$apply invoke "core.clj" 660]
[shadow.runtime.services$start_one invokeStatic "services.clj" 98]
[shadow.runtime.services$start_one invoke "services.clj" 87]
[shadow.runtime.services$start_many$fn__9395
invoke
"services.clj"
127]
[shadow.runtime.services$start_many invokeStatic "services.clj" 126]
[shadow.runtime.services$start_many invoke "services.clj" 105]
[shadow.runtime.services$start_all invokeStatic "services.clj" 144]
[shadow.runtime.services$start_all invoke "services.clj" 139]
[shadow.cljs.devtools.server$start_system
invokeStatic
"server.clj"
340]
[shadow.cljs.devtools.server$start_system invoke "server.clj" 202]
[shadow.cljs.devtools.server$start_BANG_
invokeStatic
"server.clj"
483]
[shadow.cljs.devtools.server$start_BANG_ invoke "server.clj" 385]
[shadow.cljs.devtools.server$start_BANG_
invokeStatic
"server.clj"
388]
[shadow.cljs.devtools.server$start_BANG_ invoke "server.clj" 385]
[shadow.cljs.devtools.server$from_cli invokeStatic "server.clj" 615]
[shadow.cljs.devtools.server$from_cli invoke "server.clj" 591]
[clojure.lang.AFn applyToHelper "AFn.java" 160]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$apply invoke "core.clj" 660]
[shadow.cljs.devtools.cli_actual$lazy_invoke
invokeStatic
"cli_actual.clj"
23]
[shadow.cljs.devtools.cli_actual$lazy_invoke
doInvoke
"cli_actual.clj"
20]
[clojure.lang.RestFn invoke "RestFn.java" 460]
[shadow.cljs.devtools.cli_actual$blocking_action
invokeStatic
"cli_actual.clj"
129]
[shadow.cljs.devtools.cli_actual$blocking_action
invoke
"cli_actual.clj"
116]
[shadow.cljs.devtools.cli_actual$main
invokeStatic
"cli_actual.clj"
177]
[shadow.cljs.devtools.cli_actual$main doInvoke "cli_actual.clj" 132]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.core$apply invokeStatic "core.clj" 669]
[clojure.core$apply invoke "core.clj" 660]
[shadow.cljs.devtools.cli_actual$_main
invokeStatic
"cli_actual.clj"
219]
[shadow.cljs.devtools.cli_actual$_main
doInvoke
"cli_actual.clj"
217]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$apply invoke "core.clj" 660]
[shadow.cljs.devtools.cli$_main invokeStatic "cli.clj" 75]
[shadow.cljs.devtools.cli$_main doInvoke "cli.clj" 67]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.main$main_opt invokeStatic "main.clj" 514]
[clojure.main$main_opt invoke "main.clj" 510]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]],
:cause
"Can't load library: /var/folders/x7/k_6f989d60z2k0pz_cz_3y_40000gn/T/jna14133068692257536681.tmp"}}
you could try to disable hawk maybe ? i though hawk related issue were fixed on osx though
:fs-watch {:hawk false}
in your shadow-cljs.edn
I got aswer, I had to install java for 64-bit processors beside using M1 ARM, I just switched between java 18.