Wygląda na to, że wybierasz przeciążenie terminologii zarówno „przestrzeni nazw”, jak i „modułu”. Nie powinno być zaskoczeniem, że postrzegasz rzeczy jako „pośrednie”, gdy nie pasują do twoich definicji.
W większości języków obsługujących przestrzenie nazw, w tym C #, przestrzeń nazw nie jest modułem. Przestrzeń nazw to sposób określania nazw. Moduły są sposobem zachowania zakresu.
Ogólnie rzecz biorąc, podczas gdy środowisko uruchomieniowe .Net obsługuje ideę modułu (z nieco inną definicją niż ta, której używasz domyślnie), jest on raczej rzadko używany; Widziałem go tylko w projektach zbudowanych w SharpDevelop, głównie po to, aby można było zbudować pojedynczą bibliotekę DLL z modułów zbudowanych w różnych językach. Zamiast tego budujemy biblioteki za pomocą dynamicznie połączonej biblioteki.
W języku C # przestrzenie nazw są rozwiązywane bez „warstwy pośredniej”, o ile wszystkie są w tym samym pliku binarnym; każda wymagana pośrednia odpowiedzialność spoczywa na kompilatorze i linkerze, o której nie trzeba się wiele zastanawiać. Gdy zaczniesz budować projekt z wieloma zależnościami, odwołujesz się do bibliotek zewnętrznych. Gdy Twój projekt znajdzie odniesienie do biblioteki zewnętrznej (DLL), kompilator znajdzie go dla Ciebie.
W schemacie, jeśli chcesz załadować bibliotekę zewnętrzną, musisz (#%require (lib "mylib.ss"))
najpierw zrobić coś takiego lub bezpośrednio użyć obcego interfejsu funkcji, jak pamiętam. Jeśli korzystasz z zewnętrznych plików binarnych, masz tyle samo pracy do rozwiązania zewnętrznych plików binarnych. Możliwe, że najczęściej korzystałeś z bibliotek tak często używanych, że istnieje podkładka oparta na schemacie, która wyodrębnia to z ciebie, ale jeśli kiedykolwiek będziesz musiał napisać własną integrację z biblioteką innej firmy, w zasadzie będziesz musiał trochę popracować, aby „załadować” " Biblioteka.
W Rubim moduły, przestrzenie nazw i nazwy plików są w rzeczywistości znacznie mniej połączone niż się wydaje; ścieżka LOAD_PATH komplikuje sprawy, a deklaracje modułów mogą być wszędzie. Python jest prawdopodobnie bliżej robienia rzeczy tak, jak myślisz, że widzisz w Schemacie, z tym wyjątkiem, że biblioteki stron trzecich w C nadal dodają (małe) zmarszczki.
Ponadto języki dynamicznie typowane, takie jak Ruby, Python i Lisp, zwykle nie mają takiego samego podejścia do „kontraktów”, jak języki statycznie typowane. W dynamicznie wpisywanych językach zwykle ustanawia się tylko „umowę dżentelmena”, zgodnie z którą kod będzie reagował na określone metody, a jeśli twoje klasy wydają się mówić tym samym językiem, wszystko jest dobrze. Języki o typie statycznym mają dodatkowe mechanizmy do egzekwowania tych reguł w czasie kompilacji. W języku C # korzystanie z takiej umowy umożliwia dostarczenie co najmniej umiarkowanie użytecznych gwarancji zgodności z tymi interfejsami, co pozwala łączyć wtyczki i zastępowania z pewnym stopniem gwarancji wspólności, ponieważ wszyscy kompilują się w oparciu o tę samą umowę. W Ruby lub Scheme weryfikujesz te umowy, pisząc testy, które działają w czasie wykonywania.
Wymierne korzyści wydajnościowe wynikają z tych gwarancji czasu kompilacji, ponieważ wywołanie metody nie wymaga podwójnej wysyłki. Aby uzyskać te korzyści w czegoś takiego jak Lisp, Ruby, JavaScript lub gdzie indziej, potrzebne są obecnie nieco egzotyczne mechanizmy statycznego kompilowania klas na czas w wyspecjalizowanych maszynach wirtualnych.
Jedną rzeczą, dla której ekosystem C # nadal ma stosunkowo niedojrzałe wsparcie, jest zarządzanie tymi zależnościami binarnymi; Java ma Maven od kilku lat, aby upewnić się, że masz wszystkie wymagane zależności, podczas gdy C # nadal ma dość prymitywne podejście podobne do MAKE, które obejmuje strategiczne umieszczanie plików we właściwym miejscu przed czasem.