Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

LaTeX vs. GPL, ou Vocês nunca entenderão o legalismus

2013-11-05 21:38 -0200. Tags: comp, copyright, ramble

Disclaimer: Este post, que começou muito bem-apessoado e com esperança de responder de maneira clara algumas questões, acabou virando um texto extremamente rambloso que não responde coisa nenhuma. You have been warned.

Estou escrevendo a monografia do meu TCC em LaTeX, usando um modelo do pacote iiufrgs, mantido pelo Grupo de Usuários TeX da UFRGS (UTUG). O iiufrgs é distribuído sob a GPL versão 2 ou superior. Software livre, que legal!, não? O problema é que a GPL exige que obras derivadas de uma obra sob a GPL também estejam sob a GPL. Surge, assim, uma questão que muito possivelmente não ocorreu à galera que resolveu distribuir o pacote sob a GPL: um documento produzido a partir de um pacote LaTeX sob a GPL é uma obra derivada do pacote?

No caso de a resposta ser sim, há algumas conseqüências práticas. As que me ocorrem no momento são:

Este post contém os meus palpites sobre o assunto.

Disclaimer[2]: Eu não sou advogado. Estes palpites baseiam-se no meu vago e duvidoso conhecimento das leis em questão e são oferecidos as-is sem qualquer garantia de qualquer tipo, incluindo mas não se limitando a etc etc ipsis literis hocus pocus etc. Use por sua conta e risco.

Derivados

O que conta como obra derivada? A GPLv2 diz (ênfase minha):

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

"Translated into another language" supostamente se refere à tradução do programa para outras linguagens computacionais (e.g., compilação para linguagem de máquina).

Antes de mais nada, vale lembrar que os fontes em LaTeX são basicamente programas. Para fins deste post, "programa LaTeX" se refere a um arquivo escrito na linguagem LaTeX, e pdflatex se refere ao programa que lê programas LaTeX e produz PDFs. Há dois pontos a se considerar aqui:

  1. Usar um pacote GPL torna um programa LaTeX GPL?
  2. O PDF resultante de um programa GPL está coberto pela GPL?

A primeira questão é meio duvidosa. Distribuir seu programa LaTeX com comandos \usepackage que incluem pacotes GPL sem distribuir os pacotes junto definitivamente não torna seu programa GPL. Porém, no momento em que o programa é processado pelo pdflatex, pode-se considerar que ele foi combinado com o programa GPL, e o bolo alimentar resultante está sob a GPL. Essa discussão é similar à velha história de se um programa linkado com uma biblioteca GPL está sob a GPL ou não, que se reduz à questão de se um programa que usa uma biblioteca é uma obra derivada da biblioteca. A FSF acha que sim. Eu me limito a não achar nada.

A segunda questão contém várias subquestões. A primeira é se a geração de um PDF a partir de um programa LaTeX conta como uma obra derivada do programa. Isso depende em parte de o que exatamente o pdflatex faz com os arquivos. Eu vejo duas possíveis interpretações:

  1. O pdflatex lê o programa LaTeX e o compila para PDF. Nesse caso, o resultado é uma tradução do programa LaTeX para PDF, e portanto está coberto pela GPL;
  2. O pdflatex lê o programa LaTeX e executa suas instruções. Nesse caso, o resultado é meramente a saída do programa, e não uma tradução, e a saída de um programa GPL por si só não constitui uma obra derivada do programa, a menos que ela inclua trechos do programa ou modificações ou traduções de trechos. Isto é, o status da saída de um programa como obra derivada não depende do fato de ela ter sido produzida pelo programa, e sim por seu conteúdo.

E aqui eu também fico na dúvida, pois a coisa é um tanto quanto sutil. O que me impede de dizer que o que um compilador C faz é executar um programa em uma domain-specific language para geração de código de máquina? Nesse caso o binário seria só a saída da execução do programa, e portanto não seria uma obra derivada. "A menos que a saída contenha uma obra derivada", você me diz, apontando para o fato de que a tal saída poderia ser vista como uma "tradução" do programa C, e portanto uma obra derivada (por definição pelo parágrafo 0 da GPLv2), apesar de ter sido produzida por "execução", e não por "tradução". Mas o que conta como tradução? Se eu escrevo um programa que imprime a data atual, eu não posso considerar a saída como uma "tradução" do programa? Imagino que não, porque a saída não é "equivalente" ao programa: ela produz uma data fixa, e não a data atual. Mas e se o programa produzir uma saída fixa? Se P é o programa:

for (i=1; i<=5; i++) printf("%d ", i*i);

o programa

printf("1 4 9 16 25 ");

é uma tradução / obra derivada do programa P? Se a resposta for não, então um compilador super-otimizante corre o risco de produzir um executável que não é uma obra derivada do fonte. (Imagine a mesma situação com um código-fonte de milhares de linhas transformado beyond recognition por um compilador.) Mas se a resposta é sim, abre-se um precedente para considerar a saída de um programa cuja saída é sempre a mesma uma obra derivada do programa. Acontece que um programa LaTeX é exatamente um tal programa; ignorando coisas como geração da data atual na capa do documento, um programa LaTeX sempre produz um PDF "equivalente".

Me parece difícil nesse caso, entretanto, considerar o PDF diretamente como uma obra derivada do pacote, pois o pacote não gera sempre a mesma saída; o PDF não é "operacionalmente equivalente" ao pacote. Quem gera sempre a mesma saída é o seu programa, que por acaso usa o pacote. Porém, se o fato de "linkar" o programa com o pacote produz uma substância mista que é uma obra derivada do pacote, coberta pela GPL, o PDF seria a tradução dessa substância mista, e portanto uma obra derivada, e portanto coberta pela GPL.

Conclusão

A conclusão é que eu sinceramente não sei se o PDF produzido a partir de um programa LaTeX que usa um pacote GPL está coberto pela GPL, e que copyright é uma coisa não muito bem definida quando transposta para o mundo do software. O que eu sei é que todo esse transtorno poderia ter sido evitado se o pessoal do UTUG tivesse usado uma outra licença para o iiufrgs. Talvez simplesmente usar a LGPL ao invés da GPL resolva esse problema. Outra alternativa seria adicionar uma "linking exception" à nota de copyright dos arquivos do iiufrgs. Escrever os detalhes da exceção é sugerido como exercício para o leitor.

Comentários

Tudo o que você nunca quis saber sobre union types

2013-10-23 02:45 -0200. Tags: comp, prog, pldesign

Este post relata o que eu aprendi tentando misturar (ou combinar) uniões de tipos e polimorfismo paramétrico na mesma linguagem. Faz tempo que eu não escrevo um post de 20k que ninguém vai ler, então aproveito a oportunidade para documentar essas coisas todas antes que eu as esqueça. Este post está sujeito a alterações futuras (dada a impossibilidade técnica de produzir alterações passadas) caso eu lembre de mais algum detalhe.

Contexto

Para quem não sabe, meu tema de TCC é criar uma linguagem de programação funcional didática. A motivação por trás disso é reduzir os problemas encontrados pelos alunos da disciplina de Fundamentos de Algoritmos (da qual eu fui monitor por três anos) com a linguagem Scheme/Racket (especificamente, com as linguagens didáticas do How to Design Programs, doravante HtDP).

Um dos problemas com essas linguagens é a falta de um sistema de tipos estático. Como conseqüência, as funções e estruturas no código escrito na disciplina normalmente são acompanhadas de um pequeno comentário em um formato semi-padrão indicando os tipos dos parâmetros e retorno das funções, campos de estruturas, etc. O problema é que essa informação não é usada pela linguagem, o que significa que (1) ela não é lá muito útil; (2) conseqüentemente os alunos tendem a não escrevê-la; (3) não há qualquer validação sobre o formato dos comentários, o que impede os alunos de "testar" se eles estão corretos. Para resolver isso, a nova linguagem (que, por motivos de piada interna maior, chama-se Faz), é estaticamente tipada e permite (na verdade exige) declarar os tipos das funções e estruturas.

O problema é que diversos exercícios do HtDP utilizam tipos mistos. Tipos mistos são uma maneira semi-formal de descrever os tipos de funções e de dados freqüentemente utilizados em linguagens dinamicamente tipadas. Por exemplo, se uma função área aceita como argumentos quadrados e círculos, pode-se definir um tipo misto "forma" constituído por quadrados e círculos e dizer que a função recebe uma forma e produz um número. No HtDP, tipos mistos são "definidos" por meio de comentários no código.

A maioria das linguagens estaticamente tipadas não permite definir tipos mistos dessa maneira. Ao invés disso, a maioria das linguagens funcionais estaticamente tipadas permitem a declaração de uniões etiquetadas (tagged unions), ou sum types. Por exemplo, em Haskell, um tipo para formas poderia ser escrito como:

data Forma = Retangulo Float Float
           | Circulo Float

A declaração define o tipo (Forma) e os possíveis construtores de valores desse tipo (Retangulo, Circulo) e os tipos dos campos de cada construtor. Para criar um valor, escreve-se uma chamada ao construtor, tal como Retangulo 2 3 ou Circulo 4.

Uma união etiquetada é diferente de um tipo misto porque o tipo misto não exige uma etiqueta; é possível definir um tipo misto "número-ou-string" consistindo de números ou strings, por exemplo, sem definir novos construtores para o tipo novo. Embora a diferença possa parecer trivial, em muitos casos a ausência de tags é bastante conveniente. Por exemplo, alguns exercícios do HtDP definem uma "página Web" como uma lista contendo símbolos (palavras) e outras páginas Web (subpáginas). (I know, I know.) Um exemplo de "página Web" seria algo como:

(list 'Eis 'um 'exemplo 'de 'página 'web
      (list 'the 'quick 'brown 'fox 'had 'better 'things 'to 'do)
      (list 'whatever 'whatever 'bla))

Em Haskell, uma definição para esse tipo de dados ficaria algo como:

data Webpage = List [WebpageContent]
data WebpageContent = Word String | Sub Webpage

E o exemplo ficaria:

List [Word "Eis", Word "um", Word "exemplo", Word "de", Word "página", Word "web",
      Sub (List [Word "the", Word "quick", Word "brown", Word "fox", Word "had",
                Word "better", Word "things", Word "to", Word "do"]),
      Sub (List [Word "whatever", Word "whatever", Word "bla"])]

Que é "um pouco" menos conveniente. Poder-se-ia argumentar que a escolha de representação é tosca, mas de qualquer forma, uma vez que o meu objetivo era permitir transcrever os exercícios do HtDP para Faz com o mínimo de turbulência, eu queria encontrar alguma maneira de integrar os tipos mistos do HtDP na linguagem da maneira mais direta possível.

Então pensei eu: e que tal se adicionarmos uniões de tipos na linguagem? Uniões representam diretamente a idéia de tipos mistos. O usuário simplesmente diz:

tipo Coisas = Números U Strings

e agora 42 e "foo" pertencem ao tipo Coisas, sem necessidade de declarar tags. (Por baixo dos panos, a implementação guarda "tags" para saber os tipos dos valores, como em qualquer linguagem dinamicamente tipada, mas isso é um detalhe de implementação que não interessa ao usuário.) Simples, hã? Agora temos tipos mistos e tipagem estática. (By the way, a declaração acima é válida em Faz.)

Estranhamente, entretanto, aparentemente nenhuma linguagem estaticamente tipada usada por mais do que duas pessoas suporta uniões de tipos. Por que será?

Polimorfismo paramétrico

Haskell e companhia suportam uma coisa chamada de polimorfismo paramétrico (também conhecida por tipos genéricos no mundo C++/Java). Nessas linguagens é possível declarar coisas como "listas de α, para todo α", ao invés de declarar um tipo novo para cada tipo de lista que se deseja utilizar. Também é possível escrever funções de tipos genéricos. Por exemplo, uma função para incluir um elemento em uma lista nessas linguagens possuiria um tipo como (α, lista de α) → lista de α, i.e., uma função que recebe um argumento de um tipo α qualquer, uma lista de elementos do mesmo tipo α, e produz uma lista do mesmo tipo. Uma função que implementa o operador de composição de funções (f∘g), i.e, que recebe duas funções e produz uma terceira função que é equivalente a aplicar a segunda sobre o resultado da primeira, teria um tipo como (α→β, β→γ) → (α→γ), indicando que as funções podem ser de quaisquer tipos, mas o resultado da primeira tem que ser do mesmo tipo do argumento da segunda (β), e a função resultante recebe um argumento do mesmo tipo do da primeira (α) e produz um resultado do mesmo tipo do da segunda (γ).

A verificação/inferência de tipos nessas linguagens é feita usando o famoso Hindley-Milner type system e extensões do mesmo. Os detalhes podem ser um pouco sórdidos, mas, a menos que eu esteja viajando completamente, a idéia do Hindley-Milner é extremamente simples: ao checar uma expressão, gera-se uma lista de constraints (restrições) que devem ser satisfeitas para que a expressão esteja bem tipada. Por exemplo, se temos as seguintes funções (e respectivos tipos):

compose       : (α→β, β→γ) → (α→γ)
bool_to_int   : bool→int
int_to_string : int→string

então uma chamada como compose(bool_to_int, int_to_string) só é bem-tipada se:

α→β = bool→int
β→γ = int→string

Esta é a lista de constraints que devem ser satisfeitos. Do primeiro constraint, tem-se que:

α = bool
β = int

e do segundo,

β = int
γ = string

Como não há conflito entre os constraints, tem-se que a expressão é bem-tipada, e o tipo da chamada é α→γ = bool→string. Por outro lado, uma chamada como compose(bool_to_int, bool_to_int) produziria os constraints:

α→β = bool→int   =>  α = bool
                     β = int
β→γ = bool→int   =>  β = bool
                     γ = int

que não podem ser satisfeitos, e portanto a expressão é mal-tipada.

Enter subtyping

O Hindley-Milner foi feito para resolver constraints de igualdade. Em uma linguagem com relações de subtipagem (e.g., em que se pode declarar um tipo funcionário como subtipo de pessoa), aplicações de função não exigem mais que os tipos dos argumentos sejam iguais aos declarados para os parâmetros, mas sim que eles estejam contidos nos tipos dos parâmetros. Por exemplo, se pessoa tem os campos nome e idade, e funcionário tem os campos nome, idade e salário, então uma função como obtém_idade, do tipo pessoa → int, pode receber tanto pessoas quanto funcionários como argumentos.

E a presença de uniões de tipos basicamente introduz relações de subtipagem da maneira mais desenfreada possível: quaisquer dois tipos A e B possuem um supertipo comum, A U B. Isso significa que se temos uma função f do tipo (α, α) -> α, uma chamada como f(1, "foo") produz os constraints:

int ⊆ α
strings ⊆ α

que é satisfatível com α = int U string. Note que, na verdade, int U string é apenas um limite inferior para α: qualquer outro supertipo, como int U string U char, ou (top, o supertipo de todos os tipos), também serviria. Porém, intuitivamente int U string é o tipo mais "útil" inferível para a expressão, no sentido de que é o que mantém a informação mais precisa de que tipo de coisas se pode fazer com o resultado.

Nem sempre, entretanto, existe um único tipo "mais útil". A relação de subtipagem entre tipos funcionais possui uma propriedade curiosa: um tipo Sparam → Sreturn é subtipo de Tparam → Treturn, ou

Sparam → Sreturn ⊆ Tparam → Treturn

se

Tparam  ⊆ Sparam e
Sreturn ⊆ Treturn .

[Para entender essa inversão, você pode pensar assim: um tipo (S) é subtipo de outro (T) se S puder ser usado em qualquer lugar que T possa ser usado (e.g., funcionário é um tipo de pessoa porque um funcionário pode ser usado em qualquer lugar em que uma pessoa pode ser usada). Para que uma função (f) possa ser usada no lugar de outra (g), ela não pode exigir mais do argumento do que a outra, mas pode exigir menos (e.g., se g espera funcionários, então uma função que espere pessoas pode ser usada em seu lugar, pois funções que esperam pessoas também aceitam funcionários). Por outro lado, f tem que produzir um resultado tão bom ou melhor do que o da função que ela está substituindo (e.g., se g produzia pessoas, então f tem que produzir uma pessoa ou um funcionário, pois quem vai usar o resultado da função espera trabalhar com o resultado como se ele fosse uma pessoa).]

Agora imagine que temos uma função f do tipo (α→α) → (α→α), e uma função g do tipo int U string → int. A chamada f(g) produz o constraint:

int U string → int ⊆ α → α

de onde tem-se:

α ⊆ int U string
int ⊆ α

Tanto α = int quanto α = int U string são soluções válidas para os constraints, e nenhuma é evidentemente melhor que a outra.

Coisas como a chamada compose(bool_to_int, int_to_string) da seção anterior agora produzem constraints do tipo:

α ⊆ bool
int ⊆ β
β ⊆ int
string ⊆ γ

Destas, a única variável cujo tipo é fixado pelos constraints é β; as outras duas só possuem upper e lower bounds. Novamente, a solução "óbvia" ou "mais útil" seria α = bool, γ = string. Formalizar o "mais útil" no caso geral, entretanto, é um problema não-trivial e, como visto, nem sempre existe solução (o que fazer nesses casos é uma boa pergunta).

Variable identification

No Hindley-Milner, sempre que o constraint solver encontra um constraint da forma variável = whatever, ele substitui todas as ocorrências da variável por whatever no tipo e nos demais constraints, mesmo que whatever seja outra variável. Isso é válido porque os constraints são igualdades. No mundo da subtipagem, entretanto, igualar as variáveis nem sempre é a melhor solução. Por exemplo, considere as seguinte funções, escritas em um pseudocódigo funcional esquisito:

foo(f: α→β, g: α→γ, h: α→γ, k: β→γ) : Tripla(α→γ, α→γ, α→γ)
 = Tripla(compose(f, k), g, h)

jogo_do_pim(x: Int) : Int U String
 = if x mod 4 ≠ 0 then x
                  else "pim"

jogo_do_pi(x: Int) : Int U String U Char
 = if x == 4 then 'π'
   else if x mod 4 ≠ 0 then x
   else "pim"

id(x: ι) : ι
 = x

(Procurando por "jogo do pim" na Web encontrei um negócio interessante.) Agora, queremos tipar a chamada:

foo(jogo_do_pim, jogo_do_pi, id, id)

que produz os seguintes constraints, um para cada parâmetro/argumento (note que cada ocorrência de id usa variáveis de tipo separadas; caso contrário, todas as chamadas de id do programa teriam que ter o mesmo tipo):

Int → Int U String         ⊆  α → β
Int → Int U String U Char  ⊆  α → γ
ι  → ι                     ⊆  α → γ
ι′ → ι′                    ⊆  β → γ

Digerindo esses constraints, temos:

α            ⊆ Int
Int U String ⊆ β

α                    ⊆  Int
Int U String U Char  ⊆  γ

α ⊆ ι
ι ⊆ γ

β  ⊆  ι′
ι′ ⊆  γ

Se fôssemos igualar variáveis a la Hindley-Milner, teríamos, pelos dois últimos constraints, que α = γ e β = γ, e portanto α = β, mas não é possível resolver os constraints assim, pois α ⊆ Int e Int U String ⊆ β. Uma solução válida é:

α = Int
β = Int U String
γ = Int U String U Char

o que exemplifica que identificar/unificar as variáveis em constraints da forma α ⊆ β não é uma boa idéia na presença de union types (ou subtipagem em geral). Isso significa que podemos dar tchau para o Hindley-Milner. Só que agora temos um bocado de problemas. Por exemplo, suponha que temos os seguintes constraints:

α ⊆ Listas de β
β ⊆ Listas de α

Isto é um ciclo, que produziria um tipo infinito, o que em princípio é um erro. O Hindley-Milner facilmente detecta esta situação: ao encontrar o primeiro constraint, ele substitui todas as ocorrências de α por Listas de β, e o segundo constraint fica β ⊆ Listas de Listas de β. De forma geral, um ciclo sempre leva em algum ponto a um constraint em que a mesma variável aparece dos dois lados, o que é fácil de detectar. Sem igualamento de variáveis, essa abordagem não funciona.

Side note: para evitar a unificação, uma coisa que eu tinha pensado era, ao encontrar constraints do tipo α ⊆ Listas de β, substituir todas as ocorrências de α por Listas de α′, refletindo o fato de que se sabe que α é uma lista, mas não se sabe ainda de quê. O problema é que nesse caso o algoritmo entra em loop, pois:

α ⊆ Listas de β  => Listas de α′ ⊆ Listas de β            (expansão de α)
β ⊆ Listas de α     β ⊆ Listas de Listas de α′

                 => α′ ⊆ β                  (cortando o construtor comum)
                    β ⊆ Listas de Listas de α′

                 => α′ ⊆ Listas de β′
                    Listas de β′ ⊆ Listas de Listas de α′ (expansão de β)

                 => α′ ⊆ Listas de β′
                    β′ ⊆ Listas de α′       (cortando o construtor comum)

                    (... and so on ...)

Problema dos ciclos à parte, como devemos tipar uma expressão como compose(id, id)? A expressão produz os seguintes constraints:

α  ⊆  ι
ι  ⊆  β
β  ⊆  ι′
ι′ ⊆  γ

Qual é a solução? Unificar as variáveis? Mas em quais casos é correto unificar e em quais não é?

Instanciação ambígua de variáveis

A existência de uniões de tipos torna possível declarar coisas como:

f(x: Par(Int, α) U Par(α, String)) : α
 = if x.first ∈ Int then x.second
                    else x.first

Agora se chamarmos f(Par(5, "foo")), qual é a instanciação correta para tipo de x? α = Int ou α = String? Uma solução é detectar esse tipo de coisa e proibir que o mesmo tipo paramétrico apareça múltiplas vezes em uma união com um argumento que alterna entre concreto e abstrato. Da mesma forma, parâmetros de tipos como Int U α devem ser proibidos, pois a instanciação de α é ambígua quando o argumento é Int. E α U β também é ambíguo, pois para qualquer instanciação, a instanciação "flipada" (trocando os valores de α e β) também é válida. Supostamente essas restrições resolvem o problema, mas eu não estou quite sure quanto a isso.

O que eu fiz em Faz (sic)

Com o tempo limite para concluir o TCC se aproximando e a minha paciência/interesse no problema acabando, o que eu acabei fazendo foi um sério corte orçamentário na utilização de tipos paramétricos e uniões na linguagem. Para evitar o problema da unificação de variáveis, os casos em que ela é necessária são simplesmente proibidos: constraints da forma X ⊆ Y em que tanto X quanto Y contêm variáveis abstratas de tipos são rejeitados pelo typechecker. Isso significa que coisas como compose(id, id), em que tanto o parâmetro (α→β) quanto o argumento (ι→ι) são polimórficos, são rejeitadas. É tosco, mas pelo menos some com o problema. Além disso, uniões de tipos paramétricos também possuem algumas restrições (que eu ainda não defini direito, para ser sincero; quando eu terminar o TCC pode ser que eu edite esta seção).

Conclusão

Certa vez um sábio disse o seguinte:

Follow! But follow only if ye be man of valour! For the entrance to this cave is guarded by a creature, so foul, so cruel, that no man yet has fought with it and lived! Bones of full fifty men are strewn about its lair! So, brave knights, if you do doubt your courage or your strength come no further, for death awaits you all, with nasty, big, pointy teeth...

Acredito que ele estava falando de union types.

Comentários

A personal appeal

2013-10-04 23:20 -0300. Tags: misc

[Creepy Jimmy Wales]

Alguns meses atrás eu tive este diálogo:

them: onde tu aprendeu tantas coisas?
me: na Wikipédia, a fonte de todo o conhecimento :P
them: achei que nas aulas de história
them: =P
me: pfff, aulas
me: a parte boa de estudar em colégio público é que eu tinha um horror de tempo livre pra ler a wikipédia :P
them: tb estudei em colegio publico
[...]
them: mas na minha época internet não era tão difundida
me: fora que quando eu tava no ensino fundamental eu não sabia inglês suficiente pra ler a wikipédia :P
me: na real eu meio que aprendi inglês lendo a wikipédia
me: cara
me: cara
me: ano que vem acho que eu vou fazer uma doação pra wikipédia :P
them: =D
them: se rendeu aos encantos deles?
me: não, cara
me: só me dei conta de que eles são mais responsáveis pela minha educação do que o colégio :P

Eu tenho usado a Wikipédia quase que diariamente nos últimos sete anos. Este ano a Wikipédia começou a aceitar doações por boleto bancário no Brasil, então resolvi fazer uma doação (de 20 pila) pela primeira vez. Doações podem ser feitas por aqui.

2 comentários

Prevérbios

2013-09-18 00:16 -0300. Tags: lang

Muitas pessoas ficam perplectas ao se depararem pela primeira vez com os assim chamados phrasal verbs do inglês, construções verbo+partícula tais como give up, give in, give away, throw up, take off, take over, blow up, em que o significado da expressão nem sempre é óbvio a partir do significado das partes. Acontece que o português está recheado de um primo distante dos phrasal verbs herdado do latim. Tão recheado que só até agora neste post eu já usei pelo menos seis dessas construções (possivelmente sete, dependendo (oito!) de o que conta como "phrasal verb").

Os phrasal verbs são uma feature comum entre as línguas germânicas. Em alemão e holandês, eles existem na forma de verbos separáveis, i.e., construções partícula+verbo em que a partícula ocorre incorporada ao verbo ou não dependendo da construção verbal. Entre as línguas nórdicas, construções análogas à do inglês também parecem ser bastante comuns (por exemplo, "give up" em sueco é "ge upp" [update: e em islandês a expressão "gefa upp" também existe, mas aparentemente significa "revelar, deixar escapar"]).

Essa característica das línguas germânicas tem origem em um fenômeno análogo em proto-indo-europeu. Em PIE, algumas partículas adverbiais ou posposicionais (não há uma distinção muito forte entre esses dois tipos de partícula em PIE, da mesma maneira que o status de algumas partículas como advérbio ou preposição é duvidoso/variável em inglês [citation needed]), convencionalmente chamadas prevérbios, possuem efeitos similares sobre o significado de diversos verbos, e podem ser incorporadas ou não ao verbo, de maneira similar ao que ocorre em alemão e holandês. Em latim arcaico, essa situação se preserva. O exemplo clássico é a expressão "sub vos placo" (= "vos supplico"), graças a um camaradinha que escreveu:

Posteriormente, essas partículas passaram a ser obrigatoriamente incorporadas ao verbo em latim. (Em latim, a incorporação de prefixos ocasionalmente provoca certas alterações nas vogais da palavra, daí a mudança de sub placo → supplico e ob sacro → obsecro. No período clássico, as construções originais, com a partícula separada, ja não deviam mais fazer muito sentido para os falantes de latim, razão pela qual o camarada Festus deve ter sentido a necessidade de dar essa explicação.)

Esse fenômeno é absurdamente produtivo em latim. Por exemplo:

Para encerrar, aqui vai uma lista de algumas palavras originárias de construções partícula+verbo usadas neste post:

E isso só no primeiro parágrafo (9 palavras em 81 (descartando as palavras em inglês, e contando repetições), i.e., 1/9 das palavras do parágrafo são derivadas construções partícula+verbo). Há um bocado de outras palavras do mesmo tipo, tais como "ad-vérbio", "convencional" (de "con-vir"), "in-corporar", "pre-servar", "pro-vocar", e diversas outras. E isso só contando (< "com-putando") palavras do latim ("aná-logo" é uma construção análoga em grego). De repente o inglês começa a parecer brincadeirinha de criança.

8 comentários

Anarquia Teocrática

2013-09-15 02:59 -0300. Tags: about

Resolvi criar um blog para suprir minha necessidade de publicar nonsense e outros insultos à literatura sem infligi-los aos leitores deste blog aqui, que é bello e sério demais para o assunto, ou assim eu tento me iludir. O orkut anda me fazendo falta.

Perdoem a falta de posts decentes por aqui nos últimos tempos. Há pelo menos dois na fila, assim que eu criar o ânimo para escrevê-los.

7 comentários

Convertendo archives do LISTSERV para mbox

2013-09-04 01:45 -0300. Tags: comp, unix, prog, perl

Escrevi um pequeno script em Perl para converter um archive de mailing list do LISTSERV para o formato mbox, que pode ser importado em diversos clientes de e-mail. Possa ele ser-vos útil.

3 comentários

Zoom lento no MPlayer

2013-08-27 23:52 -0300. Tags: comp, unix, mundane

Esses dias fui assistir um filme com uma resolução maior do que a minha tela com o mplayer e meu Atom não estava dando conta de fazer o zoom out/scaling em tempo real.

Solução? O mplayer suporta uma opção -sws N, que permite escolher o algoritmo de software scaling a ser usado. Usando -sws 4, obtém-se um zoom de qualidade levemente pior, mas que consome menos processamento.

Outra opção útil é -autosync N, que controla a sincronia entre áudio e vídeo. A documentação do mplayer recomenda -autosync 30 para resolver problemas de sincronia com drivers de áudio problemáticos. No meu caso, -autosync 1 pareceu funcionar melhor. Não sei exatamente o que faz essa opção, sinceramente. Para mais informações, consulte a manpage do mplayer.

Quem me contou foi esse cara.

1 comentário

Raciocínio categorial

2013-08-04 00:33 -0300. Tags: life, random

Certa vez, quando eu tinha uns sete anos de idade, estava na casa de um tio. Minha prima (que se minhas contas não falharam, tinha uns nove anos) estava tendo dificuldades com um tema de casa que provavelmente envolvia conjugações verbais. Meu tio diz:

"Mas isso aí até o Vítor sabe. Vítor, qual é a primeira pessoa?"

Eu não fazia a menor idéia do que ele estava falando. Ponderei sobre aquela questão profundamente filosófica de quem diabos era a primeira pessoa por uns três ou quatro segundos e respondi:

"Eu?"

Até hoje eu me admiro daquela resposta.

2 comentários

Die, monster

2013-07-27 03:33 -0300. Tags: life, mind

You don't belong in this world.

4 comentários

Determinando a posição do cursor em um arquivo com o lsof

2013-07-18 23:21 -0300. Tags: comp, unix, mundane

Às vezes executamos comandos do tipo:

cat imagem_grande_que_demora_para_copiar.img >/dev/sdb

e queremos saber o andamento do processo. Quando copiamos os dados de um disco para um arquivo podemos simplesmente olhar o tamanho do arquivo para ter uma idéia do andamento da operação, mas quando o destino é um disco isso não é possível.

É possível, entretanto, olhar a posição do cursor no arquivo. No Unix, todo arquivo aberto tem associado a si um cursor, i.e., a posição a partir da qual operações de leitura e escrita operam por padrão; cada leitura ou escrita no arquivo avança a posição do cursor.

Podemos utilizar um programinha chamado lsof (list open files, pacote lsof no Debian/Ubuntu) para visualizar diversas informações sobre arquivos abertos, dentre elas a posição do cursor. Por padrão, o lsof mostra uma coluna que contém ou a posição do cursor ou o tamanho do arquivo, dependendo do tipo de arquivo; a opção -o (offset) força o lsof a mostrar sempre o cursor. Além disso, por padrão o lsof mostra a posição em hexadecimal se ela ocupar mais de 8 dígitos decimais; a opção -o 0 desabilita esse comportamento. As duas opções podem ser combinadas como -oo0.

Por padrão, todos os arquivos abertos são listados. É possível especificar os nomes dos arquivos a serem listados, ou os nomes (-c nome) ou PIDs (-p pid) dos processos cujos arquivos abertos se deseja ver. Por exemplo:

# cat /dev/sda5 >/dev/sda6 &
[1] 28252
# lsof -oo0 /dev/sda6
COMMAND   PID USER   FD   TYPE DEVICE      OFFSET NODE NAME
cat     28252 root    1w   BLK    8,6 0t254476288 1303 /dev/sda6

Para mais informações, consulte a manpage do lsof.

Appendix A: Do fato de que ninguém deveria usar dd para copiar discos sem uma boa razão

# ls -lah foo
-rw-r--r-- 1 root root 512M Jul 18 23:00 foo
# time cat foo >bar

real    0m21.304s
user    0m0.068s
sys     0m5.212s
# time dd if=foo of=bar
1048576+0 records in
1048576+0 records out
536870912 bytes (537 MB) copied, 39.397 s, 13.6 MB/s

real    0m39.621s
user    0m1.528s
sys     0m25.910s

O motivo para isso é que o dd sempre copia os arquivos usando um tamanho de bloco fixo (indicado pelo parâmetro bs=tamanho, 512 bytes por padrão), enquanto o cat usa um tamanho de bloco "ótimo", o que permite que ele faça a cópia com menos chamadas de sistema (o que se reflete no tempo de sys na saída do comando time). Alternativamente, você pode especificar um tamanho de bloco maior para o dd (e.g., bs=1M), mas isso não apresenta nenhuma vantagem sobre usar o cat, a menos que você queira especificar o tamanho do arquivo também (e.g., no clássico dd if=/dev/zero of=foo bs=1M count=512 (que no entanto também pode ser substituído por um head -c 512M /dev/zero >foo)).

1 comentário

Main menu

Posts recentes

Tags

comp (83) prog (34) life (34) unix (29) random (25) lang (22) mundane (21) about (18) mind (16) web (13) img (12) pldesign (12) rant (11) ramble (11) privacy (8) lash (7) esperanto (7) bash (7) shell (6) home (6) conlang (5) misc (5) academia (5) mestrado (4) lisp (4) freedom (4) worldly (4) copyright (4) book (4) music (4) wrong (3) kbd (3) film (3) politics (3) security (3) latex (2) android (2) physics (2) poem (2) c (2) treta (2) php (2) network (2) cook (2) editor (2) pointless (1) audio (1) comic (1) philosophy (1) perl (1) kindle (1)

Elsewhere

Quod vide


Copyright © 2012-2015 Vítor Bujés Ubatuba De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.