We wdrożeniach języka programowania Scheme (standard R6RS) mogę zaimportować moduł w następujący sposób:
(import (abc def xyz))
System spróbuje wyszukać plik, w $DIR/abc/def/xyz.sls
którym $DIR
znajduje się katalog, w którym przechowywane są moduły Scheme. xyz.sls
jest kodem źródłowym modułu i w razie potrzeby jest on kompilowany w locie.
Systemy modułów Ruby, Python i Perl są pod tym względem podobne.
Z drugiej strony C # jest nieco bardziej zaangażowany.
Po pierwsze, masz pliki dll, do których musisz się odwoływać dla poszczególnych projektów. Musisz wyraźnie się do nich odwoływać. Jest to bardziej zaangażowane niż powiedzmy, upuszczając pliki dll do katalogu i każąc C # odbierać je według nazwy.
Po drugie, między nazwą pliku dll a przestrzeniami nazw oferowanymi przez dll nie ma żadnej relacji między nazwami. Mogę docenić tę elastyczność, ale może również wymknąć się spod kontroli (i ma).
Aby uczynić to konkretnym, byłoby miło, gdyby, gdy to powiem using abc.def.xyz;
, C # spróbowałby znaleźć plik abc/def/xyz.dll
w jakimś katalogu, w którym C # wie, że może go szukać (konfigurowalny dla każdego projektu).
Uważam, że sposób obsługi modułów Ruby, Python, Perl, Scheme jest bardziej elegancki. Wydaje się, że nowe języki zwykle mają prostszy wygląd.
Dlaczego świat .NET / C # robi to w ten sposób, z dodatkowym poziomem pośrednictwa?
źródło
Odpowiedzi:
Następująca adnotacja w Wytycznych w sprawie projektu ramowego, sekcja 3.3. Nazwy zestawów i bibliotek dll zapewniają wgląd w to, dlaczego przestrzenie nazw i zestawy są oddzielne.
źródło
Dodaje elastyczność i pozwala na ładowanie bibliotek (które nazywacie modułami w pytaniu) na żądanie.
Jedna przestrzeń nazw, wiele bibliotek:
Jedną z zalet jest to, że mogę łatwo zastąpić jedną bibliotekę inną. Powiedzmy, że mam przestrzeń nazw
MyCompany.MyApplication.DAL
i bibliotekęDAL.MicrosoftSQL.dll
, która zawiera wszystkie zapytania SQL i inne rzeczy, które mogą być specyficzne dla bazy danych. Jeśli chcę, aby aplikacja była zgodna z Oracle, po prostu dodajęDAL.Oracle.dll
, zachowując tę samą przestrzeń nazw. Od teraz mogę dostarczać aplikację z jedną biblioteką dla klientów, którzy potrzebują zgodności z Microsoft SQL Server, oraz z drugą biblioteką dla klientów korzystających z Oracle.Zmiana przestrzeni nazw na tym poziomie prowadziłaby do zduplikowania kodu lub konieczności przejścia i zmiany wszystkich
using
znaków wewnątrz kodu źródłowego dla każdej bazy danych.Jedna biblioteka, wiele przestrzeni nazw:
Posiadanie kilku przestrzeni nazw w jednej bibliotece jest również korzystne pod względem czytelności. Jeśli w klasie używam tylko jednej przestrzeni nazw, umieszczam tylko tę na górze pliku.
Posiadanie wszystkich przestrzeni nazw dużej biblioteki byłoby raczej mylące zarówno dla osoby czytającej kod źródłowy, jak i dla samej pisarki, ponieważ Intellisense ma zbyt wiele rzeczy do zasugerowania w danym kontekście.
Mając mniejsze biblioteki, jedna biblioteka na plik miałaby wpływ na wydajność: każda biblioteka musi być ładowana na żądanie do pamięci i przetwarzana przez maszynę wirtualną podczas działania aplikacji; mniej plików do załadowania oznacza nieco lepszą wydajność.
źródło
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.
źródło