How can one extend simply typed lambda calculus to have a type system that supports something like a monad type? Basically, I presently have a nice understanding of simply typed lambda calculus, and I'd like to understand the "minimal requirements" to add monads to that foundation. By "adding monads" I mean anything that would result in a language with an operational semantics and type assignment that allows one to recognize the "usefulness" of monads for programs, to some extent. For example, Haskell supports monads in reasonable sense even though it doesn't require the programmer to fully prove that their "monad" instance actually abides by the monad laws.
I'm hoping to understand some minimal way of extending STLC with monads in order to learn more about monads in relation to programming language theory. Personally, I find it easier to learn these things in a more stripped down/essential setting (as opposed to just using them in practice in a language like haskell). For this reason, I can't give any more of a precise description of what I'm looking for, than what I wrote above.
Edit, with regard to #Ben's comment: could you not have some kind of setup where you have a signature of "atomic" monads M, and then your simple types T are now:
T = σ | T1 → T2 | m T
where σ is an atomic type from the signature of atomic types, and m is an element of M.
And then maybe you also add constant terms to the lambda calculus terms:
t = x | t1 t2 | λ x.t | return t | t1 >>= t2$
I'm not sure if any of this would work, but it seems like something like this would be possible.
This is already addressed by Eugenio Moggi's 1991 seminal paper, "Notions of computation and monads." Here's a link: http://www.cs.cmu.edu/~crary/819-f09/Moggi91.pdf
In particular, Section 2.3 explains how to interpret a simple programming language based on lambda-calculus in a monadic framework. Note that it doesn't matter if you add return, >>= etc; it's the semantics you give to your expressions and statements that are monadic. Haskell makes this explicit by separating the "pure" parts from the "monadic" parts in a syntactically nice way, whereas ML/Scheme etc. make it "convoluted" by keeping both look the same in the type system, but allow interpretations over suitable monads.
I'm studying optimal implementations of the λ-calculus. There is a particular subset of lambda terms that is very efficient. It corresponds to the type system of Elementary Affine Logic with fixed points. In order to test my implementations of that algorithm, I have to write moderately complex terms on that system. This is difficult without a infrastructure. I have to use the untyped lambda calculus and then manually add the types; no checking, unification, no type errors.
One idea would be to write programs in Haskell - taking benefit from its mature type-checker - and then translate to EAL. Unfortunately, there is a mismatch between System-Fw and EAL. For example, you can't express Scott-encoded ADTs in Haskell without newtype, due to the lack of a type-level fix. Moreover, Haskell is a complex language, and writing a Haskell->EAl compiler would not be trivial.
Is there any quick/dirty way to get a working type checker/inferencer/unifier for that system - or at least something close enough - without having to program it all myself?
Probably the quickest and easiest way is to embed your system into Haskell as an EDSL. The finally, tagless approach may be ideal and there's an example of encoding a linear type system with it. In particular, I'd recommend using the HOAS variation by Jeff Polakow. This will give you syntax like:
*Main> :t eval $ llam $ \x -> add x (int 1)
eval $ llam $ \x -> add x (int 1) :: Int -<> Int
which isn't too terrible. One aspect of the finally, tagless approach is that you can can have multiple interpretations for the same term, so you can have an interpretation that translates the term to some AST representing EAL, or have an interpretation that performs some additional typechecking if you aren't able to capture all of it in Haskell's type system.
(Sorry, I'm stupid and uneducated, so this is probably a ridiculous question.)
I just started looking at J, and they use the terms "monadic" and "dyadic" for what seems (to me) to be unary and binary operators. Why is this done, and how does it relate to the other place I've heard the term (Haskell)? My guess is they are unrelated homonyms but I'm not sure.
They're unrelated except by both deriving from the Greek root for "one". Monadic and dyadic are indeed terms for unary and binary functions. Specifically, they're the Greek-derived equivalents--using -adic instead of -ary. Consider the word "triad", which is also Greek-derived.
Monad in the sense Haskell uses it has an unclear etymology but probably derives from "monoid".
I would encourage sticking with the Latin-derived "n-ary" terms in Haskell, though. All functions in Haskell technically have one argument because of currying, so using the Greek-derived form could produce arbitrary amounts of confusion.
They're unrelated; C. A. McCann points out the etymologies of both.
In any case, the Haskell use, of course, comes from category theory, and is thought to be an independent coining unrelated to the other senses of monad.
Indeed, the J sense of "monadic" dates back to APL, which predates Haskell by a quarter of a century! I think it might predate the category theory usage of the term, too.
Adicity (or adinity) is an alternative to arity, using Greek numeral roots instead of Latin:
niladic/medadic = nullary
monadic = unary
dyadic = binary
triadic = ternary
tetradic = quaternary
…
The various meanings of monad in philosophy, religion, biology, category theory, and functional programming are all derived separately, from its literal denotation of a “unit”. The Haskell term is probably derived from monoid, an algebraic structure equivalent to an additive monad.
No, the J use has nothing to do with the Haskell term. Monadic and dyadic functions are functions of one and two arguments, respectively.
The J terms originate from APL, which is a bit older than Haskell, but I have rarely seen them used like this outside of the APL family.
One example of the use of these terms in a non-APL context is from the book Clean Code, which in the chapter about functions talks about niladic, monadic and dyadic functions.
I want to implement the strict folding functions myself, from within Haskell: Is this possible? I've read that Lisp macros can be used to redefine the language to a massive extent, giving you the power to effectively break out of the functional paradigm whenever you need to and mould it into a personalised paradigm that gets the job done in the neatest way possible. I don't actually know lisp so that may be incorrect.
When you also take into account that in the untyped lambda calculus data types are encoded as functions, I begin to suspect that anything can be encoded as anything else (The brilliant book GEB discusses this in some detail). In that case, representing strict evaluation sounds like it should be easy.
So, how would you go about implementing the following from within haskell?
foldl' = -- ???
foldl1' = -- ???
I suspect it has something to do with Monads and/or Continuation Passing.
How can you implement foldl'? Like this
Haskell provides the seq primitive for adding strictness, and "bang patterns" as well for convenience.
See also: Haskell 2010 > Predefined Types and Classes # Strict Evaluation
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I'm going to be teaching a lower-division course in discrete structures. I have selected the text book Discrete Structures, Logic, and Computability in part because it contains examples and concepts that are conducive to implementation with a functional programming language. (I also think it's a good textbook.)
I want an easy-to-understand FP language to illustrate DS concepts and that the students can use. Most students will have had only one or two semesters of programming in Java, at best. After looking at Scheme, Erlang, Haskell, Ocaml, and SML, I've settled on either Haskell or Standard ML. I'm leaning towards Haskell for the reasons outlined below, but I'd like the opinion of those who are active programmers in one or the other.
Both Haskell and SML have pattern matching which makes describing a recursive algorithm a cinch.
Haskell has nice list comprehensions that match nicely with the way such lists are expressed mathematically.
Haskell has lazy evaluation. Great for constructing infinite lists using the list comprehension technique.
SML has a truly interactive interpreter in which functions can be both defined and used. In Haskell, functions must be defined in a separate file and compiled before being used in the interactive shell.
SML gives explicit confirmation of the function argument and return types in a syntax that's easy to understand. For example: val foo = fn : int * int -> int. Haskell's implicit curry syntax is a bit more obtuse, but not totally alien. For example: foo :: Int -> Int -> Int.
Haskell uses arbitrary-precision integers by default. It's an external library in SML/NJ. And SML/NJ truncates output to 70 characters by default.
Haskell's lambda syntax is subtle -- it uses a single backslash. SML is more explicit. Not sure if we'll ever need lambda in this class, though.
Essentially, SML and Haskell are roughly equivalent. I lean toward Haskell because I'm loving the list comprehensions and infinite lists in Haskell. But I'm worried that the extensive number of symbols in Haskell's compact syntax might cause students problems. From what I've gathered reading other posts on SO, Haskell is not recommended for beginners starting out with FP. But we're not going to be building full-fledged applications, just trying out simple algorithms.
What do you think?
Edit: Upon reading some of your great responses, I should clarify some of my bullet points.
In SML, there's no syntactic distinction between defining a function in the interpreter and defining it in an external file. Let's say you want to write the factorial function. In Haskell you can put this definition into a file and load it into GHCi:
fac 0 = 1
fac n = n * fac (n-1)
To me, that's clear, succinct, and matches the mathematical definition in the book. But if you want to write the function in GHCi directly, you have to use a different syntax:
let fac 0 = 1; fac n = n * fac (n-1)
When working with interactive interpreters, from a teaching perspective it's very, very handy when the student can use the same code in both a file and the command line.
By "explicit confirmation of the function," I meant that upon defining the function, SML right away tells you the name of the function, the types of the arguments, and the return type. In Haskell you have to use the :type command and then you get the somewhat confusing curry notation.
One more cool thing about Haskell -- this is a valid function definition:
fac 0 = 1
fac (n+1) = (n+1) * fac n
Again, this matches a definition they might find in the textbook. Can't do that in SML!
Much as I love Haskell, here are the reasons I would prefer SML for a class in discrete math and data structures (and most other beginners' classes):
Time and space costs of Haskell programs can be very hard to predict, even for experts. SML offers much more limited ways to blow the machine.
Syntax for function defintion in an interactive interpreter is identical to syntax used in a file, so you can cut and paste.
Although operator overloading in SML is totally bogus, it is also simple. It's going to be hard to teach a whole class in Haskell without having to get into type classes.
Student can debug using print. (Although, as a commenter points out, it is possible to get almost the same effect in Haskell using Debug.Trace.trace.)
Infinite data structures blow people's minds. For beginners, you're better off having them define a stream type complete with ref cells and thunks, so they know how it works:
datatype 'a thunk_contents = UNEVALUATED of unit -> 'a
| VALUE of 'a
type 'a thunk = 'a thunk_contents ref
val delay : (unit -> 'a) -> 'a thunk
val force : 'a thunk -> 'a
Now it's not magic any more, and you can go from here to streams (infinite lists).
Layout is not as simple as in Python and can be confusing.
There are two places Haskell has an edge:
In core Haskell you can write a function's type signature just before its definition. This is hugely helpful for students and other beginners. There just isn't a nice way to deal with type signatures in SML.
Haskell has better concrete syntax. The Haskell syntax is a major improvement over ML syntax. I have written a short note about when to use parentheses in an ML program; this helps a little.
Finally, there is a sword that cuts both ways:
Haskell code is pure by default, so your students are unlikely to stumble over impure constructs (IO monad, state monad) by accident. But by the same token, they can't print, and if you want to do I/O then at minumum you have to explain do notation, and return is confusing.
On a related topic, here is some advice for your course preparation: don't overlook Purely Functional Data Structures by Chris Okasaki. Even if you don't have your students use it, you will definitely want to have a copy.
We teach Haskell to first years at our university. My feelings about this are a bit mixed. On the one hand teaching Haskell to first years means they don't have to unlearn the imperative style. Haskell can also produce very concise code which people who had some Java before can appreciate.
Some problems I've noticed students often have:
Pattern matching can be a bit difficult, at first. Students initially had some problems seeing how value construction and pattern matching are related. They also had some problems distinguishing between abstractions. Our exercises included writing functions that simplify arithmetic expression and some students had difficulty seeing the difference between the abstract representation (e.g., Const 1) and the meta-language representation (1).
Furthermore, if your students are supposed to write list processing functions themselves, be careful pointing out the difference between the patterns
[]
[x]
(x:xs)
[x:xs]
Depending on how much functional programming you want to teach them on the way, you may just give them a few library functions and let them play around with that.
We didn't teach our students about anonymous functions, we simply told them about where clauses. For some tasks this was a bit verbose, but worked well otherwise. We also didn't tell them about partial applications; this is probably quite easy to explain in Haskell (due to its form of writing types) so it might be worth showing to them.
They quickly discovered list comprehensions and preferred them over higher-order functions like filter, map, zipWith.
I think we missed out a bit on teaching them how to let them guide their thoughts by the types. I'm not quite sure, though, whether this is helpful to beginners or not.
Error messages are usually not very helpful to beginners, they might occasionally need some help with these. I haven't tried it myself, but there's a Haskell compiler specifically targeted at newcomers, mainly by means of better error messages: Helium
For the small programs, things like possible space leaks weren't an issue.
Overall, Haskell is a good teaching language, but there are a few pitfalls. Given that students feel a lot more comfortable with list comprehensions than higher-order functions, this might be the argument you need. I don't know how long your course is or how much programming you want to teach them, but do plan some time for teaching them basic concepts--they will need it.
BTW,
# SML has a truly interactive
interpreter in which functions can be
both defined and used. In Haskell,
functions must be defined in a
separate file and compiled before
being used in the interactive shell.
Is inaccurate. Use GHCi:
Prelude> let f x = x ^ 2
Prelude> f 7
49
Prelude> f 2
4
There are also good resources for Haskell in education on the haskell.org edu. page, with experiences from different teachers. http://haskell.org/haskellwiki/Haskell_in_education
Finally, you'll be able to teach them multicore parallelism just for fun, if you use Haskell :-)
Many universities teach Haskell as a first functional language or even a first programming language, so I don't think this will be a problem.
Having done some of the teaching on one such course, I don't agree that the possible confusions you identify are that likely. The most likely sources of early confusion are parsing errors caused by bad layout, and mysterious messages about type classes when numeric literals are used incorrectly.
I'd also disagree with any suggestion that Haskell is not recommended for beginners starting out with FP. It's certainly the big bang approach in ways that strict languages with mutation aren't, but I think that's a very valid approach.
SML has a truly interactive interpreter in which functions can be both defined and used. In Haskell, functions must be defined in a separate file and compiled before being used in the interactive shell.
While Hugs may have that limitation, GHCi does not:
$ ghci
GHCi, version 6.10.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> let hello name = "Hello, " ++ name
Prelude> hello "Barry"
"Hello, Barry"
There's many reasons I prefer GHC(i) over Hugs, this is just one of them.
SML gives explicit confirmation of the function argument and return types in a syntax that's easy to understand. For example: val foo = fn : int * int -> int. Haskell's implicit curry syntax is a bit more obtuse, but not totally alien. For example: foo :: Int -> Int -> Int.
SML has what you call "implicit curry" syntax as well.
$ sml
Standard ML of New Jersey v110.69 [built: Fri Mar 13 16:02:47 2009]
- fun add x y = x + y;
val add = fn : int -> int -> int
Essentially, SML and Haskell are roughly equivalent. I lean toward Haskell because I'm loving the list comprehensions and infinite lists in Haskell. But I'm worried that the extensive number of symbols in Haskell's compact syntax might cause students problems. From what I've gathered reading other posts on SO, Haskell is not recommended for beginners starting out with FP. But we're not going to be building full-fledged applications, just trying out simple algorithms.
I like using Haskell much more than SML, but I would still teach SML first.
Seconding nominolo's thoughts, list comprehensions do seem to slow students from getting to some higher-order functions.
If you want laziness and infinite lists, it's instructive to implement it explicitly.
Because SML is eagerly evaluated, the execution model is far easier to comprehend, and "debugging via printf" works a lot better than in Haskell.
SML's type system is also simpler. While your class likely wouldn't use them anyways, Haskell's typeclasses are still an extra bump to get over -- getting them to understand the 'a versus ''a distinction in SML is tough enough.
Most answers were technical, but I think you should consider at least one that is not: Haskell (as OCaml), at this time, has a bigger community using it in a wider range of contexts. There's also a big database of libraries and applications written for profit and fun at Hackage. That may be an important factor in keeping some of your students using the language after your course is finished, and maybe trying other functional languages (like Standard ML) later.
I am amazed you are not considering OCaml and F# given that they address so many of your concerns. Surely decent and helpful development environments are a high priority for learners? SML is way behind and F# is way ahead of all other FPLs in that respect.
Also, both OCaml and F# have list comprehensions.
Haskell. I'm ahead in my algos/theory class in CS because of the stuff I learned from using Haskell. It's such a comprehensive language, and it will teach you a ton of CS, just by using it.
However, SML is much easier to learn. Haskell has features such as lazy evaluation and control structures that make it much more powerful, but with the cost of a steep(ish) learning curve. SML has no such curve.
That said, most of Haskell was unlearning stuff from less scientific/mathematic languages such as Ruby, ObjC, or Python.