Język oparty na ograniczonej liczbie argumentów przekazywanych do funkcji

16

Pomysł jest inspirowany faktem, że operatory takie jak +, -,% itd. Mogą być postrzegane jako funkcje z przekazanym jednym lub dwoma argumentami i bez efektów ubocznych. Zakładając, że ja lub ktoś inny pisze język, który zatrzymuje przekazywanie więcej niż dwóch argumentów, a także działa tylko poprzez wartość zwracaną:

a) czy taki język prowadziłby do łatwiejszego do zrozumienia kodu?

b) czy przepływ kodu byłby jaśniejszy? (zmuszony do większej liczby kroków, potencjalnie mniej interakcji „ukrytych”

c) czy ograniczenia sprawiłyby, że język byłby wyjątkowo nieporęczny w przypadku bardziej złożonych programów.

d) (premia) wszelkie inne komentarze na temat zalet / wad

Uwaga:

Trzeba by jeszcze podjąć dwie decyzje - pierwsza dotyczy tego, czy zezwolić na wprowadzanie danych przez użytkownika poza main () lub jego odpowiednikiem, a także jaka będzie reguła dotycząca tego, co dzieje się przy przekazywaniu tablic / struktur. Na przykład, jeśli ktoś chce, aby jedna funkcja dodawała wiele wartości, mógłby obejść to ograniczenie, łącząc je w tablicę. Można to zatrzymać, uniemożliwiając interakcję tablicy lub struktury z samym sobą, co nadal pozwala na przykład na dzielenie każdej liczby przez inną liczbę, w zależności od jej pozycji.


źródło
4
Cześć. Listy za i przeciw zwykle zawierają złe odpowiedzi. Czy możesz w inny sposób sformułować swoje pytanie, aby uzyskać potrzebne informacje, ale w innym formacie?
MetaFight
22
Twoje rozumowanie nawet nie zaczyna mieć dla mnie sensu. Niektóre funkcje mają kilka argumentów, więc ograniczmy wszystkie funkcje? Zwykle gdy ktoś proponuje arbitralne ograniczenia, istnieje powód, coś do zdobycia. Nie rozumiem, co to może zyskać.
2
Nie chodzi o to, że jest coś z natury nie tak z pytaniami typu „co, jeśli” (choć czasami trudno na nie odpowiedzieć, jak powiedział @MetaFight), ale nawet jeśli nawet ty, który pomyślałeś o tym i zadbałeś o to, by zadać pytanie, tak naprawdę nie możesz nazwać korzyść, to jestem całkiem pewien, że moja początkowa reakcja „co? nie! to głupie, dlaczego to zrobiłeś” jest trafna.
6
Istnieje wiele języków, które dopuszczają tylko jeden argument na funkcję: wszystko oparte na rachunku lambda. Rezultatem jest zwykle funkcją robienia pojedynczą listę argumentów, albo funkcją powrocie funkcję, która pobiera następny argument, aż wszystkie argumenty zostały przetworzone: result = f(a)(b)…(z). Dotyczy to rodziny języków ML, takich jak Haskell, ale także koncepcyjnie w innych językach, takich jak Lisp, JavaScript lub Perl.
amon
3
@Orangesandlemons: OK, a następnie mogę zakodować dowolną liczbę liczb całkowitych w obrębie jednej liczby całkowitej, używając tylko mnożenia i dodawania (do kodowania) oraz dzielenia i odejmowania (do dekodowania). Musisz więc także zabronić liczb całkowitych lub przynajmniej pomnożenia, dodania, podziału i odjęcia. (Jedną z konsekwencji siły programowania jest to, że można zakodować prawie wszystko przy użyciu prawie wszystkiego, a zatem ograniczenie rzeczy jest naprawdę bardzo trudne. Ogólnie rzecz biorąc, ograniczenia w rzeczywistości niczego nie „ograniczają”, po prostu denerwują programistów.)
Jörg W Mittag

Odpowiedzi:

40

Robert C. Martin w swojej książce „Clean Code” zdecydowanie zaleca korzystanie z funkcji z maksymalnie 0, 1 lub 2 parametrami, więc przynajmniej jeden doświadczony autor książki uważa, że ​​kod staje się bardziej przejrzysty przy użyciu tego stylu (jednak na pewno nie tutaj ultimative autorytet, a jego opinie są dyskusyjne).

Tam, gdzie Bob Martin jest IMHO poprawny, to: funkcje z 3 lub więcej parametrami są często wskaźnikami zapachu kodu. W wielu przypadkach parametry mogą być grupowane razem, tworząc połączony typ danych, w innych przypadkach może to być wskaźnik dla funkcji, która po prostu robi za dużo.

Jednak nie sądzę, że dobrym pomysłem byłoby wymyślenie nowego języka w tym celu:

  • jeśli naprawdę chcesz egzekwować taką regułę w całym kodzie, potrzebujesz tylko narzędzia do analizy kodu dla istniejącego języka, nie musisz wymyślać do tego zupełnie nowego języka (na przykład dla C # można by użyć czegoś takiego jak „fxcop” ).

  • czasami łączenie parametrów z nowym typem po prostu nie wydaje się warte wysiłku lub byłoby czystą sztuczną kombinacją. Zobacz na przykład File.Openmetodę ze środowiska .Net. Wymaga czterech parametrów i jestem pewien, że projektanci tego API zrobili to celowo, ponieważ uważali, że byłby to najbardziej praktyczny sposób na zapewnienie różnych parametrów funkcji.

  • czasami zdarzają się sytuacje w świecie rzeczywistym, w których więcej niż 2 parametry upraszczają sprawy ze względów technicznych (na przykład, gdy potrzebujesz mapowania 1: 1 do istniejącego interfejsu API, w którym obowiązują proste typy danych i nie można łączyć różnych parametry w jeden obiekt niestandardowy)

Doktor Brown
źródło
16
Zapach o wielu parametrach jest często tym, że różne parametry faktycznie należą do siebie. Weźmy na przykład obliczenie wskaźnika masy ciała, BMI. Jest to funkcja długości i wagi osoby. f (długość, waga), ale te dwa parametry naprawdę do siebie pasują, ponieważ czy zrobiłbyś to obliczenie przy wzroście jednej osoby i wadze innej osoby? Aby lepiej to przedstawić, otrzymujesz f (osoba), w którym osoba może mieć interfejs wagi i długości.
Pieter B
@PieterB: oczywiście, zobacz moją edycję.
Doc Brown
5
Drobny, mały język nitpick: „może oznaczać zapach kodu” Czy zapach kodu z definicji nie jest wskazówką, że należy coś ponownie rozważyć, nawet jeśli ostatecznie nie zmienisz kodu? Zatem zapach kodu nie byłby „wskazany”. Jeśli określony aspekt wskazuje na możliwość wystąpienia problemu, oznacza to zapach kodu. Nie?
jpmc26,
6
@ jpmc26: Z innego punktu widzenia zapach kodu jest możliwym problemem, a nie wskazaniem jednego ;-) Wszystko zależy od dokładnej definicji zapachu kodu (a kiedy już pachnie, zepsuło się, prawda? ?)
hoffmale
3
@PieterB Czy ktoś tak naprawdę to robi? Teraz musisz utworzyć nową instancję Person za każdym razem, gdy chcesz obliczyć BMI z dwoma dowolnymi wartościami. Jasne, jeśli twoja aplikacja już używa Persons na początku i często robisz coś takiego jak f (person.length, person.height), możesz to trochę wyczyścić, ale tworzenie nowych obiektów specjalnie dla parametrów grupy wydaje się przesadą.
Lawyerson
47

Istnieje wiele języków, które już działają w ten sposób, np. Haskell. W Haskell każda funkcja pobiera dokładnie jeden argument i zwraca dokładnie jedną wartość.

Zawsze jest możliwe zastąpienie funkcji, która przyjmuje n argumentów, funkcją, która przyjmuje n-1 argumentów i zwraca funkcję, która przyjmuje argument ostateczny. Stosując to rekurencyjnie, zawsze można zastąpić funkcję, która pobiera dowolną liczbę argumentów, funkcją, która przyjmuje dokładnie jeden argument. I ta transformacja może być wykonana mechanicznie za pomocą algorytmu.

Nazywa się to Frege-Schönfinkeling, Schönfinkeling, Schönfinkel-Currying lub Currying, po Haskell Curry, który intensywnie badał go w latach 50. XX wieku, Mojżesz Schönfinkel, który opisał go w 1924 roku, i Gottlob Frege, który zapowiedział go w 1893 roku.

Innymi słowy, ograniczenie liczby argumentów ma dokładnie zerowy wpływ.

Jörg W Mittag
źródło
2
Oczywiście, jeśli zezwalasz na zwracanie funkcji. Nawet jeśli nie, wszystko, co musisz zrobić, to wywołać następną funkcję w programie głównym. Tzn. Możesz mieć 1 + 1 + 1, gdzie pierwszy dodatek jest funkcją, która zwraca funkcję do dodatkowego dodania, lub można ją po prostu wywołać dwukrotnie. Ten drugi styl byłby teoretycznie czystszy, czy się mylę?
5
„ma dokładnie zerowy wpływ” - Pytanie OP brzmiało, czy czytelność kodu wzrośnie lub zmniejszy się, i sądzę, że nie twierdzisz, że taka decyzja projektowa dla języka nie ma na to wpływu, prawda?
Doc Brown
3
@DocBrown Mając przyzwoitą moc i przeciążenie operatora, mogę wybrać język curry i sprawić, że będzie wyglądał jak język niespieszny. Przykład: f *(call_with: a,b,c,d,e) Przeciążenie, call_with :aby rozpocząć łańcuch, ,przedłużyć łańcuch i *na LHS, aby wywołać f, przekazując mu każdą zawartość łańcucha pojedynczo. Wystarczająco słaby system przeciążania operatora sprawia, że ​​składnia jest uciążliwa, ale jest to wina systemu przeciążania operatora bardziej niż cokolwiek innego.
Jak
W rzeczywistości curry Haskella redukuje funkcję z n argumentami do funkcji z jednym argumentem zwracającym inną funkcję z n - 1 argumentami.
Ryan Reich
2
@RyanReich Masz rację, że możesz zobaczyć „ducha” „liczby argumentów” funkcji Haskell w jej podpisie typu. Jest to duch, a nie prawdziwy podpis, ponieważ generalnie nie ma możliwości dowiedzenia się, czy ostatnia zmienna typu w podpisie jest również typem funkcji. W każdym razie ten duch, który tam jest, nie unieważnia faktu, że w Haskell wszystkie funkcje pobierają 1 argument i zwracają wartość niefunkcyjną lub inną funkcję, która również przyjmuje 1 argument. Jest to wbudowane w skojarzenie ->: a-> b-> c jest a -> (b-> c). Więc w większości się tutaj mylisz.
Ian
7

W ciągu ostatnich kilku tygodni spędziłem trochę czasu próbując nauczyć się języka komputerowego J. W J prawie wszystko jest operatorem, więc dostajesz tylko „monady” (funkcje, które mają tylko jeden argument) i „dyady” (funkcje z dokładnie dwoma argumentami). Jeśli potrzebujesz więcej argumentów, musisz albo podać je w tablicy, albo podać w „polach”.

J może być bardzo zwięzły, ale podobnie jak jego poprzednik APL, może być również bardzo tajemniczy - ale jest to głównie wynikiem dążenia twórcy do naśladowania matematycznej zwięzłości. Możliwe jest zwiększenie czytelności programu J przy użyciu nazw zamiast znaków do tworzenia operatorów.

Alpheus
źródło
ah, więc pozwala tablicom na interakcję ze sobą.
5

Język oparty na tym, w jaki sposób ogranicza programistę, zależy od założenia, że ​​programista rozumie potrzeby każdego programisty lepiej niż sam programista. Są przypadki, w których jest to faktycznie ważne. Na przykład ograniczenia w programowaniu wielowątkowym wymagające synchronizacji przy użyciu muteksów i semaforów są przez wielu uważane za „dobre”, ponieważ większość programistów zupełnie nie zdaje sobie sprawy z leżących u ich podstaw złożoności specyficznych dla maszyny, które te ograniczenia przed nimi ukrywają. Podobnie, niewielu chce w pełni zrozumieć niuanse wielowątkowych algorytmów odśmiecania; język, który po prostu nie pozwala złamać algorytmu GC, jest lepszy niż język, który zmusza programistę do rozpoznania zbyt wielu niuansów.

Musisz uzasadnić argument, dlaczego jako programista języka rozumiesz przekazywanie argumentów o wiele lepiej niż programiści używający twojego języka, że ​​warto zapobiegać im, robiąc rzeczy, które uważasz za szkodliwe. Myślę, że byłby to trudny argument.

Trzeba też wiedzieć, że programiści będą obejść swoje ograniczenia. Jeśli potrzebują 3 lub więcej argumentów, użyją technik takich jak curry, aby zamienić je w wywołania o mniejszej liczbie argumentów. Jednak często wiąże się to z kosztem czytelności, a nie z jej poprawą.

Większość języków, które znam z tego rodzaju regułami, to esolangi, języki zaprojektowane w celu wykazania, że ​​rzeczywiście możesz działać z ograniczonym zestawem funkcji. W szczególności esolangi, w których każdy znak jest kodem operacyjnym, mają tendencję do ograniczania liczby argumentów, po prostu dlatego, że muszą utrzymywać krótką listę kodów operacyjnych.

Cort Ammon - Przywróć Monikę
źródło
To najlepsza odpowiedź.
Jared Smith
1

Będziesz potrzebował dwóch rzeczy:

  • Zamknięcie
  • Złożony typ danych

Dodam matematyczny przykład, aby wyjaśnić odpowiedź napisaną przez Jörga W. Mittaga .

Rozważ funkcję Gaussa .

Funkcja Gaussa ma dwa parametry dla swojego kształtu, a mianowicie średnią (środkową pozycję krzywej) i wariancję (związaną z szerokością impulsu krzywej). Oprócz dwóch parametrów należy również podać wartość zmiennej swobodnejx , aby ją ocenić.

W pierwszym etapie zaprojektujemy funkcję Gaussa, która przyjmuje wszystkie trzy parametry, a mianowicie średnią, wariancję i zmienną swobodną.

W drugim kroku tworzymy złożony typ danych, który łączy średnią i wariancję w jedną rzecz.

W trzecim kroku tworzymy parametryzację funkcji Gaussa, tworząc zamknięcie funkcji Gaussa związanej ze złożonym typem danych, który utworzyliśmy w drugim kroku.

Na koniec oceniamy zamknięcie utworzone w trzecim kroku, przekazując mu wartość zmiennej swobodnej x.

Struktura jest zatem:

  • Oceń (obliczenia)
    • ParameterizedGaussian (zamknięcie: formuła plus niektóre zmienne powiązane)
      • GaussianParameters (złożony typ danych)
        • Średnia wartość)
        • Wariancja (wartość)
    • X (wartość zmiennej wolnej)
rwong
źródło
1
  1. W prawie każdym języku programowania możesz przekazać jakiś typ listy, tablicy, krotki, zapisu lub obiektu jako jedyny argument. Jego jedynym celem jest przechowywanie innych przedmiotów zamiast indywidualnego przekazywania ich do funkcji. Niektóre środowiska IDE Java mają nawet funkcję „ Wyodrębnij obiekt parametru ”. Wewnętrznie Java implementuje zmienną liczbę argumentów, tworząc i przekazując tablicę.

  2. Jeśli naprawdę chcesz robić to, o czym mówisz, w najczystszej postaci, musisz spojrzeć na rachunek lambda. To jest dokładnie to, co opisujesz. Możesz go wyszukiwać w Internecie, ale opis, który miał dla mnie sens, znajdował się w Typach i językach programowania .

  3. Spójrz na języki programowania Haskell i ML (ML jest prostszy). Oba są oparte na rachunku lambda i koncepcyjnie mają tylko jeden parametr na funkcję (jeśli trochę się zmrużysz).

  4. Przedmiotem 2 Josha Blocha jest: „Zastanów się nad konstruktorem w obliczu wielu parametrów konstruktora”. Możesz zobaczyć, jak to się dzieje , ale przyjemność jest pracować z API napisanym w ten sposób.

  5. Niektóre języki nazwały parametry, co jest innym podejściem, które znacznie ułatwia nawigację po dużych podpisach metod. Kotlin nazwał na przykład argumenty .

GlenPeterson
źródło