One aspect of unit tests is to have the specs in runnable form.
However when employing a declarative style, that directly maps the
formalized specs to language semantics, is it even actually possible
to express the specs in runnable form in a separate way, that adds
value?
If you have specs which can be mapped directly to function declarations - fine. But typically those are two completely different levels of abstractions. Unit tests are intended to test single pieces of code, written as white box tests by the same developer who is working on the function. Specs normally look like "when I enter this value here and press on this button, this and that should happen". Typically such a spec leads to far more than one function to be developed and tested.
However a simple mistake like using x
instead of y (both being coordinates) in your code cannot be covered.
However such a mistake could also arise while writing the test code,
so I am not sure whether it is worth the effort.
Your misconception is here that unit tests are actually for finding bugs in your code first hand - that's not true, at least, it is only partially true. They are made to prevent you from introducing bugs later when your code evolves. So when you first had your function tested and your unit test working (with "x" and "y" properly in place), and then while refactoring you use x instead of y, then the unit test will show that up to you.
Unit tests do introduce redundancy, which means that when requirements
change, the code implementing them and the tests covering this code
must both be changed. This overhead of course is about constant, so
one could argue, that it doesn't really matter. In fact, in languages
like Ruby it really doesn't compared to the benefits, but given how
statically typed functional programming covers a lot of the ground
unit tests are intended for, it feels like it's a constant overhead
one can simply reduce without penalty.
In engineering, most safety systems rely on redundancy. For example, two breaks in a car, a redundant parachute for a sky diver etc. The same idea applies to unit tests. Of course, having more code to change when the requirements change can be a disadvantage. So especially in unit tests it is important to keep them DRY (follow the "Dont Repeat Yourself" principle). In a statically typed language, you may have to write some less unit tests than in a weakly typed language. Especially "formal" tests may be not necessary - which is a good thing, since it gives you more time to work on the important unit tests which test substantial things. And don't think just because you static types, you don't need unit tests, there is still plenty of room to introduce bugs while refactoring.
Is there something fundamentally different about the languages that allows F# to have the interactive console but makes it difficult to implement it for C#?
Yes.
F# is a descendant of the ML programming language, which in turn was heavily influenced by languages like Lisp and Scheme. Those languages were designed from day one to have three nice properties.
First, those languages do not really have statements the way you think of them in C#. Rather, almost everything is an expression that has a value, so an evaluate-and-then-print-the-value mechanism makes sense in almost every situation.
Second, those languages discourage programming with side effects, so you can make evaluations without worrying that you’re going to be messing up global state.
Third, most of the work you do in those languages is “at the top level”; there is typically no enclosing “class” or “namespace” or other context.
By contrast, C# emphasizes programming control flow with statements that produce side effects, and those statements are always in multiple nested containers -- a namespace, a class, a method, and so on.
So these are all things that make it harder for C# to have a REPL, but certainly not impossible. We’d just need to figure out what the semantics are for statements and expressions that appear outside of the usual context, and what the semantics are of mutations that change name bindings, and so on.
Why does F# have an interactive mode but not C#?
Because the F# team decided that having a REPL loop was a priority-one scenario for them. The C# team historically has not. Features do not get implemented unless they are the highest priority features that fit into the budget; until now, a C# REPL has not been at the top of our list.
The Roslyn project has a C# REPL (and will eventually have a VB REPL as well, but it is not ready yet.) You can download a preview release of it to see how you like it at
http://www.microsoft.com/en-us/download/details.aspx?id=27746
Best Answer
I'm addressing the overlap between Haskell and F#'s type systems. The part they share is a simplified subset of a system known sometimes as System F. F# by necessity provides bits and pieces of C#'s type system, but this isn't what the speaker was talking about.
Both C# and Haskell/F# are statically typed, but they're two different flavors.
Subtyping
Specifically, C# is a statically typed language with subtyping. This means that if you look at the typing rules for C# there's one like
Which means that any well typed term has more than one potential type. This in not the case in (vanilla) Haskell or F#. Every type has a single most general type, a principal type. This has a few benefits like total type inference.
Expressions Everywhere
Besides this, Haskell and F# have a greater emphasis on expressions, expressions have types, but statements don't. This means that in C#, the compiler is checking less of your code, because less of your code has types. In Haskell, everything has a real, checkable type.
In substantive terms, every single node on a Haskell AST has a type or kind (or sort). The same cannot be said of a C# one.
Sum Types
Beyond this, Haskell and F# have what are known as
sum
types.This is how a boolean is defined in Haskell. This provides a few different guarantees than in C#. For example, when there are a discrete number of things in C#, like say, an AST. We can model this with an inheritance hierarchy, but these are open. Anyone can come along and add something to it. We can use an
Enum
, but then there's no sane way to attach data to each tag uniformly. This is what's called a tagged union, and it's up to you to implement/use it correctly, the compiler ain't gonna help.This makes it very hard to ensure that we've covered all possible nodes of an AST. For example, say you have a function
The compiler can warn you because it can prove exactly how many nodes there are, ever. In C#, the equivalent would have to be done with downcasting, and then, since the set of nodes in our hierarchy is open, there's no way to issue compile time warnings.
A company, Jane Street, raves about how useful this feature is in their large OCaml code base. Watch a few of their talks, they talk quite a bit about how using OCaml impacts them in the Real World.
Soapbox
Now I've outlined quite a few differences over System F over many mainstream type systems, but they're all still static type systems. It does bother me when people call Java/C#/C++ "not statically typed" they are. And they certainly give you more compile time guarantees than say, Python. So it's a bit unfair to dismiss them out right.