Non deve essere. Puoi implementare il 95% di ish della digitazione in una lingua statica senza tanto lavoro. L'ultimo 5% è necessario per la digitazione dipendente ed è molto più complicato, ma è ancora un sistema tipizzato staticamente. Nello specifico, il restante 5% utilizza metodi che dipendono da altri valori, e quindi il codice che funziona ancora su dati che non supportano tutti i metodi che il codice chiama in modo condizionale.
Ecco un frammento di Haskell con cui stavo armeggiando e implementa qualcosa di molto come tipizzazione anatra statica:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
UndecidableInstances, IncoherentInstances, FlexibleContexts #-}
data Addon a b = Addon a b deriving Show
class Plugin a b where
first :: b -> a
set :: a -> b -> b
instance Plugin a a where
first = id
set = const
instance Plugin a (Addon a b) where
first (Addon a _) = a
set x (Addon a b) = Addon x b
instance (Plugin a b) => Plugin a (Addon c b)where
first (Addon _ b) = first b
set x (Addon a b) = Addon a $ set x b
class Runnable e b c where
run :: e -> b -> c
instance Plugin a b => Runnable (a -> c) b c where
run f p = f (first p)
instance (Plugin d b, Runnable e b c) => Runnable (d -> e) b c where
run f p = run (f $ first p) p
-- The constructor function should NOT be polymorphic *at all*
apply constructor f x = set (constructor $ run f x) x
La classe di tipi Plugin esiste per esprimere il contenimento di tipi all'interno di altri tipi. Un tipo che è un'istanza di Plugin a b
significa che è un tipo b
per cui first b
può essere un valore di tipo a
. Le due istanze fornite sono tutto ciò che è necessario per derivare tutti i casi rilevanti e non sono necessarie ulteriori dichiarazioni di istanza.
La classe runnable Runnable ci consente di concatenare le istanze del Plugin e quindi di chiamare una funzione multi-argomento aleatoria su un singolo tipo di dati, purché contenga un tipo di componente valido per ogni argomento curried della funzione. Ancora una volta, non sono necessarie ulteriori dichiarazioni di istanza per l'utilizzo.
La funzione apply ci consente di prendere un tipo composito, di applicarci una funzione e di memorizzare il risultato all'interno di quel tipo composito (purché contenga già un valore del tipo che desideriamo memorizzare)
L'utilizzo sarebbe così:
data Increment = Inc Integer
data Counter = Count Integer
counter1 = union (Inc 2) (Count 3)
counter2 = union (Inc 1) (Count 0)
increment (Inc i) (Count c) = i + c
E in effetti, compila e possiamo eseguirlo e osservare l'output:
*Main> apply Count increment counter1
Addon (Inc 2) (Count 5)
*Main> apply Count increment counter2
Addon (Inc 1) (Count 1)
Certo, abbiamo dovuto attivare alcune estensioni di tipo piuttosto brutte, ma a quelle estensioni non è richiesto di usare la libreria, solo per scriverla. L'argomento del costruttore da applicare non può essere polimorfo perché sovraccaricherebbe il sistema di inferenza del tipo e non sarebbe in grado di effettuare le derivazioni del tipo intermedio necessarie. Stavo cercando di aggirare il problema, ma non ho fatto alcun progresso in quella direzione.