News for hackers. Stuff that matters.
Re:Clojure is Capable by wooby on Friday December 28, 2018 @12:17AM (#18775023)
At a Clojure meetup I attended a couple years ago, a long-time Java programmer asked "how do you manage huge projects without static types?"

The best answer I could think of was, "don't manage huge projects." Instead, divide projects as early as possible in development into supporting libraries, each capable of being independently tested and maintained.

In my experience, applications developed with this attitude coalesce themselves into a small ecosystem of mostly generic library code with a domain-specific apex "application" entrypoint to bind them.

In an application factored like this, type checking isn't something you want as much anymore, since the constituent libraries interface with one another at a level above. They present and consume "public" (intra-application) APIs, the behavior of which are defined not just by types but by at least documentation and tests. The thing that static types let you do -- have huge amounts of code, woven together with structural dependencies -- is no longer a thing you need or want. It would seem bad, actually, since what you'd have is an inscrutable mass only a machine can understand.

I haven't worked enough with large non-Clojure codebases to say whether or not this approach is superior, but I can say that it scales. Another nice thing about it in a business setting is that supporting libraries lend themselves to open-sourcing, and a relationship with an open source community is a good thing for a company's development efforts/hiring/contracting in my experience.

To answer your specific questions:

* I don't think I write more unit tests (I should probably write more tests in every language I use)

* I don't have any experience with clojure.spec

* All of the features of Lisp work together to make it awesome, including macros. In particular, in the context of an "ecosystem of libraries" approach, macros are a fantastic tool for exporting a DSL from a supporting library. They're also use for DSLs in the apex code, where application-specific behavior can be defined in terms of a DSL.

* I don't really wish I had static types - I love Lisp the way it is. But I have found various languages for structural checking useful with certain kinds of sub-program, like receiving data from the outside world, and for writing compilers. If you write a compiler as a series of transformations, it's nice if the shape of the code between transformations is well-defined somehow.

You're definitely smart enough to use Lisp. I think the key to success is not necessarily smarts, but attentiveness to modularity or cognizance of the difference between "library" and "application" types of code.

* There's a Paul Graham essay related to this idea: http://www.paulgraham.com/progbot.html

* I think this approach is applicable not just to Lisp, but maybe to any dynamic language with a sufficiently rich set of generic collections and with the concepts of a "module" or "package".

Re:Clojure is Capable by ilikehurdles on Friday December 28, 2018 @01:34AM (#18775200)
Wonderfully stated.

One thing that deep, static type systems often lend themselves to is complex types, and those complex types lead to complicated designs; and if I have the option, I'd rather spend my time designing a simple architecture than exhaustively testing and validating a complicated one.

Since using clojure (roughly the last 3 years in data-pipeline style architecture) I try to follow a principle of writing functions that do just one thing, simply, and do it well, keeping the interface as basic as possible. Almost all of the time when writing clojure, I'm writing functions that act on a couple of built-in types and return one built-in type, usually the same one as before.

Additionally, I try to isolate side effects.

Following these principes, I find that even when I'm dealing with a stinker of a namespace or running into a runtime error, it's pretty cheap to try it out in the REPL and get to refactoring.

That said, I kind of want my cake and to eat it too. I do miss static types -- just not as much as I missed them when I was writing Javascript or Ruby. If I write a new clojure library now, yes, I would try to instrument it (at least the public api) with clojure-spec. If I could re-design clojure, I'd leave out null and maybe build a more intuitive interface to clojure-spec to use it as a kind of progressive validation system via annotations and type hints.

Re:Clojure is Capable by agumonkey on Friday December 28, 2018 @03:00AM (#18775415)
> static type systems often lend themselves to is complex types, and those complex types lead to complicated designs

I don't know how to call this. But it's something I find true too. And to a point, it feels like a lot of businesses leverage that as a justification point for their cost.

Re:Clojure is Capable by dmux on Friday December 28, 2018 @11:27AM (#18777678)
It reminds me of a great quote from the book Computing Fundamentals [0] regarding Wirth's design philosophy:

>Wirth’s philosophy of programming languages is that a complex language is not required to solve a complex problem. On the contrary, he believes that l languages containing complex features whose purposes are to solve complex problems actually hinder the problem solving effort. The idea is that it is difficult enough for a programmer to solve a complex problem without having to also cope with the complexity of the language. Having a simple programming language as a tool reduces the total complexity of the problem to be solved.

[0] https://www.cslab.pepperdine.edu/warford/ComputingFundamenta...

Re:Clojure is Capable by agumonkey on Friday December 28, 2018 @12:21PM (#18778049)
I'd add that most solutions to complex things require a tiny abstraction that brings beauty and ease, not the feeling you get in a messy warehouse.
Re:Clojure is Capable by lopatin on Friday December 28, 2018 @01:24AM (#18775165)
Thanks for the insightful answer! Building an ecosystem of libraries to do most of the heavy lifting is generally what I strive for as well. It's certainly the goal, and I'll usually end up there for a given project. Perhaps one thing that makes me fond of static types is that, more often than not, I don't actually know how or what to modularize until after the project is nearly built in the first place. And I use types not only as a QA tool, but also as a tool for reasoning. The feedback loop between me encoding logic in types, and the IDE providing me with errors, is a big part of how I understand what I'm building, as I'm building it. I imagine that Clojure development on the REPL can actually serve the same purpose.

I think the idea of bottom-up programming also resonates with me, though I tend to find it hard to use as a general approach to programming. In my mind, it's similar to the development of DSLs, which I do fairly infrequently, and with care. There is an undeniable elegance to it ... find a set of primitive operations that can be composed into larger ones. My development style usually goes in either direction: Either super generalized, for which I'll take the bottom-up DSL approach, or the more typical case where it's more approachable for me to be explicit with types/code.

Re:Clojure is Capable by FPGAhacker on Friday December 28, 2018 @04:23AM (#18775752)
Myself, I sort of do both bottom up and top down programming.

Usually I start at the top and write out high level code as if everything I needed magically existed. That helps me think about what I want and how I think I'll use it and that helps and clues me in to what low level stuff I need to build. Then I start to think about how to build those things and that usually goes bottom up.

I do this iteratively. When I get stuck I go back to high level top down, figure out what I'm missing, go build that, and repeat.

Re:Clojure is Capable by AnimalMuppet on Friday December 28, 2018 @03:30PM (#18779193)
That's called Ping-pong Design, and is a perfectly legitimate approach.
Re:Clojure is Capable by yogthos on Friday December 28, 2018 @08:36PM (#18781100)
My approach to modularizing from the start is to do bottom up development, and to treat each namespace as a little library. Ultimately, pretty much all problems can be viewed as data transformation pipelines, and those naturally map to independent components doing different kinds of transformations. I find that the APIs tend to form naturally around domain boundaries as you move data from one context to another.

The REPL is indeed a major component of doing the Clojure workflow where you literally run each function as you're writing it. This approach also encourages refactoring as you go since you can make changes and see the results immediately.