Gradual typing. Нужен ли Clojure core.typed?

core.typed набирает обороты в Clojure community, все это очень интересно и забавно подумал я, и решил пощупать руками.

core.typed это "Gradual typing in Clojure, as a library". Что такое "Gradual typing" понятно описано в статье What is Gradual Typing?, цитируем:

A gradual type checker is a type checker that checks, at compile-time, for type errors in some parts of a program, but not others, as directed by which parts of the program have been annotated with types.

Зачем это нужно становится можно почитать в разделе "Rationale" на wiki core.typed

Для себя я выделил два плюса:

  • Типы как средство документации кода
  • Избежание NullPointerException

Важно также что в текущей реализации core.typed никак не влияет на скомпилированный код, те. никакого performance optimization не будет. Проверка типов будет осуществляется путем вызова функции check-ns

    (clojure.core.typed/check-ns 'my-namespace)

core.typed проанализирует код и сообщит нам что все ок или что где то типы не сходятся

Решил я попробовать это на практике, делал кату GameOfLife с core.typed

(ns kata-4.core
  (:require [clojure.core.typed :as t]))


(t/def-alias Field (t/Vec (t/Vec nil)))
(t/def-alias Width t/Int)
(t/def-alias Height t/Int)
(t/def-alias Coord '[t/Int t/Int])
(t/def-alias Cell Coord)


(t/ann field [Width Height -> Field])
(defn field  [w h]
  (vec (repeat h (vec (repeat w nil)))))


(t/ann neighbours [Coord -> (t/Seq Coord)])
(defn- neighbours [[x y]]
  (t/for> :- Coord
           [dx :- t/Int [-1 0 1]
           dy :- t/Int [-1 0 1]
           :when (not (= dx dy 0))]
          [(+ dx x) (+ dy y)]))

(t/ann ^:no-check frequencies-t [(t/Seq Any) -> (t/Map Cell t/Int)])
(defn frequencies-t [c] (frequencies c))

(t/ann next-gen [(t/Set Cell) -> (t/Set Cell)])
(defn next-gen [live-cells]
  (let [neighbours (mapcat neighbours live-cells)
        freq-neghbours (frequencies-t neighbours)

        next-cells (t/for> :- Cell
                          [[cell freq] :- '[Cell t/Int] freq-neghbours
                           :when (or (= 3 freq)
                                   (and (= 2 freq)
                                        (live-cells cell)))]
                     cell)]
    (set next-cells)))

Не буду детально про core.typed хочется больше про эмоции. Эмоции были разные, было конечно итересно но:

  • Указание типов отнимает время, и не совсем понятно когда этим заниматься. Я сначала написал тест, потом код, потом типы. Но честно, это было не очень комфортно. В общем не понятно это дело гармонично совместить с TDD

  • Кода стало больше и он стал уродливей, видно что язык не был для этого придуман, может это дело привычки конечно, но...

  • Для for (и еще остальных макросов) есть спец. аналоги, например for>, в котором нужно указывать типы. Это очень не удобно и даже вызывало раздражение. На сколько я понимаю, это из за ограничений core.typed, core.typed не может автоматически определить тип макроса.

  • Не для всех функций в Clojure.core typed.clojure может определить типы, по этому приходится это делать руками, и это ужасно. Например так у меня появилась функция frequencies-t

  • Проверка типов будет проходить только если ее специально запустить, те. если у вас в команде > 1 человека вам нужно что бы инициативу использовать core.typed поддержала команда, если этого не будет все положат на проверку проверку типов, код и так будет работать, и только один человек будет кричать, визжать и парится.

Подумал, решил спросить мнение у авторитетного человека, что он думает про "Optional static typing in dynamic languages". Я написал письмо Dan-у Grossman-у, автору супер крутого курса "Programming Languages".

И он ответил:

... Languages like Typed Racket are very interesting and attack the very important problem of "gradual typing".
I tend to like them more than the "useful but unsound typing" we are seeing in things like Dart and TypeScript, but both approaches are interesting even though they are different. ...

Автор Core.typed был вдохновлен Typed Racket, на сколько я понимаю, идеи и концепции у них схожи.

В общем, выводы для себя пока сделать сложно. Есть некоторые сомнения по применимости core.typed в боевых условиях, но это только мысли. Мне кажется что нужно найти способ интеграции core.typed с тестами, и запускать проверку вместе с запуском тестов, таким образом core.typed будет как бы дополнять тесты. В любом случае это очень интересно.

Ссылки по теме:

Written on October 16, 2013