What are the Juli8 Libraries?
In addition to the fine programming language Kapulet, which doesn't yet exist in a form you can actually execute --- though the full-featured interpreter we provided a week or so ago is a good start --- I decided it would be useful to have a collection of basic libraries for our teaching (and other) purposes, that brought OCaml, Haskell, and the Scheme implementations we recommend, more onto even footing. Of course there are fundamental differences between these languages, such as the lack of types in Scheme (though both Racket and Chicken have some facility for working with types in extensions), or the default lazy evaluation strategy in Haskell. But there are also many simply accidental differences between the languages too, in that this one provides a library function doing so-and-so, but the other one doesn't, or calls it by a different name. The Juli8 collection of libraries is aimed to reduce these differences, to make it easier to move back and forth between the languages, and also to just make some of the languages generally easier to use (from my perspective). Juli8 will eventually have components that you can install into each of Haskell, OCaml, Racket, and Chicken. For the moment, OCaml is the most developed of these, and Haskell a bit, with the Scheme components deferred for another time.
Juli8 tries to give OCaml more-or-less the same functionality that you have in Haskell's Data.Maybe
, Data.List
, and Control.Monad
libraries, as well as a few others. It doesn't try for an exact match, and it doesn't strictly use the same names as Haskell does. I've aimed to at least stay close to the existing OCaml naming patterns, and also the Scheme naming patterns when they are salient to me.
Below, we'll give instructions on how to install Juli8 into your existing OCaml and/or Haskell setups. We'll also discuss how to use the non-monadic parts of the Juli8 libraries for OCaml. We discuss how to use the monadic parts of the Juli8 libraries for OCaml elsewhere. We'll also say a little bit about the little bit that Juli8 provides for you if you're using Haskell.
Setting up OCaml in general, and for use with Juli8
In our instructions for installing OCaml, some of the strategies involved installing the OPAM package manager. (This doesn't appear to be available for Windows.) If you did install OPAM, then we recommend typing the following commands inside a terminal session (that is, not inside an OCaml session, but inside the prompt where you'd start OCaml by typing
ocaml
).opam update opam upgrade opam install delimcc
We'll comment on the
delimcc
package in a moment.We also recommend upgrading to one of the more recent versions of OCaml. This gets you the most recent stable release:
opam switch 4.02.1 && opam install --switch=4.02.1 delimcc && eval `opam config env`
This will get you a slightly older release, which however has "improved type error messages":
opam switch 4.02.0+improved-errors && opam install --switch=4.02.0+improved-errors delimcc && eval `opam config env`
Installing the
delimcc
library gives you Oleg's Delimited Continuation library for OCaml, which we will encourage you to play around with later in the term. It's not essential to have it, though. There are some advantages to using one of the 4.02.x versions of OCaml, though, rather than the 4.01.0 version I think most of us ended up installing. One advantage is that now you can use the special#show symbol
command in OCaml sessions, which works like Haskell's special:info symbol
inside a GHCi session. Then for example, instead of having to type:module type SOMETHING = Monad.OPTION
as I directed you elsewhere, you can instead just type:
#show Monad.OPTION
The program that starts up when you type
ocaml
is OCaml's Standard "Toplevel" Interactive Interpreter. There's an alternative interactive interpreter that you can try out, which some people like. It's called utop and you can read about it here or here. To install it, you can just typeopam install utop
. I'm not so crazy about it myself. But I prefer to use some kind of helper program with OCaml's Standard Toplevel, because the Standard Toplevel itself doesn't let you scroll back through commands you typed previously, has only very rudimentary facilities for editing a line if you made a mistake and so on. One virtue of utop is that it does those things better, but there are also other ways to do them better. What I use is a wrapper program called rlwrap. Here are instructions for how to install that. (Kyle is using OCaml on Cygwin on Windows, and there it looks like rlwrap is already installed; on the other hand, it doesn't seem to be working. The issue might not be with rlwrap, but rather that his Terminal is not passing the appropriate keypresses through to the shell session it's hosting.)First, I had to upgrade the version of the "GNU readline" library on my computer. My Mac with System 10.9.5 has a version of that library, but it's too old to use with recent versions of
rlwrap
. So I downloaded the source code for GNU readline. Double click the downloaded archive to expand it, if your browser doesn't do that automatically. Then go inside thereadline-6.3
folder in a Terminal. On a Mac, you can click on the folder in the Finder and do a Copy (or cmd-C). Then open a Terminal and typecd
followed by a space then do a Paste (cmd-V). Then pressreturn
. Once you're inside thereadline-6.3
folder, type this command in the Terminal:./configure --prefix=$HOME && make && make install
That should build and install the readline library in your local user directories. It will take a couple of minutes. Next, download the source code for rlwrap. Double-click to expand and go inside the
rlwrap-0.42
folder in a Terminal, as before. Then type this command in the Terminal:./configure --prefix=$HOME && make CFLAGS="-g -O2 \ -I $HOME/include -L $HOME/lib" && make install
If that all completes without errors, then you have gotten
rlwrap
installed. Congratulations. To use it, you just will now typerlwrap ocaml ...
wherever before you would ordinarily typeocaml ...
This is just to make interactive OCaml sessions nicer. To compile code withocamlc ...
and so on, you don't userlwrap
for that. (If you see error messages of the form-bash: rlwrap: command not found
, then you should make sure that you have a.bash_profile
or.bashrc
in your$HOME
directory which has a line containing something likeexport PATH="$HOME/bin:$HOME/Library/Haskell/bin:$PATH"
. Then maybe start up a new Terminal.)Some keycommands you can now use are:
^H
(that is, Control+H
) will delete one char to the left of the cursor position (alsobackspace
)^D
will delete one char to the right of the cursor position (but if you type it on a blank line it will quit the program you're running)^W
will delete one word to the left of the cursor position (alsoesc
followed bybackspace
, after releasingesc
)esc
+D
will delete one word to the right of the cursor position^U
will delete from the cursor position leftwards all the way to the start of the line^K
will delete from the cursor position rightwards all the way to the end of the line- Control-
minus
will undo one editing command or sequence of typed characters esc
+R
will (usually) erase the whole line (undoing everything)^A
moves the cursor to the start of the line^E
moves the cursor to the end of the line^B
and^F
work like left and right arrowsesc
+B
andesc
+F
move left and right a whole word at a time (on the Mac, Option-left
and Option-right
do the same)- Control-
]
+x
will move the cursor rightwards to the nextx
character on the line (similarly for other characters in place ofx
). You can do the same leftwards by typingesc
+ Control-]
+x
. You can move to the second-nextx
character by typingesc
+2
+ Control-]
+x
. This is all pretty cumbersome. ^L
clears the screenup
/down
arrows let you scroll through previous lines you typed (also^P
/^N
)^R
lets you search through previous lines you typed for a matching substring; to refine the search keep making the substring longer, or type^R
again to find an earlier match. Typereturn
to use the currently showing match, oresc
or an arrow key to exit the search and edit the currently showing match. Type^G
to exit the search with a blank command line.
If you create a file named
$HOME/.inputrc
which contains:set blink-matching-paren on
then you will get the further nice feature that when you type a close parenthesis,
rlwrap
will temporarily make the cursor jump to the matching open parenthesis, so that you can see how many close parentheses you need to type. If you want to further customizerlwrap
or the above keycommands, you can read the documentation atman rlwrap
andman readline
, and also look into the Preferences of your Terminal program. (On the Mac, look under Terminal menu/Preferences/Keyboard tab.) This gets complicated quickly, but those are the places to start looking if you want to experiment. You should of course also read around on the web, rather than just changing these blindly.Whether you use
utop
orrlwrap ocaml
or just plainocaml
, you will need to set up some initialization commands in order to use the Juli8 libraries. You'll want to edit or create an file called.ocamlinit
in your$HOME
directory. The file may already be there and have some commands in it if you used OPAM.After any commands that are already there, add these lines:
#load "str.cma";; module Std = struct include Pervasives module List = List module Random = Random module String = String end #directory "+../delimcc";; (* you'll have to substitute your own $HOME directory in for `/Users/jim/` *) #directory "/Users/jim/.juli8/ocaml";; #use "juli8.ml";;
Next create a folder in your
$HOME
directory named.juli8
. Download the Juli8 code from here. That link will no doubt be updated frequently in April and May 2015. The current version is: 1.6, posted 13 April 2015. Copy the contents of theJuli8
folder that you downloaded into the$HOME/.juli8
folder that you created. (So$HOME/.juli8
should contain foldershaskell
,ocaml
, and so on.)Now whenever you start up OCaml, the Juli8 OCaml libraries (including their monad components, which we'll be making extensive use of) will automatically be loaded.
Using the Juli8 libraries with OCaml
Here is an overview of what you get in the non-monadic parts of the Juli8 library for OCaml. For the monadic part, see here.
First, at the top level of your session --- that is, not inside any module --- you get functions ident
and %
that work like Haskell's id
and .
. That is, f % g
is equivalent to fun x -> f (g x)
.
You also get the functions const
, flip
, fix
, swap
, curry
, uncurry
, even
, and odd
that work like their Haskell counterparts.
These functions have Haskell counterparts with slightly different names: OCaml's pair
is Haskell's (,)
; OCaml's sign
is Haskell's signum
; OCaml's pow (x : int) (n : int)
is Haskell's x^n
; OCaml's ignore(expr1); expr2
is like let _ = expr1 in expr2
in Haskell (and also in OCaml). When expr1
returns ()
, you can omit the ignore(...)
and just say expr1; expr2
. Because these sequences with semicolons can parse differently than you expect, it's generally a good idea to surround them with parentheses or with begin ... end
which in OCaml is just a syntactic variant of parentheses.
Note that OCaml already came with the following functions that work like their Haskell counterparts: max
, min
, not
, &&
, ||
, succ
, abs
, fst
, and snd
. OCaml's infix operators mod
and /
work like Haskell's rem
and quot
, not like Haskell's mod
and div
. (These behave the same when both arguments are positive.)
Juli8 gives you a version of pred
that replaces the default OCaml pred
, and raises an error if you say pred 0
. There's also a more tolerant variant, pred'
, where pred' 0
is simply 0
. If you want the standard OCaml pred
, use Std.pred x
or just x-1
.
Corresponding to pred
and pred'
, there are also sub
and sub'
, where sub 8 10
raises an error but sub' 8 10
is simply 0
.
Here are some functions in OCaml/Juli8 but not in Haskell: mid x y
finds the integer midway between x
and y
without overflowing; mapfst f (x, y)
is (f x, y)
; mapsnd g (x, y)
is (x, g y)
; non f
is fun x -> not (f x)
; non2 f
is fun x y -> not (f x y)
.
Haskell has a function iterate
that makes infinite lists. It's possible to make infinite (lazy) lists in OCaml, but modestly complex, and this isn't now provided by Juli8. Instead, Juli8 has a function iterate n s z
that returns what would be the n
th member of the infinite Haskell list iterate s z
, that is, the n
-fold composition of s
applied to z
.
OCaml/Juli8 also has a function iter_while p s z
, which keeps applying s
to z
until the result no longer satisfies the predicate p
. This is like the Haskell command until (not . p) s z
.
OCaml/Juli8 has a command forever f x
which keeps applying f
to argument x
forever. That is, it does let _ = f x in let _ = f x in ...
. Not f (f (... z))
. Haskell has a similar command, but it only applies to Monads, and Haskell's version doesn't involve x
. That is, Haskell does mm >> mm >> ...
for some monadic value mm
.
OCaml/Juli8 also has factorial
and choose n k
functions. But unlike Haskell, it doesn't (yet) have gcd
or lcm
functions.
Haskell's ways of handling printing is rather different from OCaml's and most other languages. But if you've learned any Haskell, you've already probably become familiar with this.
OCaml and Haskell's ways of handling errors is also somewhat different. OCaml has:
raise <exception> failwith "msg" (* same as `raise (Failure "msg")` *) invalid_arg "msg" (* same as `raise (Invalid_argument "msg")` *) undefined () (* provided by Juli8 *) assert <bool> : unit
Corresponding are the following operations in Haskell:
Prelude.error "msg" Prelude.undefined Control.Exception.assert <bool> <anything>
Option
Juli8 provides an Option
module (there's also a module Monad.Option
that only exposes the monadic interface). This provides a number of functions that you can see by typing #show Option
if you're running OCaml version 4.02 or above. For earlier versions of OCaml, type module Something = Option;;
to see the same result. A few of the functions from the Option
module are also currently exported to be available at the top level. Thus you can simply say is_some x
instead of Option.is_some x
. But if you're not sure which are available in this way, just play it safe and use the preferatory Option.
.
List
Juli8 provides a List
module that replaces the standard OCaml List
module, which is a bit skimpy. The official List
module is still available at Std.List
, but all of its functionality is present in Juli8's List
module, albeit sometimes under slightly different names. To see what's in the List
module that Juli8 provides, type #show List
in OCaml version >= 4.02. Many of the functions here parallel functions from the Haskell libraries.
As with the Option
library, a few of the functions are also currently exported to be available at the top level. Also as with Option
, there's additionally a module Monad.List
that only exposes the monadic interface.
Without trying to give an exhaustive explanation of the List
functions, here are a few comments.
Juli8's head
and tail
, like OCaml's official List.hd
and List.tl
, both raise an error if applied to an empty list. However, Juli8 also provides a tail'
function that returns []
when applied to the empty list. (Compare pred'
, described above.) There are analogous pairings between take
,drop
, and split
on the one hand, and take'
, drop'
, and split'
on the other.
Juli8 also provides a function opthead xs
that returns either Some x
if xs
has a head, else None
.
Some functions in Juli8's libraries accept OCaml's optional labeled arguments. Thus for example zip xs ys
will raise an error if the lists have different lengths, but zip ~short xs ys
will instead just stop with the shorter list. drop_while ~rev p xs
drops elements from the right end of xs
that satisfy the predicate p
, rather than from the left end; similarly find ~rev p xs
finds the last item in xs
that satisfies the predicate. remove ~many p xs
removes all the members of xs
that satisfy predicate p
, rather than just the first. There are many examples like this. See the comments in the source code for a full description.
A few more examples worth calling attention to are that insert ~many x xs
inserts x
into the appropriate position in already-sorted list xs
, but that without the ~many
flag, insert
won't insert items that are already present in the list. Similarly, sort ~many xs
will sort a list, retaining arguments that compare as equal in the order they originally appeared, but without the ~many
flag, sort
will drop all duplicates after the first.
String
Juli8 provides a String
module that replaces the standard OCaml String
module, and includes some (but not all) functionality from the standard String
, Str
, and Char
modules. The official String
module can be found at Std.String
. To see what's in the String
module that Juli8 provides, type #show String
in OCaml version >= 4.02. Some of the functions here parallel functions from the Haskell libraries.
Random
Juli8 provides a Random
module that replaces the standard OCaml Random
module, and includes some (but not all) functionality from the standard Random
module, which can still be found at Std.Random
. To see what's in the Random
module that Juli8 provides, type #show Random
in OCaml version >= 4.02.
Setting up Haskell for use with Juli8
When (If) you installed Haskell, we hope you did it via a method that gave you the Haskell Platform. This will give you a recent version of the Glasgow Haskell Compiler (GHC), which comes with the interactive Haskell session program
ghci
, along with the Cabal package manager (the analogue of OCaml's OPAM) and also with a collection of the most widely-used libraries in the Haskell community, that don't come along with GHC itself.Assuming you do have Cabal, we recommend you do the following. First, find out where Cabal installs logs of its activity. On my Mac, it logs them in the file
~/Library/Haskell/logs/world
. Now what I did was to type the following command in a Terminal:ln -s ~/Library/Haskell/logs/world ~/.cabal/
Now I can find a link to what Cabal has done inside Cabal's own folder, without needing to remember or hunt down where the hell on my disk that information has been stored. (All right, to be honest, you can skip this whole step if you want. But I recommend doing it.)
Still assuming you have Cabal, type the following in a Terminal:
cabal update cabal install --user cabal-install ghc-paths semigroups hoogle hoogle data
If you haven't already downloaded and installed the Juli8 libraries as described in Step 4 under the earlier OCaml section, do that now. Also type the following lines in a Terminal:
ln -s ~/.juli8/haskell ~/.ghc/juli8
Check to see whether any of the following files exist on your system.
$HOME
will be some directory like/Users/jim
or/home/jim
orC:/Documents\ and\ Settings/jim
:$HOME/.ghci
$HOME/.ghc/ghci.conf
$HOME/"Application Data"/ghc/ghci.conf
If you find such a file, you will add lines to it in the next step. If you don't find such a file, create one.
Add these lines to the
.ghci
orghci.conf
file identified in the previous step:-- reads ghci commands from any file named in $GHCIRC :cmd (System.Environment.getEnvironment >>= maybe (return "") readFile . lookup "GHCIRC") -- special :commands from Juli8 :{ :cmd do { dot_ghc <- System.Directory.getAppUserDataDirectory "ghc"; let { juli8 = dot_ghc ++ "/juli8"; cmds = juli8 ++ "/commands" }; juli8_exists <- System.Directory.doesDirectoryExist juli8; cmds_exists <- System.Directory.doesFileExist cmds; Control.Monad.when cmds_exists $ putStrLn "Loading juli8/commands ..."; return $ unlines $ if cmds_exists then [":set -i"++juli8, ":script "++cmds] else if juli8_exists then [":set -i"++juli8] else [] } :} :def! url (\l->return $ ":!open "++l) -- :set editor vim -- :set +m -- for multiline input -- :set +t -- to print types after each binding :load Juli8 :mod Juli8
You may want to uncomment the
:set editor vim
line, but only if you know how to use the text editorvim
. Other text editors you may be familiar with, and can use here are::set editor emacs :set editor nano :set editor open -a TextEdit -- Mac-only :set editor bbedit -- Mac-only, see http://www.barebones.com/support/bbedit/cmd-line-tools.html :set editor mate -- Mac-only, see http://manual.macromates.com/en/using_textmate_from_terminal.html -- for Windows, use one of https://wiki.haskell.org/Windows#Editors
You may want to uncomment the
:set +m
line. What this does is let you type multi-line commands in theghci
sessions. There is a different way to do that, where you type like this::{ multiline command :}
but that's pretty cumbersome. The downside of having
:set +m
on is that sometimes you'll have to type an extra blank line beforeghci
will respond to your input.
If everything works, then when you start up GHCi, you should see a prompt like this:
Prelude Juli8>
Note that the GHCi command line already by default accepts the special keycommands described under item 2 in the Setting up OCaml section above. Additionally, if you type just the start of a command and then press Tab
, GHCi will attempt to figure out what you started to type and finish the word for you.
What do I get from Juli8 for Haskell?
There are two or three benefits that Juli8 provides for Haskell, and they're not a big deal. If you're already a seasoned Haskell user, you may or may not find them helpful.
First, Juli8 comes with a bunch of extra :commands
to use at the GHCi prompt. You can see a list of what it installs by typing :?
. Some of the commands listed in :?
were already present before Juli8 arrived, and are just here collected and explained in a way I find more helpful. Others are provided by Juli8 itself. Many of these are based on commands already published elsewhere on Haskell wikis and so on, so you may have installed some versions of them already yourself. I'll leave it to you to pick and choose whether anything that comes with Juli8 suits your further needs.
I developed these :commands
on a Mac, and expect that some of the assumptions I made won't work on other systems. As the library matures, we'll try to make it work for a broader range of systems, or give specific instructions about how to customize it.
The command :help
will give you the old, official help text, that doesn't show the extra commands installed by Juli8.
Second, Juli8 comes with a module/library that collects together a number of elements from scattered other locations in the Haskell libraries. These include the Semigroup
libraries you installed in Step 3 of the above instructions, which you should use in place of Haskell's standard Data.Monoid
libraries. Note that the Semigroup
library provides First a
and Last a
types that differ from the types of the same name in Data.Monoid
. Juli8 also provides OptFirst a
and OptLast a
types that behave more like the First a
and Last a
types from Data.Monoid
. It also provides analogous types OptMax a
and OptMin a
. (If you don't know what any of this means, don't worry about it.)
Third, Juli8 comes with a module/library IOPlus
that isn't loaded by default, but which you can load manually by saying :add IOPlus
. This provides instances to make IO a
a Monoid
when a
is, and to make IO
act like an instance of the Alternative
and MonadPlus
typeclasses. This has some limitations, and can't be done perfectly, which is why it isn't done in the standard libraries. There's also an IOFirst
and an IOLast
. This is more experimental than the rest of the stuff in Juli8 and may well change or be removed. I'll explain it and refine it another time.