Les guerres sacrées sur Internet concernant les systèmes de types continuent de souffrir du mythe répandu selon lequel les systèmes de types dynamiques sont intrinsèquement mieux adaptés à la modélisation de domaines du «monde ouvert». Habituellement, l'argument est le suivant: le but du typage statique est de capturer toutes les entités aussi précisément que possible, mais dans le monde réel, cela n'est tout simplement pas pratique. Les systèmes réels doivent être couplés de manière lâche et le moins possible liés à la présentation des données, de sorte que le typage dynamique conduit à un système plus stable dans son ensemble.

Cette histoire semble convaincante, mais ce n'est pas vrai. L'erreur dans un tel raisonnement est que les types statiques ne sont pas destinés à «classer le monde» ou à déterminer la structure de chaque valeur dans le système. La réalité est que les systèmes de type statique vous permettent de spécifier exactement ce qu'un composant doit savoir sur la structure de ses données d'entrée et, inversement, ce qu'il ne sait pas. En pratique, les systèmes de type statique traitent parfaitement les données avec une structure partiellement connue, tout en étant capables de s'assurer que la logique d'application n'assume pas trop accidentellement les données.
Deux mensonges sur les types
, . , , /r/programming:
[…], . , , «» , , - .
, , . JSON , . , . […] « » , .
, , , , . , - , , ? , , .
Hacker News :
, , pickle.load()
?
, , , . , , .
, , . , , : . , ; , .
,
: , , , , ! , . , .
. , , . (payload), . , - , JSON EDN.
, , :
{
"event_type": "signup",
"timestamp": "2020-01-19T05:37:09Z",
"data": {
"user": {
"id": 42,
"name": "Alyssa",
"email": "alyssa@example.com"
}
}
}
signup
- . , . JavaScript, :
const handleEvent = ({ event_type, data }) => {
switch (event_type) {
case 'login':
/* ... */
break
case 'signup':
sendEmail(data.user.email, `Welcome to Blockchain Emporium, ${data.user.name}!`)
break
}
}
, Haskell? , Haskell, , , :
data Event = Login LoginPayload | Signup SignupPayload
data LoginPayload = LoginPayload { userId :: Int }
data SignupPayload = SignupPayload
{ userId :: Int
, userName :: Text
, userEmail :: Text
}
instance FromJSON Event where
parseJSON = withObject "Event" \obj -> do
eventType <- obj .: "event_type"
case eventType of
"login" -> Login <$> (obj .: "data")
"signup" -> Signup <$> (obj .: "signup")
_ -> fail $ "unknown event_type: " <> eventType
instance FromJSON LoginPayload where { ... }
instance FromJSON SignupPayload where { ... }
handleEvent :: JSON.Value -> IO ()
handleEvent payload = case fromJSON payload of
Success (Login LoginPayload { userId }) -> {- ... -}
Success (Signup SignupPayload { userName, userEmail }) ->
sendEmail userEmail $ "Welcome to Blockchain Emporium, " <> userName <> "!"
Error message -> fail $ "could not parse event: " <> message
, (, , ). . , Reddit, , Haskell , ! Event, . , ? .
, JavaScript . , switch
. , JavaScript . , .
, , . Event
, , handleEvent
. JavaScript, , :
const handleEvent = ({ event_type, data }) => {
switch (event_type) {
/* ... */
default:
throw new Error(`unknown event_type: ${event_type}`)
}
}
, . , , . , , Haskell:
handleEvent :: JSON.Value -> IO ()
handleEvent payload = case fromJSON payload of
{- ... -}
Error _ -> pure ()
- «, », , . , ( ) . , ! , .
: Event
Haskell « », , . , , . , , , .
, , , :
Haskell, . , , timestamp
, . , , , , , !
, , Haskell userId
SignupPayload
, . , (, , userId
), ; , , , .
, , (shotgun parsing), , .
, , , , , . , , . , , , , , , .
JavaScript , Haskell: , JSON event_type
«» signup
data.user.name
data.user.email
. ! , JavaScript , . , ; , , - .
, , , , .
, , , - -. , , . , . JavaScript :
const handleEvent = (payload) => {
const signedPayload = { ...payload, signature: signature(payload) }
retransmitEvent(signedPayload)
}
( signature JSON), , . , ?
, : , . Haskell:
handleEvent :: JSON.Value -> IO ()
handleEvent (Object payload) = do
let signedPayload = Map.insert "signature" (signature payload) payload
retransmitEvent signedPayload
handleEvent payload = fail $ "event payload was not an object " <> show payload
, , JSON.Value
. Event
— JSON , , .
: , JSON- ( -), - JSON, - . , , , , . .
, , Haskell, JavaScript! JavaScript handleEvent
( JSON ), , , , spread- …
:
> { ..."payload", signature: "sig" }
{0: "p", 1: "a", 2: "y", 3: "l", 4: "o", 5: "a", 6: "d", signature: "sig"}
, . , . «» JSON Object
, . -, , .
, . , API, , , UUID
. «, » , API Haskell UUID
:
type UserId = UUID
, Reddit, , ! API , UUID, , . UUID , , , ! , ?
, — . , — . , UserId
— :
newtype UserId = UserId Text
deriving (Eq, FromJSON, ToJSON)
, UUID
, UserId
, , Text
. ( , ), UserId
— FromJSON
. , , UserId
— UserId
ToJSON
. : .
, , . UserId
, UserId
.
1, FromJSON
UserId
, , , fromJSON
. , - . , , … UserId
. , , ( , , , ).
— , . , , , .
-
, , , , , . pickle.load()
Python? , , Python. pickle.dump()
, pickle.load()
.
, , pickle.load()
, — , pickle.dump()
. , , . , , , .
, JSON, , pickle Python Python , . ? , , pickle.load()
. , :
def load_value(f):
val = pickle.load(f)
# - `val`
, val
, , , , , - . - - , , pickle.load(f)
, , val
!
, , , val
val.foo()
, . Java, val
— , :
interface Foo extends Serializable {
String foo();
}
, pickle.load()
Java:
static <T extends Serializable> Optional<T> load(InputStream in, Class<? extends T> cls);
, , pickle.load()
, Class<T>
. Serializable.class
, . : , -, - , ! , , JSON-.
Haskell? — serialise, API, , Java, . API , Haskell JSON, aeson, , , JSON Haskell — - - , -.
, , pickle.load()
, . , . - , , , , . , (, REPL ), , .
. , , , , - , «» , , . , , , , , .
. , , , ( ). , , «» .
: ,
: . , , , , , .
, , . -, - (nominal typing). , , , , . , . , .
, , , , . , , -, , ( ).
. JavaScript Clojure - , - , . ( ) .
, , - , , . ( ) , (nominal typing). , ; (boilerplate).
, , . , , , . , :
, , , , . ( ), : , .
, , - . , , , . , .
Python, , , , TypeScript, , . , . - Clojure — , - — , - Clojure, - .
, : TypeScript, Flow, PureScript, Elm, OCaml Reason, . — Haskell, ; Haskell ( , ) .
, Haskell , ? , ; Haskell, , . Haskell, , . , Haskell, , , . ( , , Haskell!)
: , , , . , , , . — , , , , . .