we 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.
Tour of our 250k line Clojure codebase
111–120 of 237 posts
Re: Tour of our 250k line Clojure codebase
#112Earlier quoted context omitted.
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.
That's a poor hack, it moves an invariant that could be enforced at compile time to not much more than a convention that has to be preserved by code review. 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
#113Is there more info about the tool itself that claims 100X decrease in application development cost? Quite the claim.
Re: Tour of our 250k line Clojure codebase
#114Earlier quoted context omitted.
> 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…
You implement your own custom validation. The point is to encode the fact that you've validated a value in its type. So you have e.g. DeserializeJSON (String) -> Foo, and then ValidateFoo(Foo) -> ValidatedFoo. And then all the business code works on ValidatedFoo.
Re: Tour of our 250k line Clojure codebase
#115Earlier quoted context omitted.
You're writing more code than you have to though. More code = more bugs.
I don’t agree there. Most of these extra classes are just type declarations with no methods at all. While the total LoC written might be higher, the amount of written logic in which you can introduce bugs is less.
Some languages make this less likely (Haskell, Rust) but most mainstream languages will happily let you introduce this bug.
Re: Tour of our 250k line Clojure codebase
#116> 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?
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.
Re: Tour of our 250k line Clojure codebase
#117> Detecting an error when creating a record is much better than when using it later on, as during creation you have the context needed to debug the problem. This is a great insight no matter what language or framework you're operating in. Laziness has its virtues, but invariants, validity checks, run-time type checks, etc. should all be performed as early (and often) as possible - it's much easier to debug issues whe…
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…
In the ML-family of language, you have a few key things:
1. Primitive types like ints, bools, strings, floats, arrays, not much else.
2. Product types which are records/triples of other types
3. Sun types which are proper tagged unions of types. Including things like optional or result (aka or-error) types, and also list (= nil or cons) types.
4. Abstract types which are types whose representations are hidden. You can have an abstract type called “hour_of_day” which is secretly backed by an int but which you can only interact with by using conversion functions or eg something that adds two values (mod 24).
5. Polymorphic types: you can have a list type which can be a list of units or a list of floats but not really a list of a mix of arbitrary different things.
The idea is to represent with these types a model of the world in such a way that only valid states of the world can be constructed. A user’s bank account balance isn’t an int, it’s a positive_dollars and if you try to do a transaction to make it negative, that isn’t possible as you can’t construct a suitable positive_dollars value. This can have annoying difficulties for maintainability because it is tedious to invert or change a one-to-one or one-to-many relation (eg previously 1 tax_number per person, now 1 person per uk_tax_number and one or two people per us_tax_number) and hard to represent with types a many-to-many relation like “every person has at least 1 bank account, and every bank account is associated with at least one person, and the bank accounts associated with person A have A amongst their associated persons.”
The promise is that the type system makes the practice and correctness of these refactorings easier.
In Clojure, the data model is more like:
1. There is a rich set of primitive, atomic types, eg strings, ints, but also dates and symbols and keywords and fractions and Uris and so on
2. There are collections like lists (of logically unalike objects), vectors (of logically alike objects), hash tables, and sets.
Data is built up out of e.g. hash tables of keywords to objects. The language has a rich set of features for acting on these types so one can do a lot with hash tables, whereas in an ML system only a few operations are available with a record type (eg constructing, reading fields, maybe updating them) and functions that do general things with any record can’t really be written. Closure is a language for manipulating data in general more than a framework for writing functions to manipulate your small, strict data types.
Clojure tries to model the world in a metarational way, accepting that it is unlikely that one can write a strict scheme capturing all and only valid states and instead programs should try to allow for the possibility of extra or missing information. You don’t want to care about whether you have a us_person or an eu_person so much as whether your data has a :person/preferred-name field. Issues with relations come up less because those relations are not trying to be forced into rigid types (they may be enforced by a database though—another cultural difference between Clojure and ML-family languages).
Fundamentally, I think the differences stem more from philosophies about modelling the world than type systems.
Re: Tour of our 250k line Clojure codebase
#118If I want to learn Clojure, where is the best place to start? I have a lot of experience with Python/Javascript now, and spent many years in C/C++/Objective C and Java. Also have some Go.
https://purelyfunctional.tv/mini-guide/the-ultimate-guide-to...
Re: Tour of our 250k line Clojure codebase
#119Aren't there many Clojure projects out there, or is the language generally not used for large projects? I have a JavaScript frontend written in Vue that is 150k+ lines of code, without any tests. Which would be 25k lines of code more than they have, disregarding their tests.
I'm not saying that more code is better, I just found it odd that 250k was considered one of the largest Clojure codebases in the world
Re: Tour of our 250k line Clojure codebase
#120> Our codebase consists of 250k lines of Clojure split evenly between source and test code. It’s one of the largest Clojure codebases in the world. Aren't there many Clojure projects out there, or is the language generally not used for large projects? I have a JavaScript frontend written in Vue that is 150k+ lines of code, without any tests. Which would be 25k lines of code more than they have, disregarding their tes…