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

14

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 eu estou usando uma interface é porque eu sempre quero certas propriedades / métodos disponíveis.

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

Diga, 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 Bar , 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 trabalhoso, já que tenho que mencionar bar três vezes em uma única chamada. Além disso, eu realmente não pretendo que os chamadores forneçam funções de instâncias diferentes

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

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

    
por Ðаn 03.03.2013 / 05:17
fonte

5 respostas

6

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

Nesse caso, você tem um método GetID , 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 podermos dar-lhe mais conselhos significativos.

    
por 04.03.2013 / 08:02
fonte
11

O Haskell e seus derivados possuem typeclasses que são semelhantes às interfaces. Embora pareça que você está perguntando sobre como fazer o encapsulamento, que é uma questão sobre sistemas de tipos. O sistema de tipo hindley Milner é comum em linguagens funcionais e possui tipos de dados que fazem isso para você de várias maneiras em diferentes idiomas.

    
por 03.03.2013 / 05:41
fonte
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 despacho dinâmico que as interfaces OO possuem. Para este Haskell tem typeclasses, 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 todos os tipos de comportamentos complexos e interessantes em seus tipos.

    
por 03.03.2013 / 06:08
fonte
1

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

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 uma interface? Uma abordagem é usar interfaces nominais (ela está em conformidade com a interface se ela diz que faz isso) - essa geralmente depende muito de qual linguagem você está usando, então vamos deixar para a última. A outra maneira de definir uma interface é a maneira estrutural, ver quais parâmetros recebem e retornam. Este é o tipo de interface que você tende a ver em linguagens dinâmicas, com o tipo Duck e combina muito bem com FP: uma interface é apenas os tipos de parâmetros de entrada para nossas funções e os tipos que eles retornam. 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 se dar ao luxo de passar as funções separadamente empacotando-as em algum tipo de registro:

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

do_something(my_blarfable)

Usar funções ou registros de funções nuas ajudará bastante a resolver a maioria dos seus problemas comuns de maneira "livre de gordura" sem toneladas de clichê. Se você precisar de algo mais avançado do que isso, às vezes os idiomas oferecem recursos extras. Um exemplo que as pessoas mencionaram são as classes de tipos Haskell. As classes de tipo basicamente associam um tipo a um desses registros de funções e permitem que você escreva coisas para que os dicionários fiquem implícitos e passem automaticamente para 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 observação importante sobre as typeclasses é 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). Isto significa que o sistema de tipos não permite misturar "tipos" [1]. Se você quer uma lista de "blargables" ou uma função binária levando para blargables então typeclasses irão restringir tudo para ser o 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.

    
por 04.03.2013 / 17:25
fonte
0

Acho que será específico do idioma. Eu venho de um fundo lispy. Em muitos casos, as interfaces com o estado quebram o modelo funcional em um grau. Então, CLOS, por exemplo, é onde o LISP é menos funcional e mais próximo de uma linguagem imperativa. Geralmente, os parâmetros de função obrigatórios combinados com métodos de nível superior são provavelmente 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)))
    
por 03.03.2013 / 17:06
fonte