I recently had to chase down a bug where I was calling a function with the arguments in the wrong order. The function's declared argument types were each an alias for string, so the compiler happily let me swap them. In order to avoid this in the future, I'm experimenting with using single-constructor union types in place of aliases, e.g. instead of "type alias Foo = String" I'm using "type Foo = Foo String".
This seems to work well for helping me enforce the kind of type safety I'm looking for. However, it seems to force me to do a lot more de/restructuring of function arguments, and it feels a bit graceless. For example, a (contrived) function that was originally like this:
aFunc : Foo -> Foo
aFunc f =
let
x = somethingWithAString f
in
somethingWithAFoo x f
becomes:
aFunc : Foo - > Foo
aFunc (Foo f) =
let
x = somethingWithAString f
in
-- Have to "repack" f into a Foo
somethingWithAFoo x (Foo f)
That is, if I want to thread something of type "Foo" through calls while also using its data, I have to "repack" it.
So my question is: are there better ways to do this? Are there better patterns for a) adding the type-safety I want while b) avoiding the extra noise? I know some languages (e.g. clojure) let you destructure bindings *and* bind a name to the un-destructured thing. Something like that might help.
Any advice (including "you're doing it wrong!") would be appreciated.
Austin