Skip to content

Commit

Permalink
Merge branch 'release/1.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
12joan committed Jun 3, 2020
2 parents 826c386 + 89cef37 commit 2cc3908
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 48 deletions.
105 changes: 103 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ doSomethingWithMaybe[ Nothing ] #=> "You got nothing."

#### 3.3.1 Built-in functions

##### 3.3.1.1 `State`

- `State :: (s -> (a, s)) -> State s a`

Constructs a `State` object with the given function. Note that since Ruby does not support tuples, you are expected to use an `Array` as the return value of the function.
Expand All @@ -470,7 +472,7 @@ doSomethingWithMaybe[ Nothing ] #=> "You got nothing."
```

Often, composing `State` objects using the functions listed below is preferable to calling the `State` constructor directly.

- `pureState :: a -> State s a`

Constructs a `State` object which sets the result and leaves the state unchanged.
Expand Down Expand Up @@ -610,6 +612,105 @@ doSomethingWithMaybe[ Nothing ] #=> "You got nothing."
execState[ operation ][ "initial state" ] #=> "final state"
```

##### 3.3.1.2 `StateIO`

- `StateIO :: (s -> IO (a, s)) -> StateIO s IO a`

`IO` variety of `State`.

```ruby
include Rubio::State::Core
include Rubio::IO::Core
operation = StateIO[
->(s) {
println["The current state is #{s.inspect}"] >> pureIO[ ["result", s.reverse] ]
}
]
io = runStateT[operation][ [1, 2, 3] ] #=> IO
io.perform!
# The current state is [1, 2, 3]
#=> ["result", [3, 2, 1]]
```

- `liftIO :: IO a -> StateIO s IO a`

Lift an `IO` into the `StateIO` monad. Useful for performing `IO` operations during a computation.

```ruby
include Rubio::State::Core
include Rubio::IO::Core
operation = (liftIO << println)["Hello world!"]
io = execStateT[operation][ [1, 2, 3] ] #=> IO
io.perform!
# Hello world!
#=> [3, 2, 1]
```

- `pureStateIO :: a -> StateIO s IO a`

`IO` variety of `pureState`.

- `getIO :: StateIO s IO s`

`IO` variety of `get`.

- `putIO :: s -> StateIO s IO ()`

`IO` variety of `put`.

- `modifyIO :: (s -> s) -> StateIO s IO ()`

`IO` variety of `modify.`

- `getsIO :: (s -> a) -> StateIO s a`

`IO` variety of `gets`.

- `runStateT -> StateIO s IO a -> s -> IO (a, s)`

`IO` variety of `runState`.

```ruby
include Rubio::State::Core
include Rubio::IO::Core
operation = putIO["final state"] >> pureStateIO["final result"]
io = runStateT[ operation ][ "initial state" ] #=> IO
io.perform! #=> ["final result", "final state"]
```

- `evalStateT :: StateIO s IO a -> s -> IO a`

`IO` variety of `evalState`.

```ruby
include Rubio::State::Core
include Rubio::IO::Core
operation = putIO["final state"] >> pureStateIO["final result"]
io = evalStateT[ operation ][ "initial state" ] #=> IO
io.perform! #=> "final result"
```

- `execStateT :: StateIO s IO a -> s -> IO s`

`IO` variety of `execState`.

```ruby
include Rubio::State::Core
include Rubio::IO::Core
operation = putIO["final state"] >> pureStateIO["final result"]
io = execStateT[ operation ][ "initial state" ] #=> IO
io.perform! #=> "final state"
```

### 3.4 `Rubio::Unit::Core`

Expand Down Expand Up @@ -711,4 +812,4 @@ doSomethingWithMaybe[ Nothing ] #=> "You got nothing."
- [examples/fizz_buzz.rb](examples/fizz_buzz.rb) - Examples of basic functional programming, basic IO, Maybe, fmap
- [examples/repl.rb](examples/repl.rb) and [examples/whileM.rb](examples/whileM.rb) - Custom includable modules, looping, more nuanced use of IO
- [examples/ruby_2.7_pattern_matching.rb](examples/ruby_2.7_pattern_matching.rb) - Using Maybe with the experimental pattern matching syntax in Ruby 2.7
- [examples/rackio/](examples/rackio/) - A small Rack application built using Rubio; uses the State monad to store data in memory that persists between requests
- [examples/rackio/](examples/rackio/) - A small Rack application built using Rubio; uses the StateIO monad to store data in memory that persists between requests
22 changes: 10 additions & 12 deletions examples/rackio/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,31 @@
module Application
extend Rubio::Expose
extend Rubio::State::Core
extend Rubio::Functor::Core
extend Rackio::Core

# body :: Env -> Hash -> IO String
body = ->(env, state) {
render["index.erb", binding]
}.curry
# body :: Env -> StateIO Hash IO String
body = ->(env) {
getIO >> ->(state) {
(liftIO << render)["index.erb", {state: state, env: env}]
}
}

# responseWithBody :: String -> Response
responseWithBody = ->(body) {
[200, {"Content-Type" => "text/html"}, [body]]
}

# response :: Env -> Hash -> IO Response
response = (fmap[responseWithBody] << body).curry(2)

# Merge query parameters with the state hash
# updateState :: Env -> StateIO Hash IO ()
updateState = ->(env) {
modify[ ->(state) {
modifyIO[ ->(state) {
request = Rack::Request.new(env)
state.merge(request.params)
}]
}

# main :: Env -> State s (IO Response)
# main :: Env -> StateIO s IO Response
expose :main, ->(env) {
updateState[env] >> gets[ response[env] ]
updateState[env] >> body[env] >> (pureStateIO << responseWithBody)
}

expose :initialState, { "hint" => "Try setting a query parameter in the URL" }
Expand Down
4 changes: 2 additions & 2 deletions examples/rackio/rackio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def initialize(main, initial_state)
end

def call(env)
io_response, @state = runState[ @main[env] ][ @state ]
io_response.perform!
response, @state = runStateT[ @main[env] ][ @state ].perform!
response
end
end
end
1 change: 1 addition & 0 deletions lib/rubio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "rubio/functor_core"
require "rubio/unit"
require "rubio/unit_core"
require "rubio/monad_identity"
require "rubio/io"
require "rubio/io_core"
require "rubio/maybe"
Expand Down
26 changes: 26 additions & 0 deletions lib/rubio/monad_identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Rubio
module Monad
module Identity
class IdentityClass
attr_reader :value

def initialize(value)
@value = value
end

def >>(other)
case
when other.respond_to?(:call)
other.call(@value)
else
raise ArgumentError, "expected callable, got #{other.class}"
end
end

def self.pure(x)
IdentityClass.new(x)
end
end
end
end
end
13 changes: 8 additions & 5 deletions lib/rubio/state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ module State
class StateClass
include Unit::Core

def initialize(f = nil, prior_state_functions: [])
def initialize(f = nil, prior_state_functions: [], monad_state_klass: Monad::Identity::IdentityClass)
subsequent_state_functions = if f.nil?
[]
else
[ ->(_) { f } ]
end

@state_functions = prior_state_functions + subsequent_state_functions
@monad_state_klass = monad_state_klass
end

def >>(other)
Expand All @@ -26,9 +27,10 @@ def >>(other)

def run
->(s0) {
@state_functions.inject( [unit, s0] ) { |z, f|
r, s = z
f[r][s]
@state_functions.inject( @monad_state_klass.pure [unit, s0] ) { |z, f|
z >> proc { |r, s|
f[r][s]
}
}
}
end
Expand All @@ -47,7 +49,8 @@ def state_functions

def bind(subsequent_state_functions)
StateClass.new(
prior_state_functions: self.state_functions + subsequent_state_functions
prior_state_functions: self.state_functions + subsequent_state_functions,
monad_state_klass: @monad_state_klass
)
end
end
Expand Down
71 changes: 69 additions & 2 deletions lib/rubio/state_core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,24 @@ module State
module Core
extend Expose
extend Unit::Core
extend IO::Core
extend Functor::Core

State = ->(f) {
StateClass.new(f)
pureIdentity = ->(x) { Monad::Identity::IdentityClass.pure(x) }
StateClass.new(pureIdentity << f)
}

StateIO = ->(f) {
StateClass.new(f, monad_state_klass: IO)
}.curry

liftIO = ->(x) {
StateIO[ ->(s) {
x >> ->(a) {
pureIO[[a, s]]
}
}]
}

pureState = ->(x) {
Expand All @@ -14,47 +29,99 @@ module Core
}]
}

pureStateIO = ->(x) {
StateIO[ ->(s) {
pureIO[[x, s]]
}]
}

get = State[ ->(s) {
[s, s]
}]

getIO = StateIO[ ->(s) {
pureIO[ [s, s] ]
}]

put = ->(x) {
State[ ->(s) {
[unit, x]
}]
}

putIO = ->(x) {
StateIO[ ->(s) {
pureIO[ [unit, x] ]
}]
}

modify = ->(f) {
get >> ->(x) {
put[ f[x] ]
}
}

modifyIO = ->(f) {
getIO >> ->(x) {
putIO[ f[x] ]
}
}

gets = ->(f) {
get >> ->(x) {
pureState[ f[x] ]
}
}

runState = proc(&:run)
getsIO = ->(f) {
getIO >> ->(x) {
pureStateIO[ f[x] ]
}
}

runState = ->(state, initial_state) {
state.run[initial_state].value
}.curry

runStateT = ->(state, initial_state) {
state.run[initial_state]
}.curry

evalState = ->(act) {
proc(&:first) << runState[act]
}

evalStateT = ->(act) {
fmap[proc(&:first)] << runStateT[act]
}

execState = ->(act) {
proc(&:last) << runState[act]
}

execStateT = ->(act) {
fmap[proc(&:last)] << runStateT[act]
}

expose :State, State
expose :StateIO, StateIO
expose :liftIO, liftIO
expose :pureState, pureState
expose :pureStateIO, pureStateIO
expose :get, get
expose :getIO, getIO
expose :put, put
expose :putIO, putIO
expose :modify, modify
expose :modifyIO, modifyIO
expose :gets, gets
expose :getsIO, getsIO
expose :runState, runState
expose :runStateT, runStateT
expose :evalState, evalState
expose :evalStateT, evalStateT
expose :execState, execState
expose :execStateT, execStateT
end
end
end
2 changes: 1 addition & 1 deletion lib/rubio/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Rubio
VERSION = "1.0.1"
VERSION = "1.1.0"
end
Loading

0 comments on commit 2cc3908

Please sign in to comment.