sanctuary-js / sanctuary

:see_no_evil: Refuge from unsafe JavaScript
https://sanctuary.js.org
MIT License
3.04k stars 94 forks source link

Using ADT created by typescript with sanctuary? #654

Closed coodoo closed 4 years ago

coodoo commented 4 years ago

Let's say I've created a Product type named User as below, how could one use map, reduce, traverse...methods over it?

In essence, is there something like auto deriving as in haskell? Thanks.

type User = {
  first: string,
  age: number,
  registered: boolean
}
davidchambers commented 4 years ago

User is not parameterized, so there is no way to define a functor instance. You would need at least one of the fields to be parameterized (e.g. User a b). Here is a contrived example:

'use strict';

const {create}      = require ('sanctuary');
const $             = require ('sanctuary-def');
const type          = require ('sanctuary-type-identifiers');

//    userTypeIdent :: String
const userTypeIdent = 'my-package/User@1';

//    UserType :: Type -> Type
const UserType = $.UnaryType
  ('User')
  ('https://example.com/my-package#User')
  ([])
  (x => type (x) === userTypeIdent)
  (user => [user.value]);

const S = create ({
  checkTypes: true,
  env: $.env.concat ([UserType ($.Unknown)]),
});

const User$prototype = {
  'constructor': {
    '@@type': userTypeIdent,
  },
  '@@show': function() {
    return 'User (' + S.show (this.first) + ')' +
               ' (' + S.show (this.age) + ')' +
               ' (' + S.show (this.registered) + ')' +
               ' (' + S.show (this.value) + ')';
  },
  'fantasy-land/map': function(f) {
    return Object.assign (Object.create (User$prototype), {
      first: this.first,
      age: this.age,
      registered: this.registered,
      value: f (this.value),
    });
  },
};

//    User :: String -> Number -> Boolean -> a -> User a
const User = first => age => registered => value =>
  Object.assign (Object.create (User$prototype), {
    first,
    age,
    registered,
    value,
  });

//    user :: User (Array String)
const user = User ('David') (35) (false) (['foo', 'bar', 'baz']);

//    user2 :: User String
const user2 = S.map (S.unwords) (user);  // User ("David") (35) (false) ("foo bar baz")

How do you imagine map operating on members of your User type, @coodoo?

coodoo commented 4 years ago

Thanks @davidchambers for the detailed explanation, it was quite eye opening.

After tinkering around with sanctuary a bit more yesterday I think my real questions are below:

  1. Is it possible to do data modeling with interface and union type (in the sense of Product and Sum type in haskell) first, then use sanctuary for functional composition and computation?

  2. I actually give #1 a try and find the main issue while running in browser, messages printed out by console.log is way too verbose than it's nodejs counter part, say in nodejs it will be Just (2) but in chrome there will be a whole lot of other props too. Is there a way to make console.log in output in browser as clean as in node?

davidchambers commented 4 years ago

Is it possible to do data modeling with interface and union type (in the sense of Product and Sum type in haskell) first, then use sanctuary for functional composition and computation?

I believe so.

Is there a way to make console.log in output in browser as clean as in node?

I suggest using console.log (S.show (x)). :)