Projektowanie literału idealnego zasięgu

10

Zastanawiałem się, jak zająć się projektowaniem literału „idealnego” zakresu, gdybym zaprojektował język. Dla tych, którzy nie znają literału zakresu w instrukcji reprezentującej zakres wartości, np. 1-4. Są najczęściej używane w pętlach for / foreach

Wydaje się, że należy wziąć pod uwagę kilka kwestii

  • Obsługa przedziałów włączających i wyłącznych, przypisywanie +1 lub -1 do punktów końcowych wydaje się nieco nieporadne i podatne na błędy.

  • Obsługa stopniowania, dzięki czemu można na przykład ustawić zakres liczb parzystych lub nieparzystych

  • Czytelność, powinno być oczywiste, co opisuje literał zakresu

  • Jednoznaczność, powinno być całkowicie niedwuznaczne, co opisuje literał zakresu

  • Domyślnie powinna to być wartość od włączającej do wyłącznej, ponieważ w większości przypadków używa się jej do zapętlania tablic itp.

Tak czy inaczej, jednym z przykładów literału zakresu, jaki widziałem, jest Ruby, który ma postać 1..3 dla wyłącznego (na końcu) zakresu i 1 ... 3 dla włącznie (na końcu). Możesz także wykonać 1..10. krok (5). Po dokładnym rozważeniu znalazłem jednak kilka rzeczy, które nie podobały mi się w tym podejściu (z mojej ograniczonej znajomości rubinu)

  1. Możesz opisać tylko i wyłącznie na koniec. Opisywanie większości scenariuszy wydaje się nieco niespójne.

  2. Różni się tylko o dodatkowy. wydaje się być przepisem na utrudnienie sprawdzenia, czy dany zakres jest włączający, czy wyłączny. Nie wiem o tobie, ale kropki stają się czasem rozmazane :)

  3. Dodanie metody takiej jak notacja dla zakresów wydaje się mieszać pojęcie literału z pojęciem klasy, co wydaje się nieco niespójne (nawet jeśli zakresy zostaną skompilowane do klasy)

W każdym razie po rozważeniu różnych alternatyw. Wymyśliłem to

  • [5..1] 5,4,3,2,1
  • [1..5 [ 1,2,3,4
  • ] 1..5] 2,3,4,5
  • [ 0..5..20] 0,5,10,15,20

i tak dalej. Podoba mi się, ponieważ [normalnie denonuje zestaw i ten rodzaj pasuje do tego, nawet jeśli byłoby to inaczej niż zestaw.

Jedną z rzeczy, które mnie trochę podrażniają, jest wprowadzenie obowiązkowych wskaźników wykluczających / włączających, tj. Jeśli napiszesz tylko 1..5, domyślnie wyniesie 1,2,3,4, ponieważ jest to najczęstszy przypadek z tablicami itp. Jest łatwiejszy i bardziej czytelny, ale mniej konkretny, a gdybyś musiał napisać [1..5 [wcześnie] dowiadujesz się, jak działają.

Jak myślisz, czy obejmowałem większość baz, przeoczyłem coś? czy uczyniłbyś [] obowiązkowym? Czy projektowałbyś literały zasięgu inaczej w swoim języku programowania?

Kandydaci

  • styl nawiasu: [0..10 [ , z krokiem: [0..5..20 [
  • notacja interwałowa: [0..10) z krokiem: [0..5..20)
  • wykrzyknik na wyłączność. 0 ..! 10, z krokiem: 0..5 ..! 20
    • z innym krokiem. 0 ..! 20, 5
    • sprawiłoby to, że domyślna * 0..10 ' włącznie
  • wordy: [0 do! 20 na 5]

Muszę powiedzieć, że moim ulubionym do tej pory pod względem estetycznym jest 0 ..! 10 i 0..5 ..! 20 , chciałbym tylko, aby domyślna wartość 0..10 obejmująca wyłącznie była bardziej logiczna

Homde
źródło
2
1,5,10,15,20Luka 4, 5, 5, 5 ?!
Peter Taylor,
ups :) poprawione
Homde

Odpowiedzi:

14

Po co wymyślać to, co już istnieje w matematyce?

Notacja interwałowa

Powinno to obejmować punkt „jednoznaczności”, ponieważ już istnieje.

Twoim jedynym problemem byłoby zdefiniowanie stopniowania. Może coś innego w matematyce może w tym pomóc?

Dan McGrath
źródło
3
Cześć, właściwie mam notację interwałową. Jednak to, co sprawiło, że nie chciałem go używać, to fakt, że nie był symetryczny zarówno z [i) i używał nawiasów, jak i nie posiadał kroków (jak wspomniałeś). Używanie nawiasów wydaje się potencjalnie mylić dopasowanie nawiasów klamrowych i wolałbym zachować je w wyrażeniach. Używanie] i [jest standardem iso i wydawało się, że idzie lepiej z integracją stepowania, ale to tylko moja opinia, oczywiście.
Homde
7
@MKO: Twój pomysł użycia niedopasowanych nawiasów kwadratowych (jak [1..5[) dezorientuje redaktorów przynajmniej tak bardzo, jak standardowy zapis interwału.
David Thornley,
2
Hmm, kolejny pomysł, co z użyciem! dla, tj .: 1 ..! 5 lub 0..5 ..! 20 (ze
schodkami
1
@MKO: Używanie !do wykluczania pierwszego lub ostatniego elementu może być dobre.
FrustratedWithFormsDesigner
8

Widziałeś zakresy Haskell? Ich pomysł na kroki jest podobny do twojego (w środku), ale zaznaczony różnicą składniową (od odpowiedzi @ luqui ):

[1,2..10] = [1,2,3,4,5,6,7,8,9,10]
[1,3..10] = [1,3,5,7,9]
[4,3..0]  = [4,3,2,1,0]
[0,5..]   = [0,5,10,15,20,25,30,35...  -- infinite
[1,1..]   = [1,1,1,1,1,1,1,1,1,1,1...  -- infinite

Uważam tę notację za bardzo intuicyjną: zaczynasz wyliczać i przeskakiwać nad ciałem, aby zaznaczyć, gdzie powinno się kończyć.

Nie obejmuje przypadku „wyłączności”, ale szczerze mówiąc wolałbym jednoznacznie powiedzieć o tym zachowaniu (określić, która granica jest włączająca, a która wyłączna), i oczekuje od użytkownika wprowadzenia zmian, chyba że składnia jest wyjątkowo jasna (i nie myli redaktorów agnostycznych z językiem).

Matthieu M.
źródło
5

Powinieneś przeczytać o listach w językach, które je oferują.

Na przykład Python całkiem ładnie omawia twoje przypadki dzięki range()funkcji i zrozumieniu listy dla spraw zbyt skomplikowanych, by je wyrazić range().

S.Lott
źródło
2

Wierzę, że twoja notacja jest daleka od bycia jednoznacznym. Intuicyjnie, [0..5..20]nie wygląda mi to na zakres o szerokości kroku = 5. W niektórych przypadkach narożnych nawet się nie udaje: co z wyrażeniem zestawu (0,2) - [0..2..2]lub co powinienem umieścić na środku?

Myślę, że notacja plastra Pythona jest solidnym podejściem: [start:end:step](krok jest opcjonalny).

Jeśli naprawdę potrzebujesz wsparcia dla zakresów wyłącznych i włączających, użyłbym standardowej notacji zakresu (tj. Używając (i [), chyba że wprowadzi to dwuznaczności składniowe w twoim języku.

Alexander Gessler
źródło
1
dotyczące „standardowego” notacji, szkoły europejskie uczyć [], [[, ]]i ][, bez nawiasu ... i nasz standard jest lepiej, oczywiście;)
Matthieu M.
@Matthieu: Faktycznie, tutaj w Holandii (który jest w Europie), byliśmy również nauczał, że standardowym zapisie.
Frits
1

Myślę, że kiedy ustawiasz trzecią wartość przyrostu, lepiej jest dodać ją do końca, a nie do środka, ponieważ jest to wartość opcjonalna i wydaje się bardziej intuicyjna dla doświadczonych programistów niż dodawanie opcjonalnego trzeciego argumentu w środku .

więc zamiast [1..5..20] możesz zrobić coś takiego jak [1..20] [5] lub [1..20,5]

Canuteson
źródło
To ważna kwestia i być może kwestia gustu, po prostu wydawało mi się, że łatwiej byłoby pomyśleć „1 krok od 5 do 20” niż „od 1 do 2, z krokiem 5” przy użyciu [1..20] [5] wydaje się nieco zagracony, ale być może podejście z przecinkiem byłoby wykonalne. Byłbym wdzięczny za więcej opinii na ten temat
Homde
1
  • [1..5 [1,2,3,4
  • ] 1..5] 2,3,4,5
  • ] 1..5 [2,3,4

Dlaczego nie użyć po prostu [1..4], [2..5], [2..4]? Wykluczenie zakresów nie oferuje dużych korzyści. Nie musisz pisać MIN + 1 ani MAX-1, tak, ale i tak tego nie zabraniają. Zbyt duża różnorodność, imho. Mój wybrany język, scala, ma (1 do 3) i (1 do 3) [= (1 do 2)], ale na początku tylko mnie pomylił. Teraz zawsze używam „x do y” i dlatego wiem, że opcja, której nie używam, jest wykluczająca, ale oczywiście nie korzystam z opcji, której nie używam.

  • [5..1] 5,4,3,2,1

jest oczywiście (1 do MAKS.) (x => y = (MAKS. + 1) - x), ale jest to duża płyta kotłowa i nie jest przyjazna dla użytkownika.

  • [0..5..20] 0,5,10,15,20

jest oczywiście (od 0 do 4) (x => y = x * 5) to nie tyle płyty kotłowej. Sporny.

nieznany użytkownik
źródło
Główną zaletą wykluczania zakresów, afaik, jest to, że liczba elementów jest różnicą punktów końcowych.
Justin L.,
@JustinL .: 5-1 ma 4 w mojej algebrze, ale zawiera 3 elementy, wyłączając granice: 2, 3, 4.
użytkownik nieznany
1

Co powiesz na coś takiego:

  • [5..1] 5,4,3,2,1
  • [1..5] [i, e] 1,2,3,4
  • [5..1] [i, e] 5,4,3,2
  • [1..5] [e, i] 2,3,4,5
  • [1..5] [e, e] 2,3,4
  • [0..20] o 5 0,5,10,15,20

iA ew drugiej pary nawiasach kwadratowych wskazuje, czy początek lub zakończenie zakresie jest włącznie lub wyłączne (lub można użyć incl, a excljeśli chcesz być bardziej jasne). by 5wskazuje przedział impulsowania. Pierwszy i ostatni przykład zawierają pierwszą i ostatnią wartość, więc pominąłem [i,i], co moim zdaniem byłoby OK zakładanym domyślnym zachowaniem.

Wartości domyślne mogą dotyczyć [i,i]specyfikatora włączającego / wyłącznego i by 1specyfikatora kroku.

Normalnie sugerowałbym standardową notację obejmującą (i, [jak wspomniano @Dan McGrath, ale zgadzam się, że w kodzie może to wyglądać na mylące.

FrustratedWithFormsDesigner
źródło
Czy ktoś może wyjaśnić opinię negatywną?
FrustratedWithFormsDesigner
Nie widziałem nic wyraźnie złego . Głosowałem za przeciwdziałaniem głosowaniu negatywnemu.
Berin Loritsch,
1

A zwycięzcą jest

Przepraszam, że wybrałem własne rozwiązanie. Naprawdę doceniam wszystkie komentarze i opinie. Proszę o krytykę tego rozwiązania, jeśli znajdziesz coś złego

Więc zdecydowałem się na:

0..5           = 0,1,2,3,5
0..5..10       = 0,5,10
..3            = 0,1,3
!0..!5         = 1,2,3,4
3..            = 3 to infinity

segmented ranges
-
0..5,..5..20   = 0,1,2,3,4,5,10,15,20
0..3,10..5..20 = 0,1,2,3,10,15,20

Jak widać, włączanie jest domyślne dla obu zmysłów, dlatego intuicyjnie jest najbardziej sensowne, szczególnie podczas korzystania ze stepowania. Możesz użyć ! aby koniec był wyjątkowy.

Minusem jest to, że zwykle chcesz używać wyłączności podczas pracy przeciwko macierzy, ale z drugiej strony, w większości przypadków prawdopodobnie powinieneś użyć czegoś takiego jak for-each w takich przypadkach. Jeśli naprawdę potrzebujesz pracy z indeksem, analizator składni może rozpoznać, że prawdopodobnie zapomniałeś znacznika wykluczenia na końcu i wygenerował ostrzeżenie

Poszedłem z użyciem ... po obu stronach zmiennej step zamiast przecinkiem lub czymkolwiek innym, ponieważ to właśnie wyglądało najczystiej i mam nadzieję, że jest najbardziej czytelne.

Wrzuciłem też składnię z przecinkami, aby móc tworzyć zakresy, w których różne części mają różne kroki

Homde
źródło
Wydaje się być w porządku, chyba że podasz krok. Myślę, że byłoby to czystsze jako [0..10 na 5]. Zakres podzielony na segmenty może wynosić [0..5, ..20 na 5] itd. Można również określić [liczby od pierwszego do kroku], co (w przeciwieństwie do innych form) może pozwolić na wielkość kroku równą zero.
supercat
0

Nie użyłbym formularza „[1..5 [”] z tego samego powodu, dla którego uniknąłbyś mieszania parens i nawiasów klamrowych - pomyliłoby to dopasowanie nawiasów większości istniejących edytorów. Być może użyj znacznika wewnątrz nawiasów klamrowych, na przykład „[1 .. | 5]”.

Inną rzeczą do rozważenia jest zachowanie metod w celu uzyskania limitów. Na przykład „początek” / „koniec” vs. „pierwszy” / „ostatni” vs. „min” / „maksymalny”. Muszą być rozsądni zarówno w przypadku limitów włączających i wyłącznych, jak i tych o rozmiarze kroku.

AShelly
źródło
0

Ruby ma notację i działający obiekt Range . Zasadniczo wygląda to tak:

1..5  # range 1,2,3,4,5

W Ruby wielkość kroku nie jest funkcją zakresu, ale raczej iteracją w całym zakresie. Możemy użyć tego samego zakresu powyżej i iterować go inaczej, jeśli chcemy:

(1..5).step(3) { |x| puts x }
(1..5).step(2) { |x| puts x }

Oto rzeczy, które warto rozważyć:

  • Dodanie obsługi języka dla punktów końcowych włączających / wyłączających może być problematyczne. Jeśli wszystkie zakresy są włączające (wybór Ruby), programiści odpowiednio dostosują punkty końcowe zakresu. Jak reprezentujesz włączenie lub wyłączność punktów końcowych w sposób, który jest zarówno wizualnie jednoznaczny, jak i jednoznaczny za pomocą prostej gramatyki parsera?
  • Czy używasz tego zakresu do więcej niż iteracji? Typowe przykłady obejmują porównania zakresów, łączenie, porównania wartości w zakresie. Jeśli tak, jak reprezentujesz te możliwości? (Zobacz link do klasy Range, aby znaleźć pomysły na każdy z tych przykładów).

BTW, zakresy są raczej miłe, jeśli pozwalasz na ich uwzględnienie w instrukcji switch, tak jak robi to Ruby:

switch(myval)
    when 0..33 then puts "Low"
    when 34..66 then puts "Medium"
    when 67..100 then puts "High"
end

Nie twierdzę, że obiekt zakresu Ruby jest końcem wszystkiego i bądź wszystkim, ale jest to działająca implementacja koncepcji, o której mówisz. Graj z nim, aby zobaczyć, co lubisz, a czego nie, i co robisz inaczej.

Berin Loritsch
źródło
Przeczytaj uważnie pytanie, OP już zna zakresy Ruby:Anyways, one example of range literal I've seen is Ruby which is in the form of 1..3 for an exclusive (on the end) range and 1...3 for inclusive (on the end). You can also do 1..10.step(5).
FrustratedWithFormsDesigner
0

Jednym z rozwiązań, które z pewnością rozważę, jest użycie przeciążenia operatora, aby umożliwić np

1+[0..3]*5

Niekoniecznie nie byłby natychmiast przejrzysty dla wszystkich, ale kiedy przestaną i pomyślą, że mam nadzieję, że większość programistów to zrozumie, a ludzie, którzy regularnie używają tego języka, po prostu nauczą się go jako idiomu.

Aby to wyjaśnić, wynikiem byłoby [1, 6, 11, 16]podniesienie mnożenia, a następnie dodawanie ponad zakres. Nie zdawałem sobie sprawy, że użytkownicy Pythona mogą być niejednoznaczni; Myślę o zakresach jako o podzestawach całkowitych zamówień, a nie o listach. a powtarzanie zestawu nie ma sensu.

Peter Taylor
źródło
2
Bardzo niejednoznaczne. list expression * 5może również oznaczać „powtórz wartość list expression5 razy”, jak ma to miejsce w Pythonie.
nikie
To wygląda bardzo dziwnie i nie jestem pewien, co by to było ... 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3? 1,3,6,9,12? 5,10,15? może coś jeszcze? Tak czy inaczej, uważam, że ten zapis jest całkowicie sprzeczny z intuicją. Wygląda na to, że to może jakiś dziwny operacji wskaźnik C ...
FrustratedWithFormsDesigner