PodstawyProgramowanieTypy danych
#javascript#typy danych#logika#boolean#podstawy
Opublikowany 6.06.2024
Logika... dla niektórych koszmar senny, dla innych jedyna zrozumiała dziedzina matematyki.
Z wartościami logicznymi spotkaliśmy się już w każdej części serii o typach danych w JavaSripcie. Najwyższy czas przyjrzeć się im bliżej.
Logika w programowaniu
Niczym odkrywczym nie będzie, jeśli powiem, że logika stoi u podstaw wszystkiego, co związane z komputerem. Wszelkie, nawet najbardziej zaawansowane działania finalnie sprowadzają się do prostego 0 lub 1. Ale nie będziemy teraz rozważać sposobu działania procesora - choćby dlatego, że sam nie mam zbyt wielkiej wiedzy na ten temat.
Choć w JSie nie skupiamy się na elementarnych problemach, to typ boolean będzie nam towarzyszył niemal na każdym kroku.
Zmienna boolean może przyjmować tylko jedną z dwóch wartości: true (prawda) lub false (fałsz). Wydaje się, że to okrutnie mało, zwłaszcza porównując do nieskończonych kombinacji, jakie oferują string, number czy BigInt. Ale te dwie wartości dają nam prawdziwy ogrom możliwości.
Do czego nam logika?
No dobrze, ale jak niby typ mogący przyjąć zaledwie dwie wartości ma takie znaczenie? Cóż... prawda jest taka, że bez logiki bylibyśmy w stanie co najwyżej wyświetlać tekst i wykonywać najprostsze działania. Logika otwiera cały świat warunków. Warunków, które pozwalają wymusić konkretne zachowanie, w zależności od zaistniałego zdarzenia. Pozwala obsługiwać błędy. Generalnie - bez logiki ciężko mówić o programowaniu czegokolwiek.
Jeśli śledzicie serię od początku, to poniższy przykład będzie wam znany:
const isLoggedIn = true;
const userName = 'Marek';
const greeting = `Witaj, ${isLoggedIn ? username : 'Gościu'}!`; // 'Witaj, Marek!
Czas najwyższy wyjaśnić sobie, co tu się stało.
W pierwszej linijce mamy zmienną isLoggedIn będącego typu boolean. Najważniejsze dzieje się w linijce czwartej, gdzie tworzymy zmienną greeting.
Wewnątrz template literals sprawdzamy, czy isLoggedIn ma wartość true. Jeśli tak, to zwracana jest wartość zmiennej username. W innym przypadku otrzymujemy wartość Gościu.
Zdaje sobie sprawę, że zapis może być niejasny - ale do samej składni przejdziemy później.
Mam nadzieję, że ten prosty przykład użycia zmiennych typu boolean jest czytelny. Mamy wartość, sprawdzamy, czy jest prawdziwa. Jeśli tak - wykonujemy krok A, jeśli nie, wykonujemy krok B.
Innym z podstawowych zastosowań danych typu boolean są pętle (póki co nie skupiamy się na idei samych pętli, więc jak nie macie o nich pojęcia, to nic nie szkodzi):
for (let i = 0; i <= 10; i++) {
console.log(2 * i);
}
Po kolei. Tworzymy zmienną i, którą w każdym kroku wyświetlamy w konsoli pomnożoną przez 2. Po każdym kroku sprawdzany jest warunek i <= 10. Jeśli warunek nie jest spełniony, to wykonujemy krok. W innym wypadku (czyli przy spełnieniu warunku) kończymy działanie pętli.
Jak wspominałem, wartości typu boolean można wykorzystać do obsługi błędów. Załóżmy, że mamy funkcje, która przyjmuje liczbę. Zastanówmy się, co się stanie, jeśli omyłkowo przekażemy do niej string.
function sub2(a){
return a - 2;
}
sub2(3); //1
sub2('p'); //NaN
Czy można z tym coś zrobić? Tak! Wystarczy użyć warunku:
function sub2(a){
if (Number(a) === NaN) {
return 'Input value is not a number!';
}
return a - 2;
}
sub2(3); //1
sub2('p'); //'Input value is not a number!'
Komunikat o błędzie można wówczas wyświetlić użytkownikowi, który dzięki temu będzie miał jasną informację, co jest przyczyną dziwnego wyniku. Obsługa błędów jest szczególnie ważna, bo nierzadko ratuje nas przed całkowitym wysypaniem się aplikacji.
Czy jest boolean?
Bezpośrednia odpowiedź na to pytanie padła już wielokrotnie - boolean jest daną logiczną, która może przyjąć wartość true lub false. To teraz przełóżmy to na polski.
Posłużmy się na chwilę tym, co wiemy z matematyki. Królowa nauk daje nam narzędzie w postaci zdania logicznego. Posłużmy się na chwilę Wikipedią:
Zdanie logiczne jest zdaniem oznajmującym, któremu można przypisać jedną z wartości logicznych. W logikach dwuwartościowych są nimi prawda albo fałsz. Ponieważ język logiki i matematyki znacznie różnią się od języków naturalnych, można modyfikowa ć określenie podane w poprzednim zdaniu tak, aby dopasować je do wymogów języków formalnych. I tak można określać zdanie logiczne jako wyrażenie (niekoniecznie o skończonej długości), złożone z symboli danego języka połączonych relacjami iloczynu logicznego, sumy logicznej i negacji, któremu można (przynajmniej teoretycznie) podporządkować jedną z wartości logicznych.
W skrócie - zdanie logiczne to zdanie oznajmujące, które można jednoznacznie określić jako prawdziwe lub fałszywe. W JavaScripcie funkcję zdania logicznego pełni wyrażenie lub zmienna, która określa coś, czego prawdziwość możemy jednoznacznie określić.
Instrukcje warunkowe
Terminem, który będzie się w kontekście typu boolean pojawiał bardzo często jest instrukcja warunkowa. Warto w końcu wyjaśnić sobie czym ona jest.
Nazwa jest tutaj bardzo czytelna - instrukcja warunkowa jest dokładnie tym, co sugeruje nazwa. Jest wyrażeniem, które instruuje co należy zrobić przy zaistnieniu konkretnych warunków.
Podczas pracy z JavaScriptem najczęściej będziemy się spotykać instrukcją if (oraz if ... else). Istnieje też instrukcja switch - używana decydowanie rzadziej, choć w niektórych sytuacjach nieoceniona.
if oraz if ... else
Instrukcja if sprawdza podany jej warunek i wykonuje się tylko w sytuacji, jeśli jest on spełniony. Jeżeli w sytuacji niespełnienia warunku wykonać alternatywne działanie, to do tego służy if .. else.
let result = 0;
if (Number(input) {
result = Number(input) * 2;
}
W powyższym przykładzie sprawdzamy, czy zmienna input może być konwertowana do typu number. Jeśli tak, to wykonujemy mnożenie. W innym przypadku nie wykonujemy żadnego działania. Gdybyśmy chcieli wyświetlić użytkownikowi komunikat o błędnych danych, wyglądałoby to tak:
let result = 0;
if (Number(input) {
result = Number(input) * 2;
} else {
console.log('Input value is not a number');
}
Czyli - jeśli warunek jest spełniony, to wykonujemy działanie. Jeśli nie, informujemy użytkownika o problemie.
else if
Warto zauważyć, że JavaScript nie posiada słowa kluczowego elseif, znanego choćby z PHP. Nie oznacza to jednak, że możemy rozpatrywać zaledwie dwa scenariusze. Instrukcje if .. else można ze sobą łączyć, dzięki czemu uszykujemy efekt tożsamy z użyciem elseif.
const today = new Date();
if (today.getDay() = 0) {
console.log('Today is Sunday');
} else if (today.getDay() = 6) {
console.log('Today is Saturday);
} else {
console.log('Still waiting for a weekend...');
}
W powyższym sprawdzamy, że metoda getDay() zwraca wartość 0. Jeśli tak, to zwracamy komunikat. W przeciwnym razie sprawdzamy kolejny warunek - czy metoda getDay() zwraca wartość 6. Jeśli dopiero kiedy i ten warunek jest fałszywy, to zwracamy domyślny komunikat.
Ktoś może słusznie zauważyć, że składnia else if niczym nie różni się od składni elseif. Tylko, że to tylko pozory. Tak naprawdę w JavaSripcie mamy do czynienia z zagnieżdżeniem instrukcji. Powyższy przykład można zapisać również następująco:
const today = new Date();
if (today.getDay() = 0) {
console.log('Today is Sunday');
} else {
if (today.getDay() = 6) {
console.log('Today is Saturday);
} else {
console.log('Still waiting for a weekend...');
}
}
Skrócona wersja zapisu nie pokazuje tak dokładnie, że tak naprawdę wykonując instrukcję pod pierwszym else tworzymy nową instrukcję warunkową if .. else. I pomimo, że w skróconym zapisie else if jest łudząco podobne do elseif z innych języków, to sposób jego działania jest już inny.
Pozostając jeszcze w przykładzie z dniami tygodnia. Wyobraźmy sobie, że chcemy wypisać wszystkie dni, nie tylko sobotę i niedzielę.
const today = new Date();
if (today.getDay() = 0) {
console.log('Today is Sunday');
} else if (today.getDay() = 1) {
console.log('Today is Monday);
} else if (today.getDay() = 2) {
console.log('Today is Tuesday');
} else if (today.getDay() = 3) {
console.log('Today is Wednesday');
} else if (today.getDay() = 4) {
console.log('Today is Thursday');
} else if (today.getDay() = 5) {
console.log('Today is Friday');
} else if (today.getDay() = 6) {
console.log('Today is Saturday');
}
Powyższy zapis jest jak najbardziej poprawny, jednak można go nieco uprościć.
switch
Przykład z dniami tygodnia jest idealny do zastosowania instrukcji switch. Przyjmuje ona zmienną i porównuje do następujących po sobie warunków. Wygląda to następująco:
switch (expr) {
case 'Case1':
console.log('Case 1');
break;
case 'Case2':
console.log('Case 2');
break;
case 'Case3':
cosole.log('Case 3');
break;
default:
console.log('Somethig else');
}
Przeanalizujmy powyższy przykład.
Podajemy do instrukcji switch zamienną expr. Następnie sprawdzamy, czy jest ona równa Case1 (case: 'Case1'). Jeśli tak, to zwracamy komunikat oraz kończymy działanie instrukcji (w tym przypadku przerywamy ją za pomocą słowa kluczowego break). Jeśli jednak expr nie jest równe Case1, to przechodzimy do kolejnego przypadku i sprawdzamy, czy jest równe Case2. I tak dalej. Jeśli expr nie spełnia żadnego z założonych warunków, wówczas wykonujemy działanie z warunku default (czyli zachowanie domyślne).
Wróćmy zatem do dni tygodnia.
const today = new Date();
switch (today.getDay()) {
case 0:
console.log('Today is Sunday');
break;
case 1:
console.log('Today is Monday);
break;
case 2:
console.log('Today is Tuesday');
break;
case 3:
console.log('Today is Wednesday');
break;
case 4:
console.log('Today is Thursday');
break;
case 5:
console.log('Today is Friday');
break;
case 6:
console.log('Today is Saturday');
break;
default:
console.log('Invalid date format');
}
Operacje logiczne
JS oferuje nam kilka operatorów logicznych, które pozwalają nam uzyskać wartość logiczną.
Operatory porównania
Było o tym wspomniane przy okazji porównywania stringów. Jednak z uwagi na to, jak są one istotne - i jak częstą będziemy się z nimi spotykali - warto je w tym miejscu przypomnieć.
- == (równe),
- === (równe ze sprawdzeniem typu),
- != (różne),
- !== (różne ze sprawdzeniem typu),
- > (większe),
- >= (większe lub równe),
- < (mniejsze),
- <= (mniejsze lub równe).
Wynikiem operacji porównania będzie typ boolean.
const number = 2;
const string = '2';
const strictEqual = number === string; //false
const equal = number == string; //true
const strictEqualConvert = number === Number(string);//true
typeof strictEqual; //'boolean'
typeof equal; //'boolean'
typeof strictEqualConvert; //'boolean'
2 == '2'; //true
2 === '2';//false
2 != '2'; //false
2 !== '2'; //true
2 > 2; //false
2 >= 2; //true
2 < 2; //false
2 <= 2; //false
Operatory logiczne
Operatory logiczne pozwalają łączyć warunki. Do dyspozycji mamy negację (NOT), koniunkcję (AND) oraz alternatywę (OR). Przejdźmy szybko do przykładów, które zaraz sobie szczegółowo omówimy.
Koniunkcja (AND)
true && true; // true
true && false; // false
false && true; // false
false && false; // false
//zastosowanie
if (isLogIn && isAdmin) {
console.log('Hello, mr. Administrator!');
}
Operator AND zwraca wartość true w sytuacji, kiedy oba wyrażenia są prawdziwe. Jeśli wyrażenia nie są wyrażeniami logicznymi, wówczas będą do nich przekonwertowane.
Alternatywa (OR)
true || true; // true
false || true; // true
true || false; // true
false && false; // false
//zastosowanie
if (today === 0 || today === 6) {
console.log('Weekend!!!');
}
Operator OR zwraca wartość true jeśli przynajmniej jedno z wyrażeń jest prawdziwe. Podobnie jak przy koniunkcji wartości typu innego niż boolean będą przekonwertowane.
Negacja (NOT)
!true; // false
!false; //true
//zastosowanie
if (!isLogIn) {
console.log('You need to log in');
}
Operator NOT zwraca wartość odwrotną do początkowej.
Łączenie operatorów
Operatory logiczne można ze sobą łączyć. Należy jednak pamiętać o kolejności ich wykonywania!
//błąd kolejności działań:
3 > 2 || 14 < 8 && 1 === 5; //true
//poprawna forma:
(3 > 2 || 14 < 8) && 1 === 5; //false
Ponieważ operator OR zwraca wartość true, gdy przynajmniej jeden warunek jest spełniony, to po sprawdzeniu pierwszego (3 > 2) JS zwrócił true bez sprawdzania kolejnych. Dopiero zamknięcie pierwszego OR w nawiasie pozwoliło przejść do sprawdzenia warunku przy operatorze AND.
Z operatorów logicznych można też korzystać jako ze skróconej formy instrukcji warunkowej.
Napisałem, że w sytuacji, kiedy wynik wyrażeń nie jest typu boolean - jest on konwertowany na wartość logiczną. Co do zasady tak. Jednak jeśli nie oczekujemy od wyniku działania z operatorem logicznym bycia takżę booleanem (jak w powyższych przykładach), to zaczyna być ciekawie.
Operatory zamiast instrukcji warunkowych
Korzystając z operatorów logicznych AND oraz OR możemy uniknąć tworzenia klasycznych instrukcji warunkowych. Sięgnijmy na chwilę po definicję AND i OR ze znanego i lubianego MDN:
[AND - przyp.] Returns expr1 if it can be converted to false; otherwise, returns expr2. Thus, when used with Boolean values, && returns true if both operands are true; otherwise, returns false. (Zwraca wyrażenie1, jeśli można je przekonwertować na fałsz; w przeciwnym razie zwraca wyrażenie2. Tak więc, gdy jest używane z wartościami logicznymi, && zwraca true, jeśli oba operandy są prawdziwe; w przeciwnym razie zwraca false.)
[OR - przyp.] Returns expr1 if it can be converted to true; otherwise, returns expr2. Thus, when used with Boolean values, || returns true if either operand is true; if both are false, returns false. Zwraca wyrażenie1, jeśli można je przekonwertować na wartość true; w przeciwnym razie zwraca wyrażenie2. Tak więc, w przypadku użycia z wartościami logicznymi, || zwraca true, jeśli którykolwiek z operandów jest prawdziwy; jeśli oba są fałszywe, zwraca false.)
To, na co warto zwrócić w tych definicja uwagę, to wartość zwracana przez operatory. Mianowicie oba operatory zwracają wyrażenie. Nie zwracają wartości logicznej, ale właśnie wyrażenie! Co to dla nas oznacza? Zobaczmy to na przykładach:
'Ala' && 'Ola'; // Ola
false && 'Ala'; // false
'Ala' && false; // false
'Ala' && true; // true
true && 'Ala'; // Ala
'Ala' || 'Ola'; // Ala
false || 'Ala'; // Ala
'Ala' || false; // Ala
'Ala' || true; // Ala
true || 'Ala'; // true
Zastosowanie tej właściwości operatorów logicznych potrafi niekiedy bardzo uprościć nasz kod:
//z użyciem instrukcji warunkowej
if (username) {
return `Hello, ${username}!`;
} else {
return `Hello, Guest!`;
}
//z zastosowaniem operatorów logicznych:
`Hello, ${username || 'Guest'}!`;
Co tu się wydarzyło? W pierwszym przykładzie za pomocą instrukcji if sprawdzamy, czy istnieje zmienna username. Jeśli tak, to zwracamy spersonalizowane powitanie. W innym przypadku witamy gościa.
W drugim przypadku w zasadzie robimy dokładnie to samo. Interpreter dochodzi do operatora OR. Wówczas sprawdza, czy wyrażenie po jego prawej stronie wynosi true. Jeśli tak (w tym przypadku zmienna będzie miała wartość true jeśli będzie istniała - ale to wyjaśnimy za chwilę). Zatem jeśli username wynosi true lub może być do true przekonwertowany, to zwracana jest wartość zmiennej username. W przeciwnym razie zwracana jest wartość Guest.
Truthy i falsy
Przed chwilą napisałem, że użyta w przykładzie zmienna username po konwersji na boolean wyniesie true zawsze, jak będzie istniała (uwaga! tak naprawdę nie zawsze: jeśli zmienna username będzie istniała, nadal może być fałszywa - o czym za chwilę). Dlaczego tak się dzieje?
W logice matematycznej istnieje pojęcie tautologii - czyli zdania, które bez względu na swoje składowe będzie zawsze prawdziwe. W JavaScripcie wartości i wyrażenia zawsze prawdziwe nazywamy truthy.
Wartościami zawsze prawdziwymi są:
- wszystkie liczby poza 0 (np. 1, 10, 100 itd.),
- stringi niebędące pustymi ciągami znaków (np. „Ala”, „Hello World” itd.),
- tablice i obiekty (np. [1, 2, 3], {name: 'Ola'} itd.),
- wartość true.
Istnieje też odwrotność truthy, czyli wartości, które zawsze będą fałszywe - nazywane falsy:
- 0,
- pusty string,
- null,
- undefined,
- NaN,
- wartość false.
Jak widać - większość wartości niebędących typu boolean to truthy. Dlatego w przykładzie z poprzedniego akapitu można było spokojnie założyć, że jeśli username istnieje, to najprawdopodobniej po konwersji przyjmie wartość true. A jeśli trafi nam się falsy? Cóż, patrząc na to, jakie to mogą być wartości, to de facto i tak zmienną username można potraktować jako nieistniejącą 😉
Tu mała ciekawostka z mojej historii - czyli jak bez sensu utrudnić sobie życie 😋
Na samym początku przygody z programowaniem miałem manię brania pod uwagę wszystkich możliwych warunków brzegowych (w czym ogromna zasługa prowadzącego laboratoria z informatyki na WAT). Ogólnie nie jest to złe podejście. Ale ja zawsze, kiedy przyszło do sprawdzenia, czy zmienna typu string istnieje, to po kolei sprawdzałem czy nie jest pustym stringiem, nullem albo undefinedem 😂
//jak NIE wykonywać sprawdzania:
if (string !== undefined && string !== null && string !== ' ' && string !== '') {
(...)
}
//poprawnie powinno być:
if (string && string !== ' ') {
(...)
}
Tak, można powiedzieć, że początki nie były proste 😂
Słowo (albo kilka) na zakończenie
Muszę się przyznać, że jak do tej pory omówienie typu boolean sprawiło mi najwięcej problemów. Z jednej strony jest to typ bardzo prosty - w końcu może przyjąć tylko jedną z dwóch wartości. Jednak z uwagi na jego istotność oraz możliwość zastosowań bardzo ciężko było znaleźć złoty środek pomiędzy omówieniem wszystkiego, co najważniejsze, a napisaniem tego w sposób zwięzły i przystępny. Czy mi się udało - ocena należy do was 😉
Typ boolean będzie wracał niejednokrotnie, więc będzie jeszcze wiele okazji, żeby go utrwalić. Jeśli chcecie zoabczyć jego użycie w prawdziwym kodzie, to zapraszam do serii relacjonującej budowę aplikacji. Tak praca z booleanami będzie na porządku dziennnym 😉
Tymczasem - tradycyjnie już - zapraszam na mojego twixera.
Do następnego!