24 joulukuuta, 2014

Todellinen datatyyppi

Muuttujan arvon datatyyppi

Ongelma ja ratkaisu

Kuinka voi selvittää mikä muuttujan arvon todellinen datatyyppi, riippumatta siitä, miten se on muodostettu? Normaalisti tämä on ihmeellisen hankalaa JavaScriptissä, sillä esimerkiksi numeron voi luoda kahdella tavalla: 

var n = 2; 
var N = new Number(2);

Molemmat ovat kakkosia ja molemmilla voi laskea aivan normaalisti, mutta jos niitä vertaa keskenään ne ovatkin ihan eri asioita:

if ( n === N ) { // jne

Saa vastaukseksi false. Miksi? Koska toinen on primitiivi ja toinen on objekti. 

Yksi ratkaisu olisi tietty käyttää löysempää vertailua, joka kääntää molemmat arvot samaan tyyppiin ennen vertailua, mutta silloin samalla luo potentiaalisesti vaarallisen tilanteen, jossa saman numeroarvon omaavaa muuttujaa ovat aidosti erilaisia datatyypiltään. 

if ( n == N ) { // jne

Sama ongelma käänteisenä on Objektin ja Arrayn välillä: jos kysyt typeof sanalla:

var MyArr = [];
var MyObj = {};
if ( ( typeof MyArr ) === ( typeof MyObj) ) { // true !

Molemmat ovat tyyppiä object, vaikka ne pitäisi nimenomaan pystyä helposti erottamaan toisistaan. Ja samaa tyyppiä ovat myös Date, RegExp ja Math jne...

Hämmentävä perusongelma JavaScriptissä on syntynyt pitkän historian aiheuttamana, mutta ratkaisukin on kuitenkin jo olemassa. Ongelma pitää vain tiedostaa: Toisaalta pitäisi erotella sen mukaan mitä datatyyppiä on sisällä, ja unohtaa ulkoinen objektin tyyppi, toisaalta yleensä eri objektityypit (ainakin Array ja Object ) pitäisi erottaa toisistaan selkeästi. Mitä tehdä? 

Vastaan: Pitää luoda funktio, joka kertoo datan oikean tyypin.

Ratkaisu on realDataType -funktio

Se yksinkertaisesti kertoo mitä dataa sille syötetään. Ensin switch tarkistaa, ettei arvo ole jokin tyhjistä (null tai undefined) , sitten katsotaan vain tyypin prototyyppi, ja halutessa (asettamalla toinen parametri true -asentoon) tarkistetaan Number -primitiivi vielä tarkemmin sen mukaan onko kyseessä kokonaisluku vai liukuluku. 

Lisäksi samalla voidaan tarkistaa onko luku käypä numero. Jos arvo on NaN tai se on liian suuri tai pieni, tyypiksi tulee jotain muuta kuin Number, mikä mahdollistaa helpon testin. Tämä on kuitenkin tehty optioksi, jonka saa halutessaan käyttöön true -lisäparametrilla.

var realDataType = function (val, separateNumbers) { /* 'real' datatypes */
 var sn = ( separateNumbers || false); 
 switch (val){
  case undefined: return "undefined";
  break;
  case null: return "null";
  break;
 default:  
var proto=Object.prototype.toString.call(val).match(/^\[object (.+)\]$/)[1];
  if("String" === proto) {
return (/^(0x|0X|#)[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(val))?"Hex":"String"; 
  } // string
  if("Number" === proto){
   if (isNaN(val)) return "NaN"; // epäluku
   if (isFinite(val) || (val < Number.MAX_VALUE && val > -Number.MAX_VALUE)) { //"ok";
    if (sn) {
      return (val % 1 === 0) ? "Integer" : "Float"; 
    }else{
      return "Number";
    }
   } else { // too big or small:
    if ( val > Number.MAX_VALUE) {
     return "Infinity";
    } else if (val < -Number.MAX_VALUE){
     return "-Infinity";
    };
   };
  }else {
    return proto;
  }
 }; //switch end
};

alert( realDataType( Number.MAX_VALUE )); // Number
alert( realDataType( Number.MAX_VALUE, true )); // Integer
alert( realDataType( Number.MAX_VALUE * 2 )); // Infinity
alert( realDataType( "#00FF00" )); // Hex
alert( realDataType( /[a-zA-Z]/ )); // RegExp

Ohjelmoitu nörtin joululahja

Samalla logiikalla lisäsin vielä merkkijonon tunnistamisen hexadesimaaliksi.
Suosittelen: ota tästä kätevän ilmainen työkalu ohjelmoijan pakkiin. Jos tunnet jonakin päivänä olevasi kiitollisuudenvelassa, ota yhteyttä, lähetän tilinumeroni. Ei, olkoon tämä minun joululahjani koko ohjelmoivalle maailmalle.

Olen itse surfannut vuosien varrella melkoisen paljon ilmaisia ohjeita läpi, päätyäkseni tähän ratkaisuun ja muillakin vastaavia ratkaisuja on olemassa jo valmiina, mutta niissä ei sama funktio tutki esim. numeroita näin pitkälle.
Joten olkaapa hyvä ja mukavaa uutta vuotta!
On siinä varmasti vielä kehittämisen varaa, jos keksit, kerro minullekin. 

Float vai Real?

Oli vaikea päättää tätä: Float tarkoittaa liukuluku, oikeastaan se on kuitenkin Real eli reaaliluku, mutta onko se sitten amerikkalaista kulttuuria vai mitä, mutta tuntuu tutummalta käyttää tätä, en tiedä, voin olla väärässäkin.

Tulevaisuuden perusta on tässä

Funktio toimii todennäköisesti myös kaikkiin tulevaisuudessa kehitettäviin uusiin datamuotoihin. Tässä esimerkki, jota en vielä kehitellessä hoksannut ottaa testattavaksi, mutta sieltähän se datatyyppi tulla tupsahtaa. Arguments ei siis ole tyypiltään Array. Vaikka se osittain osaakin samoja temppuja, ei kuitenkaan kaikkia.

var b = function (a){
alert(realDataType(arguments)); // Arguments
}();