Existe uma metodologia de engenharia de software para programação funcional? [fechadas]

203

A Engenharia de Software, como é ensinada hoje, é totalmente focada na programação orientada a objetos e na visão orientada a objetos 'natural' do mundo. Existe uma metodologia detalhada que descreve como transformar um modelo de domínio em um modelo de classe com várias etapas e muitos artefatos (UML), como diagramas de casos de uso ou diagramas de classes. Muitos programadores internalizaram essa abordagem e têm uma boa idéia sobre como projetar um aplicativo orientado a objetos do zero.

O novo hype é a programação funcional, ensinada em muitos livros e tutoriais. Mas e a engenharia de software funcional? Ao ler sobre Lisp e Clojure, deparei-me com duas declarações interessantes:

  1. Os programas funcionais geralmente são desenvolvidos de baixo para cima em vez de de cima para baixo ('On Lisp', Paul Graham)

  2. Os programadores funcionais usam mapas nos quais os programadores OO usam objetos / classes ('Clojure for Java Programmers', palestra de Rich Hickley).

Então, qual é a metodologia para um design sistemático (baseado em modelo?) De um aplicativo funcional, ou seja, no Lisp ou no Clojure? Quais são as etapas comuns, quais artefatos eu uso, como os mapeio do espaço do problema para o espaço da solução?

Thorsten
fonte
3
Eu tenho um comentário aqui: muitos programas são escritos de cima para baixo, uma exposição prática ao processo de desenvolvimento de software em uma linguagem funcional é apresentada no livro "Programação Funcional em Limpeza Simultânea" (a linguagem em si é muito acadêmica, Apesar).
Artyom Shalkhakov
4
1. Parnas argumenta que a maioria dos programas deve ser de baixo para cima e depois fingida de cima para baixo; portanto, essas abordagens devem ser misturadas, não há resposta certa.
Gabriel Ščerbák
2
2. Os objetos fornecem comportamento dependendo do estado estruturado encapsulado; no FP, todos os estados e estruturas são explícitos e o comportamento (funções) é separado da estrutura. Portanto, para modelagem de dados, você usa mapas para objetos, mas ao projetar aplicativos, os objetos não podem ser substituídos por funções - FP é uma expressão grande gerada e avaliada por meio de pipelines, OOP é sobre a criação do modelo e o envio de mensagens entre objetos.
Gabriel Ščerbák
1
Eu fiz uma pergunta relacionada em algum momento: "como modelar dados de bancos de dados relacionais no clojure?" stackoverflow.com/questions/3067261/…
Sandeep
4
Hehe, em uma das palestras do SICP, Hal Abelson diz, meio de brincadeira, algo como "Existe uma metodologia famosa, ou devo dizer mitologia, chamada engenharia de software, [...] que cria diagramas e requisitos complicados e depois constrói sistemas com eles; essas pessoas não programaram muito ". Eu venho de uma "Java School", onde por anos ensinamos UML, artefatos e outras coisas, e embora um pouco disso seja bom, muito planejamento e esquemas (trocadilhos) são mais prejudiciais do que úteis: você nunca sabe como o software será até você realmente codificar.
Lfborjas

Respostas:

165

Graças a Deus que o pessoal da engenharia de software ainda não descobriu a programação funcional. Aqui estão alguns paralelos:

  • Muitos "padrões de design" OO são capturados como funções de ordem superior. Por exemplo, o padrão Visitor é conhecido no mundo funcional como uma "dobra" (ou se você é um teórico de cabeça pontuda, um "catamorfismo"). Nas linguagens funcionais, os tipos de dados são principalmente árvores ou tuplas, e todo tipo de árvore tem um catamorfismo natural associado a ele.

    Essas funções de ordem superior geralmente vêm com certas leis da programação, também conhecidas como "teoremas livres".

  • Programadores funcionais usam diagramas com muito menos força do que programadores OO. Muito do que é expresso nos diagramas OO é expresso em tipos ou em "assinaturas", que você deve considerar como "tipos de módulos". Haskell também tem "classes de tipo", que é um pouco como um tipo de interface.

    Os programadores funcionais que usam tipos geralmente pensam que "uma vez que os tipos são acertados; o código praticamente se escreve".

    Nem todas as linguagens funcionais usam tipos explícitos, mas o How To Design Programs livro , um excelente livro para aprender Scheme / Lisp / Clojure, depende muito de "descrições de dados", que estão intimamente relacionadas aos tipos.

Então, qual é a metodologia para um design sistemático (baseado em modelo?) De um aplicativo funcional, ou seja, no Lisp ou no Clojure?

Qualquer método de design baseado na abstração de dados funciona bem. Por acaso, acho que isso é mais fácil quando a linguagem tem tipos explícitos, mas funciona mesmo sem ela. Um bom livro sobre métodos de design para tipos de dados abstratos, facilmente adaptável à programação funcional, é Abstração e especificação no desenvolvimento de programas de Barbara Liskov e John Guttag, o primeiro edição. Liskov ganhou o prêmio Turing em parte por esse trabalho.

Outra metodologia de design exclusiva do Lisp é decidir quais extensões de idioma seriam úteis no domínio do problema em que você está trabalhando e, em seguida, usar macros higiênicas para adicionar essas construções ao seu idioma. Um bom lugar para ler sobre esse tipo de design é o artigo de Matthew Flatt, Criando idiomas na raquete . O artigo pode estar atrás de um paywall. Você também pode encontrar material mais geral sobre esse tipo de design pesquisando o termo "linguagem incorporada específica do domínio"; para conselhos e exemplos específicos além do que Matthew Flatt cobre, eu provavelmente começaria com On Lisp de Graham ou talvez ANSI Common Lisp .

Quais são as etapas comuns, quais artefatos eu uso?

Etapas comuns:

  1. Identifique os dados em seu programa e as operações nele e defina um tipo de dados abstrato que representa esses dados.

  2. Identifique ações ou padrões comuns de computação e expresse-os como funções ou macros de ordem superior. Espere executar esta etapa como parte da refatoração.

  3. Se você estiver usando uma linguagem funcional digitada, use o verificador de tipos cedo e frequentemente. Se você estiver usando Lisp ou Clojure, a melhor prática é escrever contratos de funções primeiro, incluindo testes de unidade - é o desenvolvimento orientado a testes ao máximo. E você desejará usar qualquer versão do QuickCheck que tenha sido portada para a sua plataforma, que no seu caso se parece com ClojureCheck . É uma biblioteca extremamente poderosa para a construção de testes aleatórios de código que usam funções de ordem superior.

Norman Ramsey
fonte
2
O visitante da IMO não é dobrável é um subconjunto de visitantes. O envio múltiplo não é (diretamente) capturado por dobra.
Michael Ekstrand
6
@ Michael - na verdade, você pode capturar vários despachos com vários tipos de catamorfismos de ordem superior com muito cuidado. O trabalho de Jeremy Gibbons é um local para procurar isso, mas eu recomendaria o trabalho em programação genérica de tipos de dados em geral - gosto particularmente do trabalho de composição.
Sclv
6
Concordo que vejo diagramas usados ​​com muito menos frequência para descrever projetos funcionais e acho uma pena. É reconhecidamente difícil representar o equivalente a um diagrama de seqüência ao usar muito HOF. Mas eu gostaria que o espaço de como descrever projetos funcionais com fotos estivesse sendo melhor explorado. Por mais que eu odeie UML (como especificação), acho que a UML (como esboço) é bastante útil em Java e gostaria que houvesse práticas recomendadas sobre como fazer o equivalente. Tenho experimentado fazer isso com protocolos e registros Clojure, mas não tenho nada que eu realmente goste.
7117 Alex Miller
22
+1 por "Graças a Deus que o pessoal da engenharia de software ainda não descobriu a programação funcional". ;)
Aky
1
OO é em si uma maneira de tentar programar com tipos, portanto, as abordagens não são tão diferentes. O problema com os projetos de OO geralmente parece derivar de pessoas que não sabem o que estão fazendo.
Marcin
46

Para Clojure, recomendo voltar à boa e velha modelagem relacional. Out of the Tarpit é uma leitura inspiradora.

cgrand
fonte
É um ótimo artigo, os bons velhos tempos em Ciência da Computação devem ter sido realmente impressionantemente bons, quando todos esses conceitos sobreviveram até o renascimento de hoje. Provavelmente é devido aos fortes fundamentos em matemática.
Thorsten
1
Este. ISTO. ISTO! Estou lendo este artigo, e é realmente interessante como parece cobrir todas as bases do que é necessário para construir sistemas reais, mantendo um estado mutável mínimo de uma maneira altamente controlada. Estou brincando de construir Pong e Tetris no estilo FRelP (desculpe o estranho inicialismo, mas já existe outro FRP mais popular: a Programação Reativa Funcional).
John Cromartie
Depois de ler o artigo, acho que o clojure seria a linguagem perfeita para FR (el) P, pelo menos para a lógica essencial , o estado e controle acidentais e os outros componentes. Gostaria de saber como fazer uma definição relacional do estado essencial no clojure sem reinventar o sql (sem suas falhas)? Ou a ideia é simplesmente usar um bom banco de dados relacional (sql) e criar um programa funcional sobre ele sem a incompatibilidade conceitual introduzida pelo OOP?
22411 Thorsten
1
@ Torque a idéia básica é set = table, map = index. A parte difícil é manter índices e tabelas sincronizados, mas esse problema pode ser resolvido com melhores tipos de conjuntos. Um tipo de conjunto simples que eu implementei é o conjunto com chave, que é um conjunto que usa uma função de chave para testar a unicidade. Isso significa que, ao conjurar uma inserção ou atualização de valor, a chamada get com os campos da chave primária retorna a linha inteira.
cgrand
38

Pessoalmente, acho que todas as boas práticas usuais do desenvolvimento de OO também se aplicam à programação funcional - apenas com alguns pequenos ajustes para levar em conta a visão de mundo funcional. Do ponto de vista da metodologia, você realmente não precisa fazer nada fundamentalmente diferente.

Minha experiência vem de ter me mudado do Java para o Clojure nos últimos anos.

Alguns exemplos:

  • Entenda o seu domínio de negócios / modelo de dados - igualmente importante se você deseja projetar um modelo de objeto ou criar uma estrutura de dados funcional com mapas aninhados. De certa forma, o FP pode ser mais fácil porque incentiva você a pensar no modelo de dados separadamente das funções / processos, mas ainda precisa fazer as duas coisas.

  • Orientação de serviço no design - na verdade, funciona muito bem da perspectiva do FP, pois um serviço típico é realmente apenas uma função com alguns efeitos colaterais. Penso que a visão "de baixo para cima" do desenvolvimento de software às vezes adotada no mundo Lisp é na verdade apenas bons princípios de design de API orientada a serviços de outra forma.

  • Desenvolvimento Orientado a Testes - funciona bem em linguagens FP, de fato às vezes até melhor porque as funções puras se prestam extremamente bem a escrever testes claros e repetíveis, sem a necessidade de configurar um ambiente estável. Você também pode criar testes separados para verificar a integridade dos dados (por exemplo, este mapa possui todas as chaves esperadas, para equilibrar o fato de que, em uma linguagem OO, a definição de classe o aplicaria em tempo de compilação).

  • Prototipagem / iteração - funciona tão bem com o FP. Você pode até criar protótipo ao vivo com os usuários se for extremamente bom em criar ferramentas / DSL e usá-los no REPL.

Mikera
fonte
3
Essas práticas me parecem bastante familiares. Ainda acho que alguém deveria escrever o equivalente funcional a "Engenharia de Software Orientada a Objetos usando UML, Patterns e Java", de Bruegge / Dutoit, em vez do sexto livro "Programação em Clojure". Pode ser chamado de "Engenharia de Software Funcional usando Clojure e ?? o que ??". Eles usam UML e padrões no FP? Lembro-me de que Paul Graham escreveu que os padrões são um sinal de falta de abstração no Lisp, que devem ser remediados pela introdução de novas macros.
Thorsten
3
Mas se você traduzir padrões como práticas recomendadas, também poderá haver padrões no mundo do FP, que valem a pena ser compartilhados com os não inicializados.
Thorsten
2
Existem alguns princípios interessantes no livro do PAIP. norvig.com/paip.html
mathk
1
também existem padrões de programação funcional (sistemas de recursão etc.)
Gabriel Ščerbák
13

A programação OO une fortemente os dados ao comportamento. A programação funcional separa os dois. Portanto, você não possui diagramas de classes, mas estruturas de dados e tipos de dados algébricos. Esses tipos podem ser escritos para corresponder muito bem ao seu domínio, incluindo a eliminação de valores impossíveis por construção.

Portanto, não existem livros e livros, mas existe uma abordagem bem estabelecida para, como diz o ditado, tornar valores impossíveis irrepresentáveis.

Ao fazer isso, você pode fazer uma variedade de escolhas sobre a representação de certos tipos de dados como funções e, inversamente, representar certas funções como uma união de tipos de dados, para obter, por exemplo, serialização, especificação mais rígida, otimização etc. .

Então, como você escreve funções sobre seus anúncios, estabelece um tipo de álgebra - ou seja, existem leis fixas que se essas funções. Alguns talvez sejam idempotentes - o mesmo após vários aplicativos. Alguns são associativos. Alguns são transitivos, etc.

Agora você tem um domínio sobre o qual possui funções que compõem de acordo com leis bem comportadas. Um simples DSL incorporado!

Ah, e dadas propriedades, é claro que você pode escrever testes aleatórios automatizados deles (ala QuickCheck) .. e isso é apenas o começo.

sclv
fonte
1
A abordagem de tornar valores impossíveis irrepresentáveis ​​é menos aplicável a idiomas com tipagem dinâmica como Clojure e Scheme do que a idiomas com tipagem estática como Haskell e ML.
Zak
@ Zak - bem, você não pode verificar estaticamente se eles não são representáveis, mas você pode construir suas estruturas de dados da mesma maneira.
Sclv
7

Design orientado a objetos não é a mesma coisa que engenharia de software. A engenharia de software tem a ver com todo o processo de como passamos dos requisitos para o sistema em funcionamento, dentro do prazo e com uma baixa taxa de defeitos. A programação funcional pode ser diferente da OO, mas não elimina requisitos, projetos detalhados e de alto nível, verificação e teste, métricas de software, estimativas e todas essas outras "coisas de engenharia de software".

Além disso, os programas funcionais exibem modularidade e outra estrutura. Seus projetos detalhados devem ser expressos em termos dos conceitos nessa estrutura.

Kaz
fonte
5

Uma abordagem é criar uma DSL interna na linguagem de programação funcional de sua escolha. O "modelo" é um conjunto de regras de negócios expressas na DSL.

James Kingsbery
fonte
1
Entendo a abordagem para primeiro construir a linguagem para o domínio do problema até que seja alcançado um nível de abstração que não exista mais padrões repetitivos no código, do que resolver o problema com essas abstrações.
Thorsten
1
Mas como fica quando "o modelo é um conjunto de regras de negócios expressas na DSL"? Em um aplicativo Java EE, o modelo é escrito como POJO-Entities, que são chamados de controller-EJBs que, por sua vez, atualizam o view-JSPs - por exemplo. Existem padrões arquiteturais semelhantes (como o padrão MVC) no FP? Como é isso?
Thorsten
2
Não há razão para que você não possa ter um padrão MVC no FP, exatamente assim. O FP ainda permite criar estruturas de dados ricas e, sem dúvida, com ADTs e correspondência de padrões, permite criar estruturas muito mais ricas . De qualquer forma, como o FP separa dados e comportamento, os sistemas do tipo MVC surgem muito mais naturalmente.
Sclv
5

Veja minha resposta para outro post:

Como Clojure aborda a Separação de Preocupações?

Concordo que mais precisa ser escrito sobre o assunto sobre como estruturar aplicativos grandes que usam uma abordagem FP (além disso, é necessário fazer mais para documentar as UIs orientadas por FP)

drcode
fonte
3
Eu gosto do pipeline de 90% e da abordagem macro de 10%. Parece bastante natural pensar em um programa funcional como um canal de transformações em dados imutáveis. Não sei se entendi o que você quer dizer com "colocar toda a inteligência nos dados, não no código", pois a abordagem de ter 100 funções trabalhando em 1 estrutura de dados (em vez de 10 funções em 10 estruturas de dados) parece implicar o oposto. As estruturas de dados no OOP não são mais inteligentes do que no FP, pois elas têm seu próprio comportamento incorporado?
Thorsten
3

Embora isso possa ser considerado ingênuo e simplista, acho que as "receitas de design" (uma abordagem sistemática à solução de problemas aplicada à programação, conforme defendida por Felleisen et al. Em seu livro HtDP ) estariam próximas do que você parece estar procurando.

Aqui, alguns links:

http://www.northeastern.edu/magazine/0301/programming.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371

Artyom Shalkhakov
fonte
O link para a página Nordeste parece estar morto.
James Kingsbery
1
James, você está certo, e eu não me lembro do que estava lá para consertar isso, infelizmente. Eu só sei que os autores do HtDP criaram a linguagem Pyret (e provavelmente estão revisando a 2ª edição do HtDP para usá-lo em vez do Racket, anteriormente PLT Scheme).
Artyom Shalkhakov
3

Encontrei recentemente este livro: Modelagem de Domínio Funcional e Reativo

Eu acho que está perfeitamente de acordo com sua pergunta.

Na descrição do livro:

A Modelagem de Domínio Funcional e Reativa ensina como pensar no modelo de domínio em termos de funções puras e como compô-las para criar abstrações maiores. Você começará com o básico da programação funcional e progredirá gradualmente para os conceitos e padrões avançados que você precisa conhecer para implementar modelos de domínio complexos. O livro demonstra como padrões avançados de FP, como tipos de dados algébricos, design baseado em classe e isolamento de efeitos colaterais, podem fazer seu modelo compor para facilitar a leitura e a verificabilidade.

elviejo79
fonte
2

Existe o estilo "cálculo de programa" / "projeto por cálculo" associado ao professor Richard Bird e ao grupo de álgebra de programação da Universidade de Oxford (Reino Unido). Não acho que seja exagero considerar essa metodologia.

Pessoalmente, embora goste do trabalho produzido pelo grupo AoP, não tenho disciplina para praticar design dessa maneira. No entanto, essa é a minha falha, e não o cálculo do programa.

Stephen Tetley
fonte
2

Eu descobri que o Behavior Driven Development é um ajuste natural para o desenvolvimento rápido de códigos no Clojure e no SBCL. A verdadeira vantagem de alavancar o BDD com uma linguagem funcional é que eu costumo escrever testes de unidade de granulação mais refinada do que costumo usar linguagens procedurais porque faço um trabalho muito melhor de decompor o problema em partes menores de funcionalidade.

Marc
fonte
Quais são as ferramentas que você está usando para executar o BDD no clojure?
murtaza52
Eu gosto de Midje. É atualizado e muito expressivo. Confira: github.com/marick/Midje
Marc
1

Honestamente, se você quiser receitas de design para programas funcionais, dê uma olhada nas bibliotecas de funções padrão, como o Haskell's Prelude. No FP, os padrões geralmente são capturados por procedimentos de ordem superior (funções que operam em funções). Portanto, se um padrão é visto, geralmente uma função de ordem superior é simplesmente criada para capturar esse padrão.

Um bom exemplo é o fmap. Essa função assume uma função como argumento e a aplica a todos os "elementos" do segundo argumento. Como faz parte da classe de tipo Functor, qualquer instância de um Functor (como uma lista, gráfico, etc ...) pode ser passada como um segundo argumento para essa função. Ele captura o comportamento geral de aplicar uma função a todos os elementos de seu segundo argumento.

nightski
fonte
-7

Bem,

Geralmente, muitas linguagens de programação funcional são usadas nas universidades há muito tempo para "pequenos problemas com brinquedos".

Eles estão ficando mais populares agora, já que o OOP tem dificuldades com a "programação paralela" por causa do "estado". E, em algum momento, o estilo funcional é melhor para problemas em mãos, como o Google MapReduce.

Estou certo de que, quando os funcionários funcionarem, [tentar implementar sistemas maiores que 1.000.000 de linhas de código], alguns deles virão com novas metodologias de engenharia de software com palavras de efeito :-). Eles devem responder à antiga pergunta: como dividir o sistema em pedaços, para que possamos "morder" cada um, um de cada vez? [trabalhe iterativo, de maneira evolutiva e evolutiva] usando o Estilo Funcional.

É certo que o Estilo Funcional afetará nosso Estilo Orientado a Objetos. "Ainda" muitos conceitos dos Sistemas Funcionais e adaptados às nossas linguagens OOP.

Mas serão utilizados programas funcionais para sistemas tão grandes? Eles se tornarão o fluxo principal? Essa é a questão .

E ninguém pode vir com uma metodologia realista sem implementar sistemas tão grandes, sujando as mãos. Primeiro você deve sujar as mãos e sugerir uma solução. Soluções-Sugestões sem "dores reais e sujeira" serão "fantasia".

Hippias Minor
fonte
Agora, já foram criados sistemas de larga escala suficientes com linguagens funcionais. Mesmo se não houvesse, isso não é um argumento.
Svante
Bem, cite alguns deles? Eu apenas conheço muito poucos sistemas "Erlang". [tamanho médio] Mas Haskel? Clojure? Lisp?
Hippias Minor
E esse [escrever grandes sistemas] é o argumento real. Porque esse é o caso de teste. Este caso de teste mostra que, se esse estilo funcional é útil e podemos fazer coisas práticas com ele no mundo real.
Hippias Minor
2
O engraçado das linguagens que não são analógicas "OOP" é que elas geralmente oferecem liberdade para "metodologias de design", para pensar por si mesmo e para cortar o programa da maneira mais apropriada, em vez de seguir cegamente um padrão definido e viver com o clichê burocrático. Desculpe, não há 10 pontos de 3 semanas de curso aqui.
Svante
1
Eu vi coisas que você não acreditaria.
Svante