mostly-adequate-guide
mostly-adequate-guide copied to clipboard
Chapter 10, Part Laws. We don't need liftA2(concat).
In chapter 10 on part laws. there is a code that demonstrates "applicatives are close under composition".
const tOfM = compose(Task.of, Maybe.of);
liftA2(liftA2(concat), tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
// Task(Maybe(Rainy Days and Mondays always get me down))
I think we could remove liftA2 wrapper for concat
function, It's useless.
liftA2(concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
EDIT2: Nvm, just saw this https://github.com/MostlyAdequate/mostly-adequate-guide/pull/252, and realized that I needed to use the "pumped up" version of curry
, as defined in exercises/support.js
:
// NOTE A slightly pumped up version of `curry` which also keeps track of
// whether a function was called partially or with all its arguments at once.
// This is useful to provide insights during validation of exercises.
function curry(fn) {
assert(
typeof fn === 'function',
typeMismatch('function -> ?', [getType(fn), '?'].join(' -> '), 'curry'),
);
const arity = fn.length;
return namedAs(fn.name, function $curry(...args) {
$curry.partially = this && this.partially;
if (args.length < arity) {
return namedAs(fn.name, $curry.bind({ partially: true }, ...args));
}
return fn.call(this || { partially: false }, ...args);
});
}
EDIT: So I finally got it working in javascript, but liftA2(concat)
needed an additional curry, for reasons I do not understand.
As we can see below, I had to liftA2(curry(liftA2(concat)), x, y)
in order for lifting twice to work,
otherwise there is an error in liftA2
when calling .ap
.
#+begin_src js :noweb no-export :results code
<<js maybe applicative>>
<<js task applicative>>
<<js compose>>
<<js liftA2>>
<<js identity>>
<<js concat>>
const tOfM = compose(Task.of, Maybe.of)
const x = tOfM("Hello ")
const y = tOfM("World")
const curriedAndLiftedConcat = curry(liftA2(concat))
const z = liftA2(curriedAndLiftedConcat, x, y)
////// ERROR //////
// NOTE this curry-less function throws an error, why???
const onlyLiftedConcat = liftA2(concat)
const a = liftA2(onlyLiftedConcat, x, y)
let afork
try {
afork = a.fork(id, id)
} catch (error) {
afork = error.message
}
////// ERROR //////
return {
// x,
// xfork: x.fork(id, id),
// y,
// yfork: y.fork(id, id),
z,
zfork: z.fork(id, id),
a,
afork
}
#+end_src
#+RESULTS:
#+begin_src js
{
z: Task { fork: [Function (anonymous)] },
zfork: Maybe { val: 'Hello World' },
a: Task { fork: [Function (anonymous)] },
afork: "Cannot read properties of undefined (reading 'map')"
}
#+end_src
Dependencies implementations:
Maybe
#+name: js maybe applicative
#+begin_src js
class Maybe {
constructor(val) {
this.val = val
}
static of(val) {
return new Maybe(val)
}
get isNothing() {
return this.val === null || this.val === undefined
}
map(fn) {
return this.isNothing ? this : new Maybe(fn(this.val))
}
join() {
return this.isNothing ? this : this.val
}
chain(fn) {
return this.map(fn).join()
}
ap(f) {
return f.map(this.val)
}
}
#+end_src
Task
#+name: js task applicative
#+begin_src js :noweb no-export
class Task {
constructor(fn) {
this.fork = fn
}
static of(val) {
return new Task((_reject, result) => result(val))
}
map(fn) {
<<js compose>>
return new Task(
(reject, result) => this.fork(
reject,
compose(result, fn)
)
)
}
join() {
return new Task((reject, result) => this.fork(
reject,
x => x.fork(reject, result)
))
}
chain(fn) {
return this.map(fn).join()
}
ap(f) {
return new Task((reject, resolve) => this.fork(
reject,
x => f.map(x).fork(reject, resolve)
));
}
}
#+end_src
compose()
#+name: js compose
#+name: js compose functional
#+begin_src js
const compose = (...fs) => (...args) => {
return fs.reduceRight(
(result, f) => [f.apply(null, result)],
args
)[0]
// alternatively:
// return fs.reduceRight((result, f) => f.apply(null, [].concat(result)), args)
}
#+end_src
liftA2()
#+name: js liftA2
#+begin_src js :noweb no-export
<<js curry>>
const liftA2 = curry((g, f1, f2) => f1.map(g).ap(f2))
#+end_src
curry()
#+name: js curry
#+begin_src js
const curry = (f) => {
const arity = f.length
return (...args) => {
if (args.length < arity) {
return f.bind(null, ...args)
}
return f.apply(null, args)
}
}
#+end_src
id()
#+name: js identity
#+begin_src js
const id = (x) => x
#+end_src
concat()
#+name: js concat
#+begin_src js
const concat = curry((a, b) => a.concat(b))
#+end_src
=== ORIGINAL QUESTION ===
Also I'd like to ask, what's the signature of concat
here?
When I try to analyze it, and assume that concat :: String -> String -> String
(from https://mostly-adequate.gitbook.io/mostly-adequate-guide/appendix_c#concat),
then liftA2(concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down'))
doesn't work because:
by definition, const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2))
(from https://mostly-adequate.gitbook.io/mostly-adequate-guide/appendix_a#lifta2),
which evaluates to tOfM('Rainy Days and Mondays').map(concat)
,
which means concat
expects a String
but is given a Maybe String
, since tOfM :: a -> Task (Maybe a)
.
Is it correct to assume that in this example concat :: Maybe String -> Maybe String -> Maybe String
?