Differencebetween Type Safety and Type Inference

What is the difference between Type Safety and Type Inference?

From Swift's own documentation:

Type Safety

Swift is a type-safe language. A type safe language encourages you to be clear about the types of values your code can work with. If part of your code expects a String, you can’t pass it an Int by mistake.

var welcomeMessage: String
welcomeMessage = 22 // this would create an error because you
//already specified that it's going to be a String

Type Inference

If you don’t specify the type of value you need, Swift uses type inference to work out the appropriate type. Type inference enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.

var meaningOfLife = 42 // meaningOfLife is inferred to be of type Int
meaningOfLife = 55 // it Works, because 55 is an Int

Type Safety & Type Inference together

var meaningOfLife = 42 // 'Type inference' happened here, we didn't specify that this an Int, the compiler itself found out.
meaningOfLife = 55 // it Works, because 55 is an Int
meaningOfLife = "SomeString" // Because of 'Type Safety' ability you will get an
//error message: 'cannot assign value of type 'String' to type 'Int''


Tricky example for protocols with associated types:

Imagine the following protocol

protocol Identifiable {
associatedtype ID
var id: ID { get set }

}

You would adopt it like this:

struct Person: Identifiable {
typealias ID = String
var id: String
}

However you can also adopt it like this:

struct Website: Identifiable {
var id: URL
}

You can remove the typealias. The compiler will still infer the type.

For more see Generics - Associated Types

Thanks to Swift’s type inference, you don’t actually need to declare a
concrete Item of Int as part of the definition of IntStack. Because
IntStack conforms to all of the requirements of the Container
protocol, Swift can infer the appropriate Item to use, simply by
looking at the type of the append(_:) method’s item parameter and the
return type of the subscript. Indeed, if you delete the typealias Item
= Int line from the code above, everything still works, because it’s clear what type should be used for Item.

Type-safety and Generics

Suppose you have the following code:

struct Helper<T: Numeric> {
func adder(_ num1: T, _ num2: T) -> T {
return num1 + num2
}
var num: T
}

T can be anything that's numeric e.g. Int, Double, Int64, etc.

However as soon as you type let h = Helper(num: 10) the compiler will assume that T is an Int. It won't accept Double, Int64, for its adder func anymore. It will only accept Int.

This is again because of type-inference and type-safety.

  • type-inference: because it has to infer that that the generic is of type Int.
  • type-safety: because once the T is set to be of type Int, it will no longer accept Int64, Double...

As you can see in the screenshot the signature is now changed to only accept a parameter of type Int
Sample Image

Pro tip to optimize compiler performance:

The less type inference your code has to do the faster it compiles. Hence it's recommended to avoid collection literals. And the longer a collection gets, the slower its type inference becomes...

not bad

let names = ["John", "Ali", "Jane", " Taika"]

good

let names : [String] = ["John", "Ali", "Jane", " Taika"]

For more see this answer.

Also see Why is Swift compile time so slow?

The solution helped his compilation time go down from 10/15 seconds to a single second.

What is a type inference?

Type inference is a feature of some statically-typed languages. It is done by the compiler to assign types to entities that otherwise lack any type annotations. The compiler effectively just 'fills in' the static type information on behalf of the programmer.

Type inference tends to work more poorly in languages with many implicit coercions and ambiguities, so most type inferenced languages are functional languages with little in the way of coercions, overloading, etc.

Type inference is part of the language specification, for the example the F# spec goes into great detail about the type inference algorithm and rules, as this effectively determines 'what is a legal program'.

Though some (most?) languages support some limited forms of type inference (e.g. 'var' in C#), for the most part people use 'type inference' to refer to languages where the vast majority of types are inferred rather than explicit (e.g. in F#, function and method signatures, in addition to local variables, are typically inferred; contrast to C# where 'var' allows inference of local variables but method declarations require full type information).

How type inference works when assuming collection types in swift

... how swift know the difference between the sets and array ...

["0", "1", "21"]

is an array literal, and the type inference is documented with the ExpressibleByArrayLiteral protocol (emphasis added):

Arrays, sets, and option sets all conform to ExpressibleByArrayLiteral

...

Because Array is the default type for an array literal, without writing any other code, you can declare an array with a particular element type by providing one or more values.

Therefore

let arrayOfStrings = ["0", "1", "21"]     // [String]

declares an array of strings, and an explicit type annotation is needed to declare a set of strings:

let setOfStrings: Set = ["0", "1", "21"]  // Set<String>

Note that Set is sufficient for the type annotation here, the element type String is inferred from the array literal.

Java - What's the difference between type erasure and type inference?

Both of them serve completely different needs:

Type erasure is like you said and is needed because java byte code is not generic hence you need to remove the typing. This isn't a feature that helps you code it's just an automatic compile time change that has to happen for the jvm to understand your code.

Type inference on the other hand is the compiler being "smart" and knowing what type you were referring to even though you didn't actually write it. Just like in your example the compiler knows that Box<>() actually means Box<String>() and lets you continue coding with type safety as if you wrote Box<String>. This way you can write less verbose code and the compiler would still understand it.

You can understand from all of this that generics in Java are actually mostly a compile time thing that lets you code with more safety and helps you find errors in compile-time rather than run-time.

Why isn't java typesafe when inferring array types?

Consider this example:

class A {}
class B extends A {}

class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}

public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}

public static <T> void fArray(T[] a, Generic<? extends T> b) {
List<T> list = new ArrayList<>();
list.add(a[0]);
list.add(b.get());
System.out.println(list);
}
}

As you can see, the signatures used to infer the type parameters are identical, the only thing that's different is that fArray() only reads array elements instead of writing them, making the T -> A inference perfectly justifiable at runtime.

And there is no way for the compiler to tell what your array reference will be used for in the implementation of the method.

Making sense of error message related to type inference when using a method reference

The difference between Character::isAlphabetic and c -> Character.isAlphabetic(c) is that since Character.isAlphabetic(int) is not an overloaded method, the former is an exact method reference whereas the latter is an implicitly typed lambda expression.

We can show that an inexact method reference is accepted the same way as an implicitly typed lambda expression:

class SO71643702 {
public static void main(String[] args) {
String str = "123abc456def";
List<Character> l = str.chars()
.mapToObj(c -> (char) c)
.filter(Predicate.not(SO71643702::isAlphabetic))
.toList();
System.out.println(l);
}

public static boolean isAlphabetic(int codePoint) {
return Character.isAlphabetic(codePoint);
}

public static boolean isAlphabetic(Thread t) {
throw new AssertionError("compiler should never choose this method");
}
}

This is accepted by the compiler.

However, this doesn’t imply that this behavior is correct. Exact method references may help in overload selection where inexact do not, as specified by §15.12.2.:

Certain argument expressions that contain implicitly typed lambda expressions (§15.27.1) or inexact method references (§15.13.1) are ignored by the applicability tests, because their meaning cannot be determined until the invocation's target type is selected.

In contrast, when it comes to the 15.13.2. Type of a Method Reference, there is no difference between exact and inexact method references mentioned. Only the target type determines the actual type of the method reference (assuming that the target type is a functional interface and the method reference is congruent).

Consequently, the following works without problems:

class SO71643702 {
public static void main(String[] args) {
String str = "123abc456def";
List<Character> l = str.chars()
.mapToObj(c -> (char) c)
.filter(Character::isAlphabetic)
.toList();
System.out.println(l);
}
}

Of course, that’s not the original program logic

Here, Character::isAlphabetic still is an exact method reference, but it’s congruent with the target type Predicate<Character>, so it works not different to

Predicate<Character> p = Character::isAlphabetic;

or

Predicate<Character> p = (Character c) -> Character.isAlphabetic(c);

It’s not as if the insertion of a generic method into nesting of method invocations will stop the type inference from working in general. As discussed in this answer to a similar fragile type inference issue, we can insert a generic method not contributing to the resulting type without problems:

class SO71643702 {
static <X> X dummy(X x) { return x; }

public static void main(String[] args) {
String str = "123abc456def";
List<Character> l = str.chars()
.mapToObj(c -> (char) c)
.filter(dummy(Character::isAlphabetic))
.toList();
System.out.println(l);
}
}

and even “fix” the problem of the original code by inserting the method

class SO71643702 {
static <X> X dummy(X x) { return x; }

public static void main(String[] args) {
String str = "123abc456def";
List<Character> l = str.chars()
.mapToObj(c -> (char) c)
.filter(Predicate.not(dummy(Character::isAlphabetic)))
.toList();
System.out.println(l);
}
}

It’s important that there is no subtype relationship between Predicate<Character> and Predicate<Integer>, so the dummy method can not translate between them. It’s just returning exactly the same type as the compiler inferred for its argument.

I consider the compiler error a bug, but as I said at the other answer, even if the specification backs up this behavior, it should get corrected, in my opinion.


As a side note, for this specific example, I’d use

var l = str.chars()
.filter(c -> !Character.isAlphabetic(c))
.mapToObj(c -> (char)c)
.toList();

anyway, as this way, you’re not boxing int values to Character objects, just to unbox them to int again in the predicate, but rather, only box values after passing the filter.

Compile-time type inference from a limited number of permutations of types

You need some trait to express this relationship. There are multiple ways to implement it, but I would implement it on two element tuples like so:

pub trait MatrixPermutation {
type Result;
}

impl MatrixPermutation for (Variable, Variable) {
type Result = Variable;
}
impl MatrixPermutation for (Variable, Constant) {
type Result = Variable;
}
impl MatrixPermutation for (Constant, Variable) {
type Result = Variable;
}
impl MatrixPermutation for (Constant, Constant) {
type Result = Constant;
}

This way, you can define do_operation() like:

fn do_operation<O>(&self, other: &Matrix<O>) -> Matrix<<(T, O) as MatrixPermutation>::Result>
where
(T, O): MatrixPermutation,
{
// ...
}

If you need access to specialized methods in the body of do_operation, you can create them on MatrixPermutation too.



Related Topics



Leave a reply



Submit