如果函数可以是其他类型,单子规则将如何应用

时间:2018-09-16 04:31:59

标签: scala haskell clojure

我正在考虑在Clojure和Scala之间进行一些互操作。由于Java本身现在具有lambda,因此我在考虑数据与如何将函数应用于集合之间的概括

  • clojure函数扩展了clojure.lang.IFn,并概括了clojure.lang.ISeq上的收集操作
  • scala函数扩展了scala.Function并概括了scala.collection.Traversable上的收集操作
  • java lambdas扩展了java.util.function.Function并概括了java.util.stream.Stream上的收集操作

问题:

  • 单子在这种情况下会有用吗?
  • 如果是这样,是否会在所有集合类型上实施map操作,并且该操作如何推广?

示例:

  (map (scala-fn +) 
       [1 2 3]
       (scala-seq [1 2 3]) 
       (.stream [1 2 3]))
  => (scala-seq [3 6 9])

(添加了haskell作为标签,以防人们可能知道这种铁杆类型)

Clojure,Scala和Java中都有一些操作来获取一个集合,将一个函数应用于该集合并返回一个新集合。

  • 所有这些语言都在JVM上运行。
  • 但是,每种语言都定义了自己的类来表示函数。

我对clojure较为熟悉,因此有类似的操作:

 (into {} [[:a 1] [:b 2]]) => {:a 1 :b 2}

将clojure向量转换为clojure映射。因为into操作概括于java.util.List,所以可以使用任何继承java.util.List的数据结构。

我希望在Clojure中使用一些Scala库并遇到某些障碍:

  • Scala和clojure一样也具有不变的数据结构,但是它们的定义与clojure数据结构非常不同
  • Scala函数继承自scala.Function,因此需要包装到clojure.lang.IFn
  • Scala数据结构不继承自java.util.List,这意味着:

    (into {} (scala-list [:a 1] [:b 2]))不起作用。

  • 我正在寻求重新实现一些基本的Clojure函数,这些函数还包含了Scala数据结构。 (地图,缩小,地图猫等)

该功能类似于:

 (into {} (scala-list [:a 1] [:b 2])) => {:a 1 :b 2}

 (into (scala-map) [[:a 1] [:b 2]]) => (scala-map :a 1 :b 2)

 (concat (scala-list 1 2) [3 4]) => (scala-list 1 2 3 4)

 (concat [1 2] (scala-list 3 4)) => (1 2 3 4) ;lazy seq

 (map + [1 2] (scala-list 3 4)) => [4 6]

 (map (scala-fn +) [1 2] (scala-list 3 4)) => [4 6]
  • 我要寻找的是在收集操作中同时使用clojure和scala函数的功能。
  • 我可以在不使用monad的情况下做到这一点(通过检查集合和函数类型并在应用函数之前进行一些强制)
  • 我在这里要问的是对我有点好奇,因为我阅读过的有关monad的所有文献似乎都假定任何函数f:X->Y都是通用的。
  • 但是,对于clojure / scala / lambda互操作,clojure函数,scala函数和java lambda并不是通用的。我很好奇类别理论如何用于解决此问题。

2 个答案:

答案 0 :(得分:3)

  

scala函数扩展了scala.Function并概括了对scala.collection.Traversable的收集操作

     

java lambdas扩展了java.util.function.Function并概括了java.util.stream.Stream上的收集操作

首先,好消息是:这是不正确的,Java和Scala lambdas可以实现任何SAM(单一抽象方法)接口。这允许将Java lambda与期望scala.FunctionN的API一起使用,而Scala lambda与期望java.util.function.*的API一起使用(包括Java流)。据我所知,这种互操作性应在Scala 2.12和更高版本中完成。

一个坏消息(您知道即将到来):当专门谈论Scala集合API时,它也非常依赖于隐式参数,而这些参数在Java或Clojure中并不是真正可用的。同样,Clojure集合API依赖于动态类型,并且IFn不是SAM类型(因为它涵盖了具有不同数量参数的函数)。当然,从Clojure中使用Java和Scala lambda之间的互操作无济于事。

更笼统地说,这3个集合API(如果算上Scala 2.13中的兑换,则为4个)可能相差太大,无法像这样统一。

我认为单子集本身在这里不会有用。如果我想从Clojure中做一些有用的事情,我会选择“检查集合和函数类型,并在函数应用之前进行一些强制”作为解决方案。 Protocols可以简化它,但是会降低性能。

答案 1 :(得分:1)

我只能给您Haskell答案,因为我不会说其他任何语言。但是对我来说,似乎您主要是在寻找一种将输入自动转换为函数的方法。它与单子似乎没有任何关系。

(concat (scala-list 1 2) [3 4]) => (scala-list 1 2 3 4)

如果我将其翻译为Haskell,我会给它一个这样的类型

concat :: (IsList l1, IsList l2) => l1 elem -> l2 elem -> [elem]

其中ToList是一个类型类,它将仅将此容器转换为列表

class IsListOf a where
    toList :: a elem -> [elem]

从您的示例中尚不清楚您将如何决定输出类型,所以我对此无能为力。

(map + [1 2] (scala-list 3 4)) => [4 6]

在Haskell中,此函数不称为map,而是zipWith。如果您想自动转换输入,可以这样做。

zipWith :: (IsList l1, IsList l2) => (a -> b -> c) -> l1 a -> l2 b -> [c]

如果要自动转换功能,也可以执行。

zipWith :: (IsList l1, IsList l2, Is2Function f) => f a b c -> l1 a -> l2 b -> [c]

Is2Function将再次是仅转换为2元函数的类型类

class Is2Function f where
    toFunction :: f a b c -> a -> b -> c

关于泛化,还有一些需要牢记的地方。我之前说过,我不知道您如何决定输出。当您进行许多概括时,编译器有时会遇到这个问题(至少在haskell中)。从表面上看,泛化看起来不错,但总不能使事情变得更清晰,并可能导致歧义。