Clojure - Clojure

Clojure
Clojure logo.svg
Paradigma multiparadigma :
Família Lisp
Projetado por Rich Hickey
Apareceu pela primeira vez 2007 ; 14 anos atras ( 2007 )
Versão estável
1.10.3 / 4 de março de 2021 ; 5 meses atrás ( 2021-03-04 )
Disciplina de digitação
Plataforma
Licença Licença Pública Eclipse
Extensões de nome de arquivo
  • .clj
  • .cljs
  • .cljc
  • .edn
Local na rede Internet clojure .org
Influenciado por
Influenciado

Clojure ( / k l ʒ ər / , como fechamento ) é uma dinâmica e funcional dialeto da linguagem de programação Lisp no Java plataforma. Como outros dialetos Lisp, Clojure trata o código como dados e tem um sistema de macro Lisp . O processo de desenvolvimento atual é conduzido pela comunidade , supervisionado por Rich Hickey como seu benevolente ditador vitalício (BDFL).

Clojure defende imutabilidade e estruturas de dados imutáveis e encoraja os programadores a serem explícitos sobre o gerenciamento de identidade e seus estados. Esse foco na programação com valores imutáveis ​​e construções explícitas de progressão no tempo tem como objetivo facilitar o desenvolvimento de programas mais robustos, especialmente simultâneos , que são simples e rápidos. Embora seu sistema de tipo seja inteiramente dinâmico , esforços recentes também buscaram a implementação de um sistema de tipo dependente .

O suporte comercial para Clojure é fornecido pela empresa Cognitect, que patrocina uma conferência promocional.

História e processo de desenvolvimento

Rich Hickey, criador do Clojure

Rich Hickey é o criador da linguagem Clojure. Antes de Clojure, ele desenvolveu dotLisp, um projeto semelhante baseado na plataforma .NET , e três tentativas anteriores de fornecer interoperabilidade entre Lisp e Java : uma interface de linguagem estrangeira Java para Common Lisp (jfli), Uma Interface de Objeto Estrangeiro para Lisp (FOIL) e uma interface amigável com Lisp para Servlets Java (Lisplets).

Hickey passou cerca de 2 anos e meio trabalhando no Clojure antes de lançá-lo publicamente, grande parte desse tempo trabalhando exclusivamente no Clojure sem nenhum financiamento externo. No final desse tempo, Hickey enviou um e-mail anunciando o idioma para alguns amigos da comunidade Common Lisp.

O processo de desenvolvimento é conduzido pela comunidade e gerenciado na página do projeto Clojure JIRA . A discussão geral sobre o desenvolvimento ocorre no Clojure Google Group. Qualquer pessoa pode enviar problemas e ideias em ask.clojure.org, mas para contribuir com patches, é necessário assinar o contrato de Contribuidor do Clojure. Os problemas do JIRA são processados ​​por uma equipe de analistas e, finalmente, Rich Hickey aprova as alterações.

O nome de Clojure, de acordo com Hickey, é um jogo de palavras no conceito de programação " closure " incorporando as letras C, L e J para C # , Lisp e Java respectivamente - três linguagens que tiveram uma grande influência no design de Clojure.

Filosofia de design

Rich Hickey desenvolveu o Clojure porque queria um Lisp moderno para programação funcional , simbiótico com a plataforma Java estabelecida e projetado para simultaneidade .

A abordagem de Clojure ao estado é caracterizada pelo conceito de identidades, que são representadas como uma série de estados imutáveis ​​ao longo do tempo. Como os estados são valores imutáveis, qualquer número de trabalhadores pode operar neles em paralelo, e a simultaneidade se torna uma questão de gerenciar as mudanças de um estado para outro. Para este propósito, Clojure fornece vários tipos de referência mutáveis , cada um com uma semântica bem definida para a transição entre estados.

Visão geral da linguagem

Versão Data de lançamento Principais recursos / melhorias
16 de outubro de 2007 ( 16/10/2007 ) Lançamento público inicial
1.0 4 de maio de 2009 ( 04/05/2009 ) Primeira versão estável
1,1 31 de dezembro de 2009 ( 31/12/2009 ) Futuros
1,2 19 de agosto de 2010 ( 19-08-2010 ) Protocolos
1,3 23 de setembro de 2011 ( 23/09/2011 ) Suporte primitivo aprimorado
1,4 15 de abril de 2012 ( 15/04/2012 ) Literais do leitor
1,5 1 de Março de 2013 ( 01/03/2013 ) Redutores
1.5.1 10 de março de 2013 ( 10/03/2013 ) Corrigindo um vazamento de memória
1,6 25 de março de 2014 ( 25/03/2014 ) API Java, algoritmos de hash aprimorados
1,7 30 de junho de 2015 ( 30/06/2015 ) Transdutores, condicionais de leitor
1,8 19 de janeiro de 2016 ( 2016-01-19 ) Funções de string adicionais, link direto, servidor de soquete
1,9 8 de dezembro de 2017 ( 08/12/2017 ) Integração com especificações, ferramentas de linha de comando
1,10 17 de dezembro de 2018 ( 17/12/2018 ) Relatório de erros aprimorado, compatibilidade com Java
1,10.1 6 de junho de 2019 ( 06/06/2019 ) Trabalhar em torno de uma regressão de desempenho Java e melhorar o relatório de erros de clojure.main
1,10.2 26 de janeiro de 2021 ( 2021-01-26 ) Melhorias de interoperabilidade / compatibilidade Java e outras correções de linguagem importantes
Versão estável atual: 1.10.3 4 de março de 2021 ( 2021-03-04 ) suporte prepl para condicionais de leitor
Lenda:
Versão antiga
Versão mais antiga, ainda mantida
Última versão
Versão de visualização mais recente
Lançamento futuro

Clojure é executado na plataforma Java e, como resultado, integra-se com Java e oferece suporte total à chamada de código Java de Clojure, e o código Clojure também pode ser chamado de Java. A comunidade usa ferramentas como Leiningen para automação de projetos, fornecendo suporte para integração Maven . Leiningen lida com o gerenciamento de pacotes de projeto e dependências e é configurado usando a sintaxe Clojure.

Como a maioria dos outros Lisps, a sintaxe de Clojure é construída em expressões S que são primeiro analisadas em estruturas de dados por um leitor antes de serem compiladas. O leitor de Clojure suporta sintaxe literal para mapas , conjuntos e vetores , além de listas, e estes são compilados diretamente nas estruturas mencionadas. Clojure é um Lisp-1 e não se destina a ser compatível com o código de outros dialetos do Lisp, uma vez que usa seu próprio conjunto de estruturas de dados incompatíveis com outros Lisps.

Como um dialeto Lisp, Clojure suporta funções como objetos de primeira classe , um loop de leitura-eval-impressão (REPL) e um sistema de macro. O sistema de macro Lisp de Clojure é muito semelhante ao de Common Lisp, com a exceção de que a versão de Clojure da crase (denominada "aspas de sintaxe") qualifica símbolos com seu espaço de nomes . Isso ajuda a evitar a captura de nome não intencional, pois a vinculação a nomes qualificados por namespace é proibida. É possível forçar uma expansão da macro de captura, mas isso deve ser feito explicitamente. Clojure não permite macros de leitor definidas pelo usuário, mas o leitor oferece suporte a uma forma mais restrita de extensão sintática. Clojure suporta multimétodos e para abstrações do tipo interface possui um polimorfismo baseado em protocolo e sistema de tipo de dados usando registros , fornecendo alto desempenho e polimorfismo dinâmico projetado para evitar o problema de expressão .

Clojure tem suporte para sequências preguiçosas e incentiva o princípio de imutabilidade e estruturas de dados persistentes . Como uma linguagem funcional , a ênfase é colocada em funções de recursão e de ordem superior, em vez de looping baseado em efeitos colaterais. A otimização automática da chamada final não é suportada porque a JVM não a suporta nativamente; é possível fazer isso explicitamente usando a recurpalavra - chave. Para programação paralela e simultânea , o Clojure fornece memória transacional de software , um sistema de agente reativo e programação simultânea baseada em canal .

Clojure 1.7 introduziu condicionais de leitor, permitindo a incorporação de código Clojure e ClojureScript no mesmo namespace. Transdutores foram adicionados como um método de composição de transformações. Os transdutores permitem funções de ordem superior, como mapear e dobrar, para generalizar sobre qualquer fonte de dados de entrada. Embora tradicionalmente essas funções operem em sequências , os transdutores permitem que elas trabalhem em canais e permitem que o usuário defina seus próprios modelos de transdução.

Plataformas alternativas

A plataforma principal do Clojure é Java , mas existem outras implementações de destino. O mais notável deles é ClojureScript, que compila para ECMAScript 3, e ClojureCLR, uma porta completa na plataforma .NET , interoperável com seu ecossistema. Uma pesquisa da comunidade Clojure com 1.060 entrevistados realizada em 2013 descobriu que 47% dos entrevistados usavam Clojure e ClojureScript ao trabalhar com Clojure. Em 2014 esse número havia aumentado para 55%, em 2015, com base em 2.445 respondentes, para 66%. Os projetos populares do ClojureScript incluem implementações da biblioteca React , como Reagent, re-frame, Rum e Om.

Outras Implementações

Outras implementações de Clojure em diferentes plataformas incluem:

  • CljPerl, Clojure no topo do Perl
  • Clojerl, Clojure no BEAM , a máquina virtual Erlang
  • clojure-py, Clojure em Python puro
  • Ferret, compila em C ++ 11 independente que pode ser executado em microcontroladores
  • Joker, intérprete e linter escrito em Go
  • Las3r, um subconjunto do Clojure executado na máquina virtual ActionScript (a plataforma Adobe Flash Player)
  • Pixie, dialeto Lisp inspirado em Clojure escrito em RPython
  • Rouge, Clojure no topo de YARV em Ruby

Popularidade

Com o interesse contínuo em programação funcional, a adoção de Clojure por desenvolvedores de software que usam a plataforma Java continuou a aumentar. A linguagem também foi recomendada por desenvolvedores de software como Brian Goetz, Eric Evans, James Gosling , Paul Graham e Robert C. Martin . A ThoughtWorks , ao avaliar linguagens de programação funcionais para seu Radar de Tecnologia, descreveu Clojure como "uma implementação simples e elegante de Lisp no JVM" em 2010 e promoveu seu status para "ADOPTAR" em 2012.

No "JVM Ecosystem Report 2018" (que foi afirmado ser "a maior pesquisa de todos os desenvolvedores Java"), que foi preparado em colaboração por Snyk e Java Magazine, classificou Clojure como a 2ª linguagem de programação mais usada na JVM para " principais aplicações ". Clojure é usado na indústria por empresas como Apple , Atlassian , Funding Circle , Netflix , Puppet e Walmart , bem como agências governamentais como a NASA . Também tem sido usado para computação criativa, incluindo artes visuais, música, jogos e poesia.

Ferramentas

O ferramental para o desenvolvimento de Clojure teve uma melhora significativa ao longo dos anos. A seguir está uma lista de alguns IDEs populares e editores de texto com plug-ins que adicionam suporte para programação em Clojure:

Além das ferramentas fornecidas pela comunidade, as ferramentas oficiais da CLI do Clojure também estão disponíveis no Linux , macOS e Windows desde o Clojure 1.9.

Recursos por exemplo

Os exemplos a seguir podem ser executados em um Clojure REPL, como um iniciado com as ferramentas Clojure CLI, ou um REPL online, como um disponível em REPL.it.

Simplicidade

Devido à forte ênfase na simplicidade, os programas Clojure típicos consistem principalmente em funções e estruturas de dados simples (ou seja, listas, vetores, mapas e conjuntos):

;; A typical entry point of a Clojure program:
;;   `-main` function
(defn -main ; name
  [& args] ; (variable) parameters
  (println "Hello, World!")) ; body

Programação no REPL

Como outros Lisps , um dos recursos icônicos do Clojure é a programação interativa no REPL . Observe que, nos exemplos a seguir, ;inicia um comentário de linha e ;; =>indica a saída esperada:

; define a var
(def a 42)
;; => #'user/a

; call a function named `+`
(+ a 8)
;; => 50

; call a function named `even?`
(even? a)
;; => true

; define a function that returns the remainder of `n` when divided by 10
(defn foo [n] (rem n 10))
;; => #'user/foo

; call the function
(foo a)
;; => 2

; print the docstring of `rem`
(doc rem)
;; =>
-------------------------
clojure.core/rem
([num div])
 remainder of dividing numerator by denominator.

; print the source of `rem`
(source rem)
;; =>
(defn rem
  "remainder of dividing numerator by denominator."
  {:added "1.0"
   :static true
   :inline (fn [x y] `(. clojure.lang.Numbers (remainder ~x ~y)))}
  [num div]
    (. clojure.lang.Numbers (remainder num div)))

Nomes em tempo de execução

Ao contrário de outros ambientes de tempo de execução onde os nomes são compilados, o ambiente de tempo de execução do Clojure é facilmente introspectivo usando estruturas de dados normais do Clojure:

; define a var
(def a 42)
;; => #'user/a

; get a map of all public vars interned in the `user` namespace
(ns-publics 'user)
;; => {a #'user/a}

; reference the var via `#'` (reader macro) and
; its associated, namespace-qualified symbol `user/a`
#'user/a
;; => #'user/a

; de-reference (get the value of) the var
(deref #'user/a)
;; => 42

; define a function (with a docstring) that
; returns the remainder of `n` when divided by 10
(defn foo "returns `(rem n 10)`" [n] (rem n 10))
;; => #'user/foo

; get the metadata of the var `#'user/foo`
(meta #'user/foo)
;; =>
{:arglists ([n]),
 :doc "returns `(rem n 10)`",
 :line 1,
 :column 1,
 :file "user.clj",
 :name foo,
 :ns #namespace[user]}

Codifique como dados (homoiconicidade)

Semelhante a outros Lisps , Clojure é homoicônico (também conhecido como "código como dados"). No exemplo abaixo, podemos ver como escrever código que modifica o próprio código:

; call a function (code)
(+ 1 1)
;; => 2

; quote the function call
; (turning code into data, which is a list of symbols)
(quote (+ 1 1))
;; => (+ 1 1)

; get the first element on the list
; (operating on code as data)
(first (quote (+ 1 1)))
;; => +

; get the last element on the list
; (operating on code as data)
(last (quote (+ 1 1)))
;; => 1

; get a new list by replacing the symbols on the original list
; (manipulating code as data)
(map (fn [form]
       (case form
         1 'one
         + 'plus))
     (quote (+ 1 1)))
;; => (plus one one)

Operadores expressivos para transformação de dados

As macros de threading ( ->, ->>e amigos) podem expressar sintaticamente a abstração de canalizar uma coleção de dados por meio de uma série de transformações:

(->> (range 10)
     (map inc)
     (filter even?))
;; => (2 4 6 8 10)

Isso também pode ser alcançado de forma mais eficiente usando transdutores:

(sequence (comp (map inc)
                (filter even?))
          (range 10))
;; => (2 4 6 8 10)

Gerenciamento seguro de thread de identidade e estado

Um gerador thread-safe de números de série exclusivos (embora, como muitos outros dialetos Lisp, Clojure tenha uma gensymfunção embutida que usa internamente):

(def i (atom 0))

(defn generate-unique-id
  "Returns a distinct numeric ID for each call."
  []
  (swap! i inc))

Macros

Uma subclasse anônima de java.io.Writerque não grava em nada, e uma macro usando isso para silenciar todas as impressões dentro dela:

(def bit-bucket-writer
  (proxy [java.io.Writer] []
    (write [buf] nil)
    (close []    nil)
    (flush []    nil)))

(defmacro noprint
  "Evaluates the given `forms` with all printing to `*out*` silenced."
  [& forms]
  `(binding [*out* bit-bucket-writer]
     ~@forms))

(noprint
  (println "Hello, nobody!"))
;; => nil

Interoperabilidade de linguagem com Java

Clojure foi criado desde o início para abraçar suas plataformas host como um de seus objetivos de design e, portanto, fornece excelente interoperabilidade de linguagem com Java:

; call an instance method
(.toUpperCase "apple")
;; => "APPLE"

; call a static method
(System/getProperty "java.vm.version")
;; => "12+33"

; create an instance of `java.util.HashMap` and
; add some entries
(doto (java.util.HashMap.)
  (.put "apple" 1)
  (.put "banana" 2))
;; => {"banana" 2, "apple" 1}

; create an instance of `java.util.ArrayList` and
; increment its elements with `clojure.core/map`
(def al (doto (java.util.ArrayList.)
          (.add 1)
          (.add 2)
          (.add 3)))

(map inc al)
;; => (2 3 4)

; show a message dialog using Java Swing
(javax.swing.JOptionPane/showMessageDialog
  nil
  "Hello, World!")
;; => nil

Memória transacional de software

10 threads manipulando uma estrutura de dados compartilhada, que consiste em 100 vetores, cada um contendo 10 (inicialmente sequenciais) números exclusivos. Cada thread então seleciona repetidamente duas posições aleatórias em dois vetores aleatórios e os troca. Todas as mudanças nos vetores ocorrem nas transações, fazendo uso do sistema de memória transacional do software Clojure :

(defn run
  [nvecs nitems nthreads niters]
  (let [vec-refs
        (->> (* nvecs nitems)
             (range)
             (into [] (comp (partition-all nitems)
                            (map vec)
                            (map ref))))

        swap
        #(let [v1 (rand-int nvecs)
               v2 (rand-int nvecs)
               i1 (rand-int nitems)
               i2 (rand-int nitems)]
          (dosync
            (let [tmp (nth @(vec-refs v1) i1)]
              (alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
              (alter (vec-refs v2) assoc i2 tmp))))

        report
        #(->> vec-refs
              (into [] (comp (map deref)
                             (map (fn [v] (prn v) v))
                             cat
                             (distinct)))
              (count)
              (println "Distinct:"))]

    (report)

    (->> #(dotimes [_ niters] (swap))
         (repeat nthreads)
         (apply pcalls)
         (dorun))

    (report)))

(run 100 10 10 100000)
;; =>
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
  ...
[990 991 992 993 994 995 996 997 998 999]
Distinct: 1000

[382 318 466 963 619 22 21 273 45 596]
[808 639 804 471 394 904 952 75 289 778]
  ...
[484 216 622 139 651 592 379 228 242 355]
Distinct: 1000
nil

Veja também

Referências

Leitura adicional

links externos