Retorno considerado prejudicial? O código pode ser funcional sem ele?

45

OK, então o título é um pouco clickbaity, mas sério, eu já falei, não peça chute por um tempo. Gosto de como incentiva os métodos a serem usados ​​como mensagens de maneira realmente orientada a objetos. Mas isso tem um problema incômodo que tem sido perturbador na minha cabeça.

Cheguei a suspeitar que código bem escrito pode seguir os princípios OO e princípios funcionais ao mesmo tempo. Estou tentando conciliar essas idéias e o grande ponto de discórdia em que aterrei é return.

Uma função pura tem duas qualidades:

  1. Chamá-lo repetidamente com as mesmas entradas sempre dá o mesmo resultado. Isso implica que é imutável. Seu estado é definido apenas uma vez.

  2. Não produz efeitos colaterais. A única alteração causada pela chamada está produzindo o resultado.

Então, como alguém pode ser puramente funcional se você jurou usar returncomo forma de comunicar resultados?

O dizer, não pergunte que a ideia funciona usando o que alguns considerariam um efeito colateral. Quando trato de um objeto, não o pergunto sobre seu estado interno. Eu digo o que preciso fazer e ele usa seu estado interno para descobrir o que fazer com o que eu disse para fazer. Depois que digo, não pergunto o que fez. Só espero que tenha feito algo sobre o que foi dito para fazer.

Penso em Tell, Don't Ask como mais do que apenas um nome diferente para encapsulamento. Quando uso return, não faço ideia do que me chamou. Não posso falar do protocolo, tenho que forçá-lo a lidar com o meu protocolo. Que em muitos casos é expresso como o estado interno. Mesmo que o que está exposto não seja exatamente o estado, geralmente são apenas alguns cálculos realizados nos argumentos de estado e entrada. Ter uma interface para responder oferece a chance de massagear os resultados em algo mais significativo que o estado interno ou os cálculos. Isso é passagem de mensagem . Veja este exemplo .

No passado, quando unidades de disco tinham discos e um pen drive era o que você fazia no carro quando o volante estava frio demais para tocar com os dedos, fui ensinado como as pessoas irritantes consideram funções que possuem parâmetros fora dos parâmetros. void swap(int *first, int *second)parecia tão útil, mas fomos encorajados a escrever funções que retornassem os resultados. Então, levei isso a sério pela fé e comecei a segui-la.

Mas agora vejo pessoas construindo arquiteturas onde objetos permitem que como eles foram construídos controlem para onde enviam seus resultados. Aqui está um exemplo de implementação . Injetar o objeto da porta de saída parece um pouco com a idéia do parâmetro out novamente. Mas é assim que os objetos dizer e não perguntar dizem a outros objetos o que fizeram.

Quando soube dos efeitos colaterais, pensei nele como o parâmetro de saída. Estávamos nos dizendo para não surpreender as pessoas, fazendo com que parte do trabalho acontecesse de maneira surpreendente, ou seja, não seguindo a return resultconvenção. Agora, claro, eu sei que há uma pilha de problemas de encadeamento assíncrono paralelo com os quais os efeitos colaterais se complicam, mas o retorno é realmente apenas uma convenção que faz com que você deixe o resultado pressionado na pilha para que, como quer que seja chamado, você poderá soltá-lo mais tarde. Isso é tudo o que realmente é.

O que estou realmente tentando perguntar:

É returna única maneira de evitar todo esse efeito colateral da miséria e obter segurança de linha sem travas, etc. Ou posso seguir dizer, não pergunte de uma maneira puramente funcional?

candied_orange
fonte
3
Se você optar por não ignorar a Separação de Consulta de Comando, consideraria seu problema resolvido?
Rwong
30
Considere que encontrar um pontapé pode ser uma indicação de que você está envolvido em um design orientado por dogmas, em vez de raciocinar sobre os prós e contras de cada situação específica.
Blrfl
3
Em geral, quando as pessoas falam que o retorno é prejudicial, estão dizendo que é contra a programação estruturada, não funcional, e uma única declaração de retorno no final da rotina (e talvez no final dos dois lados de um bloco if / else que é o último elemento) não está incluído nisso.
Random832
16
@jameslarge: Falsa dicotomia. Não se deixar levar pelos projetos pelo pensamento dogmático não é a mesma coisa que o Velho Oeste / tudo se aproxima. A questão é não permitir que o dogma atrapalhe o código bom, simples e óbvio. A lei universal é para carências; contexto é para reis.
Nicol Bolas
4
Há uma coisa para mim que não está clara em sua pergunta: você diz que jurou usar 'return', mas, tanto quanto posso dizer, não relaciona isso explicitamente com seus outros conceitos ou diz por que fez isso. Quando combinado com a definição de uma função pura, incluindo a produção de um resultado, você cria algo que é impossível de resolver.
1111 Danikov

Respostas:

96

Se uma função não tem efeitos colaterais e não retorna nada, a função é inútil. É tão simples quanto isso.

Mas acho que você pode usar alguns truques se quiser seguir a letra das regras e ignorar o raciocínio subjacente. Por exemplo, o uso de um parâmetro out é, estritamente falando, não o retorno. Mas ainda faz exatamente o mesmo que um retorno, apenas de uma maneira mais complicada. Portanto, se você acredita que o retorno é ruim por um motivo , o uso de um parâmetro out é claramente ruim pelos mesmos motivos subjacentes.

Você pode usar truques mais complicados. Por exemplo, Haskell é famoso pelo truque de IO da mônada, onde você pode ter efeitos colaterais na prática, mas ainda não estritamente falando, tem efeitos colaterais do ponto de vista teórico. O estilo de passagem de continuação é outro truque, que permite evitar retornos ao preço de transformar seu código em espaguete.

A conclusão é que, na ausência de truques bobos, os dois princípios das funções livres de efeitos colaterais e "sem retorno" simplesmente não são compatíveis. Além disso, vou apontar que os dois princípios são realmente ruins (dogmas realmente) em primeiro lugar, mas essa é uma discussão diferente.

Regras como "diga, não pergunte" ou "sem efeitos colaterais" não podem ser aplicadas universalmente. Você sempre tem que considerar o contexto. Um programa sem efeitos colaterais é literalmente inútil. Mesmo linguagens funcionais puras reconhecem isso. Em vez disso, eles se esforçam para separar as partes puras do código daquelas com efeitos colaterais. O ponto das mônadas do Estado ou da IO em Haskell não é que você evite efeitos colaterais - porque você não pode -, mas que a presença de efeitos colaterais é explicitamente indicada pela assinatura da função.

A regra de dizer não perguntar se aplica a um tipo diferente de arquitetura - o estilo em que os objetos no programa são "atores" independentes que se comunicam. Cada ator é basicamente autônomo e encapsulado. Você pode enviar uma mensagem e decide como reagir a ela, mas não pode examinar o estado interno do ator de fora. Isso significa que você não pode dizer se uma mensagem altera o estado interno do ator / objeto. Estado e efeitos colaterais são ocultos por design .

JacquesB
fonte
20
@CandiedOrange: Se um método tem efeitos colaterais, direta ou indiretamente, embora muitas camadas de chamadas não mudem nada conceitualmente. Ainda é um efeito colateral. Mas se o ponto é que os efeitos colaterais só acontecem através de objetos injetados, para que você controle quais tipos de efeitos colaterais são possíveis, isso soa como um bom design. Simplesmente não é livre de efeitos colaterais. É bom OO, mas não é puramente funcional.
JacquesB
12
@LightnessRacesinOrbit: grep no modo silencioso possui um código de retorno do processo que será usado. Se ele não tem isso, então isso seria realmente inútil
unperson325680
10
@progo: Um código de retorno não é um efeito colateral; é o efeito. Qualquer coisa que nós chamamos um efeito colateral é chamado de efeito colateral, porque não é o código de retorno;)
Leveza raças com Monica
8
@ Cubic esse é o ponto. Um programa sem efeitos colaterais é inútil.
Jens Schauder
5
@mathreadler Isso é um efeito colateral :)
Andres F.
32

Tell, Don't Ask vem com algumas suposições fundamentais:

  1. Você está usando objetos.
  2. Seus objetos têm estado.
  3. O estado dos seus objetos afeta o comportamento deles.

Nenhuma dessas coisas se aplica a funções puras.

Então, vamos revisar por que temos a regra "Diga, não pergunte". Esta regra é um aviso e um lembrete. Pode ser resumido assim:

Permita que sua classe gerencie seu próprio estado. Não peça seu estado e, em seguida, tome medidas com base nesse estado. Diga à classe o que deseja e deixe-a decidir o que fazer com base em seu próprio estado.

Em outras palavras, as classes são as únicas responsáveis ​​por manter seu próprio estado e agir sobre ele. É disso que se trata o encapsulamento.

Partida Fowler :

O Tell-Don't-Ask é um princípio que ajuda as pessoas a lembrar que a orientação a objetos trata de agrupar dados com as funções que operam nesses dados. Isso nos lembra que, em vez de solicitar dados a um objeto e agir com base nesses dados, devemos dizer ao objeto o que fazer. Isso nos encoraja a mudar o comportamento para um objeto para acompanhar os dados.

Para reiterar, nada disso tem a ver com funções puras, ou mesmo impuras, a menos que você esteja expondo o estado de uma classe ao mundo exterior. Exemplos:

Violação de TDA

var color = trafficLight.Color;
var elapsed = trafficLight.Elapsed;
If (color == Color.Red && elapsed > 2.Minutes)
    trafficLight.ChangeColor(green);

Não é uma violação da TDA

var result = trafficLight.ChangeColor(Color.Green);

ou

var result = await trafficLight.ChangeColorWhenReady(Color.Green);     

Nos dois exemplos anteriores, o semáforo mantém o controle de seu estado e de suas ações.

Robert Harvey
fonte
Espere um segundo, os fechamentos podem ser puros. eles têm estado (eles chamam de escopo lexical). e esse escopo lexical pode afetar seu comportamento. Você tem certeza de que o TDA é relevante apenas quando objetos estão envolvidos?
Candied_orange 13/0218
7
Os fechamentos @CandiedOrange são puros apenas se você não modificar as ligações fechadas. Caso contrário, você perderá a transparência referencial ao chamar a função retornada do fechamento.
Jared Smith
1
@JaredSmith e não é o mesmo quando você está falando sobre objetos? Isso não é simplesmente a questão da imutabilidade?
132818 Candied_orange
1
@bdsl - E agora você apontou inadvertidamente exatamente para onde cada discussão desse tipo vai com o seu exemplo trafficLight.refreshDisplay. Se você seguir as regras, levará a um sistema altamente flexível que ninguém, a não ser o codificador original, entende. Eu até apostaria que, após alguns anos de hiato, até o codificador original provavelmente também não entenderá o que eles fizeram.
Dunk
1
"Bobo", como em "Não abra os outros objetos e não olhe para as tripas deles, mas derrame suas próprias tripas (se houver) nos métodos de outros objetos; esse é o Caminho" - bobinho.
Joker_vD 15/0218
30

Quando trato de um objeto, não o pergunto sobre seu estado interno. Eu digo o que preciso fazer e ele usa seu estado interno para descobrir o que fazer com o que eu disse para fazer.

Você não apenas pede seu estado interno , mas também não tem um estado interno .

Diga também , não pergunte! que não implicam não conseguir um resultado na forma de um valor de retorno (fornecido por um returndeclaração dentro do método). Isso implica que eu não me importo como você faz isso, mas faça esse processamento! . E às vezes você quer imediatamente o resultado do processamento ...

Timothy Truckle
fonte
1
No entanto, o CQS implicaria que a modificação do estado e a obtenção de resultados deveriam ser separados
jk.
7
@jk. Como de costume: em geral, você deve separar a mudança de estado e retornar um resultado, mas em casos raros, existem razões válidas para combinar isso. Por exemplo: um iterators next()método não deve retornar apenas o objeto atual, mas também alterar as iterators estado interno para que a próxima chamada retorna o próximo objeto ...
Timothy Truckle
4
Exatamente. Eu acho que o problema do OP é simplesmente devido ao mal-entendido / aplicação incorreta de "diga, não pergunte". E corrigir esse mal-entendido faz o problema desaparecer.
21418 Konrad Rudolph
@ KonradRudolph Plus, eu não acho que esse seja o único mal-entendido aqui. Sua descrição de uma "função pura" inclui "Seu estado é definido apenas uma vez". Outro comentário indica que isso pode significar o contexto de um fechamento, mas o fraseado soa estranho para mim.
Izkata 14/02
17

Se você considera return"prejudicial" (para ficar na sua foto), em vez de fazer uma função como

ResultType f(InputType inputValue)
{
     // ...
     return result;
}

construa-o de maneira a transmitir mensagens:

void f(InputType inputValue, Action<ResultType> g)
{
     // ...
     g(result);
}

Contanto que fe gsejam livres de efeitos colaterais, encadeá-los também será livre de efeitos colaterais. Eu acho que esse estilo é semelhante ao que também é chamado de estilo de passagem de continuação .

Se isso realmente leva a programas "melhores", é discutível, pois quebra algumas convenções. O engenheiro de software alemão Ralf Westphal criou um modelo de programação completo em torno disso, que chamou de "Componentes Baseados em Eventos", com uma técnica de modelagem que ele chama de "Design de Fluxo".

Para ver alguns exemplos, comece na seção "Traduzindo para eventos" desta entrada do blog . Para uma abordagem completa, recomendo o seu e-book "Mensagens como modelo de programação - Fazendo POO como se você quisesse" .

Doc Brown
fonte
24
If this really leads to "better" programs is debatableNós apenas temos que olhar para o código escrito em JavaScript durante a primeira década deste século. Jquery e seus plugins eram propensos a esse paradigma de retorno de chamada ... retornos de chamada em todos os lugares . Em um determinado momento, muitos retornos de chamada aninhados , tornaram a depuração um pesadelo. Código ainda tem que ser lido por seres humanos, independentemente das excentricidades da engenharia de software e seus "princípios"
LAIV
1
mesmo assim, em algum momento, você precisará fornecer uma ação que execute um efeito colateral ou precisará de alguma maneira de retornar à parte do CPS
jk.
12
O @Laiv CPS foi inventado como uma tecnologia de otimização para compiladores, ninguém esperava que os programadores escrevessem o código dessa maneira manualmente.
Joker_vD 13/0218
3
@CandiedOrange Na forma de slogan, "o retorno está apenas dizendo à continuação o que fazer". De fato, a criação do Scheme foi motivada tentando entender o modelo de ator de Hewitt e chegou à conclusão de que atores e fechamentos eram a mesma coisa.
Derek Elkins #
2
Hipoteticamente, com certeza, você pode escrever seu aplicativo inteiro como uma série de chamadas de função que não retornam nada. Se você não se importa em ser bem acoplado, nem precisa das portas. Mas a maioria dos aplicativos sãos utiliza funções que retornam coisas, porque ... bem, são sãos. E como acredito que demonstrei adequadamente em minha resposta, você pode obter returndados de funções e ainda aderir ao Tell Don't Ask, desde que não controle o estado de um objeto.
Robert Harvey
7

A passagem de mensagens é inerentemente eficaz. Se você disser a um objeto que faça algo, espera que ele tenha efeito sobre algo. Se o manipulador de mensagens fosse puro, você não precisaria enviar uma mensagem.

Nos sistemas de atores distribuídos, o resultado de uma operação geralmente é enviado como uma mensagem de volta ao remetente da solicitação original. O remetente da mensagem é disponibilizado implicitamente pelo tempo de execução do ator ou é (por convenção) explicitamente passado como parte da mensagem. Na passagem de mensagens síncronas, uma única resposta é semelhante a uma returndeclaração. Na passagem assíncrona de mensagens, o uso de mensagens de resposta é particularmente útil, pois permite o processamento simultâneo em vários atores enquanto ainda fornece resultados.

Passar o "remetente" para o qual o resultado deve ser entregue explicitamente basicamente modela o estilo de passagem de continuação ou os parâmetros temidos - exceto que ele passa mensagens para eles em vez de modificá-los diretamente.

Bergi
fonte
5

Essa pergunta toda me parece uma 'violação de nível'.

Você possui (pelo menos) os seguintes níveis em um projeto importante:

  • O nível do sistema, por exemplo, plataforma de comércio eletrônico
  • O nível do subsistema, por exemplo, validação do usuário: servidor, AD, front-end
  • O nível do programa individual, por exemplo, um dos componentes acima
  • O nível do ator / módulo [isso fica obscuro dependendo do idioma]
  • O nível do método / função.

E assim por diante, até tokens individuais.

Não há realmente nenhuma necessidade de uma entidade no nível de método / função não retornar (mesmo que apenas retorne this). E não há (na sua descrição) a necessidade de uma entidade no nível do ator retornar algo (dependendo do idioma que talvez nem seja possível). Eu acho que a confusão está em confluir esses dois níveis, e eu argumentaria que eles deveriam ser fundamentados de maneira distinta (mesmo que um objeto em particular abranja vários níveis).

Jared Smith
fonte
2

Você menciona que deseja se conformar tanto ao princípio da POO de "contar, não perguntar" quanto ao princípio funcional das funções puras, mas não vejo bem como isso o levou a evitar a declaração de retorno.

Uma maneira alternativa relativamente comum de seguir esses dois princípios é incluir as declarações de retorno e usar objetos imutáveis ​​apenas com getters. A abordagem, então, é que alguns dos getters retornem um objeto semelhante com um novo estado, em vez de alterar o estado do objeto original.

Um exemplo dessa abordagem está nos tipos tuplee frozensetdados internos do Python . Aqui está um uso típico de um frozenset:

small_digits = frozenset([0, 1, 2, 3, 4])
big_digits = frozenset([5, 6, 7, 8, 9])
all_digits = small_digits.union(big_digits)

print("small:", small_digits)
print("big:", big_digits)
print("all:", all_digits)

O que imprimirá o seguinte, demonstrando que o método de união cria um novo frozenset com seu próprio estado sem afetar os objetos antigos:

small: frozenset ({0, 1, 2, 3, 4})

big: frozenset ({5, 6, 7, 8, 9})

all: frozenset ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})

Outro exemplo extenso de estruturas de dados imutáveis ​​semelhantes é a biblioteca Immutable.js do Facebook . Nos dois casos, você começa com esses blocos de construção e pode criar objetos de domínio de nível superior que seguem os mesmos princípios, obtendo uma abordagem OOP funcional, que ajuda a encapsular os dados e raciocinar sobre eles com mais facilidade. E a imutabilidade também permite que você colha o benefício de poder compartilhar esses objetos entre threads sem ter que se preocupar com bloqueios.

yoniLavi
fonte
1

Cheguei a suspeitar que código bem escrito pode seguir os princípios OO e princípios funcionais ao mesmo tempo. Estou tentando conciliar essas idéias e o grande ponto de discórdia em que aterrei é o retorno.

Eu tenho tentado o meu melhor para conciliar alguns dos benefícios de, mais especificamente, programação imperativa e funcional (naturalmente não obtendo todos os benefícios, mas tentando obter a maior parte de ambos), embora returnseja realmente fundamental para fazer isso em de uma maneira direta para mim em muitos casos.

Com relação a tentar evitar returndeclarações diretas, tentei ponderar sobre isso durante a última hora ou mais e, basicamente, a pilha transbordou meu cérebro várias vezes. Eu posso ver o apelo disso em termos de impor o nível mais forte de encapsulamento e informações ocultos em favor de objetos muito autônomos, que são instruídos apenas para o que fazer, e eu gosto de explorar as extremidades das idéias, apenas para tentar melhorar. compreensão de como eles funcionam.

Se usarmos o exemplo do semáforo, imediatamente uma tentativa ingênua desejaria fornecer a esse semáforo o conhecimento de todo o mundo que o cerca e que certamente seria indesejável do ponto de vista do acoplamento. Portanto, se eu entendi corretamente, você abstrai isso e se dissocia em favor da generalização do conceito de portas de E / S que propagam ainda mais mensagens e solicitações, não dados, através do pipeline, e basicamente injeta esses objetos nas interações / solicitações desejadas entre si. enquanto alheios um ao outro.

O Pipeline Nodal

insira a descrição da imagem aqui

E esse diagrama é o mais longe que eu tentei esboçar isso (e, embora simples, eu tive que continuar mudando e repensando). Imediatamente, acho que um projeto com esse nível de dissociação e abstração se tornaria muito difícil de raciocinar em forma de código, porque o (s) orquestrador (es) que conectam todas essas coisas para um mundo complexo pode achar muito difícil acompanhe todas essas interações e solicitações para criar o pipeline desejado. Na forma visual, no entanto, pode ser razoavelmente simples desenhar essas coisas como um gráfico e vincular tudo e ver as coisas acontecendo de forma interativa.

Em termos de efeitos colaterais, eu pude ver isso livre de "efeitos colaterais", no sentido de que essas solicitações poderiam, na pilha de chamadas, levar a uma cadeia de comandos para cada thread executar, por exemplo (eu não conto isso como um "efeito colateral" em um sentido pragmático, pois não altera nenhum estado relevante para o mundo exterior até que esses comandos sejam realmente executados - o objetivo prático para mim na maioria dos softwares não é eliminar os efeitos colaterais, mas adiá-los e centralizá-los) . Além disso, a execução do comando pode gerar um novo mundo, em vez de alterar o existente. Meu cérebro está realmente sobrecarregado apenas tentando compreender tudo isso, no entanto, não há nenhuma tentativa de criar um protótipo dessas idéias. Eu também não

Como funciona

Então, para esclarecer, eu estava imaginando como você realmente programa isso. A maneira como eu a via trabalhando era, na verdade, o diagrama acima, capturando o fluxo de trabalho do usuário (programador). Você pode arrastar um semáforo para o mundo, arrastar um cronômetro, passar um período decorrido (após "construí-lo"). O cronômetro possui um On Intervalevento (porta de saída), você pode conectá-lo ao semáforo para que, nesses eventos, esteja dizendo à luz para percorrer suas cores.

O semáforo pode então, ao mudar para determinadas cores, emitir saídas (eventos) como, On Rednesse ponto, podemos arrastar um pedestre para o nosso mundo e fazer com que esse evento diga ao pedestre para começar a andar ... ou podemos arrastar os pássaros para dentro. nossa cena e fazemos isso quando a luz fica vermelha, dizemos aos pássaros que voem e batem as asas ... ou talvez, quando a luz fica vermelha, dizemos uma bomba para explodir - o que quisermos, e com os objetos sendo completamente alheios um ao outro e não fazendo nada além de dizer indiretamente um ao outro o que fazer com esse conceito abstrato de entrada / saída.

E eles encapsulam completamente seu estado e não revelam nada sobre ele (a menos que esses "eventos" sejam considerados IMT, quando eu precisaria repensar bastante as coisas), eles dizem um ao outro para fazer indiretamente, não perguntam. E eles são super dissociados. Nada sabe sobre nada, exceto essa abstração de porta de entrada / saída generalizada.

Casos de uso prático?

Eu pude ver esse tipo de coisa sendo útil como uma linguagem incorporada específica de domínio de alto nível em certos domínios para orquestrar todos esses objetos autônomos que não sabem nada sobre o mundo circundante, não expõem nada de seu estado interno após a construção e basicamente propagam solicitações entre nós, que podemos mudar e ajustar o conteúdo de nossos corações. No momento, sinto que isso é muito específico do domínio, ou talvez não tenha pensado o suficiente, porque é muito difícil envolver meu cérebro com os tipos de coisas que desenvolvo regularmente (costumo trabalhar com código de nível médio baixo) se eu interpretasse Tell, Don't Ask a tais extremidades e desejasse o nível mais forte de encapsulamento imaginável. Mas se estivermos trabalhando com abstrações de alto nível em um domínio específico,

Sinais e Slots

Esse design me pareceu estranhamente familiar até que percebi que são basicamente sinais e slots, se não levarmos em conta as nuances de como ele é implementado. A principal questão para mim é a eficácia com que podemos programar esses nós individuais (objetos) no gráfico, seguindo rigorosamente o Tell, Don't Ask, levado ao grau de evitar returndeclarações e se podemos avaliar o referido gráfico sem mutações (em paralelo, por exemplo, bloqueio ausente). É aí que os benefícios mágicos não estão na maneira como ligamos essas coisas potencialmente, mas em como elas podem ser implementadas com esse grau de mutação ausente no encapsulamento. Ambos parecem viáveis ​​para mim, mas não tenho certeza de quão amplamente aplicável seria, e é aí que estou um pouco perplexo tentando trabalhar com possíveis casos de uso.

Dragon Energy
fonte
0

Eu vejo claramente vazamento de certeza aqui. Parece que "efeito colateral" é um termo conhecido e comumente entendido, mas, na realidade, não é. Dependendo das suas definições (que realmente estão faltando no OP), os efeitos colaterais podem ser totalmente necessários (como o @JacquesB conseguiu explicar) ou inaceitáveis ​​e inaceitáveis. Ou, dando um passo em direção ao esclarecimento, é necessário distinguir entre os efeitos colaterais desejados que não se gosta de esconder (nesse ponto, o famoso IO de Haskell emerge: não é senão uma maneira de ser explícito) e efeitos colaterais indesejados como resultado de erros de código e esse tipo de coisa . Esses são problemas bem diferentes e, portanto, exigem um raciocínio diferente.

Então, sugiro começar a reformular a si mesmo: "Como definimos efeito colateral e o que as definições dadas dizem sobre sua inter-relação com a declaração" return "?".

Sereja Bogolubov
fonte
1
"neste momento emerge o famoso IO de Haskell: não passa de uma maneira de ser explícito" - a explicitação é certamente um benefício do IO monádico de Haskell, mas há outro ponto: fornece um meio de isolar o efeito colateral para ser inteiramente fora da língua - embora implementações comuns não realmente fazer isso, principalmente devido a preocupações de eficiência, conceitualmente é verdade: a mônada IO pode ser considerado uma forma de devolver uma instrução para algum ambiente que é totalmente fora do Programa Haskell, juntamente com uma referência a uma função para continuar após a conclusão.
Jules