How do I utilize Javascript closure and private variables while also using Object.assign + Object.create in a factory function?

huangapple go评论69阅读模式
英文:

How do I utilize Javascript closure and private variables while also using Object.assign + Object.create in a factory function?

问题

我正在尝试弄清楚如何同时使用工厂函数闭包和Object.assign/Object.create继承。可能很简单,但我不知道如何做到。首先,我建立了一个希望使用闭包的工厂。我有一些操作私有变量的方法。

const projectFactory = function() {
	let _title = "new project";

	const _tabs = [];

	const getTitle = function() {
		return _title;
	}

	const setTitle = function(input) {
		_title = input;
		return;
	}

	const createTab = function() {
		// 一些填充tabs数组的方法
		return;
	}

	return {
		getTitle,
		setTitle,
		createTab
	}
};

const factory1 = projectFactory(); 
console.log(factory1); // 方法设置在对象上,_title和_tabs不可见

好吧,我创建的所有这些对象在调用此工厂函数时,所有方法都将相同。所以我决定将我的方法提取出来,存储在一个对象中供引用,然后重写我的工厂函数使用Object.assign/Object.create,这样方法就会被继承。

const projectMethods = {
    getTitle() {
        return this.title;
    },
    setTitle(input) {
        this.title = input;
        return;
    },
    createTab() {
        // 一些填充tabs数组的方法
        return;
    }
};
const projectFactory = function() {
	let title = "new project";

	const tabs = [];

	return Object.assign(Object.create(projectMethods), {
		title, tabs
	});
};

const factory1 = projectFactory();
console.log(factory1); // 方法在原型上可用,title和tabs可见

但是现在,由于我在我的工厂中返回了一个完整的对象,我不再拥有私有变量。我如何实现以下结果:

console.log(factory1); // 方法在原型上可用,title和tabs不可见
英文:

I'm trying to figure out how to incorporate factory function closures and use Object.assign/Object.create inheritance at the same time. Probably simple, but I can't figure out how to do it. First I build my desired factory that utilizes closure. I have methods that act on my private variables.

const projectFactory = function() {
	let _title = "new project";

	const _tabs = [];

	const getTitle = function() {
		return _title;
	}

	const setTitle = function(input) {
		_title = input;
		return;
	}

	const createTab = function() {
		// some method that fills the tabs array
		return;
	}

	return {
		getTitle,
		setTitle,
		createTab
	}
};

const factory1 = projectFactory(); 
console.log(factory1); // methods set on object, _title and _tabs not visible

`

Well all my methods are going to be the same for all these objects I'm creating when calling this factory function. So I decided to pull out my methods and store them in an object to be referenced then rewrite my factory function using Object.assign / Object.create, this way the methods are inherited.

const projectMethods = {
    getTitle() {
        return this.title;
    },
    setTitle(input) {
        this.title = input;
        return;
    },
    createTab() {
        // some method that fills the tabs array
        return;
    }
};
const projectFactory = function() {
	let title = "new project";

	const tabs = [];

	return Object.assign(Object.create(projectMethods), {
		title, tabs
	});
};

const factory1 = projectFactory();
console.log(factory1); // methods available on prototype, title and tabs visible

But now since I'm returning a whole object in my factory I no longer have private variables. How can I achieve the result:

console.log(factory1); // methods available on prototype, title and tabs not visible

答案1

得分: 1

以下是您要翻译的内容:

"有一种模式实际上允许在模块化的混合功能/行为之间共享(局部封闭/私有)状态。当然,它必须完全基于闭包。因此,不太合适的是闭包和 Object.assign 结合使用,后者发生在这些闭包之外(就像在OP的示例代码中所示)。另一方面,仅使用闭包的方法已经足够了。

将这种模式应用于第一次迭代时,OP的提供的代码可以实现如下...

// 基于函数的混合,包含共享状态。
function withStatfulProjectBehavior(sharedState) {
  const project = this;

  function getTitle() {
    return sharedState.title;
  }
  function setTitle(value) {
    return (sharedState.title = value);
  }

  function createTab() {
    // 一些填充tabs数组的方法...例如...
    return sharedState.tabs.push({
      title: 'new tab',
    });
  }

  return Object.assign(project, {
    getTitle,
    setTitle,
    createTab,
  });
}

// `project` 工厂函数。
function createProject() {
  const sharedState = {
    tabs: [],
    title: 'new project',
  }
  const project = {};

  return withStatfulProjectBehavior.call(project, sharedState);
};

const project = createProject();
console.log({ project });

console.log(
  'project.getTitle() ...',
  project.getTitle()
);
console.log(
  "project.setTitle('Foo Bar') ...",
  project.setTitle('Foo Bar')
);
console.log(
  'project.getTitle() ...',
  project.getTitle()
);

console.log(
  'project.createTab() ...',
  project.createTab()
);

第二次迭代的结果可能看起来像...

// 辅助功能
function validateTitle(value) {
  if (typeof value !== 'string') {
    console.warn('A title has to by a string value.');
    return false;
  } else {
    return true;
  }
}

// - 基于函数的,上下文感知的混合,它在其上下文和共享状态上应用了通用实现的受保护属性访问。
function withSharedProtectedProperty(
  state, { key, validation, enumerable = false }
) {
  const type = this;

  Reflect.defineProperty(type, key, {
    get: () => state[key],
    set: value => {
      if (validation(value)) {
        return state[key] = value;
      }
    },
    enumerable,
  });
}

// - 基于函数的,上下文感知的混合,它在其上下文和共享状态上应用了标签特定的行为。
function withSharedTabManagement(state) {
  this.addTab = function addTab(title = 'unnamed tab') {
    // 一些填充tabs数组的方法...例如...
    return state.tabs.push({ title });
  };
}

// `project` 工厂函数。
function createProject() {
  const state = {
    tabs: [],
    title: 'new project',
  }
  const project = {};

  withSharedProtectedProperty.call(project, state, {
    enumerable: true,
    key: 'title',
    validation: validateTitle,
  });
  withSharedTabManagement.call(project, state);

  return project;
};

const project = createProject();
console.log({ project });

console.log(
  'project.title ...', project.title
);
console.log(
  "project.title = 1234 ...", (project.title = 1234)
);
console.log(
  'project.title ...', project.title
);
console.log(
  "project.title = 'Foo Bar' ...", (project.title = 'Foo Bar')
);
console.log(
  'project.title ...', project.title
);

console.log(
  'project.addTab() ...', project.addTab()
);

您还可以查看...

编辑...为了考虑Bergi的一些建议...

"[@stuffz] ... 您不能同时拥有私有变量和共享继承方法。共享闭包是不可能的。" – Bergi

"@PeterSeliger 在实例的多个方法之间共享状态是容易的事情。不可能的是共享方法(通过原型继承)." – Bergi

...如果根据上述提到的胶水代码直接实现OP的原始示例代码,那么它应该非常接近...

// 项目特定模块范围。

// 通过`WeakMap`实例进行状态管理。
function getState(reference) {
  return projectStates.get(reference);
}
const projectStates = new WeakMap;

// 基于对象的状态化(后来是原型的)项目行为混合。
const statefulProjectMethods = {
  getTitle() {
    return getState(this).title;
  },
  setTitle(input) {
    return getState(this).title = input;
  },
  createTab(title = 'unnamed tab') {
    // 一些填充tabs数组的方法...例如...
    return getState(this).tabs.push({ title });
  },
};

// `project`工厂函数。
/*export */function createProject(protoMethods) {
  const project = Object.create(protoMethods);

  projectStates.set(project, {
    tabs: [],
    title: 'new project',
  });
  return project;
};
// 项目特定模块范围的结束。

// 另一个模块的范围
// import createProject from '/project.js';
const project = createProject(statefulProjectMethods);

console.log(
  "project.hasOwnProperty('getTitle') ...", project.hasOwnProperty('getTitle')
);
console.log(
  "project.hasOwnProperty('setTitle') ...", project.hasOwnProperty('setTitle')
);
console.log(
  "project.hasOwnProperty('createTab') ...", project.hasOwnProperty('createTab')
);
console.log(
  "Object.getPrototypeOf(project) ...", Object.getPrototypeOf(project)
);

console.log(
  'project.getTitle() ...', project.getTitle()
);
console.log(
  "project.setTitle('Foo Bar') ...", project.setTitle('Foo Bar')
);
console.log(
  'project.getTitle() ...', project.getTitle()
);

console

<details>
<summary>英文:</summary>

There is actually [a pattern which allows shared (locally enclosed / private) state amongst modularized mixin-functionality/behavior](https://stackoverflow.com/search?q=user%3A2627243+mixin+shared+state). It, of cause, has to be based exclusively on closures. Therefore the one thing which does not work well together is closures **and** `Object.assign` where the latter happens outside of such closures (like shown with the OP&#39;s example code). On the other hand a closures-only approach already is sufficient enough.

Applying such a pattern to a first iteration, the OP&#39;s presented code could be implemented like ...


&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    // function based mixin which incorporates shared state.
    function withStatfulProjectBehavior(sharedState) {
      const project = this;

      function getTitle() {
        return sharedState.title;
      }
      function setTitle(value) {
        return (sharedState.title = value);
      }

      function createTab() {
        // some method that fills the tabs array ... e.g. ...
        return sharedState.tabs.push({
          title: &#39;new tab&#39;,
        });
      }

      return Object.assign(project, {
        getTitle,
        setTitle,
        createTab,
      });
    }

    // `project` factory function.
    function createProject() {
      const sharedState = {
        tabs: [],
        title: &#39;new project&#39;,
      }
      const project = {};

      return withStatfulProjectBehavior.call(project, sharedState);
    };

    const project = createProject();
    console.log({ project });

    console.log(
      &#39;project.getTitle() ...&#39;,
      project.getTitle()
    );
    console.log(
      &quot;project.setTitle(&#39;Foo Bar&#39;) ...&quot;,
      project.setTitle(&#39;Foo Bar&#39;)
    );
    console.log(
      &#39;project.getTitle() ...&#39;,
      project.getTitle()
    );

    console.log(
      &#39;project.createTab() ...&#39;,
      project.createTab()
    );

&lt;!-- language: lang-css --&gt;

    .as-console-wrapper { min-height: 100%!important; top: 0; }

&lt;!-- end snippet --&gt;


And a 2nd iteration&#39;s outcome then already could look like ...


&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    // helper functionality
    function validateTitle(value) {
      if (typeof value !== &#39;string&#39;) {
        // throw new TypeError(&#39;A title has to by a string value.&#39;);
        console.warn(&#39;A title has to by a string value.&#39;);
        return false;
      } else {
        return true;
      }
    }

    // - function based, context aware mixin which applies generically
    //   implemented protected property access at its context and over
    //   shared state.
    function withSharedProtectedProperty(
      state, { key, validation, enumerable = false }
    ) {
      const type = this;

      Reflect.defineProperty(type, key, {
        get: () =&gt; state[key],
        set: value =&gt; {
          if (validation(value)) {
            return state[key] = value;
          }
        },
        enumerable,
      });
    }

    // - function based, context aware mixin which applies tab
    //   specific behavior at its context and over shared state.
    function withSharedTabManagement(state) {
      this.addTab = function addTab(title = &#39;unnamed tab&#39;) {
        // some method that fills the tabs array ... e.g. ...
        return state.tabs.push({ title });
      };
    }

    // `project` factory function.
    function createProject() {
      const state = {
        tabs: [],
        title: &#39;new project&#39;,
      }
      const project = {};

      withSharedProtectedProperty.call(project, state, {
        enumerable: true,
        key: &#39;title&#39;,
        validation: validateTitle,
      });
      withSharedTabManagement.call(project, state);

      return project;
    };

    const project = createProject();
    console.log({ project });

    console.log(
      &#39;project.title ...&#39;, project.title
    );
    console.log(
      &quot;project.title = 1234 ...&quot;, (project.title = 1234)
    );
    console.log(
      &#39;project.title ...&#39;, project.title
    );
    console.log(
      &quot;project.title = &#39;Foo Bar&#39; ...&quot;, (project.title = &#39;Foo Bar&#39;)
    );
    console.log(
      &#39;project.title ...&#39;, project.title
    );

    console.log(
      &#39;project.addTab() ...&#39;, project.addTab()
    );

&lt;!-- language: lang-css --&gt;

    .as-console-wrapper { min-height: 100%!important; top: 0; }

&lt;!-- end snippet --&gt;


One also might have a look at ...

 - [Sharing state when applying Douglas Crockford&#39;s composition pattern](https://stackoverflow.com/a/70037962/2627243)

 - [How to properly replace &#39;extends&#39;, using functional programming?](https://stackoverflow.com/a/61688193/2627243)

 - [How would one implement generic functionality which gets applied across various distinct classes without inheritance?](https://stackoverflow.com/a/76267031/2627243)


_**Edit** ... in order to take into account some of **Bergi&#39;s** comments ..._

&gt; _&quot;[@stuffz] ... You cannot have private variables and shared inherited methods at the same time. It&#39;s just impossible to have a shared closure.&quot;  Bergi_

&gt; _&quot;@PeterSeliger Sharing state between multiple methods of an instance is the easy thing. What is not possible is sharing the methods (via prototype inheritance).&quot;  Bergi_

... where one of my replied sentences stated ...

&gt; _&quot;... (Yet, a `Map` or `WeakMap` based implementation with just a little glue-code should be able to process local/private state via prototypal methods.)&quot;  Peter Seliger_

... any direct implementation of the OP&#39;s original example code, based on the above mentioned glue-code, then should be very close to ...


&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    // project specific module scope.

    // state management via `WeakMap` instance.
    function getState(reference) {
      return projectStates.get(reference);
    }
    const projectStates = new WeakMap;

    // object based mixin of stateful (later prototypal) project behavior.
    const statefulProjectMethods = {
      getTitle() {
        return getState(this).title;
      },
      setTitle(input) {
        return getState(this).title = input;
      },
      createTab(title = &#39;unnamed tab&#39;) {
        // some method that fills the tabs array ... e.g. ...
        return getState(this).tabs.push({ title });
      },
    };

    // `project` factory function.
    /*export */function createProject(protoMethods) {
      const project = Object.create(protoMethods);

      projectStates.set(project, {
        tabs: [],
        title: &#39;new project&#39;,
      });
      return project;
    };
    // end of project specific module scope.


    // another module&#39;s scope
    // import createProject from &#39;/project.js&#39;;
    const project = createProject(statefulProjectMethods);

    console.log(
      &quot;project.hasOwnProperty(&#39;getTitle&#39;) ...&quot;, project.hasOwnProperty(&#39;getTitle&#39;)
    );
    console.log(
      &quot;project.hasOwnProperty(&#39;setTitle&#39;) ...&quot;, project.hasOwnProperty(&#39;setTitle&#39;)
    );
    console.log(
      &quot;project.hasOwnProperty(&#39;createTab&#39;) ...&quot;, project.hasOwnProperty(&#39;createTab&#39;)
    );
    console.log(
      &quot;Object.getPrototypeOf(project) ...&quot;, Object.getPrototypeOf(project)
    );

    console.log(
      &#39;project.getTitle() ...&#39;, project.getTitle()
    );
    console.log(
      &quot;project.setTitle(&#39;Foo Bar&#39;) ...&quot;, project.setTitle(&#39;Foo Bar&#39;)
    );
    console.log(
      &#39;project.getTitle() ...&#39;, project.getTitle()
    );

    console.log(
      &#39;project.createTab() ...&#39;, project.createTab()
    );

&lt;!-- language: lang-css --&gt;

    .as-console-wrapper { min-height: 100%!important; top: 0; }

&lt;!-- end snippet --&gt;


</details>



# 答案2
**得分**: -1

I've translated the code parts for you:

```js
**state monad**

Maybe you're looking for the state monad. The first example in Peter's post is similar to how the state monad works. The primary difference is state changes do not mutate previous state.

Let's first design the initial state of our project -

```js
const initProject = {
  title: "&lt;insert title&gt;",
  tabs: [{ title: "Untitled.txt" }]
}

Now we can write a function that updates the title -

const setTitle = title =>
  State.get().bind(project => // get the state, bind to "project"
    State.put({...project, title}) // set new state
  )

Here's one to create a new tab -

const createTab = title =>
  State.get().bind(project => // get the state, bind to "project"
    State.put({...project, tabs: [...project.tabs, {title}]}) // set new state
  )

The bind function of any monad allows us to read the contained value, perform some computation with it, and return a new monad encapsulating the result of that computation -

console.log(
  setTitle("hello world")
    .bind(() => createTab("2"))
    .bind(() => createTab("3"))
    .execState(initState)
)
{
  "title": "hello world",
  "tabs": [
    {
      "title": "Untitled.txt"
    },
    {
      "title": "2"
    },
    {
      "title": "3"
    }
  ]
}

Let's run a code example to check our progress. Don't worry about understanding State for now -


// state monad
const State = Object.assign(
runState => ({
runState,
bind: f => State(s => {
let {value, state} = runState(s)
return f(value).runState(state)
}),
evalState: s => runState(s).value,
execState: s => runState(s).state,
}),
{
return: y => State(x => ({value: y, state: x})),
get: () => State(x => ({value: x, state: x})),
put: x => State(_ => ({value: null, state: x})),
},
)
// your methods
const setTitle = title =>
State.get().bind(project =>
State.put({...project, title})
)
const createTab = title =>
State.get().bind(project =>
State.put({...project, tabs: [...project.tabs, {title}]})
)
// your program
const initState = {
title: "&lt;insert title&gt;",
tabs: [{ title: "Untitled.txt" }],
}
console.log(
setTitle("hello world")
.bind(() => createTab("2"))
.bind(() => createTab("3"))
.execState(initState)
)
console.log(
createTab("⚠️")
.bind(() => createTab("⚠️"))
.execState(initState)
)

.as-console-wrapper { min-height: 100%; top: 0 }


```js
{
"title": "hello world",
"tabs": [
{
"title": "Untitled.txt"
},
{
"title": "2"
},
{
"title": "3"
}
]
}
{
"title": "&lt;insert title&gt;",
"tabs": [
{
"title": "Untitled.txt"
},
{
"title": "⚠️"
},
{
"title": "⚠️"
}
]
}

too much .bind!

The .bind(project => ...) allows you to read the state, similar to how Promise .then(value => ...) allows you to read the value of a promise, but these closures can be a burden to work with. Much like Promise has async..await, we can implement State.run to eliminate need for .bind closures -

const setTitle = title => State.run(function *() {
  const project = yield State.get() // State.get().bind(project => ...
  return State.put({...project, title})
})

const createTab = title => State.run(function *() {
  const project = yield State.get() // State.get().bind(project => ...
  return State.put({...project, tabs: [...project.tabs, {title}]})
})

The benefit is observed when more .bind calls are saved. If the result of the bind is not needed, you can leave the LHS of yield empty -

State.run(function *() {
  yield setTitle("hello world") // setTitle("hello world").bind(() => ...
  yield createTab("2")          // createTab("2").bind(() => ...
  return createTab("3")
})

This updated demo using State.run produces the same result without the need for .bind closures -


// state monad
const State = Object.assign(
runState => ({
runState,
bind: f => State(s => {
let {value, state} = runState(s)
return f(value).runState(state)
}),
evalState: s => runState(s).value,
execState: s => runState(s).state,
}),
{
return: y => State(x => ({value: y, state: x})),
get: () => State(x => ({value: x, state: x})),
put: x => State(_ => ({value: null, state: x})),
run: e => {
const g = e()
const next = x => {
let {done, value} = g.next(x)
return done ? value : value.bind(next)
}
return next()
},
},
)
// your methods
const setTitle = title => State.run(function *() {
const project = yield State.get()
return State.put({...project, title})
})
const createTab = title => State.run(function *() {
const project = yield State.get()
return State.put({...project, tabs: [...project.tabs, {title}]})
})
// your program
const initProject = {
title: "&lt;insert title&gt;",
tabs: [{ title: "Untitled.txt" }],
}
console.log(State.run(function *() {
yield setTitle("hello world")
yield createTab("2")
return createTab("3")
}).execState(initProject))
console.log(State.run(function *() {
yield createTab("⚠️")
return createTab("⚠️")
}).execState(initProject))

.as-console-wrapper { min-height: 100%; top: 0 }
英文:

state monad

Maybe you're looking for the state monad. The first example in Peter's post is similar to how the state monad works. The primary difference is state changes do not mutate previous state.

Let's first design the initial state of our project -

const initProject = {
  title: &quot;&lt;insert title&gt;&quot;,
  tabs: [{ title: &quot;Untitled.txt&quot; }]
}

Now we can write a function that updates the title -

const setTitle = title =&gt;
  State.get().bind(project =&gt; // get the state, bind to &quot;project&quot;
    State.put({...project, title}) // set new state
  )

Here's one to create a new tab -

const createTab = title =&gt;
  State.get().bind(project =&gt; // get the state, bind to &quot;project&quot;
    State.put({...project, tabs: [...project.tabs, {title}]}) // set new state
  )

The bind function of any monad allows us to read the contained value, perform some computation with it, and return a new monad encapsulating the result of that computation -

console.log(
  setTitle(&quot;hello world&quot;)
    .bind(() =&gt; createTab(&quot;2&quot;))
    .bind(() =&gt; createTab(&quot;3&quot;))
    .execState(initState)
)
{
  &quot;title&quot;: &quot;hello world&quot;,
  &quot;tabs&quot;: [
    {
      &quot;title&quot;: &quot;Untitled.txt&quot;
    },
    {
      &quot;title&quot;: &quot;2&quot;
    },
    {
      &quot;title&quot;: &quot;3&quot;
    }
  ]
}

Let's run a code example to check our progress. Don't worry about understanding State for now -

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// state monad
const State = Object.assign(
runState =&gt; ({
runState,
bind: f =&gt; State(s =&gt; {
let {value, state} = runState(s)
return f(value).runState(state)
}),
evalState: s =&gt; runState(s).value,
execState: s =&gt; runState(s).state,
}),
{
return: y =&gt; State(x =&gt; ({value: y, state: x})),
get: () =&gt; State(x =&gt; ({value: x, state: x})),
put: x =&gt; State(_ =&gt; ({value: null, state: x})),
},
)
// your methods
const setTitle = title =&gt;
State.get().bind(project =&gt;
State.put({...project, title})
)
const createTab = title =&gt;
State.get().bind(project =&gt;
State.put({...project, tabs: [...project.tabs, {title}]})
)
// your program
const initState = {
title: &quot;&lt;insert title&gt;&quot;,
tabs: [{ title: &quot;Untitled.txt&quot; }],
}
console.log(
setTitle(&quot;hello world&quot;)
.bind(() =&gt; createTab(&quot;2&quot;))
.bind(() =&gt; createTab(&quot;3&quot;))
.execState(initState)
)
console.log(
createTab(&quot;⚠️&quot;)
.bind(() =&gt; createTab(&quot;⚠️&quot;))
.execState(initState)
)

<!-- language: lang-css -->

.as-console-wrapper { min-height: 100%; top: 0 }

<!-- end snippet -->

{
  &quot;title&quot;: &quot;hello world&quot;,
  &quot;tabs&quot;: [
    {
      &quot;title&quot;: &quot;Untitled.txt&quot;
    },
    {
      &quot;title&quot;: &quot;2&quot;
    },
    {
      &quot;title&quot;: &quot;3&quot;
    }
  ]
}
{
  &quot;title&quot;: &quot;&lt;insert title&gt;&quot;,
  &quot;tabs&quot;: [
    {
      &quot;title&quot;: &quot;Untitled.txt&quot;
    },
    {
      &quot;title&quot;: &quot;⚠️&quot;
    },
    {
      &quot;title&quot;: &quot;⚠️&quot;
    }
  ]
}

too much .bind!

The .bind(project =&gt; ...) allows you to read the state, similar to how Promise .then(value =&gt; ...) allows you to read the value of a promise, but these closures can be a burden to work with. Much like Promise has async..await, we can implement State.run to eliminate need for .bind closures -

const setTitle = title =&gt; State.run(function *() {
  const project = yield State.get() // State.get().bind(project =&gt; ...
  return State.put({...project, title})
})

const createTab = title =&gt; State.run(function *() {
  const project = yield State.get() // State.get().bind(project =&gt; ...
  return State.put({...project, tabs: [...project.tabs, {title}]})
})

The benefit is observed when more .bind calls are saved. If the result of the bind is not needed, you can leave the LHS of yield empty -

State.run(function *() {
  yield setTitle(&quot;hello world&quot;) // setTitle(&quot;hello world&quot;).bind(() =&gt; ...
  yield createTab(&quot;2&quot;)          // createTab(&quot;2&quot;).bind(() =&gt; ...
  return createTab(&quot;3&quot;)
})

This updated demo using State.run produces the same result without the need for .bind closures -

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// state monad
const State = Object.assign(
runState =&gt; ({
runState,
bind: f =&gt; State(s =&gt; {
let {value, state} = runState(s)
return f(value).runState(state)
}),
evalState: s =&gt; runState(s).value,
execState: s =&gt; runState(s).state,
}),
{
return: y =&gt; State(x =&gt; ({value: y, state: x})),
get: () =&gt; State(x =&gt; ({value: x, state: x})),
put: x =&gt; State(_ =&gt; ({value: null, state: x})),
run: e =&gt; {
const g = e()
const next = x =&gt; {
let {done, value} = g.next(x)
return done ? value : value.bind(next)
}
return next()
},
},
)
// your methods
const setTitle = title =&gt; State.run(function *() {
const project = yield State.get()
return State.put({...project, title})
})
const createTab = title =&gt; State.run(function *() {
const project = yield State.get()
return State.put({...project, tabs: [...project.tabs, {title}]})
})
// your program
const initProject = {
title: &quot;&lt;insert title&gt;&quot;,
tabs: [{ title: &quot;Untitled.txt&quot; }],
}
console.log(State.run(function *() {
yield setTitle(&quot;hello world&quot;)
yield createTab(&quot;2&quot;)
return createTab(&quot;3&quot;)
}).execState(initProject))
console.log(State.run(function *() {
yield createTab(&quot;⚠️&quot;)
return createTab(&quot;⚠️&quot;)
}).execState(initProject))

<!-- language: lang-css -->

.as-console-wrapper { min-height: 100%; top: 0 }

<!-- end snippet -->

{
  &quot;title&quot;: &quot;hello world&quot;,
  &quot;tabs&quot;: [
    {
      &quot;title&quot;: &quot;Untitled.txt&quot;
    },
    {
      &quot;title&quot;: &quot;2&quot;
    },
    {
      &quot;title&quot;: &quot;3&quot;
    }
  ]
}
{
  &quot;title&quot;: &quot;&lt;insert title&gt;&quot;,
  &quot;tabs&quot;: [
    {
      &quot;title&quot;: &quot;Untitled.txt&quot;
    },
    {
      &quot;title&quot;: &quot;⚠️&quot;
    },
    {
      &quot;title&quot;: &quot;⚠️&quot;
    }
  ]
}

related

You may find other useful details about the state monad in an older post.

huangapple
  • 本文由 发表于 2023年7月18日 05:57:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76708326.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定