Jump to content
angelicous

Como usar várias verificações e fazer execuções diferentes para a mesma variável?

Recommended Posts

angelicous

O título parece complicado de mais para o problema que é :P

É o seguinte, eu estou a trabalhar com um tabuleiro, e as posições são baseadas em coordenadas.

Imaginem a coordenada (0,0). Eu fiz 4 funções para verificar se as posições à sua volta estavam livres, e caso estivessem livres, corriam uma função. No final quero o resultado de todos em uma única lista.

Exemplo: Se correr a função x_mais na coordenada (0,0) ele vai procurar se existe alguma peça na coordenada (0+1,0) na lista de peças do tabuleiro. Caso exista peça ele retorna valor True, caso esteja livre False. Caso o retorno seja False, vai correr a função "jogada xmais" que com base na peça na coordenada (0,0) vai dizer que peças podemos colocar ali e a respectiva coordenada. Imaginem que as peças podem ser A,B,C, o retorno da função "jogada xmais" seria

[("A",(1,0)),
("B",(1,0)),
("C",(1,0)),]

Ora eu quero que a minha função central (todas jogadas possiveis) pegue em uma coordenada e faça isto não só para xmais mas para xmenos, ymais e ymenos e no final concatenar as 4 listas de jogadas possíveis.

Precisava de ajuda em fazer a função que faça estas 4 verificações independentes e retorne as 4 listas de funções diferentes. É sequer possível?

Share this post


Link to post
Share on other sites
angelicous

Desde já obrigado thogas31 pelo tempo dispensado.

Ok vamos considerar um exemplo simples. O meu tipo peca é representado por (String,String,(String,String)) que representam (nome peça, orientação,(coordenada x, coordenaday))

Imaginemos a peça ("B","N",("0","0")) ou seja peça B com orientação Norte na coordenada (0,0).

Tenho 4 funções que verificam se as coordenadas à sua volta estão vazias ou não. Vamos só considerar 2 delas para ser mais simples.

Esta verifica se existe alguma peça do lado esquerdo (menos x)

minusx :: (String,String,(String,String)) -> [(String,String,(String,String))] -> Bool
minusx _ [] = False
minusx (a,b,(x,y)) ((w,v,(x1,y1)):ts) = let xm = show (rInt x-1)
                                in if (xm == x1 && y == y1 ) then True
                                    else minusx (a,b,(x,y)) ts

E esta em baixo (menos y)

minusy :: (String,String,(String,String)) -> [(String,String,(String,String))] -> Bool
minusy _ [] = False
minusy (a,b,(x,y)) ((w,v,(x1,y1)):ts) = let ym = show (rInt y-1)
                                in if (x == x1 && ym == y1 ) then True
                                    else minusx (a,b,(x,y)) ts

Aqueles "show (rInt " servem apenas para subtrair um valor a y, mas como y é uma string tenho que fazer 2 conversões. Ignorem. Estas funções retornam False se não existir nenhuma peça à esquerda ou em baixo respectivamente da peça que estamos a verificar, ou True se existir uma peça no tabuleiro com essas coordenadas. No meu caso tenho mais 2 funções que verificam as coordenadas +x e +y mas para facilitar consideremos só estas duas. Próximas funções.

possibleplayX :: (String,String,(String,String)) -> String -> [(String,String,(String,String))]
possibleplayX ("B","N",(x,y)) "E" = [("E","N",(x-1,y)),("E","S",(x-1,y))]

possibleplayY :: (String,String,(String,String)) -> String -> [(String,String,(String,String))]
possibleplayY ("B","N",(x,y)) "E" = [("E","E",(x,y-1)),("E","W",(x,y-1))]

Estas funções servem para listar todas as jogadas possíveis para as posições pretendidas. Imaginemos que o jogador retira a peça "E". Ora para a nossa peça no tabuleiro, ("B","N",(0,0)), caso a posição (-1,0) esteja livre, corremos possibleplayX e obtemos as jogadas possíveis para a peça "E". ("E","N",(-1,y)) e ("E","S",(-1,y)). Caso a posição menosy (0,-1) também não tenha nenhuma peça, as jogadas possíveis vão ser ("E","E",(0,-1)) e ("E","W",(0,-1)).

Eu quero fazer uma listagem de todos os resultados possíveis para todas as posições. Ou seja correr minusx e minusy e com base nos resultados correr possibleplayX e possibleplayY e fazer concat aos resultados de cada uma para formar uma só lista.

Se fosse só um caso isto era simples. Fazia um if minusx tal tal == False then possibleplayX tal tal. O problema é que ele aqui não me ia testar o próximo caso caso este se verificasse. E eu quero que corra o minusx e minusy independentemente do resultado um do outro.

Edited by thoga31
GeSHi

Share this post


Link to post
Share on other sites
thoga31

Ok, no meio de tentar entender tudo o que estás a fazer, perdi-me um pouco naquilo que pretendes mesmo. Vamos ver se consigo sintetizar em menos palavras.

Tu pretendes correr uma série de funções e, independentemente do resultado delas, queres que sejam todas executadas e os seus resultados concatenados. Dependendo do resultado de cada função, há outras funções a serem executadas. É isto?

Exemplo: quero correr fn1, fn2 e fn3 no matter what. No entanto, se fn1 for False, então executas after_fn1, e assim em diante.

Se é isto, tens, como é óbvio, várias soluções disponíveis em Haskell (muitas, aliás, quanto mais conheceres a linguagem, mais soluções possíveis te surgem :D ).

No exemplo que te vou dar a seguir tenho 3 funções a executar conforme o resultado de outras 3 funções. De qualquer das formas, as funções são todas executadas.

As funções a executar sempre são (>2), (<5) e (<10). No entanto, consoante o resultado de cada uma destas funções, um resultado deve ser retornado: 0 se for True, e a aplicação de uma segunda função se for False. Essas funções são, respectivamente, id, (*2) e (*3).

Crio então uma lista de combinações (tuplos) destas funções, com um nome muito original: combinacoes :P

Depois aplico esta lista de funções a um valor em funcao.

Para testar, criei a funcao_lista que vai aplicar funcao a uma lista de valores.

combinacoes = [((>2), id), ((<5), (*2), ((<10), (*3)]

funcao x = map (\y -> if (fst y) x then 0 else (snd y) x) combinacoes
funcao_lista xs = map funcao xs

Teste:

Prelude > funcao_lista [1,3,5,7,12]
[[1,0,0],[0,0,0],[0,10,0],[0,14,0],[0,24,36]]

Como podes constatar, para os 5 valores eu apliquei todas as funções e, consoante o resultado delas, apliquei outras funções ou nenhumas. O resultado é uma lista com 3 elementos pois testo 3 funções.

Se quiseres que não apareça nada no caso da primeira função ser True (na minha aparece um 0), existe uma opção, entre várias:

funcao_limpa x = concat $ map (\y -> if (fst y) x then [] else [(snd y) x]) combinacoes

O output já será este:

[[1],[],[10],[14],[24,36]]

É uma coisa destas que precisas, ou entendi tudo mal? :D

Não te preocupes, se eu não entendi decerto que não foi por falta de explicação da tua parte, hoje eu não estou nos meus melhores dias (não estou de ressaca, não, que eu não bebo álcool xD).

Cumprimentos.


Knowledge is free!

Share this post


Link to post
Share on other sites
angelicous

É exactamente isso que eu quero, muito obrigado. Mas também encontrei outra forma de fazer o que queria. :P

Mas eu percebi a lógica deste processo. Não era fácil lá chegar sozinho tenho que admitir. :cheesygrin:

Mas como não sou de ficar sentado à espera que as soluções caiam do céu lá cheguei ao resultado. Basicamente o que eu estou a fazer agora é fazer um filter para descobrir todas as peças que tenham a posição do lado que estou a testar livre, e depois aplico-lhe a função possibleplay para decifrar as jogadas possíveis para esse lugar livre.

Por exemplo, faço um filter para a função minusx para todo o tabuleiro, que me retorna todas as peças que tem o lado esquerdo(coordenada -x) livre. A essa lista aplico a função possibleplayX que me dá todas as jogadas possíveis para cada uma dessas peças. Isto tudo dentro de uma outra função "central" que faz um concat de todos os outros resultados aliados a este :)

Mas ao ver o código em cima fiquei com uma dúvida. Para que serve o símbolo do $? Já o vi ser usado em alguns exemplos mas nunca percebi(nem nunca me ensinaram) a sua utilidade.

Share this post


Link to post
Share on other sites
thoga31

Como o @Baderous indicou no seu link, $ é um operador de aplicação. Imagina o seguinte:

funcao x = fn1 (fn2 (fn3 x))

A utilização de parêntesis pode-se tornar confusa e é muito ao estilo de outras linguagens. Em Haskell, nós utilizamos o operador $ para fazer a aplicação de funções. Desta forma, temos que:

funcao x = fn1 $ fn2 $ fn3 x

No caso anterior, podemos ainda simplificar com o operador ., que é um operador de composição.

funcao = fn1 . fn2 . fn3

Vindo da matemática, podes ler "f . g" como "f após g". Significa que, após aplicarmos a função g, devemos pegar nesse resultado e aplicá-lo à função f. Qual a diferença para o operador $? O $ é apenas aplicativo, mas o . é constitutivo. Reparaste que eu ocultei o argumento x?

Isto acontece porque estou a construir uma nova função que é a aplicação consecutiva de outras três. Desta forma, o Haskell vai induzir que, devido ao facto de fn3 ter um argumento, argumento este que eu ocultei, então funcao também terá um argumento. Podemos torná-lo visível usando o $:

funcao x = fn1 . fn2 . fn3 $ x

Outra forma de o fazer é esta:

funcao x = (fn1 . fn2 . fn3) x

Tens de usar o $ pois tens de indicar que vais aplicar a função àquele argumento. Sem o $ vai-te dar erro relacionado com o matching de tipos de dados.

Em suma, eu usei o $ para evitar parêntesis. Torna a leitura mais simples.

-- tudo a seguir é equivalente:
fn x = f (g (h x))
fn x = f $ g $ h x
fn = f . g . h

Espero que tenhas compreendido. É importante perceber a aplicação e composição de funções em Haskell. ;)


Knowledge is free!

Share this post


Link to post
Share on other sites
pwseo

angelicous,

Confesso que não estive a ver em pormenor o teu problema, mas pelo que percebi o thoga31 já te ajudou no que precisavas. Vinha, no entanto, comentar um pouco o teu código.

O teu tipo de dados parece-me um pouco estranho: (String, String, (String, String))

O ideal seria desdobrares isso um pouco e utilizar tipos de dados adequados: parece-me muito esquisito a Orientação e as Coordenadas serem representadas por Strings:

-- Em vez disto:
(String, String, (String, String))

-- Teríamos isto:
type Piece = (String, Direction, Coord)

-- Int ou Float, String é que não!
type Coord = (Int, Int)

-- tipos de dados algébricos são ideais para isto!
data Direction = N | S | E | W
 deriving (Show, Eq)

-- E agora, em vez de termos isto:
minusx :: (String, String, (String, String)) -> [(String, String, (String, String))] -> Bool
minusx = ...

-- podemos ter isto, que é muito mais legível:
minusx :: Piece -> [Piece] -> Bool
minusx = ...

Fica muito mais legível e auto-documentado se utilizares o que foi demonstrado. Por vezes até é conveniente definir novos tipos de dados (via data em vez de type, que apenas cria sinónimos), mas não precisamos de chegar a isso.

Neste caso, e seguindo a minha sugestão, a tua minusx até pode ser simplificada através dos as-patterns, comparação directa de tuplos e utilização do _ para variáveis que não queremos utilizar:

minusx :: Piece -> [Piece] -> Bool
minusx _ [] = False
minusx a@(_,_,(x1,y1)) ((_,_,(x2,y2):ts) = 
 if (x1-1,y1) == (x2,y2)
   then True
   else minusx a ts

-- alternativa:
minusx :: Piece -> [Piece] -> Bool
minusx _ [] = False
minusx a@(_,_,(x1,y1)) ((_,_,(x2,y2):ts) =
 (x1-1, y1) == (x2, y2) || minusx a ts

Edited by pwseo
  • Vote 1

Share this post


Link to post
Share on other sites
angelicous

Perfeitamente entendido thoga31, obrigado pela explicação :)

pwseo, eu percebo que seja estranho, mas prende-se com o facto de ter que ler a informação de uma base de dados e representar tudo no output do ghci :)

posso perfeitamente fazer a conversão para inteiros, mas depois quando for representar tenho que alterar tudo novamente. Mas representei a peça toda, assim como coordenadas por types para se tornar mais fácil a leitura depois. :thumbsup:

Share this post


Link to post
Share on other sites
pwseo

angelicous,

A menos que me esteja a escapar alguma coisa, devo discordar. Se de facto os dados fossem todos texto, não haveria problema. No entanto, tu estás a lidar com dados numéricos também, e pior ainda, a fazer cálculos com eles, pelo que não faz sentido representá-los em memória como String e fazer conversões ad-hoc sempre que queres somar ou subtrair 1.

O correcto nestes casos é criar duas funções: uma que transforme uma String num objecto que represente o teu tipo de dados e outra que faça a operação inversa (ex.: readPiece e showPiece). Uma vez que vocês estão a utilizar uma API para leitura de XML (Text.XML.Light, correcto?) a "leitura" da peça parte de um Element e não de uma String, mas o output seria sempre a conversão de uma Piece para String.

Deste modo escreverias todo o raciocínio do programa de uma forma concisa, coerente e pura, e só no final terias que utilizar showPiece para fazer output do resultado do teu programa.

Share this post


Link to post
Share on other sites
angelicous

Foi o que disse. Ao ler os elementos do xml o output vai ser [Element] mas ao ler esses elementos vão ser um "Maybe String". Ao fazer essa leitura dos dados eu podia fazer essa conversão para Int, e tratar os dados e no final teria que reconverter novamente.

Eu estou de acordo, a organização e eficácia era diferente, mas penso que o uso de memória ainda não é componente de avaliação :P Se tiver tempo converto isto no final, é relativamente rápida. Os testes são feitos automáticamente, e desde que o output seja o esperado, essa cotação será sempre total, visto que a pontuação é automática. Depois na discussão é que pode complicar :cheesygrin:

Share this post


Link to post
Share on other sites
pwseo

Não se trata de saber usar bem a memória mas sim de saber representar o problema correctamente na linguagem em questão. Vocês já deram tipos de dados algébricos, Maps e assim? Eu sei que o Mooshak não distingue a vossa perícia em escrever código, mas se fosse vosso professor atribuiria metade da cotação à escolha dos tipos de dados e algoritmos a utilizar no problema.

Claro que se isso estiver a funcionar não vale a pena começares agora a alterar (os prazos são para ser cumpridos!) -- isto são coisas que ou se começa a fazer desde o início, ou então não vale a pena.

Share this post


Link to post
Share on other sites
angelicous

Existem duas componentes de avaliação, uma que é automática (mooshak) e a outra a leitura de código acompanhada de relatório e defesa do projecto. Claro que a representação vai ser importante, mas o que estou a fazer funciona. No meu caso estou a fazer tudo com Types que são sinónimos para as strings basicamente...

Já vou a mais de 2/3 do projecto, dai dizer que se tiver tempo para alterar faço-o. :)

Desde já obrigado pelas dicas.

Share this post


Link to post
Share on other sites
thoga31

Faço só notar que, segundo tenho vindo a perceber, tu também deves ter algum background em programação como o @joaomfs. Vocês terão aprendido, noutras linguagens, que um bom código implica uma boa gestao de memória, entre muitas outras coisas. Em Haskell isso não se aplica. O motivo é simples, e acho que não vale a pena alongar-me acerca disto para já: o Haskell é bem mais inteligentes do que vocês podem imaginar. Há programas em Haskell que conseguem ser mais céleres que as mesmas versões em C, a tão aclamada linguagem da rapidez. Isto acontece porque o Haskell tem sistemas de optimização simplesmente geniais que mais nenhuma linguagem, que eu conheça, tem.

Portanto, um bom código em Haskell passa pela sua boa organização e pureza e não pela gestão de memória - isso são batatas no mundo Haskelliano.

Como o @pwseo disse, deve-se modular o código de forma a ter um bloco especializado só em output, e as restantes funções especializadas em processar dados sem os mostrar de qualquer forma. ;)

Atenção: isto é só uma dica e um conselho de amigo. É normal que tenham a tendência a misturar um pouco as coisas pelo facto de serem novatos e de terem, suponho, algum background noutras LP's. :)

Cumprimentos.


Knowledge is free!

Share this post


Link to post
Share on other sites
angelicous

No meu caso sim, mas não do mundo académico. Autodidacta, o que ás vezes não é o melhor. Mas tenho essa impressão que a gestão de memória é um factor a ter em conta, pelo menos em C dai ter dito que essa componente secalhar ia ser avaliada.

thoga porque dizes que Haskell é mais inteligente? Eu pelo menos tenho ideia que Haskell é muito pouco utilizado a não ser em meio académico.

Eu já nem digo procurar emprego porque acho que nunca vi ninguém a pedir haskell, mas mesmo trabalhos freelance é muito difícil ver algo em haskell.

Share this post


Link to post
Share on other sites
thoga31

O facto de ser pouco utilizado faz de uma linguagem algo de menor calibre? O Windows também é o OS mais usado e está longe de ser o melhor OS. Marcas como Ford e Fiat são os que vendem mais, e os modelos mais vendidos estão longe de ser os melhores. Não vás pela ideia do "mais usado". O facto de X ser mais usado não significa que X seja o melhor da sua categoria. Pensava que esta era uma lógica trivial.

Haskell é mais usado no mundo académico uma vez que não é fácil criar programas com GUI com ele, que são os mais usados. Diga-se que em C também não é um mar de rosas, mas há imensas ferramentas feitas para isso. No entanto, há um Window Manager para Linux, cujo nome não me lembro, feito com Haskell, entre outros.

Ser-se autodidacta é, a meu ver, o melhor se o aprendiz estiver bem encaminhado e tiver bons pares que o ajudem a ver novos horizontes. Eu sou autodidacta e ajudo imenso aqui colegas teus com mais anos de curso em Informática do que eu (eu tenho zero, leia-se). Isso de ter professores é um mito, vejo aqui imensos alunos que fazem borrada porque os professores os ensinaram assim, e os alunos não têm sentido crítico pois, muitas vezes, nada sabem quando entram num curso e aprendem o que lhes é ensinado as is.

Haskell é superior em imensas coisas, logo pelo facto de ser lazy evaluation, por exemplo. Em C, o programa executa exactamente o que lhe pedes. Em Haskell constróis funções e ele induz o que deve calcular e como o deve calcular. Ele tem sistemas de optimização fenomenais que nem eu te consigo descrever bem.

Anyway, quando se aprende a lógica do Haskell, naturalmente nos tornamos melhores programadores. Eu considero que programo muito melhor em Object Pascal desde que entrei no mundo do Haskell.


Knowledge is free!

Share this post


Link to post
Share on other sites
pwseo

angelicous,

Haskell é uma linguagem do futuro. Se tu reparares bem nas tendências de todas as linguagens imperativas actualmente utilizadas (C, C++ (especialmente C++11), C#, Go, etc) verás que todas estão a tentar melhorar-se no que diz respeito ao sistema de tipos de dados. Nesse aspecto, Haskell deixa todas elas a um canto (um canto muito pequeno, de facto).

Como deves imaginar, optimizar um programa consiste em reconhecer certos padrões que, segundo certos pressupostos, podem ser substituídos por código mais pequeno e mais rápido. Por esse motivo, mais informação sobre os dados do programa = mais optimização possível. O compilador de Haskell tem ao seu dispor imensa informação que é impossível de se obter em C, e é por esse motivo que podemos ver frequentemente código escrito em Haskell com performance bastante semelhante à de C -- o ghc pode eliminar estruturas de dados intermédias, fazer inline de funções e até mesmo eliminar computações inteiras de uma forma muito mais radical que o gcc precisamente porque tem muito mais informação sobre o funcionamento do teu código.

Quanto à gestão de memória, Haskell é bastante diferente do que encontras em C e por isso não podemos pensar da mesma forma nas duas linguagens (ex.: em Haskell tens listas infinitas, o que em C não seria permitido). Mais ainda, para influenciares directamente a gestão de memória em Haskell terias que lidar com coisas como seq, strict patterns e o tema de boxing/unboxing, o que não me parece muito adequado no 1º ano do curso. O que me ocorre de mais importante nesse campo e que esteja dentro do vosso controlo é mesmo o tipo de recursividade: terminal ou não (tail-recursion).

Relativamente ao que referi no teu código: a gestão de memória não está em causa (o compilador lá decidirá o que achar mais conveniente e como te disse, é difícil que o influencies directamente, excepto na questão da recursividade). No entanto, a representação do teu problema em tipos de dados adequados influencia imenso as decisões do compilador, para não falar do próprio aspecto do código que escreves.

Resumindo um pouco, a tradicional abordagem de "vamos lá fazer umas funções e umas structs com ints e ver no que dá" típica de muitos programadores de C (não todos!) não se aplica. Em Haskell devemos começar por representar o nosso problema com os tipos de dados correctos, estabelecer relações entre eles com funções (puras!) e no final ligar o nosso programa ao mundo exterior com IO.

Saber programar em Haskell fará de ti um programador melhor no geral, acredita! (quanto mais não seja por te abrir horizontes)

Relativamente ao mercado de Haskell, é como para qualquer linguagem estritamente funcional: há pouca procura. Ainda assim, isto pode vir a mudar nos próximos tempos, especialmente devido a áreas como a da computação paralela onde a pureza e lazyness de Haskell são muito muito úteis. Só o tempo dirá.

De qualquer modo isso não quer dizer que Haskell seja uma linguagem pior: PHP é usado em todo o lado e não é, de forma alguma, uma linguagem excepcional (e não só, há mais exemplos, como o VB / VB.NET).

O que faz com que Haskell (e linguagens funcionais, no geral) seja pouco utilizado é o seu paradigma; a maior parte dos programadores cresceu no mundo imperativo e custa-lhes um pouco compreender o paradigma funcional. Além disso, a ênfase que Haskell dá à pureza das funções implica uma relação atribulada com tudo o que for IO. O facto de toda a gente dizer que Monads são terríveis (uma mentira!) também não ajuda nada. Mas acima de tudo, o motivo pelo qual <linguagem X> não é tão utilizada como C ou C++ é em grande parte cultural, e muitas vezes não depende da qualidade da linguagem: se de repente surge uma linguagem melhor, os programas antigos e programadores de outrora não vão necessariamente adoptá-la, mantendo assim as linguagens velhas no topo, mesmo que a qualidade não seja o motivo. Isto é ainda pior se a tal linguagem nova não for tão fácil de utilizar como as anteriores.

Mas atenção: Haskell é difícil de compreender inicialmente, mas é uma dificuldade conceptual que eventualmente desaparece à medida que compreendemos a linguagem. No futuro, essa complexidade transforma-se em expressividade: os nossos programas tornam-se mais pequenos, mais correctos, mais rápidos e mais simples de manter.

Em C passa-se o contrário: toda a gente consegue fazer um pequeno programa em C que manipule um ficheiro de texto. A linguagem em C é extremamente compacta, quase completamente desprovida de abstracções. No entanto, depois de compreendermos C na sua totalidade, começam as dores de cabeça com o quão propícios a erros são os apontadores (que em C++11 estão cada vez mais escondidos por isso mesmo), entre outras coisas. Depois de sabermos C, constatamos que a linguagem oferece muito poder à custa de um potencial enorme para fazer asneiras. Haskell tenta oferecer o mesmo poder, mas tenta garantir que as asneiras são menos frequentes, e isso requer uma linguagem conceptualmente mais complexa (como é o caso).

PS.: Peço desculpa pelo testamento :P

  • Vote 1

Share this post


Link to post
Share on other sites
angelicous

Não quis ofender ninguém ao perguntar isto :D

Eu não trabalho na área ainda, mas a impressão que tenho é que Haskell é pouco usado. Mesmo o nosso programa académico começa com programação funcional mas pelo que vi depois desliga-se dela e vai para a programação imperativa. Posso estar enganado, mas ao ver o programa foi o que me pareceu, e o que leva a concluir que funcional dá-se mas não se aprofunda muito.

Não estou a discutir a sua utilidade, pois pelo que entendi pela explicação Haskell é muito mais difícil que falhar(este conceito para mim é estranho, porque para mim o que se escreve é o que acontece sempre) que outras linguagens o que para produtos que tem que ser extremamente eficazes seja a solução. Mas a sua utilização terá que vir por parte dos programadores, e a não ser que exista algo que os "obrigue" a aprender Haskell será sempre uma linguagem secundária correcto?

Em comparação com o programa de outras univs, conseguem dar-nos uma boa razão para Haskell ser a nossa primeira linguagem de programação? A primeira coisa que nos disseram quando entramos para esta cadeira foi que quem vem de C ia ficar baralhado com Haskell, e passar agora de Haskell para C calculo que C também vá baralhar um bocado a coisa. Se haskell ficar por aqui, quais considerariam ser as coisas boas a reter de haskell?

Share this post


Link to post
Share on other sites
thoga31

Não aprendeste bem as asneiras grossas que se podem fazer com ponteiros, pois não? :D

C é todo baseado em processos de baixo nível, e é facílimo fazer asneira. Esquecemo-nos de libertar a memória, o compiladr nada assinala pois não há erro de sintaxe, e em runtime ocorre memory leak. Faço notar que memory leak não faz o programa ir abaixo nem é propriamente detectável. Só um olho muito bem treinado detecta isso ao ver um código-fonte.

E isto é só a ponta do iceberg.

Neste momento podes não compreender bem como é que uma falha e a outra não, mas acredita que é assim mesmo. Em Haskell trabalhas a um nível muito abstracto, e em C trabalhas a um nível baixo, o que requer mais cuidados. Em C podes fazer asneira da grossa com Strings, uma vez que em C não existem strings, são vectores de char, e os vectores são baseadas em apontadores. Em Haskell, uma String não tem esses problemas pois é um conjunto de caracteres (é, aliás, [Char]) sem qualquer envolvimento de apontadores. Não há caracteres de terminação nem mada dessas coisas.

Eu estou a ver o teu colega a tentar imitar o estilo de VB em Haskell, e claro que o resultado não está a ser brilhante. Haskell requer um pensamento muito mais bem estruturado, e naturalmente isso terá efeitos positivos quando se passa a outras linguagens. Eu antes, em Pascal, enchia os meus procedimentos e funções de I/O, e desde que peogramo em Haskell que aprendi as vantagens de não o fazer. Aliás, vejo bem o terror que é minar de I/O as subrotinas.

Eu estou aqui com pouco tempo, e não ando a escrever metade do que queria ou como devia, mas apreende bem o que o @pwseo disse, ele tem imensa razão. E faço notar que ele programa em mais de 10 linguagens, entre elas C e Haskell. Vais te de concordar comigo que ele decerto sabe o que está a dizer e porque o está a dizer ;)

Edited by thoga31

Knowledge is free!

Share this post


Link to post
Share on other sites
pwseo

Ninguém ficou ofendido :) Apenas quis dar-te uma boa explicação.

Relativamente ao que perguntaste, depende sempre do programador e dos seus objectivos. Naturalmente, em ambiente de produção, e especialmente no meio de uma equipa com práticas e toolkit estabelecidos, é difícil um programador novo meter Haskell lá no meio. No entanto, se for para enriquecimento pessoal, pegar em Haskell é muito fácil e a moda está a começar a pegar.

Quanto ao teu programa académico, é como dizes, mas também tens que ter consciência de uma coisa (que se calhar ninguém vos diz): é suposto vocês serem auto-didactas; se aprenderes apenas o que te é dado no curso, não serás um bom programador -- serás apenas mais um programador. O teu curso pode largar a programação funcional rapidamente, mas não que dizer que tu tenhas que a largar também ;)

Relativamente ao que consideraste difícil de compreender (da menor falibilidade do código Haskell), deixa-me elucidar-te com um pequeno exemplo:

void sayhello(void) {
   magic_var = 42;
   puts("Hello!");
}

Considera a função acima mostrada. Qual o seu efeito? Para muitos programadores (que nunca verão o seu código, apenas a invocarão), o efeito será "escrever "Hello!"" no output. No entanto há ali uma variável global a ser alterada. Imagina agora que outras funções dependem dessa variável: se alguém remover uma invocação de sayhelloo(), facilmente teremos um valor diferente para magic_var, influenciando silenciosamente outras funções que dependam do seu valor. Bugs deste tipo são muito difíceis de apanhar.

Em Haskell, por outro lado, não podes alterar variáveis globais (nem locais, já agora), porque todos os objectos são imutáveis (embora haja formas obscuras de contornar isto, claro). Como resultado, embora seja possível uma determinada função depender de uma "variável" que lhe é externa, o valor dessa "variável" será sempre o mesmo, pelo que o bug possível no exemplo anterior de C é impossível em Haskell.

O código que escreves em Haskell é puro, o que significa que as funções podem ser testadas isoladamente e os seus resultados facilmente são provados. Todas as funções que não obedecem a esta regra são claramente marcadas como tal: funções monádicas, nomeadamente as que envolvem IO. Esta distinção permite-nos raciocinar mais facilmente sobre o código que escrevemos, e com mais confiança.

Quanto à transição de C para Haskell, é de facto confusa para muita gente, e isso é perfeitamente normal: passa-se de uma linguagem essencialmente baseada na alteração de valores de uma forma imperativa para uma onde o estilo é mais declarativo, com valores imutáveis. É quase o completo oposto uma da outra. Ainda assim, há conceitos que são transversais a todas as linguagens e que em Haskell são mais proeminentes (muito mais) do que em C:

  • Funções puras -- em C não há forma de encorajar a que se escrevam funções puras, mas em Haskell todas elas são-no por defeito. Estas são funções que funcionam de uma forma independente do resto do sistema, efectivamente significando que podem ser substituidas com segurança por uma função que emita o mesmo resultado para os mesmos argumentos. Em C isto é muito difícil de se assegurar, porque apenas inspeccionando o código conseguiremos garantir que uma determinada função não faz nada mais do que calcular o seu resultado (ex.: a sayhello() mostrada acima).
    Se toda a gente saísse de Haskell com isto na cabeça, todo o seu código futuro seria muito melhor :)
  • Utilizar tipos de dados adequados -- em C isto é difícil dada a pobreza do seu sistema de tipos de dados comparativamente com Haskell. Noutras linguagens, no entanto, esta mentalidade pode ser muito útil, levando-nos a criar abstracções correctas e eficientes, além de compreendermos onde devemos de facto utilizar uma abstracção e não um tipo de dados nativo.
    Quando perceberes mais Haskell vais reparar que muitas vezes o código de uma função parece quase uma consequência directa dos tipos de dados que a mesma recebe e devolve.
  • Reutilização de código -- A maioria do código Haskell é composto de pequenas funções, mas habitualmente em grande número. Estas funções são quase sempre genéricas e descrevem raciocínios que sabem o menos possível sobre os objectos que manipulam. Este comportamento é quase o oposto do que se vê em C, e permite-nos construir complexidade através da composição de blocos simples e de fácil compreensão.

No geral, com Haskell aprendes a pensar melhor nos programas. Esta lista poderia ser maior, mas penso que o mais importante está aí referido. Acrescento que nem tudo o que é bom em Haskell fica bem em C. O estilo recursivo de Haskell nem sempre é transponível para C, essencialmente por questões de performance e memória (o compilador de Haskell faz com que isto funcione, mas a linguagem C funciona de forma fundamentalmente diferente).

  • Vote 2

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.