clojure 2025-10-14

Hello all! I have a question about code organization I have a namespace domain.utils that is - Around 2800 LoC - Around 140 definitions (`defn`, def) - Contains code for schemas, db, handlers, processing and more This is an issue because - The namespace no longer represents utilities - It is becoming harder to reason about it I'm thinking of ways to break it down and I've come to this question Is it okay to separate functions from their schemas? As in, for a function f that has -Malli- schema =>f, both live in the same ns and so take up nearly twice the lines of code. I was thinking an easy way to cut the namespace LoC in half would be to extract them to a domain.schemas namespace and then require them or refer them back My questions - What are the upsides & downsides to this approach? - Are there resources about such refactors and best practices for them? Thank you

My 2Cs: If you can separate pure domain data model schemas and functions that operate on that data model in a separate file, like domain.core, that would be useful. And then you can take out things from domain.utils that belong in domain.core. Regarding to separating the function from it’s schema, if you can use the domain schema in the function schema definition that would certainly make it smaller, but I wouldn’t separate the function from it’s schema as it is useful (imo) to have them close at hand for people that read the code for the first time so you understand what inputs & outputs it has ex: Let’s say in your domain ,you have a DataPoint data model. You can create a separate ns for that

(ns domain.datapoints)

;; mally schema
(def DataPoint 
    [:map
     ...])
     
(def =>unify [:=> [:cat DataPoint DataPoint] DataPoint]

(defn unify
  [d1 d2]
  (...))
Then in domain.utils use that new ns
(ns domain.utils
  (:require [domain.datapoints :as dp]))
  
  
(def =>some-op [:=> [:cat dp/DataPoint] dp/DataPoint]
(defn some-op
 [dp]
 ..)
    
You can argue that if some-op operates solely on the DataPoint data model, it should also be in domain.datapoints ,and I think that is true , but at least you separate based on the domain. I wanted to showcase that you can reduce LoCs in domain.utils by separating pure data model schemas without separating function schemas from the function definition

Beware that your .core ns doesn't just become another .utils! It's why I dislike the generic .core approach: names matter. It sounds like part of your problem here is having a generic domain ns, rather than specific parts of your domain, i.e., just have more nses, named for things in the domain. I use Spec but I suspect the same considerations apply. If I have specs for functions, I prefer they live with the functions. If I have specs for data, those live in their own ns. I wonder if you are over-specifying your functions? Maybe there are some helpful ideas in this https://corfield.org/blog/2019/09/13/using-spec/ if you haven't already read it?

It's ok to do, but it can be a bit annoying as well when you do need to look it up. But otherwise it's fine, you can think of it how tests are generally in a separate namespace, and that works fine.

That said, I feel it be nicer to just break things out with better logical naming/grouping?

Thank you for the responses! > pure domain data model schemas and functions that operate on that data model in a separate file I like the idea but this ns was accreted rather than planned and even planning it now wouldn't make sense as I know there will be business changes in the future, I'll try to incorporate the idea as much as possible > domain.core I want to avoid core as a namespace, I think the name should reflect its contents more > I wouldn’t separate the function from it’s schema as it is useful (imo) to have them close at hand for people that read the code for the first time so you understand what inputs & outputs it has Agreed, future me will thank me lol > I wanted to showcase that you can reduce LoCs in domain.utils by separating pure data model schemas without separating function schemas from the function definition Understood, thank you Ovi! > It sounds like part of your problem here is having a generic domain ns, rather than specific parts of your domain, i.e., just have more nses, named for things in the domain. I like this idea a lot. I'll attempt it today. > I wonder if you are over-specifying your functions? Hmm, I don't think I understood this, what does it mean to over-specify a function? > if you haven't already read it? I haven't, I'll read it now > But otherwise it's fine, you can think of it how tests are generally in a separate namespace, and that works fine. I'm looking into changing that by adapting https://github.com/hyperfiddle/rcf > That said, I feel it be nicer to just break things out with better logical naming/grouping? I'll try Sean's idea of breaking domain into domain.part

> Hmm, I don't think I understood this, what does it mean to over-specify a function? Spec/Malli are not intended to be used as type systems -- and having so many utility functions with specs feels like you're trying to use a "type system" here. That's what I meant by over-specifying your functions.

Is there any recommended library for using rationals in clojurescript? The use case is a site that should perform operations and display rational numbers. The library I am developing should also work on Clojure (so using cljc files), so I would like it if the chosen CLJS rational library was as unobtrusive as possible.

I noticed that #emmy uses Fraction.js, which I tried and haven't had any issues with it.

Hi all, I have a minimal project which just defines a protocol, and type using deftype .. and tries to test with a test … but the test run keeps complaining about not finding the type definition. I was hoping if somebody can help me with this? I run tests using

clojure -M:test:runner 
Can somebody help me figure this out?

Running tests in #{"test"}
Execution error (ClassCastException) at example.protocol-test/eval1941$loading (protocol_test.clj:1).
class clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')

{:clojure.main/message
 "Execution error (ClassCastException) at example.protocol-test/eval1941$loading (protocol_test.clj:1).\nclass clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.ClassCastException,
  :clojure.error/line 1,
  :clojure.error/cause
  "class clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')",
  :clojure.error/symbol example.protocol-test/eval1941$loading,
  :clojure.error/source "protocol_test.clj",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error macroexpanding at (example/protocol_test.clj:1:1).",
    :data
    {:clojure.error/phase :execution,
     :clojure.error/line 1,
     :clojure.error/column 1,
     :clojure.error/source "example/protocol_test.clj"},
    :at [clojure.lang.Compiler load "Compiler.java" 7665]}
   {:type java.lang.ClassCastException,
    :message
    "class clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')",
    :at [clojure.lang.Namespace referenceClass "Namespace.java" 129]}],
  :trace
  [[clojure.lang.Namespace referenceClass "Namespace.java" 129]
   [clojure.lang.Namespace importClass "Namespace.java" 158]
   [clojure.lang.Namespace importClass "Namespace.java" 164]
   [example.protocol_test$eval1941$loading__6789__auto____1942
    invoke
    "protocol_test.clj"
    1]
   [example.protocol_test$eval1941 invokeStatic "protocol_test.clj" 1]
   [example.protocol_test$eval1941 invoke "protocol_test.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7194]
   [clojure.lang.Compiler eval "Compiler.java" 7183]
   [clojure.lang.Compiler load "Compiler.java" 7653]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6908 invoke "core.clj" 6161]
   [clojure.core$load invokeStatic "core.clj" 6160]
   [clojure.core$load doInvoke "core.clj" 6144]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5933]
   [clojure.core$load_one invoke "core.clj" 5928]
   [clojure.core$load_lib$fn__6850 invoke "core.clj" 5975]
   [clojure.core$load_lib invokeStatic "core.clj" 5974]
   [clojure.core$load_lib doInvoke "core.clj" 5953]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$load_libs invokeStatic "core.clj" 6016]
   [clojure.core$load_libs doInvoke "core.clj" 6000]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$require invokeStatic "core.clj" 6038]
   [clojure.core$require doInvoke "core.clj" 6038]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$map$fn__5935 invoke "core.clj" 2772]
   [clojure.lang.LazySeq sval "LazySeq.java" 42]
   [clojure.lang.LazySeq seq "LazySeq.java" 51]
   [clojure.lang.RT seq "RT.java" 535]
   [clojure.core$seq__5467 invokeStatic "core.clj" 139]
   [clojure.core$dorun invokeStatic "core.clj" 3134]
   [clojure.core$dorun invoke "core.clj" 3134]
   [cognitect.test_runner$test invokeStatic "test_runner.clj" 71]
   [cognitect.test_runner$test invoke "test_runner.clj" 62]
   [cognitect.test_runner$_main invokeStatic "test_runner.clj" 127]
   [cognitect.test_runner$_main doInvoke "test_runner.clj" 115]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [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
  "class clojure.lang.Var cannot be cast to class java.lang.Class (clojure.lang.Var is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')",
  :phase :execution}}

The error says you have a Var where Clojure expects a class. Is this on GitHub for us to look at?

Or if the source is small enough just paste it

Looks sort of like maybe something funky with import

The error is that you are trying to :import the protocol Greeter. Change that to:

(:import [example.protocol SimpleGreeter])
and it will work.

don't import the class generated by a protocol

Hi @seancorfield thanks for your response. The source is attached to the original message as minimal.tgz sorry I was not explicit about it.

Yes, we can see that. But normally folks put stuff up on GitHub so the rest of us can just browse the code there -- instead of forcing us to download your tarball and install it locally 🙂

Anyway, you have the fix now, from both myself and hiredman.

Thanks yes it fixed it.