Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

>Also I have made use of improper lists in my code, usually when I need to do something with multiple values and I don't want to mess with multiple return values, or if I just want to create a list of tuples for some reason and don't feel like defining an actual struct (can't think of a good example right now...)

I get what you're saying, but isn't this just a shortcoming of the language? It's pretty common to want to write something like

  (define (string-lookup-that-might-fail)
    (if (random) <"string you were trying to look up" nil> <nil "It wasn't there.">))
  (define <some-string error-msg> (string-lookup-that-might-fail))
without wanting to destructure a list or use the call-values hack. That's a bad example, just pretend there were multiple reasons the lookup could fail.

I totally agree about there being pedagogical value in having a simple core. I'm torn between appreciating minimalism and believing that it's ok to introduce complexity to a language that people will use to get things done every day, so long as it's useful complexity and not just historical accident.



Okay yeah I agree with you there. The pain of destructuring cons cells is somewhat mitigated by the various forms that allow pattern matching, but it's still basically the same.

So I think that is actually a good argument for introducing option types (like Haskell's Either a b) into the language. I think Typed Racket has something like this, but in either case you'd want some nice syntax to handle it as well.

Hash tables make this sort of thing fairly easy though (at least in Racket)

(define h #hash{["a" . 3] ["b" . 5]})

(hash-ref h k "failed")

I think most lisps support this right? I remember seeing something similar in pg's bayesian spam filter code.


> So I think that is actually a good argument for introducing option types (like Haskell's Either a b) into the language. I think Typed Racket has something like this, but in either case you'd want some nice syntax to handle it as well.

I need to look into Typed Racket more. Ever since I learned the tiny bit of Haskell that I know, I can't help but think that I need ADTs in my parenthesis.

Right at the top of the docs, though, it says that they designed the Typed Racket type system to provide static typing for existing untyped Racket programs. That makes me wonder what a statically typed lisp that wasn't trying to be backwards compatible would look like. I've heard of Shen and Qi but at first glance they (especially Qi) seemed to be drifting too far from Lispy prefix notation goodness for my taste.

> Hash tables make this sort of thing fairly easy though (at least in Racket)

It doesn't really help if you're trying to look up a string, though, because then you can't differentiate between a "good" string and an error string based on their types. That means it won't help for similar string fetching operations either, like making a network request and expecting either a response or a string indicating that the request timed out, the connection couldn't be made, etc.

According to the docs, you can also pass an error continuation to hash-ref instead of a failure string. That's interesting, though I have to admit I have trouble thinking in those terms.


   #lang typed/racket
Isn't bound by backwards compatibility in the sense [I think] you are implying. What the docs are saying is that modules which enjoy static typing may be called transparently from modules which don't - though as is common with functions in the Math library there may be a performance penalty. The backwards compatibility is that the implementation of static typing does not impede dynamically typed code. Statically typed code works just fine with dynamic type checking.

+ regex-match-event from racket/port might be an alternative to handle network connections than hash table lookup. http://docs.racket-lang.org/reference/port-lib.html#%28def._...


> It's pretty common to want to write something like [. . .] without wanting to destructure a list or use the call-values hack.

This makes no sense. You want feature X (the ability to return multiple values) without having to either (a) pick apart a simple structure containing those values or (b) assign names to those values, letting the runtime system pick them apart (or never stick them together, its choice) for you. So what more do you want?!

You want an option type, right? Well what's an option type? Let's consider Haskell:

data Maybe a = Nothing | Just a

No matter what kind of cleverness you do in memory or whatever, in the end this type is a tuple of two values: (constructor, data). The only special thing going on is that the type system makes sure that (a) constructor == Maybe or constructor == Just, and (b) if constructor == Maybe the data field is meaningless (has no type, can't be read, does not effect equality, whatever).

But it's still a tuple. The cons cell is the exact same thing in the absence of a strict compile-time type system, except for your code example we have it flipped: (data, constructor) (well, actually you seem to have a flipped 'Either String a' going on there, but let's stick with option for now) where data is actual data or nil when meaningless, and constructor is like Just when nil and like Nothing when true (btw, lisp convention is the other way around; second value is true like Just and false/nil like Nothing).

Just like the original linked article, what your complaint boils down to is "non-compile-time-typed systems allow you to do things which would break compile-time-typed systems"—and while I love compile-time typing as much as the next Haskell aficionado, I don't demand compile-time typing in a language [family] whose very essence is its absence.

EDIT: if you could reply and explain what would resolve this "shortcoming of the language", maybe I could understand better what you're trying to say...


> This makes no sense. You want feature X (the ability to return multiple values) without having to either (a) pick apart a simple structure containing those values or (b) assign names to those values, letting the runtime system pick them apart (or never stick them together, its choice) for you.

I was actually aiming for (b) here, just incorporated directly into the normal function call and return syntax. Why shouldn't you be able to return multiple values in registers or on a results stack without "tupling them up"? It's clearly supported by the hardware. By "call-values" (sp) hack, I meant that needing to use "call-with-values" and "values" is the hack. Lua, for instance, lets you (with very clean syntax) return multiple values from a function, and assign the results to separate variables, during which at no point is an intermediate table constructed:

  thing, err = getthing()
  if thing == nil then print(err) return nil end
  do_stuff(thing)
I don't think I got it quite right with my angular brackets, but I was trying to think of a nice, clear syntax for multiple return values that wouldn't look out of place in a Lisp.

Maybe I was being misleading by using an example that resembled option types. I wasn't trying to bring static typing into this, though I am interested in static type systems and curious about static Lisps, it's just that emulating simple option types for error handling purposes is a very common usage for multiple return values in dynamically typed languages (and, cough, static ones like Go that have lame type systems).

> data Maybe a = Nothing | Just a

> No matter what kind of cleverness you do in memory or whatever, in the end this type is a tuple of two values: (constructor, data).

Is that true? "Maybe" seems like the perfect candidate for a nullable pointer representation, or at least some kind of small tag. I'd be surprised if GHC represents Maybe as a full blown tuple.


I still don't see how you're advocating a solution of anything other than call-with-values under a different name.

I don't know Racket itself, so let me switch to common lisp which has the same "problem"... except it comes with a super-simple macro to do exactly what you want, apparently:

(multiple-value-bind (quotient remainder) (floor x y) ;; some code referring to quotient and remainder )

which (excepting perhaps edge-cases I'm not considering, and definitely excepting some error-checking) is just

(defmacro mvb (vars form . body) `(multiple-value-call (lambda ,vars ,body) ,form)

The same thing should work in Racket, modulo syntax and names.

Asking a lisp programmer to do everything with special forms/primitive language elements is like asking a Haskell programmer to do everything in the IO monad. Sure, it's possible, it will work just fine; but... it's just wrong.

By the way, the existence of the #'values function by no means implies there's some data structure being created; indeed, that's the whole point of the #'values construct. Anything you could do with #'values you could do in a list which you then destructured; #'values is to be used when the common-case is to throw away the "structure" and just use each value individually or not at all, like with the return values of #'floor. So in fact #'values is exactly what you want; I'm not sure why it's a "hack" when lisp programmers write it under one name and "the solution" when you write it under a different name.

Ah, it occurs to me that maybe racket doesn't work like this, but in common lisp at least, the transiency of values can be seen in that when you say, e.g., (+ (floor a b) c), the remainder of a/b is thrown away; just the first value is used. That's another huge benefit over structures-to-be-destructured, but obviously doesn't work if the extra values are always important.

The point of my discussion with Maybe is that /even if/ it's just a nullable pointer, conceptually it's essentially a [restricted] tuple. (pointer-is-null,data). There's still two pieces of data there, even if the representation of one of them is cleverly folded into generic object representation or what-have-you. By definition it can't be done with a single piece of data: (Maybe a) is not the same type as (a), for all a. Even if (Maybe Word16), e.g., were represented in a single value at a single place in memory, that value would have to hold more than 16 bits. Then it's viewable as a... tuple, again; one element in 16 bits and the other in the remaining bits.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: