makeCacheable = realFn => {
	/**
	 * @template T
	 * @typedef {Map<string, T>} CacheItem
	 */
	/** @type {WeakMap<AssociatedObjectForCache, CacheItem<T>>} */
	const cache = new WeakMap();

	/**
	 * @param {AssociatedObjectForCache} associatedObjectForCache an object to which the cache will be attached
	 * @returns {CacheItem<T>} cache item
	 */
	const getCache = associatedObjectForCache => {
		const entry = cache.get(associatedObjectForCache);
		if (entry !== undefined) return entry;
		/** @type {Map<string, T>} */
		const map = new Map();
		cache.set(associatedObjectForCache, map);
		return map;
	};

	/** @type {MakeCacheableResult<T> & { bindCache: BindCache<T> }} */
	const fn = (str, associatedObjectForCache) => {
		if (!associatedObjectForCache) return realFn(str);
		const cache = getCache(associatedObjectForCache);
		const entry = cache.get(str);
		if (entry !== undefined) return entry;
		const result = realFn(str);
		cache.set(str, result);
		return result;
	};

	/** @type {BindCache<T>} */
	fn.bindCache = associatedObjectForCache => {
		const cache = getCache(associatedObjectForCache);
		/**
		 * @param {string} str string
		 * @returns {T} value
		 */
		return str => {
			const entry = cache.get(str);
			if (entry !== undefined) return entry;
			const result = realFn(str);
			cache.set(str, result);
			return result;
		};
	};

	return fn;
}
@template T
@param {((value: string) => T)} realFn real function
@returns {MakeCacheableResult<T> & { bindCache: BindCache<T> }} cacheable function