Вложенность против потоков - PullRequest
0 голосов
/ 03 апреля 2019

Clojure новичок здесь, какую из следующих форм вы считаете наиболее «clojuresque»:

  1. Тяжело вложенные

    (my-fn3 (my-fn2 (my-fn1 foo bar) baz) qux)
    
  2. Использование let

    (let [result foo
          result (my-fn1 result bar)
          result (my-fn2 result baz)
          result (my-fn3 result qux)])
    
  3. Использование первого потока

    (-> foo
        (my-fn1 bar)
        (my-fn2 baz)
        (my-fn3 qux))
    

Ответы [ 2 ]

3 голосов
/ 03 апреля 2019

Я использую все 3 техники в зависимости от ситуации.Цель состоит в том, чтобы выбрать технику, которая делает код наиболее понятным (не наименьшим количеством символов!).

Техника № 2 особенно удобна при отладке , так какВы можете легко распечатать промежуточные значения.Тем не менее, я обычно даю каждому этапу отдельное имя, чтобы прояснить ситуацию:

(let [x-1 foo
      x-2 (my-fn1 x-1 bar)
      x-3 (my-fn2 x-2 baz)
      x-4 (my-fn3 x-3 qux)]
  (println :x-1 x-1)
  (println :x-2 x-2)
  (println :x-3 x-3)
  (println :x-4 x-4)

  x-4) ; don't forget to return the final result!

Обновление

Что касается отладки, я бы так и сделал.Во-первых, необработанные 3 версии:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(defn fa [x y] (+ x y))
(defn fb [x y] (* x y))
(defn fc [x y] {:x x :y y})

(def tgt 2)

(defn stack-nest
  [foo]
  (fc
    (fb
      (fa foo 3)
      3)
    99))

(defn stack-thread
  [foo]
  (-> foo
      (fa 3)
      (fb 3)
      (fc 99)))

(defn stack-let
  [foo]
  (let [a foo
        b (fa a 3)
        c (fb b 3)
        d (fc c 99)]
    d)) ; don't forget to return the final result!

Вам не нужно изобретать собственную функцию dbg, так как в библиотеке Tupelo уже есть несколько хороших опций .Здесь мы печатаем результаты, используя макрос spyx (явный шпион):

(dotest
  (spyx (stack-nest tgt))
  (spyx (stack-thread tgt))
  (spyx (stack-let tgt)))

(stack-nest tgt)       => {:x 15, :y 99}
(stack-thread tgt)     => {:x 15, :y 99}
(stack-let tgt)        => {:x 15, :y 99}

Затем добавляем отладочную информацию, используя spy и метку:

(defn stack-nest
  [foo]
  (spy :fc (fc
             (spy :fb (fb
                        (spy :fa (fa foo 3))
                        3))
           99)))

:fa => 5
:fb => 15
:fc => {:x 15, :y 99}
(stack-nest tgt) => {:x 15, :y 99}

Это работает, но это довольно некрасиво.Как насчет формы потоков?Здесь мы можем вставить шпиона, и это немного лучше:

(defn stack-thread
  [foo]
  (-> foo
      (spy :foo)
      (fa 3)
      (spy :fa)
      (fb 3)
      (spy :fb)
      (fc 99)
      (spy :fc)
      ))

:foo => 2
:fa => 5
:fb => 15
:fc => {:x 15, :y 99}
(stack-thread tgt) => {:x 15, :y 99}

Мы получаем то, что хотим, но у него есть некоторое дублирование.Кроме того, нам нужно поместить каждое выражение (spy ...) в отдельную строку, чтобы макрос многопоточности -> отправлял значение как в вычисление, например (fa 3), так и на этап печати, например, (spy :fa).

Мы можем немного упростить его с помощью макроса it-> следующим образом:

(defn stack-thread-it
  [foo]
  (it-> foo
        (fa it 3)
        (fb it 3)
        (fc 99 it)))

Мы используем символ it в качестве заполнителя.Обратите внимание, что мы можем поместить резьбовое значение в любую позицию аргумента, как показано с помощью перевернутых аргументов fc.Для нашей отладки используйте spyx, чтобы выражения были помечены самостоятельно, и мы получили:

(defn stack-thread-it
  [foo]
  (it-> (spyx foo)
        (spyx (fa it 3))
        (spyx (fb it 3))
        (spyx (fc 99 it))))

foo         => 2
(fa it 3)   => 5
(fb it 3)   => 15
(fc 99 it)  => {:x 99, :y 15}

(stack-thread-it tgt) => {:x 99, :y 15}

Когда промежуточные переменные находятся в выражении let, я отлаживаю следующим образом:

(defn stack-let
  [foo]
  (let [a  foo
        >> (spyx a)
        b  (fa a 3)
        >> (spyx b)
        c  (fb b 3)
        >> (spyx c) ]
    (spyx (fc c 99))))

a => 2
b => 5
c => 15
(fc c 99) => {:x 15, :y 99}
(stack-let tgt) => {:x 15, :y 99}

Обратите внимание, что последняя функция fc вызывается непосредственно как возвращаемое значение (за пределами let), но мы все равно можем напечатать его значение, используя spyx.

Обратите внимание, что мне нравитсяиспользовать символ >> (не используется в Clojure) вместо подчеркивания _ в качестве фиктивного получателя значения выражения, поскольку подчеркивание иногда трудно увидеть.Символ >> не только выделяется в коде, но и выглядит как подсказка командной строки, напоминая об обязательном побочном эффекте действия печати. ​​

0 голосов
/ 19 мая 2019

Я бы сказал, что часто при написании Clojure большинство людей стремятся к комбинации подходов 2 и 3, просто потому, что они обеспечивают наибольшую читаемость.

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

Теперь подход3 не всегда достижимо, в некоторых случаях вы столкнетесь с ситуацией, когда два вызова функций не разделяют одну и ту же порядковую позицию для значения, которое вы создаете в потоке, что потребует либо небольшого переосмысления / структурирования кодаиначе, или вы можете обратиться к оператору as->, который я лично нахожу довольно отвратительным.

Я часто нахожу с Clojure, что читаемость кода отражает его идиоматичность, если что-то делает look верно, почти всегда есть лучший способ написать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...