【发布时间】:2013-08-13 03:23:11
【问题描述】:
在大多数支持可变变量的编程语言中,可以轻松实现类似以下 Java 示例:
interface Accepter<T> {
void accept(T t);
}
<T> T getFromDoubleAccepter(Accepter<Accepter<T>> acc){
final List<T> l = new ArrayList<T>();
acc.accept(new Accepter<T>(){
@Override
public void accept(T t) {
l.add(t);
}
});
return l.get(0); //Not being called? Exception!
}
只是对于那些不懂Java的人来说,上面的代码接收到的东西可以提供一个带一个参数的函数,并且它应该以这个参数作为最终结果。
这不像callCC:没有控制流交替。只关心内部函数的参数。
我认为 Haskell 中的等效类型签名应该是
getFromDoubleAccepter :: (forall b. (a -> b) -> b) -> a
因此,如果有人可以为您选择的类型提供函数(a -> b) -> b,那么他必须已经拥有a。因此,您的工作是给他们一个“回调”,而不是记住他们发送给您的任何内容,一旦他们返回给您,请将 那个 值返回给您的调用者。
但我不知道如何实现这一点。我能想到几种可能的解决方案。虽然我不知道它们每个是如何工作的,但我可以根据预期的困难对它们进行评分和排序:
Cont或ContT单子。我认为这是最简单的。RWSmonad 或类似的。任何其他单子。 纯像
Maybe这样的单子我认为更难。-
仅使用标准的纯功能特性,如惰性求值、模式匹配、定点污染器等。我认为这是最难的(甚至是不可能的)。
我希望看到使用上述任何技术的答案(并且更喜欢更难的方法)。
注意:不应该对类型签名做任何修改,解决方案应该和Java代码做的一样。
更新
一旦我看到有人评论了getFromDoubleAccepter f = f id,我就意识到我做错了。基本上我使用forall 只是为了让游戏更容易,但看起来这种扭曲太容易了。实际上,上面的类型签名强制调用者传回我们给他们的任何东西,所以如果我们选择a作为b,那么该实现给出了相同的预期结果,但它只是...... . 没想到。
其实我想到的是一个类型签名,比如:
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
而这一次更难了。
另一位评论作者要求推理。我们来看一个类似的函数
getFunctionFromAccepter :: (((a -> b) -> b) -> b) -> a -> b
这个有一个幼稚的解决方案:
getFunctionFromAccepter f = \a -> f $ \x -> x a
但在下面的测试代码中它在第三个失败:
exeMain = do
print $ getFunctionFromAccepter (\f -> f (\x -> 10)) "Example 1" -- 10
print $ getFunctionFromAccepter (\f -> 20) "Example 2" -- 20
print $ getFunctionFromAccepter (\f -> 10 + f (\x -> 30)) "Example 3" --40, should be 30
在失败的情况下,我们传递了一个返回30 的函数,我们希望得到该函数。然而最终结果又是40,所以它失败了。有什么方法可以实现只是做我想做的事吗?
如果这可以在 Haskell 中完成,那么会有很多有趣的序列。例如,元组(或其他“代数”类型)也可以定义为函数,因为我们可以说类似type (a,b) = (a->b->())->() 并以此实现fst 和snd。这就是我在其他几种没有原生“元组”支持但具有“闭包”功能的语言中使用的方式。
【问题讨论】:
-
我没有完全遵循 Java,但我很确定你的类型签名是不能满足的。第一个参数很奇怪:它说明了如何从
a获取任何类型的b,但我认为这不太可能令人满意。然后它意味着生成一个类型a,尽管没有办法这样做(即使第一个参数稍微不那么时髦,它也无法输出a)。所以我认为你需要修复你的类型签名(你说不应该在答案中修改)才能得到回答。 -
你能解释一下你的Java代码应该做什么吗?
-
@NeilBrown,我也没有到理解 Java 的地步,但该类型签名绝对可以满足。事实上,唯一的实现是
getFromDoubleAccepter f = f id。 -
你所说的所有这些 monads 都是用 “标准的纯功能特性,如惰性求值、模式匹配”等实现的。所以如果用这些“标准特性”是不可能的“那么单子也是不可能的。如果您实际上不知道如何解决其中任何一种方法的问题,我也看不出您如何评估其中任何一种方法的难度。
-
在这样的假设情况下,您将无法将直觉从 Java 转移到 Haskell。在对 Haskell 进行编程时,无论你试图用它解决什么问题,都不会出现,至少不会以你习惯于用 Java 示例推理的形式出现。我强烈建议不要尝试这种思路,而是尝试在 Haskell 中编写实际程序,看看你会遇到什么。这是建立 Haskell 直觉的唯一真正方法。
标签: haskell