
Strong Types and Their Impact on Testing - xwowsersx
http://levinotik.com/strong-types-and-their-impact-on-testing/
======
TeeWEE
Imho types should make the programmer live easier, not more difficult. Looking
at the first code, and at the second peace of code. Imho the second piece of
code is easier to read, but it does lack validation logic & testing.

First of all, from a type-theory point of view, both Email and WebForm have
the same type. (same signature).

Also from a theoretical point of view, i think a type should not have an
instance if its not valid and "EmailAddress" of value "aasda" should not
exists. However it is allowed in his types.

Also, I don't see how defining lots of very specific types such as FromAddress
and ToAddress are really making live easier.

Something like this does the job, and is typesafe (in golang):

    
    
      type EmailAddress string
    
      type Email struct {
        to EmailAddress
        from EmailAddress
        subject string
        body string
      }
    
      func sendEmail(webForm string) err = {
        email, err := validateEmailForm(form) # performs validation
        if err {
          return err
        }
        err := email_service.sendEmail(email)
        if err {
          return err
        }
      }
    
    

Tests will simply use the returnValue of sendMail to validate its inner
working.

~~~
xwowsersx
Good point. Having a separate FromAddress and ToAddress was probably overkill.
You're right about the WebForm and Email having the same type. The form
submission could probably just produce an Email directly.

I don't know much about go. What is "err"?

~~~
icebraining
Go follows the C tradition of returning errors instead of using exceptions.
'err' is a variable of type "error"[1], which should either be nil or have an
error value.

[1]
[http://golang.org/pkg/builtin/#error](http://golang.org/pkg/builtin/#error)

~~~
xwowsersx
so the return type of this little program is "error" and nil if no error?

~~~
icebraining
Yes. It's either a value that implements the 'error' interface or nil.

~~~
badbath
That's nuts. All of your code now has to check if err != nil, etc.

Furthermore, the sendEmail and validateEmailForm values are essentially
ignored and used only for their possible error values.

Why should I wrote my program as if the thing I'm concerned primarily with is
errors? Much better to encode the failure into the type. You could have, for
example, some function that returns Either[Error, SendEmailAction] where
SendEmailAction is a function that, when called, will perform the effect of
sending an email. Now I can deal with the happy path by mapping on the right-
biased Either.

~~~
ecopoesis
Not if you use Scala. With Scala, you'd just map or for comprehension over the
result.

~~~
masklinn
That's what he covers in his third paragraph...

------
marcelk
Interesting article, especially encoding as much domain knowledge as possible
into types by encapsulating simple strings/ints/etc into case classes. Fully
agree :)

However, performing validation outside of the model makes it possible to
create a model with invalid data. For example, calling val x =
EmailAddress("@") would pass. Thus, a developer must remember to call the
validation every time they create such a case class. In my experience, this
wouldn't hold since developers are as lazy as the code allows them to be.

Instead, why not include requirements directly into a case class constructor.
For example:

    
    
      case class EmailAddress(addr: String) {
        private val emailRegexp = ...
        require(emailRegex.findFirstIn(addr).isDefined, s"Email $addr not valid")
      }
    

In this way, one can be sure that an EmailAddress case class always holds a
correct email and can always safely rely on this assumption. Also, one can
easily test (e.g. with ScalaCheck) that the constructor throws an
IllegalArgumentExceptin.

The only downside is the conversion of these exceptions into Either, Scalaz
Disjunctions or Scalaz Validation. Of course, it's not that pretty and
idiomatic to convert try-catch blocks into Either. But IMHO, it's a small cost
in exchange for the guarantee that if your model exists it must be valid.

------
Offler
3\. By using meaningful values and separating side-effecting functions from
pure ones, we can more easily reason about our program

I'm not sure how that's something that static typing provides, just seems like
good functional programming/referential transparency.

~~~
sgrove
True, but signatures help statically verify that side-effects can't happen at
a call site, giving you reassurances that it's non-side-effecting. Same with
referential transparency, I believe.

------
xwowsersx
Inspired by this Twitter convo, for those who care:
[https://twitter.com/LeviNotik/status/534190509947097089](https://twitter.com/LeviNotik/status/534190509947097089)

------
serve_yay
> we can easily shoot ourselves in the foot. We might mistakenly write
> Email("hello", "jason", "jason@gmail.com", "body of email"), for example.

I would just solve that with keyword arguments. Or, in Javascript, have the
constructor take an object whose keys are fromAddress, toAddress, etc. (Pretty
much the JS/ES5 version of keyword args.)

~~~
xwowsersx
And then if you're missing a key in your object, your program blows up at
runtime.

~~~
serve_yay
In JS, true. You can't do any of this stuff in JS! :)

But I guess my real point was that that seemed like more of an argument
against positional parameters than for lots of typing. I like how ObjC handles
this, I wish more languages did it that way.

------
jimbokun
What's with functional languages and the obsession with naming things after
random sequences of punctuation characters?

    
    
        validateFrom(form.from).toValidationNel |@| validateTo(form.to).toValidationNel |@| validateBody(form.body).toValidationNel |@| validateRecipient(form.recipient).toValidationNel)(Email.apply)
    

What's the difference between "|@|" or "@" or "|@" or "|@" or "@@" or "@@@"
or...

I'm sure this made perfect sense to someone at the time this decision was
made, but naming things this way does absolutely nothing to help someone
reading the code or using your library for the first time.

Can anyone explain the motivation for naming things in this way?

~~~
xwowsersx
Because they represent a well-known concept in category theory and symbols are
a simple, uniform way of representing that concept. It's terminology. Math has
terminology and so does category theory. Should we write (3 multiplied by 2
added to 5) in math? No. We write 3 * 2 + 5. Once you know those symbols,
they're well understood and concise.

------
aharris88
What is this written in reply to?

~~~
xwowsersx
In part, this gist:
[https://gist.github.com/darrencauthon/0c11c7fd24f82733bd12](https://gist.github.com/darrencauthon/0c11c7fd24f82733bd12)

~~~
darrencauthon
Yes, my gist, but also a Twitter conversation on how strong typing altered the
_need_ for tests. I've heard from a couple Haskell programmers about how the
language practically eliminated the need for tests. I don't see how that's
possible, but I also know Haskell.

I asked for a specific code example, and I threw out the scenario of a client
who wants a contact us form that logs all responses and sends notification
emails. I threw out a simple Ruby example where I tested the functionality,
and I'm waiting to see the alternative.

~~~
tome
Haskell does not practically eliminate the need for tests. I am very concerned
that Haskell programmers are getting the reputation for claiming such things
because they're simply not true.

Haskell's type system does statically check many things you might want to
write tests for which makes _those particular tests_ unnecessary, but it
doesn't eliminate the need for tests in general. If you hear any Haskell
programmers making claims that sound like that please ask them to be more
precise.

~~~
film42
Exactly! Even though haskell is pure, it's impure in the context of the
complete system.

------
michaelochurch
_Static_ types.

Python is strongly typed, although possibly not as much as we might hope. This
fails:

    
    
        "cat" + 1
    

with a type error, but it happens at runtime. (Oddly enough, Scala permits
it.) Python's type system isn't very expressive-- for example, you have "list"
and "dict" types but not "list of int"\-- and since you can branch without
type congruence (because Python's "if" is a statement, not an expression) you
can lose static knowledge pretty quickly, but Python _does_ block type errors.
It just does so at runtime, which means that the problem may be hard to
reproduce and the error/failure distance can be high.

I agree with most of what's said in the OP. I just want to push back against
the use of "strong" and "static" typing as if they were interchangeable. I'm
in the same boat-- generally an advocate for compile-time static typing-- but
to conflate static/dynamic with strong/weak is to misrepresent the issue, and
unfair to the dynamically-typed languages. All of the modern dynlangs (e.g.
Python, Ruby, Clojure) enforce type safety. They just do it at runtime,
whereas Haskell, Scala and Ocaml do it at compile time.

~~~
mikeash
I agree, it would be good to tighten up the terminology. "Weak typing" should
be reserved for stuff where the underlying bits can get reinterpreted at will,
e.g.:

    
    
        printf("%d", 42.0);
    

Strong typing means the type of each chunk of memory is known and can't be
arbitrarily changed, and then static/dynamic refers to how much type
information is present at compile time.

Might be a losing battle, though. Strong/weak has been overloaded in this way
for a long time.

~~~
masklinn
> "Weak typing" should be reserved for stuff where the underlying bits can get
> reinterpreted at will, e.g.:

> printf("%d", 42.0);

which does not demonstrate your point and is valid Python:

    
    
        >>> "%d" % 42.0
        '42'
    

also the more odd

    
    
        >>> "%.2f" % True
        '1.00'
    

and who could forget

    
    
        >>> "foo" * 3
        'foofoofoo'
    

edit: forget about this, for some reason I'd forgotten what the example would
output…

~~~
djur
Your first Python example is just numeric coercion, not weak typing. Haskell
does numeric coercion too (try 2 + 1.1 in ghci). C does _not_ behave like
Python and print 42 in this example. Try it yourself and see.

True being coercible to a number is unfortunate, but it's still not the same
as what C does with that printf.

"foo" * 3 is just an example of a polymorphic operator. You could implement
the same in any strongly typed language that allows polymorphic operators.

~~~
tikhonj
Haskell doesn't do numeric _conversion_ , it just allows numeric _literals_ to
have any valid numeric type.

If you tried it with variables you would get a type error:

    
    
        > let x = 1.0 :: Double
        > let y = 1 :: Int
        x + y
        <interactive>:10:5:
          Couldn't match expected type ‘Double’ with actual type ‘Int’
          In the second argument of ‘(+)’, namely ‘y’
          In the expression: x + y
    

It sometimes looks like conversion because the literal `1` is polymorphic and
can have a more specific type like `Int` or `Double` inferred from context.

~~~
djur
Ah, thanks for the correction.

~~~
michaelochurch
(Different poster.) In fact, one of the beautiful things about Haskell is that
the type system can (usually) infer exactly as much specificity as is needed.

    
    
        λ> :t 5
        5 :: Num a => a
        λ> :t 5.6
        5.6 :: Fractional a => a
        λ> :t (5 :: Integer)
        (5 :: Integer) :: Integer
    

The type of the literal "Num a => a" means that the literal "5" works for any
type in the Num typeclass. So you can mix literals with any numeric type
because the compiler will correctly infer the right type. You can even get
usages like this:

    
    
        λ> :t (\x -> x + 5)
        (\x -> x + 5) :: Num a => a -> a
        λ> let f = (\x -> x + 5) in (f (0 :: Int), f (0 :: Double))
        (5,5.0)

------
lovelustus
Boomtown! This is awesome...

