Search code examples
javascripthaskellfunctional-programmingtype-inferencetraversable

Is explicit type passing not equivalent to type inference (in terms of expressiveness)?


I try to translate traverse/sequenceA to Javascript. Now the following behavior of the Haskell implementation gives me trouble:

traverse (\x -> x) Nothing -- yields Nothing
sequenceA Nothing -- yields Nothing
traverse (\x -> x) (Just [7]) -- yields [Just 7]
sequenceA (Just [7]) -- yields [Just 7]

As a Haskell newbie I wonder why the first expression works at all:

instance Traversable Maybe where
    traverse _ Nothing = pure Nothing
    traverse f (Just x) = Just <$> f x

pure Nothing shouldn't work in this case, since there is no minimal applicative context in which the value could be put in. It seems as if the compiler checks the type of this expression lazily and since mapping the id function over Nothing is a noop, it simply "overlooks" the type error, so to speak.

Here is my Javascript translation:

(Please note, that since Javascirpt's prototype system is insufficient for a couple of type classes and there is no strict type checking anyway, I work with Church encoding and pass type constraints to functions explicitly.)

// minimal type system realized with `Symbol`s

const $tag = Symbol.for("ftor/tag");
const $Option = Symbol.for("ftor/Option");

const Option = {};

// value constructors (Church encoded)

const Some = x => {
  const Some = r => {
    const Some = f => f(x);
    return Some[$tag] = "Some", Some[$Option] = true, Some;
  };

  return Some[$tag] = "Some", Some[$Option] = true, Some;
};

const None = r => {
  const None = f => r;
  return None[$tag] = "None", None[$Option] = true, None;
};

None[$tag] = "None";
None[$Option] = true;

// Traversable

// of/map are explicit arguments of traverse to imitate type inference
// tx[$Option] is just duck typing to enforce the Option type
// of == pure in Javascript

Option.traverse = (of, map) => ft => tx =>
 tx[$Option] && tx(of(None)) (x => map(Some) (ft(x)));

// (partial) Array instance of Applicative

const map = f => xs => xs.map(f);
const of = x => [x];

// helpers

const I = x => x;

// applying

Option.traverse(of, map) (I) (None) // ~ [None]
Option.traverse(of, map) (I) (Some([7])) // ~ [Some(7)]

Obviously, this translation deviates from the Haskell implementation, because I get a [None] where I should get a None. Honestly, this behavior corresponds precisely to my intuition, but I guess intuition isn't that helpful in functional programming. Now my question is

  • did I merely make a rookie mistake?
  • or is explicit type passing not equivalent to type inference (in terms of expressiveness)?

Solution

  • GHCi does not overlook any type error. It defaults an unconstrained Applicative to IO, but you only get this behavior in a GHCi prompt (and not a .hs source file). You can check

    > :t pure Nothing
    pure Nothing :: Applicative f => f (Maybe b)
    

    But still have

    > pure Nothing
    Nothing
    

    Your javascript implementation is fine; you passed in an Applicative instance for arrays and got what is expected.