(() => { 'use strict'; // cusipValid = Dict Char Int -> String -> Bool const cusipValid = charMap => s => { const ns = fromMaybe([])( traverse(flip(lookupDict)(charMap))( chars(s) ) ); return 9 === ns.length && ( last(ns) === rem( 10 - rem( sum(apList( apList([quot, rem])( zipWith(identity)( cycle([identity, x => 2 * x]) )(take(8)(ns)) ) )([10])) )(10) )(10) ); }; //----------------------- TEST ------------------------ // main :: IO () const main = () => { // cusipMap :: Dict Char Int const cusipMap = dictFromList( zip(chars( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#" ))(enumFrom(0))); console.log(unlines(map( apFn( s => validity => s + ' -> ' + str(validity) )(cusipValid(cusipMap)) )([ '037833100', '17275R102', '38259P508', '594918104', '68389X106', '68389X105' ]))); }; //----------------- GENERIC FUNCTIONS ----------------- // Just :: a -> Maybe a const Just = x => ({ type: 'Maybe', Nothing: false, Just: x }); // Nothing :: Maybe a const Nothing = () => ({ type: 'Maybe', Nothing: true, }); // Tuple (,) :: a -> b -> (a, b) const Tuple = a => b => ({ type: 'Tuple', '0': a, '1': b, length: 2 }); // apFn :: (a -> b -> c) -> (a -> b) -> a -> c const apFn = f => // Applicative instance for functions. // f(x) applied to g(x). g => x => f(x)( g(x) ); // apList (<*>) :: [(a -> b)] -> [a] -> [b] const apList = fs => // The sequential application of each of a list // of functions to each of a list of values. xs => fs.flatMap( f => xs.map(f) ); // append (++) :: [a] -> [a] -> [a] // append (++) :: String -> String -> String const append = xs => // A list or string composed by // the concatenation of two others. ys => xs.concat(ys); // chars :: String -> [Char] const chars = s => s.split(''); // cons :: a -> [a] -> [a] const cons = x => xs => Array.isArray(xs) ? ( [x].concat(xs) ) : 'GeneratorFunction' !== xs .constructor.constructor.name ? ( x + xs ) : ( // cons(x)(Generator) function*() { yield x; let nxt = xs.next() while (!nxt.done) { yield nxt.value; nxt = xs.next(); } } )(); // cycle :: [a] -> Generator [a] function* cycle(xs) { const lng = xs.length; let i = 0; while (true) { yield(xs[i]) i = (1 + i) % lng; } } // dictFromList :: [(k, v)] -> Dict const dictFromList = kvs => Object.fromEntries(kvs); // enumFrom :: Enum a => a -> [a] function* enumFrom(x) { // A non-finite succession of enumerable // values, starting with the value x. let v = x; while (true) { yield v; v = succ(v); } } // flip :: (a -> b -> c) -> b -> a -> c const flip = f => 1 < f.length ? ( (a, b) => f(b, a) ) : (x => y => f(y)(x)); // fromEnum :: Enum a => a -> Int const fromEnum = x => typeof x !== 'string' ? ( x.constructor === Object ? ( x.value ) : parseInt(Number(x)) ) : x.codePointAt(0); // fromMaybe :: a -> Maybe a -> a const fromMaybe = def => // A default value if mb is Nothing // or the contents of mb. mb => mb.Nothing ? def : mb.Just; // fst :: (a, b) -> a const fst = tpl => // First member of a pair. tpl[0]; // identity :: a -> a const identity = x => // The identity function. (`id`, in Haskell) x; // last :: [a] -> a const last = xs => // The last item of a list. 0 < xs.length ? xs.slice(-1)[0] : undefined; // length :: [a] -> Int const length = xs => // Returns Infinity over objects without finite // length. This enables zip and zipWith to choose // the shorter argument when one is non-finite, // like cycle, repeat etc (Array.isArray(xs) || 'string' === typeof xs) ? ( xs.length ) : Infinity; // liftA2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c const liftA2 = f => a => b => a.Nothing ? a : b.Nothing ? b : Just(f(a.Just)(b.Just)); // lookupDict :: a -> Dict -> Maybe b const lookupDict = k => dct => { const v = dct[k]; return undefined !== v ? ( Just(v) ) : Nothing(); }; // map :: (a -> b) -> [a] -> [b] const map = f => // The list obtained by applying f // to each element of xs. // (The image of xs under f). xs => ( Array.isArray(xs) ? ( xs ) : xs.split('') ).map(f); // pureMay :: a -> Maybe a const pureMay = x => Just(x); // Given a type name string, returns a // specialised 'pure', where // 'pure' lifts a value into a particular functor. // pureT :: String -> f a -> (a -> f a) const pureT = t => x => 'List' !== t ? ( 'Either' === t ? ( pureLR(x) ) : 'Maybe' === t ? ( pureMay(x) ) : 'Node' === t ? ( pureTree(x) ) : 'Tuple' === t ? ( pureTuple(x) ) : pureList(x) ) : pureList(x); // pureTuple :: a -> (a, a) const pureTuple = x => Tuple('')(x); // quot :: Int -> Int -> Int const quot = n => m => Math.floor(n / m); // rem :: Int -> Int -> Int const rem = n => m => n % m; // snd :: (a, b) -> b const snd = tpl => tpl[1]; // str :: a -> String const str = x => x.toString(); // succ :: Enum a => a -> a const succ = x => { const t = typeof x; return 'number' !== t ? (() => { const [i, mx] = [x, maxBound(x)].map(fromEnum); return i < mx ? ( toEnum(x)(1 + i) ) : Error('succ :: enum out of range.') })() : x < Number.MAX_SAFE_INTEGER ? ( 1 + x ) : Error('succ :: Num out of range.') }; // sum :: [Num] -> Num const sum = xs => // The numeric sum of all values in xs. xs.reduce((a, x) => a + x, 0); // take :: Int -> [a] -> [a] // take :: Int -> String -> String const take = n => // The first n elements of a list, // string of characters, or stream. xs => 'GeneratorFunction' !== xs .constructor.constructor.name ? ( xs.slice(0, n) ) : [].concat.apply([], Array.from({ length: n }, () => { const x = xs.next(); return x.done ? [] : [x.value]; })); // The first argument is a sample of the type // allowing the function to make the right mapping // toEnum :: a -> Int -> a const toEnum = e => x => ({ 'number': Number, 'string': String.fromCodePoint, 'boolean': Boolean, 'object': v => e.min + v } [typeof e])(x); // traverse :: (Applicative f) => (a -> f b) -> [a] -> f [b] const traverse = f => // Collected results of mapping each element // of a structure to an action, and evaluating // these actions from left to right. xs => 0 < xs.length ? (() => { const vLast = f(xs.slice(-1)[0]), t = vLast.type || 'List'; return xs.slice(0, -1).reduceRight( (ys, x) => liftA2(cons)(f(x))(ys), liftA2(cons)(vLast)(pureT(t)([])) ); })() : [ [] ]; // uncons :: [a] -> Maybe (a, [a]) const uncons = xs => { // Just a tuple of the head of xs and its tail, // Or Nothing if xs is an empty list. const lng = length(xs); return (0 < lng) ? ( Infinity > lng ? ( Just(Tuple(xs[0])(xs.slice(1))) // Finite list ) : (() => { const nxt = take(1)(xs); return 0 < nxt.length ? ( Just(Tuple(nxt[0])(xs)) ) : Nothing(); })() // Lazy generator ) : Nothing(); }; // uncurry :: (a -> b -> c) -> ((a, b) -> c) const uncurry = f => // A function over a pair, derived // from a curried function. x => ((...args) => { const xy = 1 < args.length ? ( args ) : args[0]; return f(xy[0])(xy[1]); })(x); // unlines :: [String] -> String const unlines = xs => // A single string formed by the intercalation // of a list of strings with the newline character. xs.join('\n'); // zip :: [a] -> [b] -> [(a, b)] const zip = xs => // Use of `take` and `length` here allows for zipping with non-finite // lists - i.e. generators like cycle, repeat, iterate. ys => { const lng = Math.min(length(xs), length(ys)), vs = take(lng)(ys); return take(lng)(xs).map( (x, i) => Tuple(x)(vs[i]) ); }; // Use of `take` and `length` here allows zipping with non-finite lists // i.e. generators like cycle, repeat, iterate. // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] const zipWith = f => xs => ys => { const lng = Math.min(length(xs), length(ys)); return Infinity > lng ? (() => { const as = take(lng)(xs), bs = take(lng)(ys); return Array.from({ length: lng }, (_, i) => f(as[i])( bs[i] )); })() : zipWithGen(f)(xs)(ys); }; // zipWithGen :: (a -> b -> c) -> // Gen [a] -> Gen [b] -> Gen [c] const zipWithGen = f => ga => gb => { function* go(ma, mb) { let a = ma, b = mb; while (!a.Nothing && !b.Nothing) { let ta = a.Just, tb = b.Just yield(f(fst(ta))(fst(tb))); a = uncons(snd(ta)); b = uncons(snd(tb)); } } return go(uncons(ga), uncons(gb)); }; // MAIN --- return main(); })();