1)

Poniżej znajduje się funkcja Pythona summation, która może wykonywać sumę kostek / kwadratów / .., podobne operacje .

def identity(k): return k def cube(k): return pow(k, 3) def square(k): return pow(k,2) def summation(n, term): if n == 0: return 0 else: return term(n) + summation(n-1, term) def sum_cubes(n): return summation(n, cube) if __name__ == "__main__": sum = sum_cubes(4) print(sum) """ In C, We can implement the same using function pointers. Goal is, to perform similar operations(Sum of ..) using single function summation()""" 

2)

Weź pod uwagę, poniżej sortowania API z C,

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); 

Tutaj qsort może sortować dane dowolnego typu , tablica liczb zmiennoprzecinkowych / nazw plików w katalogu / strings / …


Pytanie:

Jak zdefiniować funkcję ogólną?

Czy summation jest funkcją ogólną?

lub

Czy qsort jest funkcją ogólną?

lub

Czy na dwóch przykładach Funkcja ogólna jest nieprawidłową terminologią?

Uwaga: Termin motywacja do qsort lub dowolna funkcja sortowania, którą projektuję

Komentarze

  • Jaka definicja ” funkcja ogólna ” czy czytałeś, że nie ' nie rozumiesz? Byłoby pomocne, gdybyś opublikował to zamiast pisać mnóstwo kodu.
  • Termin w teorii typów określający rodzaj generycyzmu, w którym funkcja działa dla dowolnego typu bez ograniczeń i bez znajomości określonego typu, to parametryczny polimorfizm . W ten sposób funkcja tożsamości jest ogólna.
  • W niektórych językach (takich jak Java) ” funkcja ogólna ” ma określoną definicję techniczną. Ale tak nie jest w Pythonie, więc ” funkcja ogólna ” nie ma dobrze zdefiniowanego znaczenia. Nie oznacza to, że ” terminologia jest nieprawidłowa „, tylko że podczas używania terminu należy być świadomym kontekstu.
  • @AndresF. Javascrpt również często używa tej ogólnej terminologii funkcji . Ponieważ możesz mieć funkcję pobierającą dowolny element html do przetworzenia (przykład – usuń wszystkie elementy podrzędne danego elementu html)

Answer

Pojęcie„ ogólne ”ma kilka znaczeń.

Nieformalna definicja

„generic” w języku potocznym coś, co ma wspólne właściwości, ale pod pewnymi względami jest mniej szczegółowe.

W tej perspektywie qsort() można uznać za ogólny: kod tej funkcji potrafi sortować dowolną strukturę danych o stałym rozmiarze, dla której można zdefiniować funkcję porównawczą za pomocą algorytmu QSORT.

To samo dotyczy Twojej funkcji summation(), która podsumowuje warunki uzyskane przy użyciu dowolnej funkcji z jednym parametrem.

Definicja formalna

Języki programowania, takie jak C ++ lub Java, pozwalają na programowanie ogólne przy użyciu szablonów lub typów ogólnych:

Definicja ze standardu C ++ 14 : Szablon definiuje rodzinę klas lub funkcji lub alias dla rodziny typów.

Zasada jest taka, że implementację klasy lub funkcji można sparametryzować według typów.

Zgodnie z tym bardziej formalnym punktem widzenia, qsort() nie jest funkcją ogólną. Jej implementacja nie musi określać żadnego typu podczas kompilacji, a jej zachowanie jest niezależne od typu. Jedyne, czego potrzebuje, to rozmiar sortowanych elementów, a ten rozmiar jest zwykłym argumentem przetwarzanym w czasie wykonywania.

W przypadku języka, który nie jest wpisywany statycznie, np. Python , nie mam pewności, co odpowiedzieć na summation(). Myślę, że nie jest to ogólne, ponieważ jego implementacja i zachowanie nie są zależne od typu: ta funkcja jest po prostu funkcją wyższego rzędu, z argumentem term funkcja. Nie używa żadnej funkcji, która zmieniłaby zachowanie tej funkcji na podstawie typów.

Aby zilustrować funkcję ogólną, możesz spojrzeć na standardową funkcję C ++ std::sort() : jego implementacja zależy od typu argumentów (i opcjonalnie funkcji porównawczej z argumentami określonego typu). Korzystając z funkcji szablonów C ++, może sortować dowolny kontener dowolnego typu, pod warunkiem, że ma operatory / funkcje składowe / cechy / iteratory, które są wymagane przez implementację funkcji ogólnej.

Czy dynamiczny język maszynowy może mieć funkcje ogólne

Dynamicznie typowany język wymaga mniej ogólnego kodu niż języki statyczne.

Na przykład, jeśli masz kontener obiektów typu dynamicznego, funkcja qsort może generalnie sortować ten kontener, o ile można porównać dowolną kombinację dwóch elementów w kontenerze.

Ale nawet w tak elastycznym środowisku, ogólne – zależne od typu – programowanie może być pomocne. Typowym przypadkiem użycia są metody multimetody, w których zachowanie lub kod zależy od typu argumentów lub nawet kombinacji typów (na przykład do określenia przecięcia między dwoma różnymi kształtami). Aby uzyskać dodatkowe informacje, zobacz:

Komentarze

  • Nie wiem, dlaczego porównujemy Generics (głównie używany do unikania rzutowania typów w Javie & pomaga w przeprowadzaniu polimorfizmu) z definicją funkcji ogólnej?
  • @overexchange Myślę, że java oferuje również programowanie ogólne, w tym metody ogólne (patrz specyfikacja lub samouczek ). Niemniej ' nieznacznie zredagowałem część definicji, aby odnieść się do twojej uwagi.
  • Pakiet Generic z Pythona nie ma nic wspólnego z funkcjami ogólnymi. Tyle, że mają ten sam przymiotnik.
  • @Killian if programowanie ogólne dotyczy pomysłu abstrahowania od konkretnych, wydajnych algorytmów aby uzyskać ogólne algorytmy, które można łączyć z różnymi reprezentacjami danych , myślę, że multimetody w tym pakiecie powinny być w, nie ' nie sądzisz?

Odpowiedź

Funkcje ogólne przyjmują typ co najmniej jednego argumentu funkcji w czasie kompilacji. Oznacza to, że kompilator dowiaduje się, który typ jest używany w określonym miejscu i stosuje dokładnie ten typ, gdy jest używany w funkcji. Na przykład. jeśli masz w funkcji argument ogólny, który jest używany z operatorem +, typ musi mieć odpowiednie metody. W przypadku łańcuchów / tablic byłoby to w wielu przypadkach konkatenacją, a for i integer / float dodaniem. Kompilator może wykryć, że zastosuj poprawną operację. Twoja procedura w C nie jest ogólna w tym sensie, ponieważ to programista stosuje pewne informacje o rozmiarze, a nie kompilator wykrywający typ i używający właściwego rozmiaru.

Np. W jakimś fikcyjnym języku

func add(p1,p2) { return p1+p2 } print add("a", "b") // yields "ab" print add(1, 2) // yields 3 

W tym przypadku kompilator wykrywa w pierwszym przypadku, że zastosowano dwa ciągi i wewnętrznie rozwinie coś takiego jak

func add(p1:string, p2:string) 

i traktuj + jako konkatenację, podczas gdy w drugim przypadku rozwinie się

func add(p1:int, p2:int) 

zgodnie z podanymi Parametry całkowite.Oznacza, że kompilator generuje indywidualny kod w czasie kompilacji. Na przykład Python nie ma typu i mógłby dokonać tego rodzaju podstawienia w czasie wykonywania. Oznacza: Python nie ma funkcji ogólnych, ponieważ wszystko jest w pewnym sensie ogólne.

Komentarze

  • Nie rozumiem. Masz na myśli, że + to funkcja ogólna, składnia w C ++?
  • Argumenty funkcji pobierającej funkcje są wyższego rzędu funkcjonować w świecie Python / JavaScript. W C potrzebujemy wskaźników do funkcji, do tego samego.
  • Zobacz moją edycję powyżej.
  • Jaki rodzaj funkcji to summation, Funkcja wyższego rzędu? i nic więcej?
  • Istnieje wiele definicji tego, czym jest rodzaj. Na przykład Stroustrup definiuje to jako programowanie ” z typami jako parametrami „. Aby zapoznać się z odniesieniami do Wikipedii, ' raczej przejdź do: en.wikipedia.org/wiki/Generic_programming

Odpowiedź

Zacznę od perspektywy C ++, a potem przejdę do C.

W językach statycznych, takich jak C, C ++, Java itp., funkcja „ogólna” pozwala określić operacje funkcji raz , używając symboli zastępczych dla dowolnych typów, które mogą się różnić między różnymi wywołaniami (co oznacza, że funkcje takie jak qsort i bsearch to zdecydowanie nie funkcji ogólnych). Idealnie byłoby, gdyby kompilator automatycznie wykrywał wszelkie wywołania tej funkcji ogólnej i generował w razie potrzeby rzeczywisty kod.

C ++ ułatwia to 1 , oferując szablony :

template <typename T> T summation( T *values, size_t numValues ) { T result = 0; for ( size_t i = 0; i < numValues; i++ ) result += values[i]; return result; } 

T to symbol zastępczy dowolnego typu 2 , więc możesz go nazwać

int ivals[] = {1,2,3,4,5,6,7,8,9}; double dvals[] = {1,2,3,4,5,6,7,8,9}; int sumi = summation( ivals, 10 ); double sumd = summation( dvals, 10 ); 

Kiedy kod jest kompilowany, kompilator widzi dwa wywołania summation i dedukuje typy argumentów. Dla każdego typu generuje nowe wystąpienie funkcji, nadając jej unikalną nazwę:

int summation_i( int *values, size_t numValues ) // actual compilers will generate { // more complex "mangled" names int result = 0; // than this ... } double summation_d( double *values, size_t numValues ) { double result = 0; ... } 

Następnie generuje kod taki, że wynik summation_i jest przypisany do sumi, a summation_d jest przypisany do sumd.

C nie oferuje niczego podobnego do funkcji szablonu. Tradycyjnie zaatakowaliśmy programowanie ogólne na jeden z dwóch sposobów – albo za pomocą makr, albo za pomocą void * wszędzie i delegowanie operacji obsługujących typy do innych funkcji.

Oto zły przykład rozwiązania opartego na makrach:

#include <stdio.h> #define SUMMATION_DEF(t) \ t summation_##t( t *values, size_t numValues ) \ { \ t result = 0; \ for ( size_t i = 0; i < numValues; i++ ) \ result += values[i]; \ return result; \ } #define SUMMATION(t,x,s) summation_##t(x, s) SUMMATION_DEF(int) SUMMATION_DEF(double) int main( void ) { int ivals[] = {1, 2, 3, 4, 5}; double dvals[] = {1, 2, 3, 4, 5}; int sumi = SUMMATION(int, ivals, 5); double sumd = SUMMATION(double, dvals, 5); printf( "sumi = %d\n", sumi ); printf( "sumd = %f\n", sumd ); return 0; } 

SUMMATION_DEF jest z grubsza podobna do szablonu w tym sensie, że określa operacje funkcji, używając parametru makra t jako symbolu zastępczego typu. Używamy również t jako część nazwy funkcji – ## jest operatorem wklejania tokenu, a preprocesor rozwinie t i dołącz tę wartość do nazwy funkcji 3 .

To, co różni się od C ++, to fakt, że makro jest po prostu głupim podstawieniem tekstu. Nie wyzwala wszelkie specjalne operacje ze strony kompilatora. Rzeczywiste instancje funkcji nie są generowane automatycznie w oparciu o jakiekolwiek wywołania makra SUMMATION – musimy jawnie wygenerować potrzebne funkcje (stąd SUMMATION_DEF(int) i SUMMATION_DEF(double) przed main). Oznacza to również, że dzwoniąc pod numer summation_xxx poprzez makro SUMMATION musimy przekazać typ jako część listy argumentów makra, tak aby wywołana została właściwa funkcja. Co za ból.

W standardzie C 2011 dodano słowo kluczowe _Generic, które może nieco ułatwić życie w tym zakresie:

#include <stdio.h> #define SUMMATION_DEF(t) \ t summation_##t( t *values, size_t numValues ) \ { \ t result = 0; \ for ( size_t i = 0; i < numValues; i++ ) \ result += values[i]; \ return result; \ } #define SUMMATION(x,s) _Generic((x), \ int * : summation_int, \ double * : summation_double \ )(x, s) SUMMATION_DEF(int) SUMMATION_DEF(double) int main( void ) { int ivals[] = {1, 2, 3, 4, 5}; double dvals[] = {1, 2, 3, 4, 5}; int sumi = SUMMATION(ivals, 5); double sumd = SUMMATION(dvals, 5); printf( "sumi = %d\n", sumi ); printf( "sumd = %f\n", sumd ); return 0; } 

Słowo kluczowe _Generic umożliwia ocenę wyrażeń na podstawie typów ; więc jeśli typ pierwszego argumentu SUMMATION to int *, nazywamy summation_int; to jest „s it” s double *, nazywamy summation_double. W ten sposób nie musimy określać nazwy typu w argumentach makra.

Innym podejściem, jak widzieliście, jest użycie void * i delegowanie operacji obsługujących typy do innych funkcji. Jak powiedziałem powyżej, że ” nie jest to programowanie „ogólne”, ponieważ trzeba ręcznie implementować każdą funkcję porównawczą dla każdego typu. Nie możesz po prostu zakodować tego raz i skończyć z tym. A używając void *, po prostu wyrzucisz bezpieczeństwo typu przez okno i w nadjeżdżający ruch.

I zanim ktokolwiek zacznie narzekać – nie, żadna z tych funkcji sumowania nie sprawdza ani nie radzi sobie z przepełnieniem arytmetycznym. To temat na inny dzień.


  1. Wystarczająco luźne definicje „łatwego”. Język metaprogramowania używany do obsługi szablonów jest kompletny w języku Turing, więc możesz robić * naprawdę niesamowite * i niemożliwe do zrozumienia rzeczy za jego pomocą.
  2. Wystarczająco luźne definicje „dowolnego typu”. Zwróć uwagę, że niezależnie od używanego typu musi obsługiwać operator +=, w przeciwnym razie kompilator będzie na ciebie krzyczał.
  3. Ten kod będzie zepsuty dla typów takich jak unsigned int lub long double, ponieważ mają one w nazwie spacje. Nie znam od razu rozwiązania tego problemu i poświęciłem wystarczająco dużo czasu na tę odpowiedź.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *