Qual é a alternativa de programação funcional a uma interface?

15

Se eu quiser programar em um estilo "funcional", com o que eu substituiria uma interface?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Talvez um Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

A única razão pela qual estou usando uma interface em primeiro lugar é porque eu quero sempre certas propriedades / métodos disponíveis.


Edit: Mais alguns detalhes sobre o que estou pensando / tentando.

Digamos, eu tenho um método que leva três funções:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

Com uma instância de Bareu posso usar este método:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

Mas isso é um pouco doloroso, como devo mencionar bartrês vezes em uma única ligação. Além disso, não pretendo que os chamadores forneçam funções de diferentes instâncias

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

Em C #, uma interfaceé uma maneira de lidar com isso; mas isso parece ser uma abordagem muito orientada a objetos. Gostaria de saber se há uma solução mais funcional: 1) passar o grupo de funções juntos e 2) garantir que as funções estejam relacionadas corretamente.

Como
fonte
Você não faria. As interfaces para tipos de dados são perfeitas (embora você prefira objetos imutáveis).
Telastyn
1
O capítulo 2 do SICP trata basicamente disso.
precisa saber é o seguinte
7
Depois de reler sua pergunta, estou curioso para saber qual funcionalidade específica você está tentando realizar? O que você parece estar pedindo é como fazer estilo oo Progra side-effectful contra uma instância em um estilo funcional, o que não faz sentido ..
Jimmy Hoffa
A resposta seria dependente do idioma. No Clojure, você pode usar o clojure.org/protocols , onde a única área flexível são os tipos de parâmetros nos quais as funções devem operar - elas são um objeto - é tudo o que você sabe.
Job
1
Simplifique: uma estrutura contendo esses ponteiros de método, além de uma função para inicializá-lo a partir de uma instância de objeto. Por que Haskell? ;)
mlvljr

Respostas:

6

Não trate a programação funcional como uma camada fina sobre a programação imperativa; há muito mais do que apenas uma diferença sintática.

Nesse caso, você tem um GetIDmétodo, o que implica exclusividade de objetos. Essa não é uma boa abordagem para escrever programas funcionais. Talvez você possa nos dizer o problema que está tentando resolver e poderíamos dar conselhos mais significativos.

dan_waterworth
fonte
3
"Você deve pensar em russo."
Ðаn
Justo; meu verdadeiro problema não é particularmente interessante. Eu já tenho as coisas funcionando muito bem no C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay se você realmente quiser ver o código), mas seria divertido fazer isso em um estilo mais funcional enquanto ainda usando c #.
Ðаn
1
@ Isso é realmente uma citação do Firefox (filme) (porque é incrível se for)? Ou é usado em outro lugar?
Icc97 18/05/19
2
Onde, como eu concordo, é uma mudança completa de paradigma da Programação Imperitiva para a Funcional, existem muitas ordens de magnitude, mais linhas de código escritas de maneira imperiosa. Portanto, muito mais casos de esquina escritos para sistemas enormes foram encontrados com programação imperativa. Existem muitas boas práticas em programação imperativa e saber se essas habilidades podem ser traduzidas ou se não são um problema no FP é uma questão valiosa. É completamente possível escrever código horrível no FP, portanto esse tipo de pergunta deve destacar também as partes boas do FP.
Icc97 18/05/19
11

Haskell e seus derivados têm classes de tipos semelhantes às interfaces. Embora pareça que você está perguntando sobre como fazer o encapsulamento, que é uma pergunta sobre sistemas de tipos. O sistema do tipo Milley hindley é comum em idiomas funcionais e possui tipos de dados que fazem isso para você de maneira variável entre idiomas.

Jimmy Hoffa
fonte
5
+1 para classes tipográficas - a principal diferença entre uma classe Haskell e uma interface Java é que a classe tipográfica é associada ao tipo depois que ambas são declaradas separadamente. Você pode usar um tipo antigo por meio de uma nova "interface" tão facilmente quanto você pode usar uma "interface" antiga para acessar um novo tipo. Para ocultar dados, você oculta a implementação do tipo em um módulo. Pelo menos, de acordo com Bertrand Meyer, da Eiffel , uma classe OOP é um tipo de módulo.
Steve314
5

Existem algumas maneiras de permitir que uma função lide com várias entradas.

Primeiro e mais comum: Polimorfismo Paramétrico.

Isso permite que uma função atue em tipos arbitrários:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

O que é legal, mas não fornece o envio dinâmico que as interfaces OO têm. Para este Haskell tem classes tipográficas, Scala tem implícitos, etc.

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Entre esses dois mecanismos, você pode expressar todo tipo de comportamento complexo e interessante em seus tipos.

Daniel Gratzer
fonte
1
Você pode adicionar dicas de realce de sintaxe quando o idioma da resposta não corresponder ao idioma da pergunta. Veja minha edição sugerida, por exemplo.
Hugomg
1

A regra básica é que, nas funções de programação FP, o mesmo trabalho que os objetos na programação OO. Você pode chamar os métodos deles (bem, o método "call" de qualquer maneira) e eles respondem de acordo com algumas regras internas encapsuladas. Em particular, cada linguagem FP decente permite que você tenha "variáveis ​​de instância" em sua função com fechamentos / escopo lexical.

var make_OO_style_counter = function(){
   return {
      counter: 0
      increment: function(){
          this.counter += 1
          return this.counter;
      }
   }
};

var make_FP_style_counter = function(){
    var counter = 0;
    return fucntion(){
        counter += 1
        return counter;
    }
};

Agora, a próxima pergunta é o que você quer dizer com interface? Uma abordagem é usar interfaces nominais (em conformidade com a interface, se for o caso) - essa geralmente depende muito de qual idioma você está usando, então deixe para a última. A outra maneira de definir uma interface é a maneira estrutural, vendo quais parâmetros a coisa recebe e retorna. Esse é o tipo de interface que você costuma ver em linguagens dinâmicas e tipadas por patos e se encaixa muito bem com todo o FP: uma interface é apenas o tipo de parâmetro de entrada para nossas funções e o tipo que eles retornam. Todas as funções correspondem ao tipos corretos se encaixam na interface!

Portanto, a maneira mais direta de representar um objeto que corresponde a uma interface é simplesmente ter um grupo de funções. Você costuma contornar a feia de passar as funções separadamente, embalando-as em algum tipo de registro:

var my_blarfable = {
 get_name: function(){ ... },
 set_name: function(){ ... },
 get_id:   function(){ ... }
}

do_something(my_blarfable)

O uso de funções simples ou registros de funções ajudará bastante a solucionar a maioria dos problemas comuns de maneira "livre de gordura", sem toneladas de clichê. Se você precisar de algo mais avançado que isso, às vezes os idiomas oferecem recursos extras. Um exemplo mencionado pelas pessoas são as classes do tipo Haskell. As classes de tipo essencialmente associam um tipo a um desses registros de funções e permitem escrever coisas para que os dicionários sejam implícitos e passados ​​automaticamente para as funções internas, conforme apropriado.

-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
    blarg_name :: String,
    blarg_id   :: Integer
}

do_something :: BlargDict -> IO()
do_something blarg_dict = do
   print (blarg_name blarg_dict)
   print (blarg_id   blarg_dict)

-- Typeclass version   
class Blargable a where
   blag_name :: a -> String
   blag_id   :: a -> String

do_something :: Blargable a => a -> IO
do_something blarg = do
   print (blarg_name blarg)
   print (blarg_id   blarg)

Uma coisa importante a ser observada sobre as classes de tipo, porém, é que os dicionários estão associados aos tipos, e não aos valores (como o que acontece no dicionário e nas versões OO). Isso significa que o sistema de tipos não permite misturar "tipos" [1]. Se você deseja uma lista de "blargables" ou uma função binária que leve a blargables, as classes de tipo restringirão tudo para ser do mesmo tipo, enquanto a abordagem do dicionário permitirá que você tenha blargables de diferentes origens (qual versão é melhor depende muito do que você é fazendo)

[1] Existem maneiras avançadas de fazer "tipos existenciais", mas geralmente não vale a pena.

hugomg
fonte
0

Eu acho que vai ser um idioma específico. Eu venho de um fundo lispy. Em muitos casos, as interfaces com o estado quebram o modelo funcional em um grau. Portanto, o CLOS, por exemplo, é onde o LISP é menos funcional e mais próximo de uma linguagem imperativa. Geralmente, os parâmetros de função necessários combinados com métodos de nível superior provavelmente são o que você está procurando.

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
ipaul
fonte