Skip to content

Instantly share code, notes, and snippets.

@Lucifier129
Last active May 4, 2020 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lucifier129/65b7eac1ad7895c91c8b10bcbe83c7af to your computer and use it in GitHub Desktop.
Save Lucifier129/65b7eac1ad7895c91c8b10bcbe83c7af to your computer and use it in GitHub Desktop.
functional oop
// option type
interface OptionVisitor<A, B> {
None: () => B;
Some: (a: A) => B;
}
type OptionData<A> =
| {
kind: 'None';
}
| {
kind: 'Some';
value: A;
};
interface Option<A> {
case: <B>(visitor: OptionVisitor<A, B>) => B;
map: <B>(f: (a: A) => B) => Option<B>;
data: () => OptionData<A>;
}
const fromOptionData = <T>(data: OptionData<T>): Option<T> => {
if (data.kind === 'None') {
return None();
}
if (data.kind === 'Some') {
return Some(data.value);
}
};
// data constructors of option type
const None = <A = any>(): Option<A> => {
return {
case: (visitor) => {
return visitor.None();
},
map: () => {
return None();
},
data: () => {
return {
kind: 'None',
};
},
};
};
const Some = <A>(a: A): Option<A> => {
return {
case: (visitor) => {
return visitor.Some(a);
},
map: (f) => {
return Some(f(a));
},
data: () => {
return {
kind: 'Some',
value: a,
};
},
};
};
// result type
interface ResultVisitor<A, B, T> {
Ok: (a: A) => T;
Err: (b: B) => T;
}
type ResultData<A, B> = { kind: 'Ok'; value: A } | { kind: 'Err'; value: B };
interface Result<A, B> {
case: <T>(visitor: ResultVisitor<A, B, T>) => T;
map: <T>(f: (a: A) => T) => Result<T, B>;
data: () => ResultData<A, B>;
}
// data constructors of result type
const fromResultData = <A, B>(data: ResultData<A, B>): Result<A, B> => {
switch (data.kind) {
case 'Ok':
return Ok(data.value);
case 'Err':
return Err(data.value);
}
};
const Ok = <A>(a: A): Result<A, any> => {
return {
case: (visitor) => {
return visitor.Ok(a);
},
map: (f) => {
return Ok(f(a));
},
data: () => {
return {
kind: 'Ok',
value: a,
};
},
};
};
const Err = <B>(b: B): Result<any, B> => {
return {
case: (visitor) => {
return visitor.Err(b);
},
map: () => {
return Err(b);
},
data: () => {
return {
kind: 'Err',
value: b,
};
},
};
};
// tree type
interface TreeVisitor<T, TT> {
node: (value: T, ...children: Tree<T>[]) => TT;
}
interface TreeData<T> {
kind: 'Node';
value: T;
children: TreeData<T>[];
}
interface Tree<T> {
case: <TT>(visitor: TreeVisitor<T, TT>) => TT;
map: <TT>(f: (a: T) => TT) => Tree<TT>;
data: () => TreeData<T>;
}
// data constructors of tree type
const fromTreeData = <T>(data: TreeData<T>): Tree<T> => {
if (data.kind === 'Node') {
return node(data.value, ...data.children.map(fromTreeData));
}
};
const node = <T>(a: T, ...children: Tree<T>[]): Tree<T> => {
return {
case: (visitor) => {
return visitor.node(a, ...children);
},
map: (f) => {
let newValue = f(a);
let newChildren = children.map((child) => child.map(f));
return node(newValue, ...newChildren);
},
data: () => {
return {
kind: 'Node',
value: a,
children: children.map((child) => child.data()),
};
},
};
};
// test
const incre = (n: number) => n + 1;
const toCount = (n: number) => `count: ${n}`;
const t1 = None().map(incre).map(toCount).data();
const t2 = Some(1).map(incre).map(toCount).data();
const t3 = Ok(2).map(incre).map(toCount).data();
const t4 = Err('you bad bad').map(incre).map(toCount).data();
const tree0 = node(1, node(2, node(3), node(4), node(5)), node(6));
const tree1 = tree0.map(incre).map(toCount);
type TT = [typeof tree0, typeof tree1];
console.log({
t1,
t2,
t3,
t4,
});
console.log('tree0', JSON.stringify(tree0.data(), null, 2));
console.log('tree1', JSON.stringify(tree1.data(), null, 2));
console.log(
'equality testing of tree0',
JSON.stringify(tree0.data()) ===
JSON.stringify(fromTreeData(tree0.data()).data())
);
console.log(
'equality testing of tree1',
JSON.stringify(tree1.data()) ===
JSON.stringify(fromTreeData(tree1.data()).data())
);
@Lucifier129
Copy link
Author

todos

interface TodoData {
  id: number;
  content: string;
  completed: boolean;
}

interface Todo {
  data: () => TodoData;
  is: (id: number) => boolean;
  isCompleted: () => boolean;
  toggle: () => Todo;
  setCompleted: (completed: boolean) => Todo;
  setContent: (content: string) => Todo;
}

let uid = 0;
const Todo = ({
  id = uid++,
  content = '',
  completed = false,
}: Partial<TodoData>): Todo => {
  let data = {
    id,
    content,
    completed,
  };
  return {
    data: () => {
      return data;
    },
    is: (todoId) => {
      return todoId === id;
    },
    isCompleted: () => {
      return completed;
    },
    toggle: () => {
      return Todo({
        ...data,
        completed: !completed,
      });
    },
    setCompleted: (completed) => {
      return Todo({
        ...data,
        completed,
      });
    },
    setContent: (content) => {
      return Todo({
        ...data,
        content,
      });
    },
  };
};

type FilterType = 'all' | 'active' | 'completed';

interface Todos {
  data: () => TodoData[];
  addTodo: (content: any) => Todos;
  removeTodo: (todoId: number) => Todos;
  filterTodos: (filterType: FilterType) => Todos;
  toggleTodo: (todoId: number) => Todos;
  updateTodo: (todoId: number, conent: string) => Todos;
  clearCompleted: () => Todos;
  toggleAll: () => Todos;
}

const Todos = (todos: Todo[] = []): Todos => {
  return {
    data: () => {
      return todos.map((todo) => todo.data());
    },
    addTodo: (content) => {
      return Todos([...todos, Todo({ content: '' + content })]);
    },
    removeTodo: (todoId) => {
      return Todos(todos.filter((todo) => !todo.is(todoId)));
    },
    filterTodos: (filterType) => {
      return Todos(
        todos.filter((todo) => {
          switch (filterType) {
            case 'all':
              return true;
            case 'active':
              return !todo.isCompleted();
            case 'completed':
              return todo.isCompleted();
            default:
              return true;
          }
        })
      );
    },
    toggleTodo: (todoId) => {
      return Todos(
        todos.map((todo) => {
          if (todo.is(todoId)) {
            return todo.toggle();
          } else {
            return todo;
          }
        })
      );
    },
    updateTodo: (todoId, content) => {
      return Todos(
        todos.map((todo) => {
          if (todo.is(todoId)) {
            return todo.setContent(content);
          } else {
            return todo;
          }
        })
      );
    },
    clearCompleted: () => {
      return Todos(todos.filter((todo) => !todo.isCompleted()));
    },
    toggleAll: () => {
      let isAllCompleted = todos.every((todo) => todo.isCompleted());
      return Todos(todos.map((todo) => todo.setCompleted(isAllCompleted)));
    },
  };
};

let todos = Todos()
  .addTodo(1)
  .addTodo(2)
  .addTodo(3)
  .addTodo(4)
  .addTodo(5)
  .toggleTodo(3)
  .updateTodo(2, 'adfa');

console.log('todos', JSON.stringify(todos.data(), null, 2));

let todo = Todo({ content: 'my todo' }).toggle().setContent('change todo');

console.log('todo', JSON.stringify(todo.data(), null, 2));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment