You have multiple references, often function arguments, that share a common data type.
Make the types of each reference distinct using newtype.
One of my favorite advantages of static type systems is how safe they make the process of refactoring. When I change a function's type signature, the compiler lets me know, before I ever try to run my code, everywhere I need to update my program to work with the change.
Consider the following refactoring; I want setName
,
which currently takes arguments for first and last names, to take just a
single argument for a full name.
def setName(first: String, last: String): Unit
||
||
\/
def setName(fullName: String): Unit
This change will ripple compile errors through my program, showing me each place I need to update my calls to it. Unfortunately, refactoring code does not always imply a type signature change, and API errors can sneak in to my code under the compiler's radar.
Consider the following refactoring; I want to swap the order of two arguments of the same type.
def search(haystack: String, needle: String): Unit
||
||
\/
def search(needle: String, haystack: String): Unit
This breaks any uses I have of the search
function, but
the compiler isn't able to let me know about it because it can't
distinguish needle
from haystack
; they're both
String
s. The search
function is "stringly typed".
One way around this is to make discrete record types for
needle
and haystack
, so that this refactoring
causes a change in the type signature of search
.
Unfortunately, this single-value "boxing" of data leads to an
ever-growing pile of new data types, each of which imposes additional
CPU and memory overhead.
What I want is a way to distinguish between these values at compile time, but avoid extra computational burden at runtime. It turns out that several languages support this idea, commonly known as "newtype".
Go supports type
identities via the type
keyword.
search.go:
package main
import "fmt"
import "strings"
type Needle string
type Haystack string
func search(n Needle, h Haystack) bool {
return strings.Contains(string(h), string(n))
}
func main() {
const n Needle = "needle"
const h Haystack = "This haystack is nothing but needles!"
.Println(search(n, h))
fmt}
$ go run search.go
true
Haskell supports newtype via the
newtype
keyword.
search.hs:
import Data.List (isSubsequenceOf)
newtype Needle = Needle String
newtype Haystack = Haystack String
search :: Needle -> Haystack -> Bool
Needle n) (Haystack h) = isSubsequenceOf n h
search (
main :: IO ()
= do
main let n = Needle "needle"
let h = Haystack "This haystack is nothing but needles!"
print $ search n h
$ runhaskell search.hs
True
Scala does not natively support newtype, but a basic approximation can be written in a few lines using phantom types.
Search.scala:
object Search {
trait Needle
trait Haystack
def apply(n: String with Needle, h: String with Haystack): Boolean =
h contains n}
implicit class Tagged[A](val a: A) extends AnyVal {
def tag[B]: A with B = a.asInstanceOf[A with B]
}
object Main extends App {
import Search.Needle
import Search.Haystack
val n: String with Needle = "needle".tag[Needle]
val h = "This haystack is nothing but needles!".tag[Haystack]
println(Search(n, h))
}
$ scala Search.scala
true
Support for newtype in Scala is also available in libraries like Scalaz and Shapeless.
Java very does not natively support newtype, but we can adapt our Scala approach to use a boxed tagger.
Search.java:
class Tagged<A, B> {
public final A value;
public Tagged(final A value) {
this.value = value;
}
public static <A, B> Tagged<A, B> tag(final A value) {
return new Tagged<>(value);
}
}
public class Search {
interface Needle { }
interface Haystack { }
static boolean search(
final Tagged<String, Needle> needle,
final Tagged<String, Haystack> haystack) {
return haystack.value.indexOf(needle.value) != -1;
}
public static void main(String[] args) {
final Tagged<String, Needle> n =
.tag("needle");
Tagged
final Tagged<String, Haystack> h =
.tag("This haystack is nothing but needles!");
Tagged
System.out.println(search(n, h));
}
}
$ scala-cli Search.java
true