Esistono schemi di progettazione che sono possibili solo in linguaggi digitati dinamicamente come Python?

30

Ho letto una domanda correlata Esistono schemi di progettazione che non sono necessari in linguaggi dinamici come Python? e hanno ricordato questa citazione su Wikiquote .org

The wonderful thing about dynamic typing is it lets you express anything that is computable. And type systems don’t — type systems are typically decidable, and they restrict you to a subset. People who favor static type systems say “it’s fine, it’s good enough; all the interesting programs you want to write will work as types”. But that’s ridiculous — once you have a type system, you don’t even know what interesting programs are there.

--- Software Engineering Radio Episode 140: Newspeak and Pluggable Types with Gilad Bracha

Mi chiedo, ci sono schemi o strategie di progettazione utili che, usando la formulazione della citazione, "non funzionano come tipi"?

    
posta user7610 12.08.2016 - 22:28
fonte

9 risposte

4

Tipi di prima classe

La digitazione dinamica significa che hai tipi di prima classe: puoi ispezionare, creare e archiviare tipi in fase di esecuzione, inclusi i tipi della lingua. Significa anche che valori sono stati digitati, non variabili .

Il linguaggio tipizzato staticamente può produrre codice che si basa anche su tipi dinamici, come il metodo di invio, classi di tipi, ecc. ma in un modo che è generalmente invisibile al runtime. Nella migliore delle ipotesi, ti danno un modo per eseguire l'introspezione. In alternativa, è possibile simulare i tipi come valori, ma si dispone di un sistema di tipo dinamico ad-hoc.

Tuttavia, i sistemi di tipo dinamico raramente hanno solo tipi di prima classe. Puoi avere simboli di prima classe, pacchetti di prima classe, di prima classe ... tutto. Ciò è in contrasto con la stretta separazione tra il linguaggio del compilatore e il linguaggio di runtime in lingue tipizzate staticamente. Ciò che il compilatore o l'interprete può fare anche il runtime.

Ora, siamo d'accordo che l'inferenza di tipo è una buona cosa e che mi piacerebbe avere il mio codice controllato prima di eseguirlo. Tuttavia, mi piace anche essere in grado di produrre e compilare il codice in fase di esecuzione. E adoro precompilare le cose anche in fase di compilazione. In un linguaggio digitato dinamicamente, questo viene fatto con la stessa lingua. In OCaml, hai il sistema di tipo module / functor, che è diverso dal sistema di tipo principale, che è diverso dal linguaggio del preprocessore. In C ++, hai il linguaggio template che non ha nulla a che fare con la lingua principale, che generalmente ignora i tipi durante l'esecuzione. E questo è bello in quella lingua, perché non vogliono fornire di più.

In definitiva, ciò non cambia in realtà quale tipo di software puoi sviluppare, ma l'espressività cambia come li sviluppi e se è difficile o no.

Modelli

I pattern che si basano su tipi dinamici sono pattern che coinvolgono ambienti dinamici: classi aperte, dispatching, database in memoria di oggetti, serializzazione, ecc. Cose semplici come contenitori generici funzionano perché un vettore non si dimentica in fase di esecuzione del tipo di oggetti che contiene (non c'è bisogno di tipi parametrici).

Ho provato a introdurre i molti modi in cui il codice viene valutato in Common Lisp e gli esempi di possibili analisi statiche (questo è SBCL). L'esempio sandbox compila un piccolo sottoinsieme di codice Lisp recuperato da un file separato. Per essere ragionevolmente sicuro, cambio il programma, consento solo un sottoinsieme di simboli standard e avvolgo le cose con un timeout.

;;
;; Fetching systems, installing them, etc. 
;; ASDF and QL provide provide resp. a Make-like facility 
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds. 
;; https://gitlab.common-lisp.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)

;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
                           (declare (ignore args))
                           (error "Colon character disabled."))
                     nil
                     *safe-readtable*)

;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment. 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar +WHITELISTED-LISP-SYMBOLS+ 
    '(+ - * / lambda labels mod rem expt round 
      truncate floor ceiling values multiple-value-bind)))

;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
  (:import-from
   :common-lisp . #.+WHITELISTED-LISP-SYMBOLS+)
  (:export . #.+WHITELISTED-LISP-SYMBOLS+))

(declaim (inline read-sandbox))

(defun read-sandbox (stream &key (timeout 3))
  (declare (type (integer 0 10) timeout))
  (trivial-timeout:with-timeout (timeout)
    (let ((*read-eval* nil)
          (*readtable* *safe-readtable*)
          ;;
          ;; Packages are first-class: no possible name collision.
          ;;
          (package (make-package (gensym "SANDBOX") :use '(:sandbox))))
      (unwind-protect
           (let ((*package* package))
             (loop
                with stop = (gensym)
                for read = (read stream nil stop)
                until (eq read stop)
                ;;
                ;; Eval at runtime
                ;;
                for value = (eval read)
                ;;
                ;; Type checking
                ;;
                unless (functionp value)
                do (error "Not a function")
                ;; 
                ;; Compile at run-time
                ;;
                collect (compile nil value)))
        (delete-package package)))))

;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in :timeout 50)))

;; get it right, this time
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in)))

#| /tmp/plugin.lisp
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#

(read-sandbox-file #P"/tmp/plugin.lisp")

;; 
;; caught COMMON-LISP:STYLE-WARNING:
;;   The variable C is defined but never used.
;;

(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
 #<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)

Nulla di sopra è "impossibile" da fare con altre lingue. L'approccio plug-in in Blender, nel software musicale o IDE per linguaggi compilati staticamente che eseguono la ricompilazione al volo, ecc. Invece di strumenti esterni, i linguaggi dinamici favoriscono strumenti che fanno uso di informazioni già presenti. Tutti i chiamanti conosciuti di FOO? tutte le sottoclassi di BAR? tutti i metodi specializzati per classe ZOT? questo è dati internalizzati. I tipi sono solo un altro aspetto di questo.

(vedi anche: CFFI )

    
risposta data 15.08.2016 - 12:17
fonte
39

Risposta breve: no, perché l'equivalenza di Turing.

Risposta lunga: questo ragazzo è un troll. Mentre è vero che i sistemi di tipo "ti restringono a un sottoinsieme", le cose al di fuori di quel sottoinsieme sono, per definizione, cose che non funzionano.

Tutto ciò che sei in grado di fare in qualsiasi linguaggio di programmazione completo di Turing (che è linguaggio progettato per la programmazione generica, oltre a molto altro, è una barra piuttosto bassa da cancellare e ci sono diversi esempi di un sistema diventando Turing-completo involontariamente) si è in grado di fare in qualsiasi altro linguaggio di programmazione completo di Turing. Questo è chiamato "equivalenza di Turing" e significa solo esattamente ciò che dice. È importante sottolineare che non significa che si possa fare l'altra cosa altrettanto facilmente nell'altra lingua - alcuni sostengono che questo è l'intero punto di creare un nuovo linguaggio di programmazione in primo luogo: per darti un modo migliore di fare certe cose cose che le lingue esistenti fanno schifo.

Un sistema di tipo dinamico, ad esempio, può essere emulato sopra un sistema di tipo OO statico dichiarando tutte le variabili, i parametri ei valori restituiti come il tipo Object di base e quindi utilizzando la reflection per accedere ai dati specifici all'interno , quindi quando ti rendi conto di ciò, vedi che non c'è letteralmente niente che puoi fare in un linguaggio dinamico che non puoi fare in un linguaggio statico. Ma farlo in questo modo sarebbe un gran casino, naturalmente.

Il tizio della citazione è corretto che i tipi statici limitano ciò che puoi fare, ma questa è una caratteristica importante, non un problema. Le linee sulla strada limitano ciò che puoi fare nella tua auto, ma le trovi restrittive o utili? (So che non vorrei guidare su una strada trafficata e complessa dove non c'è nulla che dirà le macchine che vanno nella direzione opposta a stare dalla loro parte e non venire dove sto guidando!) Stabilendo regole che delineano chiaramente cosa è considerato un comportamento non valido e assicurandoti che non accada, diminuisci notevolmente le possibilità che si verifichi un brutto incidente.

Inoltre, ha sbagliato l'altra parte. Non è che "tutti i programmi interessanti che vuoi scrivere funzionino come tipi", ma piuttosto "tutti i programmi interessanti che vuoi scrivere richiedono tipi." Una volta superato un certo livello di complessità, diventa molto difficile mantenere il codebase senza un sistema di tipi per tenerti in riga, per due motivi.

Primo, perché il codice senza annotazioni di tipo è difficile da leggere. Considera il seguente Python:

def sendData(self, value):
   self.connection.send(serialize(value.someProperty))

Che cosa ti aspetti che i dati sembrino che il sistema all'altro capo della connessione riceva? E se riceve qualcosa che sembra completamente sbagliato, come fai a capire cosa sta succedendo?

Tutto dipende dalla struttura di value.someProperty . Ma come si presenta? Buona domanda! Che cosa sta chiamando sendData() ? Che cosa sta passando? Che aspetto ha questa variabile? Da dove proviene? Se non è locale, devi tracciare l'intera cronologia di value per rintracciare cosa sta succedendo. Forse stai passando qualcos'altro che ha anche una proprietà someProperty , ma non fa quello che pensi che faccia?

Ora diamo un'occhiata a questo tipo di annotazioni, come puoi vedere nel linguaggio Boo, che usa una sintassi molto simile ma è tipizzato staticamente:

def SendData(value as MyDataType):
   self.Connection.Send(Serialize(value.SomeProperty))

Se c'è qualcosa che non va, all'improvviso il tuo lavoro di debug ha ottenuto un ordine di grandezza più semplice: cerca la definizione di MyDataType ! Inoltre, la possibilità di ottenere comportamenti errati perché hai passato un tipo incompatibile che ha anche una proprietà con lo stesso nome improvvisamente si azzera, perché il sistema di tipi non ti consente di fare quell'errore.

Il secondo motivo si basa sul primo: in un progetto ampio e complesso, è probabile che tu abbia più contributori. (E se no, lo stai costruendo da tempo, che è essenzialmente la stessa cosa. Prova a leggere il codice che hai scritto 3 anni fa se non mi credi!) Ciò significa che non sai cosa fosse passando per la testa della persona che ha scritto quasi tutte le parti del codice nel momento in cui l'hanno scritta, perché non eri lì, o non ricordi se era il tuo codice molto tempo fa. Avere le dichiarazioni di tipo ti aiuta davvero a capire qual era l'intenzione del codice!

Le persone come il tizio nella citazione spesso errano i vantaggi della tipizzazione statica come "aiutare il compilatore" o "tutto sull'efficienza" in un mondo in cui risorse hardware quasi illimitate rendono questo aspetto sempre meno rilevante in ogni anno. Ma come ho mostrato, mentre questi benefici certamente esistono, il vantaggio principale è nei fattori umani, in particolare la leggibilità del codice e la manutenibilità. (L'efficienza aggiunta è certamente un bel bonus, però!)

    
risposta data 12.08.2016 - 23:05
fonte
27

Vado a fare un passo parallelo alla parte "pattern" perché penso che diventi parte della definizione di ciò che è o non è uno schema e da molto tempo ho perso interesse per quel dibattito. Quello che dirò è che ci sono cose che puoi fare in alcune lingue che non puoi fare in altri. Vorrei essere chiaro, sono non che ci sono problemi che puoi risolvere in una lingua che non puoi risolvere in un'altra lingua. Mason ha già indicato la completezza di Turing.

Ad esempio, ho scritto una classe in python che include un elemento DOM XML e lo trasforma in un oggetto di prima classe. Cioè, puoi scrivere il codice:

doc.header.status.text()

e il contenuto di quel percorso proviene da un oggetto XML analizzato. tipo di pulito e ordinato, IMO. E se non c'è un nodo principale, restituisce semplicemente un oggetto fittizio che non contiene nient'altro che oggetti fittizi (tartarughe fino in fondo.) Non c'è un modo reale di farlo, per esempio, in Java. Dovresti aver compilato una lezione in anticipo sulla base di una certa conoscenza della struttura dell'XML. Mettendo da parte se questa è una buona idea, questo genere di cose cambia davvero il modo in cui risolvi i problemi in un linguaggio dinamico. Non sto dicendo che cambia in un modo che è necessariamente sempre migliore, comunque. Ci sono alcuni costi definiti per gli approcci dinamici e la risposta di Mason offre una panoramica decente. Se sono una buona scelta dipende da molti fattori.

Nota a margine, puoi farlo in Java perché puoi creare un interprete python in Java . Il fatto che risolvere un problema specifico in una determinata lingua possa significare la costruzione di un interprete o qualcosa di simile ad esso è spesso trascurato quando la gente parla della completezza di Turing.

    
risposta data 12.08.2016 - 23:47
fonte
10

La citazione è corretta, ma anche molto falsa. Scopriamolo per capire perché:

The wonderful thing about dynamic typing is it lets you express anything that is computable.

Bene, non proprio. Una lingua con digitazione dinamica ti consente di esprimere qualsiasi cosa purché sia Turing completo , che la maggior parte sono . Il tipo di sistema in sé non ti consente di esprimere tutto. Dagli però il beneficio del dubbio qui.

And type systems don’t — type systems are typically decidable, and they restrict you to a subset.

Questo è vero, ma notiamo che ora stiamo parlando fermamente di ciò che il tipo di sistema consente, non di ciò che la lingua che usa un sistema di tipi consente. Mentre è possibile utilizzare un sistema di tipi per calcolare cose in fase di compilazione, questo in genere non è completo Turing (poiché il sistema di tipi è generalmente decidibile), ma quasi qualsiasi linguaggio tipizzato staticamente è anche Turing completo nel suo runtime (le lingue tipizzate dipendentemente sono no, ma non credo che stiamo parlando di loro qui).

People who favor static type systems say “it’s fine, it’s good enough; all the interesting programs you want to write will work as types”. But that’s ridiculous — once you have a type system, you don’t even know what interesting programs are there.

Il problema è che i tipi dinamici in lingue hanno un tipo statico. A volte tutto è una stringa, e più comunemente c'è qualche unione taggata dove ogni cosa è o un sacchetto di proprietà o un valore come un int o un doppio. Il problema è che anche i linguaggi statici possono farlo, storicamente è stato un po 'più clamoroso farlo, ma i linguaggi tipicamente scritti in modo moderno rendono tutto ciò semplice come usare un linguaggio di tipi dinamici, quindi come può esserci una differenza in cosa può vedere il programmatore come un programma interessante? I linguaggi statici hanno esattamente le stesse unioni taggate e altri tipi.

Per rispondere alla domanda nel titolo: No, non ci sono schemi di progettazione che non possono essere implementati in un linguaggio tipizzato staticamente, perché è sempre possibile implementare un numero sufficiente di sistemi dinamici per ottenerli. Ci possono essere schemi che si ottengono per "liberi" in un linguaggio dinamico; questo può o non può valere la pena di sopportare gli svantaggi di quelle lingue per YMMV .

    
risposta data 13.08.2016 - 09:48
fonte
5

Il pattern Proxy dinamico è una scorciatoia per l'implementazione di oggetti proxy senza la necessità di una classe per tipo necessaria per il proxy.

class Proxy(object):
    def __init__(self, obj):
        self.__target = obj

    def __getattr__(self, attr):
        return getattr(self.__target, attr)

Usando questo, Proxy(someObject) crea un nuovo oggetto che si comporta come someObject . Ovviamente vorrete anche aggiungere funzionalità aggiuntive in qualche modo, ma questa è una base utile da cui iniziare. In un linguaggio statico completo, è necessario scrivere una classe Proxy per tipo che si desidera utilizzare come proxy o utilizzare la generazione dinamica del codice (che, è vero, è inclusa nella libreria standard di molti linguaggi statici, in gran parte perché i loro progettisti sono a conoscenza di i problemi non sono in grado di fare questa causa).

Un altro caso d'uso di linguaggi dinamici è il cosiddetto "patch di scimmia". In molti modi, questo è un anti-pattern piuttosto che un pattern, ma può essere usato in modi utili se fatto con attenzione. E benché non ci sia alcun motivo teorico , le patch per scimmie non possano essere implementate in un linguaggio statico, non ne ho mai visto uno che ne abbia effettivamente.

    
risposta data 15.08.2016 - 09:33
fonte
4

Ci sono sicuramente cose che puoi fare solo in lingue digitate dinamicamente. Ma non sarebbero necessariamente buoni design.

È possibile assegnare dapprima un numero intero 5 una stringa 'five' o un oggetto Cat alla stessa variabile. Ma stai solo rendendo più difficile per un lettore del tuo codice capire cosa sta succedendo, qual è lo scopo di ogni variabile.

Potresti aggiungere un nuovo metodo a una libreria Ruby Class e accedere ai suoi campi privati. Potrebbero esserci casi in cui tale hacking può essere utile ma questa sarebbe una violazione dell'incapsulamento. (Non mi interessa aggiungere metodi basandosi solo sull'interfaccia pubblica, ma questo non è niente che i metodi di estensione C # tipicamente non possono fare.)

Potresti aggiungere un nuovo campo a un oggetto della classe di qualcun altro per passare alcuni dati extra con esso. Ma è meglio progettare semplicemente creare una nuova struttura o estendere il tipo originale.

In genere, più organizzato si desidera che il codice rimanga, minore è il vantaggio che si dovrebbe avere dall'essere in grado di modificare dinamicamente le definizioni dei tipi o assegnare valori di tipi diversi alla stessa variabile. Ma il tuo codice non è diverso da quello che potresti ottenere in un linguaggio tipizzato staticamente.

A che cosa servono i linguaggi dinamici è lo zucchero sintattico. Ad esempio, quando si legge un oggetto JSON deserializzato, si può fare riferimento a un valore annidato semplicemente come obj.data.article[0].content - molto più ordinario di dire obj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content") .

Gli sviluppatori di Ruby, in particolare, potrebbero parlare a lungo della magia che può essere raggiunta implementando method_missing , che è un metodo che consente di gestire le chiamate tentate a metodi non dichiarati. Ad esempio, ActiveRecord ORM lo utilizza in modo da poter effettuare una chiamata User.find_by_email('[email protected]') senza mai dichiarare il metodo find_by_email . Ovviamente non è nulla che non possa essere realizzato come UserRepository.FindBy("email", "[email protected]") in un linguaggio tipizzato in modo statico, ma non puoi negarlo in termini di pulizia.

    
risposta data 12.08.2016 - 23:46
fonte
3

, esistono molti modelli e tecniche che sono possibili solo in un linguaggio digitato in modo dinamico.

Patch delle scimmie è una tecnica in cui le proprietà o i metodi vengono aggiunti a oggetti o classi in fase di runtime. Questa tecnica non è possibile in un linguaggio tipizzato staticamente poiché ciò significa che tipi e operazioni non possono essere verificati in fase di compilazione. O per dirla in altro modo, se una lingua supporta il patch delle scimmie è per definizione un linguaggio dinamico.

Si può dimostrare che se una lingua supporta l'uso di patch per scimmie (o tecniche simili per modificare i tipi in fase di runtime), non può essere staticamente controllato. Quindi non è solo una limitazione nei linguaggi attualmente esistenti, è una limitazione fondamentale della tipizzazione statica.

Quindi la citazione è sicuramente corretta - più cose sono possibili in un linguaggio dinamico che in un linguaggio tipizzato staticamente. D'altra parte, alcuni tipi di analisi sono possibili solo in un linguaggio tipizzato staticamente. Ad esempio, sai sempre quali operazioni sono consentite su un determinato tipo, il che ti consente di rilevare operazioni illegali in fase di compilazione. Nessuna verifica di questo tipo è possibile in un linguaggio dinamico quando le operazioni possono essere aggiunte o rimosse in fase di runtime.

Questo è il motivo per cui non esiste un "migliore" ovvio nel conflitto tra i linguaggi statici e quelli dinamici. I linguaggi statici rinunciano a una certa potenza in fase di esecuzione in cambio di un diverso tipo di potenza in fase di compilazione, che ritengono riduca il numero di bug e faciliti lo sviluppo. Alcuni credono che il trade-off ne valga la pena, altri no.

Altre risposte hanno sostenuto che l'equivalenza di Turing significa che tutto il possibile in una lingua è possibile in tutte le lingue. Ma questo non segue. Per supportare qualcosa come il patch delle scimmie in un linguaggio statico, è necessario implementare una sotto-lingua dinamica all'interno del linguaggio statico. Questo è ovviamente possibile, ma direi che stai programmando in un linguaggio dinamico incorporato, dato che perdi anche il controllo di tipo statico che esiste nella lingua host.

C # poiché la versione 4 ha supportato oggetti tipizzati dinamicamente. Chiaramente i progettisti di linguaggi vedono beneficio nell'avere entrambi i tipi di digitazione disponibili. Ma mostra anche che non puoi avere la tua torta e mangiare anche io: quando usi oggetti dinamici in C # ottieni l'abilità di fare qualcosa come la patch delle scimmie, ma perdi anche la verifica del tipo statico per l'interazione con questi oggetti.

    
risposta data 27.08.2016 - 13:05
fonte
2

I wonder, are there useful design patterns or strategies that, using the formulation of the quote, "don't work as types"?

Sì e no.

Ci sono situazioni in cui il programmatore conosce il tipo di una variabile con più precisione di un compilatore. Il compilatore può sapere che qualcosa è un Oggetto, ma il programmatore saprà (a causa degli invarianti del programma) che in realtà è una Stringa.

Lascia che ti mostri alcuni esempi di questo:

Map<Class<?>, Function<?, String>> someMap;
someMap.get(object.getClass()).apply(object);

So che someMap.get(T.class) restituirà un Function<T, String> , a causa di come ho creato someMap. Ma Java è sicuro di avere una funzione.

Un altro esempio:

data = parseJSON(someJson)
validate(data, someJsonSchema);
print(data.properties.rowCount);

So che data.properties.rowCount sarà un riferimento valido e un intero, perché ho convalidato i dati con uno schema. Se quel campo fosse mancante, sarebbe stata generata un'eccezione. Ma un compilatore saprebbe solo che sta generando un'eccezione o restituisce una sorta di JSONValue generico.

Un altro esempio:

x, y, z = struct.unpack("II6s", data)

Gli "II6" definiscono il modo in cui i dati codificano tre variabili. Poiché ho specificato il formato, so quali tipi verranno restituiti. Un compilatore saprebbe solo che restituisce una tupla.

Il tema unificante di tutti questi esempi è che il programmatore conosce il tipo, ma un sistema di tipo a livello Java non sarà in grado di rispecchiarlo. Il compilatore non conoscerà i tipi e quindi un linguaggio tipizzato staticamente non mi consentirà di chiamarli, mentre un linguaggio tipizzato in modo dinamico lo farà.

Questo è ciò che la citazione originale otteneva in:

The wonderful thing about dynamic typing is it lets you express anything that is computable. And type systems don’t — type systems are typically decidable, and they restrict you to a subset.

Quando si utilizza la digitazione dinamica, posso utilizzare il tipo più derivato che conosco, non solo il tipo più derivato dal sistema di tipo della mia lingua. In tutti i casi precedenti, ho un codice che è semanticamente corretto, ma verrà rifiutato da un sistema di tipizzazione statico.

Tuttavia, per tornare alla tua domanda:

I wonder, are there useful design patterns or strategies that, using the formulation of the quote, "don't work as types"?

Qualunque esempio sopra, e in effetti qualsiasi esempio di digitazione dinamica può essere reso valido nella tipizzazione statica aggiungendo cast appropriati. Se conosci un tipo, il compilatore non lo fa, semplicemente dillo al compilatore eseguendo il cast del valore. Quindi, ad un certo livello, non otterrai alcun motivo aggiuntivo utilizzando la digitazione dinamica. Potresti aver bisogno di lanciare altro per ottenere il codice scritto in modo statico.

Il vantaggio della digitazione dinamica è che puoi semplicemente utilizzare questi modelli senza preoccuparti del fatto che è difficile convincere il tuo sistema di tipi della loro validità. Non cambia i pattern disponibili, ma forse li rende più facili da implementare perché non devi capire come far riconoscere al tuo tipo il pattern o aggiungere cast per sovvertire il sistema dei tipi.

    
risposta data 13.08.2016 - 20:54
fonte
1

Ecco alcuni esempi di Objective-C (digitati dinamicamente) che non sono possibili in C ++ (staticamente digitato):

  • Mettere oggetti di diverse classi distinte nello stesso contenitore.
    Ovviamente, ciò richiede l'ispezione del tipo di runtime per interpretare successivamente il contenuto del contenitore, e la maggior parte degli amici di tipizzazione statica obietterà che non si dovrebbe fare questo in primo luogo. Ma ho scoperto che, al di là dei dibattiti religiosi, questo può tornare utile.

  • Espansione di una classe senza sottoclassi.
    In Objective-C, puoi definire nuove funzioni membro per le classi esistenti, comprese quelle definite dalla lingua come NSString . Ad esempio, puoi aggiungere un metodo stripPrefixIfPresent: , in modo che tu possa dire [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"] (nota l'uso del NSSring letterali @"" ).

  • Uso di callback orientati agli oggetti.
    Nei linguaggi tipizzati staticamente come Java e C ++, è necessario fare notevoli passi per consentire a una libreria di chiamare un membro arbitrario di un oggetto fornito dall'utente. In Java, la soluzione alternativa è la coppia interfaccia / adattatore più una classe anonima, in C ++ la soluzione è solitamente basata su modelli, il che implica che il codice della libreria deve essere esposto al codice utente. In Objective-C, si passa semplicemente il riferimento all'oggetto più il selettore per il metodo alla libreria e la libreria può richiamare semplicemente e direttamente il callback.

risposta data 15.08.2016 - 14:32
fonte

Leggi altre domande sui tag