[HN Gopher] Tour of our 250k line Clojure codebase
___________________________________________________________________
Tour of our 250k line Clojure codebase
Author : grzm
Score : 217 points
Date : 2021-06-03 18:12 UTC (4 hours ago)
(HTM) web link (tech.redplanetlabs.com)
(TXT) w3m dump (tech.redplanetlabs.com)
| logistark wrote:
| I would like to ask if you are comfortable of Clojure protocols,
| because i tend to avoid them. What do you thik about it?
| nickik wrote:
| I don't really like 'Component'. I seems very clunky and we had a
| lot of issues with it and a lot incidental complexity in our
| codebase (now converted to Java). It was the first real system
| that did these sort of things but if I start a project now, I
| much rather use Integrant or Clip.
|
| https://github.com/weavejester/integrant
|
| https://github.com/juxt/clip
|
| I haven't used Clip a lot yet but my next project is defiantly
| going to be with Clip.
|
| For validation I have started to use Malli
| (https://github.com/metosin/malli). I really liked the idea
| behind clojure.spec but some of the implementation was a bit
| clunky and a bit too 'Rich Hickey', not sure how to describe it
| otherwise. Schema was again the first serious attempt at a
| library like that for Clojure so it was really nice when it came
| out. Malli sort of combines what is great about both.
| amackera wrote:
| Thank you for sharing this detailed rundown. One of the
| challenges that I faced as a new Clojure developer was
| understanding how all the parts fit together (and in fact, what
| parts I should care about in the first place). Example: is
| learning Component worth it? (yes)
|
| Extremely glad to see this resource published! Thank you to the
| Red Planet team for releasing this!
| [deleted]
| bradleybuda wrote:
| > 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 when the are proximate to an object or structure
| being created or modified, then when that data type is being used
| much later.
| vbsteven wrote:
| 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 layer takes only objects of this interface and converts
| into a Task object that gets persisted. The persisted Task then
| gets converted into a TaskResponse so only valid JSON comes
| back out.
|
| Lots of classes and interfaces but they are all small and with
| a single purpose.
| kgwxd wrote:
| > 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 setting gets you
| a little more but definitely no guarantee of a logically
| valid state. If I want to go even one step past what little
| validation static typing provides, and I usually do, I'd
| rather just take the type noise completely out of my data and
| move all validation to a single place.
| dan-robertson wrote:
| Strongly depends on the language. If you have ML style data
| types (eg ML, Haskell, OCaml, Rust) ands some way to
| abstract types (ML structs, Haskell modules hiding type
| constructors, OCaml modules, Rust modules) then it is
| generally possible to design your data structures in such a
| way that invalid states cannot be represented. If a user
| has an optional first/last name but if one is specified
| then so is the other, your user type has a name of type
| (eg) Maybe (string, string) [1]. If your deserialisation
| framework just fills in default values for everything or
| let's you have unused fields then it is, in my opinion,
| broken. If you have a field that should be a positive
| bumber, you should have a type for a positive number that
| fails to deserialise if you give it a negative number.
|
| [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.
| TeMPOraL wrote:
| 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<Foo>(String) -> Foo, and
| then ValidateFoo(Foo) -> ValidatedFoo. And then all the
| business code works on ValidatedFoo.
| kgwxd wrote:
| I know the point, I'm a C# dev by trade, but if I'm going
| to implement validation that goes beyond static type
| checking, which is most of the time, I'd rather put it
| all in a single place and not have to deal with the type
| nuisance in every single line of code I write.
| vbsteven wrote:
| That depends on the language and deserialisation library
| used. And that is also the reason why I have multiple
| classes.
|
| My CreateTaskRequest class which is used for
| deserialisation only does not have defaults (unless
| intentional) so it throws when required values are not
| present in the source json. It also takes care of
| dateformat/uuid parsing. The output is always fully valid
| typed or it throws. It's a 1-to-1 mapping on what is
| described in the API docs.
|
| The service layer that takes CreateTaskRequest and converts
| to Task for persistence is where the business logic
| validation happens (valid foreign keys, date ranges, etc,
| unique checks). Cleanly separated from deserialization.
|
| For reference: I use Kotlin with Jackson which has great
| support for this.
| mumblemumble wrote:
| At the end of the day, when we're working with external
| data, no language is a silver bullet. But you can get
| pretty far by doing https://lexi-
| lambda.github.io/blog/2019/11/05/parse-don-t-va... and
| yelling at your colleagues when they don't.
|
| 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.
| CraigJPerry wrote:
| You're writing more code than you have to though. More code =
| more bugs.
| Quekid5 wrote:
| Just for clarification: Do you equate type ascriptions to
| "more code"?
| vbsteven wrote:
| 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.
| CraigJPerry wrote:
| For any type A -> B operation, it's possible to fail -
| that is the whole point of parsing into type B, to catch
| when B's invariants would not be satisfied. The bug could
| be as simple as neglecting to handle that failure
| scenario.
|
| Some languages make this less likely (Haskell, Rust) but
| most mainstream languages will happily let you introduce
| this bug.
| Quekid5 wrote:
| > Yes, and this is where statically typed languages shine (in
| my opinion).
|
| Indeed. This feels like a just-before-the-moment-of-
| realization situation.
|
| The endless cycle between "more dynamic" and "more static"
| continues it seems.
|
| I wonder if there is any correlation between experience in
| the field and static vs. dynamic vs. "fail fast dynamic".
|
| (I'd say Erlang falls in the latter category and it has a
| pretty good track record for reliability, but so does Python.
| It's an imperfect axis for sure.)
| habibur wrote:
| > Lots of classes and interfaces but they are all small and
| with a single purpose.
|
| That's the side effect. You ultimately end up with more code
| and not less even though it saves you from type checks.
|
| There are trade offs for both.
| TeMPOraL wrote:
| One thing I wish for is some kind of "type tags". Being
| able to express concepts like List[Widget], List[Widget,
| Nonempty], List[Widget, Nonempty, Sorted], Vector[User,
| Sorted], etc. - or even, more generic, <Container>[<T>,
| NonEmpty] (where <Container> and <T> are parameters, like
| in C++ templates) - without implementing an explicit new
| type for each. Logic verification through typing would then
| involve not just changing "main" types, but also adding and
| dropping "tags" from the "set of tags" attached to the
| "main" type. This should cut down on the amount of
| boilerplate.
|
| Hell, in the extreme, perhaps types in general could be
| generalized as a set of tags?
|
| (See also, https://news.ycombinator.com/item?id=27168893)
| vbsteven wrote:
| A language like Kotlin can do some of these things using
| delegates, interfaces and extension methods.
|
| For example a MutableList<T> can be dropped down to a
| List<T> which is not mutable. And a generic conversion
| from Collection<T> to NonEmptyCollection<T> should be
| trivial to write as an extension method.
| TeMPOraL wrote:
| 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<std::vector, NonEmpty, Sorted>
|
| but such type is strictly not the same as:
| TaggedType<std::vector, Sorted, NonEmpty>
|
| 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.
| vbsteven wrote:
| You can maybe get there in kotlin with a generic type
| with multiple constraints in a where clause. Let's say
| you have Sorted and NonEmpty as interfaces (could be
| empty marker interfaces so they behave like tags). Then
| you can write a method fun <T>
| doSomething(values: T) where T: Sorted, T: NonEmpty {}
|
| And that function will take any type that has both Sorted
| and NonEmpty interfaces.
| vbsteven wrote:
| > There are trade offs for both
|
| Exactly.
|
| Personally I like that bit of extra code because it gives
| every class one reason to exist. There are no conflicts so
| the type system can be used fully without ambiguity.
|
| I've always disliked the way early rails promoted fat
| models that combined serialisation, deserialisation,
| validation, persistence, querying and business logic in the
| same class.
| TeMPOraL wrote:
| I personally bounce back and forth about this. My
| experience is probably colored by the fact that I'm doing
| this in C++. Boilerplate gets annoying there (and
| attempts to cut it down tend to produce lots of
| incomprehensible function templates). I like the idea of
| using types to encode assertions at a fine granularity. I
| dislike the amount of tiny little functions this creates.
| I also dislike that the resulting code is only navigable
| with an IDE - otherwise you spend 50% of your time
| chasing definitions of these little types.
| vbsteven wrote:
| Ok yes, C++ might not be the greatest language for this.
|
| My experience here is mostly from Kotlin which is a great
| language for this. Nullability, extension methods,
| (reified) generics, data classes, delegates, etc can all
| help reduce boilerplate.
| chairhairair wrote:
| Is there more info about the tool itself that claims 100X
| decrease in application development cost? Quite the claim.
| mberning wrote:
| It does seem odd. Stealth product. Bold claims. 3 blog posts
| one of which is a funding announcement and the other two having
| nothing to do with the product. I am intrigued but also a bit
| skeptical.
| davidrupp wrote:
| Nathan Marz was the original creator of what became Apache
| Storm [1], which powered Twitter for some time. Skepticism is
| healthy, perhaps even warranted here, but I'm not betting
| against him just yet.
|
| [1] https://en.wikipedia.org/wiki/Apache_Storm
| vbsteven wrote:
| He is also the creator of Cascalog (Hadoop query dsl in
| Clojure) and the Lambda architecture pattern.
|
| Not lambda as we know it now popularised by AWS, but an
| architecture for stream processing where batch views from
| expensive and slow batch jobs are combined with speed views
| from stream processors into the final live result.
|
| https://en.m.wikipedia.org/wiki/Lambda_architecture
| pbiggar wrote:
| I've heard from investors that it is similar to Darklang. It
| certainly has the same goals, but dunno if it's the same
| approach in any way. Will be interesting to see
| neysofu wrote:
| In that case, I wouldn't go near it with a ten foot pole.
| twobitshifter wrote:
| The team is impressive, I was wondering what all this new
| internal language, 400 macros, etc., could be put towards,
| thinking they were stuck in over-engineering. But after seeing
| that promise for their app, I changed my mind. Something that's
| capable of making you 100 times more productive probably does
| need that level of development.
| kulig wrote:
| How do VCs still fall for this shit.
|
| It is mind bogling.
| dj_gitmo wrote:
| I'm also curious. It seems like the website has been around for
| 2 years and hasn't changed much. If they wrote 250k lines in
| two years, that is around 340 lines a day. That seems like a
| rather large project to build before putting it in the hands of
| customers.
| karmasimida wrote:
| I will be very dubious of this claim, or belief is really what
| it is. There is not even simplistic metrics to back it up
| yamrzou wrote:
| It's still in stealth mode. They raised $5M in 2019:
| https://news.ycombinator.com/item?id=19565267
| swyx wrote:
| how exactly is it stealth if they have a blog and announced
| funding?
| yamrzou wrote:
| I meant that they didn't reveal the product yet.
| geospeck wrote:
| > 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?
| jahewson wrote:
| 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".
| SCLeo wrote:
| Pardon my ignorance, but can this spec thing automatically
| deduce that 8^n evaluates to "a number which is a power of 2"
| or log(2, "a number which is a power of 2") is an integer?
|
| 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)?
| dgb23 wrote:
| The biggest manifestation of this is clojure spec:
|
| https://clojure.org/about/spec
|
| If your impression is that this is like sugary unit tests: It
| is not. You can run specs during development, while running a
| in-editor REPL, code gets evaluated while you type it so to
| speak.
|
| It is way more expressive than a type system and it is opt-in,
| but it doesn't give the same guarantees obviously. It is also
| not meant to be a type system but rather a tool to express the
| shape of your data. It is used for obvious things like
| validation but also for documentation (over time) and
| generative testing among other things.
| elmers-glue wrote:
| 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.
| StreamBright wrote:
| 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.
| Zababa wrote:
| I think this would work with every language that has
| nominal and not structural typing. If you have structural
| typing, you have to wrap the int. For example, this is
| "branding" in Typescript. I'm not sure if there is a
| performance penalty and how big it is though.
| Zababa wrote:
| 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.
| CraigJPerry wrote:
| 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.
| Zababa wrote:
| I wouldn't call it a poor hack. In a way it's a parser,
| which aren't really poor hacks, but can be abused. Sure,
| it's not perfect and I'd like it better if it was checked
| at compile time, but it's way better than using a simple
| int. Also, the moment you have a bug, tracking it is
| really easy: just list the functions that returns this
| specific type.
| LeonidasXIV wrote:
| Yes, but this can be easily worked around by creating custom
| types that wrap the integer (and can be unwrapped on compile
| time) and some conversion functions. So slightly more tedious
| but saying one can't define complex constraints on static
| types is not quite correct.
| ajuc wrote:
| Turbo Pascal could.
|
| In practice the only benefit is very similar to checked
| exceptions - can't forget to check that value is in range.
| dharmaturtle wrote:
| IMO the sentence is incorrect. Static type systems would
| "enforce the stronger constraint" at run time... same as the
| dynamic type system. Perhaps the dynamic type system can have a
| fancy linter that does something crazy like running your
| code... but I'm not aware of any such linter.
| p_l wrote:
| Most static type systems that can do what clojure.spec can do
| tend to include runtime assertion and type checks and do not
| erase type data from runtime (what some static type zealots
| call "uni-type" approach).
|
| For example Ada's type system, which has equivalent of Common
| Lisp's _SATISFIES_ construct, which implements a runtime type
| assert that can use all the power of the language.
| dharmaturtle wrote:
| Sorry, I don't understand
|
| >Most static type systems that can do what clojure.spec can
| do tend to include runtime assertion and type checks and do
| not erase type data from runtime (what some static type
| zealots call "uni-type" approach).
|
| When you don't erase the type data... you're gonna have
| more than one type. How is this a "uni-type" approach?
| p_l wrote:
| A popular (stupid) talking point in the stupid
| discussions about static/dynamic while missing that the
| axes were orthogonal, was for proponents of static types
| to claim that dynamic languages were "unityped" based on
| some convoluted logic about tagging data at runtime.
| dharmaturtle wrote:
| As someone who prefers static types, I agree with you
| that claiming dynamic languages are "unityped" is stupid.
| Javascript/Python/Clojure all literally have types.
| tekacs wrote:
| > Perhaps the dynamic type system can have a fancy linter
| that does something crazy like running your code... but I'm
| not aware of any such linter.
|
| Names in Clojure codebases and libraries are pretty reliably
| annotated with trailing exclamation marks, looking like:
| `save!`.
|
| To that end, running Clojure code blindly to test it and its
| types is a fairly practical practice, coming up in cases like
| Ghostwheel [1], which uses this for generative testing of
| clojure.spec types, which can be much more sophisticated than
| what is commonly used in static systems, even with refinement
| types.
|
| [1]: https://github.com/gnl/ghostwheel#staying-sane-with-
| function...
| dharmaturtle wrote:
| >which can be much more sophisticated than what is commonly
| used in static systems, even with refinement types
|
| Can you elaborate on this? I have some experience with
| Clojure, and have been relatively unimpressed with spec.
| Everything it does I can do with (refinement) types (I
| think). Reading over the Spec documentation it constantly
| talks about predicates... which is exactly what a
| refinement type is.
|
| Spec/Clojure has the problem where it validates... but
| doesn't parse. See: https://lexi-
| lambda.github.io/blog/2019/11/05/parse-don-t-va...
| divs1210 wrote:
| You can have arbitrary types. Like a PrimeInteger type or a
| NameStartingWithD type or a complex hashmap with types defined
| for each key, like found in most configuration files.
| jb1991 wrote:
| I wonder why they are using Schema instead of Spec. Schema was
| popular before the latter existed, I didn't realize anyone still
| uses it.
| ARandomerDude wrote:
| I still prefer Schema because I find it more much more
| readable.
| synthc wrote:
| yes i also prefer it. With Schema, the defined schema's are
| normal vars, which you can jump to with editor support. With
| spec i'm always searching for the definitions.
| whalesalad wrote:
| That is probably exactly why they are using it ... you don't
| get to 250k lines of code overnight.
| jb1991 wrote:
| My understanding is that the company is a few years younger
| than Spec, but perhaps the code base is very very old.
| synthc wrote:
| Schema is a good library. Everyone seems to use spec today,
| but Schema easier to use than spec IMO.
|
| Spec is also still in alpha, with spec2 still under
| development.
| fnord77 wrote:
| 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.
| coneill wrote:
| 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 what the original
| intent of the code is anymore.
|
| The repl based development I find also breeds a really bad
| mentality of forgoing building a deployment process and instead
| people just repl in and make a bunch of changes and prod rarely
| matches whats checked into github.
| raspasov wrote:
| I've been using Clojure for almost 10 years and writing
| macros has always been discouraged in the community. You
| don't see too many of them in the wild, and for good reasons.
|
| If you're writing macros on a daily basis, you better have a
| really good reason for it.
| oh-4-fucks-sake wrote:
| Obligatory evangalism: Considered Kotlin as a JVM-lang-of-
| choice? We use it on all our backends and we really love it.
| raspasov wrote:
| Are those "clever" functions pure?
| dragandj wrote:
| I wonder if they'd be abandoned if companies using them
| considered these 3rd party libraries valuable enough to
| contribute/fund the development.
| ashes-of-sol wrote:
| Would you mind sharing the abandoned libs/framework?
|
| When you say you have too many "I'm clever" functions, do you
| mean within code your team wrote, or in the ecosystem at large?
| swamiji wrote:
| this is wonderful - I've been curious about continuations and
| program state as a language construct. Not sure I understand the
| redplanet - type system et
| mping wrote:
| This is what I imagine experienced clojure developers can squeeze
| out of a language like clojure. I would venture that they can
| train a junior programmer in a couple of weeks, and make them
| productive very fast.
|
| I guess they could make it work in any language but judging by
| the description clojure is indeed a great fit, due to the macro
| capabilities, flexibility and solid runtime via JVM.
| trutannus wrote:
| > would venture that they can train a junior programmer in a
| couple of weeks, and make them productive very fast
|
| I've got _some_ experience with functional languages, and a
| good amount with functional features in OO languages. Started
| learning Clojure this week, and was pleasantly surprised with
| how quickly I could get working projects up and running. Less
| upfront learning curve than Elixir, which was unexpected.
| tmountain wrote:
| Yeah, it's nice to see such thoughtful adoption of language
| facilities clearly oriented towards creating a successful and
| maintainable codebase.
|
| The Clojure team has always done a nice job expressing the
| rationale for specific language features, and these rationales
| often lean towards solving problems that system designers
| historically faced.
|
| Oftentimes, I think folks think of modern languages in regard
| to their syntax, tooling, ergonomics, etc; however, to me, the
| more interesting benefit in adopting a modern language is in
| how its inbuilt features address design problems that earlier
| generation languages exposed.
|
| For a real world example of what I'm talking about, you can
| google "clojure expression problem" and find compelling
| articles about how Clojure solves this with protocols.
|
| Providing a toolkit for attacking categories of problems
| inherently gets people focused on the fact that these problems
| exist in the first place when they may not even recognize them
| otherwise, and regardless of the choice of language, it leads
| to better design oriented thinking in the context of larger and
| more complex systems.
| doyougnu wrote:
| Clojure doesn't solve the expression problem and the
| expression problem tends to be trivial in dynamically typed
| languages.
|
| Strictly speaking the expression problem is only defined for
| _static_ type systems because its concerned with _static_
| type safety and extensions without recompilation.
| tmountain wrote:
| It does solve it and doing so was a key design goal of
| protocols. It's right there in the docs:
|
| There are several motivations for protocols:
|
| Avoid the 'expression problem' by allowing independent
| extension of the set of types, protocols, and
| implementations of protocols on types, by different parties
|
| I agree that it's a concern in static type systems, but the
| same issue rears its head when defining methods which
| operate on specific types of strongly typed data, so no,
| it's not always trivial, nor is it a problem exclusive to
| statically typed languages, and if it was, there wouldn't
| be a need to introduce a new abstraction for which
| addressing the problem is a key goal.
| AtNightWeCode wrote:
| What I heard from colleagues that work with Clojure is that it
| is a horrible language where the default way of writing code is
| an imperative programming style where contexts are passed
| around and updated. Far from the concepts of functional
| programming.
| stingraycharles wrote:
| 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 use, but it's just not idiomatic to do in
| Clojure. I would not, however, assert that it's an imperative
| language.
|
| Could you care to elaborate on what exactly you find is
| lacking in Clojure?
| davidrupp wrote:
| 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-542692
| c7c02...
| robthethird wrote:
| Your link might be pointing to the wrong example. I
| assume you wanted to show an example of an atom.
| divs1210 wrote:
| What is this? Are you being serious?
|
| I've worked in multiple Clojure shops and it has always been
| amazing.
|
| All the core code and business logic etc. (kernel) is
| implemented functionally, and the interface with the outside
| world (shell) is implemented imperatively.
|
| Clojure being a horrible language is a really hot take and it
| being based on hearsay makes your coomment look like it's not
| in good faith.
| AtNightWeCode wrote:
| As with all functional programming languages, if you limit
| the use to some specific areas you can get a lot done with
| a few easily understood lines of code.
| StreamBright wrote:
| Quite the opposite. Functional style is especially useful
| in larger codebases. However, I think a functional
| strongly typed language is often easier to get right than
| a weakly typed one. I mostly write F# and Clojure and
| based on my experience I would go for F# any day over
| Clojure but at the same time I would also go for Clojure
| over Java as well.
|
| I do not know where your views are coming from, sounds
| like 2nd hand experience rather than 1st one.
| AtNightWeCode wrote:
| As I stated in the beginning I do not work with Clojure.
| Pure functions is something I use in any programming
| language. I have a hands on experience from a lot of
| different functional languages though including F#. From
| what I understand it is very common to use context based
| style of coding in Clojure. That is at least what I heard
| and Google does not seem to disagree...
| [deleted]
| ltultraweight wrote:
| I use Clojure quite a bit and I don't write anything
| imperatively.
|
| But passing contexts I can see. There is a not uncommon
| pattern that one can use of keeping a large map with state in
| it.
|
| However it's completely compatible with pure functional
| programming.
| cliftonk wrote:
| I've found I typically reach for clojure when i need to do
| something on the jvm and want a better java than java.
| lukashrb wrote:
| Interesting take since Clojure and Java are two very
| different languages. And unlike for example Kotlin, Clojure
| does not try to be a better Java than Java. But true though
| Clojure leverages the power of the jvm.
| __jem wrote:
| Interop from Clojure -> Java is still incredibly easy,
| though. I often will just wrap a Java library rather than
| look for a pre-existing Clojure implementation because it's
| so easy. Of course, most Java libs don't value immutability
| and functional patterns, but you can still push this kind
| of interop to the edges of your system and keep everything
| else in pure Clojure.
| StreamBright wrote:
| Depends on the style the Java library you are trying to
| use is written in. I have had mixed results. Especially
| the Java 8 functional style had some challenges. I might
| have the relevant SO question somewhere.
| forgetfulness wrote:
| It was a better Java in many ways.
|
| The doto macro was a relief back in the days when Java APIs
| insisted on being designed around stateful setters and
| getters rather than the builder pattern, it allowed you to
| operate on such unfortunately designed objects in single
| logical blocks and simulating it all being an expression.
|
| Proxy allowed you to instantiate anonymous inner classes
| implementing only the methods of an interface that you
| needed, the rest you could omit; in Java you have to put
| them all, empty, which necessitated that you use an IDE to
| generate them, and back then IDEs were not as nice as
| today.
|
| Those two alone made interacting with contemporary Java
| libraries so much easier.
|
| It also was convenient to go from Java collections to
| Clojure collections and vice versa.
| pjmlp wrote:
| " We were not out to win over the Lisp programmers; we
| were after the C++ programmers. We managed to drag a lot
| of them about halfway to Lisp."
|
| -- Guy Steele
| pjmlp wrote:
| Basically you get a new version of a Lisp Machine, that you
| can sneak up in lots of places, whereas with Common Lisp it
| isn't so easy to do so.
| forgetfulness wrote:
| I think that's also why Clojure use peaked right before Java
| 8 was released; once Java became a better Java, and libraries
| started to be designed around more ergonomic APIs than wiring
| up objects that needed miles of stateful configuration code,
| the pressure that drove you out of Java and into Clojure
| began to diminish.
| dustingetz wrote:
| Clojure's value prop is:
|
| 1) default immutability (same simple data structures used in
| every library -> ecosystem composes better)
|
| 2) portable code across multiple host platforms (jvm, node,
| browser)
|
| 3) metaprogramming
|
| IMO, in 2007, immutability on the JVM was a competitive value
| prop, but in 2021+ it is nothing special. It is the
| combination of the three things which is a competitive value
| prop today. Metaprogramming in particular is a mostly
| unexplored frontier, because it is very hard to do well, and
| very easy to do badly. Default immutability is kind of a
| necessary starting point to do metaprogramming well.
| davidrupp wrote:
| > in 2007, immutability on the JVM was a competitive value
| prop, but in 2021+ it is nothing special
|
| Can you elaborate on this? What do you think has changed in
| that time to make it "nothing special"?
| dustingetz wrote:
| immutability as a library is available in basically all
| mainstream languages now and mainstream frameworks
| leverage it (react, any UI framework, spark, any data
| framework or database); JS vms are competitive with the
| JVM and the JVM might even be losing ground in the cloud;
| typescript is a monster and is letting people explore
| haskell concepts in industry applications; scala is _way
| better_ in 2021 than it was in 2011; every new PL can
| compile to JS and supports immutability. Clojure 's sweet
| spot is currently developing sub-languages embedded in
| Clojure like RPL is doing (we are doing exactly the same
| thing at hyperfiddle). That is still too hard to do in
| typescript imo. Maybe it is possible in scala 3 but it
| took them 10 years of pure autism to figure out how to do
| monadic IO in scala due to how complex scala is, so i'd
| expect it to take another 10 years to figure out how to
| do metaprogramming in a commercially viable way, I'd be
| happy to be proven wrong.
| davidrupp wrote:
| "10 years of pure autism" -- This made me smile.
| [deleted]
| misiti3780 wrote:
| If 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.
| pkd wrote:
| If you are into books, I will recommend Clojure for the Brave
| and True which is free to read online [1], and Living Clojure
| [2], in that order.
|
| If you're into interactive kata-style problems, there's
| 4Clojure [3].
|
| Also join the Clojurians's Slack [4] for the community.
|
| [1] https://www.braveclojure.com/clojure-for-the-brave-and-
| true/ [2] https://www.oreilly.com/library/view/living-
| clojure/97814919... [3] https://www.4clojure.com/ [4]
| https://clojurians.slack.com/
| ifFxhF938 wrote:
| The "Clojure for the Brave and True" ebook is a popular and
| free resource for beginners: https://www.braveclojure.com/
| sremani wrote:
| https://purelyfunctional.tv/mini-guide/the-ultimate-guide-to...
| chromanoid wrote:
| > 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 seamlessly.
|
| Actually this sounds quite horrible.
| afro88 wrote:
| Imagine starting a job as a closure dev and being told you have
| to learn a new language with substantially different semantics
| TeMPOraL wrote:
| Everyone is doing it already, to a large extent. Where "an
| API" ends and "a programming language" starts is a matter of
| opinion. It's a fuzzy boundary.
| dustingetz wrote:
| 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 without the typed people
| even noticing!
| TeMPOraL wrote:
| The elephant in the room is Babel, which is essentially a
| macro processor for JavaScript.
| iamcreasy wrote:
| 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.
| brundolf wrote:
| I kind of like Rust's version: macros are limited and clunky
| (and _really_ clunky if you want to do anything super fancy),
| which means they 're available, but I only end up using them
| when it's truly worth it
| SatvikBeri wrote:
| In my experience with Julia it's no easier or harder than any
| other code. Macros tend to be pretty transparent, and it's
| easy to see what code they generate, although I've never had
| to do so. Most of the time, a library with a macro-based DSL
| (e.g. JuMP) has a function-based DSL underlying it that's
| just more verbose.
| TeMPOraL wrote:
| > _I have always wondered how difficult it it to debug macro
| heavy code_
|
| It isn't much harder than regular core. `macroexpand' is your
| friend! Particularly when wrapped by your IDE[0] into a
| "macrostepper" tool, which lets you macroexpand into an
| overlay[1], step by step.
|
| Ultimately, macros are just functions - if slightly peculiar
| ones (they receive unevaluated arguments, and their return
| values are treated as if they were the code at the point of
| invocation). You can unit-test them just like any other
| function.
|
| That's not to say there aren't macros that are very tough to
| debug. But the problem isn't _macro-heavy_ code, as in code
| containing a lot of macros. The problem are _heavy macros_ -
| monstrosities with complex internal logic, expanding their
| inputs into large amount of code generation, hidden state,
| etc. Complex macros like these take skill to write[2], but
| they 're also rare to see.
|
| --
|
| [0] - By which I mean Emacs, though I guess there are Clojure
| IDEs now too.
|
| [1] - I.e. a read-only view replacing the code you're
| macroexpanding on screen.
|
| [2] - First rule: minimize the amount of code within the
| actual macro definition, move all logic into functions to be
| called by the macro, and unit test those extensively.
| rst13 wrote:
| This is sick
___________________________________________________________________
(page generated 2021-06-03 23:00 UTC)