Który aspekt programowania jest ważniejszy?

0

Mam sobie bibliotekę, w której są takie funkcyjne metody jak map(), filter(), forEach(), reduce(), flatMap() i takie podobne. Oczywiście ich parametrem jest callback/funkcja.

W niektórych językach programowania, callbacki do takich funkcji dostają index, np w JavaScript dostajemy index jako drugi parametr w map():

['hi'].map((value, index) => {
});

podobnie jest z filter(), reduce(), etc.

W JavaScript to działa, bo we wszystkich funkcjach nadmiarowe argumenty są ignorowane, tzn działają oba takie kody:

[].map(() => {});
[].map(value => {});
[].map((value, index) => {});

Czemu PHP był projektowany przez pijanych klaunów

Ale oczywiście, w PHP, jakże by inaczej, nie jest tak pięknie, bo tak jak parametry do funkcji anonimowych i metod są ignorowane, tak parametry do wbudowanych funkcji już nie są - widocznie jakiś kodzik w C jest pod spodem, który to waliduje.

Więc np w mojej bibliotece, taki kod by przeszedł:

$match = Pattern::of('\w+')->match("Welcome");

$match->map(function ($match, $index) { // $match oraz $index

});

tak już nie przejdzie całkowicie z wbudowanymi

$match = Pattern::of('\w+')->match("Welcome");

$match->map('strToUpper');  // <!--- tutaj błąd

Dostajemy

ArgumentCountError : strtolower() expects exactly 1 argument, 2 given
 C:\Users\Riddle\PhpstormProjects\T-Regx\test\Feature\CleanRegex\Match\_countable\MatchPatternTest.php:34

Pytanie

Noi, co teraz począć. Chętnie dodałbym ten drugi argument $index, żeby był przekazywany do tych funkcji

$match = Pattern::of('\w+')->match("Welcome");

$match->forEach(function ($match, $index) {});
$match->map(function ($match, $index) {});
$match->filter(function ($match, $index) {});
$match->flatMap(function ($match, $index) {});
$match->reduce(function ($match, $index) {});

i to będzie działało z callbackami, anonimowymi funkcjami i referencjami do metod, ale z wbudowanymi funkcjami nie - przynajmniej nie bezpośrednio.

Noi co zrobić?

8
<ot> Że też musiałeś właśnie z PHPem związać swoje życie... </ot>
2

JS jest chyba jedynym językiem, który pozwala na coś takiego. Za bardzo też nie widzę zastosowań, skoro można to rozwiązać czymś w stylu pythonowego enumerate(). Dla mnie im prostszy język tym lepiej, a skoro da się osiągnąć to samo używając gotowych klocków to bardziej skłaniałbym się ku temu, że JS był projektowany przez pijanych klaunów

8

Nie znam PHP i w zasadzie nie znam nowoczesnego JS, ale się wypowiem bo to już zahacza o programowanie funkcyjne.
Czemu map(), filter(), reduce(), flatMap() nie mają indeksów?

  • Bo były wymyślane w świecie funkcyjnym gdzie iteracja odbywa się na rekurencji a nie na pętli z indeksem więc indeks to dodatkowy koszt
  • Bo jeśli iterujesz przez iterator a nie przez indeks to zdobycie indeksu to dodatkowy koszt
  • Bo to co proponujesz nie działa w językach statycznie typowanych (Musiało by być przeciążenie metody/funkcji map i reszty po typie lambdy)

Jakie jest rozwiązanie w innych językach? Może można by próbować przeciążać, ale ja coraz bardziej nie lubię przeciążania. Jak googluję na szybko to:

  • w Haskellu jest imap, ifilter, ifoldr (w Haskellu jest fold zamiast reduce),
  • W Scali polecają zrobić najpier w zipWithIndex i potem pracować na tupli (element, index), np List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach{ case (e, i) => println(i+" "+e) }
0

Może mógłbyś sprawdzać czy podany argument jest stringiem i jeśli jest to podawać jeden argument?

Ale z drugiej strony nie wszystkie funkcje wbudowane będą miały jeden parametr tylko, wiec mógłbyś zrobić tablice asocjacyjna mapujaca np. „strupper” => 1

Czy to co piszę, ma jakiś sens w kontekście PHP i tego, co chcesz zrobić?

Ew. Prostsze rozwiązanie i to użytkownik będzie musiał owrappować funkcje wbudowana w funkcje anonimowa. No bo jeśli język ma jakieś ograniczenia, to trudno. Analogicznie jeśli w JS bym miał taką tablicę ["1", "2", "3"], to taki kod:

 ["1", "2", "3"].map(parseInt)

by nie zadziałał prawidłowo, bo map podaje jako 2 argument index, ale parseInt jako drugi argument przyjmuje base (np. parseInt('2A', 16)
więc trzeba by owinąć wywołanie w funkcję anonimową:

 ["1", "2", "3"].map(x => parseInt(x))
 // czyli (bardziej explicite):
 ["1", "2", "3"].map((x, index, array) => parseInt(x))

Nie mam z tym problemu.

0
KamilAdam napisał(a):

Nie znam PHP i w zasadzie nie znam nowoczesnego JS, ale się wypowiem bo to już zahacza o programowanie funkcyjne.
Czemu map(), filter(), reduce(), flatMap() nie mają indeksów?

  • Bo były wymyślane w świecie funkcyjnym gdzie iteracja odbywa się na rekurencji a nie na pętli z indeksem więc indeks to dodatkowy kosz
  • Bo jeśli iterujesz przez iterator a nie przez indeks to zdobycie indeksu to dodatkowy koszt
  • Bo to co proponujesz nie działa w językach statycznie typowanych (Musiało by być przeciążenie metody/funkcji map i reszty po typie lambdy)

Jakie jest rozwiązanie w innych językach? Może można by próbować przeciążać, ale ja coraz bardziej nie lubię przeciążania. Jak googluję na szybko to:

  • w Haskellu jest imap, ifilter, ifoldr (w Haskellu jest fold zamiast reduce),
  • W Scali polecają zrobić najpier w zipWithIndex i potem pracować na tupli (element, index), np List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach{ case (e, i) => println(i+" "+e) }

Dzięki za wartościowy wpis, wielu rzeczy stąd nie wiedziałem.

Może powinienem dodać, że w mojej bibliotece można użyć tych funkcji tak:

$match = Pattern::of("foo")->match("foo");

$match->stream()
  ->map($tutajJakiśMap)
  ->filter($tutajJakiśFilter)
  ->flatMap($tutajJakiśFlatMap)
  ->groupBy($tutajJakiśGroupBy)
  ->reduce($tutajJakiśReducer);

I powiedzmy że np w $tutajJakiśFlatMap potrzebuję dostać $index. Nie widzę prostego sposobu jakby można to było zrobić używając tego co zaproponowałeś.

LukeJL napisał(a):

Może mógłbyś sprawdzać czy podany argument jest stringiem i jeśli jest to podawać jeden argument?

Nie zadziała z dwóch powodów:

  1. Nie każdy string jest poprawnym callbackiem
  2. Nie każda wbudowana funkcja ma jeden parametr, niektóre mają 0 lub 2 i więcej (co już zauważyłeś, a robienie mapy dla każdej funkcji nie wchodzi w grę zupełnie).
  3. Nie tylko string jest poprawnym callbackiem w PHP, ale też np [$this, 'method'] oraz [Test::class, 'function'] oraz \Closure i inne takie.
LukeJL napisał(a):

Ew. Prostsze rozwiązanie i to użytkownik będzie musiał owrappować funkcje wbudowana w funkcje anonimowa. No bo jeśli język ma jakieś ograniczenia, to trudno.

No tak, to jest opcja w ankiecie: Dodać $index - używać wrapperów na wbudowane funkcje, możesz na nią oddać głos. Tylko właśnie - ja jestem świadom że język ma takie ogranicznie. Jeśli nie dodam parametru $index, to wtedy wszystko działa jak należy. Stąd pytanie - czy lepiej nie mieć $index jako parametr, czy lepiej nie pozwolić na używanie wbudowanych funkcji bez wrappera.

LukeJL napisał(a):

Analogicznie jeśli w JS bym miał taką tablicę ["1", "2", "3"], to taki kod:

 ["1", "2", "3"].map(parseInt)

by nie zadziałał prawidłowo, bo map podaje jako 2 argument index, ale parseInt jako drugi argument przyjmuje base (np. parseInt('2A', 16)
więc trzeba by owinąć wywołanie w funkcję anonimową:

True.

0
Riddle napisał(a):

Może powinienem dodać, że w mojej bibliotece można użyć tych funkcji tak:

$match = Pattern::of("foo")->match("foo");

$match->stream()
  ->map($tutajJakiśMap)
  ->filter($tutajJakiśFilter)
  ->flatMap($tutajJakiśFlatMap)
  ->groupBy($tutajJakiśGroupBy)
  ->reduce($tutajJakiśReducer);

I powiedzmy że np w $tutajJakiśFlatMap potrzebuję dostać $index.

flatMap to wredny przypadek bo skoro spłaszczasz to znaczy że masz dwie kolekcje (a w zasadzie (hahaha) dwie monady, bo monada w uproszczeniu to tyle co interfejs FlatMapable :P ). Więc podstawowe pytanie to czy ma byc to indeks z wewnętrznej kolekcji czy z zewnętrzne? A jak z zewnętrzne to przed odfiltrowaniem czy po dofiltrowaniu?

Nie widzę prostego sposobu jakby można to było zrobić używając tego co zaproponowałeś.

A nie masz metody zipWithIndex? A jak nie masz to czy możesz mieć:

  • metodę zip która zipuję dwie kolekcje. w Scali to wygląda firstCollection.zip(secondCollection) i rezultatem jest kolekcja par elementów.
  • nieskończony stream liczb robiących za indeks

? Wtedy po dopisaniu funkcji zip można zrobić to od biedy tak

$firstStream = stream()
->map($tutajJakiśMap)
->filter($tutajJakiśFilter)

zip($firstStream, $streamOfIndex) -> flatMap($tutajJakiśFlatMappracującyNaParze)

W Javie takie cudowanie wychodzi brzydko, ale działa. W Scali gdzie można dopisać tak funkcję żeby wyglądała jak część istniejącej klasy wygląda to trochę lepiej. Nie wiem jakie sa możliwości PHP

0
KamilAdam napisał(a):

Nie widzę prostego sposobu jakby można to było zrobić używając tego co zaproponowałeś.

A nie masz metody zipWithIndex? A jak nie masz to czy możesz mieć:

Nawet jakbym miał, to to by znaczyło że te metody filter(), map() etc. mają zmienny interfejs. Że czasem przyjmują jeden argument, czasem dwa. Nie podoba mi się to.

0

W Scali to jest dalej jeden argument, bo tupla dwóch elementów to jeden argument :P

0
KamilAdam napisał(a):

W Scali to jest dalej jeden argument, bo tupla dwóch elementów to jeden argument :P

No to nadal jest zmienny interfejs, albo tupla z jednym elementem albo dwoma.

0

Hm, powoli nie rozumiem o czyim interfejsie mówisz? O interfejsie metod map(), filter(), forEach(), reduce(), flatMap() czy o Interfejsie lambd używanyc w tych metodach?

Hm2, No bo przywykłem że map i reszta są generyczne więc mogą działać dla dowlnlego elementu w tym elementu który ma postać Tupla[E, Index] gdzie E to jakiś inny element a Index to indeks czyli pewnie Integer. W rezultacie mozna w Scali tworzyć kod w rodzaju:

object MyClass extends App {
  val list: List[String] = List(9, 8, 7, 6, 5, 4, 3, 2, 1).map(_.toString)

  val listWithIndex = list.zipWithIndex

  //brzydkie iterowanie po tupli
  listWithIndex.foreach(t => println(s"${t._1} -> ${t._2}"))

  //ładne iterowanie po tupli z Scalowym cukrem składniowym, chyba się to dekompozycja struktur zwało, a może jakiś inaczej?
  listWithIndex.foreach {case (element, index) => println(s"$element -> $index") }
}

Jak masz nie generyczne map i inne metody to IMHO najlepiej wpewnie zrobić wersje z prefiksem i* lub sufiksem *WithIndex (kwestia estetyki). Innym pomysłów nie mam

0
KamilAdam napisał(a):

Jak masz nie generyczne map i inne metody to IMHO najlepiej wpewnie zrobić wersje z prefiksem i* lub sufiksem *WithIndex (kwestia estetyki). Innym pomysłów nie mam

Ja też nie, stąd dylemat. Albo dodać argument $index z automatu, ale wtedy nie da się użyć wbudowanych funkcji bezpośrednio, albo nie dodawać tego argumentu.

1
Riddle napisał(a):

Czemu PHP był projektowany przez pijanych klaunów

Czy masz jakieś źródła potwierdzające tę informację lub przynajmniej taką, że którykolwiek z projektantów był klaunem?

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