Funkcja zwracająca różne typy danych

0

Chciałbym aby Funkcja zwracała mi różne dane w zależności od przekazanych parametrów. Jeśli wartość paramtru1 będzie 'A' to funkcja ma zwrócić Stringa, jeśli wartość parametru1 będzie 'C' to funckaj zwraca datę itd.
Jest to możliwe do wykonania?

0

Jest możliwe - użyj typu Variant; Tyle że możliwości nie są ograniczone i lepiej by było gdybyś wyjaśnił przeznaczenie takiej funkcji.

0

To raczej nie sprawdzi się u mnie, chyba że coś źle zrozumiałem.

Ja mam zmienne już określone i niejestem w stanie ich zmienić bo bym musiał przebudować cały kod. Chciałem stworzyć uniwersalną funkcję, która by przypisywała wartość w zależności od parametrów. Wszystkie wartości początkowo widnieją jako stringi w tablicy. Po podaniu w parametrze funkcji słowa kluczowego np. "data" wartość funkcji od razu stawałaby się datą, gdy słowo kluczowe będzie "nazwisko" to wtedy wynikiem jest string, a gdy np. słowo kluczowe to "numer" wtedy wynikiem funkcji jest integer.

2

Chciałem stworzyć uniwersalną funkcję, która by przypisywała wartość w zależności od parametrów.

No dobrze, ale jaki typ miałaby zwracać ta funkcja, żeby mogła zwracać dowolne dane? Jedyne co mi do głowy przychodzi to goły Pointer, jednak kod musiałbyś przerobić;

Po podaniu w parametrze funkcji słowa kluczowego np. "data" wartość funkcji od razu stawałaby się datą, gdy słowo kluczowe będzie "nazwisko" to wtedy wynikiem jest string, a gdy np. słowo kluczowe to "numer" wtedy wynikiem funkcji jest integer.

Taki efekt możesz uzyskać za pomocą przeładowywania:

uses
  SysUtils;

  function Convert(const AValue: String): TDateTime; { overload; }
  begin
    Result := StrToDate(AValue);
  end;

  function Convert(const AValue: String): String; { overload; }
  begin
    Result := AValue;
  end;

  function Convert(const AValue: String): Integer; { overload; }
  begin
    Result := StrToInt(AValue);
  end;

W Lazarusie nie trzeba zakańczać deklaracji tych funkcji słówkiem overload (dlatego są zaremowane);
I teraz możesz używać teoretycznie jednej funkcji, która zwracać będzie dane różnego typu; Ograniczenie jest takie, że typy danych muszą się w każdej z nich różnić - albo typy parametrów, albo typ zwracany; Dzięki temu możliwe będą poniższe wywołania:

var
  dtValue:  TDateTime;
  strValue: String;
  intValue: Integer;
begin
  dtValue  := Convert('01:03:56');
  strValue := Convert('foo bald');
  intValue := Convert('12345678');

Możesz też ustalić ten sam typ zwracany (np. String), ale różne typy argumentów.

2

@dani17: kombinujesz cholernie pod górę.
Jeśli potrzebujesz, aby wywołanie getCostam('a') zwracało integer, a getCostam('b') - ciąg znaków, utwórz odrębne metody getCostamA() oraz getCostamB() - to jedyna słuszna oraz poprawna metoda.

0
Patryk27 napisał(a):

@dani17: kombinujesz cholernie pod górę.
Jeśli potrzebujesz, aby wywołanie getCostam('a') zwracało integer, a getCostam('b') - ciąg znaków, utwórz odrębne metody getCostamA() oraz getCostamB() - to jedyna słuszna oraz poprawna metoda.

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt. mam 20 róznych słów kluczowych, które mają zwracać String, 20 zwracających integer i kilka zwracających datę. dlatego chciałbym to stworzyć w jednej funkcji i najlepiej napisać if (argument = 'slowo') or (argument = 'inne slowo') then result: string else result: inetger;

to nie jest poprawny zapis, wiem, ale chcę w ten sposób pokazać co chciałbym osiągnąć.

2

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt.
No to stwórz ich kilkadziesiąt - przecież już dawno temu miałbyś je napisane (zakładając nawet, że nie masz automatycznego generatora getterów/setterów w środowisku) :P

Naprawdę nie ma co robić sobie drogi na skróty - w programowaniu liczy się dokładność. Robiąc coś na odczep zachowujesz sobie jedynie pięć minut dzisiaj, tracąc pięć godzin w przyszłości na szukaniu błędu.

1
dani17 napisał(a)

dlatego chciałbym to stworzyć w jednej funkcji i najlepiej napisać if (argument = 'slowo') or (argument = 'inne slowo') then result: string else result: inetger;

Nie możesz napisać jednej funkcji, która może zwracać trzy różne typy danych - albo String, albo TDate, albo Integer; Musisz określić konkretny typ danych zwracanych przez funkcję; Możesz skorzystać z typu Variant, aby móc zwracać dane różnych typów, jednak musiałbyś zmienić również typy danych w programie; W ogóle to nie ma się nad czym zastanawiać;

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt. mam 20 róznych słów kluczowych, które mają zwracać String, 20 zwracających integer i kilka zwracających datę.

Skoro jest ich tyle to napisz sobie trzy przeładowane (dla każdego typu jedna).

0
dani17 napisał(a):

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt. mam 20 róznych słów kluczowych, które mają zwracać String, 20 zwracających integer i kilka zwracających datę. dlatego chciałbym to stworzyć w jednej funkcji i najlepiej napisać if (argument = 'slowo') or (argument = 'inne slowo') then result: string else result: inetger;

to nie jest poprawny zapis, wiem, ale chcę w ten sposób pokazać co chciałbym osiągnąć.

Nie wiem jak w lazarusie ale w delphi da się to zrobić używając generyków i żeby przyspieszyć TDictionary.

TVirtualFileClass = class
    public
        class function GetResult: Variant virtual; abstract;
    end;

TFileTypeClass = class of TVirtualFileClass;

TFileTypeClassA = class(TVirtualFileClass )
    public
        class function GetResult: Variant; override;
    end;

TFileTypeClassB = class(TVirtualFileClass )
    public
        class function GetResult: Variant; override;
    end;

Każda klasa będzie miała swoją funkcję GetResult i wywołanie będziesz miał tylko:

RegistrationClass(const aClassType: TFileTypeClass);

wewnątrz możesz sobie to dodawać do swojego dicta lub klasy dziedziczącej z dicta np tak:

type 
TKBFilesType = class(TDictionary<string, TFileTypeClass>)

to oczywiście tylko przykładowe definicje ale ja mam tak zrobione np wczytywanie plików operuję na jednym typie czyli

 TNazwaKlasy = class of TNazwaKlasyVirtualnej

i reszta dzieje się w definicjach poszczególnych klas. Nie ma dzięki temu w kodzie warunków: if ExtractFileExtention(pFilneName) = 'txt' then ... else if ...

0
Patryk27 napisał(a):

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt.
No to stwórz ich kilkadziesiąt - przecież już dawno temu miałbyś je napisane (zakładając nawet, że nie masz automatycznego generatora getterów/setterów w środowisku) :P

Naprawdę nie ma co robić sobie drogi na skróty - w programowaniu liczy się dokładność. Robiąc coś na odczep zachowujesz sobie jedynie pięć minut dzisiaj, tracąc pięć godzin w przyszłości na szukaniu błędu.

Tak też zrobię, w końcu wywołanie takiej funkcji będzie niemal identyczne jak to czego chciałem. Jedynie zamiast wpisywać słowo kluczowe jako parametr to będzie się ono znajdowało w nazwie funkcji.

Ale teraz mam inne pytanie. Jeżeli mam zmienną Imie: String; to czy da się zrobić aby jakaś inna zmienna przyjęła jako wartość nazwę innej zmiennej? I tak samo w drugą stronę, w sumie bardziej mi na tym zależy. Mam Tablica: Array of String = ('Imie', 'Nazwisko', 'Miasto'); i czy teraz da się zrobić coś takiego:

for I := 0 to Length(Tablica) - 1 do
  Zmienna{Tablica[I]} := Get{Tablica[I]}
 

Oczywiście mam zadeklarowane zmienne które nazywają się tak jak wartości tablicy. Czy jednak nie da się tego przyśpieszyć i trzeba robić tak:

for I := 0 to Length(Tablica) - 1 do
  begin
    if Tablica[I] = 'Imie' then
      Imie := GetImie;
    if Tablica[I] = 'Nazwisko' then
      Nazwisko := GetNazwisko;
    if Tablica[I] = 'Miasto' then
      Miasto := GetMiasto;
  end;
 
0

Znowu próbujesz sobie torować drogę na skróty :P

Teoretycznie mógłbyś spróbować utworzyć sobie klasę zawierającą metody GetImie(), GetNazwisko() oraz GetMiasto() (przy okazji: nie wykorzystuj polskiego nazewnictwa w kodzie, nigdy) oraz pobawić się z RTTI, natomiast to by było czysto dla zabawy i bardzo nie chciałbym spotkać czegoś takiego w normalnym, produkcyjnym kodzie.

0

W dalszym ciągu wygląda to na kosmiczne kombinacje; Przy czym zamiast podawać nazwy zmiennych (i trzymać te nazwy w tablicy), możesz skorzystać z typu wyliczeniowego i rozróżnienie wartości zrobić czytelnie w instrukcji wyboru:

type
  TFieldKind = (fkName, fkSurname, fkCity);

function Foo(AFieldKind: TFieldKind): String;
begin
  case AFieldKind of
    fkName:    Result := GetName();
    fkSurname: Result := GetSurname();
    fkCity:    Result := GetCity();
  end;
end;

I wywołania:

Name    := Foo(fkName);
Surname := Foo(fkSurname);
City    := Foo(fkCity);

Ale takie cuś raczej nie ma sensu - wygodniej będzie zrobić sobie więcej funkcji, nazwać je sensownie i używać; Przy czym jeśli funkcje mają elementy wspólne to można je pogrupować.

0
Patryk27 napisał(a):

Znowu próbujesz sobie torować drogę na skróty :P

Teoretycznie mógłbyś spróbować utworzyć sobie klasę zawierającą metody GetImie(), GetNazwisko() oraz GetMiasto() (przy okazji: nie wykorzystuj polskiego nazewnictwa w kodzie, nigdy) oraz pobawić się z RTTI, natomiast to by było czysto dla zabawy i bardzo nie chciałbym spotkać czegoś takiego w normalnym, produkcyjnym kodzie.

Nie, nie. Ja mam to i tak napisane inaczej. Tylko byłem ciekawy czy coś takiego może istnieje. Bo jakby była jakaś funkcja która by to umożliwiła to kod byłby na pewno bardziej przejrzysty w tym konkretnym miejscu.

0

Wcale nie byłby bardziej przejrzysty, tylko zaciemniony niejasną zależnością :P

0

a czy do takiego zadania nie byla by najlepsza fabryka po prostu ?

0

@Wybitny Młot: pokaż jak wykorzystujesz wzorzec fabryki w tym kontekście, to przecież nawet nie jest podobna sytuacja - rzuciłeś jakieś mądre słówko i pomyślałeś "o, rozwiązanie" :P

0

ehmmm oczysicei ze mozna wzorzec fabryki wykrozystac ... fabryka zwraca poporstu obiekt reagujac na dane slowa np. "string" , "date" , "int".

2

Pokaż, jak zastosowałbyś wzorzec fabryki do stworzenia getterów, bo tego jeszcze świat programistyczny nie widział, a przecież o gettery się rozchodzi.

0

A wiec wybacz nie przeczytalem dokladnie zadania , odpowiedzi do usuniecia :P Myslalem ze chodzi o obiekt ktory zwraca rozne typy danych do ktorych pozniej mozna przypisac jakies dane adekwatnie do rodzaju typu obiektu.

0

Chciałbym aby Funkcja zwracała mi różne dane w zależności od przekazanych parametrów. Jeśli wartość paramtru1 będzie 'A' to funkcja ma zwrócić Stringa, jeśli wartość parametru1 będzie 'C' to funckaj zwraca datę itd.
Jest to możliwe do wykonania?

W popularnych językach takich jak Java, C++, C#, Delphi nie jest możliwe zakodowanie czegoś takiego w systemie typów.
Jeżeli typ ma zależeć od wartości (a nie od typu wartości), to potrzebujesz języka, który potrafi tzw. "dependent types".
Takie języki to m.in. Agda, Coq, Idris i parę innych mniej znanych. Do pewnego stopnia da się zasymulować typy zależne w językach takich jak Haskell, Scala, Dotty.

https://en.wikipedia.org/wiki/Dependent_type

0
Patryk27 napisał(a):

no dobrze, ale to musiałbym takich metod stworzyć kilkadziesiąt.
No to stwórz ich kilkadziesiąt - przecież już dawno temu miałbyś je napisane (zakładając nawet, że nie masz automatycznego generatora getterów/setterów w środowisku) :P

Naprawdę nie ma co robić sobie drogi na skróty - w programowaniu liczy się dokładność. Robiąc coś na odczep zachowujesz sobie jedynie pięć minut dzisiaj, tracąc pięć godzin w przyszłości na szukaniu błędu.

Zgoda. Tylko taka rada to jest właśnie na odczep się. Nie ma nic "fajniejszego" w programowaniu, jak robienie tego samego na kilkadziesiąt różnych sposobów. To był sarkazm, oczywiście...

Pytanie jest o FPC, a ja sie na tym nie znam, zatem...
Ale FPC ma coś takiego jak TValue? Albo - poprawnie obsługuje typy generyczne?

Tak czy siak; ja mam np. coś takiego:

function AsBusinessObject<T : TBaseBusinessObjectQuery>: T;

A używa się tego tak:

AsBusinessObject<TboPpTask>.Duration := 3.5;
AsBusinessObject<TboPpTextTask>.Duration := 'trzy i pół godziny';

Tak, mógłbym napisać kilkaset funkcji - ale ja jestem na to za leniwy.

0
Patryk27 napisał(a):

Znowu próbujesz sobie torować drogę na skróty :P

Teoretycznie mógłbyś spróbować utworzyć sobie klasę zawierającą metody GetImie(), GetNazwisko() oraz GetMiasto() (przy okazji: nie wykorzystuj polskiego nazewnictwa w kodzie, nigdy) oraz pobawić się z RTTI, natomiast to by było czysto dla zabawy i bardzo nie chciałbym spotkać czegoś takiego w normalnym, produkcyjnym kodzie.

Zatem nigdy przenigdy nie używaj LiveBindings w Delphi.
Trzymaj się z dala od wszelkiej maści ORMów i nomen-omen doskonałego Spring4Delphi lub DSharp.

Dlaczego? Bo tam bardzo intensywnie używa się RTTI, ale dla Ciebie to zabawa, a nie kod produkcyjny.
Jasne, można i tak. Ale można inaczej, lepiej, prościej i szybciej - a przy tym bardziej ogólnie.

0
wloochacz napisał(a)

Bo tam bardzo intensywnie używa się RTTI, ale dla Ciebie to zabawa, a nie kod produkcyjny.
Jestem ciekaw, w którym to momencie powiedziałem, jakoby jakiekolwiek używanie RTTI było zabawą :-P

Napisałem, że w tym konkretnym przypadku, na moje oko, byłoby to zaciemnianiem kodu - co innego, gdy wykorzystuje się odrębny framework.

0
Patryk27 napisał(a):
wloochacz napisał(a)

Bo tam bardzo intensywnie używa się RTTI, ale dla Ciebie to zabawa, a nie kod produkcyjny.
Jestem ciekaw, w którym to momencie powiedziałem, jakoby jakiekolwiek używanie RTTI było zabawą :-P

W tym miejscu:
[...] oraz pobawić się z RTTI, natomiast to by było czysto dla zabawy i bardzo nie chciałbym spotkać czegoś takiego w normalnym, produkcyjnym kodzie.

Napisałem, że w tym konkretnym przypadku, na moje oko, byłoby to zaciemnianiem kodu - co innego, gdy wykorzystuje się odrębny framework.

Nie napisałeś tego - teraz to dopisałeś.
Poza tym, przecież framework to też kod.
A na moje oko, to pisanie n funkcji do tego samego, to jest zaciemnianie kodu.
Tego właśnie OP chciał uniknąć, a Ty radzisz mu coś takiego...

0
wloochacz napisał(a)

W tym miejscu:
Tak, odnosząc się do tego konkretnego przypadku, a nie wszystkich możliwych sytuacji :-P

A na moje oko, to pisanie n funkcji do tego samego, to jest zaciemnianie kodu.
Podam może zatem przykład takiego genialnego rozwiązania (pozwoliwszy sobie odnieść się do innego języka): PHP, sklep Magento 1.

W PHP można utworzyć w klasach magiczne metody m.in. __get oraz __set, które umożliwiają odnoszenie się do klasowych pól bez ich formalnej deklaracji/definicji (te metody służą za akcesory, przyjmując jako parametr nazwę pola, do którego programista w kodzie próbuje się odnieść).

W konsekwencji spora liczba metod tak naprawdę pojawia się w kodzie z tyłka, niemożliwe staje się ich sensowne debugowanie czy skakanie po kodzie, nie mówiąc o braku podpowiadania składni przez IDE, na czym przy początku mojej pracy nieraz się przejechałem (bo przecież IDE nie podpowie literówki w getBaseSubtotalWitDiscount, skoro potencjalnie każda nazwa jest poprawna, w zależności od wnętrza metody __get).

O refaktoryzacji takiej metody również można pomarzyć.

Gdyby do każdego pola istniały odrębne akcesory, to faktycznie - codebase powiększyłby się o dodatkowe dziesiątki kilobajtów, natomiast stwarzujmy prawdę:

  1. Każde sensowne IDE i tak automatycznie potrafi paroma kliknięciami wygenerować oraz zaktualizować potrzebne metody.
  2. Czytelność kodu oraz możliwość jego statycznej analizy (głównie dla analizy typów) wzrosłaby ogromnie.
    Podałbym jakiś adekwatny przykład dla Delphi, lecz dawno temu przestałem już pisać w Pascalu i nic konkretnego nie przychodzi mi na myśl.

1 użytkowników online, w tym zalogowanych: 0, gości: 1