Earlier quoted context omitted.
I’m not sure I understand what you mean. I don’t consider Clojure to be imperative at all — everything is immutable by default, and you actually have to go through considerable efforts to write things in an imperative style. When I compare Clojure to another functional language I know well, Haskell, one of the things I really feel it lacks is proper pattern matching and currying; yes there are libraries that you can…
It's not difficult to write imperative code in Clojure; [1] is an example. Immutability-by-default makes you work to mutate things, for sure, but that in itself doesn't make the language inherently functional. [1] https://clojuredocs.org/clojure.core/let#example-542692c7c02...
Tour of our 250k line Clojure codebase
101–110 of 237 posts
Re: Tour of our 250k line Clojure codebase
#102> One of the coolest parts of our codebase is the new general purpose language at its foundation. Though the semantics of the language are substantially different than Clojure, it’s defined entirely within Clojure using macros to express the differing behavior. It compiles directly to bytecode using the ASM library. The rest of our system is built using both this language and vanilla Clojure, interoperating seamlessl…
JSX would be an (early) example of this class of language. XML is equivalent to s-expressions. So React.js (drunkly) is just a reactive language embedded in a normal language with seamless ability to hop between them. I elaborate about this equivalence here https://www.reddit.com/r/Clojure/comments/mavg81/the_distinc... . The brilliance is they did this so smoothly that nobody noticed, and with dynamic types, and wit…
Re: Tour of our 250k line Clojure codebase
#103> And doing things dynamically means we can enforce stronger constraints than possible with static type systems. Can someone please explain this to a novice like me?
Taken literally the claim isn’t true - a Turing complete type system can enforce any constraint that a Turing complete programming language can. But your everyday type systems typically can’t express concepts like “a list of at least 3 elements” or “a number which is a power of 2”.
If yes, then I agree this is super helpful (and magical).
If not, how is this different from a normal constrcutor (with runtime input validation)?
Re: Tour of our 250k line Clojure codebase
#104> One of the coolest parts of our codebase is the new general purpose language at its foundation. Though the semantics of the language are substantially different than Clojure, it’s defined entirely within Clojure using macros to express the differing behavior. It compiles directly to bytecode using the ASM library. The rest of our system is built using both this language and vanilla Clojure, interoperating seamlessl…
I have dabbled in Clojure and Julia, and both of them support macro to make it easier to develop DSL looking syntax. I have always wondered how difficult it it to debug macro heavy code - where's other language cite 'no macro' as a feature such as Zig.
Re: Tour of our 250k line Clojure codebase
#105Earlier quoted context omitted.
Yes, and this is where statically typed languages shine (in my opinion). I like programming in a style that makes heavy use of the type system to enforce this. For example when writing an api endpoint to create a task I would typically deserialise the json into a CreateTaskRequest. If the object is created without exceptions I can be sure it is valid. CreateTaskRequest implements the ToTask interface. The service lay…
> If the object is created without exceptions I can be sure it is valid. Without some kind of custom validation system in place (JSON schema, property attributes, etc), that doesn't tell you much. With most serialization libraries I've used the default settings would let you deserialize {} into any class without an exception and all the properties would just have the default value for their type. Using stricter setti…
FWIW, this sort of thing can be done with a dynamic language, too. It's true that some static languages happen to be really far ahead of the curve with this sort of thing. But it's also true that some dynamic languages put you in a better position on this front than many of the most popular static languages do.
(And for those of us who really do love an intractable quagmire, there's always JavaScript.)
For my part, I tend to find this debate to be mostly a distraction, because the influence of the language's type discipline is quite small relative to the influence of the programmer's coding discipline. In the group I'm working with currently, the most committed fans of static typing tend to also be the ones who have the greatest tendency to assume, "If it compiles, it works," and proceed to check in glaring bugs. I don't bring that up in order to point fingers at static typing proponents (I tend to prefer static myself, though my preference is not particularly strong) so much as to point out that we should be wary of memes that subtly encourage us to become complacent.
Re: Tour of our 250k line Clojure codebase
#106Earlier quoted context omitted.
Most static time type systems can enforce that a voting age is an Integer, say, but can't validate that any value is at least 18, for example.
This is not really true if you include ML languages. Most of the time you create a specific type for a type that is constrained. Ada has pretty good support for this and ML languages too. Once you have a specific type you make all your functions accept only VoterAge instead of Int.
Re: Tour of our 250k line Clojure codebase
#107Earlier quoted context omitted.
A language like Kotlin can do some of these things using delegates, interfaces and extension methods. For example a MutableList can be dropped down to a List which is not mutable. And a generic conversion from Collection to NonEmptyCollection should be trivial to write as an extension method.
Can Kotlin handle multiple "tags" on a type as a set, and not a sequence? I'm not familiar with the language, so I'll use a C++ analogy. If you tried to tag types in C++, you'd end up with something like: TaggedType but such type is strictly not the same as: TaggedType What I mean by "set" instead of a "sequence" is to have the two lines above represent the same type, i.e. the order of tags should not matter.
fun doSomething(values: T) where T: Sorted, T: NonEmpty {}
And that function will take any type that has both Sorted and NonEmpty interfaces.Re: Tour of our 250k line Clojure codebase
#108Earlier quoted context omitted.
Most static time type systems can enforce that a voting age is an Integer, say, but can't validate that any value is at least 18, for example.
They can, this is a misconception. Create a new type, and make it so that it's only produced by a function that checks if the age is superior to 18.
E.g. a colleague implements de-serialisation for your type but adds an empty constructor to make their life easier. You might not learn there's a hole in the boat before your first bug.
Re: Tour of our 250k line Clojure codebase
#109we have a clojure codebase that's about 100k lines. Honestly I'm kinda fed up with it. Certain 3rd party libs we've used have been abandoned. We wrote our own libs for a major framework and it is failing behind. Too many "I'm very clever" functions that are hard to understand and also have subtle bugs.
Seconded. I work in a clojure codebase that were trying to get out of. There's just dead libraries everywhere and stuff that maintained by one person that gets no updates at all. That or we just end up making functional "wrappers" around Java libraries and at that point we might as well just write straight Java. Also yea everyone wants to be so damn smart having macros within macros within macros that no one knows wh…
If you're writing macros on a daily basis, you better have a really good reason for it.
Re: Tour of our 250k line Clojure codebase
#110Earlier quoted context omitted.
Yes, and this is where statically typed languages shine (in my opinion). I like programming in a style that makes heavy use of the type system to enforce this. For example when writing an api endpoint to create a task I would typically deserialise the json into a CreateTaskRequest. If the object is created without exceptions I can be sure it is valid. CreateTaskRequest implements the ToTask interface. The service lay…
> If the object is created without exceptions I can be sure it is valid. Without some kind of custom validation system in place (JSON schema, property attributes, etc), that doesn't tell you much. With most serialization libraries I've used the default settings would let you deserialize {} into any class without an exception and all the properties would just have the default value for their type. Using stricter setti…
[1] the caveat is that it somewhat sucks to change these restrictions and therefore the types. Compilers can hopefully make these refractors easier, but they may still suck. In languages like clojure, you mostly have to try to write programs/tests to be resilient to any reasonable changes to the data structures that you can imagine.