A Crystalben egy változó több típusból is állhat. Ezt uniótípusnak nevezzük. A Crystalben gyakori, hogy az uniótípust a fordító következteti ki.
Az uniótípus futásidőben továbbra is egyetlen típus, még akkor is, ha több típusból áll.
Ez azt jelenti, hogy ha egy uniótípus String-ből és Int32-ből épül fel, akkor nem lesz egyszerre mindkettő.
Ehelyett vagy String, vagy Int32 lesz.
Az uniótípust úgy deklaráljuk, hogy a típusokat egy függőleges vonallal (|) választjuk el.
Gyakran zárójelek közé tesszük őket, de ez nem kötelező.
A leggyakoribb uniótípus a (T | Nil), ahol T egy típus; ez olyan metódusokban fordul elő, amelyek Nil-t adhatnak vissza.
Ilyenkor azt is mondjuk, hogy a változó nilable.
foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)
foo : (String | Nil) = "Hello"
foo = nil
Nem korlátozódik két típusra; annyi lehet, amennyit csak szeretnél.
foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0
typeof vs Object#class
Kétféleképpen tudhatjuk meg egy változó típusát.
Vagy a typeof, vagy az Object#class használatával.
A különbség az, hogy a typeof fordítási időben adja vissza a változó típusát, az Object#class viszont futásidőben.
Ez azt jelenti, hogy ha például meg akarod nézni, hogy egy változó uniótípus-e, az Object#class erre nem tud válaszolni, mert csak a futásidejű típust adja vissza, ami egyetlen típus.
foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String
Mivel az uniótípus futásidőben egyetlen típus, minden szokásos művelet működik rajta. A kód fordítása során viszont a fordítónak tudnia kell, hogy éppen melyik típusról van szó. Ezért a kódot úgy kell felépíteni, hogy amikor típusspecifikus műveleteket szeretnénk használni, csak az egyik típus jöhessen szóba.
foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)
A Crystalben viszont van egy külön metódus az uniótípusokhoz: az is_a? metódus, amely argumentumként egy típust vár, és Boolean értéket ad vissza.
A nil? metódus az is_a?(Nil) rövidítése.
Ha az is_a? metódust egy vezérlőkifejezésbe tesszük, az megmondja a fordítónak, hogy melyik típusról van szó, és ezzel garantálja, hogy valóban az a típus.
Egy else ágban pedig garantált lesz, hogy nem az a típus.
foo : (String | Int32) = "Hello"
if foo.is_a?(String)
typeof(foo) # => String
foo.downcase # => "hello"
end
Az is_a? nem csak egyetlen típust kaphat argumentumként, hanem uniótípust is.
Több típus esetén az &&-tal is kombinálható.
Az is_a? metódus vezérlőkifejezéssel együtt használva nem lehet példányváltozó vagy osztályváltozó.
Ezeket előbb egy lokális változóhoz kell rendelni.
Az egyik módja annak, hogy egy uniótípusból egyetlen típust csináljunk, hogy olyan ágat írunk, amelybe csak akkor lehet belépni, ha a típus egy meghatározott típus.
Egy másik megközelítés az as metódus használata.
Ez egy futásidejű ellenőrzéssel alakítja az uniótípust egyetlen típussá.
Ha a típus nem a várt típus, kivétel keletkezik.
foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"
foo.as(Int32) # Error: can't cast String to Int32
Ez a megközelítés csak akkor használható, ha biztos vagy benne, hogy a típus a várt típus, vagy ha kivételt szeretnél kapni, amikor nem az.
Ha ezt a megközelítést nem megfelelő felépítés mellett használod, váratlan viselkedést okozhat.
Az as? nagyon hasonlóan működik, mint az as, de ha a típus nem a várt típus, akkor kivétel dobása helyett nil-t ad vissza.
Ez azt jelenti, hogy a várt típus és a Nil uniótípusát adja vissza.
foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"
foo.as?(Int32) # => nil
A nilable azt jelenti, hogy egy változó vagy egy adott típus, vagy Nil lehet.
Ezt így írhatjuk le: (T | Nil).
Mivel azonban a nilable típusok viszonylag gyakoriak, van rájuk egy rövidítés: T?.
# This:
foo : (String | Nil) = "Hello"
foo = nil
# Is the same as:
foo : String? = "Hello"
foo = nil
A cég, amelynek dolgozol, éppen most dobja piacra vadonatúj okostelefonját, a Smarty 5 néven. Az új készülék új kamerarendszert, új dizájnt és egy forradalmi új processzort kínál, amely egy új, smartyOS nevű operációs rendszeren fut.
Az új processzor a korábbiaknál is biztonságosabb jelszavak kezelésére képes, és a cég úgy döntött, hogy ezt kihasználja. Arra kértek, hogy valósíts meg egy jelszózár-rendszert a telefonhoz, amely lehetővé teszi a felhasználó számára, hogy beállítson egy jelszót, majd ellenőrizze, hogy egy adott jelszó helyes-e.
Az ebben a feladatban gyakorolt jelszórendszer nem biztonságos, és kizárólag oktatási célokat szolgál. NEM szabad valós alkalmazásokban használni.
A telefon lehetővé teszi a felhasználók számára, hogy többféle jelszótípust használjanak, az egyszerű számjelszótól a biztonságosabb alfanumerikus jelszóig, sőt az ujjlenyomatos jelszóig.
Ezek a különböző jelszótípusok különböző típusokat használnak: a számjelszó Int32-t, az alfanumerikus jelszó String-et, az ujjlenyomatos jelszó pedig Float64-et.
Valósítsd meg a PasswordLock osztályt egy kezdőértéket fogadó metódussal, amely a három jelszótípus bármelyikét argumentumként kapja, és egy @password nevű példányváltozóban tárolja.
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
A cég úgy döntött, hogy titkosítja a jelszót, hogy az ne tárolódjon nyílt szövegben, és megkért, hogy valósíts meg egy metódust a titkosításához.
Minden jelszótípusnak megvan a maga egyedi titkosítási módja:
Int32 jelszó: A jelszót elosztjuk kettővel, és a legközelebbi egész számra kerekítjük.String jelszó: A jelszót megfordítjuk.Float64 jelszó: A jelszót megszorozzuk néggyel.Valósíts meg egy encrypt nevű példánymetódust, amely nem fogad argumentumot, és módosítja a @password példányváltozót, hogy titkosított legyen.
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>
A cég arra is megkért, hogy valósíts meg egy metódust, amely ellenőrzi, hogy egy adott jelszó helyes-e.
Azt szeretnék, hogy a metódus nil-t adjon vissza, ha a jelszó helytelen, és "Unlocked"-et, ha a jelszó helyes.
Valósíts meg egy unlock? nevű példánymetódust, amely argumentumként kapja az ellenőrizendő jelszót.
A metódus adjon vissza nil-t, ha a jelszó helytelen, és "Unlocked"-et, ha a jelszó helyes.
password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Crystal nyelvet 26 fogalom142 feladat segítségével, valódi emberi mentorálással, mindez ingyen.