我花了几天的时间弄清楚monad是什么之后,才对functor,Applicative和Monad有所了解。 这是我发现的。 我正在用Kotlin举例说明
什么是函子
函子可以按以下方式定义
- 函子只是可以映射的东西。
- 函子是一种数据结构,其作用类似于持有通用类型的容器
让我们定义数据类型或容器来保存值
class Functor < T > {
object None : Functor< Nothing >()
data class Some < out T > ( val value: T) : Functor<T>()
}
虽然我们将函子作为具有值的容器引入,但有时
这些容器可以具有有趣的属性。 为此原因,
函子通常被描述为“ 上下文中的值 ”。
当值包装在上下文中时,您不能将常规函数应用于
它,这是map函数开始将普通函数应用于
包裹价值
sealed class Functor < out A > {
object None : Functor<Nothing>()
data class Some < out A >( val value : A ) : Functor < A >()
inline infix fun < B > map ( f : ( A ) -> B ): Functor < B > = when ( this ) {
is None -> this
is Some -> Some(f(value))
}
companion object {
fun <A> some(value: A): Functor<A> = Some(value)
}
}
让我们将函数应用于包装值
fun inc (value: Int ) : Int = value + 1
val increment = Functor( 3 ).map {::inc} //OR
val increment = Functor( 3 ).map {it + 1 }
因此,这是函子,具有map函数的容器。
为什么函子有用?
- 因为我们要重用代码。
- Functor概括了如何将一个函数从一个值映射到另一个值。
- 函子非常有用,因为它们使我们可以将地图与集合一起使用,从而代替循环
- 链接 :由于Functor.map返回另一个Functor并将函数的结果传递给map,因此我可以将多个map函数链接在一起
适用性
函数只能映射一个带有一个参数的函数。 如果我们有一个采用多个参数的函数,则需要应用程序。
Applicative为如何应用在多个值上使用多个参数的函数提供了抽象。
在Applicative中,我们可以为每个支持Applicative的类型定义一个apply函数,该函数知道如何将类型上下文中包装的函数应用于相同上下文中包装的值:
sealed class Functor < out A > {
object None : Functor< Nothing >()
data class Some < out A > ( val value: A) : Functor<A>()
inline infix fun <B> map (f: ( A ) -> B): Functor<B> = when ( this ) {
is None -> this
is Some -> Some(f(value))
}
companion object {
fun <A> some (value: A ) : Functor<A> = Some(value)
}
infix fun <A, B> Functor<(A) -> B>.apply(f: Functor<A>): Functor<B> =
when ( this ) {
is None -> None
is Some -> f.map( this .value)
}
}
如果仔细看,您会发现我们的运算符仅按此特定顺序工作:选项(函数)适用选项(值)
示例1: 将一个接受两个实参的函数应用于两个包装的值
fun curriedAddition (a: Int ) = { b: Int ->
a + b
}
Some( 3 ) map ::curriedAddition map Some( 2 ) // => COMPILER ERROR// Use applicative
Some( 3 ) map ::curriedAddition apply Some( 2 )
应用三乘积功能:
fun tripleProduct(a: Int, b : Int, c : Int) = a * b * c
fun <A, B, C, D> curry(f: (A, B, C) -> D): (A) -> (B) -> (C) -> D = { a -> { b -> { c -> f(a, b, c) } } }
Some( 3 ) map curry(::tripleProduct) apply Some( 5 ) apply Some( 4 )
// => Some(60)
单音
在开始使用Monad之前,了解Kotlin中的功能组成很重要
功能组成
- 功能组合是一种使用现有功能来构建功能的技术
- 函数组合将调用右手函数的结果作为左手函数的参数。
举一个例子,将给定的数字加1并乘以3
val add : (Int) -> Int = {x -> x + 1 }
val mult : (Int) -> Int = { y -> y * 3 }fun <A,B,C>composeF(f: (B) -> C, g : (A) ->B) {
return { x -> f(g(x)) }
}
val addOneThenMul3 = composeF(::mul3, ::add1)
print(addOneThenMul3( 2 )) // output 9
再举一个例子,您将获得输入为两位数的字符串,用逗号分隔,您必须将第一位数字除以第二位数字。 对于Input =“ 126,3”,输出应为output = 126/3 = 4
- 为了解决此问题,我们将分解三个功能中的问题-拆分-解析-划分
- 我们通常使用以下组成
val splitString : (String) -> Pair<Stirng,String> = { s -> s.split( "," ).first() to s.split( "," ).last() } val parseToDouble : (Pair<String,String>) -> Pair< Double , Double > = {d -> d.first.toDouble() to d.second.toDouble()} val division : (Pair< Double , Double >) -> Double = {d -> d.first/d.second}f un <A,B,C>composeF(f: (B) -> C, g: (A) ->B) { return { x -> f(g(x)) } } val result = composeF(composeF(division,parseToDouble),splitString) print(result( "126,3" )) // 42
- 上面的用例组合不是很完美,原因可能会出错,例如split函数可能会返回异常,因为这些数字在两位数之间没有逗号,parse函数可能会失败,可能没有数字,最终结果可能会失败
- 对于任何异常,该程序可以是局部的,然后我们可以做什么
- 一种实现方法是,我们以返回“装饰的”结果的方式组合这些函数,这意味着我们可以将结果包装在类( Functor )中,这就是术语Monad的来历。
单音
Monads是使特殊功能的孩子自动组合的机制
换句话说, Monad是使功能组合超负荷以对中间值执行额外计算所需的最小结构量
了解以上拆分,解析除法用例的Monad
为了解决上述组合问题,我们必须将结果包装在每个函数的上下文中,所以让我们做吧
要包装价值,我们需要Functor
sealed class Result < out T > {
object None : Result< Nothing >()
data class Some < out T > ( val value: T) : Result<T>()
}
并修改函数以返回换行结果
val split : (String) -> Result<Pair<String,String>> = { s ->
val listString = s.split( "," )
Some(listString.first() to listString.last())
}
val parse : (Pair<String,String>) -> Result<Pair< Double , Double >> = {pair -> Some(pair.first.toDouble() to pair.second.toDouble()) }
val divide : (Pair< Double , Double >) -> Result< Double > = {pair -> Some(pair.first.div(pair.second)) }
在这里,我们修改了函数以返回包装的值,
我们必须将split函数应用到parse函数,然后再划分函数。
由于每个函数的返回类型都是换行值,并且每个函数的输入参数都接受纯值,因此我们无法轻松地将这些返回换行值的函数传递给其他函数。
Monad应用使用FlatMap函数将包装后的值返回到包装后的值的函数
所以flatMap函数看起来像
sealed class Result < out T > {
object None : Result< Nothing >()
data class Some < out T > ( val value: T) : Result<T>()
inline fun <B> flatMap (f: ( T ) -> Result<B>) : Result<B> =
when ( this ) {
is None -> this
is Some -> f(value)
}
}
这里我们在Functor上写了flatMap函数
现在您可以将返回包装值的函数应用于包装值
val output = split( "126,3" ).flatMap(parse).flatMap(divide)
println(output) // will print 42
简单来说, Modad是具有flatMap的Functor
这是此示例的github链接
而已。 我希望本文能帮助您了解Functor和Monads的概念以及它们的组合。
这是我在这里的第一篇文章,希望您喜欢它;)收到任何反馈,我将很高兴!
From: https://hackernoon.com/functor-applicative-and-monads-fp1e32eh