Az erőd ostroma

    Előzmény: Újabb lépcsőfokok
    Egy barátom (aki azóta PhD hallgató), még elsőévesként a következő problémába ütközött amikor C-ben kezdett el tanulni:


    Rossz eredményt adott egy (ehhez hasonló) programja:

#include <stdio.h>

int main(int argc, char ** argv)

{

    int x = 4;

    if (6 < x < 8)

    {

      puts("true");

    }

}


    Sajnos ez így kiírja azt, hogy true.
Gondolom nem kell mondanom, hogy mi a gond. (Az első összehasonlítás
eredménye a 0 szám, ami valóban kisebb mint 8.) Pedig milyen szép lenne
így kifejezni a szándékunkat!
    Természetesen készült olyan programnyelv,
amelyben az ehhez hasonló kifejezések helyesen értelmeződnek. Ilyen
például a Fortress. Azonban a Scala igencsak támogatja a nyelv
kiterjesztését, így megpróbálom ebben a bejegyzésben imitálni ezt a
tulajdonságát a Fortressnek. Megpróbálom bevenni ezt az erődöt. 🙂
    Hogyan fogjunk hozzá? Először is jó lenne egy objektumba gyűjteni,
hogy milyen volt a legutolsó összehasonlítás eredménye, valamint az
összehasonlításban szereplő érték. Ha ez megvan, akkor szépen
végigmehetünk a láncon egy ilyen objektummal. Igen ám, de ez nem lesz Boolean
értékű! Át kell még alakítani majd azzá. Az igazi az lenne, ha az első
objektummá átalakítást is meg lehetne oldani valahogy automatikusan. Mi
az amit még jó lenne tudjon? Egyszerűsített kiértékelést: ha a
kiértékelés egy pontján meghatározható a végeredmény, akkor ne
folytatódjon a kiértékelés. Lehetőleg ilyen esetben a további
kifejezések ne is hajtódjanak végre (hatékonyság, illetve mellékhatások
minimalizálása miatt).
    Nézzük mire jutottam:

/**
An object to extend the functionality of ordering. */

object
MathRelations

{
  /**
This class represents the starting point of the relation chain. */



  final
class
PreRelation[T
<%
Ordered[T]](wrapped:
=>
T)

  {
   
def
<(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
wrapped
< that)

   
def
>(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
wrapped
> that)

   
def
<=(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
wrapped
<= that)

   
def
>=(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
wrapped
>= that)

  }

  /**
This class is for the further points of the relation chain. */

 
final
class
ChainedRelation[T
<%
Ordered[T]](wrapped: =>T,
val
isTrue:
Boolean)

  {
    def
<(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
if
(
isTrue)
(
wrapped
< that)
else
false)

    def
>(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
if
(
isTrue)
(
wrapped
> that)
else
false)

    def
<=(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
if
(
isTrue)
(
wrapped
<= that)
else
false)

    def
>=(that:
=>
T):
ChainedRelation[T]
=
new
ChainedRelation(that,
if
(
isTrue)
(
wrapped
>= that)
else
false)

    override
def
toString(): String =
String.valueOf(isTrue);

  }

  /**
Converts an Ordered object to a PreRelation. */

  def
toPreRelation[T
<%
Ordered[T]](o:
T) = new
PreRelation(o)

  /**
Converts a ChainedRelation to a Boolean value */

  implicit
def
toBoolean[T](r
:
ChainedRelation[T])
:
Boolean =
r.isTrue;

}

    Egyelőre
ennyi. Mit is tud ez így valójában, mi ez a sok krix-krax? Nos, az
ötlet adott volt, így a megvalósítás is viszonylag könnyen adódik. A PreRelation osztály típusparamétere egy olyan típus, mely ,,implicit módon” átalakítható Ordereddé. Az egyetlen konstruktor paraméterét wrappednek neveztem el, ennek a típusa olyan, hogy mindig kiértékelődik (=>) az azt meghatározó kifejezés, valahányszor hivatkozunk a mezőre, az így kapott kifejezés típusa pedig megegyezik a PreRelation típusparaméterével (T).

    Definiáltam néhány metódust, melyek az összehasonlításhoz használt ChainedRelation objektumokat hozzák létre. Természetesen létre lehet hozni más relációs jelekhez is (például ==, subset, …). Egyelőre azonban ennyi elég lesz.

    Nézzük hogy fest a másik osztály! Ennek a neve ChainedRelation, ugyanolyan típusparamétere van, mint az elsőnek. Azonban ennek a korábbihoz hasonló wrapped paraméter mellett egy Boolean típusú, el is mentett, nem változtatható (val) mezője is van.

    A metódusok itt is hasonlóak. A megvalósítás kissé túlbonyolítottnak tűnhet, mivel elég lenne a második paraméternél isTrue && wrapped
< that
, jellegű megoldást alkalmazni. Azonban a Scala sajátosságai miatt, ekkor lefutna a thatet meghatározó kód, elvesztenénk egy fontos tulajdonságot.

    Definiáltam egy toString()
metódust is, azonban erre nincs igazán szükség. A hibakereséshez
azonban jól jöhet (vagy épp rosszul) a hiba jellegétől függően.

    Mi maradt még? Az előrelációt készítő metódus, illetve a Boolean
értékké alakító. Előbbire még nem érdemes szavakat vesztegetni, utóbbi
viszont kissé érdekesebb. Itt szerepel egy újabb kulcsszó, az implicit.
Ezzel azt jelezhetjük, hogy ha ez a metódus látható, és van olyan
objektumunk, ami a paraméterének megfelel, akkor fusson le, ha az adott
környezetben az eredmény típusának megfelelő értékre van szükség
(legalábbis nagyvonalakban). Ez még hasznos lehet majd. 🙂

    Ideje visszatérni a toPreRelation
metódusra. Ez miért nem lehet implicit? Lenne értelme, hiszen így
anélkül lehetne használni a most elkészített eljárásunkat, hogy bármit
is írnunk kellene. Az elején be kellene importálni a MathRelations
összes metódusát és ilyen szép dolgokat művelhetnénk. Nos, ennek az oka
nem csak az, hogy bizonyos esetekben igen jelentős többletmunkát adnánk
a processzornak (létre kell hoznia új objektumokat,… ) akkor is, ha
erre nincs szükség (mert mondjuk nincs is igazi lánc), de az is, hogy
ezt nem teszi lehetővé a Scala fordító (legalábbis jelenlegi változata):

  • illegal cyclic reference involving method toPrerelation
  • implicit method toPreRelation is not
    contractive, because the implicit parameter type (T) => Ordered[T]
    is not strictly contained in the signature (T) => <error>

    Nagyjából ennyi lenne a teendőnk. Azonban nem ártana kipróbálni is. Nézzük:

import
MathRelations.{toBoolean,
toPreRelation => |}

object
testMathRelations
extends
Application

{
  def
unPureMethod()
:
Float =
{
println("Called");
2}

  def
p(x:
Boolean)
{
println(x)}

  p(|(4.0)
<= 3 >=
unPureMethod()
>= 1)

}

    Mit mutat így? Először is elérhetővé tettük a metódusainkat, sőt a hosszú nevű toPreRelation metódust át is neveztük | jellé. Eztán készítettünk egy olyan metódust, aminek van mellékhatása, kiírja azt, hogy Called. Emellett visszaadja a 2 értéket is. A p metódus egy Boolean értéket ír ki. Ezt csak azért írtam, hogy véletlenül se a toString érték alapján írjon ki valamit a println. (Érdemes lehet kipróbálni, hogy mit ír ki a program, ha nincs felüldefiniálva a ChainedRelation.toString metódus és csak simán a println-t használjuk a kiíráshoz.)

    Nyugodtan ki lehet próbálni különféle értékekkel, hátha valahol hibázik a program.

    Bizonyára feltűnt, hogy figyelmeztetéseket adott a programunk amikor lefordítottuk a tesztet. Nos ez hiba. (Viszont ha az osztályainkat nem finalként deklaráljuk, akkor nem jelentkezik.)

    Összegzés: Nagyjából
sikerült szimulálni a Fortress hasonló nyelvi elemét. Nem ugyanaz a
kényelem, de ha valakinek tetszik, akkor használhatja és nem kell attól
tartania, hogy olyan hibákba fut mint a C-s program esetében.


    Megint felmerül a kérdés, hogy merre tovább. Van egy programunk,
amit nem ártana mondjuk tesztelni. Vagy valamerre másfelé kellene
fordulni? Majd elválik. 🙂
    Addig is nem sokára megjelenik a Scala új változata is, mely néhány újabb nyelvi elemet hoz majd.


This entry was posted in Scala. Bookmark the permalink.

1 Response to Az erőd ostroma

  1. aborg says:

    Ha esetleg valaki egy olyan változatot szeretne, melyben nincs gond a mellékhatások kiértékelésével:

    /**
    This class is for the further points of the relation chain. */

    final
    class
    ChainedRelation[T
    <% Ordered[T]](wrapped
    : =>T,
    val
    isTrue:
    Boolean)
    {

    var
    computed
    : Option[T]
    = None

    private
    def
    compute :
    Unit =
    {

    if
    (computed
    == None)
    {computed
    = Some(wrapped)}
    }

    private
    def
    computer(that:
    => T,
    rel:
    (T,
    T)=>
    boolean)
    =
    {

    var
    thatComputed:
    Option[T]
    = if
    (isTrue)
    Some(that)
    else
    None;

    val
    r =
    new
    ChainedRelation(that,

    if
    (isTrue)

    {

    compute;

    rel(computed.get,
    thatComputed.get)

    } else
    false);

    if
    (computed
    != None)

    r.computed
    = thatComputed;

    r
    }

    def
    <(that:
    => T)
    = {computer(that,
    _ < _)}…}

    Ez memorizálja az értékeket, nem értékeli ki újra, ha szükség lenne rá. (Nem szép megoldás, a lazy val sokkal szebb lenne.)

Leave a comment