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