Entendendo Licenças Open Source com Clojure

Só quem desenvolve ou contribui com projetos open source sabe como é legal. A pior parte é definir sob qual licença soltar o código, e essa tarefa é muitas vezes negligenciada por desenvolvedores de software.

Para quem não viu, recentemente colocamos no ar o Licensator, uma aplicação web que, além de reunir informações sobre as licenças open source mais populares, é capaz de indicar as licenças apropriadas para cada caso.

A linguagem escolhida para implementar o projeto foi Clojure, que se mostrou excelente para resolver esse tipo de problema. Veremos hoje como o principal algoritmo da aplicação funciona.

REPL: onde tudo começa

Um dos pontos fortes das linguagens dinâmicas é a facilidade em transformar idéias em código funcional, e com Clojure não é diferente. A idéia por trás do Licensator surgiu justamente em uma dessas sessões de codificação no REPL, o ambiente interativo da linguagem. Após alguns minutos brincando, vi que o problema era simples de resolver.

Apesar do elevado número de licenças, podemos facilmente extrair informações comuns a todas elas, como: se a licença é copyleft, se pode ser usada por código fechado (proprietário), entre outras coisas.

Por exemplo, vamos pegar a licença Apache v2.0:

(def *licenses*
     [{:id :apl-v20
       :long-name "Apache License v2.0"
       :short-name "Apache v2.0"
       :url "http://www.opensource.org/licenses/apache2.0.php"
       :copyright true
       :patent true
       :closed-source-linking true
       :derivative-work true
       :affero false
       :copyleft false
       :charge-for-distribution true
       :charge-for-use true
       :compatible-with [:apl-v20 :afl-v30 :cddl-v10 :epl-v10 :freebsd
                         :new-bsd :mit :mpl-v11 :public-domain]}])

A lista completa de licenças pode ser vista aqui.

Considerando que todas as licenças têm esse mesmo conjunto de informações, como podemos saber quais licenças não são recíprocas e possuem cláusulas explícitas de copyright?

(map :id
  (filter #(and (:copyright %) (not (:copyleft %))) *licenses*))
=> (:afl-v30 :apl-v20 :epl-v10 :freebsd :mit :new-bsd :bsd)

Se você conhece um pouco sobre programação funcional, este código deve ser fácil de acompanhar. De qualquer forma, o que ele faz é retornar o :id de todas as licenças onde :copyright é verdadeiro e :copyleft é falso.

Outro exemplo: quais licenças são compatíveis com Apache v2.0 e CDDL v1.0?

(use 'clojure.set)
 
(map :id
  (filter #(subset? (set [:apl-v20 :cddl-v10])
                    (set (:compatible-with %))) *licenses*))
=> (:apl-v20 :cddl-v10 :epl-v10)

Aqui usamos a namespace clojure.set, que conta com algumas funções úteis para álgebra relacional. O código é muito parecido com o anterior; a mudança é que agora usamos a função subset? para buscar as licenças compatíveis com [:apl-v20 :cddl-v10].

Último exemplo: quais licenças recíprocas são compatíveis com Apache v2.0?

(map :id
  (filter #(and (subset? (set [:apl-v20]) (set (:compatible-with %)))
                (:copyleft %)) *licenses*))
=> (:cddl-v10 :agpl-v30 :gpl-v30 :lgpl-v30 :mpl-v11)

Neste ponto já se pode perceber que não existe mágica nenhuma por trás do algoritmo; o que ele faz é filtrar as licenças com base numa função.

A solução

O código que acabamos de ver funciona, mas não é apropriado para resolver o problema uma vez que a lógica de consulta se encontra fixa no código.

O ideal seria se os critérios de busca fossem expressados como um map…

(def cmap
     {:patent true, :closed-source-linking true, :compatible-with [:mit :new-bsd]})

…portanto precisamos de uma função que faça a filtragem das licenças com base nesse mapa, algo como:

(defn find-matches [cmap data]
  (filter (where cmap) data))

No caso, essa função where, que ainda não definimos, deve retornar uma função que filtre os elementos (licenças) em data de acordo com o mapa de critérios cmap.

Vamos por partes. Quais condições em cmap são verdadeiras para a licença Apache v2.0?

(defn criteria-matches? [license [kval cval]]
  "Verifica se o critério 'centry' é verdadeiro para a licença 'license'."
  (let [lval (kval license)]
    (if (nil? cval)
      true
      (if (coll? lval)
        (subset? (set cval) (set lval))
        (= cval lval)))))
 
(map (partial criteria-matches? apl-v20) cmap)
=> (true true true)

Uma licença só pode ser considerada compatível com os critérios se a lista retornada contém apenas valores verdadeiros. Portanto, neste caso, a licença Apache v2.0 é considerada compatível com os critérios informados:

(every? true? (map (partial criteria-matches? apl-v20) cmap))
=> true

Logo, uma possível implementação para a função where seria:

(defn where
  "Retorna função que diz se todos os critérios em 'cmap' são verdadeiros para a licença 'lentry'."
  [cmap]
  (fn [lentry]
    (let [res (map (fn [centry]
		     (let [cval (val centry)
			   lval ((key centry) lentry)]
		       (if (nil? cval)
			 true
			 (if (coll? lval)
			   (subset? (set cval) (set lval))
			   (= cval lval))))) cmap)]
      (every? true? res))))

Este código é uma junção dos dois anteriores. A função where recebe o mapa cmap com os critérios de pesquisa, e retorna uma segunda função. Esta função recebe uma licença lentry e verifica se todos os critérios informados são verdadeiros para esta licença.

Como where é usada em conjunto com filter na função find-matches, isso fará com que todas as licenças compatíveis sejam retornadas:

(map :id (find-matches cmap *licenses*))
=> (:afl-v30 :apl-v20 :cddl-v10 :epl-v10 :lgpl-v30 :mpl-v11)

Uma das coisas legais de Clojure — e outras linguagens funcionais — é a quantidade ridícula de código necessário para resolver certos problemas. Este algoritmo, por exemplo, tem apenas 15 linhas (muito curtas) de código. Onde está seu Deus agora?! :-P

O código da aplicação está no Github para quem tiver interesse em ver o restante do código.

Sobre Daniel Martins

Fundador da Destaquenet, ele é graduado em Sistemas de Informação e desenvolve softwares como hobby e profissão desde 2000. Especializado na plataforma Java, ele utiliza a tecnologia há vários anos, sendo programador e desenvolvedor web certificado pela Sun Microsystems, recentemente adquirida pela Oracle. Também se interessa por assuntos ligados à cultura open source, metodologias ágeis, engenharia de software, frameworks e linguagens dinâmicas tais como Python, Ruby e Smalltalk.
Esta entrada foi publicada em Português, Programação e marcada com a tag , , , , , , , , . Adicione o link permanente aos seus favoritos.

Uma resposta a Entendendo Licenças Open Source com Clojure

  1. Pingback: Tweets that mention Entendendo Licenças Open Source com Clojure | DestaqueBlog -- Topsy.com

Deixe um Comentário

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">