Fork me on GitHub
#beginners
<
2022-08-31
>
greg11:08:21

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

pyry12:08:04

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.

pyry12:08:08

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.

walterl12:08:41

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.

Drew Verlee14:08:44

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.

seancorfield14:08:56

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.

Drew Verlee15:08:35

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]

Drew Verlee15:08:18

but the ideas used in monads are probably the building blocks for things like reduce and transeduce.

Ben Sless16:08:20

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?

rolt16:08:23

you mean like a twist on some-> ? or does it have other requirements ?

Drew Verlee16:08:47

@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.

Ben Sless17:08:29

@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

Ben Sless17:08:42

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?

rolt17:08:02

i'd probably "fork" some-> then, and customize it

Ben Sless17:08:44

It unfortunately becomes a problem the more contexts you have to juggle. a sequence, an error, a channel, sometimes, all at once 🙂

Ben Sless17:08:07

and some-> is a monad, in some sense

skylize02:09:40

> 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.

didibus03:09:53

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.

didibus03:09:17

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.

didibus04:09:56

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

didibus04:09:31

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.

skylize04:09:42

> 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.)

skylize04:09:21

In the case of a Sequence, the combination of map with concat and/or mapcat gives you the basic ingredients needed for monadic morphism.

didibus04:09:09

I thought you meant that sequences are like Functors, which kinda, though they don't preserve the type, but they do preserve the structure

didibus04:09:46

And I felt they're more like Functors than Monads, which is why I meant maybe it be better to relate them to Functors

skylize04:09:24

> 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."

didibus04:09:37

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

didibus04:09:24

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.

didibus04:09:29

Still feel you have to squint a bit 😛

skylize04:09:18

But you don't only have map. You also have concat, which lets you onroll nested sequences.

didibus04:09:17

Ya, I get what you meant now, kinda like:

(defn half [x]
  (if (even? x)
    (list (quot x 2))
    nil))

(mapcat half (list 10))

InvictedPrometheus17:09:33

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.htmlhttps://funcool.github.io/cats/latest/

👍 1
Damian Koncewicz16:08:52

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 😕

rolt16:08:10

would this work ? clojure -Tclj-new create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'

rolt16:08:27

or maybe clj -X:new clj-new/create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'

Damian Koncewicz16:08:39

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

rolt17:08:10

clj -Tclj-new create :template figwheel-main :name learn-cljs/weather :args '["+deps" "--reagent"]'

Damian Koncewicz17:08:00

now something like this Error building classpath. Unknown tool: clj-new 😞

rolt17:08:21

you need to install clj-new

rolt17:08:22

well either install it as tool (and use -Tclj-new), or have it in your deps.edn (and use -X:new)

Damian Koncewicz17:08:28

im installing and checking

Damian Koncewicz17:08:33

ok now i have version bug 🙂 Library com.github.seancorfield/clj-new has invalid tag: v1.2.399 its hard to start with this 😄

rolt17:08:07

what's your clj --version ?

Damian Koncewicz17:08:34

Clojure CLI version 1.11.1.1113

rolt17:08:35

yeah the onboarding story is not great, hopefully it's more stable now. It's still a recent tool

rolt17:08:05

i'm lost, i actually have the exact same and it works on my computer 😕

Damian Koncewicz17:08:19

but its great there are People to help like You Rolt 🙂

rolt17:08:01

i noone else answer you can try in #tools-deps?

Damian Koncewicz17:08:04

i will try now 🙂

seancorfield17:08:37

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.

Damian Koncewicz17:08:00

@U04V70XH6 Yes, Thank You again 🙂

Pedja Zolinsky16:08:30

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?

rolt16:08:23

->> will pass as last argument, -> will pass as first

rolt16:08:20

that's it, clojure functions are designed to take sequence as last argument to help with this

Martin Půda16:08:43

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.

Martin Půda16:08:27

(mapv parse-long ["1" "3"])

MegaMatt16:08:16

you may also want to use some-> in case first produces nil

rolt16:08:02

i don't think you understand -> and ->> try

(macroexpand-1
 '(-> (split s #" ")
     first
     (split #"-")
))
to get an idea of what it's doing

👍 1
Martin Půda16:08:39

Result 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 $))

walterl16:08:52

Or

(-> (s/split "1-3" #" ")
    first
    (s/split #"-")
    (->> (mapv parse-long)))

rolt16:08:02

if i were to solve this problem:

(->> (re-find #"^(\d+)-(\d+)" "1-3")
          (rest)
          (map parse-long))

rolt16:08:14

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 me

rolt16:08:15

the "as->" solution looks clean too

sheluchin18:08:19

There's a really good article that covers threading https://stuartsierra.com/2018/07/06/threading-with-style

👌 1
quoll19:08:48

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.

pppaul21:08:57

-> 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.

pppaul21:08:08

however, the best ones are cond-> cond->>

Tomasz Sikora20:08:00

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

Tomasz Sikora20:08:11

{: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"}}

rolt21:08:28

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

rolt21:08:26

actually i'm not sure the option still exists

rolt21:08:43

did you try updating shadow-cljs ?

Tomasz Sikora08:09:02

I got aswer, I had to install java for 64-bit processors beside using M1 ARM, I just switched between java 18.

👍 1