domingo, 31 de julho de 2011

Metaprogramming com constexpr

Até ano passado, quando eu queria fazer algum projetinho onde eu precisava da maior velocidade possível, acabava escolhendo escrevê-lo em C++. Mas a minha vida mudou quando eu conheci o C++0x. Ele é muito superior ao C++ tradicional, adicionando features que faziam muita falta, como lambdas, variáveis auto e range for.


Uma das features novas que mais gosto é o constexpr, uma maneira de avisar ao compilador que o resultado de função pode ser determinado em tempo de compilação. Combinando isso com funções recursivas, nós temos uma maneira nova de fazer metaprogramming em C++, bem melhor que o tradicional template metaprogramming.

Para mostrar a superioridade do constexpr, resolvi escrever de três maneiras diferentes uma função bem simples: o totiente de Euler, cuja definição é a quantidade de inteiros positivos menores que n, e que não possuem fatores em comum com n. Escrito em C++ tradicional, essa função fica assim:

int gcd(int a, int b) {
  if (b == 0)
    return a;
  else
    return gcd(b, a % b);
}

int totient(int x) {
  int sum = 0;
  for (int i = 1; i < x; i++) {
    if (gcd(x, i) == 1) sum++;
  }
  return sum;
}

int main() {
  cout << totient(1000) << endl;
}

O código em si é bem simples de entender e compila em qualquer lugar. Eu calculei o GCD usando o algoritmo de Euclides, que é O(log n), então essa implementação do totiente é O(n log n).

Compare agora com o mesmo algoritmo escrito em template metaprogramming:

template<int a, int b>
struct GCD {
  const static int result = GCD<b, a % b>::result;
};

template<int a>
struct GCD<a, 0> {
  const static int result = a;
};

template<int x, int i>
struct TotientHelper {
  const static int result =
      (GCD<x, i>::result == 1) +
      TotientHelper<x, i - 1>::result;
};

template<int x>
struct TotientHelper<x, 0> {
  const static int result = 0;
};

template<int x>
struct Totient {
  const static int result =
      TotientHelper<x, x - 1>::result;
};

int main() {
  const int x = Totient<1000>::result;
  cout << x << endl;
}

Essa versão é bem mais difícil de entender e é cheia de boilerplate. A vantagem dela é que o cálculo do totiente é feito em tempo de compilação; então, em runtime, o resultado é calculado em O(1).

Por fim, a versão em c++0x usando constexpr:

constexpr int gcd(int a, int b) {
  return b == 0 ? a : gcd(b, a % b);
}

constexpr int totient(int x, int i) {
  return i == 0 ? 0 : (gcd(x, i) == 1) + totient(x, i - 1);
}

constexpr int totient(int x) {
  return totient(x, x - 1);
}

int main() {
  constexpr auto x = totient(NUMBER);
  cout << x << endl;
}

Essa versão não é tão simples quanto a que calcula em runtime, mas também não é excessivamente complexa como a versão com templates.

Para fazer metaprogramming com constexpr, você só precisa seguir algumas regras simples:
  • O corpo da função precisa ter um único return.
  • Você não pode usar if. Se você precisar de condicionais, tem que usar o operador ternário.
  • Você não pode usar for. Se você precisar de loops, tem que usar recursão.
  • Você não pode fazer atribuições a variáveis.
  • Uma pegadinha: ele só faz o cálculo em compile time se você atribuir o resultado a uma variável constexpr. Se você tentar fazer diretamente cout << totient(100), ele vai calcular em run-time.
Além disso, obviamente, você precisa de um compilador que suporte constexpr. Até onde eu sei, o único que aceita constexpr é o gcc 4.7 ou superior.

Mas a grande vantagem do constexpr sobre os templates não é só a facilidade de programação: é o tempo de compilação. Um programa com constexpr compila muito mais rápido que uma versão com templates. Para comparar o tempo de compilação, eu fiz um script que mede o tempo para vários valores do totiente:

Script em C++ para calcular o totiente em run-time
Script em C++ para calcular o totiente com templates
Script em C++0x para calcular o totiente com constexpr
Script em Python para medir o tempo dos programas acima

Vamos primeiro comparar o tempo de compilação da versão em runtime com a versão em template:



Como esperado, o tempo de compilação da versão em runtime é constante e bem pequeno. Já a versão em template é bem ruinzinha. Eu fiz uma regressão para determinar que curva é essa, e o resultado é que é uma parábola (com coeficiente de determinação R2=0.999, ou seja, com bastante certeza).

Agora a fraqueza do método com templates é evidente: o algoritmo era O(n log n) em runtime, virou O(n2) em compile-time. Veja como isso não acontece com a versão com constexpr:



Ah, agora sim! A versão com constexpr continua sendo O(n log n), só trocou o tempo de run-time pelo tempo de compilação. Comparado o template nem tem graça: para n=30000, a versão com constexpr compila em 1 segundo, e versão em template compila em 2 minutos.

A essa altura já estamos convencidos de que o constexpr é o melhor jeito de implementar metaprogramming, mas ainda falta a pergunta crucial: na prática, pra que isso serve?

Para mim, a utilidade mais imediata é pra conseguir primeiro lugar nos problemas do spoj! Como o spoj só leva em conta o tempo de run-time para ordenar as soluções, se você jogar um pouco do tempo do run-time para a compilação, consegue subir algumas colocações no ranking :)

segunda-feira, 16 de maio de 2011

Torto Reverso

No fim do ano passado o meu Nintendo DS quebrou, após anos de bons serviços prestados. Eu pensei em consertar, mas dado que o Nintendo 3DS ia sair em março, achei que valia a pena simplesmente esperar. Porém, o que fazer nesse meio tempo?


A minha solução foi ir à banca e comprar um punhado de cruzadinhas da Coquetel. No começo foi divertido, mas depois de algum tempo perdeu a graça: para quem está acostumado com a Denksel, os puzzles da Coquetel são fáceis demais.

Para aumentar a dificuldade dos puzzles, eu resolvi mudar as regras de alguns deles. A minha modificação predileta eu batizei de Torto Reverso. Antes de mostrar como funciona a minha variação, vamos relembrar as regras do Torto tradicional, tais como estão na revista. É dado um grid 3x6 de letras e a seguinte descrição:

"Deve-se formar as palavras seguindo em todas as direções, sempre ligando as letras em seqüência direta, sem cruzar, sem pular e sem repetir a mesma letra (para que uma palavra tenha a mesma letra repetida, é necessário que esta letra também esteja duplicada no diagrama). Damos como exemplo uma palavra encontrada. Só valem palavras de cinco letras ou mais. Na solução, constam 30 palavras formadas com este diagrama."

Se você tiver um bom vocabulário, esse puzzle não tem segredo. Muito mais difícil é o Torto Reverso: e se, ao invés de procurar palavras no grid, eu te der as palavras e pedir para achar um grid válido que contenha todas elas?

Depois de fazer alguns desses na mão, eu fiquei com vontade de implementar um solver. Eu poderia ter feito um solver em C++ ou Python, mas pensei aqui comigo: "eis uma boa oportunidade para aprender Prolog!"

Se você também não conhece Prolog, então acompanhe um pequeno tutorial antes de ver minha implementação do solver:

Introdução ao Prolog

Em linguagens de programação tradicionais, a metáfora que você usa para explicar é a da receita de bolo: programar é fazer uma seqüência de tarefas, como adicionar 200g de farinha e mexer até que a massa esteja consistente. Prolog usa um paradigma diferente, então a melhor metáfora é pensar que ele é um provador de teoremas: você entra com os axiomas e ele deriva os teoremas a partir deles.

Por exemplo, podemos usar o Prolog para definir os axiomas de Peano. Começamos com a definição de números naturais: zero é um número natural, e o sucessor de um natural também é um natural.

natural(0).
natural(s(X)) :- natural(X).


Com essas definições já podemos perguntar ao Prolog: será que 2, ou seja, o sucessor do sucessor do zero, é um número natural?

?- natural(s(s(0))).
true.


Bacana! E o sucessor de Togakure, é um número natural?

?- natural(s(togakure)).
false.


Está certo, afinal, se Togakure não é um número natural então o sucessor também não pode ser. Vamos agora definir a adição: zero é o elemento neutro, e se você somar qualquer coisa com o sucessor de outra coisa, o resultado é o sucessor da soma das duas coisas.

add(A, 0, A).
add(A, s(B), s(C)) :- add(A, B, C).


Podemos perguntar agora: será que 1+1=2? Será que 1+1=3?

?- add(s(0), s(0), s(s(0))).
true .
?- add(s(0), s(0), s(s(s(0)))).
false.


Até agora só usamos o Prolog para testar a veracidade de sentenças, mas a diversão começa quando usamos o Prolog para completar sentenças. Sempre que você deixar uma variável livre numa sentença, o Prolog tenta completar a sentença para torná-la verdadeira. Por exemplo, quanto é 2+2?

?- add(s(s(0)), s(s(0)), X).
X = s(s(s(s(0)))) .


Talvez a parte mais bacana do Prolog seja o fato da linguagem ser reversível. O mesmo programa que avalia a soma também é capaz de resolver uma equação. Por exemplo, qual é o número que somado com 2 dá 5?

?- add(X, s(s(0)), s(s(s(s(s(0)))))).
X = s(s(s(0))) .


E qual é o número que somado com 1 dá 0?

?- add(X, s(0), 0).
false.


O Prolog respondeu false, ou seja, no conjunto dos naturais não tem nenhum número que somado com 1 dá 0. Outra coisa bacana é que você pode deixar mais de uma variável livre. Por exemplo, quais são os pares de números cuja soma é 2?

?- add(A, B, s(s(0))).
A = s(s(0)),
B = 0 ;
A = s(0),
B = s(0) ;
A = 0,
B = s(s(0)) ;


Ou seja, as soluções possíveis são 2+0, 1+1 e 0+2.

Resolvendo o Torto Reverso

Agora que sabemos o básico do Prolog, já temos o material necessário para resolver o puzzle! Note que eu usei como interpretador o SWI-Prolog, porque ele tem a melhor biblioteca de built-ins dos Prologs que eu pesquisei. Tudo que estiver em verde são palavras reservadas do SWI-Prolog.


Em linguagens tradicionais você precisa descrever, passo a passo, um método para encontrar a solução do seu problema. Em Prolog é o contrário, você só precisa falar quais são as propriedades da sua solução, e o interpretador se vira para achar um método de solucionar.

Por exemplo, nós podemos criar um predicado que é verdadeiro se o nosso grid é uma lista com 18 elementos. Mas como Prolog é reversível, se você deixar a variável do grid livre, ele vai criar um grid para você!

validgrid(Grid) :-
  length(Grid, 18).

?- validgrid(X).
X = [_G282, _G285, _G288, _G291, _G294,|...].

O Prolog não tinha informação suficiente para deduzir quem eram os elementos do grid, então colocou placeholders no lugar (_G282, _G285, etc).

Note também que o Prolog não tem arrays bidimensionais, só listas. Por isso, vamos precisar de um predicado para checar se os índices do grid são válidos, e um para indexar o grid a partir dos índices:

valid(I,J) :-
  between(0, 2, I),
  between(0, 5, J).

validletter(Grid, Letter, X, Y) :-
  valid(X, Y),
  Pos is X + Y * 3,
  nth0(Pos, Grid, Letter).

Nesse trecho o importante é entender o comando is. Para o Prolog, 1+1*3 é diferente de 4, porque ele não faz a avaliação aritmética por default. Usando o comando is, você o força a avaliar a expressão.

Agora vamos definir a conectividade do grid. Dada uma posição válida qualquer, nós podemos nos mover para qualquer das oito direções, desde que você não saia do grid. Para isso, podemos definir as oito possibilidades para um predicado move:

move(I, J, left, X, J) :-
  X is I - 1,
  valid(X, J).

(...)

move(I, J, down_right, X, Y) :-
  X is I + 1,
  Y is J + 1,
  valid(X, Y).

Assim, já podemos perguntar ao Prolog: saindo da posição 0,0, quais são as posições atingíveis?

?- move(0, 0, Direction, X, Y).
Direction = right,
X = 1,
Y = 0 ;
Direction = down,
X = 0,
Y = 1 ;
Direction = down_right,
X = 1,
Y = 1.

Pronto, já temos as ferramentas básicas. Vamos descrever a nossa solução então. Nós queremos colocar uma palavra no grid, então quais as propriedades dessa palavra? Se a palavra tiver uma letra só, ela pode ser encaixada em qualquer posição do grid:

word(Grid, [Letter], X, Y) :-
  validletter(Grid, Letter, X, Y).

Agora, se ela tem várias letras, então você encaixa a primeira letra e faz a recursão para o resto da palavra, notando que o resto começa em um ponto que é um movimento válido a partir da primeira letra:

word(Grid, [Letter | Tail], X, Y) :-
  validletter(Grid, Letter, X, Y),
  move(X, Y, _, I, J),
  word(Grid, Tail, I, J).

Aqui temos mais dois operadores do Prolog: o operador pipe separa uma lista em head e tail, e o operador underscore significa "don't care", nós não ligamos para o que o Prolog vai encaixar ali.

Isso já é o suficiente para encaixar uma palavra no grid, por exemplo, a partir da posição 0,0:

solve(Word) :-
  word(Grid, Word, 0, 0),
  writegrid(Grid).

?- solve("torto").
tor
.ot
...
...
...
...

Eu pedi para imprimir só a primeira solução, mas nós poderíamos ter pedido para o Prolog imprimir todas as soluções. (Eu pulei a definição do writegrid, porque não é tão interessante.)

Antes de encaixar o resto das palavras, precisamos adicionar o restante das regras. Uma regra que não lidamos até agora é que uma palavra não pode repetir uma posição do grid durante o trajeto.

Para implementar essa restrição, podemos criar uma lista de posições já visitadas, e só permitir a inserção de uma letra se a posição dela ainda não estiver na lista. Vamos modificar nosso predicado word então:

word(Grid, [Letter], Visited, X, Y) :-
  validletter(Grid, Letter, X, Y),
  \+ member(pos(X, Y), Visited).

word(Grid, [Letter | Tail], Visited, X, Y) :-
  validletter(Grid, Letter, X, Y),
  move(X, Y, _, I, J),
  \+ member(pos(X, Y), Visited),
  word(Grid, Tail, [pos(X, Y) | Visited], I, J).

A novidade aqui é o operador \+, que é simplesmente um "not". Ou seja, insira a nova letra somente se a posição dela não estiver na lista Visited.

Mas ainda tem outra regra: a palavra não pode cruzar o próprio caminho. Na horizontal e na vertical isso não tem como acontecer, porque para isso ele teria que repetir uma posição. Mas pode acontecer na diagonal, como na figura abaixo:

Etna não é um vulcão válido.

Para evitar esse caso, podemos fazer uma nova lista. Sempre que passarmos na diagonal por um bloco delimitado por letras, adicionamos esse bloco na lista.

Agora, para checar se andamos na diagonal, poderíamos verificar a direção que o Prolog deduziu no predicado move. Ao invés disso, vamos fazer de um jeito diferente:

validBlock(X, _, X, _, Block, Block) :- !.
validBlock(_, Y, _, Y, Block, Block) :- !.
validBlock(X, Y, I, J, OldBlock, NewBlock) :-
  Bx is min(X, I),
  By is min(Y, J),
  \+ member(pos(Bx, By), OldBlock),
  NewBlock = [pos(Bx, By) | OldBlock].

Se um movimento repetir o X, ou se repetir o Y, então não é uma diagonal, e não precisamos adicionar nada na lista. Para isso, usamos o operador exclamação, que implementa um corte.

A idéia é simples: um predicado do tipo validBlock(0, 0, 0, 0, A, B) iria fazer match nas três instâncias declaradas, e por isso o Prolog iria deduzir três soluções para esse predicado. Mas, com o corte, ele desiste de procurar mais soluções após encontrar a primeira.

Note também que, para definir unicamente um bloco, independente da direção da diagonal, basta pegar o mínimo das posições X e Y.

Podemos agora incorporar esse predicado para chegar na versão final do word:

word(Grid, [Letter], Visited, _, X, Y) :-
  validletter(Grid, Letter, X, Y),
  \+ member(pos(X, Y), Visited).

word(Grid, [Letter | Tail], Visited, Block, X, Y) :-
  validletter(Grid, Letter, X, Y),
  move(X, Y, _, I, J),
  \+ member(pos(X, Y), Visited),
  validBlock(X, Y, I, J, Block, NewBlock),
  word(Grid,Tail,[pos(X, Y) | Visited],NewBlock,I,J).

Isso já encaixa qualquer palavra seguindo todas as regras! Agora é só falar para inserir uma lista de palavras, ao invés de uma palavra só, e terminamos nosso solver:

wordlist(_, []).
wordlist(Grid, [Word | WordList]) :-
  word(Grid, Word, [], [], _, _),
  wordlist(Grid, WordList).

solve(WordList) :-
  validgrid(Grid),
  wordlist(Grid, WordList),
  writegrid(Grid).

?- solve(["otica","dedal","tedio","cardeal","aldeia"]).
oti
dac
eda
tel
idr
oac

O solver em Prolog ficou curto, né? Note que nós descrevemos só a solução, e não o método de solução. Em outras linguagens você precisaria se preocupar com Backtracking, A*, Exact Cover e o escambau, enquanto que no Prolog isso é feito por baixo dos panos para você.

Se você quiser rodar o solver em casa, aqui está o fonte completo:

Solução do Torto Reverso em Prolog

A desvantagem de escrever em Prolog é que fica um pouco mais lento que as linguagens tradicionais. Com essa implementação mostrada, temos 30 palavras, onde cada uma tem uma posição inicial definida num grid de 18 posições, e no mínimo 5 letras, onde cada letra pode estar em 8 posições possíveis a partir da anterior.

Ou seja, o espaço de busca é de (18*84)30, o que dá um total de 106898987586074109109365096428568366376512477689679588996632532409238880450162648732507836110780231551768603122161241934850249808547434035647873024 grids possiveis. Daí, usar esse solver em Prolog para um grid com 30 palavras pode demorar um bocadinho.

Ricbit Jam #3

Você acha que consegue fazer melhor que o Prolog usando sua linguagem predileta? Então participe do Ricbit Jam! Quem conseguir fazer o solver mais rápido do Torto Reverso vai ganhar de brinde um Boliche de Dedo oficial do Google:

Boliche de dedo do Google

Para participar, é só ler as regras abaixo:

Regras do Ricbit Jam #3

Divirta-se, e boa sorte!

segunda-feira, 18 de abril de 2011

As Três Leis de Newton

Desde pequeno, eu sempre adorei ler textos sobre a história da ciência. Quando criança eu lia a Superinteressante, na época em que era boa, e mais recentemente eu lia os bons livros de divulgação, como o Drunkard's Walk do Mlodinow. Mas, em algum ponto no ano passado, caiu uma ficha aqui. Por que eu estava lendo informação de segunda mão sobre probabilidade, se podia ler diretamente os originais do Laplace?

O maior problema em ler textos originais é a língua. Afinal, o Laplace escrevia em francês, o Euclides em grego, o Einstein em alemão, e eu não domino nenhuma dessas línguas. A boa notícia é que praticamente todos os textos científicos relevantes já foram traduzidos para o inglês em algum ponto. A má notícia é que muitas vezes esses textos estão fora de catálogo e nunca foram digitalizados.

Mas como tipicamente acontece comigo, curiosidade se mistura com obsessão, e lá fui eu fui fazer arqueologia nos sebos da Califórnia, conseguindo no final uma lista bem considerável de textos originais! (Pra quem estiver curioso, eu coloquei online a lista com os textos que já consegui.)


Para dar um gostinho de como é divertido ler os originais, eu resolvi traduzir um trecho sobre um tema que todo mundo já viu no colégio: as Três Leis de Newton, tais como enunciadas em 1687. Mas antes de falar das leis em si, vale a pena falar um pouco sobre o livro em que elas aparecem, o Principia.

Ler o Principia é bastante diferente de ler um livro atual sobre Física. Em especial, tem duas diferenças que são bem gritantes. A primeira é que o Principia não tem equações. Nenhuma, nenhuma. Todos os cálculos são feitos com desenho geométrico.

Se você parar pra pensar, nem é tão diferente do que a gente faz normalmente. Antes de montar as equações em um problema de Física, você usualmente vai desenhar os vetores com as forças; o Newton simplesmente desenhava os vetores em escala e usava geometria para resolver o problema.

O motivo para usar desenho geométrico ao invés de equações só ele sabia; mas dado que ele era fã dos Elementos de Euclides, pode-se supor que ele queria deixar o Principia mais parecido com os tratados gregos.

A segunda diferença do Principia para os livros atuais é que ele não tem citações. Nas raras vezes que o Newton cita algum outro cientista, é sempre um cientista que já tinha morrido, como o Aristóteles ou o Galileu; e nunca um contemporâneo como o Leibniz ou o Hooke.

O motivo, claro, era para não dividir os créditos das descobertas com ninguém. Na esfera pessoal, não há dúvidas de que o Newton era um pilantra (mas, por outro lado, ele era o pilantra mais inteligente de sua época, o que faz dele uma espécie de Lex Luthor do século XVII).

Isaac Newton sonegou mais de 40 citações. E isso é terrível.

O Principia originalmente era dividido em três livros, mas para fins de exposição eu acho mais fácil dividi-lo nas seções abaixo. Preste atenção na ordem delas, porque tem uma pegadinha mais tarde:

1. Préfacios e Introduções
2. Definições
3. Axiomas, ou as Leis do Movimento
4. Introdução ao Cálculo
5. O Movimento dos Corpos
6. O Sistema do Mundo
7. Conclusão

A minha cópia do Principia é uma tradução recente, feita em 1999. Mas se você quiser acompanhar sem gastar dinheiro, no Google Books tem o scan da primeira edição em latim, e da primeira tradução para o inglês, feita em 1729 (se a fonte parecer esquisita demais, note que aquele símbolo que parece uma integral na verdade é um S longo).

Eu poderia falar horas sobre o Principia, mas já que o objetivo é mostrar as três leis eu vou cobrir só duas seções: as definições e os axiomas.

Definições

Um problema que o Newton tinha era evitar a ambigüidade que alguns termos de uso comum possuem. Espaço, tempo, reta, plano, essas coisas são todas bem definidas. Mas não havia um conceito correto para medir matéria e movimento.

Informalmente, media-se matéria através do peso, e movimento a partir da velocidade, mas o Newton sacou que essas medidas não são as melhores possíveis. Por isso, ele apresenta dois conceitos novos que serão mais úteis. (Tudo que estiver na caixa azul é texto traduzido do Principia):

Quantidade de matéria é uma medida da matéria que surge conjuntamente da sua densidade e de seu volume.

Se a densidade do ar dobrar em um espaço que também dobra, então existe quatro vezes mais ar, e seis vezes mais ar se o espaço triplicar. O caso é o mesmo tanto para neve e pó condensado por compressão ou liquefação, como para quaisquer outros corpos condensados de qualquer maneira por qualquer método. Por enquanto, eu não estou levando em conta o meio, se existir algum, que penetra pelos interstícios entre as partes do corpo. Além disso, eu sempre tenho essa quantidade em mente quando usar o termo "corpo" ou "massa" daqui em diante. Ela sempre pode ser deduzida a partir do peso de um corpo (por exemplo, fazendo experimentos precisos com pêndulos), e eu descobri que ela é sempre proporcional ao peso.

Nessa definição, o Newton apresenta pela primeira vez o conceito de massa. Ele já sabia que peso não é uma boa medida da matéria, afinal, ele muda de acordo com o ponto em que você está (Newton sabia que um corpo em Paris tinha um peso levemente diferente do que teria se estivesse em Londres). Além disso, faz sentido falar do peso da Lua ou de Júpiter?

Note também como o Newton apresenta suas equações de modo implícito. Primeiro ele fala que a massa é uma função da densidade e do volume, mas não fala que função é essa. Depois, pelos exemplos, fica claro que a função é uma multiplicação, então massa é igual a densidade vezes volume.

Quantidade de movimento é uma medida do movimento que surge conjuntamente da velocidade e da quantidade de matéria.

O movimento como um todo é a soma dos movimentos das partes individuais, e, portanto, se um corpo é duas vezes maior que outro, mas tem a mesma velocidade, então há duas vezes mais movimento; e se há o dobro da velocidade, então há quatro vezes o movimento.

É mais difícil empurrar um caminhão a 10km/h que uma bicicleta, por isso o Newton sacou que o movimento não é simplesmente a velocidade, mas sim a velocidade vezes a massa. Em terminologia moderna, o Newton nesse trecho está definindo o momento.

Axiomas, ou as Leis do Movimento

Esse é o trecho que interessa, onde ele define as três leis. Novamente, o formato é baseado nos Elementos de Euclides, você começa o livro com os axiomas e depois deriva todo o resto a partir deles.

Primeira lei: Todo corpo persevera em seu estado de repouso, ou de movimento uniforme e retilíneo, exceto se for compelido a mudar de estado por forças aplicadas.

Projéteis perseveram em seus movimentos, exceto se forem retardados pela resistência do ar, e impelidos para baixo pela força da gravidade. Um pião, que possui partes que impedem umas às outras de realizar movimento retilíneos por razão de sua coesão, não deixa de rodar, exceto se for retardado pelo ar. E corpos maiores, como planetas e cometas, preservam por mais tempo seus movimentos progressivos e circulares, pois eles acontecem em espaços com menor resistência.

A parte curiosa dessa explicação é que ele cria uma lei sobre movimento retílineo, mas todos os exemplos envolvem movimentos curvos :) Não sei se a parte do pião que gira é clara, o que ele quis dizer é que cada partícula individual do pião tem a tendência de sair pela tangente, e só não faz isso porque as partículas estão grudadas entre si.

Mas eu acho que a primeira lei, enquanto axioma, não é das melhores. O que essa lei diz é que, na ausência de forças, a velocidade não muda, ou seja, na ausência de forças, a aceleração é zero. Mas você não precisava de uma lei só pra isso, porque a segunda lei já vai dizer que F=ma. Se a massa é positiva e a força é zero, então a aceleração vai ser zero. A primeira lei é redundante.

Segunda lei: Uma mudança no movimento é proporcional à força motriz aplicada, e tem lugar na mesma linha reta em que a força é aplicada.

Se alguma força gera qualquer movimento, então o dobro da força gera o dobro do movimento, e o triplo da força gera o triplo do movimento, não importando se a força é aplicada de uma só vez ou sucessivamente em passos. E se o corpo já estava se movendo previamente, o novo movimento (já que o movimento é sempre na mesma direção da força motriz) é adicionado ao movimento original se ele está mesma direção, ou subtraído do movimento original se estiver na direção oposta, ou, se for numa direção oblíqua, é combinado obliquamente e composto de acordo com as direções de ambos os movimentos.

Na época de faculdade a gente sempre pregava peça nos calouros. A conversa começava com "Ei bixo, qual é a segunda lei de Newton?". O calouro respondia que era F=ma e aí você sacaneava ele: "Claro que não, F=ma só vale se a massa for constante! O que o Newton disse é que a força é a derivada do momento, e como momento é massa vezes velocidade, então a equação correta é assim:


Se você projetar um avião usando só F=ma, ele vai cair, porque você não levou em conta que ele perde massa enquanto queima o combustível!"

Esse argumento é bom pra sacanear calouro, mas é incorreto. O Newton nunca falou que a força é a derivada do momento. Lembra que eu disse que tinha uma pegadinha na ordem dos capítulos? Nesse capítulo o Newton não poderia ter falado isso, porque ele mostra o Cálculo pela primeira vez só no capítulo seguinte, e portanto não tem como definir força como sendo uma "derivada".

Na verdade, a segunda lei de Newton tal como está no Principia se aplica ao caso discreto, para diferenças de movimentos e não para as suas derivadas. O que engana aqui é o jargão: aquilo que o Newton chama de força não é o mesmo que nós chamamos de força. Ao invés disso, esse conceito apresentado na segunda lei é o que modernamente conhecemos como Impulso. Ou seja, a verdadeira segunda lei é:


Isso fica claro no trecho "não importando se a força é aplicada de uma só vez ou sucessivamente em passos", claramente ele está falando de uma aplicação impulsiva ao invés de uma aplicação contínua. Naturalmente, se você dividir dos dois lados por Δt e tomar o limite quando Δt tende a zero, você chega na definição que todo mundo conhece.

Mas eu acho que a segunda lei, enquanto axioma, não é das melhores. Imagine que eu quero medir o quão cansado eu fico ao empurrar um objeto. Eu fico mais cansado quando o objeto é pesado, e mesmo se o objeto for leve, eu fico cansado se o empurrar por muito tempo. Por isso, eu decido definir Cansaço como sendo o produto da massa pelo tempo: C=mΔt.

Mas tipo, e daí? Só porque eu dei um nome para um produto qualquer não quer dizer que isso seja uma lei da natureza. Da mesma maneira, só porque o Newton multiplicou aceleração por massa e chamou isso de força, não quer dizer que esse produto seja importante. A segunda lei não é uma lei propriamente dita: é só uma definição, e uma definição só faz sentido se tiver alguma propriedade que a acompanhe.

Terceira lei: Para cada ação, existe reação igual e oposta; em outras palavras, as ações de dois corpos um sobre o outro são sempre iguais e sempre opostas em direção.

Qualquer coisa que puxe ou empurre outra coisa, é igualmente puxado ou empurrado por ela. Se alguém aperta uma pedra com o dedo, o dedo também é apertado pela pedra. Se um cavalo puxa uma pedra amarrada por uma corda, o cavalo também será (por assim dizer) puxado igualmente na direção da pedra, já que a corda, esticada nas duas pontas, puxará o cavalo para a pedra e a pedra para o cavalo, no mesmo processo promovendo o movimento de um e impedindo o movimento do outro. Se um corpo, colidindo com outro corpo, muda a direção do segundo em razão de sua própria força, então, pela força do outro corpo (já que a pressão mútua é igual), também mudará seu movimento na direção oposta. Por meio dessas ações, mudanças iguais ocorrem no movimento, mas não na velocidade, exceto, é claro, se os corpos não forem impedidos por nada além. Por isso, mudanças na velocidade que ocorrem em direções opostas são inversamente proporcionais aos corpos, já que os movimentos são trocados igualmente. Essa lei também é válida para atrações.

Ahá, agora sim! A definição de força faz sentido porque força tem uma propriedade importante: ela é uma interação entre dois corpos que aparece em pares de igual intensidade e direções opostas. Nesse trecho eu também acho bacana os exemplos com pedras, cordas e cavalos: esse é um livro escrito em 1687, então cavalos eram muito mais comuns no dia-a-dia das pessoas que hoje em dia.

Mas eu acho que a terceira lei, enquanto axioma, não é das melhores. Imagine que você está em um carro que dá uma freada brusca. Você é acelerado para a frente, e se você multiplicar essa aceleração pela sua massa, descobre a força que te empurrou. Agora, onde está a reação dessa força, hein? hein?

A resposta é que essa força não tem reação. A terceira lei de Newton não funciona sempre, ela só funciona no caso especial em que o referencial não é acelerado. Essa força que você mediu é chamada de força inercial, ela não tem reação e só aparece em referenciais acelerados. Tem quem chame a força inercial de força fictícia, mas eu não gosto dessa nomenclatura: tente andar num carro sem cinto de segurança para ver o estrago fictício que o vidro vai fazer na sua cabeça!



"Mas peraí Ricbit, você não gosta de nenhuma das três leis!". Verdade, mas é porque dá pra fazer coisa melhor. Apresento a vocês a Lei Unificada de Newton:

Em um sistema isolado, o momento se conserva.

Pronto, uma única lei, bem mais simples que as anteriores, e você pode deduzir as três leis a partir dela. Na verdade, com esse enunciado você ainda ganha um monte de vantagens: ao contrário das três leis originais, essa versão funciona em referenciais não-inerciais, na relatividade especial e geral, e até na mecânica quântica!

Ler textos originais é um exercício muito divertido, mas para apreciar completamente você precisa se despojar do que aprendeu na escola. E a minha conclusão após ler bastante é que aprender é fácil, mas desaprender é muito mais difícil!