Skip to content

Commit

Permalink
got typescript working and some of the most basic types and tests runnin
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Chen committed May 17, 2017
1 parent 6a61ec7 commit bfb5b79
Show file tree
Hide file tree
Showing 15 changed files with 304 additions and 24 deletions.
22 changes: 19 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Extension to redux to generalize reducers from just functions to haskell arrows via extensible functions",
"main": "src/index.js",
"scripts": {
"test": "jest --notify"
"test": "jest --notify",
"build": "webpack"
},
"repository": {
"type": "git",
Expand All @@ -16,23 +17,38 @@
"declarative",
"properties"
],
"files": ["src", "lib", "dist"],
"author": "Thomas Chen",
"license": "MIT",
"bugs": {
"url": "https://github.com/foxnewsnetwork/redux-arrows/issues"
},
"homepage": "https://github.com/foxnewsnetwork/redux-arrows#readme",
"devDependencies": {
"awesome-typescript-loader": "^3.1.3",
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-jest": "^19.0.0",
"babel-plugin-syntax-decorators": "^6.13.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"jest": "^19.0.2",
"regenerator-runtime": "^0.10.3"
"regenerator-runtime": "^0.10.3",
"source-map-loader": "^0.2.1",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"typescript": "^2.3.2",
"webpack": "^2.5.1"
},
"jest": {
"testRegex": "(/test/.*\\.spec.js)$"
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "/test/.*\\.(ts|tsx|js)$"
}
}
28 changes: 20 additions & 8 deletions src/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ interface Tuple<A,B> {
* https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Arrow.html
*
* The gist of this is that arrows are wrappers that extends categories
*
* We have to split up Haskell's arrow module into the static interface
* and the instance interface because typescript doesn't support static
* interfaces because, at the end of the day, typescript's types were made
* for meme languages like Java, and not master-race languages like haskell
*/
interface Arrow<B, C> {
export interface Arrow<B, C> {
/**
* Basic Arrow Interface
*/
arr(fn: Fn<B,C>): Arrow<B, C>;
first<D>(a: Arrow<B, C>): Arrow<Tuple<B, D>, Tuple<C, D>>;
second<D>(a: Arrow<B, C>): Arrow<Tuple<D, B>, Tuple<D, C>>;
split(a1: Arrow<B, C>, a2: Arrow<B, C>): Arrow<Tuple<B, B>, Tuple<C, C>>;
fanout(a1: Arrow<B, C>, a2: Arrow<B, C>): Arrow<B, Tuple<C, C>>;
arr(fn: Fn<B,C>): ArrowInstance<B, C>;
first<D>(a: ArrowInstance<B, C>): ArrowInstance<Tuple<B, D>, Tuple<C, D>>;
second<D>(a: ArrowInstance<B, C>): ArrowInstance<Tuple<D, B>, Tuple<D, C>>;
split(a1: ArrowInstance<B, C>, a2: ArrowInstance<B, C>): ArrowInstance<Tuple<B, B>, Tuple<C, C>>;
fanout(a1: ArrowInstance<B, C>, a2: ArrowInstance<B, C>): ArrowInstance<B, Tuple<C, C>>;

/**
* Category Interface
Expand All @@ -33,7 +38,14 @@ interface Arrow<B, C> {
* differentiating between >>> composition and (.)
* composition. Nor do we implement <<< composition
*/
id: Arrow<B, C>;
compose<D>(a1: Arrow<B,C>, a2: Arrow<C, D>): Arrow<B,D>;
id: ArrowInstance<B, C>;
compose<D>(a1: ArrowInstance<B,C>, a2: ArrowInstance<C, D>): ArrowInstance<B,D>;
}

export interface ArrowInstance<B,C> {
compose<D>(a: ArrowInstance<C,D>): ArrowInstance<B,D>;
first<D>(): ArrowInstance<Tuple<B,D>, Tuple<C,D>>;
second<D>(): ArrowInstance<Tuple<D,B>, Tuple<D,C>>;
split(a: ArrowInstance<B,C>): ArrowInstance<Tuple<B, B>, Tuple<C,C>>;
fanout(a: ArrowInstance<B,C>): ArrowInstance<B, Tuple<C,C>>;
}
6 changes: 0 additions & 6 deletions src/extensible-function.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface PureFunction<A,B> {
(a: A): B
}
File renamed without changes.
34 changes: 34 additions & 0 deletions src/kleisli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Arrow, ArrowInstance } from './arrow';
import { MonadInstance } from './monad';
import { PureFunction } from './function';

export interface Kleisli<A, B, M extends MonadInstance<B> > extends Arrow<A,B> {
runKleisli(k: KleisliInstance<A, B, M>, a: A): M;

/**
* Equivalent to ^>> from haskell
* This lifts a pure function to an arrow
* then appends it to an arrow. For example:
*
* arrow >>^ pureFun
*
* would be equivalent to
*
* appendPure(arrow, pureFun)
*
* Of course, because we aren't haskell, we can infix operators.
*
* Furthermore, we'd like to constraing the MonadInstance, but we
* can't because no haskell typeclass... so just don't switch
* monads mid-way through and we should be fine (lol)
*/
appendPure<C, MC extends MonadInstance<C>>(k: KleisliInstance<A, B, M>, fn: PureFunction<B, C>): KleisliInstance<B, C, MC>

prependPure<C, MC extends MonadInstance<C>>(fn: PureFunction<A, B>, k: KleisliInstance<B, C, MC>): KleisliInstance<A, B, M>
}

export interface KleisliInstance<A, B, MB extends MonadInstance<B>> extends ArrowInstance<A,B> {
runKleisli(a: A): MB;
appendPure<C, MC extends MonadInstance<C>>(fn: PureFunction<B, C>): KleisliInstance<B, C, MC>
prependPure<Z>(fn: PureFunction<Z, A>): KleisliInstance<Z, B, MB>
}
35 changes: 35 additions & 0 deletions src/monad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

export interface Return<A,B> {
(a: A): MonadInstance<B>
}

/**
* The static Monad interface. We have to separate this from
* the instance variation because lol javascript
*
* We also have to append M to everything to avoid accidental
* name collisons with native functions like `bind` and `return`
*/
export interface Monad<A,B> {
/**
* >>=
*/
bindM(m: MonadInstance<A>, fn: Return<A,B>): MonadInstance<B>
/**
* >>
*/
thruM(m1: MonadInstance<A>, m2: MonadInstance<B>): MonadInstance<B>
/**
* return is a keyword in javascript, so we append M
*/
returnM(a: A): MonadInstance<B>
/**
* For completion reasons, we put this here, but I won't use it
*/
failM(str: String): MonadInstance<A>
}

export interface MonadInstance<A> {
bindM<B>(fn: Return<A,B>): MonadInstance<B>
thruM<B>(m: MonadInstance<B>): MonadInstance<B>
}
56 changes: 49 additions & 7 deletions src/reducer-arrow.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
type Action = any;
type State = any;
interface ActionState {
action: Action;
state: State;
import { Kleisli, KleisliInstance } from './kleisli';
import { ActionState, ReduxState } from './redux';
import { MonadInstance } from './monad'

class DepKeyState implements MonadInstance<ReduxState> {
reduxState: ReduxState
changedKeys: string[]

constructor(reduxState, changedKeys=[]) {
this.reduxState = reduxState;
this.changedKeys = changedKeys
}

bindM(fn) {
const { reduxState, changedKeys } = fn(this.reduxState);
return new DepKeyState(reduxState, changedKeys);
}
thruM(fn) {
return fn(this.reduxState);
}
}



interface Transformer {
(ks: KeyState): KeyState
}
class ReducerArrow extends ExtensibleFunction {

interface Updater {
(s: ReduxState, k: KeyState): ReduxState
}
class StaticReducerArrow implements Arrow<ActionState, State> {

function objectAssign(reduxState, keyState) {
return Object.keys(keyState).reduce((state, key) => Object.assign({}, ), reduxState);
}

class ReducerArrow implements KleisliInstance<ActionState, ReduxState, DepKeyState> {
readingKeys: string[]
writingKeys: string[]
transformer: Transformer
updater: Updater
constructor(transformer, updater=objectAssign, readingKeys=[], writingKeys=[]) {

}
runKleisli(actionState) {

}
appendPure() {

}
prependPure() {

}
}
11 changes: 11 additions & 0 deletions src/redux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Action = any;
export type ReduxState = any;

export interface ActionState {
action: Action;
state: ReduxState;
}

export interface Reducer {
(state: ReduxState, action: Action): ReduxState
}
36 changes: 36 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

export interface Hash {
[propName: string]: any
}

/**
* Similar to ember set, except copies instead of
* mutates
* @param obj
* @param key
* @param val
*/
export function set(obj: Hash, keyString: string, val: any): Hash {
const keys = parseKeys(keyString);
return setCore(keys, val, obj);
}

function setCore(keys: string[], val: any, obj?: Hash): Hash {
const hash = obj || {};

if (keys.length === 1) {
const [key] = keys;
return Object.assign({}, hash, { [key]: val });
} else if (keys.length > 1) {
const [key, ...tailKeys] = keys;
return Object.assign({}, obj, {
[key]: setCore(tailKeys, val, hash[key])
})
} else {
return hash;
}
}

export function parseKeys(keyStr: string): string[] {
return keyStr.split('.');
}
File renamed without changes.
48 changes: 48 additions & 0 deletions test/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { set } from '../src/utils';

describe('utils', () => {
it('should be propertly imported', () => {
expect(set).toBeInstanceOf(Function)
})

describe('set', () => {
const obj = { dog: 1 };
let obj2;
beforeAll(() => {
obj2 = set(obj, 'cat', 2)
})
it('should immutably set to the object', () => {
expect(obj2).not.toBe(obj)
})
it('should not affect obj1', () => {
expect(obj).toMatchObject({ dog: 1 })
})
it('should properly change obj2', () => {
expect(obj2).toMatchObject({ dog: 1, cat: 2 })
})

describe('deep set', () => {
let obj3;
beforeAll(() => {
obj3 = set(obj2, 'bird.plane.man', 44)
})
it('should not alter obj1', () => {
expect(obj).toMatchObject({ dog: 1 })
})
it('should not alter obj2 either', () => {
expect(obj2).toMatchObject({ dog: 1, cat: 2 })
})
it('should properly create a deep object', () => {
expect(obj3).toMatchObject({
dog: 1,
cat: 2,
bird: {
plane: {
man: 44
}
}
})
})
})
})
})
15 changes: 15 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"strictNullChecks": true,
"lib": ["es2017"],
"module": "es6",
"jsx": "react",
"target": "es5",
"allowJs": true
},
"include": [
"./src/"
]
}
9 changes: 9 additions & 0 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {},
"rulesDirectory": []
}
25 changes: 25 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
// change to .tsx if necessary
entry: './src/index.ts',
output: {
filename: './dist/index.js'
},
resolve: {
// changed from extensions: [".js", ".jsx"]
extensions: [".ts", ".tsx", ".js", ".jsx"]
},
module: {
rules: [
// changed from { test: /\.jsx?$/, use: { loader: 'babel-loader' } },
{ test: /\.(t|j)sx?$/, use: { loader: 'awesome-typescript-loader' } },
// newline - add source-map support
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
externals: {
"react": "React",
"react-dom": "ReactDOM",
},
// newline - add source-map support
devtool: "source-map"
}

0 comments on commit bfb5b79

Please sign in to comment.