The topic of options in programming causes a lot of difficulties in understanding, for me it is a problem that not always successful metaphors are taken as an explanation - containers.
I hope that I can be able to explain this topic from a different angle using the metaphors of "assignment" in the context of lambdas.
Why is this variance needed at all?
In general, you can live without variance and program peacefully, this is not such an important topic, we have many examples of programming languages ββin which this quality is not reflected.
Co-variance is about data types and their control by compilers. And exactly from this place we need to roll back and say about data types and why we need it.
Flashback to types
Data types by themselves are also not a very important topic, there are languages ββin which the data type is not particularly needed, for example, assembler, brainfuck, REFAL.
In the same REFAL or assembler it is very easy to confuse the type of a variable, and it is very easy, for example, to assume that I will subtract another line from one line, just a typo, no malicious intent.
In typed languages, the compiler would see this typo and prevent me from compiling the program, but ... for example JS
> 'str-a' - 'str-b'
NaN
JS (JavaScript) Calm down this code, they will tell me that this is not a bug, this is a feature , ok, let's say, then I'll take Python
>>> 'str-a' - 'str-b'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'
Or Java
jshell> "str-a" - "str-b"
| Error:
| bad operand types for binary operator '-'
| first type: java.lang.String
| second type: java.lang.String
| "str-a" - "str-b"
| ^---------------^
, - .
, .
, , , , .
: , Groovy
groovy> def fun1( a, b ){
groovy> return a - b
groovy> }
groovy> println 'fun1( 5, 2 )='+fun1( 5, 2 )
groovy> println "fun1( 'aabc', 'b' )="+fun1( 'aabc', 'b' )
groovy> println 'fun1( [1,2,3,4], [2,3] )='+fun1( [1,2,3,4], [2,3] )
fun1( 5, 2 )=3
fun1( 'aabc', 'b' )=aac
fun1( [1,2,3,4], [2,3] )=[1, 4]
JS
> fun1 = function( a, b ){ return a - b }
[Function: fun1]
> fun1( 5, 2 )
3
> fun1( 'aabc', 'b' )
NaN
> fun1( [1,2,3,4], [2,3] )
NaN
.
, , - , .
/ - .
, .
- .
TypeScript
function sub( x : number, y : number ) {
return x - y;
}
console.log( sub(5,3) )
JS.
function sub( x : number, y : number ) {
return x - y;
}
console.log( sub("aa","bb") )
- :
> tsc ./index.ts
index.ts:5:18 - error TS2345: Argument of type 'string' is not assignable
to parameter of type 'number'.
5 console.log( sub("aa","bb") )
~~~~
Found 1 error.
sub
, , number
.
TypeScript (tsc
).
,
Μ β () , .
A β G β A A. f A B G, a β A g β G f(a)=f(g(a)).
, :
- , .
, JS
> fun1 = function( a, b, c ){
... let r = b;
... if( a ) r = c;
... return r + r;
... }
[Function: fun1]
> fun1( 1==1, 2, 3 )
6
> fun1( 1==1, "aa", "b" )
'bb'
> fun1( 1==1, 3, "b" )
'bb'
> fun1( 1!=1, 3, "b" )
6
> fun1( 1!=1, {x:1}, "b" )
'[object Object][object Object]'
r - string number , fun1 , .
r. r .
r :
let r = b
, r , b.
r = c
, r , c.
, .
, :
> fun1 = function( a, b, c ){
... if( typeof(b)!=='number' )throw "argument b not number";
... if( typeof(c)!=='number' )throw "argument c not number";
... let r = b;
... if( a ) r = c;
... return r + r;
... }
[Function: fun1]
> fun1( true, 1, 2 )
4
> fun1( true, 'aa', 3 )
Thrown: 'argument b not number'
, , , .
, +, - β¦ - - ( ), - .
let r = b
r = c
, .
Typescript:
function fun1( a:boolean, b:number, c:number ){
let r = b;
if( a ) r = c;
return r + r;
}
function fun2( a:boolean, b:number, c:string ){
let r = b;
if( a ) r = c;
return r + r;
}
> tsc ./index.ts
index.ts:9:13 - error TS2322: Type 'string' is not assignable to type 'number'.
9 if( a ) r = c;
~
Found 1 error.
, string
number
.
- , .
- , ( ) .
: f(a)=f(g(a))
TypeScript:
function f(a:number) : number {
return a+a;
}
function g(a:number) : number {
return a;
}
console.log( f(1)===f(g(1)) )
- .
- , , ..
function f(a:number) : number {
return a+a;
}
function g(a:number) : number {
return a-1;
}
let r = f(1)
r = f(g(1))
function f(a:number) : number {
return a+a;
}
function g(a:number) : string {
return (a-1) + "";
}
let r = f(1)
r = f(g(1))
( ), :
g string
f number
TypeScript.
, // - , / .
-
- -, TypeScript, - Scala, .
, Solid
- , - ,
- , . ββ
:
N
N , : {0, 1, 2, 3, β¦ }
N* : {1, 2, 3, β¦ }
Z - (+/-)
Q - ( ), Z
R - ( , e, β¦)
C - a+bi, a,b - , i -
:
any -
number -
int -
double - ()
string -
TypeScript
function sum_of_int( a:int, b:int ) : int { return a+b; }
function sum_of_double( a:double, b:double ) : double { return a+b; }
function compare_equals( a:number, b:number ) : boolean { a==b }
let res1 : int = sum_of_int( 1, 2 )
, .. - int, int.
-
let res1 : number = sum_of_int( 1, 2 )
res1 = sum_of_double( 1.2, 2.3 )
res1 - number.
res1 = sum_of_int( 1, 2 ), res1 int, , .. int number number
res1 = sum_of_double( 1.2, 2.3 ) - res1 double ,
? , , .. res1:
let res1 : number = sum_of_int( 1, 2 )
let res2 : number = sum_of_doube( 1.2, 2.3 )
if( compare_equals(res1, res2) ){
...
}
, , , ββ
: Box Circle
class Box {
width : number
height : number
constructor( w: number, h: number ){
this.width = w;
this.height = h;
}
}
class Circle {
radius : number
constructor( r: number ){
this.radius = r
}
}
, ,
let boxs : Box[] = [ new Box(1,1), new Box(2,2) ]
let circles : Circle[] = [ new Circle(1), new Circle(2) ]
2 , ,
function areaOfBox( shape:Box ):number { return shape.width * shape.height }
function areaOfCircle( shape:Circle ):number { return shape.radius * shape.radius * Math.PI }
:
boxs.map( areaOfBox ).reduce( (a,b,idx,arr)=>a+b ) +
circles.map( areaOfCircle ).reduce( (a,b,idx,arr)=>a+b )
, / (, ).
, - , , - .
/ - area():number.
interface Shape { area():number }
, Box Circle Shape, areaOfBox, areaOfCircle area.
class Box implements Shape {
width : number
height : number
constructor( w: number, h: number ){
this.width = w;
this.height = h;
}
area():number {
return this.width * this.height
}
}
class Circle implements Shape {
radius : number
constructor( r: number ){
this.radius = r
}
area():number {
return this.radius * this.radius * Math.PI
}
}
,
let shapes : Shape[] = [ new Box(1,1), new Box(2,2), new Circle(1), new Circle(2) ]
shapes.map( s => s.area() ).reduce( (a,b,idx,arr)=>a+b )
, -
Shape, (.. ) (Box, Circle).
, Box Circle Shape.
, ..
let a = b
, :
a b - ,
a , b - a - -
a b, b - () - - - .
a b - - .
, Shape
class Foo {
}
let shapes : Shape[] = [ new Box(1,1), new Box(2,2), new Circle(1), new Circle(2), new Foo() ]
shapes.map( s => s.area() ).reduce( (a,b,idx,arr)=>a+b )
- :
> tsc index.ts
index.ts:31:84 - error TS2741: Property 'area' is missing in type 'Foo' but required in type 'Shape'.
31 let shapes : Shape[] = [ new Box(1,1), new Box(2,2), new Circle(1), new Circle(2), new Foo() ]
~~~~~~~~~
index.ts:2:5
2 area():number
~~~~~~~~~~~~~
'area' is declared here.
Found 1 error.
Foo area, Shape.
SOLID
L - LSP - (Liskov substitution principle): Β« Β». . .
-
-, , , .
, Scala :
package xyz.cofe.sample.inv
object App {
// , String, Boolean, : (String)=>Boolean
def strCmp(a:String):Boolean = a.contains("1")
// , Int, Boolean, : (Int)=>Boolean
def intCmp(a:Int):Boolean = a==1
// , String, Boolean, : (Any)=>Boolean
def anyCmp(a:Any):Boolean = true
def main(args:Array[String]):Unit = {
// Boolean = Boolean
val call1 : Boolean = strCmp("a")
// - Any = Boolean
val call2 : Any = strCmp("b")
// : (String)=>Boolean = (String)=>Boolean
val cmp1 : (String)=>Boolean = App.strCmp;
// - (String)=>Boolean = (Any)=>Boolean
val cmp2 : (String)=>Boolean = App.anyCmp
// : (String)=>Boolean = (String)=>Boolean
val cmp3 : (Any)=>Boolean = App.anyCmp
// !!!!!!!!!!!!!!!!!!!!!!!
//
// - (Any)=>Boolean = (String)=>Boolean
val cmp4 : (Any)=>Boolean = App.strCmp
}
}
Scala:
Any
-
Int, Boolean, String
-Any
,
:
(_,_)=>_
= .
/= .
val
Scala,const
JS
:
// Boolean = Boolean
val call1 : Boolean = strCmp("a")
// : (String)=>Boolean = (String)=>Boolean
val cmp1 : (String)=>Boolean = App.strCmp;
cmp1 - , , :
(String)=>Boolean (String)=>Boolean
-
// - Any = Boolean
val call2 : Any = strCmp("b")
// - (String)=>Boolean = (Any)=>Boolean
val cmp2 : (String)=>Boolean = App.anyCmp
call2, , cmp2.
(String) => Boolean (Any) => Boolean
String -> -> Any - -.
, WTF? - !
// , String, Boolean, : (String)=>Boolean
def strCmp(a:String):Boolean = a.contains("1")
// , String, Boolean, : (Any)=>Boolean
def anyCmp(a:Any):Boolean = true
cmp2( "abc" )
"abc"
anyCmp(a:Any)
, String Any, .
anyCmp( "string" )
anyCmp( 1 )
, anyCmp( true )
- ,
,
()
.. , - , -.
:
-
assign a <- b
- -
call a -> b
, :
-,
-,
Scala, TypeScript
TypeScript 4.2.4 - /
interface Shape {
area():number
}
class Box implements Shape {
width : number
height : number
constructor( w: number, h: number ){
this.width = w;
this.height = h;
}
area():number {
return this.width * this.height
}
}
class Circle implements Shape {
radius : number
constructor( r: number ){
this.radius = r
}
area():number {
return this.radius * this.radius * Math.PI
}
}
class Foo {
}
const f1 : (number)=> boolean = a => true;
const f2 : (object)=> boolean = a => typeof(a)=='function';
const f3 : (any)=>boolean = f1;
const f4 : (number)=>boolean = f3;
const _f1 : (Box)=>boolean = a => true
const _f2 : (any)=>boolean = _f1
const _f3 : (Shape)=>boolean = _f1
const f3 : (any)=>boolean = f1;
const _f3 : (Shape)=>boolean = _f1
( ) ,
user@user-Modern-14-A10RB:03:14:17:~/code/blog/itdocs/code-skill/types: > ./node_modules/.bin/tsc -version Version 4.2.4 user@user-Modern-14-A10RB:03:16:53:~/code/blog/itdocs/code-skill/types: > ./node_modules/.bin/tsc --strictFunctionTypes index.ts user@user-Modern-14-A10RB:03:18:26:~/code/blog/itdocs/code-skill/types: > ./node_modules/.bin/tsc --alwaysStrict index.ts user@user-Modern-14-A10RB:03:19:04:~/code/blog/itdocs/code-skill/types:
, .
-/-
.
!
, , .
- - , .
- - .
C , JS , , .
- , .
, , .
:
:
( )
( )
( )
( )
( )
( )
, .
- ( ) - .
.
Variation is, first of all, the presence of properties / methods of interest for our tasks. And this is a compiler control mechanism to ensure that these properties are present.
So, for example, this or that object can be not only some kind of under a class, but also implement (through interfaces) the properties / methods of interest to us - this is what I mean by the word compatibility .
Then you can talk about multiple inheritance, traits, and other charms of modern languages, but this is beyond the scope of the topic.