接口的功能编程替代方案是什么?


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

对于BarI 的实例,可以使用此方法:

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)确保功能彼此正确相关。


你不会的 数据类型的接口非常好(尽管您倾向于不可变的对象)。
Telastyn

1
SICP的第二章对此进行了详细介绍。
user16764 2013年

7
重读您的问题后,我很好奇您要完成哪些特定功能?你似乎是询问的是如何做OO风格的侧effectful编程与对一个实例在功能的风格,这没有任何意义..
吉米·霍法

答案将取决于语言。在Clojure中,您可以使用clojure.org/protocols,其中唯一的软区域是函数必须对其进行操作的参数类型-它们是一个对象-这就是您所知道的。
Job

1
简单点:一个包含那些方法指针的结构,以及一个从对象实例对其进行初始化的函数。为什么选择Haskell?;)
mlvljr

Answers:


6

不要将函数式编程视为比命令式编程薄的单板。不仅有语法上的差异。

在这种情况下,您有一个GetID方法,它暗示对象的唯一性。这不是编写功能程序的好方法。也许您可以告诉我们您要解决的问题,我们可以为您提供更有意义的建议。


3
“你必须用俄语思考。”
2013年

很公平; 我真正的问题不是特别有趣。我已经可以在C#中正常工作了(如果您真的想看一下代码,可以在github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay中进行),但是以一种更具功能性的方式来做会很有趣仍在使用C#。
2013年

1
@Ðаn这真的是Firefox(电影)报价吗(因为它真的很棒)?还是在其他地方使用?
icc97

2
在我同意这是从命令式编程到函数式编程的完整模式转变的地方,以命令式方式编写的代码行多了许多数量级。因此,使用命令式编程会发现很多为大型系统编写的极端案例。在命令式编程中有许多好的做法,并且知道这些技能是否可以翻译或在FP中是否不是一个重要问题。完全有可能在FP中编写可怕的代码,因此这些问题也应突出FP的优点。
icc97

11

Haskell及其派生类的类型类与接口相似。虽然听起来您正在询问如何进行封装,但这是有关类型系统的问题。印度斯坦米尔纳类型系统在功能语言中很常见,并且它的数据类型可以跨语言以不同的方式为您执行此操作。


5
类型类为+1-Haskell类型类和Java接口之间的主要区别在于,分别声明了类型类,该类型类便与该类型相关联。您可以通过新的“接口”轻松使用旧类型,就像使用旧的“接口”访问新类型一样容易。对于数据隐藏,可以将类型的实现隐藏在模块中。至少根据Eiffel著名的Bertrand Meyer所说,OOP类一种模块。
2013年

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
当答案中的语言与问题中的语言不匹配时,您可以添加sintax高亮提示。例如,请参阅我建议的编辑。
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)

但是,关于类型类要注意的一件事是,字典与类型相关联,而不与值相关联(例如在字典和OO版本中发生的情况)。这意味着您不能使用类型系统来混合“类型” [1]。如果您想获取“可疑对象”列表或要使用可疑对象的二进制函数,则类型类将约束所有内容为同一类型,而字典方法将使您拥有不同来源的可疑对象(哪个版本更好取决于您的实际情况在做)

[1]有很多先进的方法可以做“存在类型”,但是通常不值得麻烦。


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)))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.