Какая функционально-программная альтернатива интерфейсу?

15

Если я хочу программировать в «функциональном» стиле, чем бы заменить интерфейс?

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

Может быть Tuple<>?

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

Единственная причина, по которой я в первую очередь использую интерфейс, заключается в том, что я всегда хочу, чтобы были доступны определенные свойства / методы.


Редактировать: более подробно о том, что я думаю / пытаюсь.

Скажем, у меня есть метод, который принимает три функции:

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

С экземпляром Barя могу использовать этот метод:

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);

Но это немного болезненно, поскольку я должен упомянуть barтри раза за один звонок. Кроме того, я не собираюсь предоставлять вызывающим функциям функции из разных экземпляров.

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

В C # это interfaceодин из способов справиться с этим; но это кажется очень объектно-ориентированным подходом. Мне интересно, есть ли более функциональное решение: 1) передать группу функций вместе, и 2) убедиться, что функции должным образом связаны друг с другом.

Ðаn
источник
Вы бы не. Интерфейсы для типов данных превосходны (хотя вы бы предпочли неизменные объекты).
Теластин
1
Глава 2 SICP в значительной степени об этом.
user16764
7
Перечитав ваш вопрос, мне интересно, какую конкретную функциональность вы пытаетесь выполнить? Похоже, что вы спрашиваете, как выполнить побочный эффект в стиле oo в отношении экземпляра в функциональном стиле, что не имеет смысла ..
Джимми Хоффа
Ответ будет зависеть от языка. В Clojure вы можете использовать clojure.org/protocols , где единственной мягкой областью являются типы параметров, с которыми должны работать функции - они являются объектом - это все, что вы знаете.
Работа
1
Сделайте это просто: структура, содержащая эти указатели методов, плюс функция для инициализации ее из экземпляра объекта. Почему Хаскелл? ;)
mlvljr

Ответы:

6

Не относитесь к функциональному программированию как к тонкой оболочке императивного программирования; есть гораздо больше, чем просто синтаксическая разница.

В этом случае у вас есть GetIDметод, который подразумевает уникальность объектов. Это не очень хороший подход к написанию функциональных программ. Возможно, вы могли бы рассказать нам о проблеме, которую пытаетесь решить, и мы могли бы дать вам более полезный совет.

dan_waterworth
источник
3
«Вы должны думать по-русски».
Ðаn
Справедливо; Моя настоящая проблема не особенно интересна. У меня уже все отлично работает в C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay, если вы действительно хотите посмотреть на код), но было бы интересно сделать это в более функциональном стиле, пока все еще использую C #.
Ðаn
1
@ Ðаn Это действительно цитата из Firefox (фильма) (потому что это здорово, если она есть)? Или это используется где-то еще?
icc97
2
Хотя, как я согласен, это полный сдвиг парадигмы от неконтролируемого к функциональному программированию, на много порядков больше строк кода, написанных безупречным образом. Таким образом, гораздо больше угловых случаев, написанных для огромных систем, будет найдено с помощью непревзойденного программирования. Есть много хороших практик в неконтролируемом программировании, и знание того, могут ли эти навыки быть переведены или не являются проблемой в FP, является достойным вопросом. Вполне возможно написать ужасный код на FP, поэтому вопросы такого рода должны подчеркивать и хорошие стороны FP.
icc97
11

Haskell и его производные имеют классы типов, похожие на интерфейсы. Хотя, похоже, вы спрашиваете о том, как сделать инкапсуляцию, а это вопрос о системах типов. Система типов Хиндли Милнера широко распространена в функциональных языках и имеет типы данных, которые делают это для вас по-разному в разных языках.

Джимми Хоффа
источник
5
+1 для классов типов - основное отличие между классом типов Haskell и интерфейсом Java заключается в том, что класс типов связан с типом после того, как оба они объявлены отдельно. Вы можете использовать старый тип через новый «интерфейс» так же легко, как вы можете использовать старый «интерфейс» для доступа к новому типу. Для сокрытия данных вы скрываете реализацию типа в модуле. По крайней мере, согласно Бертрану Мейеру из Eiffel , класс ООП является своего рода модулем.
Steve314
5

Есть несколько способов разрешить функции работать с несколькими входами.

Первый и самый распространенный: параметрический полиморфизм.

Это позволяет функции действовать на произвольные типы:

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

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

Это хорошо, но не дает вам динамического распределения, которое есть у интерфейсов OO. Для этого у Haskell есть классы типов, у Scala есть имплициты и т. Д.

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 <+>

Между этими двумя механизмами вы можете выражать всевозможные сложные и интересные поведения на ваших типах.

Даниэль Гратцер
источник
1
Вы можете добавить подсказки подсветки синтаксиса, когда язык в ответе не соответствует языку в вопросе. См. Мое предлагаемое редактирование, например.
hugomg
1

Основное правило заключается в том, что в программировании на FP функции выполняются так же, как объекты в OO-программировании. Вы можете вызывать их методы (ну, в любом случае, метод "call"), и они отвечают согласно некоторым инкапсулированным внутренним правилам. В частности, каждый приличный язык FP позволяет вам иметь «переменные экземпляра» в вашей функции с замыканиями / лексическими ограничениями.

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;
    }
};

Теперь следующий вопрос: что вы подразумеваете под интерфейсом? Один из подходов заключается в использовании номинальных интерфейсов (он соответствует интерфейсу, если он говорит, что это так) - обычно он во многом зависит от того, какой язык вы используете, поэтому оставим его для последнего. Другим способом определения интерфейса является структурный способ, позволяющий увидеть, какие параметры получать и возвращать. Это тот интерфейс, который вы обычно видите в динамических языках типа утка, и он очень хорошо подходит для всех FP: интерфейс - это просто типы входных параметров для наших функций и типы, которые они возвращают, поэтому все функции, соответствующие правильные типы соответствуют интерфейсу!

Следовательно, наиболее простой способ представления объекта, соответствующего интерфейсу, состоит в том, чтобы просто иметь группу функций. Обычно вы обходите безобразие передачи функций по отдельности, упаковывая их в какую-то запись:

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

do_something(my_blarfable)

Использование обнаженных функций или записей функций будет иметь большое значение для решения большинства ваших общих проблем «без жира» без тонны образцов. Если вам нужно что-то более продвинутое, иногда языки предоставляют вам дополнительные возможности. Один пример, который упоминали люди, это классы типа Haskell. Классы типов по существу связывают тип с одной из этих записей функций и позволяют вам писать вещи, чтобы словари были неявными и автоматически передавались внутренним функциям по мере необходимости.

-- 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)

Однако следует отметить одну важную вещь, касающуюся классов типов: словари связаны с типами, а не со значениями (например, что происходит в словаре и версиях ОО). Это означает, что система типов не позволяет смешивать «типы» [1]. Если вы хотите получить список "blargables" или двоичную функцию, заменяющую blargables, то классы типов будут ограничивать все одним и тем же типом, в то время как подход с использованием словаря позволит вам иметь blargables различного происхождения (какая версия лучше зависит от того, кто вы есть) делать)

[1] Существуют продвинутые способы создания «экзистенциальных типов», но, как правило, это не стоит проблем.

hugomg
источник
0

Я думаю, что это будет зависеть от языка. Я родом из липы. Во многих случаях интерфейсы с состоянием до некоторой степени нарушают функциональную модель. Так, например, в CLOS LISP менее функциональный и ближе к императивному языку. Как правило, требуемые параметры функции в сочетании с методами более высокого уровня - это, вероятно, то, что вы ищете.

;; 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
источник