我实际上是在学习函数式编程,并且正在尝试学习和使用crockjs
目前,我正在尝试实现https://fsharpforfunandprofit.com/monadster/中描述的“ monadster”程序。
这就是我现在所拥有的(只是开始...)
const State = require("crocks/State");
const LivingPart = (unitOfForce, deadThing) => ({ unitOfForce, deadThing });
// Creating the potential living thing
const makeLiveThingM = deadThing => {
const becomeAlive = vitalForce => {
const unitOfForce = 1;
const remaining = vitalForce - unitOfForce;
return { part: LivingPart(unitOfForce, deadThing), remaining };
};
return State.get(becomeAlive);
};
// Using containers
const deadLegM = makeLiveThingM("deadLeg");
const deadArmM = makeLiveThingM("deadArm");
const livingThings = deadLegM.ap(deadArmM).evalWith(1);
console.log(livingThings);
我的问题是它引发了以下错误:
/Users/pc/Soft/experiments/functional/crocks/node_modules/crocks/State/index.js:101
throw new TypeError('State.ap: Source value must be a function')
^
TypeError: State.ap: Source value must be a function
据我所知,这可能是因为我不了解apply函数或State.get
的运行方式。对我来说,它在我的代码中接受一个函数作为其内部值,但事实并非如此。
有人可以解释一下,告诉我我在做什么错吗?
感谢您的帮助
答案 0 :(得分:2)
欢迎使用JS中的函数式编程,并感谢您给crocks
一炮打响。
在看那篇文章时,需要注意的事情之一是作者在介绍State
ADT内部机制的工作原理,而不是实际上如何使用现有的State
ADT。
我将提供有关如何手动处理State
事务的说明,这与您在实现中的情况很接近。然后,我将给出一个简短的示例,说明如何使用构造助手(例如get
和modify
来递减VitalForce
)来处理和构建State
事务
我还将简要介绍如何使用Applicative
。
因此,让我们从crocks
引入几个ADT
const Pair = require('crocks/Pair')
const State = require('crocks/State')
我们需要State构造函数接受一个函数,该函数将返回Pair
(作者在文章中提及的元组)。 here可以找到构造函数的工作原理。
在讨论State函数之前,我们需要LivingPart
函数:
// LivingPart :: (Integer, String) -> Object
const LivingPart = (unitOfForce, part) =>
({ [part]: { unitOfForce } })
我已经更改了您原来的结构,因此我们可以将任何给定的Part
与另一个合并。
现在有了混合功能,我们就可以实现makeLiveThing
。您在实现中几乎拥有它。唯一真正的区别是我们需要使用函数构造State
ADT并返回Pair
。注意,我们仍然引入String
,但返回State
ADT,该ADT将在调用runWith
时执行。请记住,当前状态将传递到State
实例包装的函数中(在这种情况下,vitalForce
是我们的状态):
// makeLiveThing :: String -> State Integer Object
const makeLiveThing = part => State(
vitalForce => {
const unitOfForce = 1
const remaining = vitalForce - unitOfForce
return Pair(
LivingPart(unitOfForce, part),
remaining
)
}
)
现在我们有了创建LivingPart
并处理状态交易(递减1)的方法,我们可以创建几个部分:
// rightLeg :: State Integer Object
const rightLeg =
makeLiveThing('right-leg')
// leftArm :: State Integer Object
const leftArm =
makeLiveThing('left-arm')
现在是加入这些State
实例的任务。您认为使用apply
是正确的,因为当ADT同时具有ap
方法和of
方法时,它就称为Applicative
。当我们有一个Applicative
时,我们可以认为该类型能够合并(2)个独立的实例,这些实例不依赖于另一个实例的结果。我们只需要提供一种告诉类型如何组合的方法即可。
通常使用可以对ADT中包含的类型起作用的函数来完成。在我们的例子中,它是Object
,因此组合(2)对象的一种方法是使用Object.assign
。 crocks
提供了一个名为assign
的帮助程序,它可以做到这一点,所以请带它进入:
const assign = require('crocks/helpers/assign')
现在我们有了一种组合内部值的方法,我们需要将此函数“提升”为我们的State
类型,crocks
还有一个可以在{{1}上使用的函数}来提升并应用称为Applicative
的ADT(2)实例的内部值。意思是“将一个函数提升为带有两个实例的应用程序”
因此,我们也将其引入,然后创建一个用于连接(2)liftA2
的函数:
Parts
现在,使用此功能,我们可以抬起并连接这些部分,并使用我们的const liftA2 = require('crocks/helpers/liftA2')
// joinParts :: Applicative m => m Object -> m Object -> m Object
const joinParts =
liftA2(assign)
运行结果:
VitalForce
请注意,结果的结果在左边(组合的活动部分),结果在右边(其余的joinParts(rightLeg, leftArm)
.runWith(10)
//=> Pair( { left-arm: { unitOfForce: 1 }, right-leg: { unitOfForce: 1 } }, 8 )
)。
以下是上述内容的参考:
现在,我将展示一个简短的示例,说明如何设置单个VitalForce
事务以从池中提取State
。我不会在这里详细解释,但是您应该能够在此示例与VitalForce
文档之间收集一些信息:
State
以下是包含这些功能的一些文档:
因此,作为旁注,我进行了LiveCode广播on this channel,我将翻阅此博客文章,并讨论如何在const State = require('crocks/State')
const constant = require('crocks/combinators/constant')
const mapProps = require('crocks/helpers/mapProps')
const { modify } = State
// decrementBy :: Integer -> Integer -> Integer
const decrementBy =
x => y => y - x
// VitalForce :: { units: Integer }
// decUnitsBy :: Integer -> VitalForce -> VitalForce
const decUnitsBy = units =>
mapProps({ units: decrementBy(units) })
// getVitalForce :: Integer -> State VitalForce VitalForce
const getVitalForce = units =>
modify(decUnitsBy(units))
.map(constant({ units }))
getVitalForce(3)
.runWith({ units: 10 })
//=> Pair( { units: 3 }, { units: 7 } )
中实现这一点,如果您愿意的话。感兴趣。
希望这会有所帮助!