caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* Sources, sinks, and unbound parameter types
@ 2008-03-24 21:56 Dario Teixeira
  2008-03-24 22:17 ` [Caml-list] " Daniel Bünzli
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Dario Teixeira @ 2008-03-24 21:56 UTC (permalink / raw)
  To: caml-list

Hi,

I'm looking for a way to express the composition of functional components
in a tree-like data structure.  Each node in the tree is either:

  a) a Source, producing values "out of nowhere": unit -> 'a
  b) a Sink, end point of the tree: 'b -> unit
  c) a Processor, transforming values from one type to another: 'c -> 'd


The Ocaml type that represents a node is as follows: (pretty
straightforward, though I wonder if there's a way to explicitly
say that "Source is of 'a -> 'b where 'a must be of type unit")

type ('a, 'b) node_t =
        | Source of (unit -> 'b)
        | Sink of ('a -> unit)
        | Processor of ('a -> 'b)


In addition, there are two types of connectors linking these nodes in the tree:

  a) a Pipe, connecting one node that outputs a value of type 'a into
     another that inputs an 'a.
  b) a Splitter, essentially like a Pipe, but able to feed the same
     value into multiple inputs.


If you'll pardon the ASCII art, here's a diagram of one such simple tree:


|                        ===========
|                        | source1 | 
|                        ===========
|                             |
|                             |
|                             O Pipe
|                             |
|                             |
|                        ============
|                        | process1 |
|                        ============
|                             |
|                             |
|             ----------------O----------------
|             |           Splitter            |
|             |                               |
|        ===========                     ===========
|        |  sink1  |                     |  sink2  |
|        ===========                     ===========


Where each component node can, for example, be defined as follows:

let source1 () = 10
let process1 n = 2.0 *. (float_of_int n)
let sink1 x = Printf.printf "Sink1: %f\n" x
let sink2 x = Printf.printf "Sink2: %f\n" x


To define the tree type, I would like to express something like the code
below.  Note that I am trying to get to the compiler to statically enforce
that outputs and inputs are correctly matched type-wise.

type ('a, 'b) tree_t =
        | Node of ('a, 'b) node_t
        | Pipe of ('a, 'c) tree_t * ('c, 'b) tree_t
        | Splitter of ('a, 'c) tree_t * ('c, 'b) tree_t list


The code above obviously won't work because of the unbound type parameter 'c.
(Also, please ignore for now the fact it allows splitters with an empty
output list -- that can easily be circumvented).

So, my question is if there is any way to express what I want?  I guess there
is a solution involving the creation of a syntax extension, but I'm looking
for a pure Ocaml way.

Thanks in advance for your time!
Best regards,
Dario Teixeira





      ___________________________________________________________ 
Rise to the challenge for Sport Relief with Yahoo! For Good  

http://uk.promotions.yahoo.com/forgood/


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Caml-list] Sources, sinks, and unbound parameter types
  2008-03-24 21:56 Sources, sinks, and unbound parameter types Dario Teixeira
@ 2008-03-24 22:17 ` Daniel Bünzli
  2008-03-24 22:19 ` Christopher L Conway
  2008-03-24 23:03 ` Jeremy Yallop
  2 siblings, 0 replies; 4+ messages in thread
From: Daniel Bünzli @ 2008-03-24 22:17 UTC (permalink / raw)
  To: Dario Teixeira; +Cc: caml-list

It seems what you want is existential types, see the example with  
lists of composable functions here [1,2].

Best,

Daniel

[1] http://caml.inria.fr/pub/ml-archives/caml-list/2004/01/52732867110697f55650778d883ae5e9.fr.html
[2] http://caml.inria.fr/pub/ml-archives/caml-list/2004/01/470ee4e9fe63cf36e1bc477980b102e2.fr.html


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Caml-list] Sources, sinks, and unbound parameter types
  2008-03-24 21:56 Sources, sinks, and unbound parameter types Dario Teixeira
  2008-03-24 22:17 ` [Caml-list] " Daniel Bünzli
@ 2008-03-24 22:19 ` Christopher L Conway
  2008-03-24 23:03 ` Jeremy Yallop
  2 siblings, 0 replies; 4+ messages in thread
From: Christopher L Conway @ 2008-03-24 22:19 UTC (permalink / raw)
  To: Dario Teixeira; +Cc: caml-list

This looks a lot like the "Lists of Composable Functions" example
using existentials cited here last week [1].

Your comment about the type of Source and Sink being too general is
interesting. In general, you're asking for GADTs [2], though this
instance is simple enough you could possibly hack up a solution
without them.

Chris

[1] http://groups.google.com/group/fa.caml/msg/8f11bc7839aac98f
[2] http://www.haskell.org/haskellwiki/Generalised_algebraic_datatype

On Mon, Mar 24, 2008 at 5:56 PM, Dario Teixeira <darioteixeira@yahoo.com> wrote:
> Hi,
>
>  I'm looking for a way to express the composition of functional components
>  in a tree-like data structure.  Each node in the tree is either:
>
>   a) a Source, producing values "out of nowhere": unit -> 'a
>   b) a Sink, end point of the tree: 'b -> unit
>   c) a Processor, transforming values from one type to another: 'c -> 'd
>
>
>  The Ocaml type that represents a node is as follows: (pretty
>  straightforward, though I wonder if there's a way to explicitly
>  say that "Source is of 'a -> 'b where 'a must be of type unit")
>
>  type ('a, 'b) node_t =
>         | Source of (unit -> 'b)
>         | Sink of ('a -> unit)
>         | Processor of ('a -> 'b)
>
>
>  In addition, there are two types of connectors linking these nodes in the tree:
>
>   a) a Pipe, connecting one node that outputs a value of type 'a into
>      another that inputs an 'a.
>   b) a Splitter, essentially like a Pipe, but able to feed the same
>      value into multiple inputs.
>
>
>  If you'll pardon the ASCII art, here's a diagram of one such simple tree:
>
>
>  |                        ===========
>  |                        | source1 |
>  |                        ===========
>  |                             |
>  |                             |
>  |                             O Pipe
>  |                             |
>  |                             |
>  |                        ============
>  |                        | process1 |
>  |                        ============
>  |                             |
>  |                             |
>  |             ----------------O----------------
>  |             |           Splitter            |
>  |             |                               |
>  |        ===========                     ===========
>  |        |  sink1  |                     |  sink2  |
>  |        ===========                     ===========
>
>
>  Where each component node can, for example, be defined as follows:
>
>  let source1 () = 10
>  let process1 n = 2.0 *. (float_of_int n)
>  let sink1 x = Printf.printf "Sink1: %f\n" x
>  let sink2 x = Printf.printf "Sink2: %f\n" x
>
>
>  To define the tree type, I would like to express something like the code
>  below.  Note that I am trying to get to the compiler to statically enforce
>  that outputs and inputs are correctly matched type-wise.
>
>  type ('a, 'b) tree_t =
>         | Node of ('a, 'b) node_t
>         | Pipe of ('a, 'c) tree_t * ('c, 'b) tree_t
>         | Splitter of ('a, 'c) tree_t * ('c, 'b) tree_t list
>
>
>  The code above obviously won't work because of the unbound type parameter 'c.
>  (Also, please ignore for now the fact it allows splitters with an empty
>  output list -- that can easily be circumvented).
>
>  So, my question is if there is any way to express what I want?  I guess there
>  is a solution involving the creation of a syntax extension, but I'm looking
>  for a pure Ocaml way.
>
>  Thanks in advance for your time!
>  Best regards,
>  Dario Teixeira
>
>
>
>
>
>
>       ___________________________________________________________
>  Rise to the challenge for Sport Relief with Yahoo! For Good
>
>  http://uk.promotions.yahoo.com/forgood/
>
>  _______________________________________________
>  Caml-list mailing list. Subscription management:
>  http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
>  Archives: http://caml.inria.fr
>  Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
>  Bug reports: http://caml.inria.fr/bin/caml-bugs
>
>


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Caml-list] Sources, sinks, and unbound parameter types
  2008-03-24 21:56 Sources, sinks, and unbound parameter types Dario Teixeira
  2008-03-24 22:17 ` [Caml-list] " Daniel Bünzli
  2008-03-24 22:19 ` Christopher L Conway
@ 2008-03-24 23:03 ` Jeremy Yallop
  2 siblings, 0 replies; 4+ messages in thread
From: Jeremy Yallop @ 2008-03-24 23:03 UTC (permalink / raw)
  To: Dario Teixeira; +Cc: caml-list

One useful way to attack this sort of problem (wiring things together
in a way that hides the intermediate types) is to use functions in
place of constructors.  The problem with constructors in ML-style
datatypes is that every type variable that appears in the arguments
must also appear as a parameter to the datatype, so the intermediate
types "leak out".  GADTs lift this restriction, but we can achieve a
large part of what GADTs offer by moving from a datatypes and a set of
constructors to an abstract type and a set of functions which
construct values of the type.  That is, instead of encoding "pipe" as
a constructor

     type ('a, 'b) tree_t =
       ...
     | Pipe of ('a, 'c) tree_t * ('c, 'b) tree_t
       ...

(which is not allowed, because 'c is not a type parameter) we can
encode it as a function:

     type ('a, 'b) tree_t =

     val pipe : ('a, 'c) tree_t * ('c, 'b) tree_t -> ('a, 'b) tree_t

and similarly for the other constructors.  Instead of having a
separate interpreter function that walks the tree, inspecting each
constructor, we can then encode the semantics of each constructor
directly, using these functions.  (One way to view this is as a sort
of Church encoding of the original datatype; another is as a
dualization, using a product (the module containing the abstract type
and its operations) in place of a sum).  This encoding also avoids the
problem you raised at the beginning of your mail, since the type of
"source" becomes

    val source : (unit -> 'b) -> (unit, 'b) node_t

This trick has been used to good effect in the ML community to do the
sort of things that are often assumed to require GADTs or other "fancy
types": well-typed evaluators, datatype-generic programming, etc.

Your plumbing primitives seem quite reminiscent of the combinators
used in the "arrows" approach to effectful combination.  An arrow is a
type constructor with two parameters, representing input and output:

     type ('i, 'o) arr

together with primitives for constructing an arrow from a function

     val arr : ('i -> 'o) -> ('i, 'o) arr

composing two arrows

     val (>>>) : ('i, 'j) arr -> ('j, 'o) arr -> ('i, 'o) arr

and transforming an arrow into another arrow that threads additional data

     val first : ('i, 'o) arr -> (('i*'d), ('o*'d)) arr

Your source, sink and processor primitives are all particular
instances of "arr"; your "pipe" directly corresponds to the
composition operator (>>>), and your splitter can be written using
"first", although you may like to consider the arrow combinator

     val (&&&) : ('i,'o) arr -> ('i,'p) arr -> ('i, ('o*'p)) arr

(also expressible using the primitives) which provides a way to pipe
the same input into two arrows and collect both the outputs.

Here's an example implementation:

   module type ARROW =
   sig
     type ('i,'o) arr
     val arr   : ('i -> 'o) -> ('i, 'o) arr
     val (>>>) : ('i, 'j) arr -> ('j, 'o) arr -> ('i, 'o) arr
     val first : ('i, 'o) arr -> (('i*'d), ('o*'d)) arr

     val run   : ('i,'o) arr -> ('i -> 'o)
   end

   module Arrow : ARROW =
   struct
     type ('i,'o) arr = 'i -> 'o
     let arr f = f
     let (>>>) f g x = g (f x)
     let first f (x,y) = (f x, y)
     let run f = f
   end

   module Composable :
   sig
     type ('a,'b) tree = ('a,'b) Arrow.arr

     val source    : (unit -> 'b) -> (unit, 'b) tree
     val sink      : ('a -> unit) -> ('a, unit) tree
     val processor : ('a -> 'b)   -> ('a, 'b) tree

     val pipe      : ('a, 'c) tree -> ('c, 'b) tree -> ('a, 'b) tree
     val split     : ('a, 'b) tree -> ('a, 'c) tree -> ('a, ('b*'c)) tree
   end =
   struct
     open Arrow

     type ('a,'b) tree = ('a,'b) arr

     let source    = arr
     let sink      = arr
     let processor = arr
     let pipe      = (>>>)
     let split f g =
       let dup  x     = (x,x)
       and swap (x,y) = (y,x) in
         arr dup >>> first f >>> arr swap >>> first g >>> arr swap
   end

(Actually, "arr" is supposed to be for pure functions only; it'd be
  better to have a separate operation for introducing effectful
  operations into the arrow.)

The computation given in your diagram might then be expressed as
follows:

   pipe (pipe (source source1)
              (processor process1))
        (split (sink sink1) (sink sink2))

or, using the arrow combinator syntax:

   source source1 >>> processor process1 >>> (sink sink1 &&& sink sink2)

Jeremy.


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2008-03-24 23:06 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-03-24 21:56 Sources, sinks, and unbound parameter types Dario Teixeira
2008-03-24 22:17 ` [Caml-list] " Daniel Bünzli
2008-03-24 22:19 ` Christopher L Conway
2008-03-24 23:03 ` Jeremy Yallop

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).