Collections 以相同的方式处理单个和多个元素(“透明”贴图操作符)

Collections 以相同的方式处理单个和多个元素(“透明”贴图操作符),collections,functional-programming,programming-languages,functor,language-design,Collections,Functional Programming,Programming Languages,Functor,Language Design,我正在开发一种简单、直观、简洁的编程语言(是的,我知道,我是第一个提出这个目标的人;-)。 为了简化容器类型的使用,我正在考虑的一个特性是使容器元素类型的方法在容器类型本身上可用,基本上是作为调用映射(…)方法的快捷方式。其思想是,使用多个元素与使用单个元素没有什么不同:我可以将add(5)应用于单个数字或整个数字列表,并且我不必为“一”与“多”场景编写稍有不同的代码 例如(Java伪代码): 导入静态java.math.biginger.*;//零,一。。。 ... //注意:biginger

我正在开发一种简单、直观、简洁的编程语言(是的,我知道,我是第一个提出这个目标的人;-)。 为了简化容器类型的使用,我正在考虑的一个特性是使容器元素类型的方法在容器类型本身上可用,基本上是作为调用
映射(
方法的快捷方式。其思想是,使用多个元素与使用单个元素没有什么不同:我可以将
add(5)
应用于单个数字或整个数字列表,并且我不必为“一”与“多”场景编写稍有不同的代码

例如(Java伪代码):

导入静态java.math.biginger.*;//零,一。。。
...
//注意:biginger有一个add(biginger)方法
流数=流数(0、1、2、10);
Stream ONE2TREE11=数字。添加(一);//=1, 2, 3, 11
//这相当于:numbers.map(ONE::add)
据我所知,这个概念不仅适用于“容器”类型(流、列表、集…),而且更普遍地适用于具有
map
方法的所有类似functor的类型(例如,optionals、state monad等)。 实现方法可能更符合编译器提供的语法糖分,而不是通过操纵实际类型(
Stream
显然不扩展
biginger
,即使它执行了“map add”方法必须返回
,而不是
整数
,这与大多数语言的继承规则不兼容)

我有两个关于这一建议功能的问题:

(1) 提供此类功能的已知注意事项是什么?容器类型和元素类型之间的方法名称冲突是我想到的一个问题(例如,当我在
列表上调用
add
我想向列表中添加一个元素还是向列表中的所有元素添加一个数字?参数类型应该澄清这一点,但这可能会变得棘手)

(2) 是否有任何现有的语言提供这样的功能,如果有,这是如何实现的引擎盖下?我做了一些研究,虽然几乎每一种现代语言都有类似于
map
操作符的东西,但我找不到任何一种语言的一对多区别是完全透明的(这让我相信,我在这里忽略了一些技术困难)


注意:我是在一个纯粹的功能上下文中研究这个问题的,它不支持可变数据(不确定这对回答这些问题是否重要)

您是否来自面向对象的背景?这是我的猜测,因为您认为
map
是属于每个不同“类型”的方法,而不是考虑属于
functor
类型的各种事物

比较如果
map
是每个函子的属性,TypeScript将如何处理此问题:

声明someOption:Option
map(val=>val*2)//选项
声明某人:或者
map(val=>val*2)//或者
mapLeft(字符串=>'ERROR')//或者
您还可以创建一个常量,表示每个单独的函子实例(选项、数组、标识、异步/承诺/任务等),其中这些常量作为方法具有
map
。然后有一个独立的
map
方法,该方法接受其中一个“函子常量”、映射函数和起始值,并返回新的包装值:

常量选项:函子={ 地图:(a:a)=>B)=>(o:Option)=>Option } declare const someOption:Option 映射(选项)(val=>val*2)(someOption)//选项 声明常量:函子={ 地图:(f:(a:a)=>B)=>(e:Orther)=>Orther } 声明常量或:或 映射(要么)(val=>val*2)(someother) 本质上,您有一个函子“map”,它使用第一个参数来标识要映射的类型,然后传入数据和映射函数

但是,对于像Haskell这样的函数式语言,您不必传入“函子常量”,因为该语言将为您应用它。哈斯克尔就是这么做的。不幸的是,我的哈斯克尔语不够流利,无法给你写这些例子。但这是一个非常好的好处,意味着更少的样板。它还允许您以“无点”风格编写大量代码,因此,如果您使您的语言更易于重构,那么您就不必手动指定所使用的类型来利用
map
/
chain
/
bind
/等

假设您最初编写的代码通过HTTP进行了一系列API调用。所以您使用了一个假设的异步monad。如果您的语言足够聪明,能够知道使用的是哪种类型,那么您可以编写如下代码

import{map as asynchmap}
声明常量apiCall:Async
asyncMap(n=>n*2)(apiCall)//异步
现在您更改API,使其读取文件,并将其改为同步:

import{map as syncMap}
声明常量apiCall:同步
syncMap(n=>n*2)(apiCall)
看看您必须如何更改多段代码。现在假设您有数百个文件和上万行代码

使用无点样式,您可以

从“functor”导入{map}
声明常量apiCall:Async
映射(n=>n*2)(apiCall)
并重构到

从“functor”导入{map}
声明常量apiCall:同步
映射(n=>n*2)(apiCall)
如果您有一个集中的API调用位置,那么这将是您更改任何内容的唯一地方。其他一切都足够智能,可以识别您正在使用的函子并应用映射
import static java.math.BigInteger.*; // ZERO, ONE, ...
...
// NOTE: BigInteger has an add(BigInteger) method
Stream<BigInteger> numbers = Stream.of(ZERO, ONE, TWO, TEN);
Stream<BigInteger> one2Three11 = numbers.add(ONE); // = 1, 2, 3, 11
// this would be equivalent to:  numbers.map(ONE::add)