
It might be worth learning an ML-family language - jodooshi
http://www.drmaciver.com/2016/07/it-might-be-worth-learning-an-ml-family-language/
======
agentultra
You get much more than type-checking from ML-style languages.

It's worth learning one of these languages well enough to understand the
fundamental concepts at play. If all you can see is static type analysis and
arcane signatures you haven't dug deep enough yet.

The renaissance of functional programming happening in JS is the only thing
that makes me happy about working in that language. Having immutable.js,
ramda, ramda-lens, redux, and various monads (such as data.task and
data.maybe) makes my code much more effective than using the majority of JS's
more dynamic features... and it doesn't even have static type analysis.

------
muglug
My alma mater's introductory CS course was entirely in Standard ML, and was a
great (if occasionally painful) way to learn some of the fundamentals of CS
without requiring any background knowledge.

------
SixSigma
Julia is interesting in this regard (and Go I think - I used it's ancestor:
Limbo). You can start with

    
    
        function foo(bar)
            pass
        end function
    
    

and then add types later when you've found out what was inferred

    
    
        function foo(bar::Int64)
            pass
        end function
    

I enjoy this style, it means I can create the story as I go along without re-
writing types every time

~~~
sdegutis
Wait, does Julia really use the syntax `end function` to end a function? I
haven't seen that kind of redundancy since I first learned how to program
inside QBasic 3 for MS-DOS!

~~~
jdminhbg
It doesn't, not sure where GP got that from.

------
catnaroek
> people routinely seem to get confused as to whether something is a value of
> a type, a strategy for producing values of that type, or a function that
> returns a strategy for producing the type.

In other words, they aren't sharp enough thinkers. It's the same problem as
conflating singleton lists or sets with their lone element, constant functions
with their output value, or “any list” with “list of anything”. Dijkstra put
it quite nicely:

> About the use of language: it is impossible to sharpen a pencil with a blunt
> axe. It is equally vain to try to do it with ten blunt axes instead.

------
hacker_9
News just in: Languages with static type systems give better errors about
types.

~~~
devishard
This is not only missing the point, it's easy to prove false:

Python: `foo = '1' \+ 1` gives `TypeError: Can't convert 'int' object to str
implicitly`

C: `char bar[] = "1"; char* foo = bar + 1;` gives literally no error.

Good errors occur because of strong/weak typing, not because of static/dynamic
typing.

~~~
milcron
Static+strong typing is a nice combination, which is probably what hacker_9
meant to say.

ML family is particularly nice -- with Hindley-Milner type inference, you get
a lot of static guarantees with none of the verbosity of languages like Java.

~~~
catnaroek
It's not just type inference that makes programming in ML so nice. Also
important are:

(0) Parametricity: Type variables are never case-analyzed. As a result, types
convey useful information about what functions may or may not do. Haskellers
call this “free theorems”. Java's `instanceof`, C++'s `sizeof` and template
specialization, and GHC's `TypeFamilies` are blatant violations of
parametricity that make types less informative.

(1) A clear distinction between data (sums of products) and operations
(functions). In a general-purpose language, operations will always be somewhat
ill-behaved: they may raise exceptions, fail to terminate, etc. Data tends to
be better behaved, and algebraic laws can be stated about it, which hold even
in the presence of effectful and/or non-terminating functions. This makes ML
superior to Haskell, not to mention object-oriented languages.

(2) A module system that enforces abstraction boundaries between subsystems of
a larger system. ML's opaque signature ascription (which has no counterpart in
Haskell or object-oriented languages) ensures that the internal representation
of abstract types is only visible in the module where they're implemented. In
a well architected ML program, trying to violate another module's internal
invariants is a type error!

------
xntrk
I like the strong mission statement "it might be worth..."

~~~
steego
His qualifying words make him sound weak and pensive. My money says he's not
even a modern visionary/renaissance man.

------
ozten
I think Rust borrows enough from ML to give you the skill he is pointing at.
Static types with plenty of places where type is inferred.

~~~
catnaroek
Rust is wonderful, but inference is actually one of its weakest points. It has
only one feature that's incompatible with global inference (multiple types can
have methods with the same name), but sadly that's enough to require you to
manually annotate `Foo<Bar<Qux<T>>, Herp<Derp<U>>>`s in a lot of places. Rust
arranges things so that these places are often, but not always, function type
signatures.

In Haskell and ML, you can write tricky recursive functions without annotating
a single type at all. Often, I couldn't even infer the type on my own in a
reasonable amount of time! (By which I mean “under 5 seconds”, since my
patience for these things is rather limited.) The type checker does it for me.

~~~
vvanders
That's not been my experience so far, mind providing a more concrete example?

Only place I've had to annotate is when I'm using dynamic dispatch(&mut
io::Write for instance) or collect() where I don't have enough information to
determine type.

~~~
catnaroek
In Rust, function types need to be annotated. In Haskell or ML, I can have
functions, especially recursive functions, with _monstrous_ inferred types
(although, of course, one then makes type synonyms for the sake of a more
readable API) that would have to be explicitly annotated in Rust.

The reason why this isn't much of a problem for Rust programmers, is that
idiomatic Rust code uses vectors and slices much more often than recursive
data structures. But if you tried to roll your own recursive data structures,
like those from Okasaki's book, you'd quickly feel the pain.

~~~
caconym_
I like this about Rust. Functions (top-level, at the very least) should always
have their types annotated, as it makes the code easier to read and
understand. The fact that it makes type inference easier (does it?) for the
compiler is just icing on the cake.

I consider myself to be a decent Haskell programmer and it has been a while
since I wrote a function whose type I could not figure out. I suppose it is
convenient to have the compiler be able to give you hints, especially when
learning, but overall I find that when my types start getting really strange
it's time to take a step back and see if there isn't a more straightforward
way of doing it that'll lead to less headache down the road.

Also, I haven't dug into Rust _that_ deeply but I've implemented some data
structures for fun (working on an `Rc`-based rope with persistence right now)
and I would not agree that working with types in Rust is fundamentally,
significantly more onerous than in e.g. Haskell. There are other things that
make implementing e.g. structures from the Okasaki book more difficult, like
the need to work with the ownership model and various provided pointer types
rather than simply relying on garbage collection.

~~~
catnaroek
> Functions (top-level, at the very least) should always have their types
> annotated, as it makes the code easier to read and understand.

I prefer to annotate definitions only at module boundaries, because that's
precisely where an inferred type might be more general than what I actually
want to expose. But not all functions are exported to other modules.

> I consider myself to be a decent Haskell programmer

Incidentally, Haskell's modularity story is very weak.

> and it has been a while since I wrote a function whose type I could not
> figure out.

Oh, I certainly have an idea of what type I'm expecting a top-level function
to have when I define it. But I want the type checker help me write my
program, not just confirm that what I've written is good. This is why, for
example, GHC provides typed holes.

> but overall I find that when my types start getting really strange it's time
> to take a step back and see if there isn't a more straightforward way of
> doing it that'll lead to less headache down the road.

If everybody thought like that, then Haskell's `lens` and `pipes` libraries
wouldn't exist, and that would be a great loss.

> Also, I haven't dug into Rust that deeply but I've implemented some data
> structures for fun (working on an `Rc`-based rope with persistence right
> now) and I would not agree that working with types in Rust is fundamentally,
> significantly more onerous than in e.g. Haskell.

No higher-kinded types or ML-style functors means that you can't:

(0) Parameterize recursive data structures over whether type recursion happens
via `Box`, `Rc` or `Arc` pointers.

(1) Parametrically bootstrap complex data structures from simpler ones,
Okasaki-style.

And no global type inference means that whatever little you can implement will
be infinitely more verbose than its ML or Haskell counterpart.

