Covarianza y tipos de datos

El tema de las opciones en la programación provoca muchas dificultades de comprensión, para mí es un problema que no siempre se toman como explicación metáforas exitosas - contenedores.





Espero poder explicar este tema desde un ángulo diferente usando las metáforas de "asignación" en el contexto de lambdas.





¿Por qué es necesaria esta variación?

En general se puede vivir sin varianza y programar tranquilamente, este no es un tema tan importante, tenemos muchos ejemplos de lenguajes de programación en los que no se refleja esta cualidad.





La covarianza se refiere a los tipos de datos y su control por parte de los compiladores. Y exactamente desde este lugar, debemos retroceder y decir sobre los tipos de datos y por qué los necesitamos.





Flashback a los tipos

Los tipos de datos en sí mismos tampoco son un tema muy importante, hay lenguajes en los que el tipo de datos no es particularmente necesario, por ejemplo, ensamblador, brainfuck, REFAL.





En el mismo REFAL o ensamblador es muy fácil confundir el tipo de una variable, y es muy fácil, por ejemplo, asumir que restaré otra línea de una línea, solo un error tipográfico, sin intenciones maliciosas.





En los lenguajes escritos, el compilador vería este error tipográfico y me impediría compilar el programa, pero ... por ejemplo, JS





> 'str-a' - 'str-b'
NaN
      
      



JS (JavaScript) Calma este código, me dirán que esto no es  un error, es una característica , bueno, digamos, entonces tomaré 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'
      
      



O 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





-  , - ,






-  , . —–





:





  1. N





    • N , : {0, 1, 2, 3, … }





    • N* : {1, 2, 3, … }





  2. Z - (+/-)





  3. Q - ( ), Z





  4. R - ( , e, …)





  5. 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



, :





  1. a b - ,   





  2. a , b - a -  - 





  3. a b, b - () -  -  - .





  4. 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 , , .





- , .





, , .





:





:





  • ( )





    • ( )





      • ( )









  • ( )





    • ( )





      • ( )





, .





- ( ) - .





.





La variación  es, ante todo, la presencia de propiedades / métodos de interés para nuestras tareas. Y este es un mecanismo de control del compilador para garantizar que estas propiedades estén presentes.





Entonces, por ejemplo, este o aquel objeto puede ser no solo algún tipo de debajo de una clase, sino también implementar (a través de interfaces) las propiedades / métodos que nos interesan; esto es lo que quiero decir con la palabra  compatibilidad .





Luego, puede hablar sobre herencia múltiple, rasgos y otros encantos de los lenguajes modernos, pero esto está más allá del alcance del tema.








All Articles