As guerras santas na Internet sobre sistemas de tipos continuam sofrendo com o mito generalizado de que sistemas de tipos dinâmicos são inerentemente mais adequados para modelar áreas de assunto do “mundo aberto”. Normalmente, o argumento é: o objetivo da tipagem estática é capturar todas as entidades com a maior precisão possível, mas no mundo real isso é simplesmente inconveniente. Os sistemas reais devem ser fracamente acoplados e o mínimo possível vinculados à apresentação dos dados; portanto, a digitação dinâmica leva a um sistema mais estável como um todo.

Essa história parece convincente, mas não é verdadeira. O erro desse raciocínio é que os tipos estáticos não se destinam a "classificar o mundo" ou determinar a estrutura de cada valor no sistema. A realidade é que os sistemas do tipo estático permitem especificar exatamente quanto um componente deve saber sobre a estrutura de seus dados de entrada e, inversamente, quanto ele não sabe. Na prática, os sistemas do tipo estático processam perfeitamente os dados com uma estrutura parcialmente conhecida, além de garantir que a lógica do aplicativo não assuma acidentalmente muito sobre os dados.
Duas mentiras sobre tipos
, . , , /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!)
: , , , . , , , . — , , , , . .