Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I would love to use closure, i just stay away from dynamically typed languages - after having a bad experience trying to maintain projects written in a dynamically typed languages which was an awful experience. You see a parameter named user and you have no idea which type it is, which fields it has, does this user has a phone number? how can i know? You see functions and you don't have any idea what is the type they return. just to put things in perspective I maintained X100 larger projects in a statically typed languages, with even worse code but maintaining it was easier than these other projects. Simply because I knew what functions were getting in and what their return type was.


Clojure has `defrecord`, so you can `defrecord User`.

Then you can assert that `user` is a User, with type hints, preconditions, 'spec' (a Clojure library), tests, docstrings, or some ad-hoc convention ("this kind of namespace does this kind of data transformation").

Then again, some Clojure users might prefer to use plain hashmaps (Rich tends to encourage this; or at least that's the common interpretation from his talks), and none of the mentioned mechanisms.

The mechanisms are there. Up to each to use them or not, depending on your scenario (and sincerely on one's competence too)


I generally agree with you, but Clojure is not at all like other dynamic languages (like, say, JS). For one, Clojure's dynamic dispatch is more like C++/Java's -- i.e., when you call a function, you can statically know either the precise target or an explicitly designated set of possible targets. Second, Clojure allows you to specify the types of arguments (and much more, in fact), not with a type system, but with a specification system. It's not exactly the same, but it can be more/less powerful than types, depending on usage.


How is the specification checked, statically or dynamically?


You can see my other comment to your same question. Also, even if you do not use Spec, you do get a lot of compile-time validation like making sure all arguments are passed to a function, not leaving them out, etc. Try that in Javascript!


Are you sure about "you do get compile-time validation like making sure all arguments are passed to a function"?

If I run this:

    (defn bar [x y]
        (println x y))

    (defn foo [v]
        (if v (bar 12 42 79)))

    (foo false)
The program prints nothing and Clojure reports no error.

But when I run this:

    (foo true)
Then Clojure reports an error:

    Exception in thread "main" clojure.lang.ArityException: Wrong number of args (3) passed to: user/bar, compiling:(.../test.clj:6:5)


I don't spend much time on the JVM, but in ClojureScript, this is what I get for a similar test (including multiple definitions of foo in my case, i've elided the whole source file here):

     (defn foo [a b] :hi)
     (defn bar [] (foo 1 2 3))

     WARNING: Wrong number of args (3) passed to prepost.core/foo at line 25 src/cljs/prepost/core.cljs
     WARNING: foo at line 24 is being replaced at line 48 src/cljs/prepost/core.cljs
It's a compile-time warning even when the app is not running, but ClojureScript goes through a different compilation step than JVM Clojure. Perhaps I was mistaken in the JVM case, or someone else can chime in.


Maybe ClojureScript is doing something Clojure doesn't at compilation time…


I would encourage you take a look at the rational behind Clojure spec and Schema. Both allow you to specify the expected types of the arguments.


How is the spec checked, statically or dynamically?


It can actually be used in a variety of ways. It can be a runtime assertion. It can also be used to automatically generated unlimited tests (using property-based testing, similarly to Haskell and other languages) and that can be done at compile time. That is, you write a spec for a function and the tests are automatically created to ensure that all valid input the function can handle matches corresponding output it emits.

Because these are not actual static types, you can get a lot of control and say things like "this function accepts three arguments, all integers between 5 and 50, but in increasing order, and the output must be a float between 0 and 1", and then have that checked on the function before the program ever runs. At run time, you can then verify that you are passing in valid arguments.

Spec is relatively new, and there are experiments in the community to do things more like traditional static type checking also. The future is an interesting place.


You wrote "then have that checked on the function before the program ever runs". That is the definition of static checking.

To statically check constraints like "all integers between 5 and 50", you need dependent types, and Clojure doesn't support this.

Considering this, I'm not sure what you mean by "before the program ever runs". Do you mean a check thanks to automated property-based testing (which is a way of running a part of your program)?


No, I'm not talking about static checking. I'm talking about property-based tests that run at compile time. Dependent typing is a different matter. What I describe is not dependent typing, but is quite straightforward now thanks to Clojure.spec. And there are a lot of community efforts in the research sphere to see where Spec can take static analysis, it's quite interesting.


Thanks.


Dynamically, but there are some libraries out there that are working to bring static analysis on steroids to spec-enabled code. And as spec is opt-in, you can spec just how much (or little) you find useful.


Interesting. Any link?


Sure: https://github.com/arohner/spectrum

I think the main advantage of a type system (e.g. Java's) is that it avoids a lot of stupid mistakes; and therefore does help refactoring because most "stupid" changes will simply not compile (rename a function but forget it's used in 3 other places? boom)

Static analysis with Spec might be a great booster because it gets you the expressiveness of Spec to static code reasoning.


Thanks for the link.

I've used static and dynamic languages. When using a dynamic language, I usually don't miss static language features, except when refactoring and/or maintaining a large code base. It's the reason why I'm interested by Flow and TypeScript (in the JS ecosystem), mypy (in the Python ecosystem) and even static languages like Go. It's also the reason why I'm a bit reluctant to invest in Clojure, despite being interested by the language.


Dynamically


Which language?


mostly javascript, but I had a very similar experience in ruby and python. The main challenge was maintaining projects developed by other people.


I worked exclusively with statically typed languages for about a decade before moving to Clojure. I've been using it for about 7 years now professionally, and haven't found dynamic typing to be a problem. If it was I would've gone back to using a static language a long time ago.

I think dynamic typing is a lot more problematic in imperative/OO languages. One problem is that the data is mutable, and you pass things around by reference. Even if you knew the shape of the data originally, there's no way to tell whether it's been changed elsewhere via side effects. The other problem is that OO encourages proliferation of types in your code. Keeping track of that quickly gets out of hand.

Clojure embraces immutability, and any changes to the data happen explicitly in a known context. This makes it much easier to know exactly what the shape of the data is at any one place in your application.

Meanwhile, all the data is structured using a set of common data structures. Any iterator function such as `map`, `filter`, or `reduce` can iterate any data structure, and it's completely agnostic regarding the concrete types. The code that cares about the types is passed in as a parameter. Pretty much any data transformations in Clojure are accomplished by chaining functions from the standard library together, with domain specific code bubbling up to a shallow layer at the top.

There's also the culture of using Spec and Schema in Clojure, so you often have assertions at the interface level. For example, there's ring-spec https://github.com/ring-clojure/ring-spec/blob/master/src/ri... providing the specification for Ring.


Hi, I went on and searched for a random clojure project in github, and have chosen some function which is of a small size:

(defn parse-seq [parse-el form] (when (sequential? form) (reduce #(if-let [parsed (parse-el %2)] (conj %1 parsed) (reduced nil)) [] form)))

without knowing really clojure i take it "[parse-el form]" are the arguments of this function. How is it possible with minimal effort to know what am I supposed to pass as parse-el and form? what are they?

https://github.com/tonsky/datascript/blob/master/src/datascr...


Let's try to look at it in isolation.

"parse-el" is used as a function, which takes one argument, the first argument when invoked from reduce (the accumulator), which is first [] then another vector (because of conj).

So "parse-seq" parses a sequence (named "form") and produces a vector, or nil. It applies "parse-el" to each form using reduce. When the result of any form gives a nil value, the whole result is also nil (see https://clojuredocs.org/clojure.core/reduced). The result is also nil if form is not sequential.

There is no other contract about "parse-el", it just should be able to "parse" whatever occurs in "form". You could use it on your own with any kind of data that has a corresponding parse function.

A documentation string would have been nice, or different names ("parse-el"?)

Now, if you look at the usage of "parse-seq", you can see for example:

    (let [vars* (parse-seq parse-variable vars)
         ....
And it looks like "parse-variable" parses a form and returns an instance of Variable.

    (defn parse-variable [form]
      (when (and (symbol? form)
                 (= (first (name form)) \?))
        (Variable. form)))


"the first argument" => "the second argument"


This is a very interesting question. To me, understanding in isolation some random function deep down in the code somewhere is not really something I do. Maybe that's why I prefer dynamic languages. And other people like to be able to do that, and therefore prefers static languages.


This has definitely been a challenge for me in Clojure when trying to use a library that isn't well-documented. Using the REPL helps because I can throw random things at it quickly, but it's still very frustrating.

An interesting corollary to this problem though is the wandering type-path, where when consuming (again, poorly documented) a Java library, I know the type of the function I want to call, but not how to get that type. Usually it's:

"I have B value [byte-array, int, whatever] and I need Z from this library so I can call `Z.foo`... Z's constructor takes Y, which takes X, which takes..."

Types help there, but they also hinder quite a bit if I have the correct underlying data type, but can't get Java to accept it.


Look at the function doc string. Look at the function spec/fdef Look at the function comments.

OK, there aren't any. Pull requests accepted?


>mostly javascript

Well, javascript does not have some of the features that other dynamic programming languages have, that help you manage complexity and create successful projects of large size.


Like what?


Strong typing, powerful package and namespacing facilities, a good object oriented system (if you want to do OOP, that is), interactive development with a REPL that can tell you easily which function calls which others, etc.


What's wrong with Javascript's module and OOP systems?


AFAIK JS doesn't really have modules, just ad-hoc use of functions and objects for namespacing




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: